feat(agentapi): surface agent disk-op refusal reasons; v0.101.0 + CHANGELOG/REPORT

EjectDisk/Decommission switched from c.post (drops non-2xx body) to
postWithStatus + shared refusalError, so the agent's informative 403 body
("…decommission refused (role: X)") reaches the operator instead of a bare
"HTTP 403" (campaign F2 evidence gap). Generic post + other callers untouched.
Tests T-D1/T-D2/T-D3 + ok:false case; T-D1 red-proof shows the pre-fix bare
"HTTP 403". Bundles the v0.101.0 CHANGELOG entry (this + the F3 sync deadline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-06 14:11:44 +02:00
parent fc28033fe8
commit c997d79246
4 changed files with 211 additions and 47 deletions
+22
View File
@@ -1,5 +1,27 @@
## Changelog
### v0.101.0 — campaign findings F3 (sync deadline) + F2 evidence gap (agent refusal surfacing) (2026-07-06)
From the no-mercy campaign (`felhom.eu/documentation/audits/CAMPAIGN-nomercy-2026-07-06.md`).
No behavior change for the happy paths; two robustness/diagnosability fixes.
- **F3 — sync git subprocess deadline** (`internal/sync/sync.go`): `runGitInDir` had no context,
so a hung remote parked the sync goroutine in `cmd.Run()` — the `doSync` defer never ran,
`syncing` stayed true, and every manual + periodic sync was refused with "Szinkronizálás már
folyamatban" until a controller restart. Each git command now runs under
`exec.CommandContext` with a fresh per-command `gitCmdTimeout` (120s); the deadline error names
the timeout and the masked git args (no silent hang). Debounce + failed-sync-arms-debounce
unchanged. Tests: `TestRunGitInDir_CancelledContextKillsSubprocess` (red-proof: pre-fix
`exec.Command` runs to completion → FAILs), `TestTriggerSync_FailureReleasesSyncingAndAllowsRetry`.
- **F2 evidence gap — agent refusal surfacing** (`internal/agentapi/client.go`): `EjectDisk` and
`Decommission` used `c.post`, which discards a non-2xx body — so the agent's informative refusal
(`"…decommission refused (role: system)"`) was flattened to a bare `HTTP 403` (the exact campaign
evidence). Both now use `postWithStatus` + a shared `refusalError` that carries the agent's
reason (truncated ~300, no bodies/secrets) through to the controller's Hungarian error and the
UI/API response. The generic `post` and all other callers are untouched. Tests: T-D1/T-D2
(fake-agent 403 → reason surfaced; red-proof: pre-fix `c.post` yields bare `HTTP 403` → FAILs),
T-D3 success unchanged, plus an ok:false 2xx-business-refusal case.
### v0.100.0 — one-click class-C file restore from the Tier-2 copy (2026-07-05)
TASK C2 — closes drill finding **F2** (`DRILL-appdata-restore-2026-07-04.md` §4): HDD bind-mount