205e22babe
gates / gates (push) Failing after 12s
All 29 gate scripts across the four repos were read and DECOYED - the label constructed without the fact, the gate run, the verdict recorded. 16 were fooled. None of them were in this repo. A decoy that nobody would write proves nothing, so the attempts that turned out illegitimate were WITHDRAWN rather than counted. Both of this repo were withdrawn, and both are named in the audit. The gates here that could not be given a plausible decoy are listed BY NAME in felhom.eu/scripts/decoy_coverage_gate.py EXEMPT (R-426) as UNTESTED - not as sound. A gate nobody tried to fool is UNKNOWN, and calling it sound would be the same confident guess this sweep exists to find. Survey table: felhom.eu/documentation/audits/AUDIT-gate-decoys-2026-09-01.md
112 lines
6.7 KiB
Markdown
112 lines
6.7 KiB
Markdown
# CLAUDE.md — `felhom-agent`
|
|
|
|
> Stable orientation only — **current state lives in `CONTEXT.md` and the top of `CHANGELOG.md`**,
|
|
> never here. Cross-repo conventions (artifact taxonomy, access, clean-tree gate, secrets,
|
|
> CHANGELOG/REPORT): workspace-root `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`. Path-scoped detail:
|
|
> `.claude/rules/`.
|
|
|
|
## What this repo is
|
|
|
|
The operator-tier **host agent**, one per Proxmox host, owning **all** Proxmox interaction:
|
|
provision/restore guests, host storage, backup/restore orchestration, the hub control loop, and a
|
|
narrow per-guest local API. It is the **most privilege-sensitive component in the system**.
|
|
|
|
- Renamed from `proxmox-controller`.
|
|
- **Distinct from `felhom-controller`** — that is the *in-guest* controller, Docker-only, holding no
|
|
Proxmox credentials. Do not confuse them.
|
|
- **Control plane, not data plane:** if the agent dies, apps keep serving; only management degrades.
|
|
- Pure Go stdlib + `golang.org/x/crypto`. No web frameworks.
|
|
|
|
## Doing X → read Y
|
|
|
|
| Doing | Read |
|
|
|---|---|
|
|
| writing any new code | `REUSE.md` — helpers, format-safety guards, traps, seams |
|
|
| needing current state / open threads | `CONTEXT.md` + the top `CHANGELOG.md` entry |
|
|
| Proxmox, reconcile or signed jobs | loads itself: `.claude/rules/proxmox.md` |
|
|
| local API, authz or guest hooks | loads itself: `.claude/rules/localapi.md` |
|
|
| backup, PBS or DR | loads itself: `.claude/rules/backup.md` |
|
|
| storage or escrow | loads itself: `.claude/rules/storage.md` |
|
|
| writing a health check | loads itself: `.claude/rules/health-checks.md` |
|
|
| **release, build, publish, deploy, verify a version** | the **`felhom-build-deploy`** skill — **never hand-roll it** |
|
|
| writing or reviewing a test, fixing a bug | the **`felhom-testing`** skill |
|
|
| host addresses, break-glass, node facts | `felhom.eu/documentation/operations/nodes.md` — never restate them |
|
|
| which box may I break | `felhom.eu/documentation/runbooks/target-selection.md` |
|
|
| what version is live anywhere | ask the hub (`/hosts`, `/configs`) or the box — **never a doc** |
|
|
| the authoritative design | `felhom.eu/documentation/architecture/03-host-agent.md` (locked) |
|
|
|
|
## The root-CLI fence — API-first, exactly three exceptions
|
|
|
|
This is in the core because breaching it is how this component stops being auditable.
|
|
|
|
- **API-first** via a scoped `FelhomAgent` token. Raw root-CLI is **fenced to exactly 3 exceptions**:
|
|
keyctl `pct create` (golden image), USB mount/fstab, SMART/sensors.
|
|
- **`Client` never shells out; `Privileged` never makes HTTP calls** — asserted by `routing_test.go`.
|
|
Adding a method to `proxmox.Privileged` breaks the fence; use `proxmox.Runner` plus a new sudoers
|
|
`Cmnd_Alias` and `validate.go`-style checks (`REUSE.md` §3).
|
|
- **Destructive ops go through the reconcile gate / signed-jobs path.** Never call
|
|
`Client.DestroyLXC` / `Vzdump` / `SetConfig` ad-hoc — that skips classification, signature,
|
|
per-guest serialization and crash recovery.
|
|
- **Ownership must be PROVEN, never assumed.** A raw `ListLXC` list is not "guests the agent owns";
|
|
intersect with `Client.Pool` membership and fail safe on a read failure (audit A1).
|
|
|
|
## Gates — ONE entry point
|
|
|
|
**Run `python3 scripts/agent_gates.py` from the repo root after ANY change here.** It runs this
|
|
repo's gates — `reuse_refs_check` and `instructions_gate`, both the **shared** copies in
|
|
`felhom.eu/scripts/`, never copied into this repo (a copy recreates the drift they detect; an absent
|
|
sibling clone FAILS). `--fast` selects the gates touching no network and no container runtime; today
|
|
that is all of them. **A missing gate is a FAILURE, never a skip.**
|
|
|
|
**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** — CI re-runs the same entry point on every push and **emails the operator on
|
|
failure**, so a bypass is noticed even though it is not blocked (R-168, CLOSED 2026-08-02).
|
|
|
|
<!--
|
|
WHY ONE ENTRY POINT (2026-08-02, R-29): a census of all gates across the four repos found every check
|
|
a CLAUDE.md names was passing, and two of the four nobody is told to run were failing. This repo was
|
|
the extreme case — nothing ran against it at all, and 90 cited paths were checked by no one.
|
|
-->
|
|
|
|
## Live validation — the fence
|
|
|
|
Exercise the **SERVER-SIDE PIPELINE** a real user triggers, end-to-end. **The forbidden shortcut is
|
|
BYPASSING it** — the F9 episode was a raw guest-attach with hand-set state, and it proved nothing.
|
|
|
|
`claude-in-chrome` is NOT available on DooPlex. Invoking the exact endpoint the UI invokes is an
|
|
acceptable proxy — **say which method was used**. Low-level mechanism tests where the direct call IS
|
|
the mechanism are exempt.
|
|
|
|
## Conventions
|
|
|
|
- **Trunk-based — no branches.** All shippable work commits directly to `main`; `main` equals what is
|
|
deployed. Report-only artifacts (audits, findings, fixspecs) go to `felhom.eu/documentation/`.
|
|
- **Unattended escape hatch:** if a fix cannot be cleanly verified and shipped, **revert and report**
|
|
— never park it on a branch.
|
|
- **Logging**: the slog logger fans out to journald (configured level) plus the always-DEBUG
|
|
`applog.Ring` (remote pulls). English, keys-never-values, durations on outcomes. Full rules:
|
|
`felhom.eu/documentation/runbooks/logging-conventions.md`.
|
|
- Update `REUSE.md` in the same commit that adds, changes or deprecates a shared helper or pattern.
|
|
|
|
## End-of-session checklist
|
|
|
|
- **`CHANGELOG.md`** (cumulative, newest on top) and **`REPORT.md`** (overwritten with this run only)
|
|
— in every repo touched.
|
|
- **`CONTEXT.md`** — decisions, state, what is next.
|
|
- **`REUSE.md`** — if a shared helper or pattern moved.
|
|
- **A finding goes in `felhom.eu/documentation/backlog/OPEN-ITEMS.md` first**, never only in a report
|
|
or an audit.
|
|
- **Confirm your own last push's CI run went green, by run ID** — CI mails on failure, which is a PUSH
|
|
signal; this is the PULL check that catches a lost or unread mail. An unchecked green is an
|
|
assumption, not an observation.
|
|
|
|
**A gate ships with a decoy test that has been seen to fail (R-421).** A decoy is the LABEL without
|
|
the FACT — a directory with the right name and no bake log, a note whose prose mentions the marker it
|
|
lacks. `scripts/decoy_coverage_gate.py` refuses a new gate that has neither a decoy nor a named
|
|
exemption carrying its row. The four shapes, the 2026-09-01 sweep that fooled 16 of 29 gates, and the
|
|
decoys withdrawn as illegitimate: `documentation/audits/AUDIT-gate-decoys-2026-09-01.md` and
|
|
`felhom-controller/.claude/rules/gates.md`. **Scope is a fact too** — prefer `os.walk` over
|
|
`os.listdir`, and a glob over a hand-maintained list.
|