Files
felhom-agent/CLAUDE.md
T
admin 205e22babe
gates / gates (push) Failing after 12s
decoy sweep: no gate changed here, and that is the result (R-421)
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
2026-09-01 12:38:58 +02:00

6.7 KiB

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).

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.