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

161 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.0``0.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 --fast`**all 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.md`** — **R-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.md`** — **new §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.