Commit Graph

669 Commits

Author SHA1 Message Date
admin 6bf4bef2be docs(report): hub v0.68.0 (R-39 fleet fix + R-50b(a)); STOP-2/3 need the operator 2026-07-21 10:24:52 +02:00
admin 54a4644721 docs: R-39 fleet fix SHIPPED (hub 0.68.0 + agent 0.91.2); R-50b(a) SHIPPED; (b)/(c) open
R-39's three legs are closed and deployed: the hub stamps a monotonic secret_generation
so a re-key finally moves the descriptor hash; the wrapper gains a narrow read verb so
the non-root agent can read the credential it writes; and ProbeAuth turns a 401 into a
loud auth_failed the existing damper escalates to a fresh mint. Plus a consumed_at
honesty gauge for the applied-but-never-consumed disagreement.

Recorded in the R-39 row, because both are the kind of thing a future reader needs:

- A load-bearing fact the spec did not flag, checked rather than trusted: Apply bails out
  if the storage status probe ERRORS and adopt converges without consuming when the
  storage reads active, so the fix depended on PVE's 401 behaviour. PVE's storage_info
  wraps activation in eval{} and leaves active=0, so a 401 returns HTTP 200 with
  active:0 — never an API error. The chain is sound by proof, not inference.

- A defect I shipped and caught: v0.91.0 built the probe seam and main.go never wired it,
  so the leg was inert while every test passed. Same class as controller v0.154.0 the day
  before. Fixed in v0.91.1 (artifact superseded, not overwritten); v0.91.2 made a healthy
  probe observable so "no auth_failed" can never again be confused with "never probed".

The DR-tier capability row is deliberately NOT upgraded to PROVEN-LIVE: the decisive
evidence is STOP-2, the operator pressing Re-issue and the box converging where the
identical click did nothing on 2026-07-18.

R-50b(a) shipped — wrapper sha256 in the manifest + agent reporting + host drift surface,
with unknown-on-either-side reading as quiet rather than drift. (b)/(c) remain open: the
wrapper is still fetched unversioned from raw/branch/main.
2026-07-21 10:24:11 +02:00
admin e671ba3fd1 manifests: hub 0.67.0 -> 0.68.0 (R-39 fleet fix + R-50b(a)) 2026-07-21 10:13:40 +02:00
admin 107f74ea3c hub v0.68.0 — auth_failed self-heal, consumed_at honesty gauge, wrapper drift (R-39 + R-50b(a))
Completes the hub half of R-39's fleet fix on top of the generation core (c484aa2).

pbsdrheal gains an auth_failed TRIGGER — a new trigger in the existing machine, not a
new machine. A box whose credential PBS rejects escalates to a fresh mint, never a
re-stage (which would re-feed the secret PBS just rejected), through the EXISTING damper:
a 401 flap must not become a secret-minting chain. With the generation stamp this closes
the loop end to end — agent proves the 401, hub re-keys, generation advances, descriptor
hash moves, agent re-consumes.

consumed_at honesty gauge: a staged secret still unconsumed past a 15-minute grace while
the box reports `applied` is surfaced with its own event. That is the exact 2026-07-18
fingerprint and a disagreement no single tier can see alone. Deliberately a SURFACE, not
a heal — auto-re-issuing on it would mint a second secret on top of an unconsumed one,
which is the mint/consume race R-39(a) already recorded. One event per distinct report,
and an honestly-stuck box does not double-report (its unconsumed secret is the symptom
being healed, not a contradiction).

R-50b(a): ArtifactManifest.WrapperSHA256 + operator field + host-page drift surface. The
PBS wrapper is root-owned 0755 and the pinned sudoers vector, yet installed unversioned
from raw/branch/main and absent from every manifest. Agents >=0.91.0 report the installed
hash; a mismatch is surfaced. An unknown on EITHER side reads as quiet, never as drift —
lighting every host amber on rollout day is how a warning becomes background noise. The
delivery channel itself stays R-50b(b)/(c).

Compatibility unchanged: safe for 0.90.0 agents (unknown JSON key dropped); the re-arm
and auth-honesty guarantees need agent >=0.91.0, so MinAgent moves only after the fleet
has self-updated.

Tests: auth_failed escalate/debounce/recovery-forgets-streak; honesty gauge incl. grace
window, the restage edge (consumed_at deliberately NULLed), consumed-never-alarms, and
honest-stuck-no-double-report; wrapper drift incl. both unknown directions. Red-proof run
at the assertion level: removing the auth_failed arm fails the escalation tests with
reissues=0.
2026-07-21 10:01:35 +02:00
admin c484aa204e hub: R-39 core — stamp a secret GENERATION into the pbs_dr descriptor
The fleet half of R-39. An ep0 credential re-issue re-keys the SECRET of an existing
token, so token_id, fingerprint, datastore and namespace all come back byte-identical.
The agent re-applies on the descriptor's CONTENT HASH, so a re-issue was invisible to a
converged box: it short-circuited, never consumed the fresh secret, and served a revoked
credential while reporting `applied` — the N100 failure of 2026-07-18.

host_pbs_secrets gains a monotonic per-host `generation`, advanced by every fresh MINT and
by nothing else, stamped into the descriptor as `secret_generation`. That is now the only
field a re-key moves, and it is what re-arms the agent.

DEVIATION FROM SPEC, deliberate: the brief said to return "the new row's id (int64) …
no schema change". There is no row id — host_pbs_secrets is keyed by host_id and UPSERTed
last-write-wins, so a new row never exists, and created_at collides for two mints in the
same second. An additive counter column is the only monotonic source; it uses the repo's
existing idempotent ALTER-TABLE idiom.

RestageHostPBSSecret deliberately does NOT advance it: a re-stage re-arms the SAME secret,
the descriptor content genuinely has not changed, and a bump would cause a pointless agent
refetch loop (that method's own contract says so).

Also corrects a comment that asserted the re-issue refreshes the descriptor "with the NEW
token_id/fingerprint". That is false for a re-key, and believing it is why the descriptor
was never expected to be identical in the first place.

omitempty is load-bearing: a zero generation must not start emitting a new key into every
pre-existing descriptor, which would itself be a fleet-wide spurious re-apply.

Compatibility: agents below 0.91.0 drop the unknown JSON key and behave exactly as today —
inert, not breaking (Scenario C).

Tests: store-level monotonicity + per-host isolation + restage-leaves-it-alone; descriptor
byte-change, omitempty, and sibling-key round-trip; and a FLOW-level test driving
ReissuePBSDR against a fake that models a real re-key. Red-proof run at the assertion
level (not the compiler): commenting out the stamp makes the flow test fail with both
byte-identical blocks printed.
2026-07-21 09:52:04 +02:00
admin 11ead4be0e docs: R-23(a) restart leg BANKED; "round-trips in seconds" PARTIAL -> PROVEN-LIVE
The operator moved the global floor to a version the box did NOT run (0.153.0 ->
v0.154.0) and the managed self-update fired exactly once:

  06:57:13Z  UpdateState pending, initiated_by=auto-floor
  06:57:17Z  agent: controller swap requested 0.153.0 -> 0.154.0
  06:57:21Z  container restarted
  06:57:29Z  agent: new controller healthy      (16 s save -> healthy)

Over a 39-minute window: swap requests 1, agent-driven bootstrap restarts 1,
rollbacks 0, container RestartCount 0. VerifyStartup confirmed on the next boot;
the following periodic check logged "Current version 0.154.0 is up to date" —
the at/above-floor branch correctly doing nothing.

The 2026-07-20 attempt proved nothing because it targeted an already-running
version; that was the whole reason this leg stayed open.

Disclosed in both rows: a hand-deploy of v0.155.0 at 07:17:10 falls inside the
observation window and is what StartedAt shows afterwards. It never goes through
SwapController, so the swap-count assertions hold across the full window — and it
incidentally re-confirmed the at/above-floor branch (0.155.0 running against a
0.154.0 floor -> updater did nothing).

R-23(b) (cosmetic Waiter "recovered" log timing) remains open.
2026-07-21 09:30:50 +02:00
admin c35da9de6a docs(roadmap): R-48 SHIPPED (ctrl v0.154.0); R-39 cheap half closed in-field; new R-50b
R-48 — the offsite restore controls collapse to one „Visszaállítás…" entry per app plus
a per-app wizard with three described intent cards. Shipped in controller v0.154.0
(3a9d744). Live click-through still pending the operator's floor save.

R-39 — the planned v0.90.1 artifact publish was CANCELLED as a false signal (operator
ruling 2026-07-21). 9596d5a changes zero non-test Go files; its own message says "the Go
binary is unchanged". The fix is the felhom-pbs-apply wrapper, which felhom-pve has
carried since 2026-07-18 and which every new install fetches from raw/branch/main
regardless of binary version. Publishing would have delivered no behaviour change and
advertised a versioned fix the artifact channel never carried.

R-50b (new) — that stop surfaced the real defect: a root-owned privileged host artifact
is delivered unversioned from main, absent from the Day-0 manifest, so the fleet has no
way to answer which wrapper a given host is running.
2026-07-21 08:54:03 +02:00
admin ce8c5393f1 docs(capability-map): offsite restore cell PARTIAL -> PROVEN-LIVE (prose/enum mismatch)
Row 61's status cell still read "PARTIAL — scope corrected 2026-07-19" while the note
body already ended with the destructive drill of 2026-07-20 and the words "the row now
earns PROVEN-LIVE": 40 files placed after the operator deleted the photos in immich's own
UI and emptied the trash, 1 DB dump replayed rc-0, 11 assets active, no schema drift,
timeline confirmed — taken through the customer-facing buttons, not endpoint shortcuts.

Cell-only correction; the full note history is retained verbatim.
2026-07-21 08:31:08 +02:00
admin fb0b8c1d42 docs: C6 destructive drill PASSED; R-23(a) mostly banked
Capability map — the destroy-then-recover drill ran through the customer UI:
photos deleted, TRASH EMPTIED, full files+database restore. 40 files placed
against 6 in the earlier non-destructive run, 1 DB dump replayed rc-0, 11
assets active, no drift, timeline confirmed. That is the proof the 6D
downgrade asked for, so the offsite-restore row earns PROVEN-LIVE. The
customer-restore row records the honest residual: an operator ran it, so the
row's literal 'a customer, not the operator' wording still owes one pass.

ROADMAP R-23(a) — the STOP-2 floor save released the held wait in the SAME
SECOND (hub 18:56:27 CEST = controller 16:56:27Z), out-of-cycle report 2s
later, generation advanced 0 -> 1. Still open: the self-restart single-fire
leg, since the floor was set to a version the box already ran.

Trap recorded: the wake is logx.Debugf, so it is invisible in docker logs at
INFO and lives only in the debug ring.
2026-07-20 19:23:11 +02:00
admin 0a399e3c02 docs(capability-map): offsite-restore stays PARTIAL — the 6D scope lesson applies
The operator confirmed the immich timeline renders correctly after the
2026-07-20 reconstitute, which was the last named residual. But that run
restored over an already-good state (11 assets before, 11 after), so it
proves the H4 window is closed and the operation is clean — it is NOT a
destroy-then-recover drill.

Flipping the row on it would repeat the 6D overclaim verbatim: 'immich
end-to-end from offsite alone' must not be claimed by a run in which
nothing was ever lost. Row stays PARTIAL pending a real destructive drill.
2026-07-20 19:07:20 +02:00
admin 9c8505649a docs: R-47 live-validated + golden 0.153.0 published
ROADMAP R-47 and the capability map's offsite-restore row now carry the live
evidence: a supervised reconstitute of immich from snapshot 49e7cb46 — the
same snapshot that aborted in round 2 — ran stop -> DB-service-only start ->
replay rc-0 -> full start, with no 'already exists', a SUCCESS outcome, and
immich's own 'No schema drift detected' twice where round 2 left it
reporting drift.

The offsite-restore row's residual for a PROVEN-LIVE flip is now only the
immich timeline screenshot (no browser on DooPlex).

Golden 0.153.0 baked and published the same day, sha256 15fdd191f3c660a6...,
first golden carrying all four infra images.
2026-07-20 18:04:02 +02:00
admin db3378bbf7 docs: R-47 shipped in controller v0.153.0 — H4 window closed on both restore paths
ROADMAP: R-47 collapsed to a shipped one-liner naming both paths (offsite
ReconstituteFromOffsite and local RestoreFromRecoveryUnit), the DB-only
window mechanism, the fail-closed rule, and the no-agent-coupling
declaration (MinAgent stays 0.90.0).

Capability map: the offsite-restore PARTIAL row and the customer-restore
MISSING row both note that H4 is closed in controller v0.153.0 and that they
now await only one clean acceptance run.

Also corrects the stale "closes in v0.149" wording in BOTH rows — v0.149.0
was the F3 dashboard BackupStatus fix; R-47 shipped in v0.153.0.
2026-07-20 17:26:17 +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 1e1181939b docs(roadmap): R-53 shipped in controller v0.150.0; correct R-50's TLS-pin note (leaf-DER SHA-256, SAN never checked) 2026-07-20 09:42:34 +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 b682344471 docs(report): fix docs commit hash after rebase (83c47ea)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMForrE4c1wZxd9LukxYVt
2026-07-19 13:31:28 +02:00
admin 41060521aa docs(report): backfill docs commit hash (6dbdb39)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMForrE4c1wZxd9LukxYVt
2026-07-19 13:31:09 +02:00
admin 83c47eaf23 docs(ops): Tailscale makes the N100 (felhom-pve) location-independent
felhom-pve joins the tailnet as a host package (100.70.170.35, expiry
disabled); DooPlex already advertised 192.168.0.0/24 via its GitOps k3s
tailscale pod (100.107.87.53). ssh felhom-pve now targets the tailnet IP
(direct-over-LAN at home, tunnel when away); felhom-pve-lan = LAN fallback.
--accept-dns=false on the host. Host converted static->DHCP (reservation
keeps .162 at home). Measured: --accept-routes hairpins the local subnet
at home -> travel-only opt-in. PBS is offsite (own WireGuard tunnel), needs
none of this. Recorded not fixed: controller<->agent plane hard-pinned to
192.168.0.162 (agent listen_addr + guest bootstrap.json) -> not yet portable
off-LAN. No code changed. Full doc: documentation/operations/tailscale.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMForrE4c1wZxd9LukxYVt
2026-07-19 13:31:09 +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 a771cda67b docs(report): CC workflow migration to DooPlex-local execution 2026-07-19 12:30:12 +02:00
admin 29b2b8ad7c docs: DooPlex-local commands in PROMPT-TEMPLATE and the manual-build runbook 2026-07-19 12:27:44 +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 fa8c0d5972 docs(workspace): record the migrated project-memory location 2026-07-19 12:22:07 +02:00
admin a868e3585a docs: DooPlex is the canonical CC environment; mark iso scripts executable 2026-07-19 12:18:42 +02:00
admin ee48a48288 docs: migrate workflow to DooPlex-local execution 2026-07-19 12:16:52 +02:00
admin 13af252907 docs: scripts v1.22.0 + website grid CHANGELOGs, ROADMAP R-38 shipped, R-45/R-46, REPORT
R-38 flips to SHIPPED with the live-canary evidence. Two new items registered
from tonight's work: R-45 (unified async-job feedback — five one-off progress
surfaces now exist and agree on nothing; the two lessons any framework must
encode are "probe the terminal state, never infer it" and "a source reporting
nothing is normal, not broken") and R-46 (verification copies are now visible
but still cannot be browsed and never expire — the invisible-result gap and the
disk-lifecycle gap, designed together because they are the same object).

Pre-invite checklist gains the golden note: build-golden v2.1.0 makes goldens
>= 0.147.x carry all four infra images; no rebuild for this alone.

REPORT-<topic>.md per the parallel-session rule — another session is writing in
this clone tonight, so the shared REPORT.md is left alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 10:00:10 +02:00
admin 916d936332 website: restore the index grid background
Git archaeology: the grid was never a deliberate removal. It lived as a fixed
`body::before` in index.html's inline <style> block and was dropped when bed8675
("D3 Part 2: index + kapcsolat on design system v2") migrated the page onto
site.css. dd54e4c, which created site.css, has no body::before — it was a
porting omission, and nothing took its place. No asset was lost; it was pure CSS.

Restored at its original geometry and subtlety (50px cells, 1px lines, 3%), not
redesigned. Only change: the accent is the v2 --blue #0083D8 instead of the
retired legacy #0088cc, which site_gates.py bans. Scoped to body.page-index
because index is the only page that ever had it.

site.css cache-bust bumped to v=2 across all seven pages (nginx caches 7d).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 09:03:03 +02:00
admin 8feed6d1ff scripts: optically center the single GRUB entry under the boot card
The canary boot showed the entry sitting noticeably left of the centered logo.
gfxmenu's boot_menu draws items left-aligned inside the box with a ~29px inset
and offers no item alignment, so centering means placing the box: left 25%->40%,
width 50%->35%. Measured off the canary screenshot, not guessed; the comment
records the measurement so a future label change gets re-measured instead of
re-guessed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 08:57:51 +02:00
admin aed7f33f3c scripts: the banned-entry gate must read directives, not comments
The v1.22.0 gate asserts the rendered grub.cfg has no live reference to
proxtui/proxdebug/nomodeset/Rescue Boot/memtest/fwsetup. It grepped the whole
file, so the template's own header — which documents exactly which stock entries
were dropped, and names them — tripped it. Caught on the first canary build:
fail-closed, no ISO produced, which is the behavior we want from a safety gate
that is wrong.

Strip comments before matching. A comment naming a removed entry is the point;
a directive using one is the bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 08:49:02 +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 317037f8eb scripts v1.22.0 — ISO boot screen + single-entry GRUB menu (R-38 GRUB slice)
Two jobs, one repack pass.

BRANDING. Every ISO now carries a Felhom boot screen built from the website's
og-image_2.png at repack time (ImageMagick in the assistant container), so the
boot card has ONE source and not a second pre-rendered copy in the repo to
drift. The card is scaled onto a 1024x768 gfxterm canvas, top-centered, and the
card's own subtle background grid is continued across the letterbox fill
PHASE-LOCKED to where the card's grid lands — the fill is seamless rather than a
square of grid floating in flat navy. Menu positioning needs a gfxmenu theme
(plain background_image cannot move the menu off the wordmark), so the stock
pvetheme is replaced by felhomtheme, which puts the menu in the lower third the
layout deliberately leaves empty.

SAFETY — the half that matters. The stock PVE menu offers Graphical, Terminal
UI and serial installers plus an Advanced Options submenu (nomodeset x2, three
debug variants, Rescue Boot, memtest, UEFI settings). Every one of them reaches
the MANUAL installer, whose first question is which disk to wipe. A customer, or
their helpful nephew, must not be able to get there from a boot menu. They are
not hidden and not password-gated: they are NOT EMITTED. What ships is one
entry, 'Felhom telepítés', default, 5s.

Boot behavior is unchanged. The kernel/append and initrd lines are lifted
VERBATIM from the ISO's own 'Install Proxmox VE (Automated)' entry rather than
frozen into a copy here, so a PVE bump tracks automatically; the build fails if
they cannot be found, if the append line has lost proxmox-start-auto-installer,
or if auto-installer-mode.toml is absent (which would mean the one Felhom-
labelled entry boots a manual installer). The rendered menu is then gated for
exactly 1 entry, 0 submenus, and zero references to proxtui/proxdebug/nomodeset/
Rescue Boot/memtest/fwsetup — and re-verified by reading the menu back OUT of
the finished ISO, not merely out of the extract tree.

mkimage-surgery.sh -> iso-repack.sh: branding and the slice-B loader swap need
the same extract -> modify -> re-master cycle, so they share one pass instead of
re-mastering twice. The mkimage recipe is untouched. The embedded module list is
still derived from the STOCK grub.cfg (snapshotted before branding rewrites it),
plus gfxmenu's bitmap/bitmap_scale/trig renderer deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-19 08:45:45 +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 f5ab70d6e6 og-images 2026-07-18 22:03:33 +02:00
admin 90ea253dc2 docs(roadmap): R-36 + R-37 collapse to SHIPPED (hub v0.67.0)
R-36: both halves delivered — the enabled-but-unprovisioned warning on the
customer page (reusing the same predicate the offsite re-issue handler refuses
on), and the related sub-item, auto-minting the self-bind link at customer
creation AND RESET completion so the console banner's promised email is already
true. Records the gap found while wiring it: PurgeCustomerResetDBState does not
clear selfbind_tokens, so a pre-RESET link would have survived the reset; the
skip paths now clear stale tokens.

R-37: the post-RESET staleness banner, narrow by design — an in-flight reset
does not trigger it, it clears itself on the first post-RESET report, and ties
resolve to STALE because SQLite timestamps are second-resolution and a
same-second report almost certainly predates the reset.

Both red-proofed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 21:49:27 +02:00
admin 4529922e6d manifests: hub 0.66.0 -> 0.67.0
Deploys hub v0.67.0 (auto-minted self-bind link, post-RESET staleness banner,
unprovisioned-offsite warning, pbsdr_reissued flash text). The manifest is the
truth — the code push and image build deploy nothing until this tag moves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 21:46:56 +02:00
admin b6d537d86c hub v0.67.0 — auto-minted self-bind link, post-RESET staleness, unprovisioned-offsite warning
Four small items, each a case where the hub already knew something and said
nothing. Green: build, vet, tests all pass.

(a) Self-bind link is minted automatically at customer creation AND at RESET
    completion (R-36 sub-item). The console banner tells the customer to open
    "az e-mailben kapott link"; until now that email existed only once the
    operator remembered the button, so the banner could point at something that
    did not exist — during the 2026-07-18 rehearsal the box waited ~11.7 min on
    exactly that. handleSelfBindLinkSend's body was extracted into a shared
    mintAndSendSelfBindLink core so the button and the auto-mint callers cannot
    drift apart on the honesty rules: F1 (no address -> mint nothing) and F2
    (send failed -> delete the token, never leave it live). The wrapper NEVER
    fails the operation it rides on — a create that provisioned Cloudflare,
    offsite and PBS must not 500 over a courtesy email.

    Gap found and closed while wiring it: PurgeCustomerResetDBState does NOT
    clear selfbind_tokens, so a link minted BEFORE a reset would have stayed
    live across it. A successful mint already replaces it (delete-then-insert,
    single-active); the skip paths would not have, so they now clear stale
    tokens too. Invariant: after auto-mint runs the only live link is one it
    just issued, or none.

(b) Post-RESET staleness banner (R-37). When a RESET COMPLETED after the newest
    report, every health figure on the page describes a lifecycle that no longer
    exists, and the page kept showing pre-RESET warnings as current. Narrow on
    purpose: an in-flight reset does not trigger it, and it clears itself when a
    report arrives. Ties resolve to STALE — SQLite timestamps are second-
    resolution and a same-second report almost certainly predates the reset;
    erring the other way would hide the banner exactly when it matters.

(c) Unprovisioned-offsite warning (R-36 interim). enabled==true with type=="" is
    a real, stable, silent state: provisioning is Save-triggered and the
    re-enroll auto-re-issue deliberately skips an unprovisioned target, so
    nothing self-heals it. Reuses the exact predicate the offsite re-issue
    handler already refuses on.

(d) pbsdr_reissued rendered an EMPTY flash box — the key had no template branch,
    so re-issuing PBS credentials showed a success box with no words (observed
    live 2026-07-18). Now describes what was staged plus the R-39 caveat:
    confirm `pvesm status` shows the entry active, because a converged agent can
    report `applied` while the storage still 401s.

New .flash-warn (amber, --warn tokens) for the deviation tier between success
and error — exception-color principle: only on deviation, never on a healthy
page.

Tests assert each banner is ABSENT in the nominal cases as well as present in
the deviating one — a banner that always renders is worse than none. Both
red-proofed: deleting the pbsdr_reissued branch reproduces the original empty
box; neutering the staleness predicate fails the banner assertion. New
read-only store accessor CountSelfBindTokens makes the single-active invariant
assertable.

NOT in this train: the R-39 hub-side generation-bump fix the pre-travel task
made conditional. Its condition was REFUTED (SetHostDesired bumps
unconditionally; applyPBSDR is idempotent as documented) — the real mechanism is
the agent's descriptor-hash convergence and needs its own spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 21:45:11 +02:00
admin 28811c207b REPORT: website pre-Facebook refresh (audit table, deploy verification)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1W4wBum4JSFrbaEoDkMBy
2026-07-18 21:31:57 +02:00
admin d2fd935a71 website: drop the last Kubernetes tile from the index tech preview
Follow-up to be2fc50 — the index "Technológiák" preview still carried a
"Kubernetes / Üzleti szintű rendelkezésre állás" tile, an availability
promise with no capability-map row, pointing at a section that commit
removed. Replaced with the map-backed two-tier backup (§C tier-2 +
offsite, both PROVEN-LIVE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1W4wBum4JSFrbaEoDkMBy
2026-07-18 21:28:34 +02:00
admin be2fc507bf website: pre-Facebook content refresh — capability-map alignment + Zárt teszt landing
Audience shift: a Facebook post recruiting volunteer testers is about to send real
Hungarian households (mostly on phones) to a site that until now had zero stakes.
Every claim re-checked against documentation/architecture/00-capability-map.md.

- Naming ruling: "Felhő Felügyelő" removed site-wide (14 occurrences, now 0). The
  brand is Felhom; the interface is the vezérlőpult.
- index.html: new "Mit tud a doboz ma?" (8 map-traceable cards, incl. Hálózati
  megosztás and the customer-only recovery code) + new "Zárt teszt" section with
  stated limitations (one shared household password; TV-re streamelés hamarosan).
  CTA reuses the existing live contact-mailer via /kapcsolat?tema=zart-teszt.
- og:image was a site-wide 404 (pages pointed at a .png that never existed) —
  generated a branded 1200x630 card + width/height/alt. Load-bearing for the post.
- App count 45+ -> 53 (real catalog count).
- Cut unbacked claims: the Kubernetes/k3s section + multi-node tier, Tailscale ->
  WireGuard, the RAID card -> honest two-tier backup, gyik multi-user answer
  (both JSON-LD and visible copies), and the "azonnal értesítést kapsz" overclaim.

site_gates.py green. Mobile measured at 380px: scrollWidth == clientWidth == 365.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1W4wBum4JSFrbaEoDkMBy
2026-07-18 21:27:10 +02:00
admin 1f4702fe50 docs: golden 0.146.0 baked + published (Phase 5); STOP for the operator saves
Golden 0.146.0 baked on the drill VM and published to gitea:
  felhom-golden/0.146.0/golden.tar.zst
  sha256 4834c703162c5437467a329144b1a523019bf5693ab9d439558be7323587e955
  612696588 B (584 MB archive), controller 0.146.0 confirmed baked in

All pass markers green: Result=success/ExecMainStatus=0, 0 FATAL/exclusions,
docker OK (overlay2), ALL THREE mounts included (rootfs + mp0 /var/lib/docker +
mp1 /mnt/sys_drive), pre-delete HTTP 404 (the pre-gate — version did not exist),
upload HTTP 201.

Integrity verified independently of the build host: anonymous GET | sha256sum
matches byte-for-byte, ranged GET 206, content-length matches the bake's bytes.
The version now appears in the hub dropdown (0.136.0, 0.143.0, 0.146.0).

Teardown per GL-1: log copied out as evidence first
(180:/mnt/5_hdd/felhom.eu/drill/bake-0.146.0.log), guest 9100 purged, token +
script + log shredded in-VM, VM off, qemu confirmed gone via `ps -eo comm` (not
the self-matching pgrep -f), drill disk reverted to the virgin snapshot exactly
as found. Token-leak grep = 0 against the LITERAL token value, on the bake log
and both ISO build logs from this session.

REMAINING is operator-only and password-gated: Day-0 manifest Golden -> 0.146.0
(Agent stays 0.90.0, MinAgent stays 0.90.0 — v0.146.0 declares no new agent
coupling), then the floor -> v0.146.0 saved LAST.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 21:20:50 +02:00
admin b14b5a65d9 docs: pre-travel train — R-39 diagnosis + heal, R-33 shipped, golden deferred
ROADMAP:
 - R-39 gains the full live diagnosis and REFUTES the brief's hypothesis. The
   generation IS bumped (SetHostDesired bumps unconditionally, 2->3) and
   applyPBSDR is exonerated, so no hub fix was shipped. The real mechanism is a
   signal mismatch: the hub's re-consume signal is a generation bump + poke,
   while the agent re-applies on a change of the DESCRIPTOR CONTENT HASH
   (manager.go ~L235). An ep0 re-issue re-keys the secret of an EXISTING token,
   so token_id/fingerprint are unchanged, the descriptor is byte-identical, the
   hash never moves, and the fresh secret is never consumed -> 401 forever.
   Proof: consumed-failed.json carries the same hash a4e5424... as the marker
   written two minutes before the re-issue.
   Records the second defect found while healing (wrapper reconcile passing
   --server, fixed in agent v0.90.1), marks the box HEALED with evidence
   (pvesm active, token 200, a real 9.7 GB encrypted backup listed PBS-side),
   and leaves the fleet fix explicitly pending its own spec.
 - R-33 collapses to SHIPPED (scripts v1.21.0), incl. why
   TimeoutStartSec=infinity is the load-bearing half.
 - Pre-invite checklist: golden target moves 0.145.x -> 0.146.0 and notes it is
   now MORE stale, since v0.146.0 is live on the demo box while the golden still
   bakes 0.143.0.

REPORT overwritten with the train: R-39 diagnosis verbatim + heal evidence, the
two ISO shas with the byte-identical-payload verification, the nav polish and
why the screenshot leg could not be done (the demo controller password is
customer-owned since the claim flow, so the build-server credentials are stale),
Phase 4 skipped cleanly, and Phase 5 deferred rather than half-run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 21:02:34 +02:00
admin bcdb04222a scripts v1.21.0 — the pairing wait stops looking like a failure (R-33)
Waiting to be bound is the NORMAL state of a freshly installed box, and it must
not be reported as failure. The PAIRING poll loop used to BE systemd's
Restart=on-failure/RestartSec=30 — one poll per invocation, exiting non-zero
until the bind landed — so every 30s systemd printed "Failed to start Felhom
host bootstrap" on the physical console the CUSTOMER is watching. The
2026-07-18 N100 rehearsal measured 52 FAILED lines in ~11 minutes while nothing
was wrong (VALIDATION-n100-rehearsal-2026-07-18.md F6).

felhom-bootstrap.sh: run_pairing() is now a while-loop that sleeps
POLL_INTERVAL (30s — the hub-side rate is unchanged) between polls, so the unit
sits in `activating`. Registration split into register_appliance(), which
returns non-zero for a transient problem (no network yet, no identity, no
token) and is retried by the loop instead of taking the unit down. Cadence
constants: POLL_INTERVAL=30, BANNER_EVERY=10 (5 min), HEARTBEAT_EVERY=20
(10 min).

Quiet without going dark: a 204 is logged once on entry (worded so nobody reads
it as an error) and then only on the 10-minute heartbeat with elapsed minutes;
404 and unexpected codes degrade the same way. 410 STILL exits non-zero on
purpose — delivery consumed but no local env is a real crash window, and a
clean systemd restart is the right response.

Console banner: every 5 min instead of every cycle, single accented spelling
instead of the parositasra/párosításra double, and the reassurance the
rehearsal showed was missing ("Ez a képernyő magától frissül — nincs teendő a
doboznál").

felhom-bootstrap.service: TimeoutStartSec=infinity. This is load-bearing, not
cosmetic — a Type=oneshot ExecStart is killed at DefaultTimeoutStartSec (90s),
so without it systemd would kill the new in-script wait after 90 seconds and
Restart=on-failure would silently reinstate the exact spam this removes, after
appearing to work for the first three polls. Restart=/RestartSec= are kept
deliberately: they still cover the DIRECT path, a failed host-install, and 410.

Verified behaviourally, not assumed: driven in a throwaway Debian container
against a stub hub answering 204 five times then delivering — logged the wait
once plus one heartbeat, never exited between polls, then consumed the
delivery, wrote the 0600 env, fell through to the direct install in the same
invocation and exited 0. The old design produced five unit invocations and five
"Failed to start" console lines for that same sequence.

Hub endpoints, payloads, polling rate and one-shot delivery semantics are all
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 20:39:42 +02:00
admin 00f26c4f06 docs(report): N100 rehearsal S8 wrap
Overwrites REPORT.md per convention: evidence bundle manifest, the map rows
flipped with citations, ROADMAP IDs assigned (R-30..R-39 + R-27c), the seven
discrepancies found against the brief, and the remaining-to-first-invite line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 19:58:11 +02:00
admin aa9c08f0dd docs(rehearsal): N100 S8 wrap — VALIDATION, map flips, ROADMAP R-30..R-39, R-3 draft
The 2026-07-18 N100 rehearsal ran the complete final-product flow on real metal
in one pass: RESET -> generic pairing ISO v1.20.0 -> customer self-bind -> day-0
-> managed-floor lift -> escrow ceremony -> offsite snapshots. No code changed;
every finding is recorded and ranked, none fixed.

VALIDATION-n100-rehearsal-2026-07-18.md — run context, a UTC-normalised timeline
built from the hub events stream / hub DB / controller log / bootstrap + agent
journals, per-ledger verdicts for S1-S8 + ledgers 8 and 9, 12 findings, the
not-exercised list, and 7 discrepancies against the brief.

Headline wall-clocks: bind -> credential 26 s; bind -> controller running the
current version 2 min 44 s; managed floor 0.143.0 -> 0.145.0 in 5 s unattended
(initiated_by: auto-floor); escrow ceremony -> offsite enabled 12 s; drive enrol
30.3 s. No post-bind leg stalled, which is the immediacy row's real-onboarding
proof.

Capability map (10 citations added):
- Bare-metal Felhom ISO           PARTIAL     -> PROVEN-LIVE (F1 closed on metal)
- Customer self-bind (slice 1)    IMPLEMENTED -> PROVEN-LIVE (customer_selfbind)
- Guest RAM resize (R-24)         IMPLEMENTED -> PROVEN-LIVE (shrink AND grow)
- Customer RESET                  two real firings + verified external teardown
- Escrow ceremony                 first live wizard firing
- Immediacy row                   "real-onboarding proof pending" cleared
- Publish train                   box-side floor lift proven on a fresh install
- Customer claim                  R-4 gmail half (Inbox under p=quarantine)
- Offsite orphan guard            staged live leg fired on its own
- DR tier by default              candidate PROVEN-LIVE upgrade WITHDRAWN (R-39)
Not flipped, as instructed: customer-performs-restore, BYO, DLNA, multi-user.

ROADMAP — collapsed R-1 (appliance half done, Peti half survives), R-21
(physically closed), R-24, R-27 slice 1, R-4. New ranked items:
  P2-HIGH  R-39 PBS DR applied-but-dead   R-30 liveness from the wait channel
           R-31 async offsite + status    R-32 RESET base-dir purge
           R-33 bootstrap quiet-poll
  P2       R-34 backup lifecycle          R-35 config-apply session survival
           R-36 post-RESET offsite prompt R-27c console-passphrase bind
  P3       R-37 post-RESET health card    R-38 installer GRUB slice
Plus a pre-invite checklist (golden 0.145.x rebuild, freemail.hu, C6, R-11).

R-39 is NEW and was not on the brief: the PBS DR descriptor auto-provisions and
the agent converges state=applied, but pvesm reports 401 Unauthorized/inactive
and a direct probe 401s on every endpoint including /version while WG is healthy.
The hub minted a second token secret two minutes after the agent applied the
first and consumed_at is still NULL; the converged state machine will not
re-apply, and the agent's verify loop cannot read the credential to notice it
(non-root read of a file it writes through a root wrapper). Rank is provisional
pending Viktor.

R-3 draft: all four [REFINE] slots filled, self-bind made the default path with
"send the link BEFORE the customer sees the console", the measured wall-clock
table added, and interim operator workarounds for R-31/R-36/R-39. C6 (renumbered
C7) is marked as the single unexecuted step and keeps the doc a DRAFT.

Evidence bundle: 180:~/n100-rehearsal/ (10 files + MANIFEST.md), collected before
the box was unplugged for travel. Secrets read only to run probes; recorded as
lengths and metadata, never values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
2026-07-18 19:56:17 +02:00