hub v0.68.0 — auth_failed self-heal, consumed_at honesty gauge, wrapper drift (R-39 + R-50b(a))
Completes the hub half of R-39's fleet fix on top of the generation core (c484aa2).
pbsdrheal gains an auth_failed TRIGGER — a new trigger in the existing machine, not a
new machine. A box whose credential PBS rejects escalates to a fresh mint, never a
re-stage (which would re-feed the secret PBS just rejected), through the EXISTING damper:
a 401 flap must not become a secret-minting chain. With the generation stamp this closes
the loop end to end — agent proves the 401, hub re-keys, generation advances, descriptor
hash moves, agent re-consumes.
consumed_at honesty gauge: a staged secret still unconsumed past a 15-minute grace while
the box reports `applied` is surfaced with its own event. That is the exact 2026-07-18
fingerprint and a disagreement no single tier can see alone. Deliberately a SURFACE, not
a heal — auto-re-issuing on it would mint a second secret on top of an unconsumed one,
which is the mint/consume race R-39(a) already recorded. One event per distinct report,
and an honestly-stuck box does not double-report (its unconsumed secret is the symptom
being healed, not a contradiction).
R-50b(a): ArtifactManifest.WrapperSHA256 + operator field + host-page drift surface. The
PBS wrapper is root-owned 0755 and the pinned sudoers vector, yet installed unversioned
from raw/branch/main and absent from every manifest. Agents >=0.91.0 report the installed
hash; a mismatch is surfaced. An unknown on EITHER side reads as quiet, never as drift —
lighting every host amber on rollout day is how a warning becomes background noise. The
delivery channel itself stays R-50b(b)/(c).
Compatibility unchanged: safe for 0.90.0 agents (unknown JSON key dropped); the re-arm
and auth-honesty guarantees need agent >=0.91.0, so MinAgent moves only after the fleet
has self-updated.
Tests: auth_failed escalate/debounce/recovery-forgets-streak; honesty gauge incl. grace
window, the restage edge (consumed_at deliberately NULLed), consumed-never-alarms, and
honest-stuck-no-double-report; wrapper drift incl. both unknown directions. Red-proof run
at the assertion level: removing the auth_failed arm fails the escalation tests with
reissues=0.
This commit is contained in:
@@ -1,5 +1,69 @@
|
||||
# Felhom Hub — Changelog
|
||||
|
||||
## v0.68.0 — a credential re-issue finally re-arms the box (R-39 fleet fix) + wrapper drift is visible (R-50b(a)) (2026-07-21)
|
||||
|
||||
**Coupling, stated honestly: this release is SAFE for agents at 0.90.0** — the new descriptor field
|
||||
is an unknown JSON key to them; they drop it and behave exactly as today (inert, not breaking).
|
||||
**The re-arm and auth-honesty guarantees require agent >= 0.91.0.** Raise MinAgent to 0.91.0 only
|
||||
after the fleet's agents have self-updated.
|
||||
|
||||
### The defect (R-39, fleet half)
|
||||
|
||||
An ep0 credential re-issue re-keys the **secret of an existing token**. `token_id`, `fingerprint`,
|
||||
`datastore` and `namespace` all come back byte-identical — only the side-table `host_pbs_secrets`
|
||||
row rotates. The agent's re-apply trigger is a change in the **descriptor content hash**
|
||||
(`felhom-agent internal/pbsdr/manager.go` `descriptorHash`). Same hash → the converged agent
|
||||
short-circuits → the fresh secret is never consumed → the box keeps presenting a revoked credential
|
||||
→ **401 forever, while both tiers report `applied`**. Proven on the N100 2026-07-18: the agent's
|
||||
`consumed-failed.json` hash was byte-identical to the `marker.json` written two minutes before the
|
||||
re-issue.
|
||||
|
||||
### The fix
|
||||
|
||||
- **`host_pbs_secrets.generation`** — a monotonic per-host counter advanced by every fresh **mint**
|
||||
and by nothing else, stamped into the descriptor as `secret_generation`. That is now the only
|
||||
field a re-key moves, and it is what re-arms the agent.
|
||||
- **A re-stage deliberately does not advance it**: it re-arms the *same* secret, the descriptor
|
||||
content genuinely has not changed, and a bump would cause a pointless agent refetch loop.
|
||||
- `omitempty` is load-bearing — emitting a zero into every pre-existing descriptor would itself be
|
||||
a fleet-wide spurious re-apply.
|
||||
- **Deviation from the spec, deliberate:** the brief said to reuse "the new row's id … no schema
|
||||
change". There is no row id — the table is keyed by `host_id` and UPSERTed last-write-wins, so a
|
||||
new row never exists, and `created_at` collides for two mints in one second. An additive counter
|
||||
column (existing idempotent `ALTER TABLE` idiom) is the only monotonic source available.
|
||||
- **`pbsdrheal` gains an `auth_failed` trigger** — a NEW trigger in the existing machine, not a new
|
||||
machine. A box whose credential PBS rejects is escalated to a fresh mint (never a re-stage: that
|
||||
re-feeds the secret PBS just rejected), through the **existing damping** — a 401 flap must not
|
||||
become a secret-minting chain. This closes the loop end to end: agent proves the 401 → hub re-keys
|
||||
→ generation advances → descriptor hash moves → agent re-consumes.
|
||||
- **`consumed_at` honesty gauge** — a staged secret left **unconsumed** past a 15-minute grace while
|
||||
the box reports `applied` is surfaced loudly with its own event. This is the exact July-18
|
||||
fingerprint and a disagreement **no single tier can detect alone**. Deliberately a *surface*, not
|
||||
a heal: auto-re-issuing here would mint a second secret on top of an unconsumed one — the
|
||||
mint/consume race R-39(a) already recorded.
|
||||
- **Corrected a comment that stated a falsehood**: `ReissuePBSDR` claimed it refreshed the descriptor
|
||||
"with the NEW token_id/fingerprint". That is false for a re-key, and believing it is why nobody
|
||||
expected the descriptor to come back identical.
|
||||
|
||||
### R-50b(a) — wrapper drift is answerable
|
||||
|
||||
`ArtifactManifest.WrapperSHA256` + an operator field. Unlike the agent binary and the golden, the
|
||||
PBS-DR wrapper is installed from `raw/branch/main` — unversioned, unpinned, absent from every
|
||||
manifest — yet it is root-owned 0755 and the pinned sudoers vector. Agents (>= 0.91.0) report the
|
||||
installed file's hash and the host page surfaces a mismatch. **An unknown on either side reads as
|
||||
quiet, never as drift** — lighting every host amber on rollout day is how a warning becomes noise.
|
||||
This does not fix the delivery channel; that stays R-50b(b)/(c).
|
||||
|
||||
### Tests
|
||||
|
||||
Store-level generation monotonicity, per-host isolation and restage-leaves-it-alone; descriptor
|
||||
byte-change, `omitempty` and sibling-key round-trip; a **flow-level** test driving `ReissuePBSDR`
|
||||
against a fake that models a real re-key; `auth_failed` escalate/debounce/recovery; the honesty
|
||||
gauge incl. its grace window, the restage edge, the consumed case and the honest-stuck case; wrapper
|
||||
drift incl. both unknown directions. **Two red-proofs run at the assertion level** (not the
|
||||
compiler): removing the generation stamp makes the flow test fail with both byte-identical blocks
|
||||
printed; removing the `auth_failed` arm makes the escalation tests fail with `reissues=0`.
|
||||
|
||||
## v0.67.0 — the hub stops keeping things to itself: auto-minted self-bind link, post-RESET staleness, unprovisioned-offsite warning (2026-07-18)
|
||||
|
||||
Four small items, each one a case where the hub already knew something and said nothing. Green:
|
||||
|
||||
Reference in New Issue
Block a user