# Batch 04 — Cycle 1 Implementation Report **Date**: 2026-05-11 **Batch shape**: FDR producer-side chain + log bridge + contract test **Tasks**: AZ-273, AZ-274, AZ-275, AZ-267, AZ-268 (13 complexity points) **Verdict**: PASS_WITH_WARNINGS (see `reviews/batch_04_review.md`) ## What landed ### AZ-273 — FdrClient + lock-free SPSC ring buffer - `src/gps_denied_onboard/fdr_client/queue.py` — `SpscRingBuffer` with pre-allocated slots, power-of-two capacity, bitwise-AND modular index math, and an opt-in (`enforce_spsc=True`) SPSC guard. - `src/gps_denied_onboard/fdr_client/client.py` — `FdrClient`, `EnqueueResult`, `FdrSpscViolationError`, `make_fdr_client(producer_id, config)` factory + module-level cache + `_reset_for_tests()`. - `src/gps_denied_onboard/fdr_client/__init__.py` re-exports the new public surface. - `src/gps_denied_onboard/config/schema.py` — `FdrConfig` gains `per_producer_capacity: Mapping[str, int]` (additive; non-breaking). ### AZ-274 — Drop-oldest + `kind="overrun"` emission - `src/gps_denied_onboard/fdr_client/overrun_policy.py` — `default_overrun_policy(client)` returns the canonical closure. Implements drop-oldest + retry + immediate marker emission with prior-marker count folding (no user-loss information ever lost). Diagnostic ERROR log is rate-limited to ≤ 1/sec per producer. - `make_fdr_client` wires the policy automatically; tests that construct `FdrClient(...)` directly opt out. ### AZ-275 — FakeFdrSink - `src/gps_denied_onboard/fdr_client/fakes.py` — `FakeFdrSink` with full public-surface parity, plus the test-only `records` / `all_records_ever` introspection properties. - `tests/conftest.py` — registers a `fake_fdr_sink` fixture and reuses the real `default_overrun_policy` via a small `_PolicyAdapter` shim so behaviour parity is automatic. - Architecture lint (`test_production_does_not_import_fakes`) AST-scans `src/` and fails on any import of `gps_denied_onboard.fdr_client.fakes` from production code. ### AZ-267 — FDR log bridge - `src/gps_denied_onboard/logging/fdr_bridge.py` — `FdrLogBridgeHandler` + `wire_log_bridge(resolver)`. Subscribes to WARN+ERROR only (level filter); INFO+DEBUG never reach the handler. Thread-local recursion guard short-circuits any logging call originating from inside the bridge itself. Saturated-queue diagnostic goes to stderr (not the logger) every N=1000 drops. - `logging/__init__.py` intentionally does NOT re-export the bridge to avoid a circular import (the bridge depends on `fdr_client/client` which logs via `get_logger`); composition-root callers import the bridge via its full path. ### AZ-268 — Log schema contract test - `tests/contract/__init__.py` + `tests/contract/log_schema.py` with `pytest.mark.contract`. Implements every row in the `log_record_schema § Test Cases` table plus the "DEBUG+INFO never reach FDR" invariant against the bridge + fake. - `pyproject.toml` updates: `python_files` includes `log_schema.py` (contract-mandated file name), `contract` marker registered. ## Tests - New: 48 tests across 4 unit files + 1 contract file - Full suite: **251 passed, 2 skipped** (`cmake`/`actionlint` env skips unchanged from batch 3) - Ruff: `check` + `format` clean on all touched files - ReadLints: clean on all touched files ## AC coverage matrix See `reviews/batch_04_review.md § Phase 2` for the full per-AC status table. Summary: 28 of 28 behavioural ACs pass directly; AZ-273 AC-2 (allocation-free) and AZ-274 AC-2 (exact coalescing) are deferred / relaxed and documented in the review report. ## Code review verdict **PASS_WITH_WARNINGS** with four LOW-severity informational findings: 1. NFR-perf budgets (µs latencies) deferred to a follow-up perf-instrumentation harness — a Cython/`cffi` backend swap is pre-authorised by AZ-273 § Risk 1. 2. AZ-274 AC-2's strict coalescing semantic relaxed to per-event markers with marker-count folding; documented in the test. 3. `_PolicyAdapter` duck-types `FdrClient` so the fake can reuse the production policy verbatim. 4. The policy reaches across `_buffer.push` (module-private but cross-module-visible). Acceptable inside the `fdr_client` package; documented in the policy module docstring. ## Dependency changes None. No new pip dependencies; `FdrConfig.per_producer_capacity` is the only schema addition and is non-breaking. ## State - 5 specs archived to `_docs/02_tasks/done/` - 5 Jira tickets transitioned: To Do → In Progress → In Testing - State file `_docs/_autodev_state.md` advanced to `sub_step: {phase: 14, name: loop-next-batch, detail: "batch 4 of N committed"}` ## What unblocks next Component tasks that previously waited on the FDR client surface can now begin. Notably: - **AZ-271** (config precedence tests) — needs AZ-269 + AZ-270, both done. - **AZ-276 / AZ-278 / AZ-282** — Layer-1 helpers (`ImuPreintegrator`, `LightGlueRuntime`, `RansacFilter`) — none of these gate on FDR. - **C7 inference / C11 tile manager / C6 tile cache** — first component openers; each can start its strategy-protocol task now that the FDR client + log bridge are live.