Files
felhom.eu/documentation/audits/CAMPAIGN-nomercy-2026-07-06.md
T
admin 658e78d495 docs(audit): no-mercy full-system campaign report (2026-07-06)
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
2026-07-06 12:23:12 +02:00

15 KiB
Raw Blame History

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:4012:13 CEST (P0 supervised 07:40; P1P10 detached 08:0912: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), 34 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_HOSTHIGH interest

  • Symptom: vaultwarden container in Restarting throughout. 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=true in settings (the demo's state). The mailrelay/app-email integration injects SMTP_FROM=…@felhom.eu into the compose env but not SMTP_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_stack waits on vaultwarden reaching health).
  • Hypothesis (labelled): the app-email SMTP shim (internal/mailrelay, catalog vaultwarden template) should inject both SMTP_HOST (the relay endpoint) and SMTP_FROM, or vaultwarden's template should gate SMTP on both being present. Compare with other SMTP apps — likely affects any app whose image hard-requires SMTP_HOST when SMTP_FROM is set. Cross-check against SPIKE-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 wrapping agentapi: 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/sync failed (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 P1P3 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-FORMAT PASS: type-to-confirm enforced, non-candidate refused, and the /dev/sda root-disk fence correctly refused (the FENCE/FAIL journal line is that self-test tripping the fence on purpose — it is proof the fence works, not a failure).
  • T-SEC-PROTECTED PASS: traefik/cloudflared/felhom-controller/filebrowser refuse stop/remove and stay Up.
  • T-BAK-SINGLEFLIGHT PASS: second backup correctly 409'd.
  • T-RST-NEG PASS: bad restore ids/stacks refused cleanly with zero writes under target paths.
  • Backups never touched any namespace/group but ct/9201 (T-BAK-PBS PASS).

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 H1H5 fixed and F1 worked around (set SMTP_HOST or disable app-email for the restore centrepiece), re-run P1P3 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 58126 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 P1P3 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.