From 9c10c5d600e7cb123528f841e5a3b99bd854a058 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 6 Aug 2026 11:01:13 +0200 Subject: [PATCH] docs: ledger, registers and standing ruling S-36 for the part-2 instruction/memory work (R-229) 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-.md per CLAUDE.md; shared REPORT.md untouched. --- CONTEXT.md | 30 +++ REPORT-instruction-trim-part2-2026-08-06.md | 154 +++++++++++ ...EDGER-instruction-trim-part2-2026-08-06.md | 240 ++++++++++++++++++ documentation/backlog/OPEN-ITEMS.md | 4 +- 4 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 REPORT-instruction-trim-part2-2026-08-06.md create mode 100644 documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md diff --git a/CONTEXT.md b/CONTEXT.md index afc8fa8..7b9c72a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -17,6 +17,36 @@ ## 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).** Decided while rightsizing the four `CLAUDE.md` files. The mechanisms were verified before being diff --git a/REPORT-instruction-trim-part2-2026-08-06.md b/REPORT-instruction-trim-part2-2026-08-06.md new file mode 100644 index 0000000..59a2e0b --- /dev/null +++ b/REPORT-instruction-trim-part2-2026-08-06.md @@ -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-.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. diff --git a/documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md b/documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md new file mode 100644 index 0000000..ca28c46 --- /dev/null +++ b/documentation/audits/LEDGER-instruction-trim-part2-2026-08-06.md @@ -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:`, +`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. diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index b655d53..1d3a4e8 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -113,7 +113,9 @@ the fault was real. Full observables: `tests/campaign11-evidence-2026-08-05/jour | 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:**