docs: ledger, registers and standing ruling S-36 for the part-2 instruction/memory work (R-229)
gates / gates (push) Successful in 8s

R-229 legs (a) and (c) CLOSED. R-230 opened for what this deliberately left -- the ruling owed on
auto-written staleness, the symlink decision, the spec-as-failing-test pilot. R-231 opened for
/opt/backup/scripts/ being unversioned host state, found while adding the memory store to the
backup set: the same class this task was closing, inside the fix for it.

S-36: the auto-memory store is backed up, never committed; no memory file is ever deleted;
a rule file created mid-session is NOT live in that session.

Second session in this repo, so REPORT-<topic>.md per CLAUDE.md; shared REPORT.md untouched.
This commit is contained in:
2026-08-06 11:01:13 +02:00
parent f65ea89a24
commit 9c10c5d600
4 changed files with 427 additions and 1 deletions
+30
View File
@@ -17,6 +17,36 @@
## Standing rulings ## Standing rulings
**S-36 — THE AUTO-MEMORY STORE IS BACKED UP, NOT COMMITTED; AND THE WORKSPACE IS INSTALLABLE
(2026-08-06, R-229 part 2).**
1. **`.claude-memory/` is never committed.** It is **auto-written** — Claude writes it, so nobody
reviews it before it lands — and may name hosts, paths and out-of-band secret locations that the
secrets rule keeps out of committed files. A scan on 2026-08-06 found no credential *values* (535
keyword mentions across 104 files, one `key: value`-shaped hit that was prose, zero private-key
blocks), but "no secrets today" is not a property a directory keeps on its own when a machine
writes to it unattended. It is protected by `dooplex-backup.service` instead
(`CLAUDE_MEMORY_DIR`). **Reversing this needs a passing secrets scan first.**
2. **No memory file is ever deleted.** Set-aside means **moved** to `.claude-memory/archive/`. The
part-2 reconciliation moved 40 and indexed 4, with the file count identical before and after.
3. **`MEMORY.md` is the index, and the index is what loads.** Topic files are read on demand, so
detail belongs in them, not in the index. `instructions_gate` check 6 now enforces the limits —
over-limit FAILS (auto-memory drops content past the limit with no error), an orphan WARNS (the
store is outside git), an absent store PASSES **while printing its reason**.
4. **Correcting auto-written content is NOT yet ruled on.** The part-2 session diagnosed 21 lines
carrying version literals, 5 carrying host addresses, and an entry still asserting a fact whose
hand-written twin was deleted as expired — and **changed none of them**. R-230(a).
5. **The workspace is installable.** `scripts/install_workspace.py` lays down the workspace-root
`CLAUDE.md` and the `InstructionsLoaded` hook from versioned copies. Its targets are **live
configuration**, not disposable like the skills: it backs up before every write, **merges**
`settings.json` owning exactly one key, and **refuses** an unparseable settings file rather than
overwriting it. `documentation/runbooks/workspace-CLAUDE.md` remains the canonical source — a copy
under `workspace/` would be a third copy of a file whose problem is that copies drift.
6. **A rule file created mid-session is NOT live in that session.** A directory whose instructions
were already seeded is not re-scanned. Proven the hard way: after writing four rule files, reads
that should have matched them produced no hook line at all. Verify new rules from a fresh session
(`claude -p`), never from the frontmatter.
**S-35 — INSTRUCTION FILES ARE A SHORT CORE PLUS PATH-SCOPED RULES (2026-08-06, R-229).** **S-35 — INSTRUCTION FILES ARE A SHORT CORE PLUS PATH-SCOPED RULES (2026-08-06, R-229).**
Decided while rightsizing the four `CLAUDE.md` files. The mechanisms were verified before being Decided while rightsizing the four `CLAUDE.md` files. The mechanisms were verified before being
+154
View File
@@ -0,0 +1,154 @@
# 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 --fast`**6 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]
```
**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). One-time cost: adding a path invalidated restic's parent match,
so the first run re-reads 405 GiB — dedup keeps storage flat and later runs are incremental again.
## 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.
## 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.
@@ -0,0 +1,240 @@
# LEDGER — instruction trim, part 2 (`felhom.eu`, the memory index, the versioned workspace)
Companion to `LEDGER-instruction-trim-2026-08-06.md`, which this closes legs (a) and (c) of. Same
per-block format; destinations are `rule-file`, `html-comment`, `already-in:<path>`,
`deleted-derivable`, `deleted-stale`, `kept-in-core`.
**Baselines (verified against live Gitea, all clean, all `HEAD == origin/main`):** `felhom.eu`
`c21bcf84f709`, `felhom-controller` `7db42c5fec3b`, `felhom-agent` `062a7027abff`.
**Concurrency:** a second Claude Code session (`f192bf03`, started 10:05) was live in these repos
throughout, doing `qm`/`ssh demo-hp` console work. Managed rather than waited out: clean-tree and
`origin/main` re-checked immediately before every commit, explicit-path staging only, `pull --rebase`
before every push, and the memory store snapshotted before edit and re-diffed after. Per this repo's
own rule for parallel sessions, this run wrote `REPORT-instruction-trim-part2-2026-08-06.md` and did
**not** touch the shared `REPORT.md`.
---
## A. Mechanism verification — done before relying on any of it
| Claim | Verdict | Evidence |
|---|---|---|
| `.claude/rules/*.md` + `paths:` loads **only** on a matching file read | **confirmed, both directions** | hook log, two fresh sessions: reading `website/index.html``website.md path_glob_match`, **no** `hub.md`; reading `hub/internal/api/handler.go``hub.md path_glob_match`, **no** `website.md` |
| Creating `.claude/rules/` **mid-session** arms it | **DISPROVEN** | after writing the four rule files, in-session reads of `website/index.html` produced **no hook line at all**. A directory whose instructions were already seeded is not re-scanned — same class as the settings-watcher caveat. This is why the proof above needed `claude -p` |
| On-demand rules are accounted under `Messages` in `/context` | **still inference, not measured** | two `/context all` readings: Memory-files stayed at exactly 11.5k across three rule loads while Messages grew 8 → 105.7k. That shows rules are **not** counted as memory files; it does not show where they *are*. Recorded as reasoned, per spec §3 |
The second row is the load-bearing one and it is a **negative result about the instrument**: a rule
file can be correct, pass every gate, and reach the model never, purely because of when it was
created. Nothing in the gate or the frontmatter would have shown that.
---
## B. `felhom.eu/CLAUDE.md` — 227 → 115 effective lines
| Heading / first words | Class | Destination | Reason |
|---|---|---|---|
| `## The Felhom system` three-component model (7 ln) | duplicated | `already-in:CLAUDE.md` (workspace root) | the root always loads and survives `/compact`; restating it here is a second place to edit |
| `## Hub — architecture` ingest paragraph (11 ln) | path-bound | `rule-file:hub.md` | only matters when `hub/` is being edited; the frozen-contract warning went with it |
| `## Hub stack — the two constraints` (7 ln) | path-bound | `rule-file:hub.md` | same trigger |
| `## Build & deploy — Hub (GitOps)` (25 ln) | path-bound | `rule-file:hub.md` | the clean-tree gate inside it was `already-in:CLAUDE.md` (workspace root) and dropped here |
| `## Key patterns` (8 ln) | path-bound | `rule-file:hub.md` | status logic, `parseSQLiteTime`, auto-refresh — all hub internals |
| seam-wiring / template-gates rule (4 ln) | path-bound | `rule-file:hub.md` | it is about `html/template` gates, which exist only in `hub/` |
| health-check block-I/O rule (5 ln) | path-bound | `rule-file:hub.md` | deliberately **duplicated** into three repos' rule files — a pointer to a sibling repo's rule does not load |
| logging levels/phrasing (3 ln) | path-bound | `rule-file:hub.md` | applies when writing Go |
| website deploy + BOM + emoji + cache-busting (10 ln) | path-bound | `rule-file:website.md` | |
| ArgoCD / deliberate sync / `secretKeyRef` (12 ln) | path-bound | `rule-file:manifests.md` | |
| doc routing, one-home-per-fact, N.5 coupling (part of the checklist) | path-bound | `rule-file:docs.md` | |
| `## Project overview` per-directory list (14 ln) | derivable | compressed to 6 lines + retrieval map | the tree says what the directories are; what it cannot say is which one is *authoritative for the whole system* — that was kept |
| artifact taxonomy, `TASK.md`/`RUNBOOK-*.md` (10 ln) | duplicated | `already-in:CLAUDE.md` (workspace root) | the root defines the taxonomy for all four repos |
| browser-automation / `claude-in-chrome` block (6 ln) | duplicated | `already-in:CLAUDE.md` (workspace root) | root's "Live validation — no browser here" |
| `## Environment & access` DooPlex + host pointers (22 ln) | duplicated | `already-in:CLAUDE.md` (workspace root) + `nodes.md` | replaced by two rows in the retrieval map |
| Legacy-Windows HTML comment (5 ln) | archival | `deleted-derivable` | the root carries the fuller version in its own comment |
| `## Build & deploy — Website/Manifests` R-110 installer block (17 ln) | **fence** | **`kept-in-core`, compressed to 6** | see §C |
| `## Gates — ONE entry point` (25 ln) | entry point | `kept-in-core`, compressed | §6 names it as core; the R-29 narrative stayed an HTML comment (free) |
| end-of-session checklist (18 ln) | **register discipline** | **`kept-in-core`** | see §C |
**Kept deliberately:** the gate entry point, the end-of-session checklist, register discipline, the
R-110 fence, the `git add -A` prohibition, the parallel-session `REPORT.md` rule, the `go test -run`
false-green rule, and "ask first — don't guess".
---
## C. Two placements that deviate from the spec's §6 sketch — and why
The spec fixed the rule-file names and globs and left line-by-line placement to the implementer.
Two blocks were **kept in the core** that §6's sketch would have relocated. Both would otherwise have
rebuilt the exact failure class they exist to prevent.
| Block | §6 would put it in | Kept in core because |
|---|---|---|
| **Register discipline**`OPEN-ITEMS.md` first, `STATUS.md` is a view, nothing exists only in a report | `docs.md` (`paths: documentation/**`) | it applies at the end of **every** session that ships, breaks or decides something. Behind that glob it is invisible in precisely the code-shipping sessions where findings get lost — which is how R-153/154/155 and R-156/157 were minted and lost. A rule that loads only when you are already writing documentation cannot remind you to write documentation |
| **The R-110 installer fence** — pushing `felhom-host-install.sh` publishes nothing; publishing is a tag move | `website.md` (`paths: website/**`) | its trigger is editing `scripts/felhom-host-install.sh`, which **none of the four fixed globs matches**. Behind `website/**` it would never load for the one artifact that runs **as root on a virgin box**. Compressed to its actionable clauses rather than relocated |
`docs.md` states the first of these explicitly, so a reader who expects the rule there is told where
it went and why.
---
## D. The memory store — reconciled, nothing deleted
**Backup first (ruling 2):** `/mnt/5_hdd/felhom.eu/backups/claude-memory-20260806-103418`, 158 files,
taken before any edit.
| Measure | Before | After |
|---|---|---|
| `.md` files total | 158 | **158 — equal, zero deletions** |
| top-level topic files | 157 | 117 |
| `archive/` | — | 40 |
| indexed | 113 | **117 (all of them)** |
| orphaned | **44** | **0** |
| dangling references | 0 | 0 |
| `MEMORY.md` | 145 ln / 17,688 B | **150 ln / 17,977 B** |
**The discriminator was the store's own schema, not taste.** All 4 `reference`-type orphans are
durable pointers and were **indexed**; all 39 `project`-type orphans plus 1 untyped are dated episode
records — "SHIPPED", "spike GO", "FIXED in vX.Y.Z" — whose conclusions already live in CHANGELOGs and
`documentation/audits/`, and were **archived**. Each of the four also justifies indexing on content,
not merely type; `hetzner-storagebox-api.md` carries an **open security constraint** (the
project-scoped Hetzner token can delete ep0 — move customer boxes to a dedicated project before the
hub holds a write token).
**Detail moved, not dropped.** Seven index lines were shortened; for each, the removed detail was
grep-verified present in the topic file first. One check came back **negative**
(`controller-internals.md` did not contain the module path) — the line was trimmed anyway, caught on
review, and the detail written into `controller-internals.md`. Recorded because the verification step
is what caught it.
**Headroom:** 50 lines and 7,622 bytes (29%) against the 200-line / 25 KB limits.
---
## E. Staleness diagnosis (Part 2.4) — **diagnosed, deliberately not fixed**
A ruling is owed before anyone edits Claude's own notes at scale. Filed as **R-230(a)**.
| Class | Count | Sharpest instance |
|---|---|---|
| component version literals | **21 lines** | `ctrl 0.162.0`, `hub 0.64/0.65`, `installer 1.15.0` — the exact literal class the gate now bans in every hand-written instruction file |
| bare host addresses | **5 lines** | `100.70.170.35`, `167.233.158.164`, `10.77.0.1``nodes.md` is supposed to be the single home |
| expired temporal statements | 3 | **`"demo boxes REMOTE till ~08-02"`** |
| undated open items | 2 | `RENOVATE_GITHUB_COM_TOKEN ROTATION DUE` |
**The finding that matters is structural, not the counts.** `felhom-agent/CLAUDE.md` had its expired
`TEMPORARY — felhom-pve is at a remote site (until ~2026-08-02)` block deleted in part 1, and the gate
now fails any such block. The *same claim* is still live in `MEMORY.md` — which loads in every
session, is larger than the file the rule was written for, and is the one file whose content the gate
does not police. The contradiction was moved, not resolved: the hand-written half is clean and the
auto-written half still asserts the retired fact.
---
## F. The gate — check 6, and why its three outcomes differ
| Condition | Outcome | Reason |
|---|---|---|
| index over 200 lines or 25 KB | **FAIL** | auto-memory drops content past the limit with **no error** — silent truncation, no observable |
| top-level topic file unreferenced | **WARN** | the store is outside git and changes between sessions; a failure blocks pushes for something no commit can fix |
| store absent | **PASS, printing the reason** | machine-local by design; a clone elsewhere legitimately has none |
The third is a deliberate exception to check 1's *"a missing input is a FAILURE, never a skip"*. The
printed reason is the only thing that made it safe to grant, and the test asserts on **that text**,
not on `rc == 0` — a pass with no reason is indistinguishable from a gate that stopped running.
Two load-bearing negatives: a file reachable only via `[[wikilink]]` is **indexed, not orphaned**
(flagging it would push someone to add a duplicate row), and `archive/` contents are never orphans.
**Suite 20 → 39 assertions, 0 failures.** Red-proof against the **real** store, not a fixture:
ceiling 200 → 100, gate went red naming the real file and count
(`.claude-memory/MEMORY.md: 150 lines, ceiling 100`), restored, green again.
---
## G. The versioned workspace
**Canonical path decision (§4.3):** `documentation/runbooks/workspace-CLAUDE.md` **stays** the
source. `workspace/` carries only the hook fragment. A `workspace/CLAUDE.md` would be a *third* copy
of a file whose whole problem is that copies drift, and check 5 already enforces byte-identity.
**Symlink recommendation (§4.3): recommended, not done.** Making the live root file a symlink to the
versioned copy removes the divergence class outright, and Claude Code follows symlinks (the four
skills already are). Not done here because it changes what check 5 asserts — identity of two files
becomes correctness of one link — which deserves its own reviewable change. **R-230(b)**.
**Proofs.** Merge preserved all 7 top-level `settings.json` keys byte-identically (`sha256` of the
file minus `.hooks` unchanged across the write). Run 2 wrote nothing — *idempotent* means "changed
nothing the second time", not "ran twice without erroring", and only the second is evidence.
`install_workspace.py` **refuses** an unparseable `settings.json` rather than overwriting it: a
malformed settings file silently disables every setting in it, and overwriting would destroy whatever
was mid-fix.
---
## H. Backup coverage (§4.4) — and what it does *not* buy
Added to `dooplex-backup.service`'s User Data component via `CLAUDE_MEMORY_DIR`. **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]
```
**Three caveats, each of which makes this weaker than "backed up" sounds:**
1. The destination `/mnt/5_hdd/backup` is on the **same physical disk** (`sda1`) as the store. This
protects against deletion, **not** against disk loss.
2. The DooPlex backup set has **no off-site leg**. `sync-hetzner-backups.sh` is a *jarrs.eu* job that
pulls **from** Hetzner **to** DooPlex — the opposite direction.
3. `/opt/backup/scripts/` is itself **unversioned host state** (**R-231**) — the change exists only on
the box, transcribed into `workspace/README.md` so it is at least recorded.
One-time cost, so it is not later read as a fault: adding a path invalidates restic's parent-snapshot
match, so the first run logs `no parent snapshot found` and re-reads all 405 GiB. Dedup keeps storage
flat; later runs are incremental again.
---
## I. Secrets scan of the memory store (§9.3)
Pattern set: `token|password|passwd|secret|api[_-]?key|private[_-]?key|BEGIN .*PRIVATE|bearer|credential`,
case-insensitive, over all 158 files.
| Measure | Count |
|---|---|
| lines matching the keyword set | 535 across 104 files |
| matching a `key: value` shape with a ≥16-char value | **1** — and it is prose (`scoped token: provision-into-pool, …`) |
| private-key blocks | **0** |
**No credential values.** Nothing from the store was committed regardless (ruling 2); the scan is the
evidence the later decision needs, not a licence.
---
## J. context7 (Part 5) — nothing written, by the rule
The operator's `/mcp` and `/plugin` output: `plugin:context7:context7` is **`✘ failed`** (ENOENT on
`npx -y @upstash/context7-mcp`), and `/mcp` lists only the six Google auth stubs as available. Per
§5, **write nothing** — instructions telling Claude to use a tool that does not exist are worse than
none. Not written: the `felhom-app-catalog` SKILL.md line and the infrastructure rule-file line.
**Why the Go code is not the use case either, recorded as §5 asks:** one direct dependency in
`felhom-agent` (`golang.org/x/crypto`), and the hub and controller are stdlib-only by standing rule.
There is no third-party API surface here that a docs-retrieval tool would help with; the catalog's
upstream images are the only plausible consumer, and there `docker inspect` still decides.
---
## K. Observations — noticed, not acted on
1. **`target-selection.md` t740 error** — still present, carried from part 1 §L.1.
2. **`felhom-agent/CLAUDE.md` at 173 effective lines** — passing with the least headroom; R-229(b).
3. **The `/context` Messages-accounting inference** — see §A row 3. Not built on.
4. **`rules_report.py` first run: 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` is proof of the
distortion: it fired at 08:11 the same day, before installation, and reads as silent.
5. **The other session's read of `OPEN-ITEMS.md` fired `docs.md`** — independent third-party
confirmation the new rules are live, from a session that knew nothing about them.
6. **`MEMORY.md`'s header still says "felhom-controller Project Memory"** though it indexes all four
repos plus the homelab. Cosmetic; left for the R-230(a) ruling rather than edited.
+3 -1
View File
@@ -113,7 +113,9 @@ the fault was real. Full observables: `tests/campaign11-evidence-2026-08-05/jour
| ID | What | State | | ID | What | State |
|---|---|---| |---|---|---|
| **R-229** | **The instruction-file rightsizing landed for `felhom-controller` and the workspace root; three pieces were deliberately deferred.** Done 2026-08-06: controller split into a 92-effective-line core plus four `paths:`-scoped `.claude/rules/*.md`; workspace root 208→142 effective lines with its versioned copy kept byte-identical; surgical corrections to `felhom-agent` and `felhom.eu` (expired TEMPORARY block, every version literal, the Legacy-Windows copies, the duplicated health-check rule); five contradictions resolved — including a drill-VM claim **measured live** (`qm list` on demo-hp shows VM 300 `drill-r50`; `felhom-agent` was right, `felhom-controller` was wrong); new shared `felhom.eu/scripts/instructions_gate.py` registered in `controller_gates.py` and `agent_gates.py`, 20 fixture tests + red-proof. **Deferred, and why:** (a) **`felhom.eu/CLAUDE.md` is at 227 effective lines, over the 200 ceiling** — reducing it needs the core+rules restructure that the task spec explicitly forbade for reviewability, so it is not gated today; when it lands, also register `instructions_gate` in `scripts/repo_gates.py`. (b) **`felhom-agent/CLAUDE.md` at 173 effective lines** passes with the least headroom; its release section is the next candidate for the `felhom-build-deploy` skill. (c) **The auto-memory decision**`MEMORY.md` was measured only (146 lines / 17.3 KB, **within** both the 200-line and 25 KB limits, nothing truncated), but the index references 113 memory files while **157 exist on disk**, so 44 are unindexed. (d) **The spec-as-failing-test pilot**, approved in principle and not started. Full accounting: `audits/LEDGER-instruction-trim-2026-08-06.md` | **READY** — owner Viktor | | **R-229** | **The instruction-file rightsizing landed for `felhom-controller` and the workspace root; three pieces were deliberately deferred.** Done 2026-08-06: controller split into a 92-effective-line core plus four `paths:`-scoped `.claude/rules/*.md`; workspace root 208→142 effective lines with its versioned copy kept byte-identical; surgical corrections to `felhom-agent` and `felhom.eu` (expired TEMPORARY block, every version literal, the Legacy-Windows copies, the duplicated health-check rule); five contradictions resolved — including a drill-VM claim **measured live** (`qm list` on demo-hp shows VM 300 `drill-r50`; `felhom-agent` was right, `felhom-controller` was wrong); new shared `felhom.eu/scripts/instructions_gate.py` registered in `controller_gates.py` and `agent_gates.py`, 20 fixture tests + red-proof. **Leg (a) CLOSED 2026-08-06 (part 2):** `felhom.eu/CLAUDE.md` **227 → 115 effective lines**, split into a core plus `.claude/rules/{hub,website,manifests,docs}.md`; `instructions_gate` **registered in `scripts/repo_gates.py`** (six gates, all OK) in the required order — trim first, register second, because a registered-but-failing gate refuses every push. Scoping proven from the `InstructionsLoaded` hook log in two fresh sessions, not from frontmatter. **Still deferred:** (b) **`felhom-agent/CLAUDE.md` at 173 effective lines** passes with the least headroom; its release section is the next candidate for the `felhom-build-deploy` skill. (c) **CLOSED 2026-08-06 (part 2)** — all 44 orphans resolved with **zero deletions** (file count 158 before and after): 4 durable `reference`-type files indexed, 40 dated episode records moved to `.claude-memory/archive/`. `MEMORY.md` 145 → **150 lines / 17,977 bytes**, and `instructions_gate` check 6 now watches it (over-limit FAILS, orphan WARNS, absent store PASSES *printing its reason*). (d) **The spec-as-failing-test pilot** — moved to R-230. Full accounting: `audits/LEDGER-instruction-trim-2026-08-06.md` + `audits/LEDGER-instruction-trim-part2-2026-08-06.md` | **READY** — owner Viktor |
| **R-230** | **Three instruction/memory follow-ups deliberately left by the part-2 session (2026-08-06), each needing a decision rather than an implementation.** (a) **A ruling is owed on auto-written staleness.** The hand-written `CLAUDE.md` files are now clean of version literals and expired blocks — the gate enforces it — but `MEMORY.md`, which Claude writes and which is the LARGER half of what loads (8.4k tokens vs the root file's 6.6k), carries **21 lines with component version literals**, **5 with bare host addresses**, and an entry still reading *"demo boxes REMOTE till ~08-02"* — the same expired-TEMPORARY class the gate was built to kill, now surviving in the one file the gate's content rules do not cover. Diagnosed and **deliberately not edited**: nobody has ruled on how to correct Claude's own notes at scale. (b) **The symlink decision** for the workspace-root `CLAUDE.md`. Now that `install_workspace.py` exists, making the live file a symlink to `documentation/runbooks/workspace-CLAUDE.md` would remove the divergence class outright. **Recommended, not done** — it changes what check 5 is checking (identity of two files becomes correctness of one link), so it needs its own change. (c) **The spec-as-failing-test pilot**, approved in principle and not started (was R-229(d)). | **READY** — owner Viktor |
| **R-231** | **`/opt/backup/scripts/` on DooPlex is unversioned host state** — found 2026-08-06 while adding the auto-memory store to the backup set. No repository tracks the scripts that protect the recovery chain, so the edit made that day (`CLAUDE_MEMORY_DIR` in `backup-config.sh`, multi-path restic call in `backup-data.sh`) exists only on the box. This is the same class the part-2 session was closing, found inside the fix for it; the change is transcribed in `felhom.eu/workspace/README.md` so it is at least *recorded*. **Two related facts, both understating current safety:** the backup destination (`/mnt/5_hdd/backup`) is on the **same physical disk** as the workspace it protects, and the DooPlex backup set has **no off-site leg** (`sync-hetzner-backups.sh` is jarrs.eu and pulls *from* Hetzner *to* DooPlex). Bringing a root-owned production backup script under version control, and deciding what installs it, is its own scoped change. | **READY** — owner Viktor |
**Recorded against existing rows by Phase 2:** **Recorded against existing rows by Phase 2:**