Files
felhom.eu/documentation/audits/update-arc-gaps-2026-09-21/04-scenarioA-starting-cut.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

130 lines
8.3 KiB
Plaintext

# 04 — SCENARIO A: vikunja 2.3.0 -> 2.6.0, cut decided in `starting`
# guest 9202 (demo-hp-scratch), controller 0.260.0, 2026-09-21
#
# VERDICT: the box ended HONEST. The update resumed after the reboot and finished `done`;
# all four version observables agree on 2.6.0; the seeded task read back unchanged; the
# journal is gone; the household sentence is „Naprakesz" / "Up to date".
#
# THE ONE THING THAT DID NOT GO AS THE BRIEF ASSUMED — stated first because it changes
# how capture (1) should be read:
# The DECISION was taken in `starting` (12:28:26.244 UTC, poll cadence ~21 ms).
# But `pct stop 9202` took 3 759 ms to return, and the update moved on inside that window.
# The journal read OFF the stopped guest proves where the box actually died:
# phase "verifying", not "starting".
# `starting` on this box lasts about 0.6 s (12:28:26.244 -> vikunja's own 14:28:26.881+02:00
# migration line). NO instrument available here can land a guest kill inside it: the poll is
# fast enough (21 ms), the CUT COMMAND is not (3.8 s).
# This costs nothing for the measurement, because RecoverUpdates handles `starting` and
# `verifying` in the SAME branch (update.go:909 `case UpdatePhaseStarting, UpdatePhaseVerifying`).
# Scenario A and Scenario B therefore exercise one recovery arm, not two. Recorded, not hidden.
## (1) TIMESTAMP TABLE
poll target : GET /api/stacks/vikunja, HTTPS keep-alive from DooPlex
measured poll cadence : ~21 ms (per-sample HTTP cost 1.1-1.3 ms + 20 ms sleep)
Update pressed : 12:28:23.075 UTC (POST /api/stacks/vikunja/update -> {"accepted":true})
phase AT THE DECISION : "starting" observed 12:28:26.244 UTC
cut command : ssh -S <prewarmed master> demo-hp 'pct stop 9202'
cut command LATENCY : 3 759 ms (returned 12:28:30.006 UTC, rc=0, no output)
phase the box DIED in : "verifying" (from update-journal.json read off the stopped guest)
guest restarted : pct start 9202 returned after 3.6 s; controller up 12:29:48 UTC
total app downtime : ~79 s (vikunja stopped 12:28:26.9 .. restarted 12:29:47.3 UTC)
## (1b) the phase trace, verbatim from the poller
12:28:21.860 updating=False phase='' label='' state=running err=''
12:28:23.088 updating=True phase='safety-dump' label='Adatbázis pillanatkép…' state=running err=''
12:28:23.110 updating=True phase='pinning' label='Új verzió letöltése…' state=running err=''
12:28:23.132 updating=True phase='pulling' label='Új verzió letöltése…' state=running err=''
12:28:26.244 updating=True phase='starting' label='Indítás az új verzióval…' state=running err=''
--- DECISION at 12:28:26.244: phase='starting' -> firing cut: ssh -S /tmp/cm-demohp demo-hp 'pct stop 9202'
--- CUT returned rc=0 after 3759 ms
--- CUT stdout:
--- CUT stderr:
=== TIMESTAMP TABLE (vikunja) ===
poll cadence : measured below
phase at decision : 'starting' at 12:28:26.244 UTC
cut command : ssh -S /tmp/cm-demohp demo-hp 'pct stop 9202'
cut command latency : 3759 ms (returned 12:28:30.006 UTC)
## (1c) update-journal.json read from the STOPPED guest
# The brief's path was WRONG for this guest: /var/lib/lxc/9202/rootfs/var/lib/felhom is an
# EMPTY MOUNTPOINT while the guest is stopped, because mp0 is a separate raw volume
# (nvme-scratch:9202/vm-9202-disk-1.raw). The POSITIVE CONTROL failed there: no catalog-cache/.
/bin/bash: line 131: pct: command not found
# DOES attach mp0, and then the same path is real (catalog-cache/ present).
/bin/bash: line 132: pct: command not found
/bin/bash: line 132: pct: command not found
# Remember to afterwards or refuses.
{
"updates": {
"vikunja": {
"phase": "verifying",
"started_at": "2026-09-21T12:28:23.06699273Z",
"prev_pin": {
"vikunja": "vikunja/vikunja:2.3.0"
},
"prev_compose": "/opt/docker/stacks/vikunja/pre-update-compose.yml",
"prev_applied": "/opt/docker/stacks/vikunja/pre-update-applied.yml",
"proven_copy_at": "2026-09-21T12:26:12Z",
"proven_tier": 1
}
}
}
## (2) RecoverUpdates log lines after the restart, VERBATIM
2026/09/21 12:29:48 update.go:909: [WARN] [stacks] update recovery: vikunja was interrupted in verifying (started 2026-09-21T12:28:23Z) — the new version may have run; marking it Updating and RESUMING the health wait
2026/09/21 12:29:48 update.go:951: [INFO] [stacks] update vikunja: resuming after a controller restart — `up -d` then the health wait
2026/09/21 12:29:48 update.go:858: [INFO] [stacks] update vikunja: phase verifying
2026/09/21 12:29:48 update.go:652: [INFO] [stacks] update vikunja: healthy after 0s (the app's health check passed)
2026/09/21 12:29:49 update.go:658: [INFO] [stacks] update vikunja: DONE in 1m26s
## (3) THE FOUR VERSION OBSERVABLES, SIDE BY SIDE (after recovery)
1_pinned_images : {"vikunja": "vikunja/vikunja:2.6.0"}
2_installed_images : {"vikunja": "vikunja/vikunja:2.6.0"} | digest: {"vikunja": "sha256:417ada6f94e81f0267a"}
(updating=False phase=done label=Frissítve err=- hold=-)
3_live compose line: image: vikunja/vikunja:2.6.0
4_docker inspect : vikunja/vikunja:2.6.0 | RepoDigest: sha256:417ada6f94e81f0267a | started 2026-09-21T12:29:47.287254768Z
-> all four name vikunja/vikunja:2.6.0; installed digest and the running container's
RepoDigest are the same sha256:417ada6f94e81f0267a...
## (4) the seeded data, read back through vikunja's own front door
vikunja version: v2.6.0
TASK 1 'SEED-VIKUNJA-CANARY-9f3c1e-20260921' desc= 'power-cut drill canary'
POSITIVE CONTROL: seed present = True
NEGATIVE CONTROL: absent string present = False
## (4b) the vikunja MIGRATION line, VERBATIM from its container log
3:time=2026-09-21T14:28:26.881+02:00 level=INFO msg="Running migrations…"
5:time=2026-09-21T14:28:26.915+02:00 level=INFO msg="Ran all migrations successfully."
9:time=2026-09-21T14:28:26.922+02:00 level=INFO msg="Vikunja version v2.6.0"
14:time=2026-09-21T14:29:47.713+02:00 level=INFO msg="Running migrations…"
16:time=2026-09-21T14:29:47.734+02:00 level=INFO msg="Ran all migrations successfully."
20:time=2026-09-21T14:29:47.745+02:00 level=INFO msg="Vikunja version v2.6.0"
-> the FIRST migration ran at 14:28:26.881+02:00 = 12:28:26.881 UTC — 0.64 s AFTER the cut
decision and 3.1 s BEFORE pct stop returned. The 2.6.0 schema migration had ALREADY
been applied to the customer's SQLite database when the power went. The pin did not
roll back (that only happens in the pinning/pulling branch), so old binary vs migrated
database never happened here — but it is the failure this branch is one step away from.
## (5) the app page's sentence to the household, both languages
vikunja [hu] http=200
[Naprak] ...tems:center;gap:.5rem"> <span class="stack-state-badge state-run">Fut</span> <span class="tag tag-ok" title="Ez az alkalmazás a legfrissebb elérhető változatot futtatja.">Naprakész</span> <a href="https://tasks.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a> <a href="/stacks/vikunja/logs" class="btn btn-sm btn-outl
NEGATIVE CONTROL 'ZZZNOTPRESENT' found: False
vikunja [en] http=200
[Up to date] ...align-items:center;gap:.5rem"> <span class="stack-state-badge state-run">Running</span> <span class="tag tag-ok" title="This app is running the newest version available.">Up to date</span> <a href="https://tasks.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a> <a href="/stacks/vikunja/logs" class="btn btn-sm btn-outline"
NEGATIVE CONTROL 'ZZZNOTPRESENT' found: False
## (6) done or HELD, and did a journal entry survive?
ended: update_phase=done, update_phase_label='Frissitve', updating=false, hold_reason=none
controller log: 'update vikunja: DONE in 1m26s'
POSITIVE CONTROL: catalog-cache present -> this IS the controller data dir
ls: cannot access '/var/lib/docker/volumes/felhom-controller-data/_data/data/update-journal.json': No such file or directory
-> no journal entry survived the reboot.
## STOP CONDITIONS — none tripped
seeded data gone/unreadable : NO (read back byte-identical)
updating:true that never clears : NO (cleared 1.3 s after boot)
journal surviving a 2nd reboot : N/A, no journal survived the 1st
pin naming one version, container another : NO
resumed update retrying in a loop : NO (one resume, one success)
HOLD with no household sentence : N/A, no hold