agent v0.86.0: DR-tier-by-default — capability inactive state (GatedBy/GateActive, pbsdr gate via DRConfigured) + F-3 root-run provision parent ownership

Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
2026-07-12 20:06:27 +02:00
parent bcb8dad2aa
commit c20814e6c2
11 changed files with 403 additions and 148 deletions
+16
View File
@@ -125,6 +125,22 @@ func (m *Manager) Status() *hub.PBSDRStatus {
return m.status
}
// DRConfigured reports whether the DR tier is configured ON for this box — the capability
// prober's GatePBSDR answer (v0.86.0). True when the last-seen descriptor was enabled (any live
// status except "disabled"), or, before the first desired-state fetch of this process, when a
// previously-converged marker exists (so an applied box never flaps to inactive across an agent
// restart). False = no descriptor ever / descriptor disabled → healthy pbsdr capabilities report
// "inactive (disabled by configuration)" instead of ok.
func (m *Manager) DRConfigured() bool {
m.mu.Lock()
st := m.status
m.mu.Unlock()
if st != nil {
return st.State != "disabled"
}
return m.loadMarker() != nil
}
// descriptorHash is the idempotency key: sha256 of the canonical (struct-ordered) JSON.
func descriptorHash(b *hub.WirePBSDR) string {
j, _ := json.Marshal(b)
+40
View File
@@ -362,6 +362,46 @@ func TestOldHubAndDisabledCompat(t *testing.T) {
}
}
// DRConfigured (v0.86.0) — the capability prober's GatePBSDR answer. Walks the full lifecycle:
// unconfigured → enabled(applied) → restart(marker only) → disabled. Red-proof partner: make
// DRConfigured return status!=nil (ignore the "disabled" state) → the disabled case fails.
func TestDRConfigured_Lifecycle(t *testing.T) {
r := &fakeRunner{}
st := &fakeStorage{found: false, active: []bool{true}}
c := &fakeConsumer{secret: "S"}
m, _ := newTestManager(t, r, st, c)
// Fresh box, nothing fetched: not configured.
if m.DRConfigured() {
t.Fatal("fresh manager reports DR configured")
}
// Old hub / no descriptor: still not configured.
m.Apply(context.Background(), true, nil)
if m.DRConfigured() {
t.Fatal("nil-block reports DR configured")
}
// Enabled descriptor applied: configured.
m.Apply(context.Background(), true, testBlock())
if s := m.Status(); s == nil || s.State != "applied" {
t.Fatalf("precondition: status = %+v, want applied", s)
}
if !m.DRConfigured() {
t.Fatal("applied box reports DR NOT configured")
}
// Agent restart (fresh manager over the same state dir): the persisted marker must answer
// BEFORE the first desired-state fetch — an applied box never flaps to inactive at boot.
m2 := NewManager(r, st, c, filepath.Dir(m.stateDir), "/etc/pve/priv/storage", "",
slog.New(slog.NewTextHandler(io.Discard, nil)))
if !m2.DRConfigured() {
t.Fatal("restarted manager (marker on disk) reports DR NOT configured")
}
// Operator turns the tier OFF: descriptor disabled wins over the stale marker.
m2.Apply(context.Background(), true, &hub.WirePBSDR{Enabled: false, StorageID: "felhom-pbs"})
if m2.DRConfigured() {
t.Fatal("disabled descriptor still reports DR configured")
}
}
// TestWireFieldNames pins the cross-repo descriptor contract (hub/internal/web/pbsdr.go
// pbsDRDescriptor): the exact JSON the hub writes must land in WirePBSDR field-for-field.
func TestWireFieldNames(t *testing.T) {