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.
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:
- 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. - The CLOSED exemption was line-wide.
- [Customer RESET shipped](customer-reset-…-2026-07-17.md) — …; OPEN R-25bcontains "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.
D. The workspace symlink (R-230(b)) — done, and PROVEN
/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
- R-129 has fresh evidence.
target-selection.mdrecords 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. ~/.ssh/configcarries 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.- 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.
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.register_stateis 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.