mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 09:01:14 +00:00
[AZ-619] Phase A: build_pre_constructed seeds c13_fdr + clock
Adds airborne_bootstrap.build_pre_constructed(config) returning a dict with the two foundational keys: a per-binary shared FdrClient under "c13_fdr" (via make_fdr_client with the new AIRBORNE_MAIN_PRODUCER_ID constant) and a fresh WallClock under "clock". Phases B..F (AZ-620..AZ-624) extend this function additively without breaking the AZ-619 contract. The c13_fdr instance is identity-stable across calls (per the make_fdr_client per-producer cache) so callers can call build_pre_constructed twice and get the same FdrClient back - AC-619.2. Replay-mode override is unchanged: compose_root merges replay_components over pre_constructed so the WallClock here is replaced by TlogDerivedClock in replay binaries (existing contract documented in compose_root's docstring). Tests: 5 new unit tests under tests/unit/runtime_root/ test_az619_pre_constructed_phase_a.py, all passing. AZ-591 not regressed (12/12 in the combined run). Spec moved to _docs/02_tasks/done/. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,10 +6,10 @@ step: 7
|
|||||||
name: Implement
|
name: Implement
|
||||||
status: in_progress
|
status: in_progress
|
||||||
sub_step:
|
sub_step:
|
||||||
phase: 1
|
phase: 12
|
||||||
name: parse
|
name: archive
|
||||||
detail: "AZ-618 split into AZ-619..AZ-624 (subtasks of AZ-618; epic AZ-602). Next batch = AZ-619 (Phase A: c13_fdr + clock, 2pt). AZ-618 stays In Progress as umbrella; subtasks are To Do."
|
detail: "AZ-619 (Phase A) implemented + committed + tests green (12/12 incl. AZ-591 regression). Spec moved to done/. AZ-619 transitioned In Testing. Next batch = AZ-620 (Phase B: c6_descriptor_index + c6_tile_store, 3pt). Recommended session boundary here per 2026-05-18 lesson — fresh session picks up cleanly with AZ-620."
|
||||||
retry_count: 0
|
retry_count: 0
|
||||||
cycle: 1
|
cycle: 1
|
||||||
tracker: jira
|
tracker: jira
|
||||||
last_completed_batch: 89
|
last_completed_batch: 90
|
||||||
|
|||||||
@@ -51,8 +51,10 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, Final
|
||||||
|
|
||||||
|
from gps_denied_onboard.clock.wall_clock import WallClock
|
||||||
|
from gps_denied_onboard.fdr_client.client import make_fdr_client
|
||||||
from gps_denied_onboard.runtime_root import register_strategy
|
from gps_denied_onboard.runtime_root import register_strategy
|
||||||
from gps_denied_onboard.runtime_root.matcher_factory import build_matcher_strategy
|
from gps_denied_onboard.runtime_root.matcher_factory import build_matcher_strategy
|
||||||
from gps_denied_onboard.runtime_root.pose_factory import build_pose_estimator
|
from gps_denied_onboard.runtime_root.pose_factory import build_pose_estimator
|
||||||
@@ -66,12 +68,27 @@ if TYPE_CHECKING:
|
|||||||
from gps_denied_onboard.config import Config
|
from gps_denied_onboard.config import Config
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"AIRBORNE_MAIN_PRODUCER_ID",
|
||||||
"AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS",
|
"AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS",
|
||||||
"AirborneBootstrapError",
|
"AirborneBootstrapError",
|
||||||
|
"build_pre_constructed",
|
||||||
"register_airborne_strategies",
|
"register_airborne_strategies",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
AIRBORNE_MAIN_PRODUCER_ID: Final[str] = "airborne_main"
|
||||||
|
"""Producer ID for the per-binary shared FdrClient placed under
|
||||||
|
``pre_constructed['c13_fdr']``.
|
||||||
|
|
||||||
|
Per-component callers can still obtain their own FdrClient via
|
||||||
|
``make_fdr_client(<their_slug>, config)`` — the cache in
|
||||||
|
:mod:`gps_denied_onboard.fdr_client.client` ensures one instance per
|
||||||
|
``producer_id``. The ``"airborne_main"`` instance is the one passed via
|
||||||
|
``pre_constructed`` for the wrappers that accept ``fdr_client=`` as a
|
||||||
|
kwarg.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
_LOG = logging.getLogger("gps_denied_onboard.runtime_root.airborne_bootstrap")
|
_LOG = logging.getLogger("gps_denied_onboard.runtime_root.airborne_bootstrap")
|
||||||
|
|
||||||
|
|
||||||
@@ -365,6 +382,30 @@ _AIRBORNE_REGISTRATIONS: tuple[
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_pre_constructed(config: "Config") -> dict[str, Any]:
|
||||||
|
"""Build the airborne ``pre_constructed`` dict for :func:`compose_root`.
|
||||||
|
|
||||||
|
AZ-619 (Phase A) lands the foundational keys ``c13_fdr`` and ``clock``.
|
||||||
|
Phases B..F (AZ-620..AZ-624) extend this function to populate the
|
||||||
|
remaining 10 keys in :data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`.
|
||||||
|
|
||||||
|
Returns a fresh dict on each call. The ``c13_fdr`` instance is cached
|
||||||
|
inside :func:`make_fdr_client` (per-producer cache) so two calls within
|
||||||
|
the same process return dicts where ``pre_constructed['c13_fdr']`` is
|
||||||
|
the SAME object — AC-619.2. ``clock`` is a fresh :class:`WallClock`
|
||||||
|
each call (stateless; the cache would be a no-op).
|
||||||
|
|
||||||
|
Replay-mode override: :func:`compose_root` merges ``replay_components``
|
||||||
|
over ``pre_constructed`` so the :class:`WallClock` here is replaced by
|
||||||
|
the replay branch's :class:`TlogDerivedClock`. That's intentional and
|
||||||
|
matches the contract in :func:`compose_root`'s docstring.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"c13_fdr": make_fdr_client(AIRBORNE_MAIN_PRODUCER_ID, config),
|
||||||
|
"clock": WallClock(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def register_airborne_strategies() -> None:
|
def register_airborne_strategies() -> None:
|
||||||
"""Register every airborne (component, strategy) pair into ``_STRATEGY_REGISTRY``.
|
"""Register every airborne (component, strategy) pair into ``_STRATEGY_REGISTRY``.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
"""AZ-619 — Phase A of AZ-618: ``build_pre_constructed`` seeds c13_fdr + clock.
|
||||||
|
|
||||||
|
Verifies the contract at
|
||||||
|
``_docs/02_tasks/todo/AZ-619_pre_constructed_phase_a_c13_fdr_clock.md``:
|
||||||
|
|
||||||
|
* AC-619.1: ``build_pre_constructed(default_config)`` returns a dict
|
||||||
|
containing keys ``c13_fdr`` (FdrClient instance) and ``clock``
|
||||||
|
(WallClock instance).
|
||||||
|
* AC-619.2: invoking twice in the same process returns dicts where
|
||||||
|
``c13_fdr`` is the SAME FdrClient instance (per ``make_fdr_client``
|
||||||
|
cache); ``clock`` may be a fresh WallClock each call.
|
||||||
|
|
||||||
|
AC-619.3 (this file exists with the above tests) is satisfied by the
|
||||||
|
existence of this module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from gps_denied_onboard.clock.wall_clock import WallClock
|
||||||
|
from gps_denied_onboard.config import Config
|
||||||
|
from gps_denied_onboard.fdr_client import client as fdr_client_module
|
||||||
|
from gps_denied_onboard.fdr_client.client import FdrClient
|
||||||
|
from gps_denied_onboard.runtime_root.airborne_bootstrap import (
|
||||||
|
AIRBORNE_MAIN_PRODUCER_ID,
|
||||||
|
build_pre_constructed,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _isolated_fdr_cache() -> Iterator[None]:
|
||||||
|
# Arrange: every test starts with an empty FdrClient cache so AC-619.2's
|
||||||
|
# "same instance across calls" assertion is exercised against fresh state
|
||||||
|
# rather than stale cache from a prior test.
|
||||||
|
fdr_client_module._reset_for_tests()
|
||||||
|
yield
|
||||||
|
fdr_client_module._reset_for_tests()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ac_619_1_default_config_seeds_c13_fdr_and_clock() -> None:
|
||||||
|
# Arrange
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
pre_constructed = build_pre_constructed(config)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert "c13_fdr" in pre_constructed
|
||||||
|
assert "clock" in pre_constructed
|
||||||
|
assert isinstance(pre_constructed["c13_fdr"], FdrClient)
|
||||||
|
assert isinstance(pre_constructed["clock"], WallClock)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ac_619_1_c13_fdr_uses_airborne_main_producer_id() -> None:
|
||||||
|
# Arrange
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
pre_constructed = build_pre_constructed(config)
|
||||||
|
|
||||||
|
# Assert: the FdrClient is keyed under the documented producer ID so
|
||||||
|
# any per-component caller using the same ID gets the same instance.
|
||||||
|
assert pre_constructed["c13_fdr"].producer_id == AIRBORNE_MAIN_PRODUCER_ID
|
||||||
|
|
||||||
|
|
||||||
|
def test_ac_619_2_c13_fdr_cached_across_calls() -> None:
|
||||||
|
# Arrange
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
first = build_pre_constructed(config)
|
||||||
|
second = build_pre_constructed(config)
|
||||||
|
|
||||||
|
# Assert: same FdrClient instance via make_fdr_client cache.
|
||||||
|
assert first["c13_fdr"] is second["c13_fdr"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ac_619_2_clock_is_independent_instance_per_call() -> None:
|
||||||
|
# Arrange
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
first = build_pre_constructed(config)
|
||||||
|
second = build_pre_constructed(config)
|
||||||
|
|
||||||
|
# Assert: WallClock is stateless; identity-share is not contractual.
|
||||||
|
# Both must be valid WallClock instances. Identity may or may not match.
|
||||||
|
assert isinstance(first["clock"], WallClock)
|
||||||
|
assert isinstance(second["clock"], WallClock)
|
||||||
|
|
||||||
|
|
||||||
|
def test_phase_a_only_seeds_two_keys() -> None:
|
||||||
|
"""Phase A scope discipline: exactly the two documented keys.
|
||||||
|
|
||||||
|
Phases B..F (AZ-620..AZ-624) will add more keys; this test will be
|
||||||
|
relaxed at that point. For now it pins the AZ-619 contract precisely so
|
||||||
|
a regression that adds keys here without updating the spec is caught.
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
pre_constructed = build_pre_constructed(config)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert set(pre_constructed.keys()) == {"c13_fdr", "clock"}
|
||||||
Reference in New Issue
Block a user