Files
felhom.eu/REPORT-instruction-trim-part2-2026-08-06.md
T
admin 92a076c239
gates / gates (push) Successful in 8s
docs: backup verified IN the repository — snapshot b587f775, 118/118 + 40/40 (R-229)
58,158 files / 405.865 GiB in 27:56; 994 MiB added, 77 MiB stored -- the one-time full re-read
cost I/O, not storage, as predicted.

Also records a false alarm I raised against my own instrument: `restic ls <snapshot> <path>`
reported 0 of 40 archived memories because restic 0.18.0's path filter does not recurse into
subdirectories. The unfiltered listing shows all 40. An instrument that can drop results silently
is not a measurement -- and this one was measuring a safety property.
2026-08-06 11:22:34 +02:00

9.0 KiB

REPORT — instruction trim part 2: felhom.eu, the memory index, the versioned workspace

2026-08-06. Closes R-229 legs (a) and (c). Opens R-230 and R-231. Full per-block accounting: documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md.

Parallel session. A second Claude Code session was live in these repos throughout. Per CLAUDE.md's rule this report is a REPORT-<topic>.md sibling; the shared REPORT.md was not touched.

1. Baselines

felhom.eu c21bcf84f709 · felhom-controller 7db42c5fec3b · felhom-agent 062a7027abff — all clean, all HEAD == origin/main at start. Only felhom.eu was written to.

2. felhom.eu/CLAUDE.md

before after
raw lines 252 130
effective lines 227 115
bytes 16,698 7,824

Split into a core plus .claude/rules/{hub,website,manifests,docs}.md — 4 files, all paths:-scoped, all ≤60 effective lines. instructions_gate is registered in scripts/repo_gates.py: six gates, all OK. Order was load-bearing — trim first, register second, because a registered-but-failing gate refuses every push through .githooks/pre-push.

Two blocks were kept in the core against §6's sketch, both to avoid rebuilding the failure class they prevent: register discipline (applies to every session, not only documentation/** ones) and the R-110 installer fence (triggered by scripts/felhom-host-install.sh, which no fixed glob matches). Ledger §C.

3. Hook evidence for Scenario B — both directions

Two fresh sessions, so the negative control cannot be explained by prior loading:

Run File read hub.md website.md
A website/index.html path_glob_match
B hub/internal/api/handler.go path_glob_match

Why fresh sessions were required — the session's most useful negative result: after creating .claude/rules/, in-session reads that should have matched produced no hook line at all. A directory whose instructions were already seeded is not re-scanned. A rule file can be correct, pass every gate, and reach the model never, purely because of when it was created.

4. Always-loaded total

Measured /context (operator-supplied, this session): Memory files held at 11.5k tokens across three rule loads while Messages grew 8 → 105.7k. The 11.5k baseline is unchanged by this work, because MEMORY.md moved 17,688 → 17,977 bytes (+1.6%) — the reconciliation was net-neutral by design, trading 40 archived entries for 4 indexed ones plus trimmed detail. A fresh session is needed for a post-change token reading; the byte figures above are exact.

5. Memory reconciliation

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

before after
.md files 158 158 — zero deletions
orphaned 44 0
indexed / archived 113 / — 117 / 40
MEMORY.md 145 ln / 17,688 B 150 ln / 17,977 B

Headroom: 50 lines and 7,622 bytes (29%) against the 200-line / 25 KB limits. The index-vs-archive discriminator was the store's own type: field: all 4 reference orphans indexed, all 39 project + 1 untyped archived.

6. Staleness diagnosis — diagnosed, not fixed (R-230(a))

  • 21 lines carry component version literals
  • 5 lines carry bare host addresses nodes.md owns
  • 3 expired temporal statements; 2 undated open items

The structural finding, not the counts: felhom-agent/CLAUDE.md had its expired TEMPORARY … until ~2026-08-02 block deleted in part 1 and the gate now fails any such block — while MEMORY.md still asserts "demo boxes REMOTE till ~08-02". The contradiction was moved, not resolved: the hand-written half is clean, the auto-written half — which is larger and loads every session — still states the retired fact.

7. Secrets scan

535 keyword-matching lines across 104 files; 1 key: value-shaped hit, which is prose; 0 private-key blocks. No credential values. Nothing from the store was committed regardless.

8. Gates

repo_gates.py --fast6 gates, all OK, rc=0. Test suite 20 → 39 assertions, 0 failures.

Red-proof against the real store, not a fixture — ceiling 200 → 100:

memory index             : 150 lines (ceiling 100), 17977 bytes (ceiling 25600)
instructions_gate: 1 FAILURE(S)
  - /mnt/5_hdd/felhom.eu/git/.claude-memory/MEMORY.md: 150 lines, ceiling 100. Content past the
    auto-memory limit is DROPPED WITH NO ERROR — a truncated index is a silent failure with no
    observable. …

Ceiling restored; gate rc=0 and suite 39/0 again.

9. Installer

Idempotency: run 2 reported "nothing to do" and settings.json sha was identical before and after — the claim is "changed nothing the second time", not "ran twice without erroring".

Merge safety: sha256 of settings.json minus .hooks was f1775e94aa9d1ca9 before and after the write; all 7 top-level keys, 30 permission entries, 3 plugins, effortLevel, tui and additionalDirectories survived byte-identically.

10. Backup coverage — positive observable

From the journal of the real unit:

Including Claude auto-memory store: /mnt/5_hdd/felhom.eu/git/.claude-memory
start backup on [/mnt/4_hdd/data /mnt/5_hdd/felhom.eu/git/.claude-memory]

Completed and verified in the repository. Snapshot b587f775, 58,158 files / 405.865 GiB in 27:56, 994.132 MiB added (77.440 MiB stored) — 77 MiB stored for a 405 GiB re-read confirms the one-time cost was I/O, not storage. Contents: 161 .claude-memory entries — 118/118 top-level .md and 40/40 under archive/. The 03:07 snapshot holds /mnt/4_hdd/data only, so the two snapshots are a clean before/after in one listing.

A false alarm I raised against my own instrument. The first check used restic ls <snapshot> <path> and reported 0 of 40 archive files — which read exactly like 40 memories silently unprotected. It was wrong: restic 0.18.0's path filter lists entries at that path and does not recurse. The unfiltered listing shows all 40. Recorded because it is this project's own rule — an instrument that can drop results silently is not a measurement — biting a measurement made to verify a safety property.

Three caveats that make this weaker than "backed up" sounds: the destination is on the same physical disk as the store; the DooPlex backup set has no off-site leg (sync-hetzner-backups.sh is jarrs.eu and pulls the other way); and /opt/backup/scripts/ is itself unversioned host state (R-231).

11. Rules report — first run

8 events; session_start 4, path_glob_match 3, nested_traversal 1. 6 of 9 rule files had never fired. Not a defect list: the log only covers since the hook was armed, and felhom-controller/gates.md proves the distortion — it fired at 08:11 the same day, before installation, and reads as silent.

The hook now self-rotates at 5 MB, one generation.

12. context7 — nothing written

plugin:context7:context7 is ✘ failed (ENOENT on npx -y @upstash/context7-mcp); /mcp lists only the six Google auth stubs. Per §5 that means write nothing. The Go code is not the use case either: one direct dependency in felhom-agent, and the hub and controller are stdlib-only by standing rule.

12b. A stale claim the checklist caught — corrected in all four repos

Confirming this session's own push by run ID (checklist's last item) surfaced that four instruction files claimed CI was still owed (R-168) — and R-168 was CLOSED on 2026-08-02. CI exists, runs repo_gates.py --fast on every push, and emails on failure; this session's commits produced runs 117 (success) and 118.

One of the four was felhom.eu/CLAUDE.md, where this session carried the stale sentence forward through the trim. felhom-agent/CLAUDE.md contradicted itself — its release section already said R-168 mails the failure. Corrected in all four, pushed.

Two lessons about this task, not about CI: a trim is a volume operation and carries stale content forward unless each claim is re-checked; and the gate cannot catch this class — "this register item is closed" is not mechanically checkable from the instruction file. The thing that caught it was the checklist item demanding a run ID rather than a memory.

13. Registers

  • R-229 re-scoped — legs (a) and (c) CLOSED; (b) remains; (d) moved to R-230.
  • R-230 opened — the auto-written-staleness ruling, the symlink decision, the spec-as-failing-test pilot.
  • R-231 opened — /opt/backup/scripts/ is unversioned host state.
  • S-36 added to CONTEXT.md standing rulings.

14. Observations — not acted on

target-selection.md's t740 error; felhom-agent/CLAUDE.md at 173 effective lines; the /context Messages-accounting inference (§A of the ledger — reasoned, not measured); MEMORY.md's header still reading "felhom-controller Project Memory" though it indexes all four repos.