docs: v0.127.1 REPORT (polish deployed both guests, healthy; §10 red-proofs N/A — presentation-only; visual leg awaits Viktor)
This commit is contained in:
@@ -1,58 +1,54 @@
|
||||
# REPORT — v0.127.0: customer-facing escrow ceremony wizard + Scenario-F stale-blob re-check
|
||||
# REPORT — v0.127.1: escrow wizard polish (CTA visibility, Hungarian preflight, typed-back highlight, blur-on-verify)
|
||||
|
||||
**Date:** 2026-07-13 · **Version:** controller v0.127.0 (from v0.126.4, baseline `51c871ad` confirmed) · **MinAgent: 0.88.0** (wizard only — everything else runs on older agents). Commit `08a966b` on `main`. Spike basis: felhom.eu `SPIKE-controller-escrow-2026-07-13`. Operator ruling F1 (2026-07-13): R over the CF tunnel once at reveal = accepted risk (threat model in felhom.eu RUNBOOK-escrow-ceremony.md).
|
||||
**Date:** 2026-07-13 · **Version:** controller v0.127.1 (from v0.127.0, baseline `a5041da` confirmed) · Presentation-only; MinAgent unchanged (0.88.0 for the wizard); agent/felhom.eu untouched. Commit `156e7d5` on `main`. Source: four findings from Viktor's supervised wizard passes (drill + demo).
|
||||
|
||||
## What shipped
|
||||
## Files changed
|
||||
|
||||
- **Wizard** `/backup/escrow` (`web/escrow_handlers.go`, `templates/backups_escrow.html`):
|
||||
preflight → warnings (+supersede variant) → password re-auth → run (2 s poll) → ONE-SHOT
|
||||
reveal (`no-store`; R only ever in the claim XHR + the page's JS scope) → typed-back (two
|
||||
random words, client-side) → finish; void/410 state honest. `/api/escrow/{preflight,start,
|
||||
status,claim}` behind session auth + CSRF (mux, the disk-API pattern).
|
||||
- **Start order (load-bearing):** re-auth (login rate limiter) → **re-stage-first** (abort on
|
||||
failure — no hash-less blob can be minted from the UI) → version gate (≥ 0.88.0, header-absent
|
||||
= older, fail-closed) → trigger. Each refusal exits with the agent untouched.
|
||||
- **Scenario F** (`report/escrow_confirm.go`): escrowed-state hash re-check on every ACK —
|
||||
mismatch OR hash-less blob → in-memory stale flag (card warning + re-ceremony CTA) + one WARN
|
||||
per distinct hub hash; match/auto-confirm clears; NEVER flips state, NEVER blocks runs.
|
||||
- **Card rework** (`backups_remote.html`): manual-confirm BUTTON removed (endpoint stays,
|
||||
deprecated); pending/stale/clean/agent-too-old states per spec copy. All 4 UI gates green
|
||||
(template_id, emoji, native-confirm, offbox-rename).
|
||||
- `controller/internal/web/templates/backups_remote.html` — Part 1: the escrowed-state
|
||||
"Új helyreállítási kód készítése" CTA is a real `btn btn-sm btn-outline` secondary button
|
||||
(outline, deliberately NOT primary — escrowed is healthy/available-not-urgent; the stale
|
||||
variant keeps its primary CTA). Hint line kept above it; `{{if .EscrowAgentOK}}` gate kept.
|
||||
- `controller/internal/web/templates/backups_escrow.html` —
|
||||
- Part 2: `pfDetail()` + `PF_FAIL_DETAILS` map the agent's operator-English preflight `detail`
|
||||
strings to Hungarian per the spec table (OK rows: value-bearing details only — storage id,
|
||||
age path, "előkészítve"; boolean-OK rows: no detail; not-OK rows: Hungarian text + the raw
|
||||
agent detail in a muted `var(--text-3)` diagnostic span; unknown ids fall back to the raw
|
||||
detail — a failure is never blanked; `staged_secret` keeps its informational dot).
|
||||
- Part 3: span-per-word code rendering (`createElement` + `textContent` + `createTextNode`
|
||||
ONLY — R still never flows through innerHTML; comment documents why the spans keep the same
|
||||
no-parse-context guarantee); the two typed-back words highlighted `var(--warn)`/600;
|
||||
`verifyIdx` now chosen BEFORE rendering; `finishWizard`'s `textContent=''` verified to clear
|
||||
the child spans.
|
||||
- Part 4 (optional — implemented, trivially strikeable): first focus on either verification
|
||||
input blurs the code (`filter: blur(6px)`, inline-style toggle — no style.css edit, no
|
||||
cache-bust needed) + a `btn-ghost` "Megjelenítés"/"Elrejtés" local toggle, so the typed-back
|
||||
exercises the WRITTEN copy. R stays in the JS closure throughout.
|
||||
- `controller/internal/web/escrow_wizard_test.go` — render assertions: the btn-outline CTA
|
||||
markup on the escrowed card; the Hungarian not-staged + sudo-missing strings; the blur toggle
|
||||
label; and the three raw English literals ("DR tier applied", "hub upload target configured",
|
||||
"sudo grant listed") asserted ABSENT from the page source.
|
||||
- `CHANGELOG.md` — v0.127.1 entry.
|
||||
|
||||
## Green gate + red-proofs (run → fail → revert)
|
||||
## Tests + gates
|
||||
|
||||
`go build && go vet && go test ./...` green at every phase; all four UI gates green. Test
|
||||
functions 489 → **502** (+5 stale table, +8 wizard/render). Red-proofs:
|
||||
4. `Reconcile` reverted to early-return-when-not-pending → all four `TestEscrowStale_*` FAILED.
|
||||
5. re-auth compare bypassed (compiling mutation) → `TestEscrowStart_SecurityGates/wrong_password`
|
||||
+ `/rate_limited` FAILED (agent got called / counter untouched).
|
||||
(1/2/3/6 are agent-side — felhom-agent/REPORT.md.)
|
||||
`go build ./... && go vet ./... && go test ./...` — green (502 test funcs, no count change:
|
||||
existing render test extended). All four UI gates green (template_id, emoji, native-confirm,
|
||||
offbox-rename). **§10 red-proofs: N/A** — presentation-only, no correctness/security fix
|
||||
(stated per the task). JS runtime behavior (span construction, blur toggle) is covered by code
|
||||
review + the render-source assertions; **the visual leg (steps 5–6 highlight + blur) awaits
|
||||
Viktor's eyeball pass on his next login** — no ceremony was run anywhere (§12: the demo and
|
||||
drill boxes hold fresh real recovery codes; a run would supersede them).
|
||||
|
||||
## Deployed + live-validated (2026-07-13 evening)
|
||||
## Deployed
|
||||
|
||||
| Check | Result | Method |
|
||||
|---|---|---|
|
||||
| demo guest 9201 | `0.127.0 Up (healthy)` | docker ps |
|
||||
| drill guest 9201 (VM qm 300) | `0.127.0 Up (healthy)` | docker ps |
|
||||
| **Scenario F fires LIVE (drill)** | first ACK after deploy (17:08Z): `[WARN] [escrow-confirm] STALE escrow: the hub's current blob carries NO password hash (hash-less supersession)…` — the EXACT state the spike left; state stayed escrowed, runs unblocked | controller log |
|
||||
| Scenario F fires LIVE (demo) | same warn 17:07:42Z on the demo's legacy hash-less blob — honest per the truth table; fix = one wizard run (Viktor's login; operator follow-up) | controller log |
|
||||
| Ceremony repairs the blob | drill: stage-first (real secret) → agent job → hub `restic_pw_sha256=24a887bafce4…` == sha256(local repo password) | endpoint-exact + hub DB read-only |
|
||||
| **Stale flag CLEARS** | the 17:23:01Z ACK (first after the covering blob) processed with ZERO escrow-confirm output; `escrow_state` stayed `"escrowed"` in settings.json. The hub verifiably serves the covering hash, so the match path (the only branch for it) cleared the flag — the clear itself is silent by design and pinned by `TestEscrowStale_MatchClearsFlag` | controller log + settings.json + hub DB |
|
||||
| One-shot semantics | claim 1 = 200 (10-word R, never printed) → claim 2 = **410** → status claimed | endpoint-exact |
|
||||
| Wizard route + gate | `GET /backup/escrow` unauth → 302 `/login?next=%2Fbackup%2Fescrow` via the real in-guest traefik | curl through the guest |
|
||||
|
||||
**Method disclosure + NOT yet live-validated:** the drill box's dashboard password is
|
||||
CUSTOMER-owned (Viktor set it at the take-two claim), and the wizard's re-auth requires typing it
|
||||
— so the FULL browser wizard leg (login → preflight UI → re-auth → reveal → typed-back → finish)
|
||||
could not be driven autonomously and remains **NOT live-validated**; it needs one supervised pass
|
||||
with Viktor (5 minutes — also the demo box's own wizard run to clear ITS stale warning). Every
|
||||
layer beneath it was validated endpoint-exact with the real token over the real pinned channel
|
||||
(the exact calls the handlers make), plus full unit coverage of the handler order/gates. Also not
|
||||
live-validated: a real customer box (Peti), TTL expiry in real time (clock-jump-tested).
|
||||
| Target | Verification |
|
||||
|---|---|
|
||||
| demo 9201 | `gitea.dooplex.hu/admin/felhom-controller:0.127.1 Up (healthy)`; startup log clean, `controller_started (0.127.1)` event pushed |
|
||||
| drill guest (qm 300) | `gitea.dooplex.hu/admin/felhom-controller:0.127.1 Up (healthy)`; startup log clean |
|
||||
|
||||
## Observations
|
||||
- The demo's stale warning is a FEATURE surfacing a real pre-existing gap (its blob never covered
|
||||
the current password); expect the card warning on demo until a wizard run.
|
||||
- `AgentVersion()` is populated by ANY agent traffic; the wizard page/preflight naturally warm it
|
||||
before the start gate — a cold controller straight to POST /api/escrow/start could see "" and
|
||||
409 until first traffic (self-heals; worth remembering when scripting).
|
||||
- The wizard page embeds its Hungarian detail map in page JS — if the agent ever adds a seventh
|
||||
preflight item id, the fallback renders its raw detail (by design); add the Hungarian pair in
|
||||
the same commit that adds the id.
|
||||
- The stale-card CTA remains `btn-primary` (urgent) — only the healthy escrowed state was
|
||||
demoted to outline; the pending card's primary CTA is also unchanged.
|
||||
|
||||
Reference in New Issue
Block a user