mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-23 11:51:12 +00:00
7f76acfe29
- 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
146 lines
7.3 KiB
Markdown
146 lines
7.3 KiB
Markdown
---
|
|
phase: 02-acceptance-criteria-test-taxonomy-observability-spine
|
|
plan: "07"
|
|
subsystem: observability
|
|
tags: [pydantic, boundary-log, structlog, unit-test, regression-gate, ac-traceability]
|
|
dependency_graph:
|
|
requires: [02-06-structlog-spine, 02-05-ci-pipeline, 02-04-ac-traceability]
|
|
provides: [obs-boundary-schemas, ac-coverage-AC-1.4, ac-coverage-AC-4.3, ac-coverage-AC-2.1b, ac-coverage-AC-6.3, phase2-final-gate]
|
|
affects: [Phase 3 AnchorVerifier wiring, Phase 5 MAVLink bridge wiring, Phase 6 REST middleware]
|
|
tech_stack:
|
|
added: [pydantic-v2-boundary-log-schemas]
|
|
patterns: [Literal-field-validation, ConfigDict-extra-forbid-frozen, model_dump-mode-json]
|
|
key_files:
|
|
created:
|
|
- src/gps_denied/obs/log_schemas.py
|
|
- tests/test_log_schemas.py
|
|
modified:
|
|
- src/gps_denied/obs/__init__.py
|
|
- .planning/AC-TRACEABILITY.md
|
|
decisions:
|
|
- "Boundary schemas use Literal not Enum — JSON-native, same validation speed, simpler migration path"
|
|
- "capture_logs() skips processor chain by design; frame_id propagation verified via merge_contextvars directly"
|
|
- "AC-2.1b remains DEFERRED (pending-phase-4) despite gaining 6 new test nodeids — schema contract tests do not satisfy the full AnchorVerifier integration requirement"
|
|
metrics:
|
|
duration: ~15 minutes
|
|
completed: "2026-05-11"
|
|
tasks_completed: 4
|
|
files_created: 2
|
|
files_modified: 2
|
|
---
|
|
|
|
# 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 fast
|
|
- `frozen=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
|
|
|
|
- [x] All 7 PLAN.md files have SUMMARY.md siblings (this one being the last)
|
|
- [x] pytest tests/ -q --ignore=tests/e2e passes at >= 216 (+ test_log_schemas.py count): **236 passed, 8 skipped**
|
|
- [x] pytest -m unit / -m integration / -m blackbox each pass with zero failures (210 / 69 / 12)
|
|
- [x] scripts/gen_ac_traceability.py --check exit 0
|
|
- [x] git diff --exit-code .planning/AC-TRACEABILITY.md exit 0 (matrix committed clean)
|
|
- [x] from gps_denied.obs.logging_config import configure_logging works
|
|
- [x] from gps_denied.obs.log_schemas import MavlinkGpsInputEmitted, ApiRequestCompleted, AnchorDecision works
|
|
- [x] correlation_id=frame_id propagates from orchestrator binding via merge_contextvars to hot-path log calls
|
|
- [x] 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:
|
|
1. In `ROADMAP.md`, update Phase 2 row: Plans Complete → `7/7`, Status → `Done`
|
|
2. 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:
|
|
- [x] `src/gps_denied/obs/log_schemas.py` — exists
|
|
- [x] `tests/test_log_schemas.py` — exists, 20 tests pass
|
|
|
|
Commits:
|
|
- [x] `94c1b76` — feat(02-07): add Pydantic v2 boundary-log schemas (OBS-01)
|
|
- [x] `e87fb37` — test(02-07): add unit tests for boundary-log schemas (AC-02, OBS-01)
|
|
- [x] `14717c5` — chore(02-07): regenerate AC-TRACEABILITY.md with test_log_schemas nodeids
|
|
|
|
Gate results:
|
|
- [x] 236 total tests pass (baseline 216 + 20 new)
|
|
- [x] -m unit: 210 passed; -m integration: 69 passed; -m blackbox: 12 passed
|
|
- [x] AC --check exit 0; matrix diff clean
|
|
|
|
## Self-Check: PASSED
|