Files
felhom-controller/REPORT.md
T
admin c997d79246 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
2026-07-06 14:11:44 +02:00

62 lines
4.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.101.0: campaign findings F3 (sync deadline) + F2 evidence gap (agent refusal surfacing)
**Date:** 2026-07-06 · **Class:** implementation (code + tests + deploy + live validation)
**Baseline:** `main` @ `af7ea0b` (v0.100.0, matched the task's confirmed baseline exactly).
**Provenance:** `felhom.eu/documentation/audits/CAMPAIGN-nomercy-2026-07-06.md` findings F3, F2.
**Companion repos:** catalog `d86e256` (F1 vaultwarden fix); felhom.eu audit addendum (F1/F2/F3 verdicts).
## What changed
### F3 — git subprocess deadline (`internal/sync/sync.go`)
`runGitInDir` took no context; a hung remote parked the sync goroutine in `cmd.Run()`, the `doSync`
defer never ran, `syncing` stayed true, and every subsequent manual + periodic sync was refused
("Szinkronizálás már folyamatban") until a controller restart. Now: `const gitCmdTimeout = 120s`;
a `gitCmd` wrapper derives a fresh per-command `context.WithTimeout`; `runGitInDir` takes a
`context.Context` and uses `exec.CommandContext`; on `ctx.Err()` the error names the timeout + the
masked git args. All three call sites (clone via `runGit`, fetch, reset) route through it. Debounce
and failed-sync-arms-debounce semantics unchanged.
### F2 evidence gap — agent refusal surfacing (`internal/agentapi/client.go`)
`EjectDisk` + `Decommission` used `c.post`, which drops any non-2xx body, flattening the agent's
informative refusal to a bare `HTTP 403` (the exact campaign evidence:
`agentapi: POST /disks/decommission: HTTP 403`). Both switched to the purpose-built
`postWithStatus` + a new shared `refusalError(path, status, env)` that returns
`agentapi: POST <path>: HTTP <code>: <agent reason, truncated ~300>` (and handles the ok:false-on-2xx
business-refusal case). `truncateErr` mirrors `stacks.truncateStr`. The generic `post` and every
other caller are untouched. `internal/web/storage_handlers.go` already wraps the client error into
its Hungarian message (`"a meghajtó leszerelése sikertelen: %w"` / eject `err.Error()`), so the role
reason now reaches the UI/API response verbatim — verified by reading the handler paths (no
re-truncation layer).
## Tests + red-proofs (non-hollow)
| Test | Asserts | Red-proof (pre-fix shape) |
|---|---|---|
| `TestRunGitInDir_CancelledContextKillsSubprocess` (T-C1) | cancelled ctx → error names the ctx cause + "deadline/killed", returns < 5s | reverted to `exec.Command`**FAIL**: *"got nil (git --version ran to completion — the pre-fix behavior)"* |
| `TestTriggerSync_FailureReleasesSyncingAndAllowsRetry` (T-C2) | failed sync carries "Git hiba", `syncing` false after, immediate retry hits debounce, post-debounce retry EXECUTES | (behavioral; guards the lockup) |
| `TestDecommission_RefusalReasonSurfaced` (T-D1) | error contains "(role: system)" + "HTTP 403" | reverted to `c.post`**FAIL**: *"agent refusal reason discarded — operator sees only: agentapi: POST /disks/decommission: HTTP 403"* |
| `TestEjectDisk_RefusalReasonSurfaced` (T-D2) | error contains "(role: backup)" | — |
| `TestDecommission_SuccessUnchanged` (T-D3) | 200 envelope decodes to result | — |
| `TestDecommission_OkFalseBusinessRefusal` | ok:false-on-2xx reason surfaced | — |
Both red-proofs demonstrated live (revert → FAIL with the wrong value shown → restore → pass;
`git diff` clean). Green gate `go build ./... && go vet ./... && go test ./...` = **PASS** (exit 0);
30 passing tests across `internal/sync` + `internal/agentapi` (+2 new test files, +6 new tests).
## Deploy + live validation
(Filled in during Phase 4/5 of this session — see below; secrets-safe.)
- Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.101.0`; deployed to guest 9201.
- Scenario A (F1 acceptance): fresh email-off vaultwarden deploy healthy, `_ENABLE_SMTP=false`.
- Scenario B (regression): app-email ON→healthy (`_ENABLE_SMTP=true`, host+from set) → OFF→healthy.
- Sync smoke: `POST /api/sync` 2xx.
- F2 diagnosis verdict: see the felhom.eu audit addendum.
## NOT yet live-validated
Clean backup/restore (F3-volume-tar / C2 Tier-2) coverage on the now-healthy vaultwarden — awaits
the separate targeted P1P3 campaign re-run (a runbook, not this task).
## Not changed
felhom-agent (read-only; F2 is diagnosis only). The generic `agentapi.post` error format, the sync
debounce, and the other five smtp-mapped catalog templates.