# 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](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 emits `ActionDecommission` → `ClassDecommission` → Destructive → the gate refuses `pending_signature`. - **`Reconcile`** now counts a `pending_signature` gate refusal as **`Result.Pending`** (expected, INFO-logged), not a failure; any other refusal stays a real failure. `ActionDecommission` has **no executor** (10B) — a defensive guard refuses to run it. - **`CachingProvider`** — thread-safe DesiredState + generation cache (`Desired`/`Update`/ `Generation`); the production provider, replacing `EmptyProvider` in the daemon engine. Empty until the hub serves intent → cold-start is a live no-op (unchanged behaviour). ### `internal/hub` - `ControlEnvelope` fields are now active. New wire types **`DesiredStateResponse`** + **`WireDesiredState`** (guests + forward-compat `restore_directive` (10D) / `pbs_namespace` / opaque `storage_manifest`+`backup_policy`) + **`WireDesiredGuest`**. New **`Client.FetchDesiredState`** (GET `/api/v1/hosts/{host_id}/desired-state`, self-scoped to the client's own host). New **`EnvelopeObserver`** loop seam + `SetEnvelopeObserver` (hub does not import reconcile/desired). ### `internal/desired` (new) + wiring - **`Syncer`** — implements `hub.EnvelopeObserver`; fetches on a generation advance, maps wire→domain, updates the `CachingProvider`. Caches the **fetched** generation (race-robust); a fetch failure keeps the last-known state. `restore_directive` carried + logged, not acted on (10D). Wired in `cmd/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: `FetchDesiredState` path+auth+decode (incl. `restore_directive`) + typed 403; loop notifies the observer + adopts `PollIntervalSeconds`, 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.md` updated. 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.