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
@@ -566,17 +566,40 @@ mismatch table above (`mp0` 50 G vs `mp1` 20 G) describes what a merged box no l
before provision grew them). **Read it as a function of `mp1`, and only for a box still on the split
layout.** Measured: `audits/SPIKE-r165-mp1-merge-2026-08-02.md` M1.
**What replaced the partition's second job.** `mp1` was also a BULKHEAD: an overflow was refused per
app with the last good unit byte-identical, and it **could not reach `/var/lib/docker`**, because that
was a different filesystem. On a merged box it can. Decision **B2**, shipped in controller
**v0.192.0**, is that bulkhead made deliberate — a two-term capture floor (97% used or 1 GiB free) in
**What replaced the partition's second job — the reserve.** `mp1` was also a BULKHEAD: an overflow was
refused per app with the last good unit byte-identical, and it **could not reach `/var/lib/docker`**,
because that was a different filesystem. On a merged box it can. Decision **B2**, shipped in controller
**v0.192.0**, is that bulkhead made deliberate — a two-term reserve (97% used or 1 GiB free) in
`internal/fillwatch`'s shape, sitting beyond its critical band so the customer is always warned first.
It **refuses per app and never deletes**: nothing on this filesystem is generational, so pruning could
only destroy a different app's only local copy.
**Status caveat, deliberately explicit:** as of 2026-08-03 **no box has been reinstalled from the
merged golden** (R-178), so every box in the field is still on the split layout and everything above
still describes them exactly. This subsection describes what a box built from golden ≥ 0.192.0 gets.
**THE CONTRACT, stated as what the code provides (controller v0.193.0, R-181).** The reserve is a
**per-app, per-run ADMISSION decision, not a capture check.** It is taken once for an app, immediately
before that app's FIRST write of the run, and it covers **all three write legs — the database dump, the
volume dump and the recovery-unit capture**. Those three write under one per-app root
(`backups/primary/<app>`), which is what makes one verdict able to cover them honestly.
- **What it guarantees.** A refused app has **nothing written for it in that run**, its previous unit
is **byte-identical**, it is **not stopped**, nothing anywhere is deleted, and the operator gets
**exactly one** alert naming the app, the term that bound and the disk figures.
- **Two terms, two questions.** *Headroom*: is the filesystem already below the reserve? *Size*: would
THIS app's write take it below? The size estimate is the app's previous `.sql` + `.tar` on disk;
with no history the decision degrades to headroom alone, deliberately — otherwise the first backup
is the one that can never happen.
- **Why it is decided lazily and not once per run.** Space changes during a run: app A's dump can put
app B under the reserve, so a verdict taken at run start reads a disk that no longer exists.
- **Why it is never re-decided between an app's own legs.** That is precisely the shape v0.192.0 had —
the two dump legs unguarded and only the capture refused — under which the reserve was consumed by
the very write it exists to bound, and the refusal's *"the previous unit is untouched"* was measured
false. Proven live on demo-hp 2026-08-03 (R-181), fixed the same day, and re-proven by filling the
box for each of the two terms.
- **It sits ahead of `DumpAppVolumesSafe`**, which stops the stack as its first act — a refusal
decided inside it would already have bounced the app it is refusing to back up.
**Status caveat, deliberately explicit:** every box in the field that has not been reinstalled is still
on the split layout and everything above still describes them exactly. This subsection describes what a
box built from golden ≥ 0.192.0 gets. Both demo boxes were reinstalled from it on 2026-08-03 (R-178).
Two things are deliberately **not** recorded here. **The sizing ratio is the operator's ruling**
(**R-163**) — this section states the constraint, not a number. And **the same-device placement is