All five layers gone, each verified with a positive control that must
survive and does:
VM 324 + 4 disks -> absent (VM 300 drill-r50 remains)
hub: 13 tables at 0, incl. BOTH escrow tables (demo-felhom/demo-hp/peti remain)
Storage Box u629488-sub4 -> gone (sub1/2/3 remain)
ep0 PBS ns finalwalk -> gone (demo-felhom, demo-hp remain)
WireGuard 10.77.0.5 -> gone from the LIVE wg show on ep0, not just
the hub DB (.2/.3/.4/.250 remain)
14.06 GiB reclaimed against 15 G measured before deletion.
R shredded with a planted-copy control: plant -> search finds both ->
shred -> the same search finds 0. The zero was not believed until the
instrument was proven.
R-244 (NEW): a FULL census after the cascade logged COMPLETE full teardown
found 61 rows still matching finalwalk. Four sources are deliberate
provenance; the fifth, app_log_issues (29 rows), is NOT covered by the
residue purge - and it is systematic: c11 40, rewalk 20, part4 24 still
present from the 2026-08-06 teardown, whose ledger recorded zero
occurrences. That claim used a narrower query than a census and does not
hold; the correction is recorded in both the prior ledger and the register
rather than the measurement quietly redone.
No secret material is involved. The table is a fleet-wide aggregate: 12 of
the 29 rows are finalwalk-only orphans, 17 are shared with LIVE customers
and must be de-referenced, not deleted - very likely why the leg was never
written. Not fixed; a cascade change needs its own red-proof.
Lesson, and it is the reusable part: a per-table absence query is not a
census.
12 KiB
REPORT — R-241 spike · golden 0.205.0 · finalwalk teardown (2026-08-07)
A sibling report: REPORT.md is overwritten per-session and a parallel session shares this clone.
Three parts, in the order the venue's perishability required. The spike read VM 324 before anything else touched the fleet; the bake and the vouch followed; the teardown went last.
1. THE SESSION'S ANSWER — R-241 is a MINTING defect, not a screen-predicate defect
This reverses the fix. The recovery screen was telling the truth: there genuinely was nothing recoverable under the key the box held, because the box minted that key itself, over the top of a sealed package it already knew the hub was holding for it. Mending the predicate would have papered over a box quietly making its own backup history unopenable.
Three measurements, taken from the venue this session, not copied from last night's journal:
WriteOffboxSecrets(offbox.go:411) mints on ONE input — does the file exist. No settings read at all, while its two neighbours in the same file,OffsiteRecoveryOffer()(:1412) andneedsOffsiteCredential()(:1377), both consultGetHubEscrowIdentityPresent(). The same fact is available on three paths and used on two.- That flag was not merely available — it was the precondition of the chain that reached the
minting. The 5-minute retry job logs only when
RetryIfDeclaredfires, which requires the declaration, which requires the flag. The venue loggedcredential retry: … (the box still declares a need; retrying)at 02:48:03Z and five times after — thirty minutes and six ticks before the mint at 03:18:06Z. - The box computed the right answer and threw it away. At 03:28:03Z — thirty-five minutes
before the customer looked —
EscrowAutoConfirmer.Reconcile(escrow_confirm.go:154) loggedthe hub's escrow blob does not cover the CURRENT repo password (hub hash 30ef574fe492… != local 9b4a9a9dcec7…) … staying pending. It is recomputed on every report cycle, never persisted, never surfaced.
And the hub explicitly disclaims doing this — offsiteheal's package doc: "it never runs, or asks
for, an escrow ceremony… credential automatic, key customer-present — the ruling this session
implements and must not quietly widen." The repository key is created in the seam between two sides
that each honoured their contract, by a helper doing exactly what its doc comment says. That is why
it survived review: every individual comment is accurate.
Corroboration from both sides, measured independently here: the two key hashes on the venue
(30ef574f… current, 9b4a9a9d… in repo_password.selfheal-aside, mtime 03:18:06) reproduce the
journal's figures exactly; and the hub's host_escrow row for finalwalk-ed05d6 seals
restic_pw_sha256 = 30ef574f… with a 572-byte identity_blob and no superseded row — so the hash
the box logged as "hub hash" is confirmed from the database, not inferred.
Q2 — shape (b) is structurally unreachable, not merely unfired: markOrphaned() has one producer,
ensureOffboxRepo(), reached only from inside a run, and every run passes if !m.offboxEscrowed()
(offbox.go:743) first. The box was pending and could not stop being pending, because the
auto-confirm flips only on a hash match. Positive control, because an absent log line is not
evidence: the scheduler was alive throughout (241 agent-channel-health, 120 stack-scan, 48
offsite-credential-retry, 16 hub-report), and offbox-backup is a sched.Daily leg whose slot
fell before the destruction — the absence is explained, not just observed.
Q7 — the „Helyreállítási kód létrehozása" button. It does not destroy the data: SaveHostEscrow
demotes the current row into host_escrow_superseded copy-before-delete, identity_blob included
(R-198). But the read path for a retained package is unbuilt (R-199), so it converts a
one-screen-away self-service recovery into one needing an operator and tooling that does not exist.
And it re-enables the screen while invalidating the code that screen accepts — a worse trap than a
plain dead end, because it looks like progress. (Reasoned from code and the hub schema; I did not
press it — that is a state change and would have destroyed the evidence.)
Two findings nobody asked for, both filed: R-243 (a box in this state silently stops backing up
and no alarm fires — three individually-correct exclusions leave one state unobserved) and the trap
in the obvious fix (ResetOrphanedRepo clears the orphan flag without a ceremony, so a hash-mismatch
discriminator alone would re-offer the screen forever to a customer who declined the old data).
Output: audits/SPIKE-r241-recovery-offer-2026-08-07.md — question/method/measurement/ruling, the
Q4 seven-state table, ranked options, and four operator decisions stated and left unanswered.
No product code was written, in any part of this session.
2. GOLDEN 0.205.0 — baked, verified, VOUCHED (R-239 CLOSED)
| version | 0.205.0 · sha256 8f49b2e8ccbc86a49df821fee9fb00c07293758811d3d0f0512dd0cf5fd54ee8 |
| size | 656,937,561 bytes (uncompressed 2,003,343,360) |
| MinAgent | 0.127.0 |
Round-trip verified rather than trusted: the published bytes were fetched back (HTTP 200, byte
count matches), re-hashed independently of the baker (matches), zstd -t'd clean, and
./etc/felhom-controller-image was read out of the downloaded archive →
felhom-controller:0.205.0. That last step is the one that matters, because GOLDEN_VERSION is
derived from the tag argument and could have been right over stale content. A third witness: the
hub's own dropdown lists 0.205.0 with data-sha="8f49b2e8…".
All acceptance markers pass (docker OK (overlay2 ×1, including mount point ×2 = rootfs + mp0,
upload OK (HTTP 201) ×1, and 0 each for excluding/FATAL/ERROR:/WARN:); unit
Result=success, ExecMainStatus=0. Bake VM torn down: CT 9100 purged, secrets shredded, qemu
observed gone via ps -eo comm, drill.qcow2 reverted to virgin.
Secret hygiene: token copied file→file; the invocation lives in an in-VM runner that reads the
token itself, so it never reached a command line (systemctl show … | grep -c -F <token> → 0);
literal-value leak grep on the committed log → 0, and the instrument was proven first —
token appended to a throwaway copy → 1 hit → copy shredded → the 0 is a measurement.
Vouched, with the operator's approval. In the event it was a ONE-field change, not three — read
from live hub_settings before and after, not assumed:
| field | before | after |
|---|---|---|
artifact_golden_version |
0.203.0 | 0.205.0 |
artifact_golden_sha256 |
3039c6ff… |
8f49b2e8… |
artifact_agent_version |
0.127.0 | 0.127.0 — unchanged |
artifact_min_agent |
0.127.0 | 0.127.0 — unchanged |
A trap worth naming: wrapper_sha256 is read from the form and cleared when omitted. A
headless POST that forgets it silently drops the PBS-DR wrapper hash. It was carried through
explicitly and verified present afterwards. The R-120 gate passed exactly — the newest controller
the fleet reports is 0.205.0 (demo-hp), so a 0.204.0 golden would have been REFUSED. Vouching is
reversible; a bake never deletes an older golden's package.
§4.1's systemic half is recorded, NOT built → R-242, with three proposed shapes and a stated
earliest-catch (a repo_gates.py comparison of the manifest's golden_version against the newest
released controller — it fires on the push that creates the gap, before any box is installed).
3. TEARDOWN — finalwalk, all five layers
Enumerated first and cross-checked against the hub's own delete-preview, which agreed in every field. Matched on identity, never on size.
The cascade refuses a live host, so VM 324 was stopped at 07:23:56Z (guarded on qm config 324
reading name: finalwalk-appliance — demo-hp also carries a guest 9201) and aged past the 30 m
stale_threshold, read from the deployed config. POST /configs/finalwalk/delete with all six gates
→ the hub's leg-by-leg log shows host deleted, off-site sub-account 285071 deprovisioned, PBS
namespace deprovisioned (existed=true — the positive observable), claim reset, residue purged
(124 rows, matching the preview exactly), COMPLETE … full teardown. Then qm destroy 324 --purge.
Every layer verified absent with a positive control that must survive, and does: VM 324 gone (VM
300 drill-r50 remains) · all 13 hub tables at 0 including both escrow tables (demo-felhom 50,823
/ demo-hp 7,640 / peti 1,827 rows remain) · Storage Box u629488-sub4 gone (sub1/2/3 remain) · ep0
namespace finalwalk gone (demo-felhom, demo-hp remain) · WireGuard 10.77.0.5 gone from the live
wg show on ep0, not merely from the hub DB (.2/.3/.4/.250 remain). 14.06 GiB reclaimed,
against 15 G measured before deletion.
R shredded with a planted-copy control — plant → search finds both → shred → same search finds 0. The zero was not believed until the instrument was proven.
⚠ And a correction I am reporting rather than quietly fixing
A full census (every table, every column) after the "COMPLETE" cascade found 61 rows still
matching finalwalk. Four sources are deliberate (events, notification_log, host_deletions,
customer_resets — the cascade's header says provenance outlives every tier). The fifth is a gap:
app_log_issues, 29 rows, not covered by the residue purge — and systematic, with c11 40,
rewalk 20 and part4 24 still present from the 2026-08-06 teardown, whose ledger recorded "0
occurrences". That claim used a narrower query than a census and does not hold. Both the prior ledger
and the register now carry the correction.
No secret material is involved. The table is a fleet-wide aggregate; 12 of the 29 rows are
finalwalk-only orphans and 17 are shared with live customers and must be de-referenced, not
deleted — very likely why the leg was never written. Filed as R-244, not fixed: a cascade change
needs its own red-proof. The reusable lesson: a per-table absence query is not a census.
Register
| ID | Movement |
|---|---|
| R-241 | RULED — minting defect. Diagnosed, not fixed; four operator decisions owed |
| R-239 | CLOSED — golden 0.205.0 baked, verified and vouched |
| R-242 | NEW — a release is not delivered until a golden carries it; recorded, not built |
| R-243 | NEW — the R-241 state silently stops off-site backups with no alarm |
| R-244 | NEW — app_log_issues survives the delete cascade, across all four torn-down venues |
Highest register ID moved R-241 → R-244.
Verification
| commits | 71c43f87c240 (spike) · 08b75e602e01 (bake evidence) · db578cd44d3e (R-239 closed, map + STATUS) · this one |
| CI | run 233 71c43f87c240 success · run 234 08b75e602e01 success — matched by head_sha, pulled not assumed |
--no-verify |
not used. The pre-push hook ran repo_gates.py --fast on every push and reported gates OK |
| gates | python3 scripts/repo_gates.py --fast → all six OK before each commit |
What did not run, and why
- No product code, deliberately — Part 1 was a question, and the answer changes what the fix should be. Beginning a candidate before the operator rules on §"THE OPERATOR'S DECISION" would prejudge it.
- Q4 row 7 and Q7's post-button behaviour are reasoned from code, not measured. Both need a state change on the venue; either would have destroyed the evidence for everything else. They need a fresh fixture and their own session — and row 7 shapes the fix, so it matters.
- R-242, R-243 and R-244 are filed and not built — R-242 because the task scoped it record-only; the other two because they surfaced inside a spike and an operation, and each needs its own red-proof.
- The exact wall-clock at which
hub_escrow_identity_presentfirst became true is not measured —SetHubEscrowIdentityPresentwrites only on change and logs nothing. The bound that matters is established by control flow: true at or before 02:48:03Z.