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>
62 lines
3.8 KiB
Markdown
62 lines
3.8 KiB
Markdown
# 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.
|