8ecf8929fb
The control envelope becomes live: the agent caches the hub's desired-state +
generation and re-fetches GET /hosts/{id}/desired-state only when the
generation advances. A new internal/desired Syncer maps the wire shape into a
reconcile.CachingProvider feeding the engine; benign deltas reconcile, an
explicit guest decommission is gated pending_signature (exec is 10B). Adds the
DesiredStateResponse/WireDesiredState wire types + Client.FetchDesiredState +
the loop EnvelopeObserver seam. Cross-repo golden (envelope + desired-state)
byte-identical with the hub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.8 KiB
3.8 KiB
REPORT — slice 10A (agent half): hub desired-state serving — the "Down" channel (v0.15.0) (2026-06-10)
Overwrite-latest report. Cumulative history: CHANGELOG.md.
What was implemented
The agent half of slice 10A: activate the control envelope as the live Down channel and feed a hub-backed desired-state into the reconcile engine. Pairs with hub v0.9.0.
The control loop (now live)
report (heartbeat) → control envelope → (DesiredGeneration advanced past cache? fetch desired-state) → reconcile benign / gate destructive → report. The heartbeat stays light; the heavy desired-state is fetched only on a generation advance.
internal/reconcile
DesiredGuest.Decommission— the canonical destructive desired-state delta (an EXPLICIT flag, never "absent from the list", so a partial hub list can't mass-destroy). Planner emitsActionDecommission→ClassDecommission→ Destructive → the gate refusespending_signature.Reconcilenow counts apending_signaturegate refusal asResult.Pending(expected, INFO-logged), not a failure; any other refusal stays a real failure.ActionDecommissionhas no executor (10B) — a defensive guard refuses to run it.CachingProvider— thread-safe DesiredState + generation cache (Desired/Update/Generation); the production provider, replacingEmptyProviderin the daemon engine. Empty until the hub serves intent → cold-start is a live no-op (unchanged behaviour).
internal/hub
ControlEnvelopefields are now active. New wire typesDesiredStateResponse+WireDesiredState(guests + forward-compatrestore_directive(10D) /pbs_namespace/ opaquestorage_manifest+backup_policy) +WireDesiredGuest. NewClient.FetchDesiredState(GET/api/v1/hosts/{host_id}/desired-state, self-scoped to the client's own host). NewEnvelopeObserverloop seam +SetEnvelopeObserver(hub does not import reconcile/desired).
internal/desired (new) + wiring
Syncer— implementshub.EnvelopeObserver; fetches on a generation advance, maps wire→domain, updates theCachingProvider. Caches the fetched generation (race-robust); a fetch failure keeps the last-known state.restore_directivecarried + logged, not acted on (10D). Wired incmd/felhom-agent: provider → engine, syncer → loop.
Tests (all green)
- reconcile: benign applied + destructive decommission gated pending (not executed); Plan decommission-only + classifies Destructive; CachingProvider update/isolation.
- desired: fetch-once-on-advance / no-refetch-on-unchanged / fetch-failure-keeps-cache / caches-the-fetched-generation.
- hub:
FetchDesiredStatepath+auth+decode (incl.restore_directive) + typed 403; loop notifies the observer + adoptsPollIntervalSeconds, skips the observer on a report error. - cross-repo golden (
desired-state+control-envelope) decode + key-set guard, byte-identical with felhom.eu/hub.go test ./...green.
Versioning / docs
- Version
0.14.0 → 0.15.0;CHANGELOG.mdupdated. Doc 03 §4 (control loop live) + §9 (slice table: 10A done, 10B/10C/10D pending) updated.
Out of scope (per the task)
- Signed-op execution (verify + run the gated destructive op) → 10B (10A marks it pending only).
- Restore-mode / re-enroll consumption (a new box's first directive) → 10D; 10A serves already-authenticated hosts only.
Pending
- Live validation on the demo: build+deploy agent v0.15.0 + hub v0.9.0; admin-set a desired-state
with a benign + a decommission delta → generation bumps → agent fetches → reconciles benign + gates
the decommission; change
poll_interval_seconds; confirm a host can't fetch another host's state.