[AZ-273] [AZ-274] [AZ-275] [AZ-267] [AZ-268] FDR producer chain + log bridge + contract test

AZ-273: lock-free SPSC ring buffer with pre-allocated slots, power-of-
two capacity, opt-in SPSC guard, and EnqueueResult / FdrSpscViolationError
on the public surface. make_fdr_client caches one client per producer_id
and reads capacity from config.fdr.per_producer_capacity with fallback
to queue_size.
AZ-274: default_overrun_policy implements drop-oldest + retry + immediate
marker emission, with prior-marker dropped_count folding via _evict_one
so user-loss info is never lost across iterations. ERROR diagnostic is
rate-limited to <=1/sec per producer.
AZ-275: FakeFdrSink mirrors the FdrClient public surface and reuses the
production default_overrun_policy via a duck-typed _PolicyAdapter. The
test-only records/all_records_ever properties let component tests assert
both in-buffer and lifetime state. tests/conftest.py registers the
fake_fdr_sink fixture and an AST architecture lint forbids production
imports of fakes.
AZ-267: FdrLogBridgeHandler installs on the root logger via wire_log_bridge
and forwards only WARN+ERROR records into the FDR with kind="log".
Thread-local recursion guard short-circuits internal logging; saturated-
queue diagnostics go to stderr every N=1000 drops.
AZ-268: tests/contract/log_schema.py covers every row of the schema's
Test Cases table plus the "DEBUG+INFO never reach FDR" invariant.
pyproject.toml registers the contract pytest marker and the
contract-mandated log_schema.py file-name.
251 unit + contract tests pass (48 new). Review verdict:
PASS_WITH_WARNINGS; findings are NFR-perf deferrals + documented
relaxation of AZ-274 AC-2 coalescing under permanently-stalled consumer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 03:00:49 +03:00
parent 3acc7f33dd
commit ba20c2d195
24 changed files with 2714 additions and 20 deletions
@@ -0,0 +1,119 @@
# 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.