Overnight unattended stress/regression on the demo node (9201). Platform resilience + safety strong: all fault-recovery + reboot/hard-reset recovery passed (58-126s; sysrq hard reset 103s), 2.5h soak 0 breaks, DR restore proven sound. Raw 25 FAIL triaged: ~10 collateral of one app finding (F1 vaultwarden SMTP crash-loop), ~8 harness mis-scores of correct product refusals (single-flight 409, needs-confirmation), rest harness-calibration incl. the P9 volid-format bug (DR mechanism itself works - recovered 9201 manually from R1). Genuine findings: F1 vaultwarden SMTP_HOST (HIGH), F2 decommission agent-403 (MED), F3 gitea-outage sync recovery (MED). No production code changed; demo left running+healthy at R1, notifications restored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
15 KiB
CAMPAIGN — "No-mercy" unattended full-system test (2026-07-06)
Unattended pilot-readiness stress + regression campaign on the demo node (felhom-pve / guest 9201), run overnight while the operator slept. No production code changed. Throwaway harness + evidence bundle live on DooPlex (
~/campaign-nomercy-2026-07-05/), never committed. This document is the one sanctioned artifact.Headline: the platform's resilience and safety came out strong — every fault-injection and crash-recovery scenario passed, the destroy-and-restore DR path is proven sound, the soak ran 2.5 h with zero invariant breaks, and every safety gate held. The raw FAIL tally (25) is inflated and misleading: ~10 FAILs are collateral of a single real app finding (vaultwarden crash-loop), ~8 are the product correctly refusing an operation that the harness then mis-scored, and the rest are harness-calibration bugs (incl. the one that made the P9 finale read FAIL even though the restore itself works). The genuine product findings are a short list, all MEDIUM-or-below, plus one HIGH-interest app-config bug.
Run header
| Item | Value |
|---|---|
| Date run | 2026-07-06, ~07:40–12:13 CEST (P0 supervised 07:40; P1–P10 detached 08:09–12:13) |
| Host | felhom-pve (192.168.0.162), node demo-felhom |
| Guest | 9201 (demo-felhom), LAN 192.168.0.127 |
| Controller / Agent / Hub | 0.100.0 / 0.72.0 / 0.35.0 (== baselines; git heads af7ea0b / 26664d6 / 61f4898) |
| Auth mode | DISABLED by deployed config (no dashboard password → CSRF exempt); T-AUTH → XFAIL |
| Runner | on DooPlex (survives the host reboots/hard-reset); detached setsid nohup; journal-resumable; each test in an isolated subshell |
| RNG seed (soak) | 20260706 |
| Restore points | R0 ct/9201/2026-07-06T05:40:07Z (verify ok), R0.5 (P2), R1 ct/9201/2026-07-06T06:47:23Z (verify ok) |
End state (operator's first-coffee summary)
- Guest 9201 was destroyed and restored during the finale. The automated restore hit a harness bug (below); CC recovered it manually from R1 — 9201 is running, controller healthy, all three USB drives re-bound, and the full app stack is back. State is as of R1 (2026-07-06 06:47 UTC); anything written to guest volumes during P7/P8 is expected-lost (by DR design — restore returns to snapshot state).
- Only unhealthy container:
vaultwarden(crash-looping — see Finding F1). Everything else is healthy: nextcloud, navidrome, gitea, filebrowser, cloudflared, paperless, mealie, calcom, rallly, sparkyfitness, uptime-kuma, actualbudget, felhom-controller. - Notifications restored to the original 11 events (email nagyfenyvesi.viktor@gmail.com, cooldown 6 h) and verified. They were muted for the run and P10 could not un-mute (controller was down mid-finale); CC restored them manually.
- No chaos residue: 0 leftover auto-revert timers, no disk fillers, no stray iptables DROP rules, USB drives re-authorized (device letters re-enumerated sdb↔sdd after the unplug tests — cosmetic; drives mount by stable name).
- Password status: unchanged (auth remained disabled per the operator's kickoff choice).
Result tally (raw, then triaged)
| Status | Raw count |
|---|---|
| PASS | 24 |
| FAIL | 25 |
| XFAIL | 2 |
| SKIP | 4 |
After triage the 25 FAILs decompose as: ~10 collateral of Finding F1 (a single crash-looping app failing health-embedded assertions), ~8 harness-calibration mis-scores (the product behaved correctly), 3–4 genuine findings, and the P9/P10 pair caused by one harness bug (DR restore is sound). Details below; the matrix marks each FAIL's class.
Genuine findings (product — evidence packages, hypotheses labelled)
F1 — vaultwarden crash-loops: app-email shim sets SMTP_FROM without SMTP_HOST — HIGH interest
- Symptom: vaultwarden container in
Restartingthroughout. Container log (verbatim): "Error loading config: Both SMTP_HOST and SMTP_FROM need to be set for email support without USE_SENDMAIL". - Repro: deploy vaultwarden with
app_email.enabled=truein settings (the demo's state). The mailrelay/app-email integration injectsSMTP_FROM=…@felhom.euinto the compose env but notSMTP_HOST→ vaultwarden refuses to start and crash-loops. - Blast radius (why the FAIL count looks bad): vaultwarden is the highest-stakes app and is
embedded in many assertions, so its crash collateral-failed: T-DEPLOY-SET, T-CANARY-PLANT,
T-LIFECYCLE, T-REMOVE-REDEPLOY, T-BAK-FULL, T-RST-VW/TIER2 (skipped — no vw canary), T-FLT-DOCKERD,
and all four T-RBT-* reboot tests (their
assert_stackwaits on vaultwarden reaching health). - Hypothesis (labelled): the app-email SMTP shim (
internal/mailrelay, catalog vaultwarden template) should inject bothSMTP_HOST(the relay endpoint) andSMTP_FROM, or vaultwarden's template should gate SMTP on both being present. Compare with other SMTP apps — likely affects any app whose image hard-requiresSMTP_HOSTwhenSMTP_FROMis set. Cross-check againstSPIKE-smtp-app-relay-2026-06-28.md. - Not fixed (report-only). This is the single most valuable finding of the run.
F2 — decommission (anyway) on a user-data drive: agent returns HTTP 403 — MEDIUM, verify
- Symptom:
POST /api/storage/decommission {mode:"anyway"}→ controller 502 wrappingagentapi: POST /disks/decommission: HTTP 403. - Question to resolve: is the agent's 403 correct gating (decommission is operator-signed /
role-protected even for user-data) or a real gap (the customer-facing "decommission anyway"
flow can't complete)? The storage-authz redesign makes system/backup operator-only, but this was a
user-data drive, so a 403 here may be an over-broad gate. Evidence:
evidence/T-STG-DECOM-ANYWAY/decom_anyway.txt. - Note:
T-STG-DECOM-MIGRATE(409) is not this — it was a correct backup-in-progress refusal (see harness note H3).
F3 — Gitea-outage: catalog sync does not recover cleanly after connectivity is restored — MEDIUM, verify
- Symptom: with the Gitea IP blocked from the guest,
POST /api/syncfailed (expected/graceful, controller stayed up — good); after unblocking, the follow-up sync did not return 2xx within the test window. Evidence:evidence/T-FLT-GITEA-OUT/sync_after.txt. - Hypothesis (labelled): either a sync backoff/cooldown longer than the test's post-unblock wait, or a cached failure state not cleared on recovery. Contrast with T-FLT-HUB-OUT which PASSED (hub report resumed after unblock). Worth confirming whether catalog sync self-heals on a longer horizon.
F4 — DB restore returned HTTP 000 — LOW, likely transient/collateral
T-RST-DB(paperless DB restore) recorded HTTP 000 (connection failed) at that instant — most likely a transient controller unavailability during the restore suite rather than a defect. Re-run in the targeted P1–P3 re-run (below) to confirm.
Strong positive results (real signal — do not lose these under the FAIL count)
Resilience / fault recovery — 6/6 PASS, with measured recovery times
| Scenario | Result | Time-to-recover |
|---|---|---|
Controller kill -9 (real SIGKILL of the process, mid-backup) |
PASS — restart policy recovered, API 200 | ~ (container-up) |
Guest hard pct stop mid-backup (power-loss) |
PASS — journal resume, canaries intact | 44 s |
Agent kill -9 on host |
PASS — systemd restart, clean binds | 6 s |
| App crash-loop (DB dependency knocked over) | PASS — detected + alert + recovered on repair | 45 s |
| Cloudflared down → external down → recover | PASS — external tunnel returned | (self-heal) |
| Hub outage (iptables block, auto-revert) | PASS — controller kept serving, report resumed | — |
| dockerd restart reconverge | 320 s | — |
Host reboot storms — recovery WORKED (the T-RBT FAILs are collateral of F1)
The T-RBT-* tests are marked FAIL only because assert_stack waits on vaultwarden (F1) reaching
health. The host + controller + apps actually recovered every time, proven by the recorded
times: clean reboot 126 s, reboot mid-backup 69 s, reboot mid-migration 58 s, and a true
sysrq-b HARD power-loss 103 s. Filesystems came back clean; the demo node survived a hard reset
and self-restored in under two minutes.
Safety gates — all held
T-STG-SEC-FORMATPASS: type-to-confirm enforced, non-candidate refused, and the/dev/sdaroot-disk fence correctly refused (theFENCE/FAIL journal line is that self-test tripping the fence on purpose — it is proof the fence works, not a failure).T-SEC-PROTECTEDPASS: traefik/cloudflared/felhom-controller/filebrowser refuse stop/remove and stay Up.T-BAK-SINGLEFLIGHTPASS: second backup correctly 409'd.T-RST-NEGPASS: bad restore ids/stacks refused cleanly with zero writes under target paths.- Backups never touched any namespace/group but
ct/9201(T-BAK-PBSPASS).
Stability & DR
- Soak: 76 ops over ~2.5 h, 0 invariant breaks (seed 20260706) — controller stayed < 5 s responsive throughout, no unexpected 5xx, no state-model divergence, no RSS leak signal.
- CPU soak: 20 min stress-ng, worst page latency 172 ms (< 5 s target).
- DR destroy-and-restore is proven sound. The PBS whole-guest restore mechanism, transparent decryption, 3-volume layout, drive re-bind, and full app recovery all work — demonstrated by the successful manual recovery from R1. The finale's automated FAIL was purely the harness bug H1.
Harness bugs found (fix before any re-run — some already fixed in the workspace copy)
| # | Bug | Effect | Status |
|---|---|---|---|
| H1 | P9 built the pct restore volid from the epoch backup-time, not the ISO-8601 form → unable to parse PBS volume name. Destroy succeeded, restore couldn't parse, node left down. |
Caused T-P9-DESTROY-RESTORE FAIL and, downstream, T-P10-NOTIF-RESTORE FAIL (controller down during P10). DR mechanism itself is fine. | FIXED (epoch→ISO in the snapshot pointer) |
| H2 | assert_stack / dockerd / reboot assertions hard-depend on a single app (vaultwarden) reaching health. |
Turned F1 into ~10 collateral FAILs. | Fix: assert controller+agent+binds+tunnel; treat per-app health as informational. |
| H3 | Storage lifecycle tests don't serialize around backups. | MIGRATE-ALL/APP + DECOM-MIGRATE got correct 409 "backup in progress" but were scored FAIL. | Fix: wait for /api/backup/status idle before storage ops. |
| H4 | Enrollment test didn't handle the already-enrolled/mounted precondition. | T-STG-ENROLL scored FAIL on a correct 409 needs-confirmation. | Fix: pre-clean or expect the confirmation branch. |
| H5 | Deploy-values builder sent DOMAIN=<random secret> (DOMAIN is auto-filled by the controller). |
Latent — did not cause the vw crash (F1 did), but would misconfigure DOMAIN. | Fix: never send DOMAIN. |
| H6 | T-PAGE-ALL treated /debug as must-be-200. |
FAIL on /debug→404, which is by-design when debug mode is off (12/12 real pages 200). |
Reclassify XFAIL. |
| H7 | T-RES-MEMGUARD expected the 384 MB mem-request guard to fire, but the guest had ample free RAM (avail 10.2 GB) so it correctly didn't. |
FAIL on absence of a refusal that wasn't warranted. | Fix: deploy enough to actually cross the reserve, or assert the accounting only. |
| H8 | Journal notes with embedded newlines split a TSV row (one stray blank line). | Cosmetic. | Fix: strip newlines in journal_set notes. |
Recommended follow-up: with H1–H5 fixed and F1 worked around (set SMTP_HOST or disable
app-email for the restore centrepiece), re-run P1–P3 targeted to get clean, real backup/restore
(F3-volume-tar / C2 Tier-2) coverage on a healthy app set — this is the one piece of pilot-critical
coverage the vaultwarden crash denied us tonight.
Test matrix (id | class | result | note)
| Test | Verdict | Note |
|---|---|---|
| T-P0-INVENTORY / VERSIONS / R0 / NOTIF / SMOKE | PASS | preflight clean; R0 verified ok |
| T-PAGE-ALL | FAIL → harness (H6) | only /debug→404 (by-design); 12/12 real pages 200 |
| T-DEPLOY-SET | FAIL → collateral F1 | 6/7 apps healthy; vaultwarden crash-loop |
| T-CANARY-PLANT / T-LIFECYCLE / T-REMOVE-REDEPLOY | FAIL → collateral F1 | vaultwarden-dependent |
| T-SYNC-IDEMPOTENT | PASS | zero writes on re-sync |
| T-SEC-PROTECTED | PASS | protected stacks refuse + stay Up |
| T-AUTH-1 / T-AUTH-2 | XFAIL | auth disabled by deployed config (known) |
| T-BAK-API / T-BAK-SINGLEFLIGHT / T-BAK-PBS | PASS | API truthful; single-flight 409; group-scoped |
| T-BAK-FULL | FAIL → collateral F1 | vaultwarden volume tar absent (app never ran) |
| T-BAK-OFFBOX | SKIP | needs provisioned SFTP key (unattended) |
| T-RST-NEG | PASS | bad restores refused, zero writes |
| T-RST-DB | FAIL → F4 (LOW) | HTTP 000 (likely transient) |
| T-RST-VW / T-RST-TIER2 | SKIP → collateral F1 | no vaultwarden canary (app crashed) |
| T-STG-SEC-FORMAT | PASS | type-to-confirm + /dev/sda fence + mkfs-never-ran |
| T-STG-RECOMMISSION | PASS | drive recommissioned, apps recoverable |
| T-STG-ENROLL | FAIL → harness (H4) | correct 409 needs-confirmation (device mounted) |
| T-STG-MIGRATE-ALL / -APP / -NEG, T-STG-DECOM-MIGRATE | FAIL → harness (H3) | correct 409 "backup in progress" |
| T-STG-DECOM-ANYWAY | FAIL → F2 (MED) | agent POST /disks/decommission 403 — verify |
| T-STG-UNPLUG / T-STG-FULL | FAIL → mixed | partial; re-run after H3 |
| T-STG-NET | SKIP | needs provisioned NFS/SMB export |
| T-FLT-KILL9 / POWERLOSS-GUEST / AGENT / CRASHLOOP / CF / HUB-OUT | PASS | full fault recovery (times above) |
| T-FLT-DOCKERD | FAIL → collateral F1 | reconverge saw vw=restarting |
| T-FLT-GITEA-OUT | FAIL → F3 (MED) | sync didn't recover in-window post-unblock |
| T-RBT-CLEAN / MIDBACKUP / MIDMIGRATION / HARD | FAIL → collateral F1 / H2 | host recovered 58–126 s (hard reset 103 s) |
| T-RES-CPU | PASS | 20 min soak, latency 172 ms |
| T-RES-MEMGUARD | FAIL → harness (H7) | ample RAM; guard correctly didn't fire |
| T-SOAK-LOOP | PASS | 76 ops, 0 invariant breaks |
| T-P9-DESTROY-RESTORE | FAIL → harness (H1) | volid format; DR proven via manual recovery |
| T-P10-NOTIF-RESTORE | FAIL → consequence of H1 | controller down during P10; restored manually |
| T-P10-CLEANUP / ENDSTATE / REPORT | PASS | no residue; end state captured |
What this campaign does NOT prove
- Offsite escrow restore with the operator-held recovery code R — cannot be unattended.
- True-CGNAT WG tunnel behaviour — no CGNAT vantage from the LAN runner.
- Clean backup/restore of the highest-stakes app — denied tonight by F1; recover via the targeted P1–P3 re-run.
- Non-technical-user UX — no human-in-the-loop signal.
- Any assertion reading a best-effort endpoint (alert/monitoring HTML scrape, hub-report timestamp) is corroborating, not authoritative.
Not changed
No production code, versions, or configuration were changed. The single change is this document. The demo node was destroyed and restored (authorized) and left running and healthy at R1 state, with notifications restored. Evidence bundle retained on DooPlex, never committed; no secrets in this report.