lane G: status — M16+M17 complete, 169 passed, capsule E2E evidence, CR-4 noted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
feb5588a1b
commit
4bc2638c12
101
plan/status/lane-G.md
Normal file
101
plan/status/lane-G.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# Status — lane G (persistence & capsule, branch `lane/g-capsule`)
|
||||||
|
|
||||||
|
## Round 1 — 2026-07-17 — STATUS: ready_to_merge
|
||||||
|
|
||||||
|
**Directives acknowledged:** Round 4 of plan/DIRECTIVES.md (phase 5 begins; foundation2 merged;
|
||||||
|
worktree rule followed — this lane runs in its own git worktree).
|
||||||
|
|
||||||
|
**Acceptance checklist**
|
||||||
|
|
||||||
|
M16 — server-side camera paths:
|
||||||
|
- [x] Routes implemented — by foundation2 (per the phase-5 ownership table); lane G deepened
|
||||||
|
coverage only, per scope.
|
||||||
|
- [x] `backend/tests/test_paths.py` — 30 tests: roundtrip deep-equal incl. float precision +
|
||||||
|
a 500-keyframe path, multiple paths coexist / id-ordered listing / summary-only shape,
|
||||||
|
duplicate + unicode + whitespace + 300-char names verbatim, ISO `created_at`, empty-keyframes
|
||||||
|
path valid (director degrade must be saveable), extra JSON keys ride along, get/delete 404s,
|
||||||
|
delete idempotence, an 18-case parametrized 422 matrix over the frozen camPath contract,
|
||||||
|
422 creates no row, missing/aliased request fields 422. Cleans up its rows (test_phase5_api
|
||||||
|
runs later and asserts an empty table).
|
||||||
|
- [x] `frontend/src/pathsStore.js` filled: 💾 save = name prompt + POST `camPath.toJSON()`;
|
||||||
|
dropdown load = GET `/api/paths/{id}` + `camPath.fromJSON(data.json)`; 🗑 delete with
|
||||||
|
confirm; dropdown refreshed after every mutation (new save left selected); controls
|
||||||
|
unhidden + `ready = true`. Capsule mode: save/del hide via `write-ui`; the load dropdown
|
||||||
|
stays (paths are baked read-only — below) and hides itself if the listing fetch fails.
|
||||||
|
- [ ] Live save → reload page → load reproduces keyframes exactly — NEEDS LIVE BROWSER (list
|
||||||
|
for coordinator below). The exact-roundtrip property is locked server-side by
|
||||||
|
`test_roundtrip_preserves_float_precision` / `test_roundtrip_large_keyframe_list`.
|
||||||
|
|
||||||
|
M17 — memory capsule:
|
||||||
|
- [x] `backend/festival4d/capsule.py::build_capsule` per the frozen contract: copies
|
||||||
|
`frontend/dist` (clear "run `cd frontend && npm run build`" error if missing), copies the
|
||||||
|
manifest-referenced media to `media/`, bakes every read-only API response extension-less at
|
||||||
|
the live API's relative paths (`api/manifest` with `capsule: true` + `has_capture: false`,
|
||||||
|
`api/events`, `api/anchors`, `api/videos/{id}/poses`, `api/beats`/`api/pointcloud`/
|
||||||
|
`api/splat` when present, `api/audio/{id}` via the same lazy `ingest.extract_audio_hq`
|
||||||
|
cache the live route uses), injects `<script>window.__F4D_API_BASE__=""</script>` ahead of
|
||||||
|
the app `<script>`, ships stdlib `serve.py` (HTTP/1.1, 206 Range slicing incl. `bytes=N-`
|
||||||
|
and suffix `bytes=-N`, 416 past-EOF, `Accept-Ranges`, JSON content types for the
|
||||||
|
extension-less `api/*` files). Optional artifacts degrade by omission; refuses to clobber
|
||||||
|
a non-capsule output dir; rebuild over a previous capsule is idempotent (no double-inject).
|
||||||
|
Bonus within scope: M16 saved paths are baked read-only (`api/paths/{id}`, listing at
|
||||||
|
`api/paths/index.html` — a filesystem can't hold `paths` as both file and dir; the stock
|
||||||
|
301 → `/api/paths/` → index.html keeps `fetch("/api/paths")` working unchanged).
|
||||||
|
- [x] `backend/tests/test_capsule.py` — 12 tests: bundle structure; baked JSON deep-equal vs the
|
||||||
|
live route functions (shape drift impossible — capsule bakes *through* `api.py`); manifest
|
||||||
|
key-set == live route, urls resolve inside the bundle; injection present exactly once and
|
||||||
|
before the module script; degrade-when-absent; no-dist error; overwrite guard; CLI dispatch
|
||||||
|
(`cli.main(["capsule","--out",…]) == 0`, hermetic via monkeypatched REPO_ROOT); serve.py
|
||||||
|
exercised over real HTTP in a subprocess — 200 + Accept-Ranges, `bytes=0-100` → 206 with
|
||||||
|
`Content-Range: bytes 0-100/{size}` and 101 body bytes (mp4 `ftyp` magic verified),
|
||||||
|
open-ended + suffix ranges, 416, audio 200 `audio/mp4` + ranged, 404s.
|
||||||
|
- [x] End-to-end proof WITHOUT a browser (evidence below).
|
||||||
|
|
||||||
|
**Quality bar**
|
||||||
|
- `uv run pytest backend/tests -p no:warnings` → **169 passed** (floor 127 + 30 paths + 12
|
||||||
|
capsule), zero failures.
|
||||||
|
- `cd frontend && npm run build` → clean, only the pre-existing >500 kB chunk-size warning.
|
||||||
|
|
||||||
|
**E2E capsule evidence (exact commands, run in the worktree):**
|
||||||
|
```
|
||||||
|
$ cd frontend && npm run build # ✓ built in 860ms
|
||||||
|
$ uv run python -m festival4d synthetic # 3 videos, 3106 points, 7 events, 4 anchors
|
||||||
|
$ uv run python -m festival4d capsule
|
||||||
|
capsule: baked 18 files (6826705 bytes) at <worktree>/data/capsule
|
||||||
|
$ cd data/capsule && python3 serve.py --port 8901
|
||||||
|
$ curl -s http://127.0.0.1:8901/ | grep -o 'window.__F4D_API_BASE__=""'
|
||||||
|
window.__F4D_API_BASE__=""
|
||||||
|
$ curl -si http://127.0.0.1:8901/api/manifest
|
||||||
|
HTTP/1.1 200 OK / Content-Type: application/json
|
||||||
|
capsule: True | videos: 3 | t_global_max: 21.37 | has_poses: True
|
||||||
|
$ curl -si -H "Range: bytes=0-100" http://127.0.0.1:8901/media/cam0.mp4
|
||||||
|
HTTP/1.1 206 Partial Content / Accept-Ranges: bytes / Content-Length: 101
|
||||||
|
Content-Range: bytes 0-100/1533609
|
||||||
|
$ curl -s -H "Range: bytes=500000-500099" .../media/cam0.mp4 # mid-file seek
|
||||||
|
206, received 100B
|
||||||
|
$ curl -s http://127.0.0.1:8901/api/audio/1
|
||||||
|
200, audio/mp4, 440201 bytes, magic `ftypM4A`
|
||||||
|
$ curl -sL http://127.0.0.1:8901/api/paths # [] final:200 application/json
|
||||||
|
$ curl .../api/pointcloud → 200 46800B; .../api/beats → 404 (degrade: no analysis run)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blockers / change requests:** none blocking. **CR-4 filed** (plan/CHANGE_REQUESTS.md):
|
||||||
|
`test_phase5_api.py::test_cli_dispatches_stubs_gracefully` asserted the *stub* exit-2 contract
|
||||||
|
for `capsule`; with M17 landed that assertion fails by design (CR-1 precedent). Applied the
|
||||||
|
minimal edit (removed only `["capsule"]`; features/direct untouched for lane E) — integration2
|
||||||
|
to ratify. Capsule CLI dispatch is now covered for real in test_capsule.py.
|
||||||
|
|
||||||
|
Small note for integration2 (frozen file, not edited): `.gitignore` covers `data/raw/` +
|
||||||
|
`data/work/` but not `data/capsule/` or `data/project.db`, so a default capsule build leaves a
|
||||||
|
~7 MB untracked tree; consider ignoring `data/` wholesale (or adding the two entries).
|
||||||
|
|
||||||
|
**Needs live-browser verification (for coordinator / integration2):**
|
||||||
|
1. Capsule acceptance proper: `python serve.py` in the bundle, open it focused — app loads with
|
||||||
|
**zero requests to :8000** (network tab), videos play *and seek*, 3D scene + timeline work,
|
||||||
|
🎧 spatial audio works, all `.write-ui` controls absent (body.capsule), saved-path dropdown
|
||||||
|
loads a baked path.
|
||||||
|
2. M16 live flow: save a path (💾, name prompt) → reload page → load from dropdown → camera
|
||||||
|
lands on the exact keyframes; delete removes it from the dropdown.
|
||||||
|
3. pathsStore dropdown behavior with an *older* capsule (no baked paths): dropdown hides itself.
|
||||||
|
|
||||||
|
**Next:** hand to coordinator for merge; not merging to main per lane instructions this round.
|
||||||
Loading…
Reference in New Issue
Block a user