docs: SPIKE — A1 pool-membership read for the stale-lock reaper

Live-probed on felhom-pve under the PRODUCTION scoped token vs root:
enumeration IS pool-filtered (T1: token sees [9201] of 4 guests); pool read
403s naming Pool.Audit (T2); Pool.Audit@/pool/felhom alone suffices (T3,
throwaway identity, torn down); /cluster/resources withholds the pool field
without Pool.Audit (T7); local ownership records all partial (T5).

Recommendation for the A1 impl spec (now unblocked): Pool.Audit added to
FelhomAgentGuest + GET /pools/felhom cross-check in staleLockController,
fail-safe skip on read failure. Appendix: committed-secrets rotation
micro-runbook (operator follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-03 13:00:00 +02:00
parent 9282d60f96
commit 5e3dfcb714
4 changed files with 305 additions and 97 deletions
+34 -97
View File
@@ -4,106 +4,43 @@
---
# REPORT — hub v0.31.0 critical-severity fix + Felhom skills + CLAUDE.md refresh (consolidated)
## SPIKE — A1 pool-membership read for the stale-lock reaper (2026-07-03)
**Date:** 2026-07-03 · **Class:** implementation (hub v0.31.0, deployed) + docs/skills across all repos
**Class:** SPIKE (docs-only; no production code, no felhom-agent commit, no live ACL/agent change).
**Deliverable:** `documentation/audits/SPIKE-a1-pool-membership-read-2026-07-03.md`.
## 1. Baselines & commits
Empirically answered the three open A1 mechanism questions on live felhom-pve, probing under the
**production** scoped token `felhom-agent@pve!agent` (secret read on-host, length-only recorded)
vs root as oracle:
All four repos matched the task's expected baselines (pulled clean).
- **T1 (crux):** `GET /nodes/demo-felhom/lxc` under the scoped token returns **[9201] only**; root
sees [9001, 9100, 9201, 9999]. LXC enumeration IS pool-filtered — the reaper's scan is already
ownership-scoped *as deployed*, so A1 is latent, re-armed only by a broad-token redeploy.
- **T2:** `GET /pools/felhom` under the token → **403 "Permission check failed (/pool/felhom,
Pool.Audit)"**. `GET /pools` → 200 `[]` (silently filtered, not an error).
- **T3:** throwaway `felhom-probe@pve!probe` with ONLY `Pool.Audit`@`/pool/felhom` → **200 with
members** — minimal priv delta proven. Bonus (T3b): a token with no VM.Audit gets **200 + empty
list** from the LXC index (mis-scope is silent, not an error). Full teardown verified (0 residue).
- **T4:** root `pvesh get /pools/felhom` works but would be a 4th `proxmox.Privileged` fence
exception — rejected.
- **T5:** local ownership records (guest-binds.json, reconcile journal, guests/ dir, installer
state.json) are all **partial** — journal's newest vmid (9310) no longer exists; adopted or
drive-less guests are absent — rejected as reaper gate.
- **T6:** live foreign-guest reap deliberately NOT run (needs broad-token redeploy); deferred to
the named unit test. **T7 (extra):** `/cluster/resources` withholds the `pool` field without
`Pool.Audit` — no zero-ACL-change API path exists.
| Repo | Baseline | Commits pushed to `main` |
|---|---|---|
| felhom.eu | `d331eb26d1` | `b5f0050` (fix+tests+REUSE), `4a18306` (manifest→0.31.0), `ad61e96` (render test), + the docs/skills commit carrying this REPORT |
| felhom-controller | `eda33400da` | `a518023` (CLAUDE.md refresh) |
| felhom-agent | `2a024b6039` | `3e760a5` (CLAUDE.md refresh) |
| app-catalog-felhom.eu | `c5a3d1b15b` | `15cdf7b` (CLAUDE.md expansion) |
**Recommendation (for the now-unblocked A1 impl spec):** add `Pool.Audit` to `PVE_PRIVS_GUEST`
(`scripts/felhom-host-install.sh` L183, granted at `/pool/felhom`) + intersect `ListLXC` with
`GET /pools/felhom` members inside `staleLockController.Guests()`; a pool-read failure propagates
to the existing "guest list unavailable — skipping recovery" fail-safe. Ship agent + `--rescope-acl`
together; optionally surface pool-read in the capability probe. Named tests:
`TestStaleLock_ForeignGuestNotReaped`, `TestStaleLock_PoolGuestStillReaped`,
`TestStaleLock_PoolReadFails_SkipsAll`.
## 2. Part 1 — hub v0.31.0 (the critical-severity fix)
**Appendix folded in:** committed-secrets rotation micro-runbook for the 3 live `stringData` blocks
in `manifests/felhom.secret.yaml` (healthchecks-config / umami-config / gitea-creds) — rotate
upstream first, out-of-band `kubectl create secret`, then de-git; operator follow-up, no execution.
**Change:** `handleEvent` severity switch gains `"critical"` (unknown values still coerce to `"info"`,
exact-match lowercase); `severityLabels["critical"] = "Kritikus hiba"` added; dashboard consumer
gains `EventCriticals`; critical badge renders FIRST in dashboard + customer-page count chains;
`style.css` defines the previously-referenced-but-undefined `.severity-critical` (`--crit` tokens)
and `.severity-ok` (neutral). Frozen legacy `/notify` path and `allowedEventTypes` untouched.
**Tests (new `hub/internal/api/event_test.go` — first tests on /event):**
- A: critical preserved to store — **companion red-proof run:** with the one-line fix reverted the
test FAILED with `stored severity = "info", want "critical"`; fix restored → PASS.
- B: severity `"banana"` → stored `"info"`. C: unknown event_type → 400 + store empty. Auth: 401.
- D: `TestTemplates_DashboardCriticalBadge` (render_test.go) asserts the rendered HTML contains the
`severity-critical` count badge ordered before the error badge. **Method note:** UI verification
used the Go render test — Chrome verification stopped at the operator login (password entry is
out of CC's scope).
- Green gate: `go build ./... && go vet ./... && go test ./...` — all green.
**Deploy (GitOps):** image `felhom-hub:0.31.0` built+pushed on 180; `manifests/hub.yaml` bumped;
ArgoCD app `felhom` hard-refresh + sync → `Synced`/`Healthy`, rollout complete, pod image
`...felhom-hub:0.31.0`, clean startup logs.
**Live probe (real pipeline):** POSTed one `event_type:"test"`, `severity:"critical"` event from
guest 9201 using its own controller.yaml credentials (key never echoed — length-only). Result:
HTTP 200; hub log `Event from demo-felhom: test (critical)` (stored severity critical, not info);
dispatcher sent the operator email (`Test email sent to nagyfenyvesi.viktor@gmail.com`).
**REUSE.md** §1 gotcha + §3 trap row updated in the same commit as the fix;
`reuse_refs_check.py``OK [felhom.eu]: 31 cited paths, all exist`.
## 3. Part 2 — skills
Three skills authored in `felhom.eu/skills/` (each SKILL.md ≤160 lines, pushy trigger descriptions):
`felhom-build-deploy`, `felhom-ui-design`, `felhom-testing`. Content verified against live
source/infra before writing (see §5). New `scripts/install_skills.py`: idempotent, junction-first
(`mklink /J`) with copy fallback + re-run reminder.
**Install mode: JUNCTION** — created for all three; second run detected them and no-op'd.
**Discovery (Scenario E):** fresh `claude -p` session listed all three:
```
felhom-build-deploy
felhom-testing
felhom-ui-design
```
Bonus trigger probe: "which skill to deploy a new hub version?" → answered `felhom-build-deploy`
with the correct first command (`./build.sh <VER> --push` on 180).
## 4. Part 3 — CLAUDE.md refresh
Standing rule adopted everywhere: **CLAUDE.md carries no version-pinned current state** (that lives
in CONTEXT/CHANGELOG/REUSE); deep runbooks live in the skills, CLAUDE.md keeps a summary table + pointer.
| File | Diffstat | Headlines |
|---|---|---|
| felhom-controller/CLAUDE.md | +119/294 (338→~160 lines) | full 30-package map (was 7); stale `/opt/docker` demo deploy replaced with verified 9201 bootstrap flow; hub build section deleted; patterns pruned to 6 invariants |
| felhom-agent/CLAUDE.md | +86/71 | "Current: v0.31.0" narrative + slice history deleted (30 versions stale); layout completed (+8 packages, opsign); deploy facts re-verified on felhom-pve |
| felhom.eu/CLAUDE.md | +73/58 | "Hub — current state (v0.7.x)" (23 versions stale) → version-free architecture section; GitOps rules kept incl. live-image-lag note; skills section added |
| app-catalog CLAUDE.md | +26/5 | stub → ~30-line proper file (purpose, deploy contract, pointers) |
| E:\git\CLAUDE.md (local) | +8 | Skills section only |
## 5. Commands/facts re-verified against live infra (stale findings)
- Guest 9201 deploy mechanism confirmed live (`/etc/felhom-controller-image` = 0.98.3 tag,
`felhom-controller-bootstrap.service` enabled, container healthy) — the OLD controller CLAUDE.md
still documented the pre-re-platform `/opt/docker` compose deploy for node 1: **stale, replaced**.
- Agent service confirmed on felhom-pve: `User=felhom-agent`,
`ExecStart=/usr/local/bin/felhom-agent --config /etc/felhom-agent/agent.json`, live 0.61.0 — the
OLD agent CLAUDE.md claimed v0.31.0 current: **stale, removed**.
- Build dirs on 180 confirmed: `~/build/{felhom-agent,felhom-controller,felhom-hub}` + `~/git`
clones; Go present.
- Design tokens confirmed byte-identical across website site.css / hub style.css / controller
style.css `:root` blocks (values inlined in the ui-design skill).
- Hub GitOps flow re-verified by executing it (this deploy). Red-proof citation
(`dispatcher_test.go` ~L2749) verified in source.
- Website displays no hub version anywhere (checked) — no website change needed.
## 6. Observations (noticed, NOT acted on)
- The hub severity badge classes predating this task (`.severity-error/-warning/-info`) use legacy
raw hexes, not the v2 `--warn/--crit` tokens — left alone per the no-restyle rule; a token
migration is a candidate D-series follow-up.
- The hub login page appears unstyled relative to the v2 design system (default-blue button, white
background) — pre-existing, out of scope.
- `manifests/felhom.secret.yaml` inline credentials (from the REUSE report) remain an open operator
follow-up.
- The controller currently emits no `critical`-severity events; the fix future-proofs the channel
(hub-internal `host_disk_critical`/`storage_fill_critical` already flow at critical severity —
they bypass ingest).
**Cleanup:** probe user/token/role removed and verified absent; no throwaway guest needed (three
real non-pool guests existed); production roles/token/agent untouched; secrets never persisted.