docs: REPORT covers v0.128.0 (probe numbers, A-F results, red-proofs, both guests live)
This commit is contained in:
@@ -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
|
## Step-0 probe — the Cloudflare edge cap, measured on the REAL tunnel (before any code)
|
||||||
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.
|
|
||||||
|
|
||||||
**v0.127.3 (same evening):** the reveal warning now states EXPLICITLY that the displayed code is
|
| Probe | Result |
|
||||||
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 |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| 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) |
|
| 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. |
|
||||||
| drill guest (qm 300) | `gitea.dooplex.hu/admin/felhom-controller:0.127.3 Up (healthy)` |
|
| 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
|
## 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 per-drive target picker was deliberately left out (v1 = default drive only, per §12); if a
|
||||||
the same commit that adds the id.
|
customer keeps exports on a non-default drive, the upload still lands on the default one and the
|
||||||
- The stale-card CTA remains `btn-primary` (urgent) — only the healthy escrowed state was
|
scan shows both — acceptable, but a picker is the natural follow-up.
|
||||||
demoted to outline; the pending card's primary CTA is also unchanged.
|
- `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).
|
||||||
|
|||||||
Reference in New Issue
Block a user