CC uses its hub token: live C3 proof (floor no-op 303 floor_set, vouch 403, session-only 403); R-1051 closed; skill floor step + prune script on the token; STATUS: change the hub password
gates / gates (push) Successful in 6m33s

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-15 13:10:00 +02:00
parent 2f1f6c8052
commit 5d78569bac
8 changed files with 69 additions and 32 deletions
@@ -464,7 +464,7 @@ cannot add a custom header without a CORS preflight, which the hub never answers
the route switch, so it covers every route at once (38 state-changing routes + an unknown path in
`r135_csrf_test.go`); `/login` and the public `/bind/<token>` stay exempt (no operator session to ride; the bind token
is the capability). The other choice — dropping browser-usable Basic auth entirely — was not taken: CC's headless runs
and the runbooks drive the hub with Basic auth, and the header costs them one flag.
and the runbooks drive the hub with Basic auth, and the header costs them one flag. *Since v0.152.0 CC's scripts use a scoped token instead (§17); Basic stays for the operator's own scripts.*
### 16.2 Secrets at rest in `hub.db` (R-133, R-821)
@@ -0,0 +1,18 @@
1 floor no-op save: (303, '/configuration?flash=floor_set')
2 vouch attempt: (403, '', 'Forbidden: missing scope vouch:write')
3 session-only (customer page): (403, '', 'Forbidden: session only — never by a token')
3b session-only (reveal): (403, '', 'Forbidden: session only — never by a token')
4 read Configuration: 200
DB ('min_controller_version', '0.313.0')
DB ('min_controller_version_declared_min_agent', '0.313.0=0.155.0')
DB token ('cc-releases', '2026-10-15T11:07:33Z', '10.42.0.1')
2026/10/15 13:06:06 [INFO] access token "cc-releases" created from 192.168.0.110: scopes overview:read,floor:read,floor:write,customers:read,hosts:read,offsite:read,offsite:write,os:read,os:write, expires 2027-04-13T11:06:06Z
2026/10/15 13:07:32 [INFO] Global controller-version floor set to "0.313.0" (declared MinAgent "0.155.0")
2026/10/15 13:07:32 [WARN] global floor 0.313.0 does NOT move customer demo-hp: its own floor 0.311.0 wins (set 2026-10-14 15:18 UTC) — clear it on the customer page to let the global floor apply
2026/10/15 13:07:32 [INFO] Operator email sent for /floor_raise_skipped
2026/10/15 13:07:33 [INFO] access token "cc-releases": POST /configuration/global-floor → 303
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: missing scope vouch:write — POST /configuration/artifacts → 403
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: session only — never by a token — GET /customers/demo-felhom → 403
2026/10/15 13:07:33 [WARN] access token "cc-releases" refused: session only — never by a token — POST /hosts/demo-hp-bb76ea/reveal-recovery-credential → 403
2026/10/15 13:07:34 [INFO] access token "cc-releases": GET /configuration → 200
skill recipe run verbatim 2026-10-15 ~13:20 Budapest (no-op 0.313.0/0.155.0): HTTP/1.1 303, Location: /configuration?flash=floor_set; header file removed
+1
View File
@@ -30,6 +30,7 @@
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-1051** | **CC could not raise the global floor without the operator's own password.** (P3) | CLOSED 2026-10-15 — hub v0.152.0 deployed (operator „yes"); the operator created the token „cc-releases" (2026-10-15 13:06, wider than the task's two scopes: also customers/hosts read, offsite and os read+write — the operator's choice). Live with the token: a no-op floor save 0.313.0/MinAgent 0.155.0 → 303 `flash=floor_set`, read back from the hub DB with its WAL; the vouch → 403 `missing scope vouch:write`; the customer page and a recovery-credential reveal → 403 `session only`. The `felhom-build-deploy` skill's floor step and `gitea-image-prune.sh` now use the token. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `audits/hub-tokens-2026-10-15/C3-token-live.txt`, `deploy.txt`; `05` §17; `09` §3 decision 251 |
| **R-952** | **Nothing failed when a new hub route was added without deciding who may call it.** (P3) | CLOSED 2026-10-15 — **built in hub v0.152.0**, both surfaces: `/api/v1` — `internal/api/routes.go` names each route's caller (public with a reason, customer, host, global, passphrase, appliance, recovery), pinned to the switch from source; every non-public route with no credential, for a household and host that exist, answers 401/403. Web — `internal/web/routes.go` classifies every route for access tokens, pinned the same way. Full text: `git show e92501f5:documentation/backlog/OPEN-ITEMS.md` | `TestAPIRouteTable_MatchesSwitch`, `TestAPIRoutes_NoCredential`, `TestRouteTable_MatchesServeHTTPSwitch` (each red-proved, `REPORT-hub-tokens.md`); `05` §17.3–17.4 |
## 2026-10-15 (afternoon) — the floor reached 0.313.0
+1 -2
View File
@@ -260,7 +260,7 @@ stopping line that lies.
| **R-1049** | Monitoring & notifications | P3 | **A pulled drive mails the operator „unexpectedly disconnected” (error), and nothing ever mails that it came back.** MEASURED 2026-10-14 night (`audits/media-drive-events-2026-10-14/K8-hub-mails.txt`, the hub DB with its WAL from the running pod, and the mailbox as the second channel): two pulls → two `storage_disconnected` mails; the hub RECEIVED both `storage_reconnected` and both `health_recovered` (severity info) and logged no notification for any of them — they are not in `recoveredPairedDownTypes` (`hub/internal/notify/dispatcher.go`), so the severity gate drops them. The R-97a shape: told it broke, never told it healed — the operator cannot tell a returned drive from one still gone without opening the hub. **BUILT 2026-10-15 — hub v0.151.0 (`12552aca`): both pairs in `recoveredPairedDownTypes`, cooldowns ended by the next loss (`TestR1049_DriveBackMailsBothEdges`, red-proved). Not deployed: the operator's „yes” first (decision 162); the image build waits for it too.** | **WAITING-ON-OPERATOR** — hub v0.151.0 code pushed, not built, not deployed | the operator's „yes” to the hub deploy | On the „yes”: build + deploy hub v0.151.0; read a „came back” mail on the next pull | CC |
## Hub & operator — 13 rows (P3 7, P4 6)
## Hub & operator — 12 rows (P3 6, P4 6)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -275,7 +275,6 @@ stopping line that lies.
| **R-844** | Hub & operator | P4 | **The household's OS-update line exists only on the hub's customer timeline.** 2026-10-04: the box itself has no event surface for agent results (the controller UI shows no timeline), so `os_update_applied` is a hub customer event (info: recorded, never mailed). Its stored text is the hub's English sentence; the hu/en bundle text (`mail.event.os_update_applied`) is used only if it is ever mailed. Fix direction: a controller-side line (the controller already polls the agent's local API) when the box gets a household timeline. `audits/os-guest-lane-2026-10-04/partG/hub-customer-timeline-demo-hp.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A DESIGN** — a household timeline on the box does not exist yet. | — | — | CC |
| **R-1002** | Hub & operator | P4 | **A box the household switched off reads ONLINE on the hub's hosts page for its first 45 minutes** (seen live 2026-10-13, R-998 proof, `audits/poweroff-2026-10-13/B-hub-watch.log`: the „Power: Kikapcsolva…" line at 18:53, the KIKAPCSOLVA badge only at 19:38, when the host report turned stale). The customer page read „warn" in part of that gap (seen, not captured). No alarm and no mail fired; the operator page is only slow to say it. | **READY** — filed 2026-10-13 | — | CC: let `hostDisplayStatus` read `deliberate_off_at` before staleness, with a render test per state; next hub release | CC |
| **R-1011** | Hub & operator | P3 | **The Day-0 „Agent" list offers step-bundle packages (`0.156.1-step1`, `0.152.0-step1` …) as agent versions; one has no agent binary, so the hub shows the bundle JSON's sha (`FileSHA256` falls back to the first file) and a vouch would point fresh installs at a package with no binary.** Seen 2026-10-14 (`/configuration`: two „0.156.1" entries, `ac83ea41…` is the step bundle). | **READY** — filed 2026-10-14 | — | CC: list only versions whose package holds the `felhom-agent` file (refuse a missing preferred file instead of falling back), with a test; next hub release | CC |
| **R-1051** | Hub & operator | P3 | **CC cannot raise the global floor without the operator's own password.** `09` §3 decision 250 lets CC raise the floor after the three test boxes, but the only route was the operator page with the operator password. Operator ruling 2026-10-15 11:38 (`09` §3 decision 251): scoped tokens; until CC's token works, the password for the floor only. | **BLOCKED** — hub v0.152.0 DEPLOYED 2026-10-15 (`05` §17: tokens with groups, CC = `overview:read` + `floor:write`) | the operator creates „cc-releases" and saves it to `~/.felhom-hub/cc-token` | A no-op floor save with the token, read back; a vouch attempt → 403; a session-only route → 403; then close | operator |
| **R-1056** | Hub & operator | P4 | **Five operator routes fit no token group, so they are session only until the operator places them:** apps reset-telemetry and dismiss-issues, the Configuration asset re-seed, the off-site WireGuard endpoint save, the off-site abandon-cancel (`05` §17.3). Nothing needs them by token today. | WAITING-ON-OPERATOR — filed 2026-10-15 by the token build | operator decision | Place each in a group, or leave session only | operator |
@@ -246,7 +246,7 @@ The full 0.188.0 run, with the observables: `documentation/audits/tester-gate-go
When the Gitea volume fills, `~/git/misc-scripts/gitea-image-prune.sh` (repo `admin/misc-scripts`) prunes old versions.
**Dry-run first, always** (`./gitea-image-prune.sh --all prune`, then `--type generic --all prune`): it keeps the newest
20 of each package plus every version in use — the floor, the vouched golden, the vouched agent and `min_agent` (read
from the hub with `HUB_PW`), the images the vouched golden baked (its `bake.log` here), the hub `manifests/hub.yaml`
from the hub with the scoped token `HUB_TOKEN`, `floor:read`), the images the vouched golden baked (its `bake.log` here), the hub `manifests/hub.yaml`
runs — and prints each with its reason. It refuses when that list cannot be read. **`--apply` is a person's act**,
after reading the dry-run; nothing schedules it. The August 2026 `--keep 7` run (HM-024) predates the rule.