docs: R-193 CLOSED (the recovery screen); R-213 minted for the put-back
gates / gates (push) Successful in 7s
gates / gates (push) Successful in 7s
- OPEN-ITEMS: R-193 CLOSED with both 2026-08-05 rulings (unlocking and restoring are separate; 'I do not want the old data' moves the store aside after a double confirmation), and the shape-(b) reasoning — WriteOffboxSecrets auto-generates a repository password on re-apply, so the literal 'fresh data area' trigger would have opened a window that closes by itself. - R-213 MINTED (R-212 was and still is the highest, re-checked for the second writer): putting files back in place, with the live-versus-backup comparison named as its requirement. Not started, deliberately. - capability map: the 'needs someone who knows to look' qualifier is GONE; what remains is stated narrowly — no correct-code run through the page, the put-back is out of scope, and the journey has not been re-walked end to end. - 07-backup-architecture 7.0: a fifth row, and where the screen deliberately stops. - CONTEXT: standing ruling S-34. - STATUS: the headline change and the two things still owed as proof. No hub change and no hub bump.
This commit is contained in:
+42
@@ -17,6 +17,48 @@
|
||||
|
||||
## Standing rulings
|
||||
|
||||
**S-34 — UNLOCKING AND RESTORING ARE SEPARATE. The recovery screen shipped (2026-08-05, controller
|
||||
v0.200.0, R-193 CLOSED). Read with S-32 and S-33; together they close the whole customer journey up to
|
||||
the listing.**
|
||||
|
||||
**THE RULINGS (operator, 2026-08-05), recorded as given:**
|
||||
|
||||
1. **Unlocking and restoring are separate.** This screen unlocks only. Restore stays per-app, in the
|
||||
backups area. **A screen that unlocks and then offers to overwrite is two decisions wearing one
|
||||
button.** Putting files back is its own item (→ **R-213**), and its stated requirement is a
|
||||
live-versus-backup comparison — the customer must see what would change before anything is
|
||||
overwritten.
|
||||
2. **"I do not want the old data" MOVES THE STORE ASIDE**, after double-checking with the customer.
|
||||
|
||||
*Mechanism facts a future session needs:*
|
||||
|
||||
- **ONE CORE, TWO CALLERS.** `backup.RecoverInstallCore` is the only fetch→unseal→compare→install path.
|
||||
`RecoverAndInstall` is now a thin CLI wrapper (exit codes and printed lines byte-identical — every
|
||||
pre-existing CLI test passed unchanged) and the web handler calls the same function. **Asserted from
|
||||
source by AST on BOTH sides**, plus a third test that the routes and the landing-page interception
|
||||
exist. Two implementations of the one operation that can permanently lose a customer's data would
|
||||
drift, and only one would be tested.
|
||||
- **THE TRIGGER HAS TWO SHAPES, and the second is the one that matters in practice.**
|
||||
`backup.OffsiteRecoveryOffer` = the hub holds a package **AND** (no repository password **OR** the
|
||||
tier is orphaned). The literal "no repository password" alone is a window that CLOSES BY ITSELF:
|
||||
`WriteOffboxSecrets` auto-generates a password on re-apply — R-193's own orphaning mechanism — and
|
||||
since hub v0.96.0's credential self-heal the re-apply happens within ~15–30 minutes. Shape (b) is
|
||||
also the state the shipped move-aside requires, which is what lets the discard choice reach the
|
||||
existing handler instead of needing a new one.
|
||||
- **CLAIMED IS PART OF THE PREDICATE.** A legacy-open box (no password anywhere) reaches `ServeHTTP`
|
||||
through `RequireAuth`'s pass-through, so without an explicit `authEnabled()` check the interception
|
||||
fires for an unauthenticated visitor. Caught by a test, not by review.
|
||||
- **"Most nem" suppresses the FULL PAGE ONLY.** The backups-area entry point is bound to
|
||||
`recoveryOffer`, never to the postpone flag. A one-shot notice a flustered person clicks past is a
|
||||
notice that never happened.
|
||||
- **The code is handled no more loosely than on the CLI:** POST body only (`PostFormValue`), never
|
||||
logged, never persisted, never echoed, cleared on every path, `no-store`, `autocomplete="off"`.
|
||||
**NO LOCKOUT, deliberately** — the code is a ten-word phrase and locking a customer out of their own
|
||||
data for a typo is worse than anything a lockout prevents; failures are logged locally without the code.
|
||||
- **NOT YET PROVEN LIVE: a CORRECT code through the page.** No recovery code was kept for demo-felhom's
|
||||
orphaned history (`RECON-offsite-dr-chain-2026-08-04.md` §12.3) and demo-hp's is operator-held. The
|
||||
live run exercised handler → agent → hub fetch → age KDF and stopped at the unseal.
|
||||
|
||||
**S-33 — THE BOX DECLARES, THE HUB ANSWERS. R-204 item 4 / R-193's credential half closed
|
||||
(2026-08-05, controller v0.199.0 + hub v0.96.0). Read with S-32; together they close all four of the
|
||||
drill's manual interventions.**
|
||||
|
||||
@@ -22,16 +22,19 @@ over; and the file was restored **byte for byte identical**. *(R-201)*
|
||||
|
||||
## What's broken
|
||||
|
||||
- **All four recovery steps are now automatic — but a customer still would not know to start.** The
|
||||
four steps that stood between "the key is recoverable" and "the file is back" are closed *(R-204)*:
|
||||
the reset-code tool works first time; re-issuing the storage credential no longer falsely marks the
|
||||
recovery key "stale"; the everyday restore says in plain Hungarian that it returned the app's
|
||||
settings and database and **not** your documents; and, as of today, **a rebuilt machine asks for its
|
||||
storage credential itself and the hub answers** — no Re-issue click. **What is missing is the
|
||||
offer:** there is no screen that meets the owner of a rebuilt machine, tells them a sealed package
|
||||
is waiting, takes their recovery code and shows what would come back. So nothing needs *you* any
|
||||
more, but it still needs someone who knows to look. **And the whole journey has not been re-run end
|
||||
to end since these fixes** — the four are proved one at a time, not as a single walk. *(R-193)*
|
||||
- **A customer can now get their backups open on their own — but not yet put the files back.** Every
|
||||
step from a rebuilt machine to an open backup store is done, and today the last piece landed: a
|
||||
**full screen** meets the owner of a rebuilt machine, explains that the backups are still there,
|
||||
says plainly that **nobody can replace a lost recovery code**, takes the code, and shows what is in
|
||||
the store — which apps, from when, how big. Nothing needs you, and nothing needs a command line.
|
||||
**What it deliberately does not do is put files back.** That is per-app, in the backups area, and
|
||||
the piece that would guide it — showing what would change before anything is overwritten — is not
|
||||
built yet. *(R-193 closed; the put-back is R-213)*
|
||||
- **Two things still owed as proof.** The final unlock has never been done with a **correct** code
|
||||
through the new screen: no recovery code was kept for the N100 machine's orphaned history, and the
|
||||
HP machine's is in your hands, not ours — so the live test ran the whole chain and stopped at the
|
||||
last step. **And the whole journey has not been re-run end to end since these fixes** — the pieces
|
||||
are proved one at a time, not as a single walk. That re-run is one more drill. *(R-201)*
|
||||
- **Rebuilding a machine still throws away its off-site backup HISTORY.** The machine invents the key
|
||||
that encrypts its own off-site backups, and a rebuilt machine invents a brand-new one. Both demo
|
||||
machines did this on 3–4 August — 51 backups (~1.2 GB) between them. The old key now survives the
|
||||
@@ -47,6 +50,14 @@ over; and the file was restored **byte for byte identical**. *(R-201)*
|
||||
|
||||
## What shipped recently
|
||||
|
||||
- **2026-08-05** — **The recovery screen: a customer whose machine was rebuilt is now told, and shown
|
||||
how.** Until today they had everything needed to get their data back and no way to find out — the
|
||||
only route was a command line. The screen unlocks the backups and lists what is in them; it does
|
||||
**not** restore anything, because unlocking and restoring are two different decisions and mixing
|
||||
them would turn one clear moment into a wizard. Three ways out, none of them a dismiss button — and
|
||||
the „most nem" option keeps the route to the data permanently visible in the backups area, because
|
||||
a notice someone clicks past once is a notice that never happened. *(R-193)*
|
||||
|
||||
- **2026-08-05** — **The orphaned backups are deleted — and the list you were given was wrong, which
|
||||
is why you were asked again.** You had approved "about 1.2 GB in two set-aside stores". Measured
|
||||
before touching anything: there were **three** set-aside stores totalling **~1.45 GB** — and the
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -367,13 +367,23 @@ here — §8 has rows where it is the actual state.
|
||||
| 3 | **The restore's default returned the wrong thing, silently** | `mode=unit` restores the recovery unit — the app's definition, configuration and database dumps — and **not the customer's files**; the userdata in the same snapshot is excluded by `--include`. The outcome message was one sentence for both modes and named neither scope. On the last step of a disaster recovery, the default quietly did not do what the person asked. | **CLOSED — controller v0.198.0** (R-204 item 3). The unit outcome names what came back, what did not, and the step that gets it; the wizard's intent card states its scope before the choice. **The size gate on `mode=full` is untouched**, and the default stays `unit`. |
|
||||
| 4 | **A rebuilt box cannot obtain an off-site credential unaided** | The one-time provider password was spent by its predecessor, so the rebuilt guest has nothing to authenticate with and an **operator Re-issue** was required. | **CLOSED — controller v0.199.0 + hub v0.96.0** (2026-08-05, operator ruling: automate it, and **the trigger is a state the BOX DECLARES**). The box now reports `offsite.state=needs_credential` when two local facts hold together — a fresh data area AND a hub-held recovery package — and the hub's `internal/offsiteheal` re-arms the stored one-time secret, minting only when there is nothing to re-arm. **Deliberately NOT automated: the escrow ceremony.** A credential is replaceable; the recovery code is not. |
|
||||
|
||||
> **[FACT] The honest current answer, updated 2026-08-05: all four are gone, and a customer alone
|
||||
> still does not have a guided recovery — for a different reason, which is worth keeping straight.**
|
||||
> No step now REQUIRES an operator. What is missing is the OFFER: there is no customer-facing screen
|
||||
> that tells a rebuilt box's owner a sealed package is waiting, takes their recovery code and previews
|
||||
> what would come back (R-193's remaining half). The machinery is self-service; the experience is not
|
||||
> yet built. **And the whole journey has not been re-walked end to end since these fixes** — the four
|
||||
> closures are proven individually, not as one uninterrupted run.
|
||||
> **[FACT] The honest current answer, updated 2026-08-05 (controller v0.200.0): all four are gone AND
|
||||
> the customer is now offered the recovery.** A full-page screen meets the owner of a rebuilt box while
|
||||
> the hub holds a sealed package the box cannot open: it explains the situation, says plainly that
|
||||
> **nobody can replace a lost recovery code**, takes the code, opens the repository and **lists what is
|
||||
> in it** — apps, dates, sizes. No step requires an operator, and no step requires a command line.
|
||||
>
|
||||
> **WHERE THAT STOPS, and it is a real stop.** The screen **unlocks and only unlocks**. It restores
|
||||
> nothing. Putting files back is per-app, lives in the backups area, and the step after the listing —
|
||||
> a customer seeing what would change before anything is overwritten — is **not built** (→ R-213). A
|
||||
> screen that unlocks and then offers to overwrite is two decisions wearing one button, which is why
|
||||
> the operator ruled them separate.
|
||||
>
|
||||
> **What is still owed as evidence:** the final unlock has never been driven with a CORRECT code
|
||||
> through the page (no code was kept for demo-felhom's orphaned history; demo-hp's is operator-held),
|
||||
> so the live run exercised handler → agent → hub fetch → age KDF and stopped at the unseal. **And the
|
||||
> whole journey has not been re-walked end to end since these fixes** — the closures are proven
|
||||
> individually, not as one uninterrupted run. That re-walk is one more drill.
|
||||
>
|
||||
> **WHY THE TRIGGER FOR STEP 4 IS A DECLARATION, recorded here because it is the design and not an
|
||||
> implementation detail:** from the hub, an ABSENT off-site object means *never configured*,
|
||||
@@ -979,3 +989,4 @@ to now *implement* D5 remains an open scheduling decision, not a blocked one.
|
||||
- It does not estimate a single RTO or RPO. Every blank in §8 is a real gap.
|
||||
- It does not answer §11. Those are the operator's.
|
||||
- It does not claim ratification.
|
||||
| 5 | **A customer had no way to BEGIN — the only route was a command line** | Everything above was self-service, and nothing told the owner of a rebuilt box that a sealed package was waiting or how to open it. | **CLOSED — controller v0.200.0** (R-193). A full-page recovery screen, shown while the hub holds a package this box cannot open; it unlocks and lists, and **restores nothing** (→ R-213 for the put-back). |
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -67,6 +67,7 @@
|
||||
| R-201 | **The wipe-and-recover drill** | L | **PREPARED, HALTED BEFORE THE WIPE (2026-08-04)** | Nothing irreversible done. Established live for the first time: a rebuilt box's off-site run REFUSES (the orphan card, not a silent fresh history — closes R-193 Q3), the orphan reset works move-aside-never-delete, and demo-hp's pre-rebuild key is permanently gone (superseded four hours before v0.93.0). Resume needs R-203 |
|
||||
| R-203 | **The app and its backup looked in different directories, and a run that skipped a mandatory folder still said `ok`** | M | **SHIPPED + PROVEN-LIVE (controller v0.197.0, 2026-08-04)** | Adds the capability-map row *"off-site app-data capture covers MANDATORY paths on both drive layouts"* as PROVEN-LIVE, and corrects that row's predecessor, which was optimistic. Unblocks R-201 |
|
||||
| R-204 | **The recovered key cannot be used: the Re-issue that reconfigures a rebuilt box's off-site tier marks the escrow stale, which gates every run, and the only way to clear it destroys the key** | M | **ALL FOUR ITEMS SHIPPED (controller v0.199.0 + hub v0.96.0, 2026-08-05)** | Collapsed per the coupling rule. The fix taken was R-196's — remove the guess, keep the two MEASURED signals (the controller's per-ACK hash re-check, which the stale flag was blinding, and R-197's supersession detector). Plus the claim-code read-through and the restore-scope wording. **Flips the capability-map recovery row from four crutches to one**, and that one is R-193's operator Re-issue, which keeps the row's *with a person present* qualifier. Does NOT touch R-198's retention, which is still unit-proven and needs the second drill **Item 4 shipped 2026-08-05 by DECLARATION, per the operator ruling: the box states its condition, the hub answers; an absence has four meanings and the hub cannot tell them apart. `internal/offsiteheal` mirrors `pbsdrheal` (declared states only, two-distinct-report debounce, restage before mint, healthy = no-op). Closes R-193's credential half and R-192's guard half (by replacement). **The capability-map row keeps a qualifier for a NEW reason** — no step needs an operator any more, but there is no customer-facing recovery SCREEN, so the experience still requires someone who knows to look. |
|
||||
| R-193 | **A rebuilt box's off-site history was unreachable, and later reachable only from a command line** | L | **CLOSED (2026-08-05)** — credential half controller v0.199.0 + hub v0.96.0; the customer-facing SCREEN controller v0.200.0 | Collapsed per the coupling rule. Three legs: (a) the hub's retention keeps the sealed key (R-198); (b) a rebuilt box gets its credential back by DECLARING it needs one (R-204 item 4); (c) **the recovery screen** — a full page that unlocks the repository and lists what is in it, and **restores nothing**. Operator ruling: unlocking and restoring are SEPARATE, and the put-back needs a live-versus-backup comparison first (→ R-213). Flips the capability-map row's *needs someone who knows to look* qualifier; what remains there is one end-to-end re-walk and a correct-code run through the page |
|
||||
| R-201 | **The wipe-and-recover drill** | L | **PASSED + PROVEN-LIVE (2026-08-04 night)** | Flips the off-site app-data row to *"a customer's file survives a machine rebuild and comes back"* — PROVEN-LIVE, scoped to demo-hp and to a controller-data rebuild. The journey took four undocumented manual steps (R-204); the capability is real, the customer experience is not yet |
|
||||
| R-19 | Internet-outage customer-experience drill: pull WAN on demo, verify lan_resolver path, document what the customer actually sees/does | S | idea | Flips map row E "LAN access" IMPLEMENTED→PROVEN-LIVE |
|
||||
| R-20 | ~~Verify operator-key pinning is fully in the day-0 install flow~~ | XS | **closed** (2026-07-16) | Confirmed against `scripts/felhom-host-install.sh` source (not changelog): keys resolve at L1181–1219 (script constants `OPERATOR_KEY_*`, populated, `--operator-pubkey-file` override), pinned automatically by `step_agent_config()` "STEP 6/8" (L2044; python builds `authz.signers` L2146–2156, reinstall preserves existing), verified at L2332–2337 ("authz signers: N … operator-signed self-update armed"). No interactive prompt or post-install hand-edit — fully automatic. Doc-drift note: the L193–197 "EMPTY by default" comment is stale vs the now-populated constants (→ R-16 hygiene) |
|
||||
|
||||
Reference in New Issue
Block a user