[AZ-528] Consolidate c1_vio strategy facade orchestration spine

Replace 3-way byte-equivalent orchestration-spine duplication across
okvis2.py / vins_mono.py / klt_ransac.py with a single c1-internal
helper at components/c1_vio/_facade_spine.py. Closes cumulative
review batches 52-54 Finding F1. No behaviour change — all existing
AZ-332 / AZ-333 / AZ-334 AC tests pass unmodified (114 c1_vio tests
green, 237 with adjacent regression suite).

The helper exposes 5 stateless free functions (now_iso, bias_norm,
se3_from_4x4, frame_ts_ns, frame_image) and a FacadeSpine mixin
class providing _classify_state / _tick_lost / _emit_transition.
Concrete strategies inherit the mixin and set spine-required
instance attributes in __init__. Mirrors the AZ-527 precedent for
c2_vpr-side _assert_engine_output_dim consolidation.

New test file test_az528_facade_spine.py covers AC-1..AC-8 with 19
tests, including an AST regression guard that prevents future
re-introduction of the consolidated free functions in any strategy
module, plus a Risk-1 static check that every strategy's __init__
assigns every spine-required attribute.

Archive AZ-528 task spec to done/, bump autodev state to batch 56.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 03:03:16 +03:00
parent ac3e288dbd
commit f12789ebf0
9 changed files with 969 additions and 249 deletions
@@ -0,0 +1,97 @@
# Batch 55 — Cycle 1 Report
**Date**: 2026-05-14
**Tasks**: AZ-528 (Hygiene — c1_vio strategy facade orchestration-spine consolidation)
**Verdict**: COMPLETE — PASS
## Summary
Consolidated the 3-way orchestration-spine duplication across the
c1_vio `VioStrategy` modules (`okvis2.py`, `vins_mono.py`,
`klt_ransac.py`) into a single c1-internal helper module
`_facade_spine.py`. Closes cumulative review batches 52-54 Finding
F1 (Medium / Maintainability). Mirrors the AZ-527 precedent for the
c2_vpr-side `_assert_engine_output_dim` consolidation. No behaviour
change — every existing AZ-332 / AZ-333 / AZ-334 AC test passes
unmodified.
## Files added / modified
### Added (2)
- `src/gps_denied_onboard/components/c1_vio/_facade_spine.py` — new
c1-internal helper, 225 lines. Exports 5 free functions (`now_iso`,
`bias_norm`, `se3_from_4x4`, `frame_ts_ns`, `frame_image`) and the
`FacadeSpine` mixin class (`_classify_state`, `_tick_lost`,
`_emit_transition`).
- `tests/unit/c1_vio/test_az528_facade_spine.py` — new test file, 19
tests covering AC-1..AC-8 + a Risk-1 mitigation test (each
strategy's `__init__` statically asserts that every spine-required
attribute is assigned). All pass.
### Modified (3)
- `src/gps_denied_onboard/components/c1_vio/okvis2.py` — inherit from
`FacadeSpine`; import the 5 free functions; delete the 5 local
module-level definitions, 3 instance methods, and the unused
`_BIAS_NORM_FLOOR` constant. Set the 3 spine-required attributes
(`_feature_threshold`, `_producer_id`, `_strategy_label`) in
`__init__`.
- `src/gps_denied_onboard/components/c1_vio/vins_mono.py` — same
pattern as `okvis2.py`.
- `src/gps_denied_onboard/components/c1_vio/klt_ransac.py` — same
pattern; spine threshold reads `self._cfg.min_features_for_pose`
(the KLT-side equivalent of OKVIS2's `degraded_feature_threshold`).
Deferred-consolidation comments referencing this PBI removed.
## Tests
- `tests/unit/c1_vio/test_az528_facade_spine.py` — 19 new tests, all
pass.
- `tests/unit/c1_vio/` (full focused suite) — 120 collected, 114
pass + 6 tier-2 skipped (unchanged from pre-AZ-528 state).
- Adjacent regression suite (`test_az270_compose_root`,
`test_az272_fdr_record_schema`, `test_az273_fdr_client_ringbuf`,
`test_ac1_scaffold_layout`, `tests/unit/c1_vio/`) — 237 pass + 6
skipped.
## AC traceability
| AC | Status | Notes |
|-------|--------|---------------------------------------------------------|
| AC-1 | ✓ | Helper module exposes documented surface. |
| AC-2 | ✓ | `now_iso()` returns aware UTC `+00:00` ISO-8601. |
| AC-3 | ✓ | `bias_norm` matches L2 formula (accel + gyro). |
| AC-4 | ✓ | `se3_from_4x4` builds `gtsam.Pose3` identity correctly. |
| AC-5 | ✓ | `_classify_state` INIT during warmup, T/D thresholds. |
| AC-6 | ✓ | `_tick_lost` demotes T→D first call, escalates to LOST. |
| AC-7 | ✓ | `_emit_transition` exactly one FDR record per change. |
| AC-8 | ✓ | AST guard: 0 module-level free funcs in 3 strategies. |
| AC-9 | ✓ | All AZ-332 / AZ-333 / AZ-334 AC tests pass unmodified. |
| AC-10 | ✓ | AZ-270 layer-lint regression still passes. |
## Code review
See `_docs/03_implementation/reviews/batch_55_review.md` — verdict
PASS, no findings.
## Outcomes & lessons
- Pattern of an "AST regression guard against duplicated free
functions" continues to work well (AZ-508 / AZ-526 / AZ-527 /
AZ-528 all use it). It catches re-introduction by future authors
who don't know the consolidated home exists.
- Mixin-via-instance-attributes (rather than constructor parameters)
keeps strategy `__init__` signatures stable — the AZ-331 factory
signature `(config, *, fdr_client)` is preserved across all three
strategies post-refactor.
- A 3-point hygiene PBI was the right granularity: 1 new module,
3 small edits to existing strategies, 1 test file with AST + import
regression guards. Same shape and complexity as AZ-527.
## Outstanding
None for AZ-528. F2 from cumulative review batches 52-54 (test fake
+ `_patch_pose_recovery` helper consolidation) remains deferred —
sits at a different abstraction layer and will ride a later hygiene
pass.
@@ -0,0 +1,145 @@
# Code Review Report
**Batch**: 55 (AZ-528 — c1_vio strategy facade orchestration-spine consolidation)
**Date**: 2026-05-14
**Verdict**: PASS
## Scope
Single-task hygiene batch closing Finding F1 from
`_docs/03_implementation/cumulative_review_batches_52-54_cycle1_report.md`.
Replaces the 3-way byte-equivalent orchestration-spine duplication
across `okvis2.py`, `vins_mono.py`, `klt_ransac.py` with a single
c1-internal helper module `_facade_spine.py`. No behaviour change;
existing AZ-332 / AZ-333 / AZ-334 AC tests pass unmodified.
### Changed files
- `src/gps_denied_onboard/components/c1_vio/_facade_spine.py` — NEW
c1-internal helper. Exports 5 stateless free functions (`now_iso`,
`bias_norm`, `se3_from_4x4`, `frame_ts_ns`, `frame_image`) and one
mixin class (`FacadeSpine`) providing `_classify_state`,
`_tick_lost`, `_emit_transition`. 225 lines including docstring +
attribute declarations.
- `src/gps_denied_onboard/components/c1_vio/okvis2.py` — inherit from
`FacadeSpine`; import the 5 free functions; delete the 5 local
module-level definitions + 3 instance methods + unused
`_BIAS_NORM_FLOOR` constant; set 3 new spine-required attributes in
`__init__` (`_feature_threshold`, `_producer_id`, `_strategy_label`).
- `src/gps_denied_onboard/components/c1_vio/vins_mono.py` — same
pattern as `okvis2.py`.
- `src/gps_denied_onboard/components/c1_vio/klt_ransac.py` — same
pattern; reuses `_grayscale` (KLT/RANSAC-specific, not consolidated).
Spine threshold attribute reads `_cfg.min_features_for_pose` (the
KLT-side equivalent of OKVIS2's `degraded_feature_threshold`).
- `tests/unit/c1_vio/test_az528_facade_spine.py` — NEW. 19 tests
covering AC-1..AC-8 + a Risk-1 mitigation test that statically
verifies every concrete strategy's `__init__` sets every
spine-required attribute.
## Phase 2 — Spec Compliance
| AC | Test | Verified |
|-------|-----------------------------------------------------------------------------------------------|----------|
| AC-1 | `test_ac1_helper_module_exposes_documented_surface` | ✓ |
| AC-2 | `test_ac2_now_iso_returns_aware_utc_with_plus_offset` | ✓ |
| AC-3 | `test_ac3_bias_norm_matches_l2_formula` + `test_ac3_bias_norm_includes_gyro_component` | ✓ |
| AC-4 | `test_ac4_se3_from_4x4_builds_identity_pose` | ✓ |
| AC-5 | `test_ac5_classify_state_returns_init_during_warmup` + tracking + degraded | ✓ |
| AC-6 | `test_ac6_tick_lost_demotes_tracking_to_degraded_first_call` + escalates-to-lost | ✓ |
| AC-7 | `test_ac7_emit_transition_no_record_on_steady_state` + one-record-per-change + idempotent | ✓ |
| AC-8 | `test_ac8_no_duplicated_free_functions_remain_in_strategy_module` (parametrised over 3 files) | ✓ |
| AC-9 | Existing AZ-332 / AZ-333 / AZ-334 AC tests — all unmodified, all pass | ✓ |
| AC-10 | `tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` | ✓ |
Test counts:
- `tests/unit/c1_vio/test_az528_facade_spine.py` — 19 tests, all pass.
- `tests/unit/c1_vio/` total — 120 collected, 114 pass + 6 tier-2
skipped (unchanged from pre-AZ-528 state).
- Adjacent regression suite (`test_az270_compose_root`, `test_az272_fdr_record_schema`,
`test_az273_fdr_client_ringbuf`, `test_ac1_scaffold_layout`,
`tests/unit/c1_vio/`) — 237 pass + 6 skipped.
Scope discipline: only the 5 files in the task spec's "Included"
list were touched; no test files outside the new
`test_az528_facade_spine.py` were modified (AC-9 hard requirement).
## Phase 3 — Code Quality
- **SRP**: `FacadeSpine` owns one responsibility — the c1_vio
strategy state machine + FDR `vio.health` record emission. Free
functions are stateless pure utilities. Each concrete strategy
retains its geometry pipeline + native-binding driver. Clean split.
- **Naming**: instance attributes the mixin reads are documented in
the class docstring; type annotations on the class declare them for
IDE / type-checker consumption (Risk 1 mitigation).
- **Comments**: deferred-consolidation comments referencing this PBI
(`# post-AZ-334 hygiene PBI ...`) were removed from
`klt_ransac.py` as required by the task spec.
- **Dead code**: removed `_BIAS_NORM_FLOOR` from `okvis2.py` (verified
via grep; not used anywhere in the c1_vio component or
cross-component).
- **DRY**: the consolidation eliminates ~120 lines of duplicated code
across the three strategy modules.
- **Test quality**: every test follows Arrange / Act / Assert; AC
tests use parametrisation where appropriate (AC-8 over the 3
strategy modules).
## Phase 4 — Security Quick-Scan
N/A — pure refactor, no new inputs, no I/O, no string interpolation
into queries, no subprocess invocation. The free functions take only
typed DTOs (`ImuBias`, `NavCameraFrame`, `np.ndarray`); the mixin
methods only mutate instance state.
## Phase 5 — Performance
No hot-path regression. The mixin call sites are byte-equivalent to
the previous in-line definitions (same conditionals, same
`FdrClient.enqueue` invocation, same string interpolation in
`bias_norm`). The single new indirection is method dispatch through
the mixin's MRO — Python class-method resolution is O(1) on a fixed
MRO and is amortised inside the hot-path's existing call overhead.
## Phase 6 — Cross-Task Consistency
Single-task batch — N/A.
## Phase 7 — Architecture Compliance
- **Layer direction**: `_facade_spine.py` imports only from L1
substrate (`_types.nav`, `components.c1_vio.errors`,
`fdr_client.records`) + stdlib (`datetime`, `math`) + numpy. No
imports from a higher layer; no imports from a sibling component.
- **Public API respect**: `_facade_spine` is underscore-prefixed and
NOT in `c1_vio/__init__.py`'s `__all__`. AZ-270's
`test_ac6_only_compose_root_imports_concrete_strategies`
regression-gate passes. Mirrors the AZ-527 precedent for
`_assert_engine_output_dim` in `c2_vpr`.
- **Cycles**: no new cyclic dependencies. `_facade_spine` is a leaf
inside `c1_vio`; the 3 strategy modules already imported from
`c1_vio.errors`, so the new sibling import slots in cleanly.
- **Duplicate symbols**: AC-8 AST regression guard asserts the 5
consolidated free-function names appear only in `_facade_spine.py`.
- **Cross-cutting concerns**: the spine is a c1-internal concern by
task-spec decision (component state machines + per-strategy FDR
record producers do not generalise across c2..c5; AZ-507 carved
that boundary intentionally). NOT hoisted to `shared/helpers/`.
## Findings
None.
## Conclusion
PASS. The consolidation removes ~120 lines of duplication, preserves
behaviour across all three strategies (verified by 237 passing tests
in the focused + adjacent suite), and respects the c1_vio component
boundary. The cumulative review batches 52-54 Finding F1 is closed.
Risk 4 (mixin couples future divergence) is mitigated by the task
spec's documented escape hatch: future divergent strategies can
either override mixin methods or use the free functions in isolation.
The 3 current strategies' state machines are byte-equivalent
post-refactor, so the mixin is the right shape today.
+2 -2
View File
@@ -12,6 +12,6 @@ sub_step:
retry_count: 0
cycle: 1
tracker: jira
last_completed_batch: 54
last_completed_batch: 55
last_cumulative_review: batches_52-54
current_batch: 55
current_batch: 56