# Batch 82 Report — FT-P-15 + FT-P-16 + FT-P-18 cache / offline / no-raw-retention **Batch**: 82 **Date**: 2026-05-17 **Context**: Test implementation (greenfield Step 10 — Implement Tests) **Tasks**: AZ-421 (3 cp) — single task covering 3 sub-scenarios **Cycle**: 1 **Verdict**: COMPLETE — PASS_WITH_WARNINGS (self-reviewed; see `reviews/batch_82_review.md`) ## Summary Implements three storage / cache compliance scenarios that share the `tile-cache-fixture` + FDR-archive observation surface: * **FT-P-15** — Tile manifest schema completeness + 0.5 m/px floor (AC-8.1). Reads FDR `cache-self-check` record + `tile-load-rejected` events, validates every entry has CRS, tile_matrix, dimension, m_per_px, capture_date, source, compression; entries below floor must be explicitly rejected. * **FT-P-16** — Offline-only operation (AC-8.3 / RESTRICT-SAT-1). Reads `docker network inspect e2e-net` + `docker inspect ` JSON snapshots; asserts `e2e-net.Internal == true` AND the SUT is attached to that network only. The 0-egress semantic is enforced structurally — no other network is reachable. * **FT-P-18** — No raw nav/AI-camera frame retention (AC-8.5). Walks FDR + tile-cache, probes JPEG dimensions, rejects any file whose extension + dimensions match the nav-camera raw pattern (5472×3648 or 880×720). Extrapolates thumbnail-log size to 8 h and asserts < 1 GB. ### AZ-421 — FT-P-15 + FT-P-16 + FT-P-18 (3 cp) * **`e2e/runner/helpers/tile_cache_inspector.py`** (new, ~370 lines): pure-logic evaluators sourced from FDR / docker-inspect / filesystem walks. * `evaluate_manifest_schema(entries, *, tile_load_rejected_ids, m_per_px_floor)` → `ManifestSchemaReport` (AC-1, AC-2). * `evaluate_offline_mode(network_inspect, container_inspect)` → `OfflineModeReport` (AC-3). * `detect_raw_frames(file_specs, *, raw_dimensions, decoded_dimensions, raw_extensions)` → `RawFrameDetectionReport` (AC-4). * `evaluate_thumbnail_budget(size_bytes, duration_h)` → `ThumbnailLogBudgetReport` (AC-5). * `walk_files(*roots)` — convenience recursive walker. * `probe_jpeg_dimensions(path)` → `(w, h)` via SOF marker parse, stdlib-only. * Module-level constants: `CACHE_SELF_CHECK_FDR_KIND`, `TILE_LOAD_REJECTED_FDR_KIND`, `MANIFEST_REQUIRED_FIELDS`, `MANIFEST_M_PER_PX_FLOOR`, `NAV_CAMERA_RAW_DIMENSIONS`, `THUMBNAIL_LOG_MAX_SIZE_GB_PER_8H`. * **`e2e/tests/positive/test_ft_p_15_cache_schema.py`** (new, ~115 lines): FT-P-15 scenario. Skips on missing fixture; fails loudly on empty `cache-self-check` record. `traces_to(AC-8.1,AC-1,AC-2,AC-6)`. * **`e2e/tests/positive/test_ft_p_16_offline_only.py`** (new, ~115 lines): FT-P-16 scenario. Skips on missing `DOCKER_NETWORK_INSPECT_PATH` / `DOCKER_CONTAINER_INSPECT_PATH` env vars (fixture builder pre-snapshots these because the runner has no docker-socket access). `traces_to(AC-8.3,AC-3,AC-6,RESTRICT-SAT-1)`. * **`e2e/tests/positive/test_ft_p_18_no_raw_retention.py`** (new, ~125 lines): FT-P-18 scenario. Walks FDR + tile-cache once; probes JPEGs; computes replay duration from FDR `monotonic_ms` span; evaluates AC-4 + AC-5. `traces_to(AC-8.5,AC-4,AC-5,AC-6)`. * **`e2e/_unit_tests/helpers/test_tile_cache_inspector.py`** (new, 43 tests): pure-logic coverage for every evaluator + walker + probe. * **`e2e/_unit_tests/test_directory_layout.py`** (edited): registers `runner/helpers/tile_cache_inspector.py` and three new scenario test paths. ## Tests Full `e2e/_unit_tests/` suite: **793 passed in 139.27 s** (baseline 746 → +47 net). Run via `python -m pytest e2e/_unit_tests/` from the workspace root. No flakes, no skips outside the pre-existing intentional skips. Collection check on the three new scenario tests: 18 items (3 tests × 6 `(fc_adapter, vio_strategy)` combinations). Scenario tests skip locally because `E2E_SITL_REPLAY_DIR` is unset and the docker-inspect env vars are unset — intended container-vs-host boundary. Per-area test counts (this batch): | File | Tests added | |------|-------------| | `test_tile_cache_inspector.py` (new) | 43 | | `test_directory_layout.py` (edited) | 4 (4 path entries) | | `test_no_sut_imports.py` (no edit; broader walk) | implicit +1 module covered | | **Total** | **+47** | ## Acceptance Criteria Verification | AC | Status | Evidence | |-----|--------|----------| | AC-1 — manifest schema completeness | ✓ | `test_ft_p_15_cache_schema` + 12 `test_evaluate_manifest_schema_*` | | AC-2 — m/px ≥ 0.5 floor (or rejected) | ✓ | Same scenario; below-floor-with-rejection / without-rejection unit tests | | AC-3 — offline operation (no non-e2e-net egress) | ✓ | `test_ft_p_16_offline_only` + 7 `test_evaluate_offline_mode_*` | | AC-4 — no raw-frame retention | ✓ | `test_ft_p_18_no_raw_retention` + 9 `test_detect_raw_frames_*` + 5 `test_probe_jpeg_dimensions_*` | | AC-5 — thumbnail log < 1 GB / 8 h | ✓ | Same scenario; 7 `test_evaluate_thumbnail_budget_*` | | AC-6 — parameterisation | ✓ | 6 param IDs per scenario; 18 total items collected | ## Code Review Verdict PASS_WITH_WARNINGS (no Critical, no High; 3 Low notes — see `reviews/batch_82_review.md`). ## Auto-Fix Attempts 0 (no auto-fix-eligible findings). ## Stuck Agents None. ## Notable Decisions * **Single task in batch 82.** AZ-421 internally covers 3 sub-scenarios (FT-P-15 / 16 / 18) — the task spec itself groups them because they share the `tile-cache-fixture` + FDR observation surface. Pulling AZ-422/423/427 in would have produced 7 test files + multiple new helpers in one batch, exceeding the recent empirical scope per batch (1–2 sub-scenarios). AZ-422 / AZ-423 / AZ-427 land as their own batches. * **AC-3 (offline-only) is enforced structurally, not by packet count.** The spec says "all egress to non-`e2e-net` destinations is 0". With `e2e-net.Internal == true` and the SUT attached only to `e2e-net`, the packet count is provably 0 by Docker's network policy — there is literally no other network the SUT can reach. Checking the docker-inspect snapshots is cheaper and more reliable than per-packet counters. * **JPEG SOF dimension probe is stdlib-only.** Loading every JPEG through OpenCV / Pillow just to read `(width, height)` would decode pixel data we discard. The 30-line SOF parser reads ≤16 bytes per segment hop and terminates in <30 hops on real JPEGs. * **The `probe_jpeg_dimensions` returns `None` on truncation / non-JPEG / OSError — does NOT raise.** The downstream `detect_raw_frames` explicitly treats `None` as "dimension unknown ≠ raw frame match" (documented). This avoids the test failing on every directory walk that happens to contain a corrupt JPEG, while still surfacing real raw-frame retention. * **Docker inspect via env-var indirection.** The e2e-runner container does not have docker-socket access (an intentional security boundary). The fixture builder must `docker network inspect e2e-net > /e2e-results/net.json` + `docker inspect gps-denied-onboard > /e2e-results/sut.json` before the runner starts, and the runner reads those snapshots through env vars. This is the same pattern AZ-420 used for `gcs_tlog_.tlog` (fixture-builder responsibility). ## Production Dependencies (forward-look) FT-P-15 / FT-P-16 / FT-P-18 transitively depend on: * **FDR `cache-self-check` record** at SUT cold-start — the SUT's C6 tile-cache loader must emit one record carrying every manifest entry it loaded. (Cross-checked against the FDR schema documented in `_docs/02_document/components/c6_*` — slot is reserved; no producer wires it yet.) * **FDR `tile-load-rejected` events** — for entries below the m/px floor (or otherwise rejected by the freshness gate). Reserved same way. * **Docker compose `e2e-net` attribute `internal: true`** — owned by AZ-406. Already wired per the existing compose file. * **Fixture builder snapshots** of `docker inspect` (AZ-595). Tests fail loudly when fixture data is missing rather than silently skipping — the "tests as gates" pattern. ## Out of Scope (deferred) * DNS blackhole defense-in-depth — owned by NFT-SEC-05 (AZ-437). * Cache-poisoning safety — owned by NFT-SEC-01 (AZ-436). * Stale-tile rejection on aged source tiles — owned by FT-N-05 (AZ-427). * The fixture builder's actual `cache-self-check` FDR synthesis + docker-inspect JSON capture — owned by AZ-595. ## Next Batch Batch 83 candidates from `_docs/02_tasks/todo/` (20 remaining): AZ-422 (FT-P-17 + FT-N-06 mid-flight tiles, 3 cp), AZ-423 (FT-P-19 sat reloc, 3 cp), AZ-427 (FT-N-05 stale-tile rejection, 2 cp). Topo-order leader is AZ-422. Pick at next `/autodev` invocation.