5beedcce1a
gates / gates (push) Successful in 4m13s
Unreleased; ships with tomorrow's hub release. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
60 lines
5.3 KiB
Markdown
60 lines
5.3 KiB
Markdown
# R-304 — the household's old recovery code and the retained packages: a one-page design (2026-10-08)
|
|
|
|
**Status:** option C's first slice BUILT 2026-10-08 afternoon on the operator's ruling (09:04, `09` §3 decision 183) — see the end. Ships with the next controller + hub releases. Customer data and promises are the
|
|
operator's: they are the two questions at the end.
|
|
|
|
## Where it stands (read in source today, not from the row)
|
|
|
|
- **Retention works** and the material opens the old store (drill 2026-08-12, `audits/DRILL-retained-key-2026-08-12.md`).
|
|
- **R-311 shipped** (hub v0.103.0, agent v0.129.0, controller v0.214.0): after the current package refuses a code, the
|
|
agent fetches the retained packages (`GET /hosts/<id>/escrow/retained`, self-scoped, cap 16) and tries up to 6. If one
|
|
opens, the screen says „the code is correct, it opens an earlier package; contact support" (HTTP 422).
|
|
- **R-312 is DECIDED (2026-08-13): no in-product route from the recovery screen to a set-aside store** — „re-evaluate on
|
|
a real customer request". So retention is an **operator-only** capability today, by decision.
|
|
- **What was still false until today:** the agent answered „the code did not open the sealed bundle" (400) also when it
|
|
had NOT tried every earlier package — the hub withheld rows (no key material, over the cap), a package was malformed,
|
|
the 6-try cap stopped the loop, or the retained list could not be read. **Fixed on main today** (agent 424
|
|
`older_unchecked`, controller `RecoveryOlderUnchecked`, Hungarian + English: „we do not know whether your code is
|
|
wrong … contact support"). Ships with tomorrow's releases. The fix is in the agent and the controller, not the hub:
|
|
the hub never sees the code (zero-knowledge, `07` §2), so it cannot check a row; it already reports what it withheld
|
|
(`unopenable_count`, `truncated_count`), and the agent now counts those.
|
|
|
|
## „Which package?" — the question is smaller than it looked
|
|
|
|
Each escrow ceremony seals with a NEW recovery code (the household is shown it once). A code opens only the package it
|
|
sealed. So when a household holds several old codes, **each code selects its own package** — no list, no choice screen.
|
|
The agent already tries newest-superseded first. The only real limits are the two caps (16 served, 6 tried), which
|
|
today's fix turns from a silent „wrong code" into an honest „not all checked".
|
|
|
|
## Options for really serving the old copy
|
|
|
|
| | What | Costs | Customer data / promise |
|
|
|---|---|---|---|
|
|
| **A** | Keep it operator-only (R-312). The screen's „contact support" is the route. | Nothing more. The operator needs SQLite, `age` and a shell (the drill's §4) — slow, error-prone, undocumented as a runbook. | No change. |
|
|
| **B** | In-product: when a retained package opens and carries a repository password, the screen offers a READ-ONLY browse of the old store (list + download), never a restore into place. | New surface: the old store's location (moved aside / orphaned, R-241), a second repository password in memory, a second browse path. ~2 sessions + a drill. | Changes a promise (the household can reach old history alone). **Reverses R-312** — operator only. |
|
|
| **C** | Operator-assisted, first slice: when the agent answers 422 (opens retained) or 424 (not all checked), the controller sends ONE operator event naming the box and the package date; plus a runbook „open a retained package for a household" (the drill's §4 written down, the household types the code on its own box). | Small: one event type (operator-only), one runbook. ~½ session. | No new promise; makes the existing „contact support" true in practice. |
|
|
|
|
**Pick: C now; B only on R-312's own trigger** (a real customer asks). C makes the sentence the screen already says —
|
|
„contact support" — something the operator can act on the same day, without reversing a decision.
|
|
|
|
**First slice of C:** controller: on `RecoveryCodeOpensRetained` / `RecoveryOlderUnchecked`, send `recovery_retained_needed`
|
|
(operator-only, warning, once per box per day) with the package date and the class — never the code. Hub: allowlist +
|
|
`operatorOnlyEvents` in the same commit. Docs: `runbooks/RUNBOOK-open-retained-package.md` from the drill's §4.
|
|
|
|
## Two questions for the operator
|
|
|
|
1. **May the product keep promising, in the capability map and the countdown banner, that old backups „stay
|
|
recoverable"?** Today that is true only with your hands. *If you do nothing:* the promise stays worded as it is, and
|
|
the honest route is „contact support" (A/C).
|
|
2. **Do you want C's first slice built (an operator mail when a household's code opens — or may open — an old
|
|
package)?** *If you do nothing:* nothing is built; you learn of such a household only when they write to you.
|
|
|
|
## Built 2026-10-08 (afternoon) — option C, first slice
|
|
|
|
- **Controller** (`a40729a`): on a 422 (opens an older package) or a 424 (may open one), `recovery_older_package`
|
|
(warning) to the hub — the class and the package's date, never the code; at most once per day per box
|
|
(`<data>/recovery-older-mail.day`). `TestR304_OlderPackageMail_*`.
|
|
- **Hub** (same day): the type is allowlisted and operator-only. `TestR304_RecoveryOlderPackageIsAllowlistedAndOperatorOnly`.
|
|
- **Not built:** the runbook „open a retained package for a household" (the drill's §4 written down) — next slice.
|
|
- **Question 1 (the promise wording) was not answered:** the wording stays.
|