docs(R-120): CLOSED on both halves — golden current, and the class has a gate that refuses

Half 1, the artifact: golden 0.186.0 baked, published, vouched, and proven on a REAL
day-0 on demo-hp (not the fixture, per the rule committed in Part 1). With the target
detached, the fresh box's endpoint returned the TargetAbsent copy -- "A rendszermentés
meghajtója nem érhető el — amíg vissza nem csatlakoztatod..." -- with offer_path
absent entirely. The day-old read on the 0.185.1 golden had returned the false
system-disk message plus an offer of the other drive. That is the customer-visible
defect closed.

Half 2, the mechanism: operator ruled REFUSE, shipped as hub v0.82.0 and DEPLOYED.
Proven live by re-attempting the original mistake -- vouching the stale 0.185.1 golden
now yields HTTP 303 flash=golden_behind_fleet plus [WARN] artifact vouch REFUSED, and
the manifest reads back unchanged at 0.186.0. Refused AND unwritten, against the real
fleet signal rather than a unit fixture.

Recorded on R-29's audit list as the first ENFORCED gate beside its three orphans, so
the contrast is kept rather than lost. The orphans are unchanged -- this proves the
pattern is available, not that the backlog moved.

Teardown all three layers: VM 9402 purged, r120-images removed with the space measured
back, hub layer gate-blocked on ONLINE with the command recorded. Last session's sess-e
was deleted this run, discharging its recorded layer 3.
This commit is contained in:
2026-07-30 10:48:16 +02:00
parent 1a68b53b06
commit 29bcfeb214
2 changed files with 65 additions and 14 deletions
@@ -1,7 +1,9 @@
# R120-golden-rebake-2026-07-30 — the golden is current again; the class still needs a mechanism
# R120-golden-rebake-2026-07-30 — the golden is current again, and the class now has a gate that refuses
**Run:** R-120, CC on DooPlex, 2026-07-30. **Golden 0.186.0 baked, published, vouched, and proven on a
fresh box.** The mechanism half (Part 3) awaits an operator ruling — see §6.
**Run:** R-120, CC on DooPlex, 2026-07-30. **CLOSED on both halves.** Golden **0.186.0** baked,
published, vouched and proven on a real day-0; and the class now has a **mechanism** — hub **v0.82.0**'s
vouch-path gate, which **refuses** a golden the fleet has already outrun, deployed and proven live by
re-attempting the original mistake.
## 1. Baselines, established separately
@@ -125,7 +127,28 @@ R-120's exact state — and agent 0.116.0's R-116 fix is visible on it:
pvesm: felhom-backup dir inactive 0 0 0
```
**The endpoint proof is NOT yet captured**, and is not claimed: `GET /api/storage/backup-target` returns
### The capture — R-120's symptom, inverted
Operator-relayed claim code, then `GET /api/storage/backup-target` **with the target still detached**, on
controller **0.186.0** from the new golden. Verbatim:
```json
{"data":{"degraded":true,"known":true,"label":"",
"message":"A rendszermentés meghajtója nem érhető el — amíg vissza nem csatlakoztatod, a teljes rendszermentés nem készül el.",
"target":"felhom-backup"},"ok":true}
```
- ✅ the **`TargetAbsent`** copy — *the backup drive is unreachable; until you reconnect it the full system
backup will not be made*. Reconnect **that** drive.
-**`offer_path` is absent from the payload entirely** — no drive is offered as a remedy.
-**not** the generic *"A rendszermentés jelenleg ugyanazon a lemezen van, mint a rendszer"*.
For contrast, the **same read on the OLD 0.185.1 golden** one day earlier
(`audits/R116-v0116-2026-07-30.md` §6a caveat) returned the generic system-disk message **plus**
`offer_label: "Adat meghajto"` and `offer_path: "/mnt/felhom-drives/adat"`. The false message and the wrong
offer are both gone. **That is R-120 closed at the customer-visible layer.**
The claim gate had to be cleared by an operator-relayed code — the endpoint first returned
`{"ok":false,"error":"dashboard not yet claimed"}`. The claim gate runs **before** auth
(`controller/internal/web/auth.go:52-67`) and exempts only `/claim`, `/claim/request-new-code`,
`/api/health`, `/static/` (`claim.go:221-228`) — **no Bearer escape**, and `regen-password`
@@ -165,22 +188,50 @@ that the response is the refusal **and** that the manifest was not written. Thre
this project were fully green with the seam disconnected; a gate that can be inert is the thing this
gate exists to prevent.
**This is the operator's call because a refusing gate can block a release.** Not implemented pending the
ruling. If it is declined, the weaker form goes in and the reason is recorded here rather than absorbed
silently.
### RULING: REFUSE — approved by the operator, 2026-07-30. Built, deployed, proven live.
**If the gate is deferred, R-120 does not close** — it is re-opened, or a successor is filed naming the
class: *"a published artifact and its baked copy drift and nothing checks"*. Three instances closed
individually is how a class survives.
Shipped as **hub v0.82.0**: the gate in `handleSetArtifacts` immediately before the only write;
`store.NewestReportedControllerVersion()` over `reports.controller_version` (the column `SaveReport`
denormalises, `store.go:903`), **semver-compared in Go**; an operator-legible refusal flash naming the
remedy; **fail-open in exactly two deliberate cases** — an empty golden field, and an unknown fleet version.
**A near-miss worth keeping.** The first draft read **`guests.controller_version`** — a column that exists
in the schema (`store.go:294`) and that **nothing writes**. That gate would always have seen `""` and
failed open: **inert, i.e. this gate's own failure shape**, and one `grep` from shipping. Caught by looking
for a writer before trusting the column.
**Tests: 4, through the production handler over `httptest`, never an injected seam.** The refusal asserts
**both** the flash **and** that the manifest was not written — a gate that redirects and saves anyway reads
as enforcement while providing none. Plus the allow case, both fail-open cases, and the semver-ordering
case (0.99.0 vs 0.186.0, a pair this fleet has shipped). **Red-proof:** deleting the block makes the stale
golden vouchable and both refusal assertions fail.
### PROVEN LIVE on the deployed hub — the original mistake, re-attempted and blocked
Hub 0.82.0 built, pushed, manifest pinned, ArgoCD hard-refreshed + synced → **Synced / Healthy**, pod on
`felhom-hub:0.82.0`. Then the exact R-120 mistake was re-attempted — vouch the stale **0.185.1** golden:
```
POST /configuration/artifacts → HTTP 303 …/configuration?flash=golden_behind_fleet
hub log: [WARN] artifact vouch REFUSED: golden 0.185.1 is older than the newest controller the fleet
reports (0.186.0) — a fresh install would land on stale application code (R-120)
manifest read back afterwards: golden still 0.186.0, sha still b760ac6a… — the refused POST did NOT write
```
**Refused *and* unwritten**, on the live hub against the real fleet signal rather than a unit fixture.
**R-29's audit list now records it** (`ROADMAP.md`) as the **first enforced gate** beside its three
orphans, so the contrast is kept rather than lost. The orphans are unchanged — this proves the pattern is
available, not that the backlog moved.
## 7. Teardown
| layer | item | disposition |
|---|---|---|
| 1 — machine | DooPlex bake fixture: build guest 9100 purged, `drill.qcow2` reverted to **`virgin`** | **DONE** |
| 1 — machine | demo-hp VM **9402** `r120-drill` | **RETAINED pending the claim code** (§5's endpoint read) |
| 2 — host | `r120-images` dir storage at `/mnt/nvme-1tb` | **RETAINED** with 9402 |
| 3 — hub | customer **`sess-f`**, host **`sess-f-2670b5`**, appliance **14** | **RETAINED** with 9402 |
| 1 — machine | demo-hp VM **9402** `r120-drill` + all volumes | **DESTROYED** `qm destroy 9402 --purge`; `qm list` shows only `drill-r50` |
| 2 — host | `r120-images` dir storage at `/mnt/nvme-1tb` | **REMOVED**; `felhom-backup` available **928787076 KiB after == before**, `local-lvm` **38.84 %** unchanged — space returned |
| 3 — hub | customer **`sess-f`**, host **`sess-f-2670b5`**, appliance **14** | **GATE-BLOCKED — command recorded below.** Cascade attempted, correctly refused **HTTP 409** (host still ONLINE, `customer_delete.go:220-228`). Deletable once it ages ONLINE→DOWN, exactly as `sess-e` was this run |
`pvesm status` on demo-hp before this run, for the layer-2 comparison at teardown: