From 61c1391ff68a623a05a1bec3289b9e6963ea0eb8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 13 Jul 2026 21:14:18 +0200 Subject: [PATCH] docs: REPORT covers v0.128.0 (probe numbers, A-F results, red-proofs, both guests live) --- REPORT.md | 147 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/REPORT.md b/REPORT.md index 220ffd0..06f8b86 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,67 +1,92 @@ -# REPORT — v0.127.1–.3: escrow wizard polish (CTA visibility, Hungarian preflight, typed-back highlight, manual hide toggle, live-code copy) +# REPORT — v0.128.0: chunked browser .fab upload on /import (tunnel-proof) -**Date:** 2026-07-13 · **Version:** controller v0.127.3 (from v0.127.0, baseline `a5041da` confirmed) · Presentation-only; MinAgent unchanged (0.88.0 for the wizard); agent/felhom.eu untouched. Commits `156e7d5` (v0.127.1) + `788f8e6` (v0.127.2) + `8eed73b` (v0.127.3) on `main`. Source: four findings from Viktor's supervised wizard passes (drill + demo), plus his live follow-up on the v0.127.1 blur. +**Date:** 2026-07-13 · **Version:** controller v0.128.0 (baseline v0.127.3 `59e4ca7` reconfirmed +against live Gitea; local == origin/main) · Commit `db16371` on `main` · MinAgent unchanged (no +agent coupling — the feature is controller-local fs + HTTP). Implements +TASK-fab-browser-upload (ruling F3 2026-07-13: chunked). -**v0.127.2 (same evening):** the auto-blur-on-focus was REVERSED on Viktor's live finding — the -code blurred exactly when the customer clicked into a verification input to type from the screen. -The code now stays visible; the "Elrejtés"/"Megjelenítés" toggle appears with the reveal and is -manual-only (kept for screen-share situations). Render test asserts no `onfocus` auto-blur remains. +## Step-0 probe — the Cloudflare edge cap, measured on the REAL tunnel (before any code) -**v0.127.3 (same evening):** the reveal warning now states EXPLICITLY that the displayed code is -already the LIVE one (the supersede happens at upload, inside the ceremony job, before display): -"Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentse el most: a kód -többé nem jeleníthető meg." Pre-generation cancel already existed (the "Mégsem" beside "Kód -létrehozása"); the render test now pins it. The typed-back step KEPT (proof-of-capture friction + -transcription-error catch — with the manual-only blur it verifies engagement, not paper accuracy; -Viktor briefed, his call if he ever wants it dropped). - -## Files changed - -- `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. - -## Tests + gates - -`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 - -| Target | Verification | +| Probe | Result | |---|---| -| demo 9201 | `gitea.dooplex.hu/admin/felhom-controller:0.127.3 Up (healthy)` (0.127.1/.2 each verified healthy before it; `controller_started` events pushed) | -| drill guest (qm 300) | `gitea.dooplex.hu/admin/felhom-controller:0.127.3 Up (healthy)` | +| POST 120 MiB (zeros) → `https://felhom.demo-felhom.eu/step0-cap-probe` | **HTTP 413 "Payload Too Large" from the edge** (`Server: cloudflare`, `CF-RAY a1aa78cd1f04324d-VIE`, cloudflare-branded HTML body). Rejected on the declared Content-Length: only ~1.1 MB left the client, 0.41 s. | +| POST 80 MiB, same target | **HTTP 302 → `/login?next=…`** — the controller's auth redirect, i.e. the origin was reached (edge passage PROVEN). 0.48 s; the origin answered before consuming the body (unauth redirect), so no full-upload throughput datum — the edge decision is header-based, which is the datum that matters. | + +Gotcha recorded: local DNS (192.168.0.250) overrides `felhom.demo-felhom.eu` to the LAN guest +(192.168.0.139), and curl's default `Expect: 100-continue` lets the server answer before the body +is sent — the honest probe needed `--resolve` onto Cloudflare's public IP (172.67.149.59) plus +`-H "Expect:"`. **Outcome: cap confirmed at ~100 MB → `uploadChunkBytes` = 64 MiB (the spec's +default), server-side per-request cap 96 MiB.** + +## What shipped + +- `controller/internal/web/handler_export_upload.go` (new) — `POST + /api/export/upload/{init,chunk,finalize,abort}` inside `ServeExportAPI` (inherits the main.go + `RequireAuth(CsrfProtect(...))` mount — nothing added at the mux). Single-flight slot + (netstorage-job shape); init sanitizes to a `[A-Za-z0-9._ -]` base name + mandatory `.fab`, + free-space gate (declared + 1 GiB margin, Hungarian error with both numbers); chunk requires + `offset == received` (else 409 + `received_bytes` echo) and streams via `io.Copy`; finalize = + exact declared size (else 422 + `.part` deleted) → fsync → atomic rename, collision → lowest-free + `"name (N).fab"` computed from the ORIGINAL name (never `(1)(1)`); abort deletes the `.part`. + Startup GC sweeps `*.part-*` on every registered drive (wired in `main.go` after + `SetAppExporter`); 15-min idle timer aborts server-side. No client-side sha256 — deliberate, + rationale in the file header (WebCrypto can't stream-hash multi-GB; `.fab` self-validates at + import). +- `controller/internal/appexport/estimate.go` — `diskFree` exported as `DiskFree` (REUSE.md row + added); web seam `uploadDiskFree` package var. +- `controller/internal/web/templates/app_import.html` — upload zone above the bundle list + (drag-and-drop + "Fájl kiválasztása", accept=".fab"), progress "Feltöltés: {pct}% ({done} / + {total} GB)" + "Megszakítás", spec'd Hungarian error strings; `File.slice` sequential loop with + one retry per chunk re-synced from the 409 echo; on success the page reloads (the EXISTING scan + renders the row — no second lister). Copy-by-hand hint now names BOTH paths. Gates green: + template_id, emoji, native_confirm, mojibake. + +## Tests (all green; full module `go build && go vet && go test ./...` = 23 packages ok) + +| Scenario | Test | Result | +|---|---|---| +| A happy path (3 chunks byte-exact, existing scan lists it, no `.part` residue) | `TestFabUpload_HappyPath` | PASS | +| B offset replay → 409 + echo, disk unchanged, re-sync continues | `TestFabUpload_OffsetMismatchRejected` | PASS | +| C traversal neutered inside exports; ext/charset/hidden refused; oversize (faked statfs) → Hungarian msg with 3.0/2.0 GB; 2nd init → 409 | `TestFabUpload_SecurityGates` | PASS | +| C route wrap (unauth POST → 401 on all four paths via the main.go-shaped mount) | `TestFabUpload_MountInheritsAuth` | PASS | +| D finalize size mismatch → 422, `.part` deleted, slot freed | `TestFabUpload_FinalizeSizeMismatch` | PASS | +| E startup GC removes stray `.part`, keeps real bundles; idle-expiry deletes + frees slot | `TestFabUpload_GCAndIdleTimeout` | PASS | +| F collision → `app (1).fab`, re-run → `app (2).fab`, original bytes untouched | `TestFabUpload_CollisionLowestFreeSuffix` | PASS | +| abort deletes `.part` + frees slot | `TestFabUpload_Abort` | PASS | + +**Red-proofs (§10, all run + reverted, `git diff` clean):** +- **C** — sanitize bypassed (raw client filename used): `TestFabUpload_SecurityGates` FAILED with + "sanitized upload must land inside the exports dir" (the file materialized at the traversal + target `{drive}/evil.fab`). +- **B** — offset check removed (append regardless): `TestFabUpload_OffsetMismatchRejected` FAILED + with "replay: got 200, want 409". +- **F** — naive overwrite (rename straight to the requested name): + `TestFabUpload_CollisionLowestFreeSuffix` FAILED with `first collision landed as "app.fab", + want "app (1).fab"`. + +## Deploy + live verification + +- Image `gitea.dooplex.hu/admin/felhom-controller:0.128.0` + (`sha256:4c943ea1…`) built on 180. +- **Demo guest 9201**: `Up … (healthy)` on 0.128.0; selfupdate log confirms "Current version + 0.128.0 is up to date"; no ERROR/FATAL. **Drill guest** (nested PVE 192.168.0.152, guest 9201): + `Up … (healthy)` on 0.128.0. +- Route live-checked on the deployed box: unauthenticated `POST /api/export/upload/init` → 401 + JSON both via LAN and **through the real Cloudflare edge** (`--resolve` onto 172.67.149.59) — + the new path is deployed and auth+CSRF-wrapped on the customer path. + +## NOT live-validated (Viktor's ~5-minute leg) + +The end-to-end multi-GB browser upload through the real tunnel needs a dashboard login (CC has no +customer session). Suggested test: export a real app to `.fab`, download it via the browser, +re-upload it on `/import`, import it — the full circle. Everything beneath the login was +exercised: edge cap probed, endpoints tested A–F, deployed route answers through the tunnel. ## Observations -- 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. + +- The per-drive target picker was deliberately left out (v1 = default drive only, per §12); if a + customer keeps exports on a non-default drive, the upload still lands on the default one and the + scan shows both — acceptable, but a picker is the natural follow-up. +- `controller/cmd/controller` is matched by a gitignore rule — `main.go` changes need + `git add -f` (it IS tracked; add refuses the path without `-f`). +- Startup GC logs only when it removes something; the clean deploy logged nothing (expected).