# 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.
