Files
felhom-controller/REPORT.md
T
admin 9d5a588ca3 docs: REPORT live-validation evidence + F2 verdict; CONTEXT v0.101.0
Scenario A/B env excerpts (restarts=0, _ENABLE_SMTP off/on), sync smoke 2xx,
F2 real-finding verdict. CONTEXT dated to v0.101.0 with open follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-06 14:26:45 +02:00

81 lines
5.7 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 (executed, real UI pipeline)
- Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.101.0` on 180; deployed to guest 9201
via golden/bootstrap → `docker ps` `:0.101.0 Up (healthy)`.
- Catalog synced (startup sync pulled the F1 template; `grep _ENABLE_SMTP` present in the guest
`docker-compose.yml` + `.felhom.yml`).
- **Scenario A (F1 acceptance):** stopped + removed the crash-looping vaultwarden via the UI
`/stop` + `/remove` endpoints (remove correctly 409'd until stopped — "still running"); fresh
`POST /api/stacks/vaultwarden/deploy` (app-email default off) → 202 → `running` in ~70s,
`docker inspect` **restarts=0**; env: `_ENABLE_SMTP=false`, `SMTP_HOST=`, `SMTP_FROM=` (empty).
No crash-loop — the pre-fix instance that was `Restarting` is gone.
- **Scenario B (regression):** `POST /stacks/vaultwarden/app-email app_email_enabled=on` → recreate
→ healthy, restarts=0, `_ENABLE_SMTP=true`, `SMTP_HOST=felhom-controller`,
`SMTP_FROM=vaultwarden@felhom.eu`; then `app_email_enabled=false` → healthy, `_ENABLE_SMTP=false`.
Left OFF. The send-tested ON path is unchanged.
- **Sync smoke (F3):** `POST /api/sync` → HTTP 200 ("Sablonok naprakészek") — the deadline change
is transparent on the healthy path.
## F2 verdict (real finding — agent fix deferred)
Campaign-time agent journal (persistent, survived the P6 reboots):
`"local-api: protected — decommission refused by role" where=/mnt/teszt_enroll role=system`.
`roleForMountPath` (agent `disks.go:1031`) fail-safe-returns `system` when no PVE **storage target**
matches the mount — but `/disks` already classifies teszt_enroll as `user-data`. Two role paths
disagree; a bind-mounted user-data drive that isn't a standalone PVE storage can't be decommissioned.
Over-refusal (fail-safe), not a hole → **MEDIUM**. Full write-up: felhom.eu audit addendum. Agent
untouched (read-only per scope); fix is a separate follow-up task.
## 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.