0bdffbe865
The spec listed backups_remote.html:98 as 'Accurate; keep'. It ended with the same unevaluable promise as surface 2, in the plural - and because the spec's own guard was written against the singular form, it could not catch it either. Both corrected; implemented in controller v0.212.0 (R-299).
98 lines
6.8 KiB
Markdown
98 lines
6.8 KiB
Markdown
# SPEC — the orphan card must stop promising what it cannot know (2026-08-10)
|
|
|
|
**Specification only. Nothing here is implemented, deliberately** — see §5.
|
|
|
|
**Why this exists.** When a machine's off-site history is set aside, the card tells the customer — in
|
|
Hungarian, at the moment they have just lost that history — that the old copies *may be restorable
|
|
later with their recovery code*. **For anything set aside before 2026-08-04 that is false and
|
|
unfixable**, and on 2026-08-10 it was said to a real machine in exactly that state (`demo-felhom`).
|
|
Register: **R-202**.
|
|
|
|
---
|
|
|
|
## 1. The surfaces, at `file:line`
|
|
|
|
All in `felhom-controller`:
|
|
|
|
| # | file:line | what it says today |
|
|
|---|---|---|
|
|
| 1 | `controller/internal/web/templates/backups_remote.html:98` | *"A távoli tárhelyen lévő mentések egy korábbi, már nem elérhető kulccsal készültek…"* — the orphan explanation. ~~**Accurate; keep.**~~ **WRONG — CORRECTED 2026-08-12 (R-299).** Its first two clauses are accurate and were kept, but it ENDED *"— a hozzájuk tartozó helyreállítási kóddal később **visszaállíthatók lehetnek**"*: the same unevaluable promise as surface 2, in the plural. This spec called it accurate, so it survived the 2026-08-10 fix; and because §5's guard was written against the SINGULAR form it could not catch the plural either. **It is also the ALWAYS-VISIBLE half of the card** — surface 2 only renders after the customer clicks through — so on first view this was the only text they read, and it was the one still promising. |
|
|
| 2 | `controller/internal/web/templates/backups_remote.html:101` | *"A régi előzmény **félretéve marad** (nem törlődik), és a hozzá tartozó helyreállítási kóddal később visszaállítható lehet."* — **the false promise.** |
|
|
| 3 | `controller/internal/web/templates/layout.html:143` | the 14-day abandon countdown — *"a korábbi távoli mentéseidet {{.RecoveryAbandonDays}} nap múlva véglegesen töröljük"*. **Accurate; keep**, but see §3. |
|
|
| 4 | `controller/internal/settings/settings.go:337-338` | schema comment on `OrphanedRenamedTo`: *"so the card/log can name where the old history was set aside."* **Accurate; keep.** |
|
|
|
|
The `00-capability-map.md` restorability wording was searched for and **not found** — the map does not
|
|
make this claim, so there is nothing to correct there. (Stated because the task expected one.)
|
|
|
|
## 2. What is actually true, in three cases
|
|
|
|
| case | truth |
|
|
|---|---|
|
|
| material set aside **before** hub v0.93.0 (in force 2026-08-04 ~11:11Z) | **Not recoverable, by construction.** The restic password lives only in the identity bundle (`escrow/identity.go:39`, read by `escrow/recover.go:91`); for these rows `identity_blob` is NULL. No code and no recovery code can produce it. |
|
|
| material set aside **after** the fix | **Recoverable in principle** — the blob is retained and the customer's code unwraps it. Never yet demonstrated end to end on a superseded row. |
|
|
| **today's population** | Both cases that exist are the first one. Census 2026-08-10: two hosts hold a supersession, both `identity_blob` ABSENT, both superseded ~4 h before the fix. |
|
|
|
|
## 3. THE DECIDING FACT: neither the box nor the customer can tell which case they are in
|
|
|
|
- **The hub can tell** — it holds `host_escrow_superseded.identity_blob` (`store/store.go:393`).
|
|
- **The box cannot.** It caches only `HubEscrowIdentityPresent`
|
|
(`controller/internal/settings/settings.go:71`), which is about the **current** escrow, not a
|
|
superseded one. **No field on the report or ACK wire carries superseded-blob retention.**
|
|
- **The box is what renders the card.**
|
|
|
|
**So the card is making a conditional promise its renderer cannot evaluate. That is the same defect
|
|
as an unconditional false promise, in a longer sentence.** The wording below therefore stops
|
|
promising and starts stating.
|
|
|
|
## 4. The replacement copy
|
|
|
|
**Surface 2 — `backups_remote.html:101`. Replace:**
|
|
|
|
> A régi előzmény **félretéve marad** (nem törlődik), és a hozzá tartozó helyreállítási kóddal később
|
|
> visszaállítható lehet.
|
|
|
|
**with:**
|
|
|
|
> A régi előzmény **félretéve marad a tárhelyen — nem töröljük**. Új, üres tárolót hozunk létre, és a
|
|
> következő mentés oda készül.
|
|
>
|
|
> **A félretett mentések megnyithatóságát itt nem tudjuk megígérni.** Ez attól függ, megvan-e még a
|
|
> hozzájuk tartozó kulcs, és ezt ez a gép nem tudja megállapítani. Ha szeretnéd, hogy utánanézzünk,
|
|
> **írj nekünk** — a félretett másolat addig is a helyén marad.
|
|
|
|
Three properties, each deliberate: it **states** what happens (set aside, not deleted, fresh store);
|
|
it **declines** the claim it cannot evaluate, and says *why* rather than going vague; and it **names a
|
|
route the customer can take** — writing in — instead of leaving them nowhere.
|
|
|
|
*(Surface 3's countdown stays, but the two must be read together: the card must not simultaneously say
|
|
"we cannot promise these are openable" and "we will delete them in N days" without the second making
|
|
clear it is the set-aside copy being deleted. Recommend re-reading the pair once implemented.)*
|
|
|
|
## 5. The test that should pin it
|
|
|
|
`controller/internal/web/` render test, one per branch of the orphan card:
|
|
|
|
1. **Orphan card rendered → the STEM `visszaállíthat` does not appear anywhere in the output.**
|
|
That is the regression guard: it fails if the promise returns in any form.
|
|
|
|
**CORRECTED 2026-08-12 (R-299).** This originally said the string `visszaállítható lehet`, and
|
|
that is how surface 1's plural („visszaállíthatók lehetnek") shipped past it. **A guard matching one
|
|
inflection of a Hungarian verb guards one sentence, not the claim** — match the stem. The plain
|
|
forms the rest of the UI uses („visszaállítás", „visszaállítani") do not contain `visszaállíthat`,
|
|
so this does not over-match. Proven by planting the shipped plural: the stem guard convicts, and the
|
|
old singular guard does **not** match that sentence at all.
|
|
2. **Orphan card rendered → the "write to us" route is present.** A refusal that names no route is a
|
|
defect in this project; pin the route, not only the absence.
|
|
3. A render test **per branch of the gate** that shows the card at all, per the seam-wiring rule —
|
|
a handler test that POSTs proves nothing about reachability.
|
|
|
|
## 6. Why it is not implemented tonight, and what it should ship with
|
|
|
|
The change lands in `felhom-controller`, and **a controller release is not delivered until a golden
|
|
carries it** — which costs one image bake and one operator approval (R-242 is the row about exactly
|
|
that gap). **The next session changes the controller anyway.** One bake and one approval should cover
|
|
this copy change together with that work, rather than spending two of each.
|
|
|
|
**Do not let this decouple:** if the next controller release ships without this, the promise is still
|
|
being made to the next customer who loses a history.
|