docs: CLAUDE.md becomes a core plus path-scoped rules (R-229)
gates / gates (push) Successful in 12s
gates / gates (push) Successful in 12s
215 lines -> 110 (92 effective; block-level HTML comments are stripped before injection and never reach the model, verified empirically on Claude Code 2.1.222 with a control and a treatment run). Four new .claude/rules/*.md, each with a paths: glob list so it loads only when a matching file is read: gates, ui-hungarian, backup-paths, agent-coupling. The ## Layout tree was deleted as derivable; REUSE.md already owns the per-package seams its annotations stood in for. The host/access table was deleted in favour of a pointer to documentation/operations/nodes.md -- it carried three defects at once: demo-felhom given as the LAN fallback address as if it were the route, a pinned "agent 0.93.0" against the project's own no-versions-in-docs rule, and the claim that no drill VM was provisioned on demo-hp. Measured live: qm list shows VM 300 drill-r50. felhom-agent/CLAUDE.md was right; this file was wrong. Kept verbatim: the seven session-critical invariants, the F9 live-validation fence, the end-of-session checklist. controller_gates.py registers the shared instructions gate (felhom.eu/scripts/, never copied here; an absent sibling clone FAILS). Docs only -- no Go, no version bump, no image, no deploy. Ledger: felhom.eu/documentation/audits/LEDGER-instruction-trim-2026-08-06.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJc8sAGRWmavP3rMtdpkr2
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
---
|
||||
paths: ["controller/internal/agentapi/**"]
|
||||
---
|
||||
|
||||
# Coupling to the host agent — felhom-controller
|
||||
|
||||
`internal/agentapi` is **the disk seam**: the pinned-TLS client to the host agent's per-guest local
|
||||
API. The controller holds no Proxmox credentials; everything disk/host/Proxmox goes through here.
|
||||
|
||||
## Declaring a coupled feature
|
||||
|
||||
Controller behaviour that depends on a specific agent version needs **all three**, or it ships broken
|
||||
on an older box:
|
||||
|
||||
1. a `featureProbes` table row in `internal/agentapi/features.go`
|
||||
2. a `Supports` gate call **at the feature's entry point** — not somewhere on the path to it
|
||||
3. `MinAgent: X.Y.Z` in the CHANGELOG entry header
|
||||
|
||||
Rules: `felhom.eu/documentation/runbooks/publish-train-rules.md`.
|
||||
|
||||
## Never push a controller past the agent it depends on
|
||||
|
||||
The R-216 guard compared the box's agent against the **golden's** MinAgent while serving a **floor**
|
||||
that could point elsewhere. Raise a floor above the vouched golden — which the day-0 runbook
|
||||
recommends and a per-customer override makes trivial — and the guard checks a version it is not
|
||||
serving. A box then landed on a controller needing a newer agent, and its customer was told a correct
|
||||
recovery code was wrong.
|
||||
|
||||
**A floor above the vouched golden is HELD, with its own reason** (hub v0.97.0).
|
||||
|
||||
## Distinguish "could not reach" from "wrong answer"
|
||||
|
||||
A failed bundle FETCH must not be reported to a customer as a bad recovery code. Classify by **value**
|
||||
(`ErrBundleFetch` → HTTP 502), never by error string — a string is not something a caller can branch
|
||||
on. Unknown class → neutral message, never the typing message.
|
||||
|
||||
<!--
|
||||
R-224, measured live 2026-08-05 (CAMPAIGN-11 F3/F4) with a correct current code: 0.0556 s with the
|
||||
hub firewalled off and 0.0299 s with the agent stopped, against ~1.0 s for a genuine unseal — the
|
||||
machine accused the customer of something it had not attempted. A green test named this exact
|
||||
consequence since v0.125.0 and did not prevent it, because it asserted this package's error STRING
|
||||
one layer below where the merge happened. Fixed agent v0.126.0 + controller v0.202.0.
|
||||
-->
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
paths: ["controller/internal/backup/**", "controller/internal/appbackup/**", "controller/internal/recovery/**", "controller/internal/appexport/**", "controller/internal/quiesce/**"]
|
||||
---
|
||||
|
||||
# Backup, recovery units and export — felhom-controller
|
||||
|
||||
## Assert the consequence across the whole run, not the mechanism inside one function
|
||||
|
||||
The R-181 recovery-unit refusal claimed *"the previous unit is untouched and NOTHING was deleted"*.
|
||||
*Nothing deleted* held; **untouched was measured false** — the floor was checked ONLY in
|
||||
`captureAllRecoveryUnits`, while the two dump legs wrote the bulk into the same tree first and
|
||||
unguarded, so a 182,272 B tar became 2,147,666,432 B under a manifest that had not moved. A full
|
||||
green suite plus three of its own red-proofs missed it, because every one asserted the mechanism
|
||||
inside `captureAllRecoveryUnits`.
|
||||
|
||||
**The test that catches this class: fingerprint the tree before and after the whole backup run, and
|
||||
compare.** Full doctrine and the other eight instances: the `felhom-testing` skill.
|
||||
|
||||
## Presence is not success
|
||||
|
||||
A timestamp recording an **attempt** must never be read as evidence of a **result**. Where a status
|
||||
field travels alongside a timestamp, the verdict consults both — or the timestamp records only
|
||||
successes. Ask of any timestamp: *what exactly must have happened for this to be set?* If the answer
|
||||
is "we tried", it cannot answer "did it work".
|
||||
|
||||
**Corollary:** when a verdict changes which field it counts from, the alarm text has to change with
|
||||
it. `last run 8h ago` while alarming on a six-day-old success turns a true alarm into one the
|
||||
operator dismisses.
|
||||
|
||||
<!--
|
||||
Two instances. F-CRIT-2: a phantom snapshot's ctime set tier freshness — an aborted 1-byte upload
|
||||
made the tier look backed up. R-100: LastRun is written on failure, so a nightly-failing offsite
|
||||
tier kept the staleness clock fresh forever.
|
||||
-->
|
||||
|
||||
## Storage keys and paths
|
||||
|
||||
- Never guess a persisted key — it is `offbox`, not `offbox_target` (R-7b).
|
||||
- `.fab` export/import uses strict segment validation; bundles from controller ≤0.124.0 are hollow.
|
||||
- Recovery-unit restore and tier-2 copies share `appbackup`'s path primitives — change them there,
|
||||
once, not per caller.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
paths: ["controller/**/*.go", "controller/**/*.html", "controller/**/*.css", "controller/scripts/**"]
|
||||
---
|
||||
|
||||
# Gates and logging — felhom-controller
|
||||
|
||||
## The ONE entry point
|
||||
|
||||
**Run `python3 controller/scripts/controller_gates.py` (from `controller/`) after ANY change in this
|
||||
repo.** It runs all seven local gates — `template_id_gate`, `emoji_gate`, `native_confirm_gate`,
|
||||
`offbox_rename_gate`, `app_row_dedup_gate`, `mojibake_gate`, `docker_run_volume_path_gate` — plus
|
||||
`reuse_refs_check` and `instructions_gate` on the repo root, streaming each gate's own output and
|
||||
exiting non-zero if any fails.
|
||||
|
||||
- `--fast` selects the gates that touch no network and no container runtime; today that is all of them.
|
||||
- **A missing gate script is a FAILURE, never a skip.**
|
||||
- **The shared `reuse_refs_check.py` and `instructions_gate.py` live in `felhom.eu/scripts/` and are
|
||||
never copied here** — a copy would recreate the drift they detect; an absent sibling clone FAILS.
|
||||
- **The pre-push hook** (`.githooks/pre-push`) runs it with `--fast` and refuses a failing push. It is
|
||||
per-clone — switch it on once with `git config core.hooksPath .githooks`, and a manual run WARNS
|
||||
when this clone is unarmed. `git push --no-verify` bypasses it deliberately; **say so in the session
|
||||
report when you use it.** Both facts are why CI is still owed (`OPEN-ITEMS.md` R-168).
|
||||
|
||||
<!--
|
||||
WHY A RUNNER AND NOT SEVEN INVOCATIONS (2026-08-02, R-29) — rationale, not a directive.
|
||||
A census of all thirteen gates across the four repos found that every check a CLAUDE.md named was
|
||||
passing, and two of the four nobody is told to run were failing. This repo's CLAUDE.md used to name
|
||||
two of the seven; the other five were reachable only through a line in REUSE.md, and
|
||||
docker_run_volume_path_gate.py was RED. The single-entry-point shape is the only one that
|
||||
demonstrably gets run. app-catalog-felhom.eu/scripts/catalog_gates.py is the canonical version of
|
||||
the runner (R-161); repo_gates.py copies it. site_gates.py is a *gate*, not a runner — do not model
|
||||
new work on it.
|
||||
-->
|
||||
|
||||
## Logging
|
||||
|
||||
New leveled lines use `internal/logx` — DEBUG always reaches the debug ring; stdout respects
|
||||
`logging.level`. English, keys-never-values, durations on outcomes. Full rules:
|
||||
`felhom.eu/documentation/runbooks/logging-conventions.md`.
|
||||
|
||||
## Health checks issue no block I/O
|
||||
|
||||
A probe that touches a wedged device enters uninterruptible sleep, survives `SIGKILL`, and cannot be
|
||||
recovered until the device returns or the host reboots — so `systemctl restart` hangs too. A timeout
|
||||
protects the caller's control flow and nothing else: the blocked thread remains. Liveness is decided
|
||||
from `/proc` and kernel state, never by reading or writing the filesystem.
|
||||
|
||||
<!--
|
||||
Measured, R-117 spike §6.3 (felhom.eu/documentation/audits/SPIKE-r117-bind-liveness-2026-07-30.md):
|
||||
a probe stayed in D state 3m50s after kill -9; a buffered write with no fsync blocked too (O_CREAT
|
||||
needs journal access); and statfs/getdents returned HEALTHY on a namespace that EIOs every byte —
|
||||
fast, and wrong.
|
||||
-->
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
paths: ["controller/internal/web/templates/**", "controller/internal/web/**/*.go", "**/*.css", "**/*.html"]
|
||||
---
|
||||
|
||||
# UI and Hungarian copy — felhom-controller
|
||||
|
||||
- **All UI text is Hungarian**, Budapest timezone.
|
||||
- Design tokens, badge/colour rules, the 2px/no-shadow/no-emoji/BOM hard rules and the mechanical
|
||||
gate to run after each surface: **use the `felhom-ui-design` skill.**
|
||||
- Template methods need **value receivers** — pointer receivers compile, pass `go vet`, pass the
|
||||
suite, and then 500 at render time.
|
||||
|
||||
## Grep fetched pages with ASCII-only substrings
|
||||
|
||||
Accented Hungarian patterns get mangled through the `ssh → pct exec → bash -c` chain and return a
|
||||
false `0` — which reads exactly like the banner or string being gone. Use `kezel`, `Utols`,
|
||||
`Biztons`. **Never let an accented pattern gate a conclusion.**
|
||||
|
||||
<!--
|
||||
From the 2026-07-20 remediation: an accented grep nearly produced a wrong "banner cleared" claim.
|
||||
This is the "an absent line is not evidence" rule aimed at a UTF-8 transport, not at a log.
|
||||
-->
|
||||
|
||||
## Credentials containing `!` or `'` break in heredoc-built helper scripts
|
||||
|
||||
History expansion eats `!!`. Use the proven inline `-d "password=$PW"` form for authed curl, and
|
||||
delete any credential-bearing helper from `/tmp` (host AND guest) when done.
|
||||
Reference in New Issue
Block a user