v0.90.1 — R-39 hotfix: PBS reconcile must not pass --server to pvesm set
Config-only (wrapper + red-proof); the Go binary is unchanged, so this ships
with the next agent deploy as a config artifact.
The reconcile verb built `args=(--server "$server" --fingerprint "$fp")`. PVE
treats a PBS storage's `server` as a CREATE-ONLY parameter and rejects the
ENTIRE `pvesm set` call — "can't change value of fixed parameter 'server'" —
even when the value passed is byte-identical to the stored one. So reconcile
could never succeed against an existing entry; it exited 255 every time.
That is severe rather than cosmetic because the agent consumes the hub's
ONE-TIME PBS token secret BEFORE invoking the wrapper. Each hub "Re-issue PBS
credentials" therefore minted a secret, the agent burned it, the wrapper
rejected the apply, and the entry stayed pinned to the revoked credential —
a PBS DR tier authenticating 401 indefinitely while the agent reported
`pbsdr: converged state=applied`.
Live-diagnosed on the N100 during the rehearsal wrap (felhom.eu
tests/VALIDATION-n100-rehearsal-2026-07-18.md F2, ROADMAP R-39). Proven on the
live entry before writing code: `pvesm set <id> --server <same> --fingerprint
<same>` -> rejected; the same call without --server -> rc 0. K (<id>.enc) and
the .pw store verified byte-untouched after the rejected call — PVE rejects
atomically, so the set-only law held.
Fix: drop --server. The server address is immutable by construction (relocating
a PBS endpoint needs a fresh create), so there was never anything to reconcile
there. --fingerprint (+ --password when a secret is fed) remain.
Red-proof TestReconcileNeverPassesServerToPvesmSet: isolates the reconcile)
block from the shipped wrapper, asserts no --server reaches `pvesm set` and
that --fingerprint is still pushed. Verified RED on the unfixed wrapper, GREEN
after. Handles two vacuous-pass traps that both fired while authoring it: the
pattern is line-ending tolerant (\r?\n — this repo is cloned on Windows, and an
\n-only pattern matches nothing and passes silently), and comment lines are
stripped before matching (the WHY note quotes the very flag under test).
NOT fixed here, both still open and riding the spec'd R-39 agent train:
1. R-39's primary half — the agent re-applies on a change of the DESCRIPTOR
HASH (manager.go ~L235), but a credential re-issue leaves the descriptor
byte-identical (same token_id/fingerprint; only the side-table secret
rotates) and bumps only the generation, so a converged agent still ignores
a fresh secret. This makes the apply succeed once it re-applies; it does
not make it re-apply.
2. The verify loop reads /etc/pve/priv/storage/<id>.pw directly as non-root —
a path it can only ever WRITE through the root wrapper (0700 root:www-data;
sudoers exposes create|reconcile|grant, no read verb), so it is permanently
blind to the failure it exists to catch.
Demo box: wrapper hotfixed in place (.bak-20260718-preR39 kept). NOT yet healed
— diagnosis consumed the pending secret against the unfixed wrapper; the agent
parked correctly in consumed-failed (no burn loop). Healing needs Viktor to
click "Re-issue PBS credentials"; the agent will then pick it up unaided
(marker.json absent, so the L235 short-circuit does not apply).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
@@ -1,3 +1,54 @@
|
||||
## v0.90.1 — R-39 hotfix: PBS reconcile must not pass `--server` to `pvesm set` (2026-07-18)
|
||||
|
||||
**Config-only fix (wrapper + red-proof); the Go binary is unchanged.** Ship the wrapper with the
|
||||
next agent deploy — `configs/felhom-pbs-apply` is a shipped artifact, not a build input.
|
||||
Green: `go build ./... && go vet ./... && go test ./...` all pass.
|
||||
|
||||
- **The defect.** The `reconcile` verb built its argv as
|
||||
`args=(--server "$server" --fingerprint "$fp")`. PVE treats a PBS storage's `server` as a
|
||||
**create-only** parameter and rejects the ENTIRE `pvesm set` call —
|
||||
`can't change value of fixed parameter 'server'` — **even when the value passed is byte-identical
|
||||
to the stored one**. So `reconcile` could never succeed against an existing entry; it exited 255
|
||||
every time.
|
||||
|
||||
- **Why that was severe rather than cosmetic.** The agent consumes the hub's **one-time** PBS token
|
||||
secret BEFORE invoking the wrapper. A wrapper failure therefore *burned* the credential: each hub
|
||||
"Re-issue PBS credentials" minted a fresh secret, the agent consumed it, the wrapper rejected the
|
||||
apply, and the storage entry stayed pinned to the **revoked** one. Observable end state: the PBS DR
|
||||
tier authenticating **401 Unauthorized** indefinitely while the agent reported
|
||||
`pbsdr: converged state=applied`. Live-diagnosed on the N100 demo host during the 2026-07-18
|
||||
rehearsal wrap (`felhom.eu/documentation/tests/VALIDATION-n100-rehearsal-2026-07-18.md`, finding
|
||||
F2 / ROADMAP **R-39**).
|
||||
|
||||
- **The fix.** Drop `--server` from the reconcile argv. The server address is immutable by
|
||||
construction — relocating a PBS endpoint requires a fresh `create` — so there was never anything
|
||||
for `reconcile` to reconcile there. `--fingerprint` (and `--password` when a secret is fed) remain,
|
||||
which is the mutable identity the verb exists to push. Proven on the live box before committing:
|
||||
`pvesm set felhom-pbs --server <same> --fingerprint <same>` → rejected;
|
||||
the same call without `--server` → **rc 0**.
|
||||
|
||||
- **Red-proof** `TestReconcileNeverPassesServerToPvesmSet` (`internal/pbsdr/manager_test.go`):
|
||||
isolates the `reconcile)` block from the shipped wrapper and asserts no `--server` reaches
|
||||
`pvesm set`, plus that `--fingerprint` is still pushed (so the verb can't be hollowed out).
|
||||
Verified RED against the unfixed wrapper and GREEN after the fix. Two traps the proof handles
|
||||
explicitly: the pattern is **line-ending tolerant** (`\r?\n`) because this repo is cloned on
|
||||
Windows and an `\n`-only pattern would match nothing and pass **vacuously**; and comment lines are
|
||||
**stripped before matching**, because the WHY note above the fix necessarily quotes the very flag
|
||||
the test forbids.
|
||||
|
||||
- **NOT fixed here (deliberately, and each still open):**
|
||||
1. **R-39's primary half** — the agent re-applies on a change of the *descriptor hash*
|
||||
(`manager.go` ~L235), but a hub credential re-issue leaves the descriptor byte-identical
|
||||
(same `token_id`, same `fingerprint`; only the side-table secret rotates) and bumps only the
|
||||
generation. So a converged agent still ignores a fresh secret. This wrapper fix means the apply
|
||||
now *succeeds* once the agent is made to re-apply; it does not make it re-apply.
|
||||
2. **The verify-loop read** — `pbs: cannot read token secret … permission denied`: the non-root
|
||||
agent reads `/etc/pve/priv/storage/<id>.pw` directly, a path it can only ever *write* through
|
||||
the root wrapper (`/etc/pve/priv` is `0700 root:www-data`, and sudoers exposes only
|
||||
`create|reconcile|grant` — there is no read verb). The loop is therefore permanently blind to
|
||||
the failure it exists to catch.
|
||||
Both ride the spec'd R-39 agent train.
|
||||
|
||||
## v0.90.0 — agent train: guest RAM resize (R-24) + fast-tick-until-convergence (R-28) (2026-07-17)
|
||||
|
||||
**MinAgent coupling:** felhom-controller v0.143.0 gates its guest-memory-resize UI on this agent
|
||||
|
||||
Reference in New Issue
Block a user