Wire the airborne bootstrap to seed pre_constructed['c5_isam2_graph_handle'] so c4_pose's compose-time lookup is satisfied (c4_pose runs before c5_state in topological order; the iSAM2 graph handle is built INSIDE the C5 estimator's constructor and so must be produced eagerly at bootstrap time). build_pre_constructed now invokes a new internal _build_c5_state_estimator_pair helper that calls state_factory.build_state_estimator once, captures the (estimator, handle) tuple, and seeds two slots: 'c5_isam2_graph_handle' for C4's lookup, and an internal '_c5_prebuilt_estimator' look-aside key for the C5 wrapper's short-circuit. _c5_state_wrapper checks the look-aside key first and returns the prebuilt instance as-is — the SAME object the handle was extracted from, so c4_pose._isam2_handle and c5_state._isam2_handle reference ONE object across the C4 / C5 seam (AC-625.3 cross-seam identity invariant). C5_STATE_BUILD_FLAGS mirrors state_factory._STATE_BUILD_FLAGS so the bootstrap can name the gating BUILD_STATE_* flag in operator errors before the lower level StateEstimatorConfigError fires (AC-625.2). When the factory itself rejects the configuration with the flag ON, the error wraps into AirborneBootstrapError with __cause__ preserved (matches AZ-621 / AZ-622 patterns). Constraints respected per AZ-618 umbrella: no per-component factory signature changed; additive on top of AZ-619..AZ-623; no edits under state_factory, pose_factory, or c5_state internals. Tests: tests/unit/runtime_root/test_az625_c5_isam2_graph_handle_ordering.py adds 8 tests covering AC-625.1..3 (presence + Protocol conformance, internal key invariant, BUILD-flag-OFF error, unknown-strategy error, factory error wrapping, cross-seam identity, wrapper short-circuit, wrapper fallback). Autouse stubs added to test_az619/620/621/622/623 so prior phase tests stay isolated from the new builder. Quality gates: ruff format clean, ruff lint clean, 32/32 phase tests pass, 255/255 runtime_root + c5_state regression suite passes. Code review verdict PASS (2 Low findings; full report in _docs/03_implementation/reviews/batch_95_review.md). Co-authored-by: Cursor <cursoragent@cursor.com>
8.3 KiB
Batch Report
Batch: 95 Tasks: AZ-625 (Phase E.5 of AZ-618: c5_isam2_graph_handle ordering — separate handle from estimator construction) Date: 2026-05-19 Cycle: 1
Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|---|---|---|---|---|---|
| AZ-625_c5_isam2_graph_handle_ordering | Done | 7 files | 8 new + 24 carry-over | 4/4 ACs covered | 0 blocking (2 Low / Style + Maintainability — accepted per code review) |
AZ-625 was split out of AZ-623 in batch 94 after the AZ-623 task spec's two-path investigation surfaced an unresolvable construction-order conflict (c4_pose consumes c5_isam2_graph_handle from pre_constructed; the handle is built inside build_state_estimator's tuple return, which c5_state's wrapper invokes — but the c5_state wrapper runs AFTER c4_pose in topological order). Path 1 (handle-only separation) required a Protocol-seam change in C5 — explicitly forbidden by the AZ-618 umbrella's "MUST NOT touch any per-component factory signature" constraint. Path 2 (eager (estimator, handle) build at bootstrap) is the chosen approach: this batch lands it.
Files Changed
Production
src/gps_denied_onboard/runtime_root/airborne_bootstrap.py:- Added
C5_STATE_BUILD_FLAGS: Final[Mapping[str, str]]constant mirroringstate_factory._STATE_BUILD_FLAGSso the bootstrap can name the gatingBUILD_STATE_*flag in operator errors. - Added
_resolve_c5_state_strategy(config)mirroring_resolve_c3_matcher_strategyfor symmetry. - Added
_C5_PREBUILT_ESTIMATOR_KEY: Final[str] = "_c5_prebuilt_estimator"internal coordination key (deliberately NOT inAIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS— only the C5 wrapper consults it as a fast path). - Added
_build_c5_state_estimator_pair(config, *, imu_preintegrator, se3_utils, wgs_converter, fdr_client, tile_store=None, camera_calibration=None, flight_id=None, companion_id=None)that resolves the strategy, gates on the per-strategyBUILD_STATE_*flag, lazily registers the strategy via_ensure_state_strategy_registered, callsbuild_state_estimatoronce, captures the(estimator, handle)tuple, and wraps anyStateEstimatorConfigErrorintoAirborneBootstrapErrorwith__cause__preserved. - Modified
_c5_state_wrapperto short-circuit onconstructed.get(_C5_PREBUILT_ESTIMATOR_KEY): when the look-aside key is present, the prebuilt estimator is returned as-is; otherwise the wrapper falls through to the originalbuild_state_estimatorpath (preserves test isolation for fixtures that bypassbuild_pre_constructed, e.g.test_az401_compose_root_replay). - Extended
build_pre_constructedto call_build_c5_state_estimator_pairafter the AZ-619..AZ-623 keys are populated and seed bothc5_isam2_graph_handle(Public API key consumed by C4) and_c5_prebuilt_estimator(internal coordination key consumed by_c5_state_wrapper). - Updated
__all__to exportC5_STATE_BUILD_FLAGS. - Updated
build_pre_constructed's docstring to describe the AZ-625 wiring + error contract (eager-pair construction, look-aside key, BUILD-flag gating, error wrapping).
- Added
Tests
-
tests/unit/runtime_root/test_az625_c5_isam2_graph_handle_ordering.py(NEW, 8 tests):test_ac_625_1_adds_c5_isam2_graph_handle_with_protocol_surface— AC-625.1 (key present +isinstance(handle, C4ISam2GraphHandle)+ per-methodhasattr+ AZ-619..AZ-623 keys still present).test_ac_625_1_internal_prebuilt_estimator_key_not_in_required_keys— internal-key invariant (the coordination key is NOT exposed viaAIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS).test_ac_625_2_build_state_gtsam_isam2_off_raises_named_error— AC-625.2 (BUILD_STATE_GTSAM_ISAM2=OFFraises with the missing key + flag +c5_stateslug; no upstream cause because the gate fires before the factory).test_ac_625_2_unknown_strategy_raises_named_error_with_supported_set— AC-625.2 (unknown strategy raises with the supported strategy set named).test_ac_625_2_build_state_estimator_config_error_wraps_into_bootstrap_error— defense-in-depth (StateEstimatorConfigErrorwraps intoAirborneBootstrapErrorwith__cause__preserved).test_ac_625_3_handle_is_same_object_as_estimator_isam2_handle— AC-625.3 cross-seam identity (pre_constructed[_c5_prebuilt_estimator]._isam2_handle IS pre_constructed['c5_isam2_graph_handle']).test_ac_625_3_c5_state_wrapper_short_circuits_on_prebuilt_estimator— wrapper short-circuit returns the prebuilt estimator without consulting the fallback infrastructure keys.test_ac_625_3_c5_state_wrapper_falls_back_when_prebuilt_absent— wrapper falls back tobuild_state_estimatorwhen the look-aside key is absent (preserves existing fixture behavior).
-
tests/unit/runtime_root/test_az619_pre_constructed_phase_a.py— added autouse stub for_build_c5_state_estimator_pair(returns(MagicMock, MagicMock)) so AZ-619's bareConfig()bootstrap path doesn't trip on the defaultgtsam_isam2strategy needing a real registered factory. -
tests/unit/runtime_root/test_az620_pre_constructed_phase_b.py— same autouse stub. -
tests/unit/runtime_root/test_az621_pre_constructed_phase_c.py— same autouse stub. -
tests/unit/runtime_root/test_az622_pre_constructed_phase_d.py— same autouse stub. -
tests/unit/runtime_root/test_az623_pre_constructed_phase_e.py— same autouse stub.
Reviews
_docs/03_implementation/reviews/batch_95_review.md(NEW) — code review report (verdict: PASS; 2 Low findings on style + function-scoped import).
Specs
_docs/02_tasks/todo/AZ-625_*.md→ ARCHIVED to_docs/02_tasks/done/.
AC Test Coverage
All 4 ACs covered:
| AC | Coverage |
|---|---|
| AC-625.1 | test_ac_625_1_adds_c5_isam2_graph_handle_with_protocol_surface (presence + Protocol conformance) + test_ac_625_1_internal_prebuilt_estimator_key_not_in_required_keys |
| AC-625.2 | test_ac_625_2_build_state_gtsam_isam2_off_raises_named_error + test_ac_625_2_unknown_strategy_raises_named_error_with_supported_set + test_ac_625_2_build_state_estimator_config_error_wraps_into_bootstrap_error |
| AC-625.3 | test_ac_625_3_handle_is_same_object_as_estimator_isam2_handle + test_ac_625_3_c5_state_wrapper_short_circuits_on_prebuilt_estimator + test_ac_625_3_c5_state_wrapper_falls_back_when_prebuilt_absent |
| AC-625.4 | file tests/unit/runtime_root/test_az625_c5_isam2_graph_handle_ordering.py exists with the above tests |
Test Run
| Suite | Result |
|---|---|
tests/unit/runtime_root/test_az619..test_az623 + test_az625 (targeted) |
32 passed in 1.18s |
tests/unit/runtime_root/ + tests/unit/c5_state/ (regression) |
255 passed in 1.38s |
No failures; no skips beyond pre-existing environment-gated tests.
Code Review
- Verdict: PASS (0 Critical, 0 High, 0 Medium, 2 Low).
- F1 (Low / Style):
_C5_PREBUILT_ESTIMATOR_KEYdefined after first reference site — accepted (grouping with C5-state constants is the dominant readability axis; Python's lazy resolution makes this correct). - F2 (Low / Maintainability): function-scoped
StateEstimatorConfigErrorimport — accepted (matches the file's existing function-scope-import convention for c5_state submodules). - 0 auto-fix attempts; 0 escalated findings.
Full report: _docs/03_implementation/reviews/batch_95_review.md.
Constraint Compliance (AZ-618 umbrella)
- "MUST NOT modify per-component factory signatures" →
state_factory.build_state_estimatorinvoked with the same kwargs the wrapper would have passed; no signature changed. ✓ - "MUST be additive on top of AZ-619..AZ-623" → AZ-619..AZ-623 keys still present in
pre_constructed. ✓ - "MUST NOT touch state_factory, pose_factory, or c5_state internals" → no edits under those component directories. ✓
- "All changes confined to
airborne_bootstrap.py+ new test file" → diff scope respected. The autouse-stub additions in AZ-619..AZ-623 phase tests are hygiene maintenance for the additive bootstrap call (each test now stubs the new_build_c5_state_estimator_pairbuilder), not a behavioral change to those phases' contracts. ✓
Loop Status
- AZ-625 unblocks AZ-624 (the only remaining AZ-618 subtask). AZ-624's dependency edge in
_dependencies_table.mdlistsAZ-619..AZ-623, AZ-625as upstream — all done. Next batch (96) will pick up AZ-624.