Files
felhom.eu/documentation/audits/update-arc-gaps-2026-09-21/08-the-lock-proven-live.txt
T
admin c85262111c
gates / gates (push) Successful in 23s
The update arc's two missing measurements, the lock, and the floor to 0.260.0
Part 0 — floor raised to 0.260.0, MinAgent 0.131.0 declared. 3 boxes below, all
down or blocked; both demo boxes SERVED.

Part 1 (R-610) — the DANGEROUS power cut, measured three times with three apps and
two cut mechanisms. All ended honest: resumed, completed, and pinned/installed/live
compose/docker inspect all agreed. vikunja's 2.6.0 migration had ALREADY run 0.64 s
after the cut decision and the seeded data read back intact — so the branch that is
one step from old-binary-on-migrated-database is now evidence, not argument.
Instrument limit stated: `starting` lasts under a second; all three landed in
`verifying`, which RecoverUpdates handles in the same branch.

Part 3 (R-611) — the night the previous session skipped without saying so. An app
updated with nobody pressing anything; a terminally-refused app was pressed exactly
once and never again over three passes. The unattended HOLD was NOT produced: the
within-a-major rule correctly refused the broken edge before it was attempted, so
Q4 still rests on the attended hold from slice 4. Said plainly rather than implied.

Rows: closed R-608/609/610/611; opened R-612 (P1 wishlist unusable on a fresh
install, and its error is a lie), R-613 (uptime-kuma healthy on its setup wizard),
R-614 (stale update phase survives a redeploy). R-520's pointer corrected.

Catalog: two drill pairs, both reverted; every image line byte-identical to
ff9717d3. The alpine:3.20 negative control a security review flagged is cleared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 15:00:09 +02:00

49 lines
3.0 KiB
Plaintext

# 08 — THE v0.261.0 LOCK, PROVEN LIVE (R-608), with a positive AND a negative control
#
# Guest 9202, controller v0.261.0, 2026-09-21. The question is not "is the callback wired" but
# "does the swap actually refuse, and is it OUR refusal doing it".
== WHY A CONTROL WAS NEEDED, AND WHY THIS ONE IS THE RIGHT ONE ==
Guest 9202 has NO host agent wired, so `TriggerUpdate` refuses anyway — with the AGENT's sentence.
That makes it the perfect negative control: in `TriggerUpdate` the new app-update check sits BEFORE
the agent check, so if the lock fires we see OUR sentence and if it does not we see the AGENT's.
Two different sentences, one probe. It also makes the probe SAFE: no swap can reach a machine.
(Self-update is `enabled: false` on this guest by design. It was turned on for this probe and
turned back OFF immediately afterwards — the original file was copied to
/root/controller.yaml.pre-lock-probe first and restored from it. Verified reverted below.)
== CONTROL A — self-update status, no app update running ==
GET /api/selfupdate/status -> {"ok":true,"data":{"running":false}}
== CONTROL B (NEGATIVE) — the MANUAL trigger with NO app update running ==
12:39:55.917 POST /api/selfupdate/update
-> {"ok":false,"error":"A frissites nem erheto el (nincs gazda-ugynok)"}
i.e. the AGENT refusal. The lock is NOT firing, because nothing is in flight. Correct.
== THE PROBE (POSITIVE) — the MANUAL trigger WHILE an app update is in flight ==
12:39:55.941 POST /api/stacks/uptime-kuma/update -> 202 "Frissites elindult"
12:39:56.241 app update IN FLIGHT, phase = safety-dump
12:39:56.244 POST /api/selfupdate/update
-> {"ok":false,"error":"Egy alkalmazas frissitese eppen folyamatban van. A vezerlo frissitese utana inditható."}
THE SENTENCE CHANGED. That is the v0.261.0 gate firing, and firing BEFORE the agent check.
12:39:56.270 GET /api/selfupdate/status -> {"running":false} — nothing was started.
(accents transliterated here only to keep this file ASCII-searchable; intact on the wire)
== WHAT THE PHASE TELLS US, and it is worth stating ==
The probe landed in `safety-dump`. That phase is NOT covered by the pre-existing `backupRunning`
gate — only `backing-up` is, because that one takes the backup single-flight. So the probe hit
exactly the window the new lock exists for, rather than a window that was already protected.
== THE REVERSE DIRECTION — NOT staged live, and why ==
`UpdatePreflight` refusing an app update with reason `self_updating` while the controller swaps is
covered by `TestR608_PreflightRefusesWhileTheControllerSwaps` (a consequence test, red-proofed by
deleting the block). Staging it LIVE would need a real controller swap in flight, which needs a host
agent this guest does not have and a genuine image swap mid-measurement. **Not measured live. Stated
as a gap rather than implied.**
== CONFIG RESTORED ==
self_update.enabled is back to `false` on guest 9202, verified by re-reading the file after the
restore, and the controller was restarted on the restored config.