Campaign 8: F-REBOOT/F-LEAK/F-OBS closed; R-100 filed (offsite staleness reset by failures)
F-LEAK's first fix (pool adoption) was refuted live and is recorded as such. R-100 is F-CRIT-2's defect class on the hub for the restic tier: isStale reads only LastRun, which the controller writes unconditionally on failure. Investigated, not fixed.
This commit is contained in:
@@ -371,10 +371,92 @@ no re-backup thrash (with 91 scheduler ticks proving the loop was alive). Detail
|
||||
two real snapshots PLUS the phantom — so the feared retention/data-loss scenario does not occur. It
|
||||
never removes them either; filed as R-99.
|
||||
|
||||
### F-REBOOT — FIXED (agent v0.107.0, 2026-07-28)
|
||||
A periodic guest-power watchdog (`internal/localapi/guestpower.go`, 60 s) starts a guest that is
|
||||
`onboot:1`, stopped, unlocked, and has no vzdump in flight. It closes the two narrow gaps that let
|
||||
`RecoverStaleLockedGuests` miss fault 11: that recovery acts only on a guest holding a **stale vzdump
|
||||
lock** (fault 11's guest was unlocked) and runs **once at agent startup** (fault 11's guest went down
|
||||
while the agent was already up).
|
||||
|
||||
`onboot` is the deliberate-stop discriminator, and it is not invented here — it is already what the
|
||||
stale-lock path uses for this decision, it is 0 on scratch/golden guests, and it is what `pve-guests`
|
||||
itself consults at host boot, so the agent agrees with the platform rather than keeping a second
|
||||
private definition of "should be running". Retry is bounded (3 attempts, 1m/2m/4m) and then escalates
|
||||
once; an unbounded silent retry loop would be the over-correction.
|
||||
|
||||
Replayed live on demo-hp: a guest stopped out from under the agent came back **unattended in 120 s**,
|
||||
against the **587 s** the finding's original incident needed a human. The lock-deferral guard fired
|
||||
live during the replay. Scenario B proven on the same box — an `onboot:0` guest was left stopped
|
||||
across the whole window.
|
||||
|
||||
### F-LEAK — FIXED (host-install v1.21.0, 2026-07-28) — and the first fix was WRONG
|
||||
`FelhomAgentGuest` is now granted at each `/vms/990000`…`/vms/990009` — the restore-test's scratch
|
||||
band. The cause is structural rather than a missing privilege in the role: the role is granted at
|
||||
`/pool/felhom`, and **a guest only joins that pool when its restore completes**, so a *failed*
|
||||
restore-test leaves a guest that exists, is in no pool, and is outside the token's reach.
|
||||
|
||||
**The first attempt was refuted live and is recorded because it looked right.** Agent v0.107.0 shipped
|
||||
a teardown fallback that adopted the stranded guest into the pool and retried. It fired exactly as
|
||||
designed and PVE refused it: `PUT /pools/felhom -> HTTP 500: permission denied at /vms/990000 (missing
|
||||
privilege ...)`. `PUT /pools/{pool}` **also** requires `VM.Allocate` on the VM being added — pool
|
||||
membership cannot bootstrap its own authority. That code was removed in agent v0.108.0.
|
||||
|
||||
**Why the grant is not a widening**, proven live at the same seam the defect lives in — and note that
|
||||
PVE checks **permission before existence**, so these 403s are genuine refusals, not artifacts:
|
||||
|
||||
| `DELETE /nodes/<node>/lxc/<id>` with the agent's own token | result |
|
||||
|---|---|
|
||||
| `990000` (stranded, pool-less, restored scratch) | **destroyed** — the defect's exact case |
|
||||
| `990010` (one past the band) | **403** `Permission check failed (/vms/990010, VM.Allocate)` |
|
||||
| `100` (arbitrary non-pool guest) | **403** same |
|
||||
|
||||
Granting at `/vms` was rejected: it would authorise destroying every guest on the box, including a
|
||||
co-tenant's.
|
||||
|
||||
### F-OBS — FIXED (controller v0.180.0, 2026-07-28)
|
||||
`deadapp-check` now emits a summary line at **INFO** every 20th scan (10 min at its 30 s cadence),
|
||||
carrying scans-since-boot, stacks evaluated, and how many are currently down. The original silence
|
||||
came from its per-cycle line going through `Scheduler.dbg()`, which is gated on `logging.level==debug`
|
||||
and so was never *produced* on a default box — meaning "no alarms" was indistinguishable from "the
|
||||
detector never ran", the exact fallacy this project has a standing rule against. A line per run was
|
||||
not an option either (2880/day is what made silence attractive), so the cadence is asserted by test in
|
||||
both directions: it must not flood, and it must stay frequent enough to expose a stalled detector
|
||||
inside the 180 s alarm grace it feeds.
|
||||
|
||||
*(Full write-ups for every finding, with complete evidence, are in `~/campaign8/evidence/phaseB/`.)*
|
||||
|
||||
---
|
||||
|
||||
## 6b. Follow-up investigation, 2026-07-28 — R-100 (filed, deliberately NOT fixed)
|
||||
|
||||
### A restic offsite tier that fails every night never goes stale on the hub
|
||||
|
||||
This is **F-CRIT-2's defect class, one layer up and on the other tier** — a *failed* run resetting the
|
||||
freshness clock — and it was found by asking whether the F-CRIT-2 shape existed anywhere else.
|
||||
|
||||
Two halves, each verified in the source rather than inferred:
|
||||
|
||||
- **Controller.** `o.LastRun = time.Now()` is set **unconditionally** in
|
||||
`controller/internal/backup/offbox.go:716`, outside the `runErr` branch. The failure is recorded
|
||||
faithfully — but in a *different* field: `o.LastStatus = "error"` at :725.
|
||||
- **Hub.** `isStale()` reads **only** `off.LastRun`
|
||||
(`hub/internal/monitor/offsite.go:120`, `:127`, `:131`). It never consults `LastStatus`.
|
||||
|
||||
So a nightly restic run that fails every night keeps `LastRun` fresh, `isStale` is permanently false,
|
||||
and the staleness alarm never fires — while no successful offsite backup has occurred at all.
|
||||
|
||||
**Scope of the silence, stated precisely.** `LastStatus` *does* reach the hub: it is parsed into the
|
||||
report struct and **only logged** (`offsite.go:270`). It drives no checker and no notification. The
|
||||
controller's own guest UI does surface `LastStatus="error"`, so the failure is *visible to someone who
|
||||
looks*; what is missing is the push — the operator's fleet-wide alarm plane is silent, which is the
|
||||
plane that matters for an unattended appliance.
|
||||
|
||||
**Not fixed**, per this task's investigation-only scope. Filed as **R-100**; the fix direction is to
|
||||
gate staleness on the last *successful* run rather than the last attempt, which is precisely what
|
||||
F-CRIT-2's `NewestArchiveTime` fix did for the PBS tier.
|
||||
|
||||
---
|
||||
|
||||
## 7. What can come off the "never validated" list
|
||||
|
||||
**Retired — proven live tonight:**
|
||||
|
||||
Reference in New Issue
Block a user