diff --git a/.claude/rules/docs.md b/.claude/rules/docs.md new file mode 100644 index 0000000..55107bd --- /dev/null +++ b/.claude/rules/docs.md @@ -0,0 +1,46 @@ +--- +paths: ["documentation/**"] +--- + +# Documentation — routing and register discipline + +`documentation/` is the **authoritative design home for the whole Felhom system**, not just this +repo. Sibling repos point here; this is where the pointed-at thing must actually be. + +## Where a fact goes — one home each + +| Fact | Home | +|---|---| +| the locked design | `architecture/01..05-*.md` | +| capability status + its evidence | `architecture/00-capability-map.md` | +| host addresses, routes, node names, break-glass, what is provisioned | `operations/nodes.md` | +| Tailscale topology, accept-dns/accept-routes | `operations/tailscale.md` | +| which box may be broken, and what may be done to it | `runbooks/target-selection.md` | +| an operational procedure | `runbooks/RUNBOOK-*.md` | +| logging levels and phrasing | `runbooks/logging-conventions.md` | +| a spike or campaign result | `audits/` | +| every open finding | `backlog/OPEN-ITEMS.md` | +| the operator's one-screen view | root `STATUS.md` | + +**Do not restate a fact that has a home** — point at it. Re-check an address rather than trusting one +written down. + +## Register discipline + +The end-of-session register rules (`OPEN-ITEMS.md` first, `STATUS.md` is a view, nothing exists only +in a report) are in the repo core `CLAUDE.md` **deliberately** — they apply to every session that +ships or decides something, not only to sessions that touch `documentation/`, so they must load +unconditionally. This file adds only the routing above. + +## The coupling rule + +**A recipe or contract section is a TWO-REPO change.** If a document here defines something a sibling +consumes — a DR recipe's producers, an allow-list, an event type — the consumer's code and this +document move in the same session, or the hub silently drops what it does not recognise. + +## Version literals + +**No component version literal in any instruction file, and none in a doc that reads as current +fleet state.** Versions change several times a day and the fleet is not uniform. A *historical* +citation ("fixed in hub v") is fine in an audit or an HTML comment beside the rule it justifies — +it cannot go stale the way "this box currently runs agent v" can. diff --git a/.claude/rules/hub.md b/.claude/rules/hub.md new file mode 100644 index 0000000..6407ba8 --- /dev/null +++ b/.claude/rules/hub.md @@ -0,0 +1,72 @@ +--- +paths: ["hub/**"] +--- + +# Hub — architecture, constraints, deploy + +## What the hub is + +Operator backend. Authors operator *intent*, mirrors box *reality*, holds **no data-plane role**, and +**never connects inbound to a box**. If it dies, apps keep serving; only management degrades. + +Three inbound contracts, and one is frozen: the agent's host-domain report +(`POST /api/v1/host-report`, the heartbeat/dead-man's-switch), the legacy controller report +(`POST /api/v1/report`, **frozen until the slice-10 cutover — do not modify**), and structured +controller events (`POST /api/v1/event`, gated by `allowedEventTypes`). + +Everything else — checkers, the two-tier notification dispatcher, the app-mail relay, customer-config +and Day-0 artifact-manifest management, the operator UI — is mapped in **`REUSE.md`**. Full design: +`documentation/architecture/05-hub-architecture.md`. + +## The two stack constraints + +The dependency list is `hub/go.mod`'s business and the deploy shape is `manifests/`. Only these two +cannot be read off the code: + +- **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). + +## Deploy — GitOps, and the manifest is the truth + +**Full runbook: the `felhom-build-deploy` skill.** The load-bearing rules: + +- **A code change + CHANGELOG bump deploys NOTHING.** The running image changes only when + `manifests/hub.yaml`'s `image:` tag changes in git and the app is synced. +- **Pin explicit versions, never `:latest`. Never bare `kubectl set image` / `kubectl apply`** — + reverted on the next sync. +- **The live image can lag the CHANGELOG** when a bump was committed but the manifest/sync step never + happened — reconcile via the manifest, not the changelog. +- Green gate before any hub commit: `go build ./... && go vet ./... && go test ./...` in `hub/`. + +## Rules that bite here + +- **Seam-wiring — it covers TEMPLATE GATES:** a feature is not shipped until its entry point is + reachable. Any conditional affordance (`{{if .Flag}}` around a button, form or script) ships with a + render test **per branch of the gate**. Handler tests that POST directly prove nothing about + reachability. +- **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 kernel state, never by reading or + writing the filesystem. +- New event types must enter `allowedEventTypes` **and** `customerMessages` together, or `POST + /event` 400s. +- Status logic: OK (report < 30m), WARN (30m–1h or `health=warn`), DOWN (> 1h or `health=fail`). + Host-liveness thresholds are **shared** between UI and checker — never invent a second definition. +- SQLite timestamps vary in format — always `parseSQLiteTime()`. +- **Logging**: DEBUG = flow detail, INFO = state change + duration; operator English; keys never + values (`documentation/runbooks/logging-conventions.md`). The bundle secret-gate fails closed. + + diff --git a/.claude/rules/manifests.md b/.claude/rules/manifests.md new file mode 100644 index 0000000..98fe978 --- /dev/null +++ b/.claude/rules/manifests.md @@ -0,0 +1,33 @@ +--- +paths: ["manifests/**", "**/*.yaml"] +--- + +# Manifests — ArgoCD, deliberate sync, secrets + +## One app, and auto-sync is OFF + +The whole cluster is GitOps via a **single ArgoCD app `felhom`** syncing this repo's `manifests/` to +the `felhom-system` namespace. **Auto-sync is OFF — a deploy is a deliberate manual sync.** + +ArgoCD's source of truth is the **manifest in git**: + +- Committing a manifest change deploys nothing until the app is synced. +- **Pin explicit image versions, never `:latest`.** +- **Never bare `kubectl set image` / `kubectl apply`** — the next sync reverts it, so the fix looks + like it worked and then silently disappears. +- Sync method (hard-refresh + sync, local `sudo kubectl`): the **`felhom-build-deploy`** skill. + +## Secrets + +- **Out-of-band `secretKeyRef` only — never inline `stringData`** (`REUSE.md` §3). +- `manifest_bearer_gate.py` fails on any bearer-shaped literal anywhere under `manifests/`. It runs + from `python3 scripts/repo_gates.py`. + +## The two git-syncs in `webpage.yaml` + +`manifests/webpage.yaml` runs **two** git-syncs and they track different refs: the website from +`main`, and `/scripts/` from the tag `installer-v`. Changing either `--ref` changes +what real machines download — the init container **and** the sidecar both carry it, and both must +move together. `hostinstall_gates.py` gate 6 fails if the manifest stops naming an `installer-v…` +tag or if the website stops tracking `main`. The full publish/rollback procedure is the R-110 fence +in the repo core `CLAUDE.md`. diff --git a/.claude/rules/website.md b/.claude/rules/website.md new file mode 100644 index 0000000..789fa39 --- /dev/null +++ b/.claude/rules/website.md @@ -0,0 +1,36 @@ +--- +paths: ["website/**"] +--- + +# Website — build, deploy, and the encoding fences + +## Deploy + +The website **auto-deploys via git-sync from `main`** — just push, live in 1–2 min. No ArgoCD sync, +no image build. Emergency edits: https://files.felhom.eu. + +**Do NOT pin the website to the installer tag.** The sparse-checkout used to cover `/website/` and +`/scripts/` in one sync; pinning that would turn every copy edit into a release. The installer's +tag-based publishing is a separate mechanism — see the R-110 fence in the repo core `CLAUDE.md`. + +## Gates + +Website changes go through `python3 scripts/repo_gates.py`, which runs `site_gates.py`. **A new page +must be added to that gate's `PAGES` list** or it is unchecked. + +`site_gates.py` asserts: BOM, emoji, nav/footer consistency, analytics, CDN, design tokens, and +cache-busting. + +## The encoding fences + +- **All `website/` HTML is UTF-8 *with BOM*. Preserve it.** An editor that strips the BOM is a + silent, whole-file change. +- **Emoji count is zero.** The design system forbids them; the gate enforces it. +- Cache-busting: bump the asset query string when CSS or JS changes, or browsers serve the old file + and the change looks like it did not deploy. + +## Copy + +Customer-facing copy is **Hungarian**, and its tone and terminology rules live in the +**`felhom-ui-design`** skill — use it rather than inventing phrasing. Operator-facing text is +English. diff --git a/CLAUDE.md b/CLAUDE.md index 5a84aa8..36e88e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,252 +1,130 @@ -# CLAUDE.md — Project Instructions for Claude Code (`felhom.eu`) +# CLAUDE.md — `felhom.eu` -> Read automatically when Claude Code works in this repo. 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 orientation (the felhom system, artifact -> taxonomy, access): workspace-root `/mnt/5_hdd/felhom.eu/git/CLAUDE.md`; this file is -> `felhom.eu`-specific. A versioned copy of that workspace file lives at -> `documentation/runbooks/workspace-CLAUDE.md`. +> 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/`. -## Project overview +## What this repo is -This repo contains: -- **Website** (`website/`) — static HTML at felhom.eu, served via k3s nginx + git-sync sidecar. -- **Hub** (`hub/`) — Go application (felhom-hub), the **operator backend**, on k3s at `hub.felhom.eu`. -- **K8s manifests** (`manifests/`) — k3s deployment manifests for felhom-system services. -- **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; install/update with +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). -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. +## Doing X → read Y -## The Felhom system (so the hub's role is in context) +| 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` | -Felhom is **Proxmox-based**, with a locked **three-component model**: -- **Hub** (this repo, `hub/`) — operator backend. Authors operator *intent*; mirrors box *reality*; - holds **no data-plane role** and never connects inbound to a box. -- **Host agent** (repo `felhom-agent/`) — one per Proxmox host; owns all Proxmox interaction. -- **In-guest controller** (repo `felhom-controller/`) — one per customer LXC; Docker-only. - -## Hub — architecture (version-free; current version = `manifests/hub.yaml` image tag) - -The hub ingests two report streams — the agent's host-domain report (`POST /api/v1/host-report`, the -heartbeat/dead-man's-switch) and the legacy controller report (`POST /api/v1/report`, **frozen until -the slice-10 cutover — do not modify**) — plus structured controller events (`POST /api/v1/event`, -gated by `allowedEventTypes`). Around them: staleness/disk/storage-fill/leaf/capability monitor -checkers, the two-tier notification dispatcher (operator English / customer Hungarian, Resend, -cooldowns), the app-mail relay, customer-config + Day-0 artifact-manifest management (the checksum -trust root the host bootstrap verifies against), assets serving, and the password-gated operator web -UI. Package map, helpers, seams, extension points: **`REUSE.md`** (e.g. new event types must enter -`allowedEventTypes` + `customerMessages` together). - -## Code quality rules +## Code quality - 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 — 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 +- **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.** -- **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. -- UI/design work (tokens, gates, copy rules): use the `felhom-ui-design` skill. -- **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). +## The installer publishes by TAG, not by push (R-110) -## Workflow & artifacts +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**. -The planning/architecture assistant ("project Claude", in claude.ai) writes specs and validates -pushes; **you (Claude Code) implement**. A file being open in the editor is NOT an instruction. +- **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`. -- **`TASK.md` / `TASK-*.md`** — a spec for you to implement. Then push and update `hub/CHANGELOG.md` - and root `REPORT.md` per the convention below. -- **`RUNBOOK-*.md`** — an operational procedure. CC executes the steps it has access and capability - for, including live validation on the demo nodes and the demo Proxmox host (CC has root@felhom-pve - SSH + the felhom-agent token). Mark a step HUMAN only when it genuinely needs physical presence, a - real-world decision, or credentials CC truly lacks. -- Validation of a push against a spec's criteria is project Claude's job, not yours, unless asked. -- **Browser automation is NOT available** in the DooPlex environment (`claude-in-chrome` was a - Windows-workstation capability). Validate at the endpoint level — invoke the exact endpoint the UI - invokes — and via render tests; say which method was used. The hub UI is operator-password-gated - anyway, so render tests were already the method for UI changes. Strict end-to-end UI coverage is a - manual click-through by the operator. +## Workflow — what is specific to this repo -> **In every repository where you make a change, update both files in that repo:** -> - **`CHANGELOG.md`** — cumulative log, newest on top (here: per-area `hub/`, `scripts/`, `website/`). -> - **`REPORT.md`** — **overwrite** with the most recent implementation/validation summary only. -> **Parallel sessions:** `REPORT.md` is overwritten, so two sessions working in this repo at once -> will clobber each other. The second session writes **`REPORT-.md`** instead and never -> touches the shared `REPORT.md`. -> -> **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. - 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). +- **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/`. - -## End-of-session checklist - -- **`CHANGELOG.md` + `REPORT.md`** per the rule above, in every repo touched. -- **`REUSE.md`**, if a shared helper or pattern moved (same commit). -- **The capability map** (`documentation/architecture/00-capability-map.md`), if a capability's status - changed — with its new evidence citation. -- **The architecture doc** that owns any changed contract (S-1, `CONTEXT.md`). -- **Root `STATUS.md`** — *update it at the end of every session in which something shipped, broke, or - was decided. It is a view of `documentation/backlog/OPEN-ITEMS.md` — **nothing may exist only - there**. One screen; cut items rather than extending it.* It is written for the **operator** in - plain language, and is deliberately **not** `CONTEXT.md` — do not consolidate the two. -- **A finding goes in `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). -- **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 in the session report, 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. - -## Hub stack — the two constraints - -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 - -Claude Code runs **on DooPlex (192.168.0.180, Debian 13, user `kisfenyo`)** — the k3s node itself. -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 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. **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. - - - -## Build & deploy — Hub (GitOps via ArgoCD) - -**Full runbook: use the `felhom-build-deploy` skill.** The load-bearing rules: - -The whole cluster is GitOps via a **single ArgoCD app `felhom`** syncing this repo's `manifests/` -to `felhom-system`. **Auto-sync is OFF** — deploys are a deliberate manual sync. ArgoCD's source of -truth is the **manifest**: - -- **A code change + CHANGELOG bump deploys NOTHING.** The running image changes only when - `manifests/hub.yaml`'s `image:` tag changes in git and the app is synced. -- **Pin explicit versions, never `:latest`.** Never bare `kubectl set image`/`kubectl apply` - (reverted on next sync). -- **The live image can lag the CHANGELOG** when a bump was committed but the manifest/sync step never - happened — reconcile via the manifest, not the changelog. -- Green gate before any hub commit: `go build ./... && go vet ./... && go test ./...` in `hub/`. - -> **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). - -Steps: commit+push code → `cd /mnt/5_hdd/felhom.eu/build/felhom-hub && ./build.sh --push` -(local) → bump `manifests/hub.yaml` tag + push → ArgoCD hard-refresh + sync (kubectl-patch method in -the skill, now local `sudo kubectl`) → verify Synced/Healthy + rollout + image + startup log. - ## Gates — ONE entry point -**Run `python3 scripts/repo_gates.py` after ANY change in this repo.** It is the one entry point -and runs every gate — `site_gates.py`, `hostinstall_gates.py`, `hub_confirm_gate.py`, -`manifest_bearer_gate.py` and `reuse_refs_check.py` on this root — streaming each gate's own output -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. +**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.** Both facts are why CI is still owed (`OPEN-ITEMS.md` R-168). + +## End-of-session checklist -**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 -clone does not carry it, and any manual `repo_gates.py` 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 continuous integration is still owed (`OPEN-ITEMS.md` R-168) — this hook is -local and skippable, and only CI is neither. +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`. -## Build & deploy — Website / Manifests - -- **Website** auto-deploys via git-sync; just push to `main` (live in 1–2 min). Website changes go - through `repo_gates.py` above (it runs `site_gates.py`); new pages go into that gate's `PAGES` - list. Emergency edits: https://files.felhom.eu. All `website/` HTML is **UTF-8 with BOM** — preserve it. -- **THE INSTALLER DOES NOT (R-110, 2026-08-03).** `manifests/webpage.yaml` runs **two** git-syncs: - the website from `main` as above, and `/scripts/` from the tag **`installer-v`**. - Pushing `scripts/felhom-host-install.sh` therefore changes nothing that any machine downloads — - which it used to, within thirty seconds, for the one artifact that runs as **root on a virgin box**. - - **To publish:** cut `installer-v`, bump the `--ref` in `webpage.yaml` - (both the sidecar and the init container), commit, and sync. `hostinstall_gates.py` gate 6 - fails if the manifest stops naming an `installer-v…` tag or if the website stops tracking `main`. - - **To roll back:** move the tag back to the previous commit and wait ~30 s. **No ArgoCD sync and - no deploy** — git-sync picks up a moved tag on its next period, measured live on 2026-08-03 in - both directions. That is the emergency lever; fix forward with a new version afterwards. - - **Do NOT pin the website to the tag.** The sparse-checkout used to cover `/website/` and - `/scripts/` in one sync, and pinning that would turn every copy edit into a release. - - The **URL never carries a ref** (`https://felhom.eu/scripts/felhom-host-install.sh`), so - `felhom-bootstrap.sh` and the hub's day-0 command follow the tag with no edit — do not add one. - - The installer's own sixteen run-time fetches are pinned separately, to `raw/tag/v$ART_AGENT_VER` - in the **agent** repo (R-183) — they are the agent's configs, not this repo's. -- **Manifests** are GitOps via the `felhom` app — commit to `main`, then deliberate sync. - -## Key patterns - -- Hub status logic: OK (report < 30m), WARN (30m–1h or health=warn), DOWN (> 1h or health=fail); - host liveness thresholds shared between UI and checker (never invent a second definition). -- SQLite timestamps vary in format — always `parseSQLiteTime()`. -- Dashboard/detail auto-refresh every 60s via meta refresh. Geo-restricted to Hungary via nginx - ingress annotation. -- Helpers, seams, extension points, traps: **`REUSE.md`** — the map is maintained same-commit. +- **`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. diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 781ba7a..a937643 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,3 +1,38 @@ +## repo_gates.py — `instructions` registered, and the repo that owns the gate now runs it (2026-08-06, R-229) + +`instructions_gate.py` **lives in this repo's `scripts/`** and was registered in `controller_gates.py` +and `agent_gates.py` on the day it was written — but not in `repo_gates.py`. The reason was +mechanical: `felhom.eu/CLAUDE.md` was 227 effective lines against a 200 ceiling, and a +registered-but-failing gate refuses **every** push through `.githooks/pre-push`. So the one repo that +owns the check was the one place it did not run — the exact failure the R-29 gate census was created +to find. + +Fixed in the required order: **trim first, register second.** + +- `CLAUDE.md` **227 → 115 effective lines**, restructured into a core plus + `.claude/rules/{hub,website,manifests,docs}.md`, all `paths:`-scoped, all ≤60 effective lines. +- `repo_gates.py` gains gate 6, `--fast` safe. Six gates, all OK. + +**Two placements deviate from the spec's sketch, both to avoid rebuilding a failure class:** +- **Register discipline (`OPEN-ITEMS.md` first, `STATUS.md` is a view) stayed in the core**, not in + `docs.md`. It applies to every session that ships or decides something; behind a + `documentation/**` glob it would be invisible in exactly the code-shipping sessions where findings + get lost — which is how R-153/154/155 and R-156/157 were minted and lost. +- **The R-110 installer fence stayed in the core.** Its trigger is editing + `scripts/felhom-host-install.sh`, which none of the four fixed globs covers; behind `website/**` it + would never load for the one artifact that runs as root on a virgin box. Compressed to its + actionable clauses rather than relocated. + +**Scoping proven from the hook log, not the frontmatter** (`InstructionsLoaded`, +`~/.claude/instructions-loaded.jsonl`), in two fresh sessions so the negative control is clean: +reading `website/index.html` produced `website.md path_glob_match` and **no** `hub.md` line; reading +`hub/internal/api/handler.go` produced `hub.md path_glob_match` and **no** `website.md` line. + +**Trap worth keeping:** creating `.claude/rules/` mid-session does **not** arm it. The in-session +reads that followed produced no hook line at all — a directory whose instructions were already seeded +is not re-scanned. Same class as the settings-watcher caveat. A new rule file is only live in a +session started after it exists, which is why the proof above needed `claude -p`. + ## instructions_gate.py 1.0.0 — instruction files cannot silently regrow (2026-08-06, R-229) New shared gate, registered in `controller_gates.py` and `agent_gates.py`, never copied into a diff --git a/scripts/repo_gates.py b/scripts/repo_gates.py index 8ab4989..7433722 100644 --- a/scripts/repo_gates.py +++ b/scripts/repo_gates.py @@ -13,6 +13,14 @@ Gates, in order (all must pass; **non-zero exit on any failure**): 3. hub-confirm no native confirm()/prompt() in hub templates 4. manifest-bearer no bearer-shaped literal anywhere in manifests/ 5. reuse-refs every path cited by this repo's REUSE.md still resolves + 6. instructions CLAUDE.md length/versions/TEMPORARY, rule-file scoping, workspace-copy identity + +WHY 6 IS HERE AND WAS NOT (2026-08-06, R-229 deferred leg). instructions_gate.py LIVES in this +repo's scripts/ and was registered in the controller and agent runners on the day it was written — +but not in this one, because this repo's own CLAUDE.md was still 27 lines over the ceiling and a +registered-but-failing gate refuses every push through .githooks/pre-push. The file was trimmed +(227 -> 115 effective lines, core + .claude/rules/) and the gate registered in the same session. +A check that does not run in the place it applies is the exact failure the R-29 gate census found. WHY THIS FILE EXISTS (2026-08-02, closing R-29 leg (a) and half of leg (b)). @@ -49,6 +57,7 @@ GATES = [ ("hub-confirm", os.path.join(SCRIPTS, "hub_confirm_gate.py"), [], True), ("manifest-bearer", os.path.join(SCRIPTS, "manifest_bearer_gate.py"), [], True), ("reuse-refs", os.path.join(SCRIPTS, "reuse_refs_check.py"), [ROOT], True), + ("instructions", os.path.join(SCRIPTS, "instructions_gate.py"), [ROOT], True), ] VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}