Files
admin 3a9dd81e18
gates / gates (push) Successful in 8s
docs+gate: felhom.eu/CLAUDE.md becomes core + path-scoped rules; instructions gate registered (R-229)
227 -> 115 effective lines, split into .claude/rules/{hub,website,manifests,docs}.md, and
repo_gates.py gains gate 6. Trim first, register second: a registered-but-failing gate refuses
every push through the pre-push hook, which is why this repo -- the one that OWNS the gate --
was the only one not running it.

Register discipline and the R-110 installer fence deliberately stayed in the core; both have
triggers no fixed glob covers, and scoping them would have rebuilt the failure class they exist
to prevent.

Scoping proven from the InstructionsLoaded hook log in two fresh sessions, not from frontmatter.
2026-08-06 10:41:42 +02:00

3.9 KiB
Raw Permalink Blame History

paths
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 (30m1h 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.