Commit Graph

154 Commits

Author SHA1 Message Date
admin 5ffdcecb2d F-LEAK: record all three attempts; the ACL fix is consumed by use, the fenced destroy is the fix 2026-07-28 11:32:45 +02:00
admin 8c793955b1 Campaign 8: F-REBOOT/F-LEAK/F-OBS closed; R-100 filed (offsite staleness reset by failures)
F-LEAK's first fix (pool adoption) was refuted live and is recorded as such. R-100 is
F-CRIT-2's defect class on the hub for the restic tier: isStale reads only LastRun, which
the controller writes unconditionally on failure. Investigated, not fixed.
2026-07-28 11:17:59 +02:00
admin e168600148 docs: F-CRIT-1 + F-A1 shipped (controller v0.179.0); invariant rule
Both marked SHIPPED + PROVEN-LIVE in OPEN-ITEMS and the campaign doc. All three
of Campaign 8's alarm findings are now closed (F-CRIT-1, F-CRIT-2, F-A1).

Adds the standing rule earned by this arc to the versioned workspace CLAUDE.md:
a comment asserting an invariant needs a test pinning it, or it is a wish — with
all six shipped-false-guarantee instances catalogued, and the corollary that a
test should assert the CONSEQUENCE (does the alarm fire?) not the MECHANISM
(does suppression expire?).
2026-07-28 09:48:51 +02:00
admin d0cec9d3f3 docs: F-CRIT-2 shipped (agent v0.106.0) — OPEN-ITEMS, campaign doc, report
F-CRIT-2 marked SHIPPED + PROVEN-LIVE: NewestArchiveTime now counts only
plausibly-complete entries (measured 1 MiB floor), verified by replaying
campaign fault 2 on demo-hp in both directions.

Files two follow-ups:
- R-99: prune never REMOVES phantoms. It does NOT count them toward keep-last
  (dry-run kept 2 real + the phantom), so there is no retention/data-loss bug —
  but they accumulate one per aborted upload.
- F-CRIT-1: filed READY-HIGHEST. Campaign 8's other HIGH finding, untouched by
  this fix.
2026-07-28 08:08:56 +02:00
admin 33a7cc51d3 docs: CAMPAIGN 8 — GC run 2, fault 13 complete, final fleet state
GC run 2 (04:44Z, past the atime boundary) reclaimed 0 B, and the cause is the
campaign itself: the datastore is relatime, so the night's three full verifies,
~14 backups, two GC runs and several restores kept refreshing chunk atimes.
Sampled atimes confirm no chunk was ever 24h5m cold. Not a defect - GC made a
correct, deliberate decision both times - but a real operational property:
on a relatime datastore, activity defers reclamation. The spec's intended
experiment therefore could not complete inside the window; recorded as such.

Fault 13 now complete: backup-vs-restore-test single-flight holds, and a full
two-tier backup ran successfully DURING GC run 2 (both artefacts landed, GC
TASK OK, zero .bad chunks). True three-way overlap remains untested.

Final fleet verification clean on all three hosts. Campaign-caused outage
(F-REBOOT, 9m47s on demo-hp) stated plainly in the final-state section.
2026-07-28 06:59:22 +02:00
admin 6c57658c1b docs: CAMPAIGN 8 — faults 11 and 18 results, F-LEAK root cause, evidence corrections
Tail-of-campaign additions after the Phase D revert (both re-injections declared):
- fault 18 (delete a snapshot mid restore-test): detection PASS, and it ROOT-CAUSES
  F-LEAK — a failed restore-test cannot destroy its own scratch guest (403,
  missing VM.Allocate; the agent token is pool-scoped and a failed restore never
  joins the felhom pool)
- fault 11 (guest reboot mid-backup): new finding F-REBOOT — the backup succeeds
  but the guest never comes back; ~9m47s outage until a manual pct start

Two evidence corrections, both self-inflicted tooling errors:
- pgrep -cf <pattern> matches its own ssh command line, which invalidated fault
  11's first two injections and put one unsound line in fault 9 (withdrawn; that
  finding stands on the controller's own job state)
- ep0 runs Etc/UTC, so its 03:30 prune fires at 05:30 CEST — nearly misread as a
  broken prune job

Nine findings now, still two HIGH. Fleet healthy.
2026-07-28 06:06:57 +02:00
admin 14b0939060 docs: CAMPAIGN 8 — adversarial backup & restore campaign report
Unattended 10h run against demo-felhom, demo-hp and ep0. No production code
changed; findings recorded and ranked, not fixed inline.

8 findings, 2 HIGH — both in the system's ability to report that a backup did
NOT happen:
- F-CRIT-1: an app failing to restart after a quiesce never alarms (invariant
  I1 in main.go:1213 is false for the failed-restart path)
- F-CRIT-2: a failed offsite backup leaves a phantom snapshot that resets the
  tier's freshness clock (NewestArchiveTime has no completeness check)

Retires several never-validated items, including R-87 (first restic restore
round-trip, byte-verified), the full R-88 backoff ladder, age_state=absent,
and the crash-recovery unquiesce under a real SIGKILL.

peti-felhom untouched; ep0 rollback copy intact; fleet healthy at end.
2026-07-28 01:59:59 +02:00
admin ad8057c4e3 docs(ep0): relocate the felhom-offsite PBS datastore onto the 100 GB volume
Supervised runbook execution. No code change, no version bump.

felhom-offsite moved from ep0's 40 GB root disk (/srv/pbs-felhom) to a
dedicated 100 GB Hetzner Cloud Volume (/mnt/pbs-datastore, ext4 -m 0,
by-id fstab, relatime). Datastore NAME unchanged, so the PBS-DR
descriptors, per-box storage ids, ACLs and namespaces are untouched.

Capacity: 37.2 GB -> 98 GB total, 28.9% -> 13% used, headroom to the 80%
warn 19 GB -> ~65 GB. This CLEARS the R-82 Phase 0 P0.3 STOP. Per-tenant
encryption still precludes cross-customer dedup, so the slope is
unchanged - the volume buys runway, not a better cost model.

Verified: byte totals and chunk counts identical (9748), 7/7 snapshots
across all three namespaces, backup:backup ownership, clean itemised
dry-run, full verify job TASK OK with 0 errors, and a restore round-trip
(source_tier pbs, pass true, mount_parity ok, clean teardown).

Nothing deleted - the original 13 GB stays at /srv/pbs-felhom as the
rollback until a new weekly backup lands. GC deliberately not run.

Three findings recorded:
- the `scratch` datastore points at a non-existent path (pre-existing;
  now logs ENOENT every start) - operator decision
- the runbook's S6 guard test proves the wrong proposition:
  RequiresMountsFor re-mounts rather than refusing, so the test only
  bites when the device is genuinely unavailable (re-run that way, and
  the refusal was observed) - amendment recommended
- S11: storage box u629193 has no live backup path, BUT ep0 carries an
  enabled sshfs mount unit against it that must be removed before the
  box is deleted

Deviations: the volume arrived pre-formatted and mounted; S8 ran on
demo-felhom rather than demo-hp (no SSH key for demo-hp); the window was
contended by a stale in-memory 10-minute restore-test cadence whose
config had already been reverted on disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ARoadHBf8rHoscfiqeVZn
2026-07-27 09:36:11 +02:00
Claude Code 65e30f2e42 docs(audits): R-82 Phase 0 — P0.1 weekly CONFIRMED, P0.2 benign, P0.3 STOP (capacity)
Read-only gate. No code, no backup triggered, no config changed.

P0.1 weekly CONFIRMED: the only 7-day-exposed state is the non-SMB half of
settings.json (storage_paths, app_backup toggles, notification prefs,
password_hash, launcher token) — none catastrophic, all recoverable. The two
items that would have overturned it, encryption.key and the offbox
credentials, are STABLE files unchanged since first boot, so a 7-day-old copy
is byte-identical. Everything referentially coupled to app state is carried
daily by Tier-1/2/3, and Tier-3 offsite was verified running and ok on both
production boxes. CONDITIONAL: drill-r50 has offsite:null, so the verdict does
not hold there.

P0.2 RESOLVED, benign: PBS returns HTTP 200 with zeroed usage to the
namespace-scoped token (DatastoreBackup, not Datastore.Audit), so PVE prints
0/0/0. Ground truth via the hub ep0 df op: 28.9% of 37.2 GB. Writes work —
the 07-18 snapshot is owned by this token, 9.74 GB, verify ok.

P0.3 STOP: 37.2 GB datastore, 10.8 GB used, 80% alert at 29.8 GB. Encrypted
per-tenant backups do not dedup across customers. Current 3 boxes project to
~15-21 GB weekly keep-3; each additional customer costs ~5-10 GB, so the 80%
alert fires at roughly the SECOND additional customer — inside the alpha
horizon. Needs a ruling on datastore size / retention before recurring writes.
2026-07-26 12:08:32 +02:00
Claude Code add5b9bbbb docs(audits): R-80 DIAG — expected_backup_missed root-caused to in-memory agent backup store
Read-only diagnostic. No fix applied.

Verdict: FALSE ALARM for demo-felhom + demo-hp, true-but-premature for
drill-r50. Fired ONCE (2026-07-26 03:00 UTC), not nightly. No real
external customer notified.

Root cause: felhom-agent/internal/backup/store.go is in-memory by design;
an agent restart empties the host-report 'backups' array until the next
backup runs. The R-50 island migration restarted the fleet on 2026-07-25
12:44 UTC, opening an ~18h blind window that the 03:00 UTC deadline check
fell into. Hub's assessBackupFreshness reads the empty array as 'no
backup exists'.

Separate real finding: the PBS/offsite-DR tier has no schedule at all
(local_backup_target=local, no jobs.cfg) — demo-felhom has one snapshot
from 07-18, demo-hp has zero since pbs_dr was applied 07-21.
2026-07-26 09:59:35 +02:00
admin 9e94479449 DIAG: agent-channel banner is a stale controller.yaml endpoint, not island fallout
Read-only diagnostic; nothing restarted, reconfigured or deployed.

CAUSE (H2, supported): the island migration rewrote bootstrap.json
(169.254.253.1:8443) but the controller reads controller.yaml, which still
holds the pre-island LAN address on both boxes -- mtimes 2026-07-21 and
2026-07-23, i.e. days BEFORE the migration. ensureLocalAPI
(bootstrap.go:254) returns early when local_api.endpoint is non-empty, so it
fills a MISSING endpoint and never reconciles a STALE one. The agent no longer
binds the LAN address, hence "connection refused".

H1 refuted: every island leg is healthy on both hosts -- agent active and bound
169.254.253.1:8443, vmbr9 up, guest eth1 up, TCP from the guest to the island
is OPEN. The island works; nothing is using it.

H3 refuted: hub-persisted events put the first alarm at 2026-07-25 12:44/12:46,
~17.5h before the v0.172.0 deploy. Today's re-fires are restart artifacts --
channel state is in-memory by design.

H4 refuted: felhom-agent is active on both hosts.

Also: the logged "up->down" is an ARTIFACT -- the debounce branch seeds an
unseeded state to "up", so the channel was born-down and has never been up.

Symptom B confirmed: EffectiveProtected gates cloudflared on a token predicate
but samba on smb.Enabled alone, so sharing-without-password reports FAIL for a
deliberately-undeployed stack. The function's own doc comment asserts the parity
that is broken. Caused by leg 4 of the R-75 task. Blast radius: zero emails so
far, but demo-hp flipped ok->fail and the health_critical path has reached the
CUSTOMER channel historically. The English issue strings are whole-surface, not
a one-off.

Fixes described, none applied.
2026-07-26 08:51:45 +02:00
admin 0c20c91e85 SPIKE: catalog-derived userdata skeleton + import surfaces (2026-07-26)
Verdict GO, with one mandatory constraint.

Phase 0 HOLDS: the customer-facing path set is fully derivable from data the
controller already parses (ParseComposeClassifiableBinds), and
ValidateBackupSpec's refusal set already covers the path-safety class
data_paths: needs -- so the annotation-only design introduces no new
filesystem-write primitive. No catalog folder is reachable only via env
indirection; zero templates use long `type: bind` syntax.

Found off-brief: the derivation is ALREADY LIVE at deploy time
(stacks/manager.go:183 ensureUserdataMounts, "the deploy belt"), proven by a
clean two-box control -- media/podcasts exists on demo-felhom where
audiobookshelf is deployed, not on demo-hp, and is in no skeleton.

P0 GO: derived set 14 dirs vs hardcoded 14 (+media/podcasts, -documents); all
three hand-verified anchors match. P1 GO: 4 sources incl. nested + accented
names all index ready. P2 GO: deep-link template constructible in Go from
(sourceName, relPath) alone; login redirect preserves the target. P3 GO for the
feature, with a pre-existing setgid-chain break recorded. P4 GO: userdata/import
is shareable, guard unchanged, no live share created. P5: before-state inventory
captured read-only on both boxes. P6 GO-conditional: the naive derivation
produced 20 distinct outputs from 20 identical runs -- a guaranteed FileBrowser
force-recreate loop -- fixed by one sort. P7: 12 sources safe server-side.

No production code, no version bump, no live mutation.
2026-07-26 06:59:46 +02:00
admin a2500532c3 R-50: A4 fresh-provision validated (agent auto-net1 live); spike caveat closed
- A4: scratch guest provisioned from golden on the island-configured drill came
  up with net1 automatically (zero manual edits) — the v0.96.0 provisioning path
  proven live. Spike method caveat CLOSED.
- REPORT: Phase A done + vouched 0.96.0; B0/B1/A4 done; B2/B3 await operator go.
2026-07-25 14:36:56 +02:00
admin 515e0c3cc7 R-50 island-bridge: SPIKED -> GO (probes P1-P8 pass live on t740 drill)
Provisioned nested-PVE drill 'drill-r50' (qm300 on demo-hp) via the v1.25.0
nested-vm ISO through the real day-0, then ran the R-50 empirical spike:
- vmbr9 portless island bridge + guest island NIC hot-add (LAN undisturbed)
- F1 replay money shot: LAN move survives on the island; LAN-literal bind
  reproduces the 2026-07-20 daemon-exit bug verbatim
- dnsmasq trap confirmed live + lan_resolver.host_ip fix proven
- pin address-independent (leaf SHA-256 unchanged, HTTP 200 over island)
- survival matrix: agent/guest/host-cold-reboot all return on the island

Docs: SPIKE verdict BLOCKED->GO, ROADMAP R-50 SPIKED->GO, nodes.md drill VM,
REPORT overwrite.
2026-07-25 12:19:09 +02:00
admin 5d56f93755 docs: R-50 island-bridge spike attempt 2 (t740) — STILL BLOCKED (no drill VM there); nodes.md drill-host ruling; REPORT 2026-07-25 09:59:10 +02:00
admin 1d65f44205 docs: SPIKE R-50 island-bridge — drill env ABSENT (empirical blocked); source design findings + dnsmasq trap; ROADMAP R-50 spiked 2026-07-25 08:30:25 +02:00
admin 770ee5a5f6 docs: SPIKE — SMART coverage on real hw (why the card shows Nincs adat); findings-only
Read-only probes on demo-felhom: both disks (system SSD /dev/sda, USB /dev/sdb) report
PASSED via the exact allowlisted 'smartctl -a -j <dev>'. Card shows Nincs adat because the
agent never reads: 'local' is a dir on LVM pve-root (backing='' + smartDeviceFor has no
dm/LVM branch), and the USB is surfaced via the non-enriched driveTargets.Known union path.
-d sat NOT needed (bridge passes SMART through; sudoers grants only -a -j). Graded fix
directions B(low-risk, USB) > A(system SSD, medium) > C(reject). STOP — no fix implemented.
2026-07-25 07:56:02 +02:00
admin 8462338ad4 docs(R-71): SHIPPED (a)+(c) — the apply-bridge settle-gate (controller v0.162.0)
R-71(a) day-0 ordering fix shipped: offsiteapply settle-gate defers the offsite
one-time-password consume past a managed floor-update, reading the updater's own
floor/update-running state (no second floor path). Ordering-only; (b) two-phase
consume rejected-by-design. ROADMAP R-71 -> SHIPPED (a)+(c); DIAG-f10 final
annotation added. Layering: v1.25.0 build gate prevents, (a) defers, (c) heals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7Drmtm2RzoqbkJZCNSFNQ
2026-07-24 07:49:11 +02:00
admin 6cc875cc36 drill: R-63 ő/ű captured live on the KVM HP (+ pairing + rootpw); HP SVM-enable fix; evidence PNG
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
2026-07-23 20:46:27 +02:00
admin 6dab0f6a2c docs: ISO train v1.25.0 — REPORT (belt/apt/R-63/gate/vault live), F9 resolved, R-63 shipped, nodes belt+vault, F8 checklist; critical golden<floor finding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
2026-07-23 15:48:01 +02:00
admin 527d81cf70 docs: v0.72.0 ship report + live validation, CONTEXT rulings, DIAG-f10 R-70/R-71 status annotations
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
2026-07-23 13:05:03 +02:00
admin c801cee647 F10 offsite leg resolved on demo-hp: day-0 update-vs-bridge race diagnosed, designed-path repair + tier round-trip proven; R-70/R-71 minted
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NKSN3gSg4TKVBBqkwW2djR
2026-07-23 12:21:05 +02:00
admin 5b35023574 docs(audits): power-outage recovery audit 2026-07-22 — power cut confirmed, full self-heal in 3m15s, dead-man's-switch fired on schedule; F8-F13 2026-07-22 19:57:44 +02:00
admin a12c6f9730 docs(v1.24.0): R-59/R-60/R-61 SHIPPED — CHANGELOG, README, ROADMAP (+R-62), runbook, capability map, drill evidence, REPORT
Virgin-ISO nested drill closed the train: dead-NIC install baked the
fallback (incl. the dead default gateway), the R-59 screen painted
(capture committed beside the spike doc), the cable move healed +
registered at the hub in 23s unaided, and the build's rootpw file
matched the installed box's shadow hash. R-59 SHIPPED with the recorded
deviation (first-boot gate; installer-initrd abort out of scope by
operator ack). R-60 SHIPPED (spike + drill cited; F-P9 route-flush fix
included). R-61 slice 1 SHIPPED. New R-62 row (hub delete-dialog
cosmetics, XS). Capability map: new PROVEN-LIVE row (nested != metal,
said so). Cleanup verified: felhom-pve interfaces byte-identical,
bridge/VMs/ISO removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UuFPHmHNrCJj1VhY6QdDMU
2026-07-22 11:45:55 +02:00
admin c043ba9abe docs(spike): R-59/R-60 Phase-0 — the NIC sweep mechanics are GO, measured on a nested wrong-NIC install
Nested VM 9310 on felhom-pve (throwaway vmbr9 rig): re-point
bridge-ports -> ifreload (0.36s, raises the port itself) -> dhclient
lease 3.3s -> hub HTTPS 302 in 160ms; a dead candidate costs exactly
its timeout bound (rc=124 at 20s). Bonus finding: the auto-installer
picks the NIC that leases and bakes the lease as STATIC — so wrong-NIC
boxes come in two variants (192.168.100.2 fallback bake vs
truth-changed-after-install), and the gate must trigger on
hub-unreachable, never on the fallback signature. Eight design inputs
(F-P1..F-P8) recorded for the Part-3 gate. STOP-0 discharged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UuFPHmHNrCJj1VhY6QdDMU
2026-07-22 10:50:43 +02:00
admin 1d1d60a737 docs: DIAG addendum 2 — S-3 fixed (samba 1.1.0 mDNS + v0.152.0 copy); Finder sidebar stays OPEN
Capture disproved addendum 1's guess: macOS DOES send a correct NBNS query and
IS answered correctly in 140us; it simply never acts on it. Records the proven
per-client matrix and keeps sidebar discovery explicitly unproven.
2026-07-20 13:40:55 +02:00
admin a7d9837976 docs: INCIDENT — guest 9201 dhclient killed as a host orphan; tunnel down ~1h15m
The v0.150.0 'orphaned dhclient' cleanup killed guest 9201's DHCP client, not a
host one: LXC guests share the host PID namespace, so the missing eth0, pidfile
and lease file were all true ON THE HOST and all irrelevant. Lease expired ~80
min later, guest lost its address, tunnel/hub/catalog/agent all went dark.

Restored; records the one-command ownership check (/proc/<pid>/cgroup) and
leaves the unsupervised-dhclient risk open for a ruling with R-50.
2026-07-20 12:09:01 +02:00
admin eaa217373f docs: DIAG-sharing addendum — S-1/S-2/S-4-core/S-5 shipped in controller v0.151.0; S-3 stays open
Also records against R-45 the lesson the loop taught: a phase a client answers
with a one-shot action must be an EDGE the registry serves once, never a level
synthesised into the phase channel.
2026-07-20 10:49:35 +02:00
admin 0c4886958e docs: DIAG — Megosztás reload loop root-caused (b5d78d1 phase coercion) + SMB endpoint is the guest, not .162
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nhk3eBHT8Mg5L8c2aj57aU
2026-07-20 10:11:58 +02:00
admin 30fd9d3ecd docs(audits): remote-ops remediation — F1 mitigated, F2 closed, F3 fixed (v0.149.0), Immich resolved; R-50..R-53 minted 2026-07-20 09:09:25 +02:00
admin 648ee67910 docs(audits): vacation remote-ops — access change + demo-node health diagnosis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nhk3eBHT8Mg5L8c2aj57aU
2026-07-20 08:07:49 +02:00
admin 97249099d6 docs: DIAG round 2 — H1 confirmed, H4 found; two standing rulings landed
VERDICT. H1 confirmed: the reconstitution never ran. /backup/offbox/reconstitute
was never hit (reconstituted/safety-dump/replay line counts all 0); the operator
clicked the old missing-only button, which merged 34 files and left the DB
untouched. H2 rejected (the full path never executed; staging used the correct
snapshot 49e7cb46). H3 rejected (no replay happened; DB was genuinely asset:0).

R-44 is exonerated and working: 49e7cb46 carries offsite_run_id 20260719T102851Z
and its dump probes to asset:11 / user:1 — a properly coherent pair, against
round 1's asset:0 / user:0.

H4 (NEW, the more important result). Phase-3 recovery ran the real sequence and
the v0.148.0 path executed correctly — safety dump, stop, start, replay — then
the replay ABORTED: relation "clip_index" already exists. Proven to the second:
  10:58:25 controller begins replay
  10:58:33 immich-server "Reindexing clip_index" -> "Reindexed clip_index"
  10:58:35 dump's CREATE INDEX clip_index fails, exit 3
The replay races the app's own schema repair, because reconstitution starts the
stack before replaying (ImportDump needs a live container). The photos survived
only because pg_dump emits COPY data before CREATE INDEX — luck, not design. The
same start-then-replay shape is on the LOCAL path, so it is a class defect.

Recovery: PARTIAL and STOPPED as instructed. All 11 assets are back and visible
by every DB criterion (status=active, deletedAt null, all files resolve), but the
operation reported failure and immich reports schema drift. Not clean acceptance
evidence; no second attempt made; safety dump read only.

Findings -> R-47 (the replay race), R-48 (restore controls separable only by
layout — the proven cause of this incident), R-49 (1.1 GB of a 1.2 GB immich
backup is ML model cache + a postgres data-dir tar duplicating the .sql dump +
immich's own nightly dump; recorded, not changed).

RULINGS LANDED.
  - 00-capability-map.md:61 -> PARTIAL, scope-corrected: 6D's destruction hit the
    FILE TREE only; the DB survived in its named volume, so "end-to-end from
    offsite alone" overclaimed scope.
  - The 704.6 MiB "discrepancy" -> CLOSED, not a defect: it was immich's own
    Tarhely widget, never a controller page. Same for round 2's 650MiB->1.4GiB.
  - customer-restore row: partial evidence recorded, NOT flipped.

Diagnosis only — no code, label or layout changes; no forget/prune.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9Nn14TWGzKoqAJAiVwC2s
2026-07-19 13:04:58 +02:00
admin 238472b8ed docs: R-43 + R-44 shipped in controller v0.148.0 (live acceptance still pending)
ROADMAP R-43/R-44 → SHIPPED with what actually landed:
  - R-43: ReconstituteFromOffsite — safety dump → stop → files overwritten to
    the snapshot version → start → the snapshot's own dump replayed from the
    SCRATCH unit (the live unit is still never overwritten). Invariants:
    nothing is ever deleted, and the undo is verified on disk before the act.
  - R-44: every run, manual AND nightly, refreshes dumps + units BEFORE the
    capture, so each snapshot is a coherent {DB@T, files@T} pair stamped with
    offsite_run_id + dumps_at. Nightly ordering is now structural rather than a
    coincidence of two scheduler entries at 02:30 and 04:15.

DIAG doc gains a "fixed in v0.148.0" header pointing at both.

Capability map deliberately NOT flipped: the DB half now exists but is PARTIAL,
not PROVEN-LIVE — shipped and deployed with no live acceptance behind it. The
customer-restore row stays MISSING, now blocked only on the evidence run rather
than on missing capability, and R-3 stays DRAFT.

Still open: the 00-capability-map.md:61 ruling — did CAMPAIGN-6D's "immich
end-to-end from offsite alone" exercise the DB half, or only the file half?

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9Nn14TWGzKoqAJAiVwC2s
2026-07-19 12:27:11 +02:00
admin f3d1ba3062 docs: DIAGNOSE immich offsite restore — findings + ROADMAP R-43/R-44
Viktor deleted 11 immich photos to test offsite restore; both place runs
merged 0 files and flashed success. Two-layered root cause:

- A UI delete means TRASH, not deletion — no file left the disk, so there
  was genuinely nothing missing to restore. The test proved nothing.
- A valid test would also have failed: no offsite path loads a database.
  All three buttons are file-only (offbox_restore.go:171-232, :346-421).
  Files return; the library stays empty. The only DB replay is
  RestoreFromRecoveryUnit, reachable solely from the LOCAL restore panel.

Compounding: the manual push never refreshes dumps (offbox_handlers.go:
203-227) — today's unit dump is from 02:30 and probes to asset:0 / user:0 /
album:0, predating both the admin user and the photos. Had a full restore
loaded it, it would have written an empty DB over the live one, destroying
the trashed rows that were the only surviving recovery path.

Answer to "can a customer trust same-day offsite?" for a DB-indexed app:
no — files come back, content does not.

No product code changed. Photos left in trash (recovery not wanted).
Capability map: customer-restore row stays MISSING by finding, not by
absence of attempt; offsite-restore row flagged scope-contested pending
a ruling on whether CAMPAIGN-6D exercised the DB half.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9Nn14TWGzKoqAJAiVwC2s
2026-07-19 08:47:34 +02:00
admin 3e2078be8a CAMPAIGN 7 doc: MAJOR breaking-note table (§6.3)
Fills the gap flagged in §8.1. Upstream notes for bookstack, immich, nextcloud,
n8n, grafana, vikunja, privatebin, radarr, romm; plus the ones this sweep found
EMPIRICALLY on a real deploy (outline PGSSLMODE, tandoor port+ALLOWED_HOSTS,
zipline DATABASE_URL/healthcheck rename, homebox tag prefix + pepper, wger
DJANGO_DB_* + :8000) -- which are the more useful half, being what actually
happens rather than what a changelog remembered to mention.

Four majors (calcom, claper, gramps-web, onlyoffice) deployed clean but have no
retrieved upstream one-liner; recorded as not-retrieved, not fabricated.

Campaign 7 catalog sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 03:30:50 +02:00
admin 13d8bffa96 CAMPAIGN 7 doc: offsite continuity across reinstall PROVEN (R-34 evidence)
An offsite run after the trio's fresh redeploy shows the snapshot TAGS are stable
across uninstall -> volume-destroy -> reinstall: calibre-web 243ad08c -> 9698ba74
and immich 6df12205 -> ea511336 both keep their exact tag sets, so a
'restic --tag <app>' query still spans the reinstall boundary and pre-uninstall
restore points stay addressable.

Side effect: the standing 'kimaradtak: bookstack' offsite warning is now CLEARED.
bookstack had no offsite snapshot because it had no tier-1 backup to push; the
fresh redeploy produced one, so its first-ever offsite snapshot landed (run ok,
1m13s, 6 snapshots, 211.6 MB, last_warning None).

Campaign 7 catalog sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 03:29:00 +02:00
admin eee2c85321 docs: CAMPAIGN 7 catalog sweep — audit, ROADMAP R-40/41/42, REPORT convention
Adds the CAMPAIGN 7 audit doc (53/53 apps deployed and validated on the demo box
through the real controller endpoints; 45 pass, 13 template fixes, ranked
findings) and the uninstall-semantics map row it proves out: remove requires stop
first, named docker volumes are ALWAYS destroyed including the DB, HDD data and
backups survive unless explicitly ticked, and the per-app offsite toggle outlives
the app while tier-2 config is cleared.

ROADMAP +3:
  R-40 the update path cannot express a multi-hop major upgrade (nextcloud
       forbids skipping majors; the template moved 31 -> 34)
  R-41 no standing 'does every template still deploy?' check -- the sweep was
       the first thing to try, and found 5 apps that had NEVER been deployable
  R-42 ruling needed on DB/cache sidecar majors (deliberately not bumped)

CLAUDE.md: REPORT.md is overwritten, so a second concurrent session in this repo
writes REPORT-<topic>.md instead -- applied to this run's own report.

Campaign 7 catalog sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 03:24:39 +02:00
admin 7e0370f700 spike(R-6): human Explorer leg PASSED (nmbd) + stack torn down, report-clean
Viktor re-tried the double-click after the nmbd fix: Network -> FELHOM-SPIKE ->
spike-share opened, README.txt displayed. Full visible-and-openable one-click UX
works. Spike containers + /tmp scripts removed on guest and DooPlex; ports free;
verified report-clean. Spike COMPLETE.
2026-07-18 10:50:14 +02:00
admin d3eb8099a3 spike(R-6): S4b name-resolution finding — wsdd visible but flat name unresolvable
Human Explorer leg exposed the split: FELHOM-SPIKE renders (WSD PASS) but the
double-click fails 0x80070035 — flat name resolves by no path (DNS/LLMNR/NetBIOS
all silent; disable netbios=yes). By-IP mount works => SMB is healthy, the gap is
name resolution. Fix verified live: adding nmbd (NetBIOS) => nbtstat lists
FELHOM-SPIKE, ping resolves, \FELHOM-SPIKE\spike-share mounts by name. R-7 must
ship smbd+wsdd+nmbd (+avahi/.local), not wsdd alone.
2026-07-18 10:46:53 +02:00
admin 900d3041fa spike(R-6): LAN service discovery from the guest — SSDP/WSD/mDNS/SMB matrix
Verdict: appliance guest is LAN-bridged; multicast discovery works only in the
guest netns (guest-direct or docker --network host) — default bridge is deaf to
LAN multicast. Real samba+wsdd on host-net: Windows 11 ProbeMatch + 445 + SMB
round-trip PASS; SSDP MediaServer:1 reaches LAN clients. R-7 => host-network
LAN-bound; R-8 Jellyfin-DLNA plausible if host-network. ROADMAP R-6 -> spiked,
R-7/R-8 unblocked. S4.4 Explorer render pending human.
2026-07-18 10:38:46 +02:00
admin 106c3379b0 docs: offsite continuity Part B REPORT/CONTEXT; DIAGNOSE incident→fix pointer; capability map (offsite reinstall-continuity + escrow retention); ROADMAP R-25 (TOCTOU hardening) + R-26 (guided old-history recovery) 2026-07-17 10:55:17 +02:00
admin 10e07f5747 docs(spike): immediate-sync transport — poke-relay vs long-poll, both GO/complementary
Empirical spike (demo arc, docs-only, no product code). Measures the two hub->box
'sync now' transports. Verdict: (b) long-poll = primary for user config (hub-only,
reaches controller, no ep0/WG/agent coupling; binding ceiling = twin 60s nginx
proxy_read_timeout + hub WriteTimeout, both config lifts); (a) ep0-relayed poke
= agent-plane nudge (~0.42s, zero ep0/box infra change, EKEYREJECTED-confined).
Viktor Q2 answered: hub.felhom.eu is DNS-only, no Cloudflare. All live mutations
reverted; arc re-verified green.
2026-07-16 20:00:01 +02:00
admin 914d51aa5f docs(spike): bare-metal Felhom ISO — R-21 spiked (all mechanisms GREEN on nested virt)
Empirical spike of the Proxmox automated-installation path for R-21 (bare-metal
customer-hardware ISO). All legs proven on nested VM 310 (felhom-pve):
- S1 assistant 9.2.7 + ISO provenance; validate-answer exits 0 on failure (parse output)
- S2/S2b zero-touch install SeaBIOS + UEFI incl. Secure Boot enforcing (no keypress/MOK)
- S3 first-boot hook: fully-up = root + working pvesh/pct, exactly-once
- S4 webhook payload enumerated (SMBIOS-UUID + MAC + host SSH keys)
- S5 disk-filter installs only target (canary byte-identical), match-nothing fails safe
- S6 from-url + cert-fingerprint fails CLOSED (red-proof)
- S7 http answer-fetch identity; S8 no-net + invalid-answer failure modes
Findings doc + ROADMAP R-21 status=spiked. No production code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qDiBqKKQ5vPB5fXBqu7Kp
2026-07-16 11:29:03 +02:00
admin c2b7145cbd docs(audit): CAMPAIGN-6E — P-TIER2 deep-4 + P4-DEEP timing (all 8 legs PASS)
Closes the two deferred deep campaigns from 6D on live infra: tier-2 v2 layout
engine (A1-A4) and F7 atomic-write + restic C2 self-heal (B1-B4). No production
code shipped. Two LOW findings (F-6E-1 demo drive drift; T-6E-1 DB-dump dir-fsync
parity). Drill repo torn down; fixtures reverted; box healthy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 21:11:39 +02:00
admin dee72cd0de docs: 6D audit — tag campaign6 autofs finding as track-only (operator reboot) 2026-07-15 19:48:43 +02:00
admin e8f8c441fa docs: SPIKE PBS-DR self-heal — root cause = consumed secret, not missing descriptor
Refutes the surviving theory: on the drill host the pbs_dr descriptor is present
and enabled, the WG peer exists, verify passes — the block is a one-time secret
consumed 07-12 that no path re-mints after the agent loses its converged marker
(re-install/rollback; same WG pubkey -> changed==false -> cascade can't re-fire).
Live-proven: staging any consumable secret converges in one tick (existing token,
zero ep0 churn); re-asserting a converged descriptor is a clean idempotent no-op.
Safe re-trigger = re-serve a secret gated on agent waiting_secret/consumed_failed,
never blind-timer Reissue. Design-inputs table handed to the self-heal TASK spec.
Drill left CONVERGED (P-DAY0-DEEP PBS-DR leg now GREEN).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 17:53:42 +02:00
admin aff272c4be docs: CAMPAIGN-6D — P-DAY0-DEEP BLOCKED + MED/HIGH PBS-DR self-heal finding
Fresh-appliance PBS-DR consume races the WG handshake at install (fingerprint probe timeout),
consumes nothing, and does not re-request the descriptor once the tunnel is up; reused-peer
re-provision never re-issues it. No operational re-trigger. Escrow ceremony + offsite round-trip
blocked on this fresh guest → friend-alpha alpha #1 escrow must be supervised live (customer-holds-R).
Drill deviation (CC-held R) never exercised. Recorded, not fixed inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 17:15:11 +02:00
admin 12a2eee9eb docs: CAMPAIGN-6D — P-DAY0 CORE PASS (golden 0.136.0 provision drill)
Fresh nested PVE → installer v1.16.0 sha-verified agent 0.88.0 + golden 0.136.0 vs hub manifest
→ Day-0 provision SUCCESS, controller 0.136.0 healthy. Escrow correctly pending (fresh repo
password vs stale hub blob); escrow/offsite deep legs deferred (DR/PBS/WG chain, proven 07-12).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 15:33:02 +02:00
admin 13eb450d9f docs: CAMPAIGN-6D — P3-BROWSER PASS (escrow typed-back ceremony, hub 8-tab ring)
Escrow wizard completed (new code claimed, escrow_state=escrowed) — closes the long-pending
supervised wizard pass. Cross-tab session shared. Hub 8-tab ring + live-refresh + dirty-form
suppression (Auto-refresh paused on static tabs) verified. Secrets not captured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 14:46:10 +02:00
admin a17d863d3d docs: CAMPAIGN-6D — P3-DELIVERY PASS (enlarge-block + customer email), G4 restored
immich enlargement blocked at real 50 GiB quota via sparse mandatory fixture; unit-only
continues; EnlargedBlocked edge-fires once; both operator + customer emails delivered;
fully reverted. G4 notification email restored via the real form (wipe-bug regression).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPuEwyyGDJdcsXLFsTWJn
2026-07-15 14:04:07 +02:00