Files
felhom.eu/REPORT.md
T
admin e54f882e70 slice 10A: hub desired-state serving + signed-jobs queue (Down channel) (hub v0.9.0)
Serve operator intent to authenticated hosts: PUT /admin/hosts/{id}/desired-state
(global key) bumps desired_generation; GET /hosts/{id}/desired-state + /jobs are
per-host self-scoped; the host-report envelope now carries the real generation +
has_signed_ops. New signed_jobs table + store methods. Desired-state stored/served
opaquely (agent owns the schema). Cross-repo golden (envelope + desired-state)
byte-identical with felhom-agent; doc 03 §4/§9 updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 19:03:14 +02:00

3.0 KiB

felhom.eu — task reports

Overwrite this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in hub/CHANGELOG.md.


REPORT — Slice 10A (hub half): desired-state serving — the "Down" channel (hub v0.9.0) (2026-06-10)

Type

TASK (CC-implemented). The hub half of slice 10A. Pairs with felhom-agent v0.15.0.

What changed (hub)

The hub now serves operator intent down to already-authenticated hosts; the control envelope stops returning placeholders and carries the host's real generation + signed-jobs flag.

Store (internal/store)

  • New signed_jobs table (per-host opaque signed-op blob queue). New methods: SetHostDesired (set desired-state + atomically bump desired_generation), EnqueueSignedJob / GetSignedJobs / CountSignedJobs. The hosts table's previously-inert desired_json / desired_generation columns are now live.

API (internal/api)

  • PUT /api/v1/admin/hosts/{id}/desired-state (global key) — set + bump generation; body stored + served opaquely (validated only as well-formed JSON — the agent owns the schema).
  • GET /api/v1/hosts/{id}/desired-state (per-host key, self-scoped) — {generation, desired_state}; host A's key cannot read host B (403); global key may read any.
  • GET /api/v1/hosts/{id}/jobs (per-host key, self-scoped) — serves the host's pending opaque signed-op blobs, oldest first (verify+execute is 10B).
  • POST /api/v1/admin/hosts/{id}/jobs (global key) — enqueue a pre-signed opaque blob (the hub holds no signing key).
  • The host-report control envelope now reports the real desired_generation + has_signed_ops, degrading safely to defaults on a store error.

Tests (green)

  • admin-set bumps the generation + serves the latest body; global-key-only (per-host 403, malformed 400, unknown host 404); GET /desired-state self-scoped (A→B 403, global any, no-token 401); envelope carries generation + has_signed_ops flips on enqueue; GET /jobs self-scoped oldest-first; cross-repo golden round-trip (set → fetched back unchanged), byte-identical with felhom-agent.

Docs

  • Doc 03 §4 (control loop live: heartbeat → envelope generation/jobs → fetch-on-change → reconcile benign / gate destructive) + §9 slice table (10A done; 10B signed-op execution / 10C escrow consumption / 10D DR capstone pending; the restore_directive field exists now, consumed in 10D).

Deferred / out of scope

  • Signed-op execution + signature verification → 10B (10A only serves the queue + flag).
  • Restore-mode / re-enroll consumption (a new box's first directive) → 10D; 10A serves already-authenticated hosts only. Rich desired-state editing UX → doc-05 (10A's admin-set is minimal).

Pending

  • Build + deploy hub v0.9.0 (+ agent v0.15.0) and live-validate against the demo host (admin-set benign+destructive → generation bump → agent fetch → reconcile/gate; self-scope refusal).