docs: v0.125.0 path-strand fix — CHANGELOG + REPORT (probe transcript, red-proofs, round-trip evidence, Tier-1/2 mechanism) + CONTEXT + REUSE + README flows; hollow-bundle asymmetry recorded (containerized ≤0.124.0 exports suspect — re-export); floor note for the next train
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
This commit is contained in:
@@ -1,86 +1,72 @@
|
||||
# REPORT — v0.124.0: backups IA restructure (four sub-pages, Felhom-offsite status card, .fab browser download)
|
||||
# REPORT — v0.125.0: .fab volume path-strand data loss FIXED (IA finding 1, HIGH)
|
||||
|
||||
**Date:** 2026-07-13 · **Version:** controller v0.124.0 (from v0.123.0) · **MinAgent:** 0.81.0 (UNCHANGED) · **Floor:** untouched (rides the next train) · Controller-only.
|
||||
**Date:** 2026-07-13 · **Version:** controller v0.125.0 (from v0.124.0) · **MinAgent:** 0.81.0 (UNCHANGED) · **Floor:** may advance to 0.125.0 on the next train (must NOT stop at >0.124.0 without this) · Controller-only. **Disposition:** closes the v0.124.0 REPORT's finding 1.
|
||||
|
||||
## What shipped (commits `0ece2ba`, `aa967fb`, `753cd83`)
|
||||
## §3 live probe (ran FIRST, before any code)
|
||||
|
||||
1. **IA split** — `/backups` (Áttekintés), `/backups/remote` (Távoli mentés), `/backups/apps`
|
||||
(Alkalmazások), `/backups/restore` (Visszaállítás), sidebar children (Tárhely pattern).
|
||||
Sections MOVED verbatim from the v0.123.0 page — the committed one-shot
|
||||
`scripts/backups_split_move_check.py` verifies all 15 blocks against baseline `df7ad37`
|
||||
(whitespace-normalized; the two allowed divergences — tier-3 anchor retarget + the
|
||||
restore-to-verify relocation — are encoded explicitly). Shared handler builders extracted
|
||||
(`backupsCommonData`/`backupsOffboxData`); flash redirects, tier-3 anchors and the
|
||||
tier2-config back-link retargeted per page.
|
||||
2. **Felhom-offsite status card** — three honest states from local data, display-only by
|
||||
construction (no form/button inside the card; unit-enforced). Decision 2 honored: the card
|
||||
never changes anything.
|
||||
3. **.fab browser download** — existing export pipeline + staging dest + guarded streaming exit
|
||||
with estimate-first UX, post-stream cleanup and a 1h TTL sweep; per-app sequential batch;
|
||||
portability framing everywhere (decision 3). Import stays drive-scan.
|
||||
On the drill guest: scratch volume seeded with a subdir, a file, a symlink, an empty file and a
|
||||
non-root owner (1234:1234) → `docker create -v fabprobe:/vol alpine true` → `docker cp
|
||||
<cid>:/vol/. -` → tar (5120 B) → volume wiped → `docker cp - <cid>:/vol` → **everything
|
||||
byte-identical, symlink and uid/gid preserved**. Zero surprises; transcript in the session
|
||||
record. Green light for implementation.
|
||||
|
||||
## Tests + red-proofs (run → FAIL → restored)
|
||||
## What shipped (commit `466f427`)
|
||||
|
||||
- **Move check red-proof:** one moved block reworded → exit 1 ("1 block(s) rewritten"); restored → 0.
|
||||
- **Traversal guard red-proof:** guard loosened to raw prefix-matching → `../decoy.fab` came back
|
||||
**200 serving the decoy** — the test caught it (got 200, want 400). Restored → green.
|
||||
- **Round-trip + corruption (data-mutation red-proof):** real `executeExport` → bundle →
|
||||
real `executeImport` → byte-identical restored content; a mid-file-corrupted copy is REFUSED
|
||||
(job fails, app not started, nothing restored). Integrity = gzip CRC + manifest segment
|
||||
validation — there is NO per-file checksum (documented; the spec's "manifest checksum" wording
|
||||
adapted to reality).
|
||||
- Nav/state tests (marker per section, present on exactly one page; `.Page` ids), status-card
|
||||
state tests, TTL sweep test (fake mtimes/now). Full green gate + all five template gates green
|
||||
per commit.
|
||||
1. **Streaming legs** — `dockerExec` seam + `withVolumeHelper` (create → fn → ALWAYS `rm -f`,
|
||||
error paths included) + `exportVolumeTar` (`cp <cid>:/vol/. -` → file) and `importVolumeTar`
|
||||
(file → `cp - <cid>:/vol`). Zero shared paths: correct on bare metal AND containerized.
|
||||
2. **Scenario B** — volume-export failure is FATAL; `assertBundleDataComplete` blocks packaging
|
||||
any bundle whose manifest claims a missing/empty tar (volumes + HDD; the HDD leg also stopped
|
||||
pre-claiming subdirs before the tar succeeds).
|
||||
3. **Scenario C** — `validateBundleData` runs in import step 0, BEFORE the app is stopped or any
|
||||
volume removed; hollow bundles are refused with copy naming the ≤0.124.0-containerized-export
|
||||
cause and stating the app is untouched. Missing-tar soft-skips in both restore legs are hard
|
||||
errors now.
|
||||
4. **Scenario D** — `scripts/docker_run_volume_path_gate.py`: every `"-v"` arg in non-test Go
|
||||
needs an allowlist entry WITH ITS WHY. Gate output: 7 allowlisted usages, 0 violations.
|
||||
**Tier-1/Tier-2 mechanism, proven by inspection:** `backup.go` (dump) and `backup/restore.go`
|
||||
mount `dumpDir` — always a registered-drive namespace path (`/mnt/**`, `/opt/docker/**`),
|
||||
which the golden deployment bind-mounts into the controller container at IDENTICAL paths
|
||||
(container-inspect verified), so the daemon resolves them correctly; their volume-side mounts
|
||||
are named volumes (daemon-side). They are NOT in the strand class; unchanged.
|
||||
|
||||
## §13 live validation
|
||||
## Tests + the three red-proofs (run → FAIL → restored)
|
||||
|
||||
- **Deployed:** 0.124.0 on the drill guest (qm300) AND demo 9201, both healthy.
|
||||
- **Four-page walk (drill, screenshots in session record):** every moved control clicked once —
|
||||
tier-2 Beállítás (+ retargeted back-link), tier-3 toggle (off→on), restore-to-verify from its
|
||||
NEW home (`/backups/restore` flash), manual-target form open/close. Old `/backups` bookmark →
|
||||
Áttekintés on both boxes; sidebar active states correct.
|
||||
- **Status card:** state 3 (no card) and state 2 ("Aktív — nincs kijelölt alkalmazás") both
|
||||
live-rendered on the drill box; state 1 by unit render test (demo has a managed target — spec
|
||||
says do NOT unconfigure).
|
||||
- **.fab download:** drill ActualBudget — estimate 3.6 KB shown first, bundle 2102 B streamed,
|
||||
staging bundle removed (server log). Demo ActualBudget — downloaded to the operator machine,
|
||||
sha256-identical after placement on `felhom-usb/exports` (`e2a1233f…` both ends).
|
||||
- **Supervised import (Viktor GO):** ON DEMO (see finding 2 for why not drill): scan found the
|
||||
bundle, manifest read (exported-by 0.124.0), "Importálás kész! Az alkalmazás sikeresen
|
||||
visszaállítva.", ActualBudget Up (healthy). **The round-trip surfaced finding 1.**
|
||||
- **B:** `TestExport_HollowVolumeTarAbortsExport` (cp "succeeds" writing nothing → export fails
|
||||
naming vol1, no .fab). Red-proof: assertion disabled → "a hollow volume tar must FAIL the
|
||||
export — got success".
|
||||
- **C:** `TestImport_HollowBundleRefusedBeforeDestroy` (handcrafted hollow bundle → refused;
|
||||
asserts stopped=0, removedVolumes=0, zero docker calls, app not started). Red-proof: pre-flight
|
||||
disabled → **`refusal happened AFTER destruction: removedVolumes=1`** — the exact pre-fix
|
||||
disaster shape.
|
||||
- **D:** violating `docker run -v /tmp/x:/out` line added → gate exit 1; restored → 0.
|
||||
- Plus command-shape tests (create/cp/rm arg vectors pinned to the §3-probed shapes; import cp
|
||||
streams stdin; helper removed on cp failure; no partial tar left). Full green gate + all seven
|
||||
gates green.
|
||||
|
||||
## FINDINGS (both pre-existing, surfaced by this task's first real containerized round-trip)
|
||||
## §13 live validation (0.125.0 on drill + demo, both healthy)
|
||||
|
||||
1. **(HIGH — data-loss trap) Containerized .fab export ships EMPTY volume data.**
|
||||
`exportVolumeData` runs `docker run -v <volDir>:/out alpine tar …` where `<volDir>` is the
|
||||
controller-CONTAINER's `os.MkdirTemp` path — the guest's docker daemon resolves it against the
|
||||
HOST filesystem, so the tar lands stranded on the guest host (`/tmp/felhom-export-*/…`, found
|
||||
with ActualBudget's 66 KB tar in it) and the bundle's `data/volumes` is empty while the
|
||||
manifest claims volume data and the export reports SUCCESS. Import then wipes the app's
|
||||
volumes and brings it up EMPTY. Affects every golden/bootstrap (containerized) box for
|
||||
volume-backed apps; HDD-data apps are unaffected (direct FS copy). Demo's ActualBudget was
|
||||
repaired from the stranded tar; both temp dir and staged bundle cleaned. **Fix direction:**
|
||||
stage the docker-run output under a host-visible bind (e.g. the controller data volume) or use
|
||||
`docker cp`/stdout streaming; plus a post-export assertion that manifest-claimed volume tars
|
||||
exist in the bundle (fail LOUD).
|
||||
2. **(MEDIUM — agent-side, out of this controller-only task) Drive wizard offers zero candidates
|
||||
on a legacy-boot PVE.** With LVM root and no ESP mount, every system mount resolves to
|
||||
device-mapper, `storage.SystemDisks` finds no raw disk → `sysKnown=false` → `isSystemBacked`
|
||||
fail-safes EVERYTHING to system → `ListCandidateDisks` is always empty (drill box; a hot-added
|
||||
blank 5G disk stayed invisible). The fail-safe is right to exist but overreaches here — needs
|
||||
an agent ruling (e.g. resolve device-mapper parents to their PVs' disks).
|
||||
- **Supervised round-trip (Viktor GO), the EXACT leg that failed yesterday:** demo ActualBudget
|
||||
export → browser download (`actualbudget_20260713-085217.fab`, 3762 B — the bundle now carries
|
||||
the **66048-byte volume tar**; yesterday's was 2097 B with an EMPTY data/volumes) → sha256-
|
||||
identical after placement on felhom-usb/exports → import → "Importálás kész" → **volume
|
||||
fingerprint after == before: `ec8ea6cb…` (2 files)** → app Up (healthy) → zero leaked alpine
|
||||
helpers → bundle cleaned from the drive.
|
||||
- **Scenario B live:** a scratch stack whose compose declared the engine-invalid volume
|
||||
`"bad vol"` (docker auto-creates *nonexistent* names, so engine-invalid is the honest live
|
||||
trigger) → export failed LOUD: "Kötet mentése sikertelen: volume scratchvol_bad vol export
|
||||
failed … invalid characters …" — zero bundles staged, zero leftover containers; scratch stack
|
||||
removed after.
|
||||
|
||||
## Observations
|
||||
|
||||
- **.fab browser-UPLOAD** remains the noted follow-up (import is drive-scan only — downloaded
|
||||
bundles return via a drive/share, exactly as exercised here).
|
||||
- **Combined mega-zip** stays parked (decision; per-app sequential batch shipped instead).
|
||||
- **appexport packages LIVE state** (fresh config copy + DB dump + live data), NOT the tier-1
|
||||
recovery unit — §8's "stale unit" concern is moot; no "futtass előbb mentést" needed.
|
||||
- The import page's drive-label mojibake ("TÃ˘rhely (felhom-usb)") is the KNOWN open fix-5 item.
|
||||
- `NoUserDataBackupWarning` is referenced by the per-app section but never set by any handler
|
||||
(dead conditional, pre-existing); `DBDumpTotalBytes` was computed but never rendered — dropped
|
||||
in the split.
|
||||
- Drill box left as found: hot-added disk detached + LV removed; ActualBudget re-toggled; demo
|
||||
exports dir + stray export temp cleaned.
|
||||
- **Hollow-bundle asymmetry (customer-facing docs line needed):** any .fab exported by a
|
||||
CONTAINERIZED controller ≤0.124.0 has empty volume data while claiming it — treat as suspect
|
||||
and RE-EXPORT on ≥0.125.0. The import guard now refuses them loudly (app untouched) instead of
|
||||
silently importing emptiness. Bare-metal/felhotest exports were never affected; HDD-data apps
|
||||
were never affected.
|
||||
- Spec §7B's "point a volume at a nonexistent volume" live construction doesn't fail under
|
||||
docker (named volumes auto-create; an empty volume exports as an honest small tar — correct
|
||||
behavior) — the live loud-fail used an engine-invalid name instead; the missing/empty-tar
|
||||
refusal is unit-pinned with red-proof.
|
||||
- The one Windows-runner nuance: none — the docker seam keeps unit tests fully offline.
|
||||
|
||||
Reference in New Issue
Block a user