diff --git a/CHANGELOG.md b/CHANGELOG.md index edf73bc..71db6e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ ## Changelog +### v0.114.0 — agent-capability gate for coupled features (2026-07-11) — MinAgent: — + +Box-level backstop for the publish-train ordering discipline (incident: the 0.81/0.113 train's +9-minute controller-before-agent skew on Peti's box — RUNBOOK-publish-0.81-0.113-2026-07-11): the +controller now detects whether its agent supports a coupled feature and refuses that feature up +front, instead of failing mid-pipeline with a misleading rollback. No agent or hub changes; works +against agents 0.79–0.81 as they exist. (Retroactive note: v0.113.0's effective MinAgent was +0.81.0 for the NAS add — this release is the machinery that makes such coupling self-protecting. +Header convention from here on: coupled releases declare `MinAgent: X.Y.Z` on this line.) + +- **agentapi typed status (1.1):** non-2xx GETs surface as typed `*StatusError{Path,Code}` (same + message text as the old formatted error) — the probe keys on `Code==404` via `errors.As`, never + string matching. +- **`internal/agentapi/features.go`:** `Feature`/`SupportState` + `featureProbes` table (one row: + `netstorage_verify` → `GET /netstorage/verify-status`, the route that shipped WITH the coupled + add semantics in agent v0.81.0) + `SupportCache` (TTL 5 min, Yes/No cached, Unknown NEVER cached + or refused) + `Client.Supports`. 2xx ⇒ Yes; 404 ⇒ No; transport/timeout/401/5xx ⇒ Unknown — an + agent problem is never claimed as "too old". +- **Add gate:** `handleNetStorageAdd` refuses on `SupportNo` BEFORE the single-flight claim — + HTTP 412, machine code `agent_outdated`, message "Az ügynök frissítése szükséges ehhez a + funkcióhoz — a frissítés megérkezése után próbáld újra." `SupportUnknown` passes through to the + existing agent-error paths. `remove`/`list`/health are NOT gated — old shares stay manageable. +- **Settings page:** `NetAddSupport` (yes/no/unknown, short 2 s probe budget + cache) — `no` swaps + the add form for the honest banner; the share list + remove render in every state. +- **Tests:** T1 gate refusal (job never starts, slot never claimed, zero agent calls), T2 unchanged + happy path + warm-cache NEGATIVE assertion (probe count stays 1 across two adds), T3 + indeterminate-never-refuses, T4 classification incl. the string-match trap case, T5 banner + render, T6 TTL re-fire, wire-level 404-typing through the pinned client. Red-proofs RP1–RP5 run + + reverted (recorded in REPORT.md). +- Also: fixed a scheduling flake in `TestBackupTier2Restore_DoubleClickRefused` (pre-existing). +- **Docs:** publish-train rules codified at `felhom.eu/documentation/runbooks/publish-train-rules.md` + (manifest-before-floor; floor field LAST — the DB row overrides env and acts immediately; + MinAgent fleet gate; this gate as backstop). + ### v0.113.0 — NAS verify-before-commit + page redesign + protocol-honest guidance (2026-07-11) Kills the "bogus share sits at Készenlét forever" bug: `POST /api/storage/netstorage/add` now diff --git a/CLAUDE.md b/CLAUDE.md index b0290ae..d17d9c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,6 +88,10 @@ Per-package helpers/seams/traps: **`REUSE.md`** (maintained same-commit as helpe skill; templates must pass `controller/scripts/template_id_gate.py` + `emoji_gate.py`. - Testing doctrine (non-hollow tests, red-proofs, seams): use the `felhom-testing` skill. - Update `REUSE.md` if you added/changed/deprecated a shared helper or pattern (same commit). +- **Coupled features** (controller behavior that depends on a specific agent version): add a + `featureProbes` table row in `internal/agentapi/features.go` + a `Supports` gate call at the + feature's entry point; declare `MinAgent: X.Y.Z` in the CHANGELOG entry header. Rules: + `felhom.eu/documentation/runbooks/publish-train-rules.md`. > **In every repository where you make a change, update both files in that repo:** > - **`CHANGELOG.md`** — cumulative log, newest on top. diff --git a/CONTEXT.md b/CONTEXT.md index 509c399..9e0be0f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,23 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-07-11 (v0.113.0 — NAS verify-before-commit + page redesign; pairs with agent v0.81.0) +Last updated: 2026-07-11 (v0.114.0 — agent-capability gate for coupled features) + +> **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 +> protects itself. `internal/agentapi/features.go` — `Supports(Feature)` route-probes the agent +> (`GET /netstorage/verify-status` = the v0.81.0 coupling signal; typed `StatusError` 404 ⇒ No, 2xx +> ⇒ Yes, transport/5xx ⇒ Unknown NEVER refused; `SupportCache` TTL 5m both polarities, Unknown +> uncached). `handleNetStorageAdd` refuses on No BEFORE the single-flight claim (412 + +> `agent_outdated` + honest Hungarian message); settings page swaps the add form for a banner +> (list/remove untouched in every state). remove/list NOT gated. NO agent/hub changes; NOT +> published, floor untouched, Peti stays 0.113.0 — the gate is inert protection until the next +> train. Rules codified: `felhom.eu/documentation/runbooks/publish-train-rules.md` (manifest before +> floor; floor field LAST — hub_settings DB row overrides env + acts immediately; MinAgent fleet +> gate — CHANGELOG header convention starts with this release; the gate as box-level backstop). +> Tests T1–T6 + wire-level 404-typing; red-proofs RP1–RP5 in REPORT.md. Roadmap: agent +> version-in-envelope upgrade of `Supports`; hub floor-UI separation = its own task. The +> `agent_outdated` branch is test-proven only (demo agent is current — downgrade not justified). > **2026-07-11 — v0.113.0: NAS verify-before-commit + page redesign (LIVE on 9201, pairs with agent > v0.81.0 + host-install v1.13.0).** `POST /api/storage/netstorage/add` no longer registers blind diff --git a/REUSE.md b/REUSE.md index b989ef1..fe8af8f 100644 --- a/REUSE.md +++ b/REUSE.md @@ -110,6 +110,8 @@ | `Client.FormatDisk` | controller/internal/agentapi/client.go | `(ctx, device, fstype, confirmed, durableID)` | ONLY format/wipe entry | Sentinels: `ErrNeedsConfirmation` (user-data, resubmit confirmed+durableID) / `ErrFormatRefused` (system/backup — operator opsign only). Agent re-checks role server-side | | `Client.EjectDisk` / `Decommission` / `AssignDisk` / `GuestAttach` / `ListCandidates` | controller/internal/agentapi/client.go | disk lifecycle | Delegate ALL disk ops to agent | Controller holds no Proxmox creds — never shell out to disk tools in-guest | | `Client.AddNetStorage/ListNetStorage/RemoveNetStorage` | controller/internal/agentapi/client.go | NAS mounts (A1) | Network storage | Password passes through to agent's 0600 cred file; controller NEVER persists it | +| `agentapi.StatusError` | controller/internal/agentapi/client.go | `{Path, Code}` typed non-2xx GET error | Distinguishing HTTP statuses from transport errors (`errors.As`) | NEVER string-match agent error text — the capability probe keys on `Code==404` | +| `SupportCache.Supports` / `Client.Supports` | controller/internal/agentapi/features.go | `(ctx, prober, Feature) SupportState` | Agent-capability gate for COUPLED features (route probe, TTL 5m) | 404 ⇒ No; transport/5xx ⇒ Unknown (NEVER refuse on Unknown). New coupled feature = new `featureProbes` row + gate call at the entry point + `MinAgent:` in the CHANGELOG header (publish-train-rules.md). Web layer: `Server.netFeatures` through the `netAgent` seam | ### Notifications / hub sync diff --git a/controller/README.md b/controller/README.md index 2ba0a8f..549c8f3 100644 --- a/controller/README.md +++ b/controller/README.md @@ -813,6 +813,17 @@ not just those with HDD data. Non-HDD apps can configure destination, method, an > (`storage_network.html`) is on the canonical form pattern with staged poll progress and the > protocol-honest NAS guidance (SMB-first; NFS map-all-users vs full-fidelity `anonuid=`). > Authoritative doc: `felhom.eu/documentation/controller/network-storage-nas.md`. +> - **Agent-capability gate (v0.114.0):** the verify-before-commit add is COUPLED to agent ≥ v0.81.0, +> so the add entry point now probes the agent first (`internal/agentapi/features.go`, +> `Supports(FeatureNetstorageVerify)` — a route probe on `GET /netstorage/verify-status`: 2xx ⇒ +> supported, typed 404 ⇒ older agent, transport/5xx ⇒ indeterminate, cached 5 min both polarities). +> On an older agent the add is refused SYNCHRONOUSLY (HTTP 412, machine code `agent_outdated`, +> honest Hungarian message) BEFORE the single-flight claim — never a misleading mid-pipeline +> rollback; indeterminate NEVER refuses (a down agent speaks through the existing error paths). The +> settings page swaps the add form for a banner on `SupportNo` (share list + remove stay usable in +> every state). Convention: every future coupled feature adds a `featureProbes` row + a gate call at +> its entry point and declares `MinAgent` in its CHANGELOG header — see +> `felhom.eu/documentation/runbooks/publish-train-rules.md`. > - **Limits (v1):** a share's `+100000` uid mapping is fixed at add-time (one app / same-uid apps); for > write apps on a soft NFS mount, an in-flight file can truncate if the NAS vanishes mid-write (prefer > atomic-write apps / SSD-staging).