Outcome A, qualified. Not B and not C. It does not degrade: 5 of 5 restarts of 12.3 on an 11.6 datadir, readback passed every time, mariadb_upgrade_info unchanged, the entrypoint line never escalated past [Note]. It also never heals - the engine answers 'Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!' on every start and will forever. The trade R-459 was expected to produce is not real. Converting properly SUCCEEDS across the multi-major jump, takes 7 seconds, backs up the system database unasked - and putting 11.6 back afterwards STILL starts and serves the data. So the operator is being handed a cheap correction, not a choice between a correct engine and a reversible one. The exit-code polarity was measured rather than read: 0 means the upgrade IS needed, 1 means it is not. Assuming either the flag name or the polarity would have inverted the headline. And run without credentials the same command returns a confident-looking FATAL ERROR that is an auth failure. R-464: after converting and going back, the entrypoint prints 'MariaDB upgrade not required' on a state the same engine calls an unsupported downgrade. The obvious cheap instrument for R-459 would have been to grep for that line, and it would have reported fine for the broken case. R-463: the PostgreSQL analogue, deliberately NOT measured here. 11 templates, 8 on postgres:16-alpine, register grep for pg_upgrade returns zero. The two engines fail in OPPOSITE directions - MariaDB skips quietly, Postgres refuses to start - so that one cannot hide; it presents as eight apps down at once. No template changed. Teardown all three layers, hub checked rather than asserted, local-lvm 30.53 percent before and after.
9.9 KiB
REPORT — SPIKE R-459: is a skipped MariaDB upgrade harmless, and what does fixing it cost? (2026-09-06)
Overwritten each session. Nothing durable lives only here.
OUTCOME A, qualified — and the trade this task was commissioned to price DOES NOT EXIST. The skipped conversion is stable but never self-resolving. Fixing it costs 7 seconds and does not cost the ability to abort, which is what B assumed. It is not C either: the conversion succeeded across the multi-major jump.
1. Confirmed baselines — none had moved
| repo | task's baseline | found |
|---|---|---|
| app-catalog-felhom.eu | 0474ce387e6f |
0474ce387e6f |
| felhom.eu | a1a6c73fe132 |
a1a6c73fe132 |
| felhom-controller | bab82c471e03 |
not touched |
Highest R- id 462, confirmed. Minted R-463, R-464. No version bump, no release.
The task's §2 table was verified and is exactly right: four MariaDB apps at the stated pins, and
no MARIADB_* env in any of the 53 templates.
2. The outcome, and the evidence that assigns it
| A | unconverted datadir is benign | THIS ONE, qualified — 5 of 5 restarts, no degradation. But the engine says a check is required every start, so "benign" holds only as measured: no decay over restarts, one seeded record, over minutes. |
| B | converting works, the abort dies with it | NO. Converting works; the abort still starts and serves. |
| C | converting fails, the jump is too big | NO. It succeeded in 7 s and took its own backup first. |
3. Observable 1 — mariadb_upgrade_info, all four moments, verbatim
| moment | state | contents |
|---|---|---|
| 1 | fresh 11.6 datadir | 11.6.2-MariaDB (14 bytes) |
| 2 | 12.3 started on it, skip logged | 11.6.2-MariaDB — unchanged |
| 3 | after 5 restarts of 12.3 | 11.6.2-MariaDB — still unchanged |
| 4 | comparison arm, after conversion | 12.3.3-MariaDB |
The engine serving at moments 2–3 was 12.3.3-MariaDB, confirmed by mariadb --version — so the
mismatch is real and not the wrong container.
4. Observable 2 — the engine's own verdict, invocation, output and exit code
Check mode located in the image, not assumed: --check-if-upgrade-is-needed.
$ docker exec bookstack-db sh -c 'mariadb-upgrade --check-if-upgrade-is-needed \
--user=root --password=$MYSQL_ROOT_PASSWORD'
Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required!
[exit=0]
After conversion:
This installation of MariaDB is already upgraded to 12.3.3-MariaDB.
There is no need to run mariadb-upgrade again.
[exit=1]
The two runs establish the exit-code semantics between them — 0 = needed, 1 = not. Measured, not read from documentation, and worth stating because the polarity is the reverse of the usual convention.
A false start, recorded because it nearly produced the wrong answer: without credentials the same
command returns ERROR 1045 (28000): Access denied … FATAL ERROR: Upgrade failed with exit 1 —
an authentication failure wearing the shape of a verdict. → R-464.
5. Observable 3 — the restart series: 5 of 5
| restart | settled | readback | upgrade_info |
engine check | entrypoint |
|---|---|---|---|---|---|
| 1–5 of 5 | yes, each | pass, each | 11.6.2-MariaDB, unchanged |
Check required!, each |
[Note], never escalated |
It does not degrade. It also never heals. R-459's hypothesis that the Note might become a Warning or an Error is not supported.
6. Observable 4 — the comparison arm
Scratch copy of the template inside the guest with MARIADB_AUTO_UPGRADE=1. Nothing with
MARIADB_ in it was committed to the catalog.
The conversion succeeds, verbatim and in order:
[Note] [Entrypoint]: Starting temporary server
[Note] [Entrypoint]: Backing up system database to system_mysql_backup_11.6.2-MariaDB.sql.zst
[Note] [Entrypoint]: Backing up complete
[Note] [Entrypoint]: Starting mariadb-upgrade
[Note] [Entrypoint]: Finished mariadb-upgrade
Cost: mariadb-upgrade itself 17:33:24 → 17:33:31 = 7 s; whole TO step 35.9 s with it
against ~10.5 s without → ≈ 25 s of extra startup, once. Measured on a nearly empty
database — it works over system tables rather than row data, so it should scale with table count,
but this run did not measure that and 7 s must not be quoted as a fleet figure.
The decision-relevant fact — §14.6 of the task
Converting does NOT kill the abort. With the datadir at 12.3.3-MariaDB, putting 11.6 back:
abort: starts-and-serves settled 10.5 s readback: PASS
survived a further restart: Up 25 seconds (healthy), upgrade_info: 12.3.3-MariaDB
…but the engine calls that state unsupported, and the entrypoint hides it. → R-464:
| asked | answer |
|---|---|
| the entrypoint, every start | [Note] [Entrypoint]: MariaDB upgrade not required |
mariadb-upgrade --check-if-upgrade-is-needed |
FATAL ERROR: Version mismatch (12.3.3-MariaDB -> 11.6.2-MariaDB): Trying to downgrade from a higher to lower version is not supported! |
So "the abort works" is an observation that it started and served — not a claim the datadir is sound. One restart cycle, one seeded record. Saying more would repeat the mistake this task exists to correct.
7. The harness change, and E3b re-run showing it
upgrade-test.py gained engine_state_after, reported beside the verdict and never folded
into it — an unconverted datadir is not known to be a failure, so a verdict that said so would
encode an unproven judgement. E3b re-run:
"verdict": "proven",
"engine_state_after": {"bookstack-db": {
"image": "mariadb:12.3",
"answer": "11.6.2-MariaDB| Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required! [exit=0]"}}
The exact thing the harness was blind to, now visible next to a green verdict. A PostgreSQL probe is included; it has never run against a real Postgres major (→ R-463).
8. Register — 206 open before, 208 after; closed 168, unchanged
| row | disposition |
|---|---|
| R-459 | NARROWED, P2 → P3, WAITING-ON-OPERATOR. Consequence measured; the expected trade does not exist. The fleet-wide env change remains the operator's call (4 apps). VIKTOR rules, CC implements |
| R-463 | OPENED, P2-MEDIUM, CC — the PostgreSQL analogue. 11 templates, 8 on postgres:16-alpine; register grep for pg_upgrade/"postgres major" returned 0, confirmed twice. Deliberately not measured here. The two engines fail in opposite directions: MariaDB skips quietly, Postgres refuses to start — so this one cannot hide, it presents as eight apps down at once |
| R-464 | OPENED, P3-LOW, CC — MariaDB upgrade not required is printed on an unsupported downgrade, so that line cannot be a soundness signal. Same class as "presence is not success" and R-443's HTTP 200 over a crash-looping app |
9. Teardown — three layers
| layer | result |
|---|---|
| 1 — machine | guest 9402 destroyed; pct list shows only 9201. scratch-r459 removed. Downloaded template deleted. |
| 2 — host | local-lvm 30.53 % before and after — never touched. local 19 630 088 → 19 631 740 KiB (+1.6 MB). pct fstrim 9402: 35.9 GiB trimmed. Guest peak 3.2 GB / 2.10 GB images. |
| 3 — hub | Checked, not asserted: /hosts = exactly demo-felhom-8363b5, demo-hp-bb76ea; 0 customers. This run created no customer, no appliance, no host record. |
Evidence off after each arm, before teardown: documentation/audits/r459-spike-2026-09-06/
(20 files, 164 KB). Scanned for secrets before commit: clean.
10. Claims in the task that turned out to be wrong, named
- §11's venue is still stale, exactly as R-461 records.
/mnt/nvme-1tbdoes not exist — the NVMe is at/mnt/hdd_1. The scratch storage went at its root, honouring the rule's reason;local-lvmread 30.53 % before and after.drill-r50(VM 300) still does not exist —qm listreturns nothing. Both were already filed as R-461 yesterday; this run is the second session to work around them, which is the cost that row predicts. - §6 Observable 2 assumed a check mode would exist but told me not to assume its flag — correct,
and the caution earned its place. The flag is
--check-if-upgrade-is-needed, and its exit-code polarity is the reverse of the usual convention (0 = work needed). Assuming either the name or the polarity would have inverted the headline. - §7's Outcome B was the expected result and it is FALSE. The abort survives a real conversion. The task was right to name three outcomes and to say "do not steer" — the run steered nowhere and landed outside the shape the brief most anticipated.
- §9's Postgres numbers were exact: 11 templates, 8 on
postgres:16-alpine, register grep 0. - §4.7's pointer was right —
mariadb_upgrade_infowas already visible in the persistence-sweep probe, and it is the observable that carried this task.
11. Observations — noticed, documented, NOT acted on
mariadb-upgradewithout credentials returns a confident-looking failure that is an auth error. FILED: R-464, which carries it as the second half of the row.- The conversion leaves its own backup in the datadir (
system_mysql_backup_*.sql.zst, 622 437 bytes here), which any volume-level backup will then copy. NOT-A-FINDING: it is upstream's deliberate safety net and it is harmless; recording it in the audit is enough to stop the next person reporting an unexplained file. - Python heredocs do not survive three shells (
ssh→pct exec→bash). Two scripts had to be written locally and pushed as files. NOT-A-FINDING: it is a working technique, not a property of the product, and it is now written into the audit where the next session will meet it.