From 7db42c5fec3ba2d48fa65b523c0bee0e86459b65 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 6 Aug 2026 09:38:27 +0200 Subject: [PATCH] docs: CLAUDE.md becomes a core plus path-scoped rules (R-229) 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) Claude-Session: https://claude.ai/code/session_01JJc8sAGRWmavP3rMtdpkr2 --- .claude/rules/agent-coupling.md | 43 ++++ .claude/rules/backup-paths.md | 41 ++++ .claude/rules/gates.md | 53 +++++ .claude/rules/ui-hungarian.md | 27 +++ CHANGELOG.md | 25 +++ CLAUDE.md | 281 ++++++++----------------- controller/scripts/controller_gates.py | 3 + 7 files changed, 280 insertions(+), 193 deletions(-) create mode 100644 .claude/rules/agent-coupling.md create mode 100644 .claude/rules/backup-paths.md create mode 100644 .claude/rules/gates.md create mode 100644 .claude/rules/ui-hungarian.md diff --git a/.claude/rules/agent-coupling.md b/.claude/rules/agent-coupling.md new file mode 100644 index 0000000..d56e619 --- /dev/null +++ b/.claude/rules/agent-coupling.md @@ -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. + + diff --git a/.claude/rules/backup-paths.md b/.claude/rules/backup-paths.md new file mode 100644 index 0000000..9b6e6f4 --- /dev/null +++ b/.claude/rules/backup-paths.md @@ -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. + + + +## 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. diff --git a/.claude/rules/gates.md b/.claude/rules/gates.md new file mode 100644 index 0000000..b71a0f8 --- /dev/null +++ b/.claude/rules/gates.md @@ -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). + + + +## 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. + + diff --git a/.claude/rules/ui-hungarian.md b/.claude/rules/ui-hungarian.md new file mode 100644 index 0000000..adfb90c --- /dev/null +++ b/.claude/rules/ui-hungarian.md @@ -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.** + + + +## 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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a788a1..fb6bb64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## docs — CLAUDE.md split into a core plus path-scoped rules (2026-08-06, R-229) — no version bump + +**Documentation and gate registration only. No Go changed, no image built, no deploy.** + +`CLAUDE.md` went from 215 lines to 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). What moved, rather than what was cut, is the point: + +- Four new `.claude/rules/*.md`, each carrying a `paths:` glob list so it loads only when a matching + file is read: `gates.md`, `ui-hungarian.md`, `backup-paths.md`, `agent-coupling.md`. +- The `## Layout` tree was deleted as derivable (`ls controller/internal/`); `REUSE.md` already owns + the per-package seams and traps 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 plain `root@192.168.0.162` (the LAN + *fallback*, not 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 2026-08-06:** + `qm list` shows VM `300 drill-r50`. `felhom-agent/CLAUDE.md` was right; this file was wrong. +- The seven session-critical invariants, the F9 live-validation fence and the end-of-session + checklist were kept verbatim — they are the file's highest-value content. + +`controller_gates.py` now registers **`instructions`**, the shared +`felhom.eu/scripts/instructions_gate.py`. Never copied here; an absent sibling clone FAILS. + +Full per-block accounting: `felhom.eu/documentation/audits/LEDGER-instruction-trim-2026-08-06.md`. + ## v0.202.0 — the customer is blamed only after a real attempt refused their code (2026-08-06, R-224/R-226/R-225/R-227/R-228) — MinAgent 0.126.0 **CAMPAIGN-11's headline defect had moved, not gone.** v0.201.0 stopped an agent that is too OLD from diff --git a/CLAUDE.md b/CLAUDE.md index c6c133d..97f87aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,215 +1,110 @@ -# CLAUDE.md — Project Instructions for Claude Code (`felhom-controller`) +# CLAUDE.md — `felhom-controller` -> Read automatically at session start. Stable orientation only — **current state lives in -> `CONTEXT.md` and the top of `CHANGELOG.md`**, never here. Cross-repo orientation: workspace-root -> `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`. +> Stable orientation only — **current state lives in `CONTEXT.md` and the top of `CHANGELOG.md`**, +> never here. Cross-repo conventions (clean-tree gate, secrets, trunk-based, artifact taxonomy): +> workspace-root `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`. Path-scoped detail: `.claude/rules/`. -!!! IMPORTANT !!! -- Always update CHANGELOG.md whenever you modified the code, and pushed to git!! -- IF controller feature changed (new/modify/remove) always update the relevant part of controller/README.md with the architectural change!! +## What this repo is -## Project overview - -Felhom is a managed home-server business for Hungarian customers. This repo contains the -**felhom-controller** — the Go application that manages Docker Compose stacks inside each customer -LXC guest via a Hungarian-language web dashboard. - -Read in this order: -- **`REUSE.md`** — before writing new code (canonical helpers, patterns, traps, seams). -- `CONTEXT.md` — current project state, decisions, roadmap (update after each session). -- `controller/README.md` — full feature/architecture reference (update when features change). -- `TASK.md` — the current task to implement (if it exists). - -## System context — the three-component model - -The project runs **on Proxmox**, with a locked three-component model: -- **Hub** (`felhom.eu/hub/`) — operator backend on k3s. -- **Host agent** (`felhom-agent/`) — one per Proxmox host; operator-tier; owns ALL Proxmox interaction. -- **In-guest controller** (THIS repo) — one per customer LXC; **Docker-only; holds NO Proxmox - credentials**. De-privileged: disk/host/Proxmox concerns are delegated to the host agent via the - pinned local-API client (`internal/agentapi`); the controller keeps the app domain — stack/deploy - management, the Hungarian web UI, app-data backup, metrics/telemetry, integrations, git-sync, - notifications. Whole-guest backup (PBS vzdump) is the agent's. - -> **Authoritative maps:** `felhom.eu/documentation/architecture/01/02/03-*.md` (topology/trust, -> controller module map, host agent) + the code-verified feature docs in -> `felhom.eu/documentation/controller/`. Match the current code, not summaries, if they drift. +The **in-guest controller** — one per customer LXC, Docker-only, **holds NO Proxmox credentials**. It +owns the app domain: stack/deploy management, the Hungarian web UI, app-data backup, metrics, +integrations, git-sync, notifications. Disk/host/Proxmox concerns are delegated to the host agent via +`internal/agentapi`. Whole-guest backup (PBS vzdump) is the agent's, not ours. **Don't confuse the two ex-"controllers":** `felhom-agent` (host, operator-tier, was -`proxmox-controller`) vs this `felhom-controller` (in-guest, was `deploy-felhom-compose`). +`proxmox-controller`) vs this repo (in-guest, was `deploy-felhom-compose`). -## Layout (verified against the tree) +## Doing X → read Y -``` -controller/cmd/controller/ entry point + startup wiring (scheduler block, init-only setters) -controller/internal/ - agentapi/ pinned-TLS client to the host agent's per-guest local API (THE disk seam) - api/ REST /api/* router (writeJSON envelope, limitBody, config writes) - appbackup/ felhom-data paths/namespaces, DB dumps, userdata skeleton (shared primitives) - appexport/ .fab export/import bundles (password crypto, strict segment validation) - assets/ app logo/screenshot sync from the hub - backup/ app-data backup manager, recovery units, tier-2 copies, offbox restic - bootstrap/ bootstrap.json ingest → controller.yaml (Day-0 + refresh) - channelhealth/ agent-channel health checker (debounce + born-down alerting) - cloudflare/ geo-enforcement remnant (agent-delegated) - config/ controller.yaml load/validate (LoadPermissive = setup-mode only) - crypto/ AES-256-GCM app.yaml secret encryption (ENC: prefix) - infra/ traefik/cloudflared/filebrowser base-stack templates - integrations/ app-to-app integrations (e.g. OnlyOffice) - mailrelay/ app-email SMTP shim → hub relay - metrics/ telemetry collection - monitor/ health checks, protected containers - notify/ hub event push (typed Notify* wrappers) - quiesce/ quiesce loop for whole-guest backup (marker + recover) - recovery/ recovery-unit restore - report/ hub report builder/pusher + pull-based config refresh - scheduler/ background jobs (Every/Daily, Budapest DST-safe) - selftest/ startup self-checks - selfupdate/ controller image self-update via the agent swap - settings/ settings.json persistence (registry, flags, corruption recovery) - setup/ first-boot setup wizard (own CSRF) - stacks/ compose ops: deploy/delete/migrate/state (THE app domain core) - sync/ git-sync of the app catalog - system/ mounts/probes (linux + permissive _other stubs) - util/ small shared helpers - web/ dashboard UI: server, auth/CSRF, handlers, funcmap, templates (Hungarian) -``` +| Doing | Read | +|---|---| +| writing any new code | `REUSE.md` — canonical helpers, patterns, traps, seams | +| needing current state / roadmap | `CONTEXT.md` | +| needing a feature or architecture reference | `controller/README.md` | +| build, deploy, publish, verify a version | the **`felhom-build-deploy`** skill | +| writing or reviewing a test, fixing a bug | the **`felhom-testing`** skill | +| UI, tokens, badges, Hungarian copy | the **`felhom-ui-design`** skill | +| which box may I break | `felhom.eu/documentation/runbooks/target-selection.md` | +| host addresses, break-glass, node facts | `felhom.eu/documentation/operations/nodes.md` | +| what version is live anywhere | ask the hub (`/hosts`, `/configs`) or the box — **never a doc** | +| the authoritative design | `felhom.eu/documentation/architecture/01/02/03-*.md` | -Per-package helpers/seams/traps: **`REUSE.md`** (maintained same-commit as helper changes). +## Session-critical invariants -## Conventions & cardinal rules - -- **Trunk-based — no branches.** All shippable work commits directly to `main`; `main` equals what is - deployed. Report-only artifacts → `felhom.eu/documentation/` (`audits/`, `backlog/`). Risky fixes - are implemented during the supervised session itself, on `main`; if a fix can't be verified/shipped, - revert + report — never park on a branch. -- Code quality: double-check for bugs/edge cases; add debug logging; **ask rather than guess**. -- All UI text is Hungarian (Budapest timezone). Design tokens/gates: use the `felhom-ui-design` skill. -- **Run `python3 controller/scripts/controller_gates.py` (from `controller/`) after ANY change in - this repo.** It is the ONE entry point and 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` 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. - **Why a runner and not seven 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. This file 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 shared `reuse_refs_check.py` lives in `felhom.eu/scripts/` and is never copied here** — - a copy would recreate the drift it detects; an absent sibling clone FAILS the gate. - **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). -- Testing doctrine (non-hollow tests, red-proofs, seams): use the `felhom-testing` skill. -- **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 in - `felhom.eu/documentation/runbooks/logging-conventions.md`. -- Update `REUSE.md` if you added/changed/deprecated a shared helper or pattern (same commit). -- **Coupled features** (controller behavior that depends on a specific agent version): add a - `featureProbes` table row in `internal/agentapi/features.go` + a `Supports` gate call at the - feature's entry point; declare `MinAgent: X.Y.Z` in the CHANGELOG entry header. Rules: - `felhom.eu/documentation/runbooks/publish-train-rules.md`. - -> **In every repository where you make a change, update both files in that repo:** -> - **`CHANGELOG.md`** — cumulative log, newest on top. -> - **`REPORT.md`** — **overwrite** with the most recent implementation/validation summary only. -> -> **Never write secrets** into any committed file — reference them as "stored out-of-band". - -## Live validation - -Exercise the SERVER-SIDE PIPELINE a real user triggers, end-to-end (connect → enroll → deploy). The -forbidden shortcut is BYPASSING that pipeline (the F9 episode: raw agent guest-attach + hand-set -state). **`claude-in-chrome` is NOT available in the DooPlex environment** — the standard method is -endpoint-level: invoke the exact endpoint the UI invokes (no server logic is skipped, only -rendering) and say which method was used. Strict end-to-end UI coverage is a manual click-through. - -Two traps in that method, both from the 2026-07-20 remediation: -- **Grep the fetched page 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/string being gone. Use `kezel`, `Utols`, `Biztons`; never let an accented pattern gate a - conclusion (it nearly produced a wrong "banner cleared" claim). -- **Credentials with `!` 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. - -## Environment & access - -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/`. All repos hosted at -`gitea.dooplex.hu/admin/`. Builds are local commands; felhom-pve is one SSH hop. - -| Host | Access | Role | -|------|--------|------| -| **DooPlex (this host)** | local — `/mnt/5_hdd/felhom.eu/{git,build}/` | build + push images, `sudo kubectl` | -| Demo Proxmox host `demo-felhom` | `ssh felhom-pve` (root@192.168.0.162) | `pct` into guests; live validation | -| Demo guest 9201 | `ssh felhom-pve "pct exec 9201 -- ..."` | the live demo controller (golden/bootstrap-managed) | -| Demo host `demo-hp` (HP t740) | `ssh demo-hp` (TS `100.76.96.79` / LAN `192.168.0.87`); **no baked key** — break-glass root via hub `host_recovery/demo-hp-bb76ea` + `sshpass` (recipe in `felhom.eu/documentation/operations/nodes.md`) | 2nd demo node (guest 9201 `demo-hp`, agent 0.93.0). **Designated drill+build VM host** per the 2026-07-25 ruling — but **no drill VM is provisioned there yet** (forward-looking); the drill `drill.qcow2` still lives on DooPlex (off). See nodes.md. | -| felhotest (legacy) | `ssh -p 33022 kisfenyo@router.abonet.hu` | OLD /opt/docker compose mechanism | - -> **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 lacks -> the Windows agent and fails silently — see `docs/vscode-ssh-fix.md`), plus `MSYS_NO_PATHCONV=1` -> for `pct exec`. Retained in case that environment is revived. - -> **felhom-pve is back on the home LAN (as of 2026-07-25).** The host holds `192.168.0.162` again and -> the **agent is UP** — `localapi` binds `192.168.0.162:8443`, the service is `active`, capabilities -> self-check clean, and all agent-backed features (storage, PBS backup, quiesce, restore-test, DR) are -> reachable. `ssh felhom-pve` remains a **Tailscale** alias (`100.70.170.35`, location-independent, the -> N100 is travel-portable) with `Host felhom-pve-lan` as the direct-LAN fallback; both work. The earlier -> "remote site — agent DOWN (binds a stale `.162`)" block was the 2026-07-20→24 vacation window and is -> now retired; the historical record stays in -> `felhom.eu/documentation/audits/AUDIT-vacation-remote-ops-2026-07-20.md`. - -External access via Cloudflare Tunnel → Traefik; Pi-hole forwards `*.demo-felhom.eu` → .162 locally. - -## Build & deploy — MANDATORY after code changes - -**Full runbook: use the `felhom-build-deploy` skill.** Summary (guest 9201 is bootstrap-managed — -**no compose file**; `felhom-controller-bootstrap.service` runs the tag in `/etc/felhom-controller-image`): - -> **Clean-tree gate before any build:** `git status --porcelain` must be empty and -> `git rev-parse HEAD` must equal `git rev-parse origin/main` in the repo being built. An unpushed -> change does not exist — never build a dirty or unpushed tree. The `git pull` in the build step -> stays (it is a no-op when you work in this tree, and load-bearing if anything was pushed from -> elsewhere). - -| Step | Command | -|------|---------| -| 1. Commit + push | `git add && git commit -m "..." && git push` | -| 2. Build + push image | `cd /mnt/5_hdd/felhom.eu/build/felhom-controller && git -C /mnt/5_hdd/felhom.eu/git/felhom-controller pull && ./build.sh --push` (build.sh does NOT pull — the explicit pull is load-bearing) | -| 3. Deploy (9201) | `ssh felhom-pve "pct exec 9201 -- bash -c 'docker pull gitea.dooplex.hu/admin/felhom-controller: && echo gitea.dooplex.hu/admin/felhom-controller: > /etc/felhom-controller-image && systemctl restart felhom-controller-bootstrap.service'"` | -| 4. Verify | `ssh felhom-pve "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'"` + container logs | - -Hub build/deploy lives in `felhom.eu` (GitOps) — see that repo's CLAUDE.md / the skill. Catalog -changes (`app-catalog-felhom.eu`): commit+push; controller sync picks them up ≤15 min or via the -"Sablonok frissítése" button. - -## Session-critical invariants (the rest live in REUSE.md) +The rest live in `REUSE.md`. These cost incidents to learn: - `docker compose restart` does NOT pick up new images/env — always `up -d` (`RedeployFromEnv`). -- Docker's `.State` says "running" even for unhealthy containers — `.Status` parse is the truth. -- In-memory `Deployed` flag is set BEFORE `compose up -d` (slow-pull race); reverted on failure. -- `compose up -d` exits 0 on crash-loops — post-start status check is the detection. +- Docker's `.State` says "running" even for unhealthy containers — the `.Status` parse is the truth. +- In-memory `Deployed` is set BEFORE `compose up -d` (slow-pull race); reverted on failure. +- `compose up -d` exits 0 on crash-loops — the post-start status check is the detection. - Env var KEYS are logged, never values. Protected stacks (traefik, cloudflared, felhom-controller) - can't be stopped from the UI. + cannot be stopped from the UI. - Verify a container image HAS the healthcheck tool before using it (BusyBox wget / python3 / curl — - catalog REUSE.md maps the families). + the catalog `REUSE.md` maps the families). +- `IsRunning()` is CONCURRENCY, false during a verification restore — display MUST use + `RestoreStatus()`. + +## Live validation — the fence + +Exercise the SERVER-SIDE PIPELINE a real user triggers, end-to-end (connect → enroll → deploy). **The +forbidden shortcut is BYPASSING that pipeline** — the F9 episode was a raw agent guest-attach with +hand-set state, and it proved nothing. + +`claude-in-chrome` is NOT available on DooPlex. The standard method is endpoint-level: invoke the +exact endpoint the UI invokes (no server logic is skipped, only rendering) and **say which method was +used**. Strict end-to-end UI coverage is a manual click-through by the operator. + +Two traps in that method live in `.claude/rules/ui-hungarian.md` (ASCII-only greps; `!` in +credentials) — they load when you touch a template or stylesheet. + +## Commands — one per surface + +| Surface | Command | +|---|---| +| Gates (after ANY change) | `python3 controller/scripts/controller_gates.py` — from `controller/` | +| Green gate | `go build ./... && go vet ./... && go test ./...` | +| Build + deploy | the **`felhom-build-deploy`** skill — do not hand-roll it | + +Guest 9201 is **bootstrap-managed — there is no compose file**; +`felhom-controller-bootstrap.service` runs the tag written in `/etc/felhom-controller-image`. Catalog +changes (`app-catalog-felhom.eu`) are picked up by controller sync ≤15 min, or via the "Sablonok +frissítése" button. ## Working with CHANGELOG.md **DO NOT read the full file** — it is large and will waste context. -- Session start: use `CONTEXT.md` + `controller/README.md` for current state. + +- Session start: `CONTEXT.md` + `controller/README.md` for current state. - Adding an entry: Read only the top ~30 lines for format, then Edit-insert after line 1. - History: Grep for topics instead of reading. ## End-of-session checklist -1. **Commit and push** all code changes -2. **Build, push, and deploy** the new controller image (if controller code changed) -3. **Update CHANGELOG.md** with what was done -4. **Update CONTEXT.md** with decisions made, state and what's next -5. **Update controller/README.md** if architecture or features changed -6. **Verify** the deployment is working (check `docker ps` and logs) -7. **Update REUSE.md** if you added/changed/deprecated a shared helper or pattern (same commit) +1. **Commit and push** all code changes (explicit paths; no `git add -A`). +2. **Build, push, and deploy** the new controller image, if controller code changed. +3. **`CHANGELOG.md`** — always, whenever code changed and was pushed. +4. **`CONTEXT.md`** — decisions made, state, what is next. +5. **`controller/README.md`** — whenever a feature was added, modified or removed. +6. **`REPORT.md`** — overwrite with this run's summary only. +7. **`REUSE.md`** — if a shared helper or pattern was added/changed/deprecated (same commit). +8. **Verify** the deployment (`docker ps` + logs). + + diff --git a/controller/scripts/controller_gates.py b/controller/scripts/controller_gates.py index f752c29..7fbc3d3 100644 --- a/controller/scripts/controller_gates.py +++ b/controller/scripts/controller_gates.py @@ -45,6 +45,8 @@ SCRIPTS = os.path.dirname(os.path.abspath(__file__)) CTRL = os.path.dirname(SCRIPTS) # /controller — every gate's cwd REPO = os.path.dirname(CTRL) # — the root REUSE.md lives here SHARED_REUSE = os.path.join(os.path.dirname(REPO), "felhom.eu", "scripts", "reuse_refs_check.py") +SHARED_INSTRUCTIONS = os.path.join( + os.path.dirname(REPO), "felhom.eu", "scripts", "instructions_gate.py") # (label, absolute script path, args, fast) GATES = [ @@ -56,6 +58,7 @@ GATES = [ ("mojibake", os.path.join(SCRIPTS, "mojibake_gate.py"), [], True), ("docker-v", os.path.join(SCRIPTS, "docker_run_volume_path_gate.py"), [], True), ("reuse-refs", SHARED_REUSE, [REPO], True), + ("instructions", SHARED_INSTRUCTIONS, [REPO], True), ] VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}