docs: R-181 closed, R-156 closed, R-110 + R-115 rulings recorded, R-182 filed
gates / gates (push) Successful in 7s

R-181 CLOSED (controller v0.193.0 + v0.193.1) and proven live on demo-hp for
BOTH reserve terms. The reserve is now a per-app, per-run ADMISSION decision
taken before the app's first write and covering all three write legs, and it
gained a size term. The refusal's wording was not weakened; the behaviour moved
so it became true, verified by sha256 tree fingerprint.

R-156 CLOSED — papra's template mounts the app's own data root. Precondition
re-measured rather than inherited (both boxes were wiped today).

Part 4, documentation only, nothing built:
- R-110 WAITING-ON-OPERATOR -> READY. Ruling: option (b), the installer's
  publish channel moves to a TAG. Recorded with the condition that decides
  whether it works at all — it must cover BOTH the /scripts/ git-sync and the
  nine files the installer fetches from raw/branch/main.
- R-115 WAITING-ON-OPERATOR -> READY. Ruling: mechanism (b), a build-side gate
  refusing to deploy or vouch an unpublished version. The third instance (agent
  v0.120.0) would have silently downgraded both demo boxes while succeeding.

R-182 NEW: the periodic status refresh has no admission scope, so a refused app
re-alerts on every poll (measured: a second alert pair 13s after the run's).
Pre-existing in v0.192.0; deliberately not fixed in the R-181 task.

capability map: the local-backup row moves to PROVEN-LIVE in BOTH halves.
ROADMAP: R-165 collapses to CLOSED; R-181 collapsed into it.
07-backup-architecture.md: the reserve's contract stated as what the code
provides (S-1 — an architectural contract changed in the same session).
STATUS.md trimmed 150 -> 111 lines, "What's broken" no longer holds shipped
work, and the stale "After:" line (pointing at work that shipped on 2 August)
is fixed.
This commit is contained in:
2026-08-03 11:36:16 +02:00
parent 06cbf8df29
commit fb652024ea
7 changed files with 324 additions and 449 deletions
+36 -4
View File
@@ -60,10 +60,42 @@ demo boxes were wiped and reinstalled from golden 0.192.0 and taken through clai
proved the disk shape *and* the delivery route rather than one of them twice. Live shape on both:
`mp0` at `/var/lib/felhom`, `backup=1`, **no `mp1`**; `/var/lib/docker` and `/mnt/sys_drive` both real
mounts of its subdirectories via `/etc/fstab`; ONE `df` figure and one device id on all three paths;
3/3 reboots each with the binds surviving every time. **What is NOT proven is B2****R-181**: the
floor guards `captureAllRecoveryUnits` and not `runVolumeDumps`, which is the leg that fills the
volume, and its refusal message's "the previous unit is untouched" was measured false. R-165 is
therefore **IMPLEMENTED**, not PROVEN-LIVE.
3/3 reboots each with the binds surviving every time. B2 was **not** proven on that pass**R-181**:
the floor guarded `captureAllRecoveryUnits` and not `runVolumeDumps`, the leg that fills the volume,
and its refusal's "the previous unit is untouched" was measured false. **R-181 CLOSED the same day
(controller v0.193.0 + v0.193.1), so R-165 is now PROVEN-LIVE in both halves** — see S-14.
**S-14 — the reserve is a per-app, per-run ADMISSION decision, not a capture check (2026-08-03, R-181;
controller v0.193.0 + v0.193.1).** B2 as first shipped was consulted in exactly one place —
`captureAllRecoveryUnits`, a few KB — while `RunDBDumps`' database leg and `runVolumeDumps` wrote the
bulk into the same `backups/primary/<app>` tree, first and unguarded. The reserve was therefore
consumed by the very write it exists to bound, and the refusal then claimed *"the previous unit is
untouched"* about a tree the earlier leg had already rewritten (182,272 B → 2,147,666,432 B under a
manifest that had not moved). **Sixth entry in `CLAUDE.md`'s table of shipped guarantees the code did
not provide, and the fourth of those found on live hardware rather than by review.**
- **`internal/backup/admission.go``admitApp` is now THE gate**, and every per-app write leg calls
it. One verdict per app per run covers all three; they share one per-app root, which is what makes
that honest.
- **Decided lazily at the app's first write, never once at run start** (app A's dump can put app B
under the reserve), **never re-decided between an app's own legs** (that is the split it closes),
and **reset per run**.
- **Ahead of `DumpAppVolumesSafe`**, which stops the stack as its first act — a refusal decided
inside it has already bounced the app. **After** the volume-less check, which has no write to gate.
- **Size term added:** *would THIS app's write cross the reserve?*, estimated from the app's previous
`.sql` + `.tar`. **No history → headroom-only**, deliberately — otherwise the first backup is the
one that can never happen.
- **A container-based `du` was MEASURED and rejected**, not waved away: median **~355 ms/volume** over
66 runs on demo-hp, on volumes holding tens of KB (container start-up, not the walk). Decisive on
top: `docker run` needs the writable layer, so the instrument can fail under exactly the pressure
the reserve handles.
- **The wording was NOT weakened; the behaviour moved so it became true**, and it is checked by
sha256 tree fingerprint, never by reading the log line — the log line is what lied.
- **v0.193.1**, found by the proof run itself: a 178 KB estimate printed as `0.00 GiB`, which reads as
*no estimate available*. Rendering moved to `humanizeBytes`; arithmetic still in GiB.
- **New finding, deliberately not fixed here → R-182**: `GetFullStatus`'s periodic capture sweep has
no run scope, so a refused app re-alerts on every status refresh (measured: a second identical alert
pair 13 s after the run's). Pre-existing in v0.192.0; R-181 changed neither caller.
**S-11 — D-c's routing, and why R-158's own proposal was overruled (2026-08-02, R-167 SHIPPED).**
Decision D-c splits two signals by AUDIENCE, and the split is the ruling: **a fill warning is the