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
20 KiB
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 live — ssh 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.6–1.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.7–2× 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
target-selection.mdcarries a known error (the t740's off-site tier). Out of scope per §12; still present.- The workspace-root
CLAUDE.mdlives outside any git repo./mnt/5_hdd/felhom.eu/gitis not a repository, so the live file is untracked and only thefelhom.eucopy 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. - 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 on192.168.0.180in the agent's own file. - 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.
- 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.
demo-hpalso hosts VM321 c11-appliance, running, alongside the drill VM. Seen in the sameqm list;nodes.mddoes not mention it. Not investigated.- 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. - The
felhom-agentcore is at 173 effective lines — passing, but with the least headroom. Its release section is the next candidate for thefelhom-build-deployskill when §12's corrections-only restriction is lifted.