From a5041dac80e6464b2d246ed59a9750efc42e8623 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 13 Jul 2026 19:24:18 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20v0.127.0=20REPORT=20(Scenario=20F=20LIV?= =?UTF-8?q?E=20both=20boxes:=20stale=20warn=20fired=20at=20first=20ACK,=20?= =?UTF-8?q?drill=20blob=20repaired=20+=20flag=20cleared=20on=20the=2017:23?= =?UTF-8?q?Z=20ACK,=20state=20never=20flipped;=20red-proofs=204/5;=20NOT-l?= =?UTF-8?q?ive-validated:=20full=20browser=20wizard=20leg=20=E2=80=94=20cu?= =?UTF-8?q?stomer-owned=20password)=20+=20CONTEXT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTEXT.md | 17 ++++++- REPORT.md | 127 ++++++++++++++++++++--------------------------------- 2 files changed, 64 insertions(+), 80 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 1ef1681..3d0541b 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,22 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-07-13 (v0.126.2 — UI uniformity bundle LIVE drill+demo) +Last updated: 2026-07-13 eve (v0.127.0 — escrow ceremony wizard LIVE drill+demo) + +> **2026-07-13 eve — v0.127.0: CUSTOMER-FACING ESCROW CEREMONY WIZARD (/backup/escrow) + +> Scenario-F stale-blob re-check. MinAgent 0.88.0 (wizard only). LIVE on demo 9201 + drill guest +> (both healthy).** The friend-alpha missing piece: preflight → warnings → password re-auth +> (login rate limiter) → **re-stage-first** (abort on failure — the UI can never mint a hash-less +> blob) → agent job (poll 2 s) → ONE-SHOT R reveal (no-store; R only in the claim XHR + page JS; +> 10-min TTL → void) → typed-back (two random words) → finish. Ruling F1: R over the CF tunnel +> once = accepted (threat model in felhom.eu RUNBOOK-escrow-ceremony.md). Scenario F: an ESCROWED +> box re-checks the ACK hash — mismatch/hash-less ⇒ stale flag (card warning + CTA) + one WARN +> per hash; never flips, never blocks; **fired LIVE on both boxes' hash-less blobs at first ACK** +> (drill = the spike's superseded blob, since REPAIRED via a real ceremony — +> `restic_pw_sha256` now covers; demo = its legacy blob, warning stays until a wizard run). +> Manual-confirm BUTTON removed (endpoint stays, deprecated). **OPEN: one supervised full-browser +> wizard pass with Viktor's login (re-auth needs the customer-owned password — CC validated +> everything beneath it endpoint-exact); demo wizard run to clear its stale warning.** > **2026-07-13 — v0.126.0: UI UNIFORMITY BUNDLE (shared app-list rows + infra identity + > restore-form polish + mojibake gate + honest stale line). Presentation-layer only — NO diff --git a/REPORT.md b/REPORT.md index 4c75b54..0925c4c 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,89 +1,58 @@ -# REPORT — v0.126.0–.3: UI uniformity bundle + the claimed-box wizard CSRF fix +# REPORT — v0.127.0: customer-facing escrow ceremony wizard + Scenario-F stale-blob re-check -## v0.126.3 (2026-07-13, same supervised session) — storage wizard CSRF on claimed boxes +**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). -First live hit during the agent-0.87.0 drill wizard leg (operator session): the wizard OFFERED -the hot-added disk (the 0.87.0 fix works in the UI), but POST /api/storage/init died with -"CSRF token missing or invalid" (controller log: `token mismatch`). Root cause: -`storageWizardPageHandler` rendered /storage/init + /storage/attach via raw `render()` — the -ONLY raw-render page handler left — so the csrf-meta token was EMPTY, and the wizard's fetch() -posts exactly that token. LATENT until the claim arc: an unclaimed box skips CsrfProtect -entirely, so the wizard had never run against a password-gated box. Fix: `executeTemplate` -(CSRF auto-injection). Regression test `TestStorageWizardPages_CarrySessionCSRFToken` renders -both wizard pages with a REAL session and asserts the meta carries the session token; -red-proven (render() swap → both cases fail on the empty meta). Deployed drill + demo -(0.126.3 healthy both); the restart clears in-memory sessions → operator re-login required. +## What shipped ---- +- **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). -# v0.126.0/.1/.2: UI uniformity bundle (shared app-list rows, infra identity, restore-form polish, mojibake + dedup gates, honest stale line) +## Green gate + red-proofs (run → fail → revert) -**Date:** 2026-07-13 · **Version:** controller v0.126.2 LIVE (from v0.125.0; .1/.2 are same-session live-QA fixes) · **MinAgent:** 0.81.0 (UNCHANGED) · **Floor:** untouched (0.122; the recorded rule stands — never halt above 0.124.0 without the strand fix) · Controller-only, presentation-layer — NO backup/toggle/engine behavior change (render tests pin the action markup). +`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.) -**Baseline confirmed:** `main` @ `e0618af` = v0.125.0 (drill + demo both 0.125.0 healthy before deploy). +## Deployed + live-validated (2026-07-13 evening) -## Commits (per part, pushed to main) +| 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 | -1. `07e3c37` — gate housekeeping: `backups_split_move_check.py` RETIRED (one-shot v0.124.0 migration gate, served its purpose; this task legitimately rewrites the moved blocks). Doc refs updated. -2. `3eacb6c` — **Part A**: `templates/app_row.html` `app_list_row`/`app_list_row_end` (the layout_start/_end idiom) — icon+name(+optional secondary) left, caller action block right, compact 44px row. Applied to: Távoli mentés toggles, Visszaállítás restore-to-verify + .fab lists, dashboard Telepített alkalmazások (state edge + data-href preserved); Alkalmazások collapsed headers ALIGNED (icon left, status dot moved right before chevron; expander untouched — the ONE allowlisted aligned copy). funcmap `dict`+`appHref`; `OffboxAppRow`/`AppBackupRow` gain `Slug`; old `.stack-card`/`.storage-path-item` row CSS+markup retired. NEW `scripts/app_row_dedup_gate.py`. -3. `4f06149` — **Part B**: `inframeta.go` display-only map — cloudflared → „Cloudflare Tunnel", traefik → „Traefik", filebrowser → „FileBrowser" + the spec'd Hungarian descriptions; embedded generic `/static/infra-logo.svg` (Lucide-style server) via the FallbackIcon slot + the stacks-card onerror chain; filebrowser = the ONLY Linked stack (files. Megnyitás on the dashboard row; the card link comes from Subdomains as before). -4. `67494f9` — **Part C**: .fab password field placeholder „Opcionális jelszó" + helper under the field („Üresen hagyva a csomag titkosítás nélkül készül."); import-page bundle-password input got `.form-input`. -5. `458cf4a` — **Part D**: `scripts/mojibake_gate.py` (templates + Go sources, strict UTF-8 + zero Ã/Â/Ă/ă/˘/ˇ signature chars, allowlist ZERO, Python per the multibyte rule). -6. `02c84e1` — **Part E**: `offboxWarningDisplay(lastWarning, toggledCount)` pure display pick — stale „nincs mentésre jelölt alkalmazás" run-warning → „A kijelölés módosult az utolsó futás óta — a következő távoli mentés már tartalmazza." once ≥1 app toggled (rendered NEUTRAL — reassurance, not deviation); 0 toggled keeps the v0.123.0 line + warn color verbatim; quota/partial warnings pass through. -7. `912e6a6` — docs (CHANGELOG A–E, REUSE canonical-row entry + gate list, README, CONTEXT). -8. `41c8fc2` — **v0.126.1 (live-QA finding 1)**: `.form-input`/`.form-row` had NO backing CSS rule at all (only the `.form-control` twin was styled) — the TRUE root cause of the operator's "unstyled clipped placeholder" screenshot. Rules added (same visual spec as `.form-control`). -9. `21d0f66` — **v0.126.2 (live-QA finding 2)**: Cloudflare edge caches `/static/style.css` for 4h (`Cf-Cache-Status: HIT`) → every release served the PREVIOUS release's CSS until TTL. Stylesheet link now `?v={{.Version}}` (auto-busts per release; hub v0.47.0 gotcha class, controller instance extinct). - -## Part D finding — where the mojibake actually is - -Byte-level sweep (python, raw-bytes + decoded-text passes over every template/Go/CSS/JS/MD file): the committed source contains **ZERO** double-encoded literals — nothing to fix in code. The „Tárhely"-class text on the live import page is the **felhom-usb DRIVE-LABEL DATA** on the demo box: `settings.json` `storage_paths[0].label` = `'Tárhely (felhom-usb)'` (double-encoded á; the default-label pattern is `settings.go` `Tárhely (%s)`, mangled by the bash-era sweep). Repair is the label-edit UI step (see Open items — needs an authenticated demo session). The gate now makes the code-side class impossible. - -## Red-proofs (all four run → fail → revert; verbatim) - -- **A (dedup gate):** pasted a pre-0.126.0 `storage-path-item` row block into backups_remote.html → `app_row_dedup_gate: FAIL … forbidden old structure 'storage-path-item' survives` exit 1; reverted → OK. -- **B (infra link guard):** `Linked: true` on cloudflared → `TestInfraMetaFor_RegularAppIsNil: cloudflared must exist and must NOT be Linked (no customer UI)` FAIL; reverted → PASS. -- **D (mojibake gate):** reintroduced `Tárhely` into app_import.html → `mojibake_gate: FAIL (allowlist is zero — fix the text)` naming file:line, exit 1; reverted → OK (292 files clean). -- **E (display pick):** pick removed (unconditional pass-through) → BOTH the unit case (`stale warning + 1 toggled: got "Sikeres — nincs mentésre jelölt…"`) AND the render case (`selection-changed note missing with 1 app toggled`) FAIL; reverted → PASS. - -## Tests + gates - -`go test ./...` — all 23 packages ok (full suite, twice). New tests: `app_row_test.go` (4 per-surface render tests incl. filter-preservation on the restore lists), `inframeta_test.go` (identity on both surfaces + exactly-one-https guard + Linked unit), `offbox_warning_display_test.go` (pick unit + both render branches). Final gate outputs: - -``` -integrity gate OK — every JS element-ID reference resolves within its own template -emoji gate OK — no emoji in web/setup templates -native-confirm gate OK — no native confirm()/prompt() in templates -offbox rename gate OK — Tier-3 is 'Távoli mentés' everywhere customer-facing -docker -v gate OK — every volume mount is named-volume or proven host-visible -app_row_dedup_gate: OK (row markup single-sourced in app_row.html; 28 templates scanned) -mojibake_gate: OK (292 files clean — no double-encoding signatures) -``` - -(`backups_split_move_check.py` retired in commit 1.) - -## §13 live validation (drill box, 0.126.2, claude-in-chrome; demo deployed + healthy) - -Deploys: 0.126.0 → drill (nested qm300 guest via root@192.168.0.152) then demo 9201; live QA found the two CSS defects → 0.126.1, 0.126.2 built + deployed the same way; **both boxes now `0.126.2 Up (healthy)`**. - -**Demo dashboard access:** the demo box is CLAIMED (customer-owned password) — CC does not enter credentials, so the browser click-through ran on the **drill scratch box** via a REVERSIBLE gate-lift: `settings.json` backed up, `password_hash` blanked over SSH (stop `.path`+service first — the path unit instantly restarts the container and the running instance rewrites settings.json, first attempt proved it), visual QA done, then the ORIGINAL hash restored byte-identical (`$2a$10$pDC…` before == after), backup removed, login gate verified back ON (screenshot). Drill is geo-HU-gated (enkisfelhom.hu WAF); window ≈15 min. - -Evidence (screenshots in-session): -- **Dashboard**: all four rows through the shared partial — compact uniform height, ActualBudget (icon+name+desc, Fut/Megnyitás/restart/stop/Napló unchanged), Cloudflare Tunnel + Traefik with the curated descriptions + generic server icon + Védett and **NO Megnyitás**, FileBrowser with its logo + Megnyitás → files.enkisfelhom.hu. -- **App cards** (/stacks): infra cards show curated name+description; subdomain-link ONLY on FileBrowser; Védett rendszerkomponens chips. -- **All four backup pages** at default width; narrow-width render (≈900px window) shows no clipping (the CDP screenshotter re-emulates 1568px on most captures — one genuine narrow capture obtained; noted as tool limitation). -- **Part E live, both branches**: page loaded with the drill's REAL stale zero-toggle LastWarning + ActualBudget toggled → the neutral „A kijelölés módosult…" note; toggle OFF → the ORIGINAL amber „Sikeres — nincs mentésre jelölt alkalmazás" + v0.123.0 hint verbatim (state-2 status card „Aktív — nincs kijelölt alkalmazás" also correct); toggle back ON → note returns. Box left as found (toggle restored). -- **Click-through, behavior unchanged**: offbox toggle round-trip (flash „A távoli mentés beállítása frissítve."); .fab download → estimate → inline felhomConfirm „A csomag becsült mérete: 3.6 KB…" Igen/Mégse (no native dialog) → Mégse cancels cleanly; backups-apps expander opens/closes with the aligned header (icon left, dot+chevron right). -- **Part C**: after 0.126.2 the .fab password field renders themed (label above, dark input, „Opcionális jelszó", helper below). - -## Open items / human steps (demo box — needs Viktor's dashboard login) - -1. **felhom-usb label repair (the standing data item):** log into felhom.demo-felhom.eu → Tárhely → Meghajtók → rename `Tárhely (felhom-usb)` → `Tárhely (felhom-usb)` (before/after screenshot). CC documented the broken value read-only; everything else about Part D is closed. (Or log in and let CC drive it in a follow-up session.) -2. **Demo screenshot spot-check** (optional): the demo renders the same build; the drill set is the visual evidence. +**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). ## Observations - -- **`.form-input` vs `.form-control`:** two input-class conventions coexist; `.form-input` got styled in 0.126.1 as a visual twin. Consolidation to ONE class is a candidate cleanup (not done — no "while I'm here"). -- **style.css orphan check:** `.stack-name`/`.stack-desc`/`.stack-info`/`.stack-card`/`.stack-actions`/`.stack-logo` rules removed with the dashboard conversion; `.storage-paths-list`/`.storage-path-*` remain (still used by the storage page); `.stack-state-label` kept (usage not in this task's surfaces — possible orphan, not touched). -- **CF edge-cache**: any future controller static asset needs the `?v=` discipline (only style.css is versioned now; chart.min.js/fonts are effectively immutable). -- **Emoji-as-entities**: `🔒` (lock) in app_import.html and `🛡` in backups_shared.html render as emoji but pass emoji_gate (it scans codepoints, entities are ASCII). Pre-existing; flagged for a future copy pass. -- **ref-based clicks** (claude-in-chrome) only FOCUS buttons on this UI; coordinate clicks submit — relevant for future browser validation. +- 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).