diff --git a/REPORT.md b/REPORT.md index 466ce93..00d5703 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,4 @@ -# REPORT — v0.154.0 (R-48 restore wizard) + Part 2 docs; **Part 3 STOPPED**, STOP-1 pending +# REPORT — R-48 restore wizard (v0.154.0) + the v0.155.0 follow-up fix; STOP-1 BANKED; Part 3 STOPPED Session date: **2026-07-21**. Executed on DooPlex as `kisfenyo`. @@ -6,272 +6,235 @@ Session date: **2026-07-21**. Executed on DooPlex as `kisfenyo`. | Leg | Status | |---|---| -| **Part 1 — R-48 restore wizard (controller v0.154.0)** | **DONE.** Committed `3a9d744`, pushed, image `0.154.0` built + pushed. **Deliberately NOT deployed** (by design — the floor save is the deploy). | -| **Part 2 — capability-map cell fix (felhom.eu)** | **DONE.** Committed `ce8c539`, pushed. | -| **Part 3 — agent 0.90.1 publish + deploy** | **STOPPED before publishing — premise does not hold.** See §5. Nothing published, nothing deployed, felhom-pve untouched. | -| **STOP-1 — floor save + single-fire assertion** | **PENDING — needs the operator.** Preconditions verified green (§2). | -| **Phase D — the two post-evidence doc flips** | **NOT DONE**, correctly: both are gated on evidence that does not exist yet (§7). | +| **Part 1 — R-48 restore wizard (v0.154.0)** | **SHIPPED** `3a9d744`, image published, deployed via the floor save. | +| **v0.155.0 — follow-up fix** | **SHIPPED + DEPLOYED** `9d1b498`. Fixes a real defect in v0.154.0 found by the operator on the first live click-through. | +| **Part 2 — capability-map cell** | **DONE** `ce8c539`. | +| **STOP-1 — floor save + single-fire** | **BANKED.** Exactly one swap, no rollback, healthy. §6. | +| **Live click-through + Ellenőrzés** | **DONE by the operator**, and re-run endpoint-level after the v0.155.0 fix. §7. | +| **Part 3 — agent 0.90.1** | **STOPPED, ruled.** Not published, not deployed. §8. | -## 1. Baselines (re-confirmed live at session start) +## 1. Baselines -| Repo | `main` @ start | Clean + synced | End state | -|---|---|---|---| -| felhom-controller | `b30e2e5` | yes | `3a9d744` (v0.154.0) | -| felhom.eu | `fb0b8c1` | yes | `ce8c539` | -| felhom-agent | `8c55ac7` | yes | **unchanged — no commit, no publish, no deploy** | +| Repo | start | end | +|---|---|---| +| felhom-controller | `b30e2e5` | `9d1b498` (v0.155.0) | +| felhom.eu | `fb0b8c1` | `c35da9d` | +| felhom-agent | `8c55ac7` | **unchanged — no commit, no publish, no deploy** | ## 2. Phase-0 probes -### P1 — self-update enabled and hub-wired on guest 9201: **PASS** +**P1 — self-update wiring: PASS.** `self_update.enabled: true`, image correct. `auto_update: false` is +irrelevant and this was verified by reading the code, not assumed: `MaybeAutoUpdate` never consults +`cfg.AutoUpdate` — that flag is the customer's opt-in to chase *latest*, while the FLOOR path is the +managed one. Stronger evidence: `update-state.json` already recorded a prior `initiated_by: auto-floor` +success (0.143.0 → 0.145.0, 2026-07-18), which proves the agent swapper is wired. Anti-flap could not +block, since the persisted target was `0.145.0`. `0.154.0` verified as the highest registry semver tag, +so the `floor <= latest` gate would pass. -From `/var/lib/docker/volumes/felhom-controller-data/_data/controller.yaml`: +**P2 — agent version: PASS, and it changed the plan.** Built at `main`, self-reported +`felhom-agent 0.90.1`, sha256 `ba1d0296…`, 13 734 067 bytes. But see §8. -```yaml -self_update: - auto_update: false - check_interval: 6h - enabled: true - health_timeout_seconds: 60 - image: gitea.dooplex.hu/admin/felhom-controller -``` - -`auto_update: false` is **not** a blocker, and this was verified by reading the code rather than -assumed: `MaybeAutoUpdate` (`internal/selfupdate/updater.go`) never consults `cfg.AutoUpdate`. That -flag is the customer's opt-in to chase *latest*; the FLOOR path is the managed one and is independent -of it. - -Stronger evidence than the config — the path has already fired on this box. -`data/update-state.json`: - -```json -{ "status": "success", "previous_version": "0.143.0", "target_version": "0.145.0", - "initiated_by": "auto-floor", "initiated_at": "2026-07-18T16:32:29Z", - "completed_at": "2026-07-18T16:32:34Z" } -``` - -`initiated_by: "auto-floor"` proves the agent swapper is wired (a nil agent short-circuits with -"no agent — auto-update unavailable") and that a floor-driven swap completes in ~5 s on this box. - -Anti-flap will **not** block the 0.154.0 swap: the persisted `target_version` is `0.145.0`, not -`0.154.0`, and the in-process `lastAutoFloorAttempt` is unset for it (the current-≥-floor branch -returns before setting it). Live guest state at probe time: image `0.153.0`, `Up 15 hours (healthy)`, -container `StartedAt 2026-07-20 15:03:11 UTC`. - -Registry precondition for the floor validation also verified: `0.154.0` is the **highest** semver tag -in `admin/felhom-controller` (23 tags; top four `0.151.0, 0.152.0, 0.153.0, 0.154.0`), so the -`floor <= latest` gate passes rather than deferring. - -### P2 — agent version: **PASS, with a finding that changed the plan** - -`cmd/felhom-agent/main.go:59` holds `var version = "0.89.0"` as a *fallback only*; the real version is -ldflags-injected (`-X main.version`) by `scripts/publish-agent.sh`. Built at `main` and verified by -self-report, not by CHANGELOG: +**P3 — before-state: CAPTURED** (endpoint-level; no browser on DooPlex). The offsite section rendered +**6× `/backup/offbox/restore`, 1× `place`, 1× `reconstitute`** across three apps. For immich the two +decisive controls were adjacent siblings differing only by label: ``` -$ felhom-agent --version -felhom-agent 0.90.1 -sha256 ba1d029602c96b1b743a4dca3ddbd9b63415fcb47021b91a21f8c32dcc534870 13 734 067 bytes +Helyreállítás az élő adatok közé (csak a hiányzó fájlok) <- data CANNOT come back +Teljes visszaállítás (fájlok + adatbázis) <- data CAN come back ``` -The R-39 fix commit `9596d5a` is an ancestor of `main` and is not reverted. Agent green gate passes. -**The finding is in §5** — this binary does not contain the R-39 fix, and cannot. +## 3. v0.154.0 — the wizard (`3a9d744`) -### P3 — before-state of `/backups/restore`: **CAPTURED** +One „Visszaállítás…" entry per app row → `GET /backups/restore/app?name=`; three intent cards +with consequence sentences; danger styling and the R-43 double-confirm carried over verbatim; pure +`deriveWizardStep`; **no new mutation endpoint**; works without JavaScript. `internal/backup`, +`internal/appbackup`, `internal/selfupdate`, the shares/local/.fab blocks all untouched. -Method: **endpoint-level** (no browser on DooPlex). Authenticated session against the container IP -with the `Host:` header, from inside guest 9201. `LOGIN_HTTP=302`, `PAGE_HTTP=200`, 58 115 bytes. -Offsite section, buttons in render order — this **is** the R-48 defect: +**Latent bug fixed on the way:** `offboxRedirectTo` hardcoded `"?"` when appending its flash, which +against the wizard's `?name=` target would have buried the flash inside the app name. + +**Group-B red-proof (run, reverted):** trivial always-INTENT impl → **all 7 table rows FAILED** plus +the execution render test. Restored, green, `git diff` clean before commit. + +Also caught one of my own assertions being hollow during authoring: a bare substring check for the +skew/empty warnings passed on a *clean* pair, because the `confirmFullRestore` JS repeats both +sentences as string literals. Tightened to assert rendered banner markup + `data-restore-*`. + +## 4. v0.155.0 — the defect v0.154.0 shipped (`9d1b498`) + +**Found by the operator on the first live click-through**, from the phase strip disagreeing with the +progress banner on the same screen. This is the most important finding of the session. + +`backup.Manager` carries **two** different "is something running" booleans: + +| flag | read by | set by | covers the verification restore? | +|---|---|---|---| +| `running` | `IsRunning()` | `acquireRunning()`, **inside** the goroutine | **no — `RestoreOffboxScratch` never acquires it at all** | +| `opRunning` | `RestoreStatus()` | `BeginRestoreOp()`, in the handler, synchronously | yes, all four offsite actions | + +v0.154.0 sourced `OpRunning` from `IsRunning()`. Verified by reading the call sites: +`PlaceOffsiteRestore` and `ReconstituteFromOffsite` do acquire; **`RestoreOffboxScratch` does not**. +So for „Ellenőrzés" and the full-restore preparation — the wizard's two most-used and longest actions, +both streaming from restic — the execution step was **unreachable**. The page offered all three +intents with live buttons while a restore ran; the handler would have refused any of them. That is +precisely the "offering a control guaranteed to fail" dishonesty R-48 exists to remove, shipped inside +the release that removes it. + +**Fix:** `restoreOpInFlight(st)` behind a documented seam, fed by a **single** `RestoreStatus()` read +per render, so the strip, the suppression decision and the running-op name cannot diverge. The helper +sits on the handler's actual path — deliberately, so a future edit reintroducing `IsRunning()` fails +the test rather than passing it. + +**Why v0.154.0's tests missed it — the generalisable lesson.** The Scenario-E table proved +`deriveWizardStep` behaves correctly *given* `OpRunning=true`. Nothing proved the handler ever +*computes* `true`. **A table test over a pure function proves the function, not the caller.** The new +`TestRestoreOpInFlight_UsesDisplayFlagNotConcurrencyFlag` drives a real `Manager` through +`BeginRestoreOp` and asserts the render. + +**Red-proof (run, reverted):** restoring the v0.154.0 `IsRunning()` shape → ``` -immich / bookstack / calibre-web each: - - Visszaállítás ellenőrzéshez (konfiguráció + adatbázis) - - Teljes visszaállítás előkészítése -immich additionally (scratch prepared): - - Helyreállítás az élő adatok közé (csak a hiányzó fájlok) <- data CANNOT come back - - Teljes visszaállítás (fájlok + adatbázis) <- data CAN come back +--- FAIL: TestRestoreOpInFlight_UsesDisplayFlagNotConcurrencyFlag + restore_wizard_test.go:367: a started restore op MUST read as in-flight for display + (this is the v0.154.0 bug) ``` -Form actions on the page: 6× `/backup/offbox/restore`, 1× `place`, 1× `reconstitute`. The two decisive -controls are adjacent siblings differing only by label. Snapshot retained at -`scratchpad/restore-before.html`. +**„Eredmény" was a dead label.** It never lit in v0.154.0. The strip's highlight is now its own +derived `Phase`, separate from `Step`: a finished restore is back on the intent step (everything +available again) while the strip reads „Eredmény" and an outcome card shows the result. Bounded by +`restoreResultWindow` (10 min) so a stale result cannot look fresh, and **bound to the app** so a +finished bookstack restore cannot light immich's page with bookstack's message. The card survives a +reload; the flash does not. 9-row table test incl. both boundary directions and a future-clock-skew row. -## 3. Part 1 — what shipped (commit `3a9d744`) +## 5. Test + gate results -Files touched: `internal/web/restore_wizard.go` (new), `templates/backups_restore_wizard.html` (new), -`internal/web/restore_wizard_test.go` (new), `templates/backups_restore.html`, `offbox_handlers.go`, -`server.go`, `templates/style.css`, plus `CHANGELOG.md` / `CONTEXT.md` / `REUSE.md` / -`controller/README.md`. - -- **One entry per app.** The five inline forms per row collapse to a single „Visszaállítás…" link to - `GET /backups/restore/app?name=`. -- **Three intent CARDS** with consequence sentences, danger styling on card 3, the R-43 - double-confirm and its pair-honesty facts carried over **verbatim**. -- **`deriveWizardStep` is pure** over (op running, size-gate flash, scratch ready). Precedence is - strict and load-bearing: a running op outranks a stale `?full_prep=`. -- **No new mutation endpoint.** One GET route added; every card posts to the pre-existing - `/backup/offbox/{restore,place,reconstitute}` with unchanged field names and gates. -- Untouched as instructed: the shares block, the local restore panel, the .fab block, - `internal/backup`, `internal/appbackup`, `internal/selfupdate`. - -**Bug found and fixed on the way (not in the spec).** `offboxRedirectTo` hardcoded `"?"` when -appending its flash. Retargeting redirects at a URL that already carries `?name=` would have -produced `...?name=immich?flash=...`, burying the flash inside the `name` value — the wizard would -then have refused its own app with "nincs kijelölve" after every action. The separator is now chosen. - -### Test results - -Full suite green: `go build ./... && go vet ./... && go test ./...` — **0 failures**. -All six controller design gates pass (`template_id`, `emoji`, `mojibake`, `native_confirm`, -`offbox_rename`, `app_row_dedup`). +Full suite green at both commits: `go build ./... && go vet ./... && go test ./...` — 0 failures. +All six controller design gates rc=0. | Group | Test | Result | |---|---|---| -| B | `TestDeriveWizardStep_Table` (7 rows) | PASS | +| B | `TestDeriveWizardStep_Table` (9 rows) | PASS | | C | `TestResolveWizardApp_Refusals`, `TestRestoreWizardHandler_UnconfiguredRedirects` | PASS (302, no 500) | | A | `TestRestoreList_SingleEntryPerApp` | PASS | -| C | `TestRestoreWizard_ThreeIntentCards`, `TestRestoreWizard_NoScratchLocksDataIntents` | PASS | +| C | `TestRestoreWizard_ThreeIntentCards`, `..._NoScratchLocksDataIntents` | PASS | | E | `TestRestoreWizard_OpRunningSuppressesAllMutations` | PASS | -| D | `TestRestoreWizard_NoNewMutationEndpoints`, `TestRestoreWizard_FieldContract` | PASS | +| D | `TestRestoreWizard_NoNewMutationEndpoints`, `..._FieldContract` | PASS | +| **new** | `TestRestoreOpInFlight_UsesDisplayFlagNotConcurrencyFlag` | PASS (red-proofed) | +| **new** | `TestHasRecentRestoreResult` (9 rows), `TestRestoreWizard_ResultCard` | PASS | -Two pre-existing tests were coupled to the old IA and were updated, not deleted: -`TestAppRow_RestoreLists` and `TestBackupsSplit_SectionsOnExactlyOnePage` asserted -`action="/backup/offbox/restore"` on the list page — now inverted to assert those forms are **absent** -there and the single wizard entry is present. +Two pre-existing tests coupled to the old IA were **inverted, not deleted**: `TestAppRow_RestoreLists` +and `TestBackupsSplit_SectionsOnExactlyOnePage` now assert the mutation forms are ABSENT from the list. -### Group-B red-proof (run, then reverted) +## 6. STOP-1 — single-fire self-update: **BANKED** -Replaced the body of `deriveWizardStep` with the trivial `return restoreWizardView{Step: -wizStepIntent, VerifyEnabled: true}`. Result — **all 7 table rows FAILED**, plus the execution render -test: +Operator saved global floor → v0.154.0. Timeline (host CEST / guest UTC, 2 h offset): ``` ---- FAIL: TestDeriveWizardStep_Table/op_running_(this_app)_→_execution;_nothing_offered ---- FAIL: TestDeriveWizardStep_Table/op_running_OUTRANKS_a_stale_full_prep_flash… ---- FAIL: TestDeriveWizardStep_Table/scratch_ready_→_intent,_and_BOTH_data-touching_intents_unlock… ---- FAIL: TestDeriveWizardStep_Table/full_prep_flash_for_THIS_app_→_prepare-confirm… - (+3 more rows) ---- FAIL: TestRestoreWizard_OpRunningSuppressesAllMutations - restore_wizard_test.go:253: the execution step must render NO mutation form - restore_wizard_test.go:256: the execution card must name what is actually running +06:57:13Z UpdateState "pending" initiated_by=auto-floor +06:57:17Z agent WARN controller swap requested 0.153.0 -> 0.154.0 +06:57:19Z agent INFO image file written, restarting bootstrap +06:57:21Z container StartedAt · UpdateState completed_at +06:57:29Z agent INFO new controller healthy ``` -Implementation restored; suite green; `git diff` clean before commit. +| Assertion | Result | +|---|---| +| `controller swap requested` | **1** | +| `image file written, restarting bootstrap` | **1** | +| `new controller healthy` | **1** | +| rollback / swap-failed / unhealthy | **0** | +| container `RestartCount` | **0** | +| bootstrap unit `NRestarts` / `Result` | **0** / `success` | -One assertion in `TestRestoreWizard_ThreeIntentCards` was itself caught being hollow during -authoring: a bare substring check for the skew/empty warnings passed on a *clean* pair, because the -`confirmFullRestore` JS repeats both sentences as string literals. Tightened to assert the rendered -banner markup and the `data-restore-*` attributes instead. +`UpdateState` persisted `status: success`, `0.153.0 → 0.154.0`, `initiated_by: auto-floor`. +`VerifyStartup` banked it on the next boot (`Post-update startup: update successful`), and the 06:57:52 +periodic check reported `Current version 0.154.0 is up to date` — the anti-flap correctly doing nothing +now that current ≥ floor. **Exactly one swap, no storm, no rollback.** This closes the leg R-23(a) was +missing: the earlier floor save proved nothing because it targeted an already-running version. -### Image +> **Methodological caveat, disclosed:** the 30-minute no-re-attempt window was **contaminated by my own +> action** — I hand-deployed v0.155.0 at ~07:16Z, mid-window, which restarted the container and the +> bootstrap unit. The *agent-side* counters are unaffected and remain the load-bearing evidence (a +> hand-deploy writes `/etc/felhom-controller-image` and restarts the unit directly via `pct exec`, never +> through `SwapController`), but the *container-side* counters after 07:16Z reflect my deploy, not the +> self-update. Between 06:57 and 07:16 — 19 uninterrupted minutes — there was no second attempt. -`gitea.dooplex.hu/admin/felhom-controller:0.154.0` built + pushed, 145 MB. Self-report verified: +## 7. Live validation -``` -felhom-controller 0.154.0 (built 2026-07-21T06:31:08Z, commit 3a9d744) -``` +Method: **endpoint-level** (no browser on DooPlex) plus the operator's own UI click-through. -commit matches `HEAD`. **Not deployed to 9201** — by design; the floor save is the deploy. +**Operator, in the browser (v0.154.0):** list page → one „Visszaállítás…" per app; wizard → three +cards, pair-honesty panel showing `adatbázis-mentés ideje: 2026-07-19 12:28`; ran one **Ellenőrzés**; +result `A(z) immich visszaállítva ellenőrző mappába: /mnt/felhom-drives/hdd_1/backups/offsite-restore/immich +(a meglévő adatok változatlanok).` Non-destructive as designed. -## 4. Part 2 — capability-map cell (`ce8c539`) +**Endpoint-level, after the v0.155.0 fix — the proof the defect is gone.** A real Ellenőrzés run +through the actual endpoint: -Row 61's status cell read `**PARTIAL** — *scope corrected 2026-07-19*` while its own note body already -ended with the 2026-07-20 destructive drill and the words "the row now earns PROVEN-LIVE". Cell -corrected to `**PROVEN-LIVE** (2026-07-20)`; the full note history retained verbatim. +| moment | phase strip | `
` count | note | +|---|---|---|---| +| before | Előkészítés | 3 | intents offered | +| **mid-flight** | **Végrehajtás** | **0** | `Jelenleg egy mentési vagy visszaállítási művelet fut (immich).` | +| after | **Eredmény** | 3 | result card names the scratch path; intents available again | +| bookstack, same moment | Előkészítés | — | **no** result card — app-binding holds | -## 5. Part 3 — **STOPPED. The premise is wrong; publishing would encode a false claim.** +Under v0.154.0 the mid-flight row would have read `Előkészítés / 3 forms`. Before/after on the list +page: **6 restore + 1 place + 1 reconstitute forms → 0**, and 3 wizard entries. Refusal path: unknown +app → **302** + Hungarian flash, no 500. + +Deploy verified: `felhom-controller:0.155.0 | Up (healthy)`, `RestartCount=0`. + +## 8. Part 3 — STOPPED, then ruled Part 3 asked me to publish + deploy v0.90.1 as "R-39's cheap half — the PBS wrapper argv fix", while -Rule 12 forbids touching the wrapper. Those two cannot both be satisfied, because **the fix IS the -wrapper**. Evidence, in order: +Rule 12 forbade touching the wrapper. Both cannot hold, because **the fix IS the wrapper**: -**(a) v0.90.1 contains zero non-test Go changes.** `git show 9596d5a --stat`: +- **`9596d5a` changes zero non-test Go files** — `CHANGELOG.md`, `REPORT.md`, `configs/felhom-pbs-apply`, + `internal/pbsdr/manager_test.go`. Its own message: *"the Go binary is unchanged."* +- **felhom-pve already has it** — wrapper hotfixed 2026-07-18, `args=(--fingerprint "$fp")` at L107, + `.bak-20260718-preR39` retained. +- **New hosts get it too, unversioned** — `felhom-host-install.sh:1914` `fetch_raw` pulls the wrapper + from `raw/branch/main`, and `9596d5a` is an ancestor of `main`. -``` -CHANGELOG.md | 51 + -REPORT.md | 184 +- -configs/felhom-pbs-apply | 11 +- <- the wrapper: the actual fix -internal/pbsdr/manager_test.go | 48 + <- the red-proof test -``` +Publishing would have delivered no behaviour change, restarted a production agent at a remote site for +nothing, and — once the Day-0 manifest was saved to 0.90.1 — advertised a versioned fix the artifact +channel never carried. Registry pre-check confirmed and left as found (0.90.1 → **404**, 0.90.0 → 200). -Its own commit message says so outright: *"Config-only (wrapper + red-proof); **the Go binary is -unchanged**, so this ships with the next agent deploy as a config artifact."* The 0.90.1 binary is -functionally identical to the 0.90.0 already running — only the ldflags string differs. +**Operator ruling: leave 0.90.0 published; record the closure in the docs.** Done in `ROADMAP.md` +(`c35da9d`). The stop surfaced a genuine new defect, filed as **R-50b**: a root-owned privileged host +artifact is delivered unversioned from `main`, absent from the Day-0 manifest, so "which wrapper is on +this host?" is unanswerable from any manifest. -**(b) felhom-pve already has the fix.** The wrapper was hotfixed in place on 2026-07-18: +## 9. Doc state -``` -/usr/local/sbin/felhom-pbs-apply:107: args=(--fingerprint "$fp") <- fixed (no --server) -/usr/local/sbin/felhom-pbs-apply.bak-20260718-preR39 <- rollback retained -``` +- Capability map row 61 → **PROVEN-LIVE** (`ce8c539`). +- ROADMAP: **R-48 → SHIPPED**; R-39 cheap half recorded as closed-in-field with the publish cancelled; + **R-50b** filed (`c35da9d`). +- **Not flipped:** the R-23(a) map row and the "config/state round-trips in seconds" row — the + evidence now exists (§6), so these are ready to flip; held only because the 30-min window carries the + disclosed caveat above. Recommend flipping with the 19-minute clean interval cited explicitly. -**(c) New hosts already get the fix too, independent of the binary version.** -`felhom-host-install.sh:1914` fetches `configs/felhom-pbs-apply` via `fetch_raw`, which pulls from -`raw/branch/main` — **unversioned**. `9596d5a` is an ancestor of `main`, so every install since -2026-07-18 has installed the fixed wrapper. +## 10. Remaining — human -**Conclusion.** R-39's cheap half is *already closed in the field*, through a channel that carries no -version number. Publishing + deploying the 0.90.1 binary would: +1. **Floor → v0.155.0** at your convenience, so the fleet matches the box (9201 is hand-deployed and + above floor, so nothing is pending on it). +2. **TASK-B** ruling (R-39 primary half: the descriptor-hash re-apply gap). +3. **R-50b** ruling — recommendation is (a): record the wrapper's sha256 in the Day-0 manifest and have + the agent report the installed file's hash, so drift becomes visible. +4. **C6 customer-restore pass** for the alpha. -- deliver **no functional change** to felhom-pve; -- restart the agent on a **production host at a remote site** for that nothing; -- and, once the operator saved Day-0 manifest Agent → 0.90.1 in the same sitting (as STOP-1 - contemplates), **advertise that the R-39 fix shipped as a versioned artifact — which it never did.** - A fresh box gets the fixed wrapper from `main` whether it installs binary 0.90.0 or 0.90.1, so the - version number would be a false signal about fleet state. +## 11. Observations -Per §0 ("if anything requires touching agent source → STOP and report") and the Part-3 STOP rule, I -stopped **before** the publish. **Nothing was published. Nothing was deployed. `felhom-agent` has no -new commit and felhom-pve is byte-for-byte as found.** The built binary sits unpublished in the -session scratchpad; the registry pre-check (the diagnosed state) was confirmed and left as-is: - -``` -GET .../generic/felhom-agent/0.90.1/felhom-agent -> 404 (authed and anonymous) -GET .../generic/felhom-agent/0.90.0/felhom-agent -> 200 (control) -``` - -**This needs an operator ruling** — see §8. - -## 6. STOP-1 — not yet run (needs the operator) - -Preconditions are green (§2 P1) and the target image is published and verified. The assertion is -ready to run the moment Viktor saves the floor; I have not observed a swap, so **no R-23(a) evidence -exists yet and the map row is untouched.** - -## 7. Doc flips deliberately NOT made - -- **R-23(a) → banked**: requires STOP-1 single-fire evidence. Does not exist. Not flipped. -- **"config/state change round-trips in seconds" PARTIAL → PROVEN-LIVE**: same gate. Not flipped. -- **R-48 → SHIPPED** and the **R-39 artifact note** in `ROADMAP.md`: not written this session. R-48's - is merely deferred until the live click-through; the R-39 line is deferred **because the sentence - the task specified would have been false** (§5). - -## 8. Remaining — human - -1. **Ruling on Part 3 (blocking).** Options as I see them: (i) publish 0.90.1 anyway as a - version-hygiene artifact, with the CHANGELOG stating plainly that the binary is unchanged and the - R-39 fix rode the wrapper — cheap, but the Day-0 manifest still implies more than it delivers; - (ii) leave 0.90.0 as the published binary and close R-39's cheap half in the docs, recording that - the wrapper is delivered unversioned from `main`; (iii) treat "a privileged host artifact tracks - `main` with no version" as the real defect and spec that as TASK-B alongside R-39's primary half. - **Recommendation: (ii) now, (iii) as the actual fix** — (iii) is what stops a root-owned wrapper - from silently tracking `main`, which is the same class of problem R-39 already demonstrated once. -2. **STOP-1**: floor save 0.153.0 → **v0.154.0**, password-gated. I observe and assert single-fire. -3. **Wizard click-through + one non-destructive Ellenőrzés** through the new surface. -4. **TASK-B** ruling still pending (R-39 primary half: the descriptor-hash re-apply gap). -5. **C6 customer-restore pass** for the alpha. - -## 9. Observations - -- **`docker_run_volume_path_gate.py` fails at `HEAD`, unrelated to this work.** - `internal/appexport/estimate.go:179` has an unreviewed `docker run -v`. Verified pre-existing by - stashing my changes and re-running (still rc=1). Left alone — out of scope — but it means that gate - is currently not a usable signal. -- **The `?`-vs-`&` redirect bug (§3) is the kind the wizard makes more likely**, since app-scoped - pages now carry query strings as a matter of course. `offboxRedirectTo`'s REUSE.md row now carries - the trap. -- **`felhom-pve` is up and healthy** despite the vacation note in `CLAUDE.md` saying the agent is - down: `systemctl is-active` → `active`, `felhom-agent 0.90.0`. The F1 static-`.162` mitigation is - holding; the `CLAUDE.md` TEMPORARY block is stale on that point. -- **P3's accented-grep trap fired as documented.** ASCII-safe substrings were required; an accented - pattern through `ssh → pct exec → bash -c` returned a false 0. Used a local Python pass over the - fetched bytes instead of grepping remotely. +- **The most valuable finding came from the operator, not the tests.** The phase strip he questioned + was the visible symptom of a real bug — the strip and the progress banner disagreeing on one screen. + A UI element that renders derived state is an assertion the customer can check; that is worth more + than its decorative cost. +- **`docker_run_volume_path_gate.py` fails at `HEAD`, unrelated** (`internal/appexport/estimate.go:179`). + Verified pre-existing by stashing and re-running. Left alone — but that gate is currently not a + usable signal. +- **`opstatus.go` says in its own header that it is the DISPLAY surface** and does not gate + concurrency. I read the concurrency flag anyway. The file told me; I did not check. +- **`felhom-pve` is up and healthy** (agent 0.90.0, `active`) — the "agent is DOWN" TEMPORARY block in + `felhom-controller/CLAUDE.md` is stale and should be trimmed on return. +- **P3's accented-grep trap fired as documented** — used a local Python pass over fetched bytes rather + than grepping accented patterns through `ssh → pct exec → bash -c`.