# 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

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.
