Files
felhom.eu/documentation/audits/LEDGER-instruction-closeout-2026-08-06.md
T
admin 5ca5082e7c
gates / gates (push) Successful in 8s
docs: close out the instruction arc — t740 corrected on evidence, registers, ledger, S-37
target-selection.md said demo-hp has no off-site tier. Measured first: pvesm list felhom-pbs on the
box returns two snapshots in demo-hp's OWN namespace (2026-07-28, 2026-08-04) against ep0's
felhom-offsite. The claim was TRUE WHEN WRITTEN and went stale when F10 resolved 2026-07-23. The
measurement is kept in an HTML comment beside the corrected sentence. This file decides which
machine may be destroyed, so the sentence was load-bearing, not cosmetic.

R-229(b) CLOSED (agent 175 -> 99 eff). R-230(b) CLOSED (symlink, proven from fresh sessions).
R-230(a) part-actioned -- three false statements fixed, WARN loop added, bulk ruling still owed.

S-37: a claim in an instruction file is checked, not trusted.
2026-08-06 11:49:22 +02:00

12 KiB

LEDGER — instruction arc close-out (agent split, memory truth, checks 6-content and 7, symlink, t740, context7)

Third and final part. Companions: LEDGER-instruction-trim-2026-08-06.md, LEDGER-instruction-trim-part2-2026-08-06.md. Same per-block format.

Baselines, reconfirmed live before editing, all clean and HEAD == origin/main: felhom.eu 92a076c239b9, felhom-controller 66d80efb9f20, felhom-agent 5b2666e3a2ae, app-catalog-felhom.eu 459766cb1639.


A. felhom-agent/CLAUDE.md — 175 → 99 effective lines

Measured 175, not the spec's 173: the CI correction pushed in part 2 added two lines.

Heading / first words Class Destination Reason
## Layout (verified against the tree) (27 ln) derivable rule-file:* + deleted-derivable the tree and REUSE.md are its home — but the per-package annotations that were doing real work moved into the rule file for the area they describe, rather than being dropped as the controller's were
## Build / run release table + R-115/R-186/R-188 narrative (34 ln) duplicated already-in: the felhom-build-deploy skill the file already said "never hand-roll it" and then restated the table, which drifts from the script it describes
UPID/WaitTask, privsep intersection, TLS pinning (6 ln) path-bound rule-file:proxmox.md only bite when internal/proxmox/** is open
destructive-op trap rows (4 ln, from REUSE.md §3) path-bound rule-file:proxmox.md
local-API scoping, nonce store, bind traps path-bound rule-file:localapi.md
PBS three laws, verify default, prune grant path-bound rule-file:backup.md
format-safety, durable-ID schemes, escrow errors path-bound rule-file:storage.md
## Demo host addresses (8 ln) duplicated already-in:operations/nodes.md replaced by two rows in the retrieval map
CHANGELOG/REPORT/secrets blockquote, code-quality line duplicated already-in:CLAUDE.md (workspace root)
artifact taxonomy, TASK.md/RUNBOOK-*.md (12 ln) duplicated already-in:CLAUDE.md (workspace root)

Kept in the core — it is the only part re-injected after /compact: the root-CLI fence and its three named exceptions, the destructive-op gate, prove-ownership (audit A1), the gate entry point, the F9 live-validation fence, trunk-based with its revert-and-report escape hatch, and the checklist.

New: .claude/rules/{proxmox,localapi,backup,storage}.md, all ≤46 effective lines.

Glob overlap, stated rather than silently resolved. health-checks.md already matched internal/{capability,storage,localapi,hub,guesthook}/**, which localapi.md and storage.md now also match. Both rules load in those directories and neither supersedes the other; each new file says so in its own text, so a reader who sees two rules fire is not left guessing which wins.


B. The memory index — three false statements, corrected

Backup first: /mnt/5_hdd/felhom.eu/backups/claude-memory-20260806-112853 (158 files).

The premise needed correcting before the work could be done, and the error was mine. Part 2's report said "3 expired temporal statements", and this task inherited that number. Re-run with the cause printed, all three matched the ISO date inside a markdown link TARGET — a filename, not a claim — while the one real expired claim in the same file was written ~08-02, carried no ISO date, and was never matched. A scan that reports the wrong three and misses the right one is worse than no scan.

The three genuinely false statements, each established by evidence rather than by the scan:

Line Claim How it was falsified
17 R-193 decision open its register row reads **CLOSED 2026-08-05 — controller v0.200.0**
34 demo boxes REMOTE till ~08-02 live: ssh felhom-pve answers and holds 192.168.0.162/24 on vmbr0 — the home LAN, same /24 as DooPlex
68 OPEN R-25b ROADMAP: **SHIPPED hub v0.69.0 (2026-07-21)**; capability map: R-25b CLOSED

Only the temporal claim was removed from line 34; its durable half — that ssh felhom-pve still resolves to the tailnet address while felhom-pve-lan is the LAN alias — was kept, because it is still true and still surprising.

Nothing else in the file was edited. No memory file was deleted. 158 files before and after. The remaining 32 version literals and 4 host addresses stay, for the warning loop to work on — their survival is deliberate, not an oversight.


C. Check 6 gains content WARNings; check 7 is new

Why WARN and not FAIL for the index

Claude writes MEMORY.md between sessions, so a hard failure would refuse a human's push over a line no human typed. The warning is read by the same model that will next edit the file, which makes the loop self-correcting rather than a chore for a person.

Check 7 — a citation that calls a register item OPEN must be right

Deviation from the task's literal wording, stated because it is a real one. The task says every citation of a non-open item must itself contain CLOSED. Applied literally that fires on ~30 legitimate provenance citations — (R-161), R-117 spike §6.3 — which cite an item as the source of a fact, not as outstanding work. A gate that noisy is switched off, which is precisely the fate R-29 documented. The trigger is therefore an OPENNESS CLAIM, which still convicts both real defects and keeps the spec's required pass case (R-168, CLOSED 2026-08-02) green.

Two bugs found by this check's own red-proofs, both of which would have shipped:

  1. The state marker is not self-closing. Real rows read **SHIPPED — and the alarm is DEMONSTRATED**. The first parser required **SHIPPED** and therefore read R-168 — the row the whole check exists for — as OPEN. A gate that cannot convict its own founding case is decoration.
  2. The CLOSED exemption was line-wide. - [Customer RESET shipped](customer-reset-…-2026-07-17.md) — …; OPEN R-25b contains "shipped" twice, in a title and a filename, and the whole line was pardoned. Found only because the red-proof failed to go red, and now scoped to the citation's clause with a regression test.

Suite 39 → 68 assertions, 0 failures. Red-proofs against real files, both directions, quoted in the report.

What check 7 finds across the four repos right now: nothing — 19 citations, 0 failures. That is because part 2 already corrected the four stale sentences; the red-proof, not the zero, is what shows the check works.


/mnt/5_hdd/felhom.eu/git/CLAUDE.md is now a relative symlink to felhom.eu/documentation/runbooks/workspace-CLAUDE.md. Backup of the pre-link file: /mnt/5_hdd/felhom.eu/backups/workspace-CLAUDE.md.bak-20260806-113952.

Check 5 now has two legal shapes and asserts a different thing in each:

Shape Asserted Rationale
LINK it points at the versioned copy and resolves to a real file divergence is impossible; what can still break is the link. A dangling link is worse than a diverged copy — the instructions load NOTHING and there is no content left to notice is wrong
COPY byte-identical, as before a clone elsewhere may legitimately have two files; the link is not forced on anyone

Proven, not assumed — three fresh sessions each logged session_start … /mnt/5_hdd/felhom.eu/git/CLAUDE.md (Claude Code reports the LINK path, not the resolved one). And because a logged path proves discovery rather than delivery, a fourth fresh session with no tools at all was asked to quote standing rule 1 and returned it verbatim. The content reaches the model.


E. The t740 off-site claim — measured, then corrected

target-selection.md said the PBS-DR / off-site tier is carried by the N100 and "(demo-hp has none)", and used that as the reason to steer backup-disturbing tests at demo-felhom.

Measured on the box, from the tier's own evidence:

demo-hp:/etc/pve/storage.cfg   pbs: felhom-pbs -> datastore felhom-offsite, server 10.77.0.1,
                               namespace demo-hp, username felhom@pbs!demo-hp
demo-hp# LC_ALL=C pvesm list felhom-pbs
  felhom-pbs:backup/ct/9201/2026-07-28T19:19:45Z  pbs-ct  6264034048  9201
  felhom-pbs:backup/ct/9201/2026-08-04T19:24:16Z  pbs-ct  4637840512  9201
ep0:/mnt/pbs-datastore/ns/     c11  demo-felhom  demo-hp  rewalk

Two snapshots, newest two days old, in demo-hp's own namespace on the shared off-site datastore. The claim is false. It was true when written and went stale when F10 resolved on 2026-07-23 — the first snapshot is 2026-07-28. Corrected, with the measurement kept in an HTML comment beside it.

Why this outranked a typo: the sentence was load-bearing for "which machine may be destroyed". "Its backup chain is not disturbable" is exactly the belief under which a drill lands somewhere it should not.


F. context7 — diagnosed, nothing written

There is no Node.js on DooPlex at all. npx, node and npm are all absent from PATH, no nodejs/npm package is installed, and no runtime directory exists. The plugin manifest (~/.claude/plugins/.../context7/.mcp.json) is:

{ "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp"] } }

So the ENOENT is literal: the npx binary does not exist. The fix is one package install away — and that decision is not this task's to make. DooPlex is Tier 2 and is the recovery chain (hub, Gitea, registry, PBS, k3s+Longhorn); adding a language runtime to it for a docs-lookup server is its own change with its own review, not a rider on a documentation task.

Whether it is worth pursuing — measured, not repeated from the spec:

Module Direct third-party dependencies
felhom-agent 1 (golang.org/x/crypto)
felhom.eu/hub 3
felhom-controller 6

~8 distinct packages, all small, stable and well known. The plausible consumer is the app catalog's upstream images — and there the skill's existing trap already answers it: a more authoritative-sounding source of guesses is still a source of guesses, and docker inspect decides. Per §6, nothing was written to felhom-app-catalog/SKILL.md or to any rule file.


G. Before / after

File before (eff/B) after (eff/B)
workspace root CLAUDE.md 142 / 11,280 142 / 11,280 — now a SYMLINK, one file not two
felhom.eu/CLAUDE.md 117 / 8,006 117 / 8,006 (untouched)
felhom-controller/CLAUDE.md 92 / 6,341 92 / 6,341 (untouched)
felhom-agent/CLAUDE.md 175 / 14,093 99 / 6,267
app-catalog-felhom.eu/CLAUDE.md 80 / 6,397 80 / 6,397 (untouched)
rule files, workspace-wide 9 13, every one paths:-scoped, all ≤60 effective
MEMORY.md 150 ln / 17,977 B 150 ln / 18,078 B — 3 statements corrected
gate suite 39 assertions 68

Every CLAUDE.md in the workspace is now under 120 effective lines except the workspace root at 142, which is deliberate: it is the only file re-injected after /compact.


H. Observations — noticed, not acted on

  1. R-129 has fresh evidence. target-selection.md records that demo-hp has no baked SSH key and needs G1 break-glass, "but a key authenticated on 2026-07-31 — R-129, unresolved". A key authenticated again today, 2026-08-06, non-interactively (BatchMode=yes) from DooPlex — that is how the t740 evidence in §E was gathered. The row is still right to be open, and now has a second dated observation.
  2. ~/.ssh/config carries the same expired vacation claim the memory index did: "demo-hp … at the VACATION site until ~2026-08-02, LAN 192.168.0.87 there". It is host state, outside every repo and outside this task's scope, but it is the same statement in a third place.
  3. The hub's operator UI did not answer over its ClusterIP during this session (request hung, 2 min timeout) — the off-site evidence was gathered from the boxes instead. Not investigated; the memory note describing that access path may need re-checking.
  4. MEMORY.md's header still reads "felhom-controller Project Memory" though it indexes all four repos plus the homelab. Cosmetic, left for the R-230(a) ruling.
  5. register_state is now a reusable register parser living in the gate. If anything else ever needs "is R-nnn open", it should call this rather than re-deriving it — the two parsing traps in §C are not obvious and were both found the hard way.