# 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.

---

## 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:

```json
{ "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.
