Files
felhom.eu/REPORT.md
T
admin 0dbd954fec
gates / gates (push) Successful in 7s
docs: R-196 closed, R-204 items 1-3 closed, item 4 open (R-193)
- OPEN-ITEMS: R-196 CLOSED; R-204 items 1-3 CLOSED with item 4 named and
  its dependency stated. Header restates that R-202, the 1.2 GB ciphertext
  deletion and R-198's still-unit-proven retention all REMAIN OPEN.
- capability map: the recovery row keeps its 'with a person present'
  qualifier, names which crutch remains, and cites the three now gone.
- 07-backup-architecture: new 7.0 - what a customer can and cannot do
  ALONE, the four steps in a table with status. This is the section a
  future reader will use to answer that question.
- CONTEXT: standing ruling S-32, superseding S-31 steps 2-5.
- STATUS: rewritten to one screen per its own header; removes a corrupted
  half-overwritten section left from the drill session.
- ROADMAP: R-196 and R-204 collapsed.
2026-08-05 07:37:35 +02:00

11 KiB
Raw Blame History

REPORT — R-196 / R-204 item 2 (hub v0.95.0), 2026-08-05

A re-issue no longer marks a healthy escrow stale. One behaviour change, one register closed, and the coverage claim proved rather than assumed. The controller's half of R-204 (items 1 and 3) is felhom-controller v0.198.0.

1. Baselines, re-read on arrival

Repo Expected Found
felhom.eu 2a7ac03c4726 / hub v0.94.0 (deployed felhom-hub:0.94.0) exact match, tree clean, HEAD == origin/main

§3.2's landmark had DRIFTED, and the drift changed the work. The task described offsite.go:222-231 under a known-consequence comment saying the mark was made on a false premise. That comment had already been rewritten by the R-196 comment-correction commit, and the version on main gave a non-false ground for the mark: "the box's re-apply may mint a fresh repository password (it does exactly that whenever <DataDir>/offbox/repo_password is absent — the guest-rebuild shape)". So the question was no longer "delete a comment's lie" but "is the shape it guards actually covered elsewhere?" — which is Scenario D, and §8.2 says to stop and report if it is not. It is; §3 below is the evidence.

2. What changed

offsite.ReissueCredentials no longer calls MarkEscrowStale and no longer emits the escrow_stale event. offsite_reissued is untouched and still fires on every re-issue. The known-consequence comment is rewritten to record what was done, when, and why — with the disagreement below stated in it rather than absorbed.

What the mark actually cost, established mechanically rather than asserted (this is why it was a blocker and not a nit):

  1. stale_at set → GetEscrowStatusForCustomer withholds restic_pw_sha256 from the report ACK.
  2. With no hash, the controller's SLICE-3 auto-confirm returns early and cannot flip pending → escrowed.
  3. OffboxRunnable() = OffboxConfigured() && EscrowState == "escrowed"every off-site backup refused, indefinitely, on a box whose repository key was never in doubt.
  4. The customer is told to re-run the recovery ceremony — which mints a new recovery code and supersedes the sealed blob. During a recovery that is the one act that would have destroyed the key just recovered.

3. Scenario D — the evidence that the removed marking is covered

The mark was precautionary and aimed at ONE shape: a re-issue followed by a box that mints a fresh repository password (the guest-rebuild shape, where offbox/repo_password is absent). That shape is measured in two independent places, and the mark was blinding one of them:

  • Continuous, box-side — the real coverage. report.EscrowAutoConfirmer.reconcileEscrowed (controller) compares the ACK's sealed restic_pw_sha256 against the box's CURRENT local repo password on every report ACK, raising the stale flag, the customer card and the „create a new recovery code" CTA on a mismatch. That is a measurement, not a guess, and it is continuous rather than edge-triggered. Pinned by the controller's TestEscrowStale_MismatchWarnsOnceAndFlags — re-run green this session. And step 1 above was blinding it: a stale flag empties the very hash that comparison needs, so the box could only reach the hash-LESS branch and report "the hub's current blob carries NO password hash" — which is false. Removing the mark restores the true signal.
  • Edge-triggered, hub-side. R-197's offsite_repo_key_changed fires from api.maybeEmitRepoKeyChanged on a proven hash difference across a supersession and pages the operator. Red-proved: removing the maybeEmitRepoKeyChanged call from handleHostEscrowPut made TestEscrowPut_ChangedRepoKey_RaisesSignal fail with "the repository key demonstrably changed and NO signal was raised", while the two silence tests stayed green.

Disagreement recorded, per the R-96 standing rule. Scenario D as written asks that a real key change also "mark the escrow stale". It must not, and nothing was changed to make it. The hub learns of a real change at the instant a supersession seals the new password — i.e. when the escrow is at its freshest. Marking it stale there would ask the customer for a ceremony to fix the ceremony that just ran. The correct consequence at that instant is the operator alarm, which is exactly what R-197 already does. This is recorded in the code comment, the CHANGELOG and OPEN-ITEMS, not only here.

4. MarkEscrowStale is kept with no caller — deliberately

Per task §5 it was not to be modified, and it is not deleted either. The stale_at flag remains live and correct — read by the ACK, the operator config card and the PBS-DR view — and the right way to set it is a future EVIDENTIAL caller that has measured a key change rather than guessed at one. Its doc comment now says so plainly instead of naming a caller that no longer exists, and TestEscrowStaleMechanism_StillWithholdsAndClears keeps the mechanism from decaying to inert while nothing writes it (the seam-built-but-never-wired shape, in reverse).

The schema comment and EscrowStatus.Stale's comment were corrected the same way — each of the three previously asserted a writer that is now gone.

5. Files modified

File Change
hub/internal/offsite/offsite.go the pessimistic MarkEscrowStale + escrow_stale event removed; comment rewritten to record the change, the coverage and the disagreement
hub/internal/offsite/offsite_test.go TestReissue_InvalidatesEscrow replaced in place by its exact inverse TestReissue_DoesNotMarkAHealthyEscrowStale; new TestEscrowStaleMechanism_StillWithholdsAndClears
hub/internal/store/store.go three comments corrected (MarkEscrowStale, the stale_at schema note, EscrowStatus.Stale) — each had named a writer that no longer exists
manifests/hub.yaml image tag 0.94.00.95.0
hub/CHANGELOG.md, CONTEXT.md, STATUS.md, documentation/… v0.95.0 entry; ruling S-32; the register and architecture updates below

Commits on main: d1a8edb (behaviour + tests + comments) · 5c7d671 (CHANGELOG) · 975a690 (manifest bump).

Deploy: built + pushed felhom-hub:0.95.0, bumped manifests/hub.yaml, pushed, then a deliberate ArgoCD hard-refresh + sync (auto-sync stays off; no kubectl set image anywhere). Result: app felhom Synced / Healthy, deploy/hub rolled out, running image gitea.dooplex.hu/admin/felhom-hub:0.95.0, startup log clean (offsite provisioning, pool-box checker, PBS-DR reconciler and all six host checkers initialised; Listening on :8080).

6. Tests and red-proofs

Green gate: cd hub && go build ./... && go vet ./... && go test ./...rc=0. python3 scripts/repo_gates.py --fastall five gates OK.

Test Result Red-proof — what was mutated Outcome
TestReissue_DoesNotMarkAHealthyEscrowStale (C) PASS restored the pessimistic MarkEscrowStale block in ReissueCredentials, exactly as it was FAILED"a re-issue marked a HEALTHY escrow stale…"
TestEscrowStaleMechanism_StillWithholdsAndClears PASS same mutation stayed GREEN — correctly: the mutation restores a caller, not a break in the mechanism. That split is the evidence Scenario C's assertion is about the caller and not the flag.
TestEscrowPut_ChangedRepoKey_RaisesSignal (D) PASS removed the maybeEmitRepoKeyChanged call from handleHostEscrowPut FAILED"the repository key demonstrably changed and NO signal was raised"
TestEscrowPut_UnchangedRepoKey_Silent, TestEscrowPut_HashlessSupersession_NoSignal PASS same stayed green — the detector's silence branches are independent
controller TestEscrowStale_MismatchWarnsOnceAndFlags PASS — (cited as the continuous-coverage pin)

Scenario C asserts the consequence (the ACK still carries the hash, so auto-confirm can proceed) rather than the mechanism (that a function was not called), because the hash is what the drill's blockage actually turned on. It also asserts that offsite_reissued still fires — removing a false alarm must not remove the true notice.

7. Live validation

Per task §12 point 5, a live re-issue was NOT run, and must not have been on demo-hp — it would have been a credential rotation on the box holding the drill's evidence. Part 2 is proved by test and by the deployment being live and healthy. The controller-side halves of R-204 were validated live and are reported in felhom-controller/REPORT.md.

8. Register and documentation

  • OPEN-ITEMS.mdR-196 → CLOSED (hub v0.95.0); R-204 → items 13 CLOSED, item 4 OPEN (→ R-193) with its dependency named. The header block is updated and states explicitly that R-202, the ~1.2 GB orphaned-ciphertext deletion and R-198's retention (still UNIT-PROVEN ONLY — the second deliberate wipe is the next item) all remain open, so nothing is presumed closed by association. R-201 is recorded as PASSED. R-204 is still the highest ID; nothing new was minted.
  • architecture/00-capability-map.md — the recovery row now says three of the four crutches are gone, names the fixes and their evidence, and states that item 4 (R-193) is the one that remains and is why the row keeps its "with a person present" qualifier. The R-199 back-pointer was already present on the adjacent key-recovery row (added when that row was last corrected), so it needed no further action — verified, not assumed.
  • architecture/07-backup-architecture.mdnew §7.0, "What a customer can and cannot do ALONE": the four steps in a table with what each cost and its status, plus the honest current answer. This is the section a future reader will use to answer the question.
  • documentation/backlog/ROADMAP.md — R-196 and R-204 collapsed per the coupling rule.
  • CONTEXT.md — new standing ruling S-32, which supersedes S-31's steps 25 and carries the blinding mechanism, the fail-closed rule and the "no TTL" reasoning forward.
  • STATUS.md — rewritten to one screen (191 → ~90 lines) per its own header. It also had a corrupted, half-overwritten "What we're working on" section left from the drill session, which is now gone. Next item stated as the retention drill.

CI: felhom.eu runs 154 (5c7d671, code) and 155 (975a690, manifest) — both success. --no-verify was not used; the pre-push gate ran and passed on every push.

9. Observations — noticed, NOT acted on

  • allowedEventTypes still lists escrow_stale, which after this change has no producer in either repo. It is inert rather than harmful; removing an allowlist entry is a behaviour change and is out of this session's scope.
  • MarkEscrowStale is now dead code by call-graph. Kept on purpose (§4 above) — but if a future session's linter or cleanup pass proposes deleting it, the reason it exists is in its doc comment and in the test that exercises it.
  • / on DooPlex is at 86% used — under the 90% abort line, but worth watching before large builds.