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

4.3 KiB
Raw Blame History

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.CommandFAIL: "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.postFAIL: "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.