Files
Oleksandr Bezdieniezhnykh ba20c2d195 [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>
2026-05-11 03:00:49 +03:00

5.1 KiB

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.pySpscRingBuffer 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.pyFdrClient, 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.pyFdrConfig 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.pydefault_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.pyFakeFdrSink 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.pyFdrLogBridgeHandler + 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.