docs: REPORT v0.115.0 + CONTEXT (version-aware Supports, DSM guidance, Q1c FAIL)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
+16
-1
@@ -7,7 +7,22 @@
|
||||
>
|
||||
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
|
||||
|
||||
Last updated: 2026-07-11 (v0.114.0 — agent-capability gate for coupled features)
|
||||
Last updated: 2026-07-11 (v0.115.0 — version-aware Supports + DSM-validated guidance; pairs with agent v0.82.0 + hub v0.45.0)
|
||||
|
||||
> **2026-07-11 — v0.115.0: version-aware Supports + DSM-validated NAS guidance (LIVE on 9201, pairs
|
||||
> with agent v0.82.0 + hub v0.45.0). MinAgent: 0.81.0.** Capability detection now compares the agent
|
||||
> version from agent v0.82.0's `X-Felhom-Agent-Version` header (`Client.noteAgentVersion` captures it
|
||||
> on every response, strict semver; `features.go featureMinAgent` table + version-first `Supports`)
|
||||
> instead of route-probing — the probe stays as the fallback for header-less (≤0.81) agents, so
|
||||
> nothing changed for Peti's box. THE one comparator moved to `internal/util/version.go` (selfupdate
|
||||
> aliases it). Part A DSM spike (real DSM 7.2 via virtual-dsm) validated the consumer recipes E2E; the
|
||||
> NAS-page NFS guidance gained the verified Synology steps (File Services → NFS → **NFSv4.1**; "Map
|
||||
> all users to admin"; `/volume1/<share>`); caveat narrowed to QNAP-only. Live-checked on demo: a real
|
||||
> add shows `capability gate: netstorage_verify=yes` via the version compare, zero probes. **Q1c
|
||||
> (Part E, supervised) FAILED**: a NAS automount trigger does NOT survive a guest reboot (guest sees
|
||||
> an empty dir; agent has no network-mount reassert) — fix is felhom-agent's, spec'd at
|
||||
> felhom.eu/documentation/backlog/FOLLOWUP-nas-automount-guest-reboot-reassert.md (controller
|
||||
> health-cross-check follow-on noted there). NOT published (agent 0.82 demo-only; Peti 0.81).
|
||||
|
||||
> **2026-07-11 — v0.114.0: agent-capability gate (option-1) + publish-train rules (option-2).**
|
||||
> Answer to the 0.81/0.113 train's 9-minute controller-before-agent skew (Peti's box): the box now
|
||||
|
||||
@@ -1,122 +1,61 @@
|
||||
# REPORT — v0.114.0: agent-capability gate for coupled features (option-1) + publish-train rules (option-2)
|
||||
# REPORT — v0.115.0: version-aware Supports (agent version channel) + DSM-validated NAS guidance
|
||||
|
||||
**Date:** 2026-07-11 · **Version:** controller v0.114.0 (from v0.113.0) · **MinAgent:** — (the gate
|
||||
itself works against agents 0.79–0.81 as they exist; that is the point) · **Agent/hub: UNTOUCHED.**
|
||||
**Date:** 2026-07-11 · **Version:** controller v0.115.0 (from v0.114.0) · **MinAgent:** 0.81.0 (the
|
||||
version channel FALLS BACK to the probe — agent 0.82 is NOT required). Pairs with agent v0.82.0 +
|
||||
hub v0.45.0. Part B of the NAS/coupling backlog-clear task (composite A–F; this repo = B).
|
||||
|
||||
## What shipped
|
||||
## Baselines → 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 + T1–T4, 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) |
|
||||
| Repo | main @ start | → shipped | Version |
|
||||
|---|---|---|---|
|
||||
| `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 | — |
|
||||
| felhom-controller | `ac6adaaa` (v0.114.0) | `b6842a4` | **v0.115.0** — live on 9201 |
|
||||
| felhom-agent | `1e60e88` (v0.81.0) | `fa9c7fe` | v0.82.0 — demo-deploy (version channel) |
|
||||
| felhom.eu | `4eaabb2` | (A findings + hub train) | DSM spike + hub v0.45.0 |
|
||||
|
||||
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.
|
||||
## What shipped (files)
|
||||
|
||||
`internal/agentapi/client.go` (`noteAgentVersion` on all four Do sites — passive capture even on
|
||||
404/errors; strict bare-semver validation; `AgentVersion()`), `internal/agentapi/features.go`
|
||||
(`featureMinAgent` table + `AgentVersionReporter` + version-first `Supports`, probe fallback
|
||||
unchanged), NEW `internal/util/version.go` (THE one comparator, moved verbatim from selfupdate which
|
||||
now aliases it), `internal/selfupdate/version.go` (aliases), `internal/web/templates/storage_network.html`
|
||||
(DSM-validated NFS guidance from Part A). Tests: `internal/agentapi/features_version_test.go`,
|
||||
`storage_network_template_test.go` (unchanged content check).
|
||||
|
||||
## Tests + red-proofs (mutation → predicted failure → reverted → green)
|
||||
|
||||
| Test | Red-proof mutant | Observed failure |
|
||||
|---|---|---|
|
||||
| `TestSupports_VersionKnown_ComparesWithoutProbe` (0.82/0.81/1.0/0.100→Yes; 0.79/0.80.9→No, no probe) | version short-circuit dropped | `version-known path must NOT probe (ver=0.82.0, probes=1)` |
|
||||
| `TestSupports_GarbageOrNoVersion_ProbeFallback` (`""`/dev/`0.82`/rc1/`evil;`… → exactly one probe, cached) | trust unparseable header as "too old" | `unusable version "dev" must fall back to EXACTLY one probe, got 0` |
|
||||
| `TestSupports_NonReporterProber_Unchanged` | — | (non-reporter probers keep v0.114.0 behavior) |
|
||||
| `TestVersionComparator_Table` (numeric-not-lexicographic, v-prefix, pre-release rejected) | — | — |
|
||||
| `TestClient_VersionHeaderWins_WireLevel` / `TestClient_GarbageHeaderIgnored_WireLevel` (real pinned client) | — | header wins over a routeless agent; garbage ignored at capture |
|
||||
|
||||
Green gate `go build && go vet && go test ./...` PASS; `template_id_gate.py` + `emoji_gate.py` green.
|
||||
|
||||
## 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).
|
||||
9201 (golden/bootstrap): `felhom-controller:0.115.0 Up (healthy)`. Live check (real add endpoint,
|
||||
agent 0.82 = the SUPPORTED branch): a bogus-export add returned the categorized `nfs_export`
|
||||
failure + full rollback, and the debug log shows `capability gate: netstorage_verify=yes` — the
|
||||
verdict now sourced from the **version compare** (agent 0.82 header), zero probe calls. The probe
|
||||
fallback is regression-tested (T3/non-reporter) and is Peti's live path (his agent stays 0.81, no
|
||||
header → probe).
|
||||
|
||||
## Live validation (the supported branch; anti-F9 — the exact endpoint the UI invokes)
|
||||
## Part A (DSM spike) — the guidance edits landed here
|
||||
|
||||
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.
|
||||
Real DSM 7.2 (virtual-dsm) validated the consumer recipes end-to-end through the v0.114 pipeline:
|
||||
NFS "Map all users to admin" = Route A PASS (hardlink incl.); squash-off = `not_writable` refusal;
|
||||
plain-user SMB PASS **with working hardlinks** (the Q5 caveat CLOSED for Synology); wrong-password/
|
||||
wrong-share classify identically (`smb_auth`/`smb_share`). The NFS guidance gained the verified
|
||||
Synology menu path (File Services → NFS → **NFSv4.1**; NFS Permissions rule; `/volume1/<share>`);
|
||||
the "útmutató készül" caveat narrowed to **QNAP only** (no emulator — nothing fabricated). Record:
|
||||
`felhom.eu/documentation/audits/SPIKE-nas-dsm-2026-07-11.md`.
|
||||
|
||||
## 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.
|
||||
- The `agent_outdated`/held branches on a real <MinAgent agent (no such box exists — test-proven).
|
||||
- Q1c restart survival: tested in Part E and **FAILED** (empty-dir; not a controller bug) — the fix
|
||||
is felhom-agent's, spec'd at `felhom.eu/documentation/backlog/FOLLOWUP-nas-automount-guest-reboot-reassert.md`.
|
||||
A controller follow-on (health cross-check the GUEST-visible mount) is noted there.
|
||||
|
||||
Reference in New Issue
Block a user