docs+gate: instruction files cannot silently regrow (R-229)
gates / gates (push) Successful in 7s

New shared scripts/instructions_gate.py, registered in controller_gates.py and
agent_gates.py, never copied into a sibling repo (the reuse_refs_check.py
precedent). 20 fixture tests, all asserting the effect: exit code AND that the
message names the file and the reason.

It is a consistency gate, not a budget gate, and the failure message says so. A
/context reading measured the instruction files at 15k tokens against 869k free in
a 1M window -- space is not the constraint, and a future reader must not re-derive
the wrong reason. The 200-line ceiling is adherence guidance; a file nobody can
hold in their head is where contradictions hide, and five were found here.

Checks run against effective text (HTML comments stripped, because they are
stripped before injection): the line ceiling; every .claude/rules/*.md declares
paths: or an explicit unconditional: true; no component version literal; no
TEMPORARY block carrying a past date; and the workspace-root CLAUDE.md is
byte-identical to its versioned copy -- the live file sits outside any git repo,
so that copy is its only version-controlled record.

Two traps recorded so they are not reintroduced: a bare \d+\.\d+\.\d+ matches the
first three octets of every IPv4 (the gate excludes dotted quads, or it fails on
192.168.0.180 in the agent's own file); and unconditional: true is NOT a Claude
Code feature but this project's own marker.

Workspace-root CLAUDE.md 208 -> 182 lines (142 effective), copy kept identical.
The nine-instance invariant table moved into the felhom-testing skill, which
triggers when writing or reviewing a test; all three directive bullets stayed in
the core. felhom.eu/CLAUDE.md got surgical corrections only and is knowingly still
over the ceiling at 227 effective lines -- closing it needs the restructure R-229
defers, said plainly rather than quietly absorbed.

CONTEXT.md gains standing ruling S-35. OPEN-ITEMS.md gains R-229.

Docs only -- no Go, no version bump, nothing built or deployed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJc8sAGRWmavP3rMtdpkr2
This commit is contained in:
2026-08-06 09:38:52 +02:00
parent d30c2a51ed
commit c21bcf84f7
10 changed files with 1125 additions and 333 deletions
+62 -51
View File
@@ -16,10 +16,8 @@ This repo contains:
- **Architecture docs** (`documentation/`) — the **authoritative design home for the whole Felhom
system**: `architecture/01..05-*.md`, `proxmox-platform.md`, `tests/phase*-findings.md`,
runbooks, audits. Read these before designing.
- **Skills** (`skills/`) — the versioned source of the Claude Code skills
(`felhom-build-deploy`, `felhom-ui-design`, `felhom-testing`, `felhom-app-catalog`);
install/update with `python3 scripts/install_skills.py` (symlink into `~/.claude/skills/` on
POSIX, junction on Windows — either way repo edits are live immediately).
- **Skills** (`skills/`) — the versioned source of the Claude Code skills; install/update with
`python3 scripts/install_skills.py` (symlink — repo edits are live immediately).
See `README.md` for full architecture/DNS/email/SEO docs. See `TASK.md` for the current task (if any).
See `REUSE.md` before writing new code.
@@ -46,32 +44,40 @@ UI. Package map, helpers, seams, extension points: **`REUSE.md`** (e.g. new even
## Code quality rules
- Always double-check generated code for bugs, logic issues, syntax errors.
- Handle edge cases without overcomplicating.
- Add debug capabilities (logging, verbose output).
- If you need more input or troubleshooting output, **ask first — don't guess**.
- Testing doctrine (non-hollow tests, red-proofs, seams): use the `felhom-testing` skill.
- **Seam-wiring rule — and it covers TEMPLATE GATES (fourth inert seam, hub v0.70.1):** a feature
is not shipped until its entry point is reachable. For UI, any conditional affordance
(`{{if .Flag}}` around a button/form/script) ships with a render test per branch of the gate
handler tests that POST directly prove nothing about reachability. The v0.70.0 ghost-delete was
fully implemented server-side and fully dead UI because the button sat inside the wrong gate.
- **A `go test -run` pattern that matches no test prints `ok` and exits 0.** Found 2026-08-02 while
red-proofing: `-run TestCustomerUnified` matched nothing in the target file and reported
`ok … 0.062s`, which was read as a passing red-proof. **A red-proof that uses `-run` must first
prove the filter matched something** (`-v` and look for `=== RUN`). This is the "an absent line is
not evidence" rule aimed at the one place a false green costs most — the proof itself. The same
class bit twice that day: a `| tail -5` inside a census query silently dropped rows and looked
exactly like a real finding. **An instrument that can drop results silently is not a measurement.**
- **Seam-wiring rule — it covers TEMPLATE GATES:** a feature is not shipped until its entry point is
reachable. For UI, any conditional affordance (`{{if .Flag}}` around a button/form/script) ships
with a render test **per branch of the gate** — handler tests that POST directly prove nothing
about reachability.
- **A `go test -run` pattern that matches no test prints `ok` and exits 0.** A red-proof that uses
`-run` must first prove the filter matched something (`-v`, look for `=== RUN`). Generally: **an
instrument that can drop results silently is not a measurement.**
- **A health check issues 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 the kernel's own state, never by reading
or writing the filesystem. Measured, R-117 spike §6.3
(`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 `EIO`s every byte — fast, and wrong.
or writing the filesystem.
- UI/design work (tokens, gates, copy rules): use the `felhom-ui-design` skill.
<!--
CODE-QUALITY RULE CITATIONS — history, not directives.
Seam-wiring / template gates: the fourth inert seam, hub v0.70.1. The v0.70.0 ghost-delete was fully
implemented server-side and fully dead UI because the button sat inside the wrong gate.
go test -run: found 2026-08-02 while red-proofing — `-run TestCustomerUnified` matched nothing in the
target file and reported `ok ... 0.062s`, which was read as a passing red-proof. This is the "an
absent line is not evidence" rule aimed at the one place a false green costs most: the proof itself.
The same class bit twice that day — a `| tail -5` inside a census query silently dropped rows and
looked exactly like a real finding.
Health check / block I/O: measured, R-117 spike §6.3
(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.
-->
- **Logging**: levels/English/no-secrets rules per `documentation/runbooks/logging-conventions.md`
(DEBUG = flow detail, INFO = state change + duration; logs are operator-tier English; keys never
values — the hub's bundle secret-gate blocks violating pulls fail-closed).
@@ -104,11 +110,13 @@ pushes; **you (Claude Code) implement**. A file being open in the editor is NOT
> **Never write secrets** into any committed file — reference them as "stored out-of-band".
- Update `REUSE.md` if you added/changed/deprecated a shared helper or pattern (same commit).
- **Never `git add -A` in this repo** — parallel sessions share the clone and it sweeps foreign
WIP (the v0.47.0 `146d165` incident: a red-proof-mutated guard got swept to `main`). Stage
explicit paths only, `git pull --rebase` before every push, and do not run two writing
- **Never `git add -A` in this repo** — parallel sessions share the clone and it sweeps foreign WIP.
Stage explicit paths only, `git pull --rebase` before every push, and do not run two writing
sessions on one clone (use `git worktree` if truly needed).
<!-- The sweep incident: v0.47.0, commit 146d165 — a red-proof-mutated guard got swept to main. -->
## End-of-session checklist
- **`CHANGELOG.md` + `REPORT.md`** per the rule above, in every repo touched.
@@ -128,13 +136,13 @@ pushes; **you (Claude Code) implement**. A file being open in the editor is NOT
`curl -s "https://gitea.dooplex.hu/api/v1/repos/admin/<repo>/actions/tasks?limit=3"` → match the
`head_sha` to your commit. An unchecked green is an assumption, not an observation.
## Tech stack (Hub)
## Hub stack — the two constraints
- **Language:** Go (stdlib `net/http` + `html/template`, no frameworks). **DB:** SQLite via
`modernc.org/sqlite` (pure Go). **Auth:** bcrypt + Bearer tokens + session cookies + CSRF.
- **Deploy:** Docker on k3s (`felhom-system` ns). **Storage:** Longhorn PVC at `/data/` (SQLite DB).
- **Config:** YAML via ConfigMap at `/etc/felhom-hub/hub.yaml`. Secrets via out-of-band
`secretKeyRef` (never inline stringDataREUSE.md §3).
The dependency list is `hub/go.mod`'s business and the deploy shape is `manifests/`. Only the two
rules that the code cannot tell you belong here:
- **No web frameworks.** Go stdlib `net/http` + `html/template`, and it stays that way.
- **Secrets via out-of-band `secretKeyRef` never inline `stringData`** (REUSE.md §3).
## Environment & access
@@ -142,23 +150,22 @@ Claude Code runs **on DooPlex (192.168.0.180, Debian 13, user `kisfenyo`)** —
Repos in `/mnt/5_hdd/felhom.eu/git/`, build dirs in `/mnt/5_hdd/felhom.eu/build/`. `kubectl` and the
image build/push are local commands; felhom-pve is one SSH hop.
| Host | Access | Role | Blast radius |
|------|--------|------|--------------|
| **DooPlex (this host)** | local — `/mnt/5_hdd/felhom.eu/{git,build}/` | Build + push images, `sudo kubectl` | **Tier 2 — precious.** It *is* the recovery chain (hub, Gitea, registry, PBS, k3s+Longhorn). **Never a drill target** |
| Demo Proxmox host (N100) | `ssh felhom-pve` — via Tailscale `100.70.170.35` (location-independent); `felhom-pve-lan` = LAN `192.168.0.162` fallback | pveum/pct + live Proxmox validation | **Tier 0 — disposable** |
| Demo Proxmox host (HP t740) | `ssh demo-hp` — via Tailscale `100.76.96.79`; `demo-hp-lan` = LAN `192.168.0.87` (ProxyJump `felhom-pve`). **No baked SSH key** — G1 break-glass password vaulted in the hub | **The designated drill + build VM host** (operator ruling 2026-07-25) | **Tier 0 — disposable. Reach here first** |
**Host addresses, routes, node names, break-glass and what is provisioned on each:**
**`documentation/operations/nodes.md`** — the single home. Do not restate them here; re-check an
address rather than trusting one written down. Tailscale topology, the accept-dns rule, the
accept-routes spike result and rollback: `documentation/operations/tailscale.md`.
**Which box do I break?****`documentation/runbooks/target-selection.md`** — the tiers, and per
machine what is freely permitted / needs care / forbidden, each with its reason. Read it before picking
a machine for a drill, a destructive test or a throwaway VM.
a machine for a drill, a destructive test or a throwaway VM. **DooPlex is Tier 2 — precious**: it *is*
the recovery chain (hub, Gitea, registry, PBS, k3s+Longhorn), and never a drill target. Both demo
Proxmox hosts are Tier 0 — disposable; drill and build VMs belong on the t740.
The `felhom-pve` transport is Tailscale (the N100 is travel-portable) — topology, the accept-dns
rule, the accept-routes spike result, rollback, and the vacation-day checklist live in
`documentation/operations/tailscale.md`.
> **Legacy: Windows workstation.** Until 2026-07-19 CC ran on Windows 11 with repos in `E:\git\`,
> and every remote command needed `SSH=/c/Windows/System32/OpenSSH/ssh.exe` (Git Bash's ssh fails
> silently). Retained in case that environment is revived.
<!--
LEGACY: WINDOWS WORKSTATION — until 2026-07-19 CC ran on Windows 11 with repos in E:\git\, and every
remote command needed SSH=/c/Windows/System32/OpenSSH/ssh.exe (Git Bash's ssh fails silently). The
workspace-root CLAUDE.md carries the full version. Retained in case that environment is revived.
-->
## Build & deploy — Hub (GitOps via ArgoCD)
@@ -194,12 +201,16 @@ and runs every gate — `site_gates.py`, `hostinstall_gates.py`, `hub_confirm_ga
and exiting non-zero if any fails. `--fast` selects only 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.
**Why a runner and not five invocations** (2026-08-02, R-29): a census of all thirteen gates across
the four repos found that every check a `CLAUDE.md` names was passing, and two of the four nobody
is told to run were failing — one since 14 July. The single-entry-point shape is the only one that
demonstrably gets run here; `app-catalog-felhom.eu/scripts/catalog_gates.py` is the canonical
version of it (R-161) and `repo_gates.py` copies it. `site_gates.py` is a *gate*, not a runner —
do not model new work on it.
`site_gates.py` is a *gate*, not a runner — do not model new work on it;
`app-catalog-felhom.eu/scripts/catalog_gates.py` is the canonical runner (R-161).
<!--
WHY A RUNNER AND NOT FIVE INVOCATIONS (2026-08-02, R-29): a census of all thirteen gates across the
four repos found that every check a CLAUDE.md names was passing, and two of the four nobody is told
to run were failing — one since 14 July. The single-entry-point shape is the only one that
demonstrably gets run here.
-->
**The pre-push hook.** `.githooks/pre-push` runs `repo_gates.py --fast` and refuses the push if it
fails. It is **per-clone** and switched on once with `git config core.hooksPath .githooks` — a