- 02-07-SUMMARY.md: log_schemas.py shape, test counts, AC-TRACEABILITY delta, E2E smoke notes, Phase 2 DoD checklist (9/10 checked, ROADMAP human action) - 02-PHASE-SUMMARY.md: Phase 2 capstone — 236 tests, 15 ACs covered, 39 declared, 10 hot-path structlog files, 3 boundary schemas, handoff notes for Phase 3
7.3 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-acceptance-criteria-test-taxonomy-observability-spine | 07 | observability |
|
|
|
|
|
|
Phase 2 Plan 07: Pydantic Boundary-Log Schemas + Final Regression Gate — Summary
One-liner: Pydantic v2 boundary-log schemas (MavlinkGpsInputEmitted, ApiRequestCompleted, AnchorDecision) with frozen+extra=forbid contracts, 20 unit tests, and Phase 2 final regression gate: 236 tests pass, AC --check exit 0.
What Was Built
1. log_schemas.py — 3 Pydantic v2 Boundary Models
File: src/gps_denied/obs/log_schemas.py
| Model | Fields | AC Link | Phase Wiring |
|---|---|---|---|
MavlinkGpsInputEmitted |
lat_deg, lon_deg, alt_m, fix_type (ge=0 le=6), horiz_accuracy_m, source_label (Literal[3 values]), anchor_age_ms, cov_semi_major_m | AC-4.3, AC-1.4, AC-1.3 | Phase 5 / MAVOUT-01 |
ApiRequestCompleted |
path, method (Literal[5 values]), status_code (ge=100 lt=600), duration_ms | AC-6.3 | Phase 6 REST middleware |
AnchorDecision |
decision (accept/reject), reason (AnchorRejectReason Literal[5]), n_inliers, mre_px | VERIFY-02 | Phase 3 AnchorVerifier |
All models extend _BoundaryLogRecord(BaseModel) with ConfigDict(extra="forbid", frozen=True):
extra="forbid": producer-side field drift fails validation fastfrozen=True: records are immutable facts, not state
Type aliases exported: SourceLabel, AnchorRejectReason for producer code reuse.
2. tests/test_log_schemas.py — 20 Unit Tests, 17 AC-Tagged
| Test Group | Count | AC Tags |
|---|---|---|
MavlinkGpsInputEmitted round-trip + source_label 3 parametrize + rejects_unknown + fix_type 3 parametrize + extra_rejected + frozen |
10 | AC-4.3, AC-1.4 |
ApiRequestCompleted round-trip + unknown_method + status_bounds (2 assertions) |
3 | AC-6.3 |
AnchorDecision accept round-trip + 5 vocab parametrize + rejects_unknown_reason |
7 | AC-2.1b |
Module-level: pytestmark = [pytest.mark.unit]
Per-test: @pytest.mark.ac("AC-N.n") on relevant tests.
All 20 tests pass in 0.04 seconds (no I/O, pure Pydantic validation).
3. AC-TRACEABILITY.md Delta
| AC | Before (tests) | After (tests) | Status |
|---|---|---|---|
| AC-1.4 | 2 | 7 | OK |
| AC-4.3 | 20 | 26 | OK |
| AC-2.1b | 0 | 6 | DEFERRED (pending-phase-4)* |
| AC-6.3 | 6 | 7 | OK |
| ACs covered total | 14 | 15 | — |
*AC-2.1b: schema contract tests added, but full AnchorVerifier integration requirement remains deferred to Phase 4.
4. End-to-End Frame-ID + Boundary-Record Smoke
structlog.contextvars.merge_contextvars (first processor in the configured chain) injects
bound contextvars into every log record before the renderer fires. Verified directly:
merge_contextvars propagation OK: {'event': 'test', 'correlation_id': 999, 'source_label': 'satellite_anchored', 'fix_type': 3}
E2E spine + boundary-record smoke OK: {'event': 'mavlink_emit_gps_input', 'correlation_id': 999, 'source_label': 'satellite_anchored', 'fix_type': 3}
Note: structlog.testing.capture_logs() intentionally replaces the full processor chain
with a minimal capture, so merge_contextvars does not run inside capture_logs() blocks.
This is expected structlog behavior. The plan's Task 4 inline script was adapted to verify
merge_contextvars directly rather than through capture_logs().
Phase 2 Definition of Done — verified by Plan 02-07
- All 7 PLAN.md files have SUMMARY.md siblings (this one being the last)
- pytest tests/ -q --ignore=tests/e2e passes at >= 216 (+ test_log_schemas.py count): 236 passed, 8 skipped
- pytest -m unit / -m integration / -m blackbox each pass with zero failures (210 / 69 / 12)
- scripts/gen_ac_traceability.py --check exit 0
- git diff --exit-code .planning/AC-TRACEABILITY.md exit 0 (matrix committed clean)
- from gps_denied.obs.logging_config import configure_logging works
- from gps_denied.obs.log_schemas import MavlinkGpsInputEmitted, ApiRequestCompleted, AnchorDecision works
- correlation_id=frame_id propagates from orchestrator binding via merge_contextvars to hot-path log calls
- git diff --name-only tests/ shows only pytestmark + @pytest.mark.ac additions + new test_log_schemas.py (no logic edits)
- ROADMAP.md Phase 2 row updated to "Plans Complete: 7/7" + "Status: Done" — REQUESTED FOR HUMAN REVIEWER after merge
ROADMAP.md Update Request
After merging Phase 2 branch into main:
- In
ROADMAP.md, update Phase 2 row: Plans Complete →7/7, Status →Done - Optionally update the AC-2.1b row from "DEFERRED (pending-phase-4)" to add a note that schema contract tests now exist (6 nodeids), but the integration requirement is still pending Phase 4.
Deviations from Plan
Auto-adapted: capture_logs() assertion
Found during: Task 4 final regression gate
Issue: The plan's Task 4 inline script asserts e["correlation_id"] == 999 inside a
capture_logs() block. structlog.testing.capture_logs() replaces all processors with a
bare capture — merge_contextvars does NOT run, so context vars are absent from captured
events. This is documented structlog behavior, not a bug.
Fix: Verified merge_contextvars propagation directly by calling the processor
function against an event dict with bound context. The production chain (Plan 02-06's
configure_logging) wires merge_contextvars as the first processor — the invariant holds
in production.
Impact: No code change needed; assertion strategy adapted in smoke test verification.
Self-Check
Files created:
src/gps_denied/obs/log_schemas.py— existstests/test_log_schemas.py— exists, 20 tests pass
Commits:
94c1b76— feat(02-07): add Pydantic v2 boundary-log schemas (OBS-01)e87fb37— test(02-07): add unit tests for boundary-log schemas (AC-02, OBS-01)14717c5— chore(02-07): regenerate AC-TRACEABILITY.md with test_log_schemas nodeids
Gate results:
- 236 total tests pass (baseline 216 + 20 new)
- -m unit: 210 passed; -m integration: 69 passed; -m blackbox: 12 passed
- AC --check exit 0; matrix diff clean