v0.83.0: observability pass — always-DEBUG capture ring + GET /debug/logs + heartbeat log-pull + gap-fill sweep

Capture layer: applog.New returns (logger, Ring) — slog fan-out, stderr at the
configured level, ~1000-entry ring fixed at LevelDebug (remote diagnostics
without a config flip). GET /debug/logs (token-authed, ?raw=1) + request-level
DEBUG middleware. Heartbeat log-pull mirrors the report logtail pattern:
envelope log_tail_requested -> next heartbeat carries log_tail (128KB cap,
consume-once, failed-push retry proven). Gap-fill sweep over netverify/
netstorage/netmount/signedjobs/selfupdate/disks/controller-swap/desired/loop.
Red-proofs: ring-at-emit-level FAILs capture test; drain removed FAILs
consume-once; dropped phase line FAILs the S7 log-sequence smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 16:24:07 +02:00
parent 461eaf42c1
commit cb692f8788
20 changed files with 902 additions and 29 deletions
+9 -1
View File
@@ -319,6 +319,7 @@ func (s *Server) handleDiskAssign(w http.ResponseWriter, r *http.Request, vmid i
writeErr(w, http.StatusBadRequest, "assign failed: "+err.Error())
return
}
s.logger.Info("local-api: disk assigned (host mount ensured)", "vmid", vmid, "where", req.Where)
writeOK(w, map[string]any{"vmid": vmid, "assigned": req.Where})
}
@@ -372,6 +373,8 @@ func (s *Server) handleDiskEject(w http.ResponseWriter, r *http.Request, vmid in
return
}
}
s.logger.Info("local-api: drive ejected (bind detached, raw mount kept)",
"vmid", vmid, "where", req.Where, "dependent_guests", len(dependents))
writeOK(w, map[string]any{"vmid": vmid, "ejected": req.Where, "dependent_guests": dependents})
}
@@ -440,6 +443,8 @@ func (s *Server) handleDiskDecommission(w http.ResponseWriter, r *http.Request,
// the RAW /mnt/<name> host mount — that would orphan the drive (a non-removable SATA drive doesn't get
// re-plugged), so a one-click re-enroll (H3) could not re-bind it. The soft decommission marker blocks
// scheduling; physical removal is the separate "remove from system" action. NEVER format/mkfs here.
s.logger.Info("local-api: drive decommissioned (logical retire, data untouched)",
"vmid", vmid, "where", req.Where, "durable_id", id, "dependent_guests", len(dependents))
writeOK(w, map[string]any{"vmid": vmid, "decommissioned": req.Where, "dependent_guests": dependents})
}
@@ -920,7 +925,10 @@ func (s *Server) ReassertGuestBinds(ctx context.Context) {
}
}
}
for vmid, ids := range s.guestBinds.Guests() {
guests := s.guestBinds.Guests()
s.logger.Debug("reconcile: guest-bind re-assert pass",
"guests", len(guests), "resolved_mounts", len(mountByDurable))
for vmid, ids := range guests {
for _, id := range ids {
// Intent-aware (B2, load-bearing): NEVER bind a drive that is not currently `enrolled` — an
// ejected or decommissioned drive must not auto-rebind, even if still host-mounted. A nil