# CLAUDE.md — `felhom.eu` > Stable orientation only — **current state lives in `CONTEXT.md` and the tops of > `hub/CHANGELOG.md` / `scripts/CHANGELOG.md` / `website/CHANGELOG.md`**, never here. Cross-repo > conventions (the three-component model, artifact taxonomy, access, clean-tree gate, secrets): > workspace-root `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`, whose versioned copy is > `documentation/runbooks/workspace-CLAUDE.md`. Path-scoped detail: `.claude/rules/`. ## What this repo is Four surfaces in one repo, plus the design home for the whole system: - `hub/` — **felhom-hub**, the operator backend (Go, k3s, `hub.felhom.eu`). - `website/` — static HTML at felhom.eu, served by k3s nginx + git-sync. - `manifests/` — k3s manifests for felhom-system, GitOps via one ArgoCD app. - `scripts/` — the **public installer** (`felhom-host-install.sh`) and this repo's gates. - `documentation/` — the **authoritative design home for all of Felhom**, not just this repo. - `skills/` — versioned source of the Claude Code skills; install with `python3 scripts/install_skills.py` (symlink — repo edits are live immediately). ## Doing X → read Y | Doing | Read | |---|---| | writing any new code | `REUSE.md` — helpers, seams, extension points, traps | | needing current state / roadmap | `CONTEXT.md` | | hub work (architecture, deploy, patterns) | loads itself: `.claude/rules/hub.md` | | website or installer work | loads itself: `.claude/rules/website.md` | | manifests / ArgoCD / secrets | loads itself: `.claude/rules/manifests.md` | | writing or routing a document | loads itself: `.claude/rules/docs.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 | | host addresses, break-glass, node facts | `documentation/operations/nodes.md` — never restate them | | which box may I break | `documentation/runbooks/target-selection.md` | | what version is live anywhere | ask the hub (`/hosts`, `/configs`) or the box — **never a doc** | | the authoritative design | `documentation/architecture/01..05-*.md` | ## Code quality - If you need more input or troubleshooting output, **ask first — don't guess**. - **A `go test -run` pattern that matches no test prints `ok` and exits 0.** A red-proof using `-run` must first prove the filter matched something (`-v`, look for `=== RUN`). Generally: **an instrument that can drop results silently is not a measurement.** ## The installer publishes by TAG, not by push (R-110) This fence is in the core deliberately: its trigger is editing `scripts/felhom-host-install.sh`, and no path-scoped rule covers that file. It governs the one artifact that runs **as root on a virgin box**. - **Pushing `scripts/felhom-host-install.sh` to `main` publishes NOTHING.** `manifests/webpage.yaml` runs two git-syncs: the website from `main`, and `/scripts/` from the tag `installer-v`. - **To publish:** cut `installer-v`, bump the `--ref` in `webpage.yaml` (**both** the sidecar and the init container), commit, sync. - **To roll back:** move the tag back and wait ~30 s. No ArgoCD sync, no deploy — that is the emergency lever; fix forward afterwards. - **Do NOT pin the website to the tag**, and the URL never carries a ref — `felhom-bootstrap.sh` and the hub's day-0 command follow the tag with no edit. - `hostinstall_gates.py` gate 6 fails if the manifest stops naming an `installer-v…` tag or if the website stops tracking `main`. ## Workflow — what is specific to this repo - **Never `git add -A` here** — parallel sessions share the clone and it sweeps foreign WIP. Stage explicit paths only, `git pull --rebase` before every push. - **`REPORT.md` is overwritten, so two sessions in this repo clobber each other.** The second session writes **`REPORT-.md`** and never touches the shared `REPORT.md`. - `CHANGELOG.md` here is **per-area**: `hub/`, `scripts/`, `website/`. ## Gates — ONE entry point **Run `python3 scripts/repo_gates.py` after ANY change in this repo.** It runs every gate — `site_gates.py`, `hostinstall_gates.py`, `hub_confirm_gate.py`, `manifest_bearer_gate.py`, `reuse_refs_check.py` and `instructions_gate.py` — 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.** `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). **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; CI reports rather than refuses because there is no PR to gate — R-169). ## End-of-session checklist Registers first — **a finding goes in `documentation/backlog/OPEN-ITEMS.md` first**, never only in a report, an audit or `STATUS.md`. Four items in this project were minted in a spike doc and lost (R-153/154/155, R-156/157). This applies to **every** session that ships, breaks or decides something, not only sessions that touch `documentation/` — which is why it is here and not in `docs.md`. - **`CHANGELOG.md` + `REPORT.md`** in every repo touched (see the workspace root for the rule, and the parallel-session caveat above). - **`REUSE.md`**, if a shared helper or pattern moved (same commit). - **`OPEN-ITEMS.md`** — every finding, with a number. - **Root `STATUS.md`** — at the end of every session in which something shipped, broke or was decided. It is a **view** of `OPEN-ITEMS.md`; nothing may exist only there. One screen, written for the operator in plain language, and deliberately **not** `CONTEXT.md`. - **The capability map** (`documentation/architecture/00-capability-map.md`), if a capability's status changed — with its new evidence citation. - **Confirm your own last push's CI run went green, by run ID.** CI emails on failure, which is a PUSH signal; this is the PULL check that catches a lost, filtered or unread mail. Quote the run id and its conclusion, e.g. `curl -s "https://gitea.dooplex.hu/api/v1/repos/admin//actions/tasks?limit=3"` → match the `head_sha` to your commit. An unchecked green is an assumption, not an observation.