Evidence off the machine at the end of the phases that produced it (R-320). Teardown follows. PHASE 2 — the two database engines, through the REAL Update button: - MariaDB 11.6 -> 12.3 on nextcloud: PROVEN, and pressed through the button for the first time. All four SPIKE-r459 observables: the datadir's own record moved 11.6.2 -> 12.3.3; the engine itself says "already upgraded ... no need to run mariadb-upgrade again"; the entrypoint says "Major version upgrade detected ... Check required!" and then STARTED and FINISHED it (not the `skipped due to $MARIADB_AUTO_UPGRADE` line R-459 feared); and the engine took its own pre-upgrade backup, 631 905 B. The seeded Nextcloud account read back. - PostgreSQL 16 -> 17 on docmost: FAILED exactly as R-463 predicted and nobody had measured. 5.1 s to held; the pin named 17 while nothing ran; the restore brought it back in 29.1 s. The engine's REFUSAL LINE was destroyed by failAndHold before any probe could read it, so it was REPRODUCED INDEPENDENTLY with a control on every step (R-320). PHASE 3 — the bad days. B1 produced THE UNATTENDED HOLD, which this project has never had: the caller pressed once with nobody watching, the app held after 312.9 s, and passes 2 and 3 pressed nothing. B2 put the pin back on a pull failure in 1.0 s. B3 refused `busy` six times. B4 showed there is NO single-flight — 5 of 5 updates ran at once and all ended honest. B5 cut the power in `backing-up` and the box recovered itself and said so. B7 refused under the 2 GB floor. B9 found R-458's risk narrower than the row states. PHASE 4 — every badge on the box is TRUE, and the held app answers all four of Q4's questions. FINDINGS, five new and three corrections to existing rows. The one that matters: R-618 is P1 — two templates name a health probe the app does not answer, and because the guarded update waits on that same probe, a SUCCESSFUL update ends by STOPPING a working app. Measured: tandoor served HTTP 200 on the new version at four samples across five minutes and was then stopped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
3.8 KiB
Phase 2.1 — MariaDB across a major, through the REAL Update button. PROVEN.
The first time this has ever been pressed through the product. R-459 measured the conversion on the harness; R-469 lifted the gate on 2026-09-21 morning; the BUTTON had never been used for it.
Venue: guest 9202, controller v0.261.0. App: nextcloud — the app image does NOT move
(nextcloud:34.0.1-apache throughout); only the mariadb: sidecar, 11.6 → 12.3.
Seeded and read back through the app's own CLI: occ user:add created drillb9a9d0
("The account … was created successfully"), occ user:info found it before the move (control C1)
and after it, each time with the fixture's own negative control — a uid that cannot exist must
read as absent — passing.
The four observables of SPIKE-r459-mariadb-upgrade-2026-09-06
| # | observable | before | after |
|---|---|---|---|
| 1 | mariadb_upgrade_info — the datadir's own record |
11.6.2-MariaDB |
12.3.3-MariaDB |
| 2 | the engine's OWN check (R-464: never the log line) | "Access denied" — the probe was unauthenticated, see the note below | „This installation of MariaDB is already upgraded to 12.3.3-MariaDB. There is no need to run mariadb-upgrade again." |
| 3 | the entrypoint's own lines | — | „Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!" → „Starting mariadb-upgrade" → „Finished mariadb-upgrade" |
| 4 | the engine's own pre-upgrade backup | absent | /var/lib/mysql/system_mysql_backup_11.6.2-MariaDB.sql.zst, 631 905 bytes |
| 5 | the version actually running | 11.6.2-MariaDB-ubu2404 |
12.3.3-MariaDB-ubu2404 |
| 6 | container | mariadb:11.6 running, 0 restarts |
mariadb:12.3 running, 0 restarts |
Observable 3 is the one that matters most, because R-459's whole finding was that MariaDB can
apply a major and skip the conversion quietly, printing skipped due to $MARIADB_AUTO_UPGRADE.
That line is absent; the entrypoint says the conversion was detected, started and finished.
And observable 2 confirms R-464's own warning about the instrument: the command exits 1 when it means no upgrade is needed. The exit code carries no usable information here; the SENTENCE is the discriminator, which is exactly what R-464 says.
The four version observables, all agreeing
pinned nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
installed nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
compose nextcloud:34.0.1-apache · mariadb:12.3 · redis:7-alpine
inspect nextcloud-db mariadb:12.3 running=true restarts=0
nextcloud nextcloud:34.0.1-apache running=true restarts=0
nextcloud-redis redis:7-alpine running=true restarts=0
Two honest notes on the instrument
- The first
engine-before.txtcapture asked the engine without credentials and gotERROR 1045 Access deniedwhere observable 2 should be. The probe was corrected mid-run to pass--user=root --password=$MARIADB_ROOT_PASSWORD(the recipeupgrade-test.py's ownENGINE_PROBESalready uses) and the AFTER capture above was retaken with it. The BEFORE value for observable 2 is therefore not measured, and is stated as such rather than inferred from observable 1. - The
lsin observable 4 printed(no pre-upgrade backup file present)after listing the file, because it globs two patterns and only one matched. The file is there; the fallback text is the shell's, not the engine's. Noted so nobody reads the line as a contradiction.
What this settles
The MariaDB half of 09 §3 decision 5 and R-469's lift is now proven through the button a
household presses, not only on the harness — conversion detected, run, finished, the engine's own
pre-upgrade backup taken, and the customer's account still there afterwards.