Files
felhom-controller/REPORT.md
T

123 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REPORT — v0.114.0: agent-capability gate for coupled features (option-1) + publish-train rules (option-2)
**Date:** 2026-07-11 · **Version:** controller v0.114.0 (from v0.113.0) · **MinAgent:** — (the gate
itself works against agents 0.790.81 as they exist; that is the point) · **Agent/hub: UNTOUCHED.**
## What shipped
The 0.81/0.113 publish train's 9-minute controller-before-agent skew (Peti's box) answered at the
box level: the controller detects whether its agent supports a coupled feature (route probe on
`GET /netstorage/verify-status` — the route that shipped WITH the v0.81.0 coupled add semantics)
and refuses the NAS add up front with an honest Hungarian message instead of failing mid-pipeline
with a misleading rollback. Riding along: the publish-train conventions codified in
`felhom.eu/documentation/runbooks/publish-train-rules.md`.
## Baselines (verified live before work)
| Repo | main @ start | → main @ end |
|---|---|---|
| felhom-controller | `5d06ecf374` (v0.113.0) | `53ab971` (v0.114.0) |
| felhom.eu | `a487ab2fc4` | `4eaabb2` |
| felhom-agent | `1e60e88eb2` (v0.81.0) — read-only, NOT touched | unchanged |
## Commits
| Hash | Content |
|---|---|
| `d347dc4` | Commit 1 — agentapi typed `StatusError` (1.1) + `features.go` (Feature/SupportState/`featureProbes`/`SupportCache` TTL 5m/`Client.Supports`) + the add gate before the single-flight claim + T1T4, T6 |
| `50da732` | Commit 2 — `NetAddSupport` threaded to `storage_network.html` (banner on `no`; list+remove in every state; JS load guard) + T5 |
| `4a5c40b` | Pre-existing scheduling flake fixed in `TestBackupTier2Restore_DoubleClickRefused` (IsRunning flips before StopStack lands — wait for the first stop, then assert) |
| `d9da5a0` | Docs — CHANGELOG v0.114.0 (MinAgent header convention starts), README NAS gate paragraph, CONTEXT, REUSE rows, CLAUDE.md coupled-feature rule |
| `53ab971` | `[DEBUG]` gate-verdict log per add + `SupportState.String()` |
| felhom.eu `4eaabb2` | `documentation/runbooks/publish-train-rules.md` (the 4 standing gates, each citing its incident) + CONTEXT entry — pushed |
## Files touched (controller)
`internal/agentapi/client.go` (StatusError; `get` non-200 branch — same message text),
`internal/agentapi/features.go` + `features_test.go` (NEW),
`internal/web/server.go` (`netFeatures` field), `internal/web/netstorage_handlers.go` (gate +
`netAddOutdatedMsg` + `netAddSupport`), `internal/web/handlers.go` (`networkStoragePageData`),
`internal/web/templates/storage_network.html`, `internal/web/netstorage_gate_test.go` (NEW),
`internal/web/storage_network_template_test.go` (T5 + `NetAddSupport` in C8's data),
`internal/web/async_restore_test.go` (flake fix). Docs: CHANGELOG/CONTEXT/REUSE/CLAUDE/README.
## Verify-first (1.1) finding
`Client.get` returned a generic `fmt.Errorf("agentapi: GET %s: HTTP %d")` on non-200 — a 404 was
NOT typed. Added `StatusError{Path, Code}` returned by that branch (identical message text, so no
behavior shift elsewhere); classification uses `errors.As` + `Code==404` only. Pinned wire-level by
`TestClient_404IsTypedAndSupportsSaysNo` (httptest TLS + real leaf pin, routeless mux).
## Tests + red-proofs (every red-proof run → predicted failure observed → reverted; final tree clean)
Counts: web 76 → 80 test funcs; agentapi 28 → 33.
| Test | Scenario | Result | Red-proof (verbatim failure) |
|---|---|---|---|
| `TestNetAddGate_OldAgent_RefusedUpFront` (T1) | A | PASS — 412 + `"code":"agent_outdated"` + the §Part-3 message; job slot nil; 0 agent add calls; 0 removes; 0 registered; slot re-acquirable | RP1 gate disabled → `gate: got 200 want 412 ({"data":{"name":"media","started":true},"ok":true})` — the exact pre-gate WRONG behavior |
| `TestNetAddGate_CurrentAgent_UnchangedAndProbeCached` (T2) | B | PASS — two adds both `done`, registered, no rollback; probe count across both = 1 | RP2 cache disabled → `probe (verify-status) calls across two adds = 2, want exactly 1 (cached)` |
| `TestNetAddGate_ProbeIndeterminate_PassesThrough` (T3) | C | PASS — transport error AND 5xx: 200 `{started:true}`, job fails `agent_error` (the existing path), never `agent_outdated` | RP3 classify-any-error-as-No → both subtests: `indeterminate probe must pass the gate: got 412 ({"data":{"code":"agent_outdated"},"error":"Az ügynök frissítése szükséges…"})` — the false "too old" trap, caught |
| `TestSupports_Classification` (T4) | 2xx/404/wrapped-404/401/500/502/refused/timeout/**plain-error-with-404-text** → Yes/No/No/U/U/U/U/U/**U** | PASS | RP4 string-match mutant → `plain_error_with_404_text: Supports(agentapi: GET /netstorage/verify-status: HTTP 404) = 2, want 0` (RP3 also failed 6 T4 cases) |
| `TestStorageNetworkTemplate_AgentOutdatedBanner` (T5) | D | PASS — `no`: banner present, `ns-add-form` absent, list+Eltávolítás present; `yes`/`unknown`: form, no banner | RP5 conditional dropped → `the §Part-3 banner is missing on an old agent` + `the add form must NOT render on an old agent` |
| `TestSupports_CacheTTL` (T6) | warm-cache negative (calls stays 1), re-fire after TTL+1s, negative polarity cached, Unknown NEVER cached | PASS | covered by RP2 (`probe calls on a warm cache = 2, want 1`; `after TTL expiry = 3, want 2`; `negative verdict not cached (calls=2)`) |
| `TestClient_404IsTypedAndSupportsSaysNo` / `TestClient_SupportsYesOnLiveRoute` | wire-level 1.1 | PASS — real TLS+pin client: routeless mux ⇒ `*StatusError{404}``SupportNo`; live route ⇒ `SupportYes` | — (pins the verify-first finding) |
| `TestSupports_UnknownFeature` | table gap fails open, no probe fired | PASS | — |
Green gate `go build && go vet && go test ./...`: PASS per commit (template_id_gate + emoji_gate
green for the template commit). One unrelated failure surfaced under parallel load —
`TestBackupTier2Restore_DoubleClickRefused` — diagnosed as a PRE-EXISTING race in the test itself
(asserted StopStack count right after IsRunning flips; goroutine not yet parked), fixed in
`4a5c40b`; full suite green since.
## Deployed + verified
Guest 9201 (golden/bootstrap mechanism, built on 180, image pushed):
`gitea.dooplex.hu/admin/felhom-controller:0.114.0 Up (healthy)`; startup log clean; selfupdate:
`Current version 0.114.0 is up to date`. Agent on felhom-pve: 0.81.0 (untouched).
## Live validation (the supported branch; anti-F9 — the exact endpoint the UI invokes)
Method: `curl` from inside guest 9201 to the controller container (`http://172.17.0.2:8080` +
`Host: felhom.demo-felhom.eu`; the demo has no password so auth/CSRF do not apply) — the residual
vs a browser is client-side rendering only. Demo agent = 0.81 ⇒ the SUPPORTED branch. Done inside
one cache window.
1. **Gate-pass + real pipeline** — POST add `gatecheck114` (nfs, server 192.168.0.180 = reachable,
export `/srv/nonexistent-gate-check` = bogus): response `{"data":{"name":"gatecheck114",
"started":true},"ok":true}` (identical shape to 0.113.0); controller log
`[DEBUG] [web] netstorage add "gatecheck114" capability gate: netstorage_verify=yes` (the probe
verdict, visible); status poll → `failed` in ~2.5 s, category `nfs_export`, the exact merged
Hungarian message, real journal detail — byte-for-byte the 0.113.0 behavior.
2. **Nothing changed state** — share list `{"network_storage":[]}`; felhom-pve: 0 unit files;
the known residuals only (one `not-found failed` unit listing → `systemctl reset-failed`; the
empty `/mnt/felhom-drives/gatecheck114` dir → removed; both documented v0.113.0 observations,
re-confirmed, no new gaps). Host+guest now show only `felhom-usb`.
3. **Page render** — GET `/storage/network`: `ns-add-form` present (2 occurrences), the banner text
`régebbi verziójú` 0 occurrences ⇒ the SupportYes render; list intact.
## NOT live-validated
- **The `agent_outdated` refusal branch (SupportNo)** — test-proven only (T1/T4/T5 with red-proofs
RP1/RP3/RP4/RP5). The demo agent is current (0.81.0); downgrading a live agent to prove a banner
is not justified. Stays on this list until a future old-agent window shows it naturally.
- Concurrent double-probe on a cold cache (documented harmless: one extra cheap GET) — not raced live.
## STOP honored
NOT published (Day-0 manifest untouched, no `publish-agent.sh`, no golden bake), NO floor movement,
NO hub changes, NO agent changes, Peti untouched on 0.113.0. The gate is inert protection until the
next train. Note: the `0.114.0` registry tag was rebuilt once (the `[DEBUG]` gate-log commit) before
any vouch existed — demo-only, nothing references it.
## Observations (out of scope, documented NOT acted on)
- **Hub floor-UI separation** (decoupling the floor field from the manifest-save screen / staging
it) — its own task; the rules doc only references it (rule 2's "save LAST" is the interim control).
- **Agent version-in-envelope** — the documented future upgrade of `Supports` (prefer version
comparison over route probing); the `featureProbes` table comment + rules doc carry the pointer.
- `Client.Supports` exists as the spec'd public API; the web layer drives the same `SupportCache`
machinery through its `netAgent` seam (`Server.netFeatures`) so unit tests fake the probe — one
code path in production and tests.
- The known v0.113.0 remove-residuals (failed-unit listing needs `reset-failed`; empty mountpoint
dir lingers) reproduced on the failed-add rollback path too — same cleanup, still LOW.