Files
felhom.eu/documentation/audits/LEDGER-instruction-trim-2026-08-06.md
admin c21bcf84f7
gates / gates (push) Successful in 7s
docs+gate: instruction files cannot silently regrow (R-229)
New shared scripts/instructions_gate.py, registered in controller_gates.py and
agent_gates.py, never copied into a sibling repo (the reuse_refs_check.py
precedent). 20 fixture tests, all asserting the effect: exit code AND that the
message names the file and the reason.

It is a consistency gate, not a budget gate, and the failure message says so. A
/context reading measured the instruction files at 15k tokens against 869k free in
a 1M window -- space is not the constraint, and a future reader must not re-derive
the wrong reason. The 200-line ceiling is adherence guidance; a file nobody can
hold in their head is where contradictions hide, and five were found here.

Checks run against effective text (HTML comments stripped, because they are
stripped before injection): the line ceiling; every .claude/rules/*.md declares
paths: or an explicit unconditional: true; no component version literal; no
TEMPORARY block carrying a past date; and the workspace-root CLAUDE.md is
byte-identical to its versioned copy -- the live file sits outside any git repo,
so that copy is its only version-controlled record.

Two traps recorded so they are not reintroduced: a bare \d+\.\d+\.\d+ matches the
first three octets of every IPv4 (the gate excludes dotted quads, or it fails on
192.168.0.180 in the agent's own file); and unconditional: true is NOT a Claude
Code feature but this project's own marker.

Workspace-root CLAUDE.md 208 -> 182 lines (142 effective), copy kept identical.
The nine-instance invariant table moved into the felhom-testing skill, which
triggers when writing or reviewing a test; all three directive bullets stayed in
the core. felhom.eu/CLAUDE.md got surgical corrections only and is knowingly still
over the ceiling at 227 effective lines -- closing it needs the restructure R-229
defers, said plainly rather than quietly absorbed.

CONTEXT.md gains standing ruling S-35. OPEN-ITEMS.md gains R-229.

Docs only -- no Go, no version bump, nothing built or deployed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJc8sAGRWmavP3rMtdpkr2
2026-08-06 09:38:52 +02:00

20 KiB
Raw Permalink Blame History

LEDGER — instruction-file trim, 2026-08-06

Every block removed from a CLAUDE.md in this task, where it went, and why. Nothing was deleted without a row here. Destinations: rule-file, html-comment, already-in:<path>, deleted-stale, deleted-derivable, deleted-amnesty.

Baselines (verified against live Gitea before starting): felhom-controller a62bb3874b25, felhom-agent a2e914f683bd, felhom.eu d30c2a51ed2a, app-catalog-felhom.eu ee2c8102016a — all HEAD == origin/main, all clean.

Correction to the spec's §1 table: the workspace-root CLAUDE.md measured 16,642 bytes / 208 lines, not the stated 15,431 / 207 — it was edited at 08:32 on 2026-08-06, after the spec was written. The other five files matched the spec exactly.


A. Mechanism verification (done before any edit — the task's design depends on these)

Claim (spec §3) Verdict Evidence
.claude/rules/*.md + paths: loads only on a matching file read confirmed product-authored strings in the installed Claude Code 2.1.222 binary describe exactly this
Block-level HTML comments are stripped before injection confirmed empirically control (two plain markers → both reported) vs treatment (one marker inside <!-- --> → not reported, run twice), via claude -p against a scratch CLAUDE.md
unconditional: true frontmatter NOT a product feature absent from the build. It is this project's own marker; the gate documents it as such
Root CLAUDE.md survives /compact not verified taken from the spec; drove keeping fences in the core, so recorded as an assumption

The HTML-comment test needed two attempts. The first asked an open question ("list the markers you can see") and the red-proof did not go red — the model reported one marker in both arms, so the instrument was dropping a result silently. Rewritten as two forced yes/no questions with a both-plain control, it discriminated cleanly. Recorded because it is the project's own rule ("an instrument that can drop results silently is not a measurement") catching a measurement made for this task.


B. felhom-controller/CLAUDE.md — 215 → 110 lines (92 effective)

Heading / first words Class Destination Reason
## Layout (verified against the tree) (36 ln) derivable deleted-derivable reconstructible by ls controller/internal/; the per-package seams and traps the annotations stood in for are REUSE.md's job
!!! IMPORTANT !!! header (3 ln) duplicated already-in:CLAUDE.md end-of-session checklist items 3 and 5 a rule stated twice in one file gets edited in one of them
## Environment & access host table (13 ln) duplicated + stale already-in:documentation/operations/nodes.md see §D — three separate defects in one table
> **Legacy: Windows workstation.** (4 ln) duplicated already-in:CLAUDE.md (workspace root, as an HTML comment) archival; the root carries the fuller version
> **felhom-pve is back on the home LAN…** (8 ln) stale deleted-stale bookkeeping about a retired block; the record is audits/AUDIT-vacation-remote-ops-2026-07-20.md
gates paragraph, **Run … controller_gates.py** (16 ln) path-bound rule-file:.claude/rules/gates.md only matters when Go/HTML/CSS/scripts are being edited
R-29 "why a runner and not seven invocations" (6 ln) inert rationale html-comment in gates.md nobody acts on it; it exists so a future reader narrows the rule correctly
logging paragraph (3 ln) path-bound rule-file:.claude/rules/gates.md applies when writing Go
Hungarian-UI + design-tokens line (2 ln) path-bound rule-file:.claude/rules/ui-hungarian.md applies when editing templates/CSS
ASCII-grep trap + !-in-credentials trap (8 ln) path-bound gotcha rule-file:.claude/rules/ui-hungarian.md both are UI-validation traps; the core keeps a pointer
coupled-features / featureProbes (4 ln) path-bound rule-file:.claude/rules/agent-coupling.md applies only in internal/agentapi/
> **In every repository…** CHANGELOG/REPORT/secrets (5 ln) duplicated already-in:CLAUDE.md (workspace root) §8.4 names the root as the single home; the root always loads and survives /compact
generic code-quality line (1 ln) duplicated already-in:CLAUDE.md (workspace root) "double-check for bugs, add debug logging, ask rather than guess"
build/deploy 4-step table (10 ln) duplicated already-in: the felhom-build-deploy skill the file already said to use the skill, then restated it; the bootstrap-managed/no-compose-file gotcha was kept

Kept deliberately: the seven session-critical invariants (highest-value block in the file), the F9 live-validation fence, the CHANGELOG.md read-discipline, the end-of-session checklist.

New: .claude/rules/{gates,ui-hungarian,backup-paths,agent-coupling}.md — 4 files, all paths:-scoped, all under 60 lines. backup-paths.md additionally carries the R-181 consequence-vs- mechanism lesson and "presence is not success", which apply exactly where backup code is written.


C. Workspace-root CLAUDE.md (+ its versioned copy) — 208 → 182 lines (142 effective)

Heading / first words Class Destination Reason
## Per-repo guidance (7 ln) derivable deleted-derivable those files load on their own; the section said so itself
## Skills roster, 4 names + purposes (5 ln) duplicated already-in: the resident skill listing kept the install_skills.py line, which is not derivable
(119 files) in ## Memory stale + derivable deleted-stale the directory holds 157 memory files plus the index
## Access host table, 5 rows (7 ln) duplicated already-in:documentation/operations/nodes.md replaced by a pointer; the Tier-2 DooPlex fence and the "an absent fence is not permission" line were kept
## Legacy: Windows workstation (14 ln) archival html-comment operator wants it kept for revival; invisible to the model, still in the file for a human
R-96 four incident narratives (18 ln) inert rationale html-comment the four rules stay as directives; only the stories moved
"Presence is not success" 2-row table (5 ln) inert rationale html-comment rule + corollary stay in the core
nine-row invariant table (11 ln) actionable doctrine already-in: felhom.eu/skills/felhom-testing/SKILL.md it triggers on writing/reviewing a test, hardening a guard, fixing a bug — exactly when the table matters. All three directive bullets stayed in the core

Kept deliberately: every production-infrastructure prohibition, the artifact taxonomy, the four R-96 rules, the clean-tree gate, the CHANGELOG/REPORT/secrets blockquote, trunk-based, the live-validation fence, the no-versions-in-docs rule, the target-selection fence.

Copy discipline: felhom.eu/documentation/runbooks/workspace-CLAUDE.md was re-synced and is byte-identical (cmp clean). The live file sits in a directory that is not a git repo, so the copy is the only version-controlled record of it — the new gate now enforces the equality.


D. felhom-agent/CLAUDE.md — surgical only, 216 → 205 lines (173 effective)

Heading / first words Class Destination Reason
> **TEMPORARY — felhom-pve is at a remote site (until ~2026-08-02).** (16 ln) stale deleted-stale expired four days before this task and instructed its own deletion; the location-independence fact worth keeping moved to an HTML comment
> **Legacy: Windows workstation.** (4 ln) duplicated html-comment root carries the full version
drill-VM claim, "that ruling is realized — it hosts drill VM 300" (5 ln) duplicated already-in:documentation/operations/nodes.md:96 this file was right (see §F) but the fact already had a single home
host addresses / node names / break-glass (4 ln) duplicated already-in:documentation/operations/nodes.md replaced by a pointer
felhom-agent --version → 0.115.0 stale deleted-stale version literal; went with the TEMPORARY block
go.mod directive go 1.25.0 derivable deleted-derivable go.mod's business
module path + binary name line (1 ln) derivable deleted-derivable go.mod + the tree
R-115 / R-188 / R-186 release narratives (26 ln) inert rationale html-comment + already-in: felhom-build-deploy skill the directives stayed (never hand-roll; the order; reproducible build); the history and the verification recipe moved
health-check block-I/O rule (4 ln) path-bound rule-file:.claude/rules/health-checks.md it was duplicated from felhom.eu/CLAUDE.md with a note explaining why — that reasoning predates path-scoped rules

New: felhom-agent/.claude/rules/health-checks.md, scoped to the five packages where health checks are written.


E. felhom.eu/CLAUDE.md — surgical only, 241 → 235 lines (227 effective — still over the 200 ceiling)

Heading / first words Class Destination Reason
## Tech stack (Hub) list (5 ln) derivable deleted-derivable hub/go.mod + manifests/; the two constraints that are not derivable ("no web frameworks", "never inline stringData") were kept
three generic code-quality bullets (3 ln) amnesty deleted-amnesty "double-check generated code", "handle edge cases", "add debug capabilities" — no recorded production violation, recoverable worst case. "ask first — don't guess" was kept: it is a real behavioural directive
skills roster, 4 names (4 ln) duplicated already-in: the resident skill listing kept the install_skills.py line
host table, 3 rows (6 ln) duplicated already-in:documentation/operations/nodes.md replaced by a pointer; the Tier-2/Tier-0 verdicts kept inline
> **Legacy: Windows workstation.** (3 ln) duplicated html-comment root carries the full version
seam-wiring / -run / health-check citations (12 ln) inert rationale html-comment the rules stayed; the incident detail and the three version literals moved into the comment beside them
R-29 gate-census narrative (6 ln) inert rationale html-comment as elsewhere
git add -A sweep incident citation (1 ln) inert rationale html-comment the prohibition stayed

F. The contradictions — resolved, not annotated

# Contradiction Resolution Where the fact lives now
1 agent: demo-hp "hosts drill VM 300 (drill-r50)" vs controller: "no drill VM is provisioned there yet" measured livessh demo-hp "qm list" shows 300 drill-r50 stopped. felhom-agent was right; felhom-controller was wrong. No new text needed: documentation/operations/nodes.md:96 already stated it correctly, and runbooks/target-selection.md:64 already fences it nodes.md (unchanged)
2 agent: TEMPORARY — felhom-pve is at a remote site (until ~2026-08-02) vs controller: "back on the home LAN (2026-07-25)" both blocks deleted; neither belongs in a CLAUDE.md. The gate now fails any TEMPORARY block whose date has passed audits/AUDIT-vacation-remote-ops-2026-07-20.md
3 agent 0.93.0 recorded in controller's host table, against the root file's own no-versions rule deleted, with every other version literal in effective text ask the hub /hosts + /configs, or the box
4 controller gave demo-felhom as plain root@192.168.0.162 (the LAN fallback) while the other two documented the Tailscale route host tables removed from all three CLAUDE.md files nodes.md
5 root file: memory is (119 files); the directory holds 157 + the index parenthetical deleted rather than corrected — it is derivable and would go stale again ls .claude-memory/

The sweep found no contradictions beyond the five. After the trim: zero version literals in effective text across all four files, zero TEMPORARY blocks, and exactly one file (nodes.md) stating the drill-VM status.


G. Amnesty list (ruling 1) — every prohibition deleted, and why it qualified

Deliberately short. Only one item met the bar.

Rule deleted Where Why it qualified
"Always double-check generated code for bugs, logic issues, syntax errors" / "Handle edge cases without overcomplicating" / "Add debug capabilities (logging, verbose output)" felhom.eu/CLAUDE.md Generic best practice with no recorded production violation and a recoverable worst case. Also duplicated in the workspace-root file, which keeps the one clause that is a real directive: ask rather than guess

Nothing on the protected list was touched. The eight-invariant table (in fact nine rows — the spec's §8.1 undercounts it), "presence is not success", the four R-96 standing rules, the F9 live-validation fence, secrets-never-in-committed-files, trunk-based/no-branches, the DooPlex protection rules, the destructive-target fences with their permitted targets, and the clean-tree gate all survive — moved or compressed, never dropped, and every compressed fence kept its permitted target and its reason.


H. Before / after

File before (ln/B) after raw (ln/B) after effective (ln/B) ceiling
workspace-root CLAUDE.md 208 / 16,642 182 / 11,280 142 / 8,105 pass
felhom.eu/…/workspace-CLAUDE.md 208 / 16,642 182 / 11,280 142 / 8,105 pass (byte-identical)
felhom-controller/CLAUDE.md 215 / 14,775 110 / 6,341 92 / 4,881 pass
felhom-agent/CLAUDE.md 216 / 15,554 205 / 13,619 173 / 11,491 pass
felhom.eu/CLAUDE.md 241 / 17,471 235 / 17,003 227 / 16,286 OVER — deferred, see below
app-catalog-felhom.eu/CLAUDE.md 79 / 6,294 untouched 79 / 6,294 pass — the reference shape

Effective = with block-level HTML comments stripped, i.e. what the model actually receives.

Controller-session total (workspace root + repo file): 31,417 → 17,621 bytes raw, and 12,986 bytes effective — a 59% reduction in what loads. Stated as bytes, not tokens: the measured /context ratio (§I) shows a bytes/4 token estimate understates the real cost by 1.61.9×, so the byte figure is the honest one.

felhom.eu/CLAUDE.md is knowingly left over the ceiling at 227 effective lines. Spec §12 forbids restructuring it into core+rules ("a diff Viktor cannot read is a diff that gets approved unread") and §6.2 registers the gate only in the controller and agent runners, so it is not gated today. Bringing it under 200 would require the restructure §12 prohibits. Filed as deferred work (§N.5 row below); when that lands, register instructions_gate in felhom.eu/scripts/repo_gates.py too.


I. Measured context baseline (operator-supplied /context, workspace root, Opus 5 / 1M window)

Category Measured
Window 1,000,000 tokens · 131.1k used (13%) · 868.9k free (86.9%)
Memory files 2 files · 15.0k tokens (1.5%) — root CLAUDE.md 6.6k + MEMORY.md 8.4k
Skills 19 · 2.5k (0.3%)
MCP tools deferred · 0 tokens

Which files load, settled: the /context breakdown names exactly two memory files at the workspace root — the root CLAUDE.md and the auto-memory MEMORY.md. The per-repo CLAUDE.md files are not loaded at launch; they load on demand once a file in that directory is read. This confirms the spec's §1 assumption and is why the irreversible fences were kept in the root file.

Token ratio, measured: root CLAUDE.md 16,642 B → 6.6k tokens (2.52 B/token); MEMORY.md 17,688 B → 8.4k tokens (2.11 B/token). A bytes/4 estimate understates by 1.59× and 1.90× respectively — the spec's 1.72× range is right, and every disk-based figure in this ledger is labelled as bytes for that reason.

Space was never the constraint. 869k tokens were free. The justification for this task is the contradiction count (§F) and the adherence guidance, and the gate's failure message says so explicitly so that no future reader re-derives the wrong reason.


J. MEMORY.md — measured only, unchanged (ruling 4 deferred it)

Measure Value Limit Verdict
Lines 146 200 within
Size 17,688 B (17.3 KB) 25 KB within
Referenced past the limit 0 nothing is truncated

The expected finding — a truncated index — is not present. A different one is: the index references 113 distinct memory files while 157 exist on disk (plus the index itself), and zero referenced files are missing. So 44 memory files exist that the index never points at. Recorded, not acted on.


K. Reconciliation

Repo git diff --stat Ledger rows
felhom-controller CLAUDE.md 92 ins / 194 del; controller_gates.py +3 14 rows (§B)
felhom-agent CLAUDE.md 65 ins / 73 del; agent_gates.py +3 9 rows (§D)
felhom.eu CLAUDE.md +113 ctx; workspace-CLAUDE.md 242 changed; SKILL.md +25 8 rows (§C) + 8 rows (§E)

Rows reconcile against the diffstat: every deleted block above appears in a deletion, and the insertions are the replacement pointers, the rule files, the HTML comments and the skill section. Files created: 5 rule files, instructions_gate.py, test_instructions_gate.py, this ledger.


L. Observations — noticed, not acted on

  1. target-selection.md carries a known error (the t740's off-site tier). Out of scope per §12; still present.
  2. The workspace-root CLAUDE.md lives outside any git repo. /mnt/5_hdd/felhom.eu/git is not a repository, so the live file is untracked and only the felhom.eu copy is version-controlled. Making the live file a symlink to the versioned copy would remove the divergence class entirely and looks safe here — both are on the same filesystem and Claude Code reads through symlinks (the four skills are already symlinks into this tree). Not done in this task, per §3.2. If adopted, the gate's copy-identity check becomes trivially true and should be replaced by a check that the symlink still points where it should.
  3. A blanket version-literal ban has a false-positive class the spec did not anticipate: a bare \d+\.\d+\.\d+ matches the first three octets of every IPv4 address. The gate excludes dotted quads; without that it fails on 192.168.0.180 in the agent's own file.
  4. Historical version citations are not fleet state. "fixed in hub v0.97.0" cannot go stale the way "this box runs agent 0.93.0" can. Rather than carve an exception into the gate, each citation moved into the HTML comment beside its rule — the rule text stays clean and the gate stays absolute. Recorded because it is a deviation from §8.2 item 3's literal wording ("delete every one") in favour of its stated purpose.
  5. The spec's §8.1 calls the invariant table "the eight-invariant table"; it has nine rows and its own text says "Nine instances". Nothing was dropped — flagging the miscount so the protected list is not later applied to eight of nine.
  6. demo-hp also hosts VM 321 c11-appliance, running, alongside the drill VM. Seen in the same qm list; nodes.md does not mention it. Not investigated.
  7. Two unused Claude Code plugins and a broad user-scope allowlist (Bash(python3:*), Bash(curl:*), Bash(scp:*) — standing arbitrary execution and network egress in every project) were found by the earlier setup audit. These live in ~/.claude/settings.json, are not in any repo, and are deliberately out of scope here. Recorded only.
  8. The felhom-agent core is at 173 effective lines — passing, but with the least headroom. Its release section is the next candidate for the felhom-build-deploy skill when §12's corrections-only restriction is lifted.