- Changed paths in documentation and configuration files to reflect the new naming convention for the NetVLAD model, transitioning from `models/netvlad/netvlad.pt` to `models/net_vlad/net_vlad.pt`.
- Updated the `.gitignore` to include additional file types and directories related to input data and locally-generated evidence frames.
- Removed the old NetVLAD checkpoint file as part of the transition to the new naming scheme.
These changes ensure consistency across the project and improve the management of generated files.
AZ-964 SHIPPED — AZ-840 orchestrator test moves past FAISS gate.
Changes:
* tests/e2e/replay/_faiss_seed.py — extracts the empty HNSW32
seeding logic from scripts/mk_test_faiss_fixture.py into a
reusable test-infra module: seed_empty_faiss_index(root_dir,
*, descriptor_dim=512, backbone_label="ultra_vpr") -> Path.
* scripts/mk_test_faiss_fixture.py rewritten as a thin CLI shim
importing the same helper. compose `tile-init` contract is
preserved.
* tests/e2e/replay/conftest.py::_build_operator_pre_flight_cache
now calls seed_empty_faiss_index(cache_root) immediately before
build_descriptor_index(config), so the factory's _load() finds
a valid .index + .sha256 + .meta.json triplet at the fixture's
override root_dir. populate_c6_from_route later in the fixture
rebuilds the real index once route tiles are downloaded.
* docker-compose.test.jetson.yml: BUILD_PYTORCH_FP16_RUNTIME: "ON"
added to e2e-runner.environment. Scope creep documented honestly
in the spec — Tier-2 surfaced this third config gap on the same
fixture chain while validating AZ-964 (RuntimeNotAvailableError:
... the flag is OFF). One-line wiring; the dustynv/l4t-pytorch
base image bakes the Tegra-tuned PyTorch wheel and
pytorch_fp16_runtime.py exists, so flag flip is sufficient.
Tier-2 verdict (4F / 48P / 3S / 1XF / 1XP in 86.07s, 0 errors —
was 2 errors before this commit): AZ-840 orchestrator test moves
from ERROR at FAISS gate to SKIP at empty-backbones gate — exactly
the AZ-965 gate AZ-964 AC-3 promised. test_operator_pre_flight_
integration SKIPs cleanly too. The 4 derkachi_1min ESKF-divergence
FAILs are constant across all three runs today (AZ-963 path,
independent of orchestrator chain).
Three Tier-2 runs today on the orchestrator chain:
i. pre-AZ-962: SKIP at env-var gate
ii. post-AZ-962: ERROR at FAISS gate
iii. post-AZ-964: SKIP at backbones gate (AZ-965)
Cycle-4 e2e gate still NOT GREEN. Orchestrator chain remaining =
AZ-965 (NetVLAD backbone provisioning); 60s smoke chain remaining
= AZ-963 (ESKF divergence). OKVIS2 deferral directive unchanged.
Pre-existing yamllint false positive on docker-compose.test.jetson
.yml:185 (sibling `volumes:` keys flagged as duplicates without
respecting parent-key scope) — PyYAML parses cleanly with no
duplicates and docker-compose accepts the file at runtime.
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-962 SHIPPED — Tier-2 Jetson AZ-840 orchestrator test no longer
SKIPs at the env-var gate. configs/operator_replay.yaml registers
c6/c7/c10/c11 with sane defaults (backbones intentionally empty,
see AZ-965); docker-compose.test.jetson.yml exports
GPS_DENIED_OPERATOR_CONFIG_PATH=/opt/configs/operator_replay.yaml
and bind-mounts ./configs:/opt/configs:ro. ENV_KEY_MAP gains
SATELLITE_PROVIDER_URL → c11_tile_manager.satellite_provider_url
and SATELLITE_PROVIDER_API_KEY → c11_tile_manager.service_api_key
so secrets flow from .env.test and never sit in YAML. README drops
the manual export step. 97/97 c11 + config unit tests stay green.
Tier-2 re-run (4 failed / 48 passed / 1 skipped / 1 xfailed /
1 xpassed / 2 errors in 84.99s vs baseline 3 skipped — i.e. -2
skipped, +2 errors): AZ-840 orchestrator test moves from SKIP to
ERROR with a deeper, real gate — IndexUnavailableError on
FaissDescriptorIndex against a fresh c6_tile_cache.root_dir.
AZ-964 (3 SP, todo/) filed for FAISS index bootstrap in the AZ-839
C3 fixture. AZ-965 (3 SP, todo/, blocked by AZ-964) filed for
NetVLAD ONNX backbone provisioning — the next gate the orchestrator
test will hit once FAISS clears.
Cycle-4 e2e gate remains NOT GREEN: AZ-840 chain is now AZ-964 →
AZ-965 → PASS; 60s smoke chain is AZ-963 → PASS. OKVIS2 deferral
directive (2026-05-29) unchanged — still gated behind Derkachi
e2e green, still NOT MET.
Co-authored-by: Cursor <cursoragent@cursor.com>
ReportContext.tlog_path was widened in-place by AZ-959 to mean
"ground-truth source path" without renaming, leaving the rendered
report's "- Tlog: <csv_path>" line cosmetically wrong for CSV
runs. This rename + label fix completes the cleanup.
- helpers/accuracy_report.py: field rename + docstring update +
rendered line now reads "- Ground truth: <path>" for both
inputs.
- replay_api/app.py: kwarg updated, AZ-959 inline comment about
the overload removed (field name now carries the intent).
- tests/unit/test_az699_report_writer.py: fixture updated, two
new symmetric tests assert the canonical label for tlog AND
csv inputs (AC-2).
- tests/e2e/replay/_e2e_orchestrator.py +
test_derkachi_real_tlog.py: kwarg updated.
Tests: 62/62 green across test_az699_report_writer.py,
test_az700_render_map.py, test_az701_replay_api.py.
CSV-replay-input chain (AZ-959 + AZ-960 + AZ-961) is now coherent:
- API accepts (video, csv) with XOR validation
- /static/example-csv serves the AZ-896 reference doc
- Runner dispatches --imu vs --tlog argv
- Report renders with source-agnostic "Ground truth:" label
- Map renders from CSV truth via gps-denied-render-map dispatch
Bookkeeping: AZ-961 spec moved todo/ → done/, dep-table preamble
eighth bump documents the rename + summarises the cycle-4 CSV
chain, state.md records batch 7 complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
Closes AZ-835 Epic C6 (docs) and folds the cycle-4 replay-input
redesign narrative (AZ-894 CSV adapter / AZ-895 auto-sync deprecation
/ AZ-896 format spec / AZ-897 UI follow-up) into the three
authoritative documents.
Modified:
- _docs/02_document/contracts/replay/replay_protocol.md: extend
Invariant 12 with sub-invariants 12.c (route-driven supersedes
bbox; ~100x tile efficiency + did-fly-vs-might-fly honesty) and
12.d (fixture failure-handling: validation/terminal re-raise;
transient -> C11 backoff x3). Add Invariant 14 with sub-
invariants 14.a-14.d covering the single canonical clock model,
the CSV-driven path, the tlog adapter's audit-only role, the
auto-sync deprecation, and the AZ-897 UI follow-up pointer.
- _docs/02_document/architecture.md: add the AZ-777 Phase 3+
superseded-by-Epic-AZ-835 supersession block + new "Replay input
redesign (cycle 4)" sub-section with the cycle-4 ticket table.
- tests/e2e/replay/README.md: top section restructured for two
distinct entry points (AZ-265/AZ-404 vs. AZ-835/AZ-840); add
full AZ-835 orchestrator-test section (env vars, skip gates,
expected runtime, verdict report path); add Imagery (c) Google
attribution + dev-only caveat; add Epic AZ-835 ticket map.
Spec deviation: AC-1b says "new Invariant 13" but Invariant 13 is
already taken (C4<->C5 pairing, AZ-776 / ADR-012), and is referenced
by number in architecture.md, c4_pose description.md, and ADR-012
prose. Cycle-4 content shipped as Invariant 14 to preserve those
cross-references; renumbering would have cascaded to 3 files outside
AZ-842's ownership envelope. Documented in batch report.
Out-of-scope hygiene gap (NOT fixed in this batch):
BUILD_CSV_REPLAY_ADAPTER flag is not yet enumerated in
_docs/02_document/module-layout.md's Build-Time Exclusion Map.
Inherited from cycle-4 AZ-894. Suggested as a cycle-5+ hygiene PBI.
AZ-835 epic file stays in todo/ until AZ-841 (backlog) is resolved.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the tlog two-clock replay surface with a single-clock path
driven by the Derkachi-schema CSV. --imu is the new required CLI arg;
--tlog stays as a deprecated alias (warned + ignored when --imu set)
until AZ-895 deletes it.
* csv_ground_truth.py parses the 15-column schema, fails fast at
startup on every documented schema fault (AC-5).
* CsvReplayFcAdapter slots into ReplayInputBundle.fc_adapter alongside
the tlog sibling; mirrors Invariant-5 outbound wiring; inbound bus is
intentionally a no-op since the loop reads CSV directly.
* _run_replay_loop branches on imu_csv_path, stamps
VioOutput.emitted_at_ns from the CSV-derived frame_end_ns (AC-4),
closing the AZ-848 two-clock surface for the new path.
* AZ-896 ships the operator-facing format spec at
_docs/02_document/contracts/replay/csv_replay_format.md plus a
20-row example CSV (AC-3 regression-locked).
Tests: 11 + 12 new unit tests, plus updates to AZ-401 import-boundary
and AZ-402 CLI suites. Full unit suite 2,327 passed / 86 skipped.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cycle-3 refactor run 02-az507 (RouteSpec relocation + module-layout
refresh + AZ-270 lint widening). Single batch of 3 tasks; epic AZ-844.
AZ-845 — Relocate RouteSpec DTO to _types/route.py (rule-9 fix):
* New canonical home: src/gps_denied_onboard/_types/route.py
(frozen+slots dataclass; full docstring carried over verbatim).
* c11_tile_manager/route_client.py imports from _types.route.
* replay_input/tlog_route.py and replay_input/__init__.py keep
re-exports for backward-compat (RouteSpec in __all__).
* 5 test files updated to import from _types.route for symmetry.
* Identity-preserving re-export verified by new test
test_az845_routespec_canonical_home_and_reexport_identity.
AZ-846 — Refresh module-layout.md cycle-3 entries:
* c11_tile_manager Internal list rewritten with all 8 internals
(alphabetised) — corrects a stale entry that referenced files
(satellite_provider_*.py) that no longer exist.
* shared/replay_input file list adds errors.py (cycle-2 carry),
tlog_ground_truth.py (cycle-2 carry), tlog_route.py (cycle-3 NEW).
* shared/_types section registers route.py with provenance line.
* Out-of-scope cycle-2 carry-overs (replay_api/, cli/render_map.py,
helpers/gps_compare.py, etc.) intentionally untouched.
AZ-847 — Widen test_az270 lint to enforce full rule-9 allow-list:
* test_ac6_only_compose_root_imports_concrete_strategies now walks
every components/<X>/*.py ImportFrom/Import and rejects anything
not in the rule-9 allow-list (own subpackage + _types + helpers
+ config/logging/fdr_client/clock + frame_source interface-only).
* Strict superset of the original AC-6 narrow check.
* Reports zero violations on the codebase post-AZ-845.
* Two principled carve-outs documented in the test docstring:
- components/<X>/bench/** path skip (measurement code legitimately
constructs production strategies via runtime_root factories).
- register_* lazy self-registration imports from
runtime_root.<X>_factory (central-registry plugin pattern).
* Both carve-outs surfaced to user via Choose A/B/C/D Risk-1
protocol; user skipped both — agent proceeded with documented
defaults. Doc-only follow-up tracked in
_docs/_process_leftovers/2026-05-24_az847_rule9_wording_followup.md
for rule-9 wording update in module-layout.md.
Test results: 2287 passed, 90 skipped (environmental — Docker / CUDA
/ TensorRT / Jetson hardware / fixtures), 0 failed. Focused subset
(replay_input/ + c11_tile_manager/ + test_az270_compose_root.py)
also clean: 169 passed, 1 skipped.
Tracker: AZ-845/846/847 transitioned In Progress -> In Testing.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wraps the AZ-699 verdict-report path with the AZ-839
operator_pre_flight_setup C3 fixture so a single Tier-2 test
takes only (tlog, video, calibration) and runs the full 7-step
pipeline on the Jetson harness without operator hand-curation.
New surface (tests-only, no src/ changes):
- tests/e2e/replay/_e2e_orchestrator.py — orchestrator with
OrchestratorStep enum, OrchestrationFailure exception (step
prefix per AC-5), OrchestrationReport dataclass,
write_effective_replay_config helper, and
run_e2e_orchestration entry point covering steps 1-2-6-7.
- tests/e2e/replay/test_e2e_orchestrator_unit.py — 17 unit
tests covering each failure mode + happy path with mocked
subprocess + ground-truth loader (AC-8).
- tests/e2e/replay/test_az835_e2e_real_flight.py — Tier-2 +
RUN_REPLAY_E2E gated integration test asserting verdict
report exists, 15-min budget held (AC-1, AC-2, AC-3, AC-4,
AC-6).
The effective config write overlays c6_tile_cache.root_dir
onto the static operator YAML at runtime so the airborne
subprocess shares the cache_root the C3 fixture chose. Field-
level merge — every other operator-config block stays
verbatim. The static YAML on disk is never touched.
Test run: tests/e2e/replay 45 passed, 10 skipped (10 skips
were 9 pre-existing + 1 new tier2). No src/ touched, no
AZ-839 driver changes; AC-7 (AZ-699 still passes) holds by
inspection.
Co-authored-by: Cursor <cursoragent@cursor.com>
The batch 108 fixture built tile_store + descriptor_index from
the static operator config (root_dir baked into YAML) but built
the AC-3/AC-6 verifier from cache_root/descriptor.index (fresh
tmp path). On Tier-2 the descriptor_batcher would write under
the YAML root and the verifier would open the tmp path, raising
IndexUnavailableError before the fixture could yield a
PopulatedC6Cache. Unit tests missed it because every test
stubbed descriptor_index_factory.
Mutate the c6_tile_cache config block in-memory at fixture entry
so root_dir = cache_root and faiss_index_path falls back to
<cache_root>/descriptor.index. Production C6 components and the
verifier now share one path source. Align tile_store_path with
PostgresFilesystemStore's <root_dir>/tiles layout so the
integration test's tile_store_path.is_dir() assertion holds.
Driver and unit tests are path-agnostic and unaffected. Batch
108b report documents the defect, the fix, and the self-review
miss.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the placeholder operator_pre_flight_setup pytest fixture (the
mkdir stub at tests/e2e/replay/conftest.py:293-310) with a real driver
that wires C1 (AZ-836 RouteSpec) + C2 (AZ-838 SatelliteProviderRoute
Client) + C11 (AZ-316 HttpTileDownloader) + C10 (AZ-322 Descriptor
Batcher) end-to-end and yields a typed PopulatedC6Cache. AZ-306 FAISS
sidecar triple-consistency is verified post-rebuild via a caller-
supplied descriptor_index_factory; partial sidecars are cleaned up on
failure (AC-7) while pre-existing warm-cache files are preserved.
Algorithm lives in tests/e2e/replay/_operator_pre_flight.py with
pure dependency injection so the AC-8 unit suite (11 tests covering
happy / transient-retry / terminal-failure / validation-error /
tamper-detection / cleanup-on-failure) runs against stubs and the
AC-9 Tier-2 integration test runs the same algorithm against the
real Jetson harness. The conftest fixture skip-gates on RUN_REPLAY
_E2E + SATELLITE_PROVIDER_URL/API_KEY + BUILD_FAISS_INDEX +
GPS_DENIED_OPERATOR_CONFIG_PATH and wires deps through the existing
runtime_root factories. Supersedes AZ-777 Phase 3.
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 1 hotfix:
- C11 HttpTileDownloader adapted to satellite-provider v2.0.0
z/x/y inventory contract (bulk POST keyed by slippy-map coords).
- Unit tests rewritten to exercise the new inventory schema.
- E2E smoke test updated to match the v2.0.0 wire.
Phase 2 (Derkachi seed + smoke-validated on Jetson):
- tests/fixtures/derkachi_c6/{README,bbox.yaml,seed_region.py}
drives POST /api/satellite/region against satellite-provider
with Google Maps as the imagery source. Smoke run produced
4 regions, 175 tiles, inventory 32/32.
- scripts/mint_dev_jwt.py + run-tests-jetson.sh auto-mint and
export SATELLITE_PROVIDER_API_KEY using JWT_SECRET / JWT_ISSUER
/ JWT_AUDIENCE env vars (no host port mappings; e2e-runner
reaches SP via internal docker network only).
Spec amendment: AZ-777 todo spec updated to record the
Google Maps imagery source decision and STOP-gate state.
AZ-777 Phase 3+ work is superseded by Epic AZ-835 (see next
commit).
Co-authored-by: Cursor <cursoragent@cursor.com>
Adapt C11 HttpTileDownloader to the AZ-505 v1.0.0 tile-inventory
contract (POST /api/satellite/tiles/inventory + GET /tiles/{z}/{x}/{y})
and wire the Jetson e2e harness against the real parent-suite
satellite-provider service. Closes Phase 1 of 5 for AZ-777; STOP
gate before Phase 2 (Derkachi catalog seed).
C11 changes:
- _LIST_PATH / _GET_PATH replaced with _INVENTORY_PATH + _TILES_PATH.
- _do_enumerate enumerates bbox tile coords client-side and posts
chunked inventory requests (5000-entry cap per the contract).
- _download_one_tile parses tile_id_str into (z,x,y) and fetches
the slippy-map URL.
- Common GET / POST retry+auth ladder consolidated into _send_request.
- New module helpers: _enumerate_bbox_tile_coords,
_tile_center_latlon, _tile_size_meters_at, _format_tile_id_str,
_parse_tile_id_str, _chunk_iter.
- _DEFAULT_ESTIMATED_TILE_BYTES (50 KiB) replaces the inventory-side
estimatedBytes field the v1.0.0 contract dropped.
Tests:
- 14/14 unit tests in tests/unit/c11_tile_manager/test_tile_downloader.py
rewritten for the new POST inventory + slippy-map GET handler.
_StubTileWriter rekeyed by call-index (the downloader now derives
lat/lon from the slippy-map coord, so fixtures can't fabricate
arbitrary positions).
- New Tier-2 smoke at tests/e2e/satellite_provider/test_smoke.py:
validates inventory POST schema + drives HttpTileDownloader against
the real service. Gated by RUN_REPLAY_E2E=1 + tier2.
Compose / env:
- e2e-runner SATELLITE_PROVIDER_URL switched from mock-sat:5100 to
https://satellite-provider:8080; TLS_INSECURE + Bearer JWT env +
depends_on satellite-provider added.
- .env.test.example documents SATELLITE_PROVIDER_API_KEY + dev TLS
bypass security note.
- scripts/mint_dev_jwt.py mints HS256 dev JWTs from env / .env.test.
- pyjwt added to dev extras.
Tracker hygiene:
- AZ-777 row in _dependencies_table.md bumped 5pt -> 8pt to match
the 2026-05-21 override decision log.
Code review: PASS_WITH_WARNINGS (3 medium/low findings, all deferred
to later AZ-777 phases) -- see batch_104_review.md. Batch report at
batch_104_cycle3_report.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
New replay_api component: FastAPI service wrapping the offline
gps-denied-replay pipeline. POST tlog+video (multipart) → either
sync 200 with result/map/report URLs, or async 202 + job id with
/jobs/{id} polling. Magic-byte validation, bearer auth, in-memory
JobRegistry with concurrency + queue caps (429 on overflow).
Helper accuracy_report.py promoted from tests/ to src/ because the
API needs the Markdown report writer at runtime; all AZ-699 imports
re-pointed. OpenAPI spec exported to docs.
18/18 unit tests pass (AC-1 sync, AC-2 async, AC-3 state machine,
AC-5 auth, AC-6 health, AC-8 concurrency, AC-9 magic-byte). Full
unit suite: 2251 pass, 86 skip, 1 pre-existing C12 cold-start flake
(unchanged). mypy --strict clean on the new surface.
Co-authored-by: Cursor <cursoragent@cursor.com>
New e2e test runs gps-denied-replay --auto-trim against the real
derkachi.tlog + flight video + AZ-702 calibration, computes the
horizontal-error distribution (mean/p50/p95/p99 + 10/25/50/100 m
threshold-hit share), writes _docs/06_metrics/real_flight_
validation_{date}.md, and asserts honest PASS/FAIL with no @xfail
mask. AZ-404's 1-min test is untouched (sibling, not replacement).
Extends gps_compare.py with HorizontalErrorDistribution +
percentile_sorted (numpy-equivalent linear interpolation). New
test helper _report_writer.py renders the canonical Markdown
schema documented as FT-P-20 in blackbox-tests.md.
16 new unit tests pin distribution arithmetic, verdict gate,
failure-message templating (references calibration acquisition
method per AC-3), and report layout. 129 passed in focused
regression, 3 skipped (real video / Tier-2 prerequisites).
Zero new mypy --strict errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
Batch 98 (cycle 2) — first two PBIs of epic AZ-696 (real-flight
validation harness):
AZ-697: direct binary-tlog GPS-truth extractor
- New src/gps_denied_onboard/replay_input/tlog_ground_truth.py reads
GLOBAL_POSITION_INT (with GPS_RAW_INT fallback) from a binary
ArduPilot tlog via pymavlink.mavutil and returns a frozen+slotted
TlogGroundTruth DTO with per-record ts_ns / lat_deg / lon_deg / alt_m
/ hdg_deg / vx_m_s / vy_m_s / vz_m_s.
- Promoted l2_horizontal_m + match_percentage + GroundTruthRow from
tests/e2e/replay/_helpers.py into the new production module
src/gps_denied_onboard/helpers/gps_compare.py. The e2e helper now
re-exports the same objects (identity, not copies) so existing test
imports continue working untouched.
- tests/e2e/replay/conftest.py prefers the real derkachi.tlog when
present, falls back to the CSV synth path otherwise.
- 22 new unit tests cover AC-1..AC-5 (mypy --strict subprocess test
included). All passing.
AZ-702: Topotek KHP20S30 factory-sheet camera calibration
- New _docs/00_problem/input_data/flight_derkachi/khp20s30_factory.json:
fx = fy = 4644.444, cx = 960, cy = 540, HFOV ~ 23.3 deg, VFOV ~ 13.2
deg, computed from the published 8.5 mm focal length + 1/2.8" sensor
+ 1920x1080 capture at lowest zoom step. Distortion zeroed,
body_to_camera_se3 = identity with nadir convention. Acquisition
method explicitly recorded as factory_sheet so downstream code can
expect higher residual error than a lab calibration.
- _docs/00_problem/input_data/flight_derkachi/camera_info.md updated
to document the assumptions, expected residual error window, and
conftest pick-up rule.
- tests/e2e/replay/conftest.py::_calibration_path() prefers
khp20s30_factory.json when present, falls back to adti26.json.
- 9 new unit tests cover AC-1..AC-4 (schema, intrinsics traceback,
doc reference, conftest pick-up). All passing.
Test run: 45 new tests, all passing. Full-suite gate deferred to
Step 16 (after the last batch in cycle 2 per the implement skill).
Adjacent note (not fixed in this batch, recorded in the batch report):
auto_sync.py has the same redundant pymavlink type:ignore + a few
numpy/cv2 mypy --strict issues. None on this batch's path.
Refs: _docs/03_implementation/batch_98_cycle2_report.md
Refs: _docs/02_tasks/done/AZ-697_tlog_ground_truth_extractor.md
Refs: _docs/02_tasks/done/AZ-702_khp20s30_calibration.md
Co-authored-by: Cursor <cursoragent@cursor.com>
Mid-flight fixtures (Derkachi) and stationary-still scenarios
(FT-P-01) have no take-off spike for the IMU detector and produce
false-positive video motion onsets, so the AC-9 frame-window
validator rejects every plausible offset. Add an operator-acknowledged
opt-out: a new ReplayConfig.skip_auto_sync_validation flag that
suppresses validation, paired with a hard requirement that
time_offset_ms also be set (silent-zero guard at both schema and
adapter layers).
Wired through schema -> CLI (--skip-auto-sync) -> composition root
-> ReplayInputAdapter; Derkachi e2e fixture now passes
time_offset_ms=0 + skip_auto_sync=True by default since the synth
tlog and the video share the same t=0 anchor by construction.
5 new unit tests:
* schema gate rejects skip=True without manual offset
* schema gate accepts the legal pair
* default field value is False (default-construction safety)
* adapter constructor mirrors the schema gate
* adapter open() bypasses validate_offset_or_fail when flag is set
All 38 unit tests in test_az401 + test_az405 pass on Mac.
Co-authored-by: Cursor <cursoragent@cursor.com>
The Derkachi auto-sync coordinator compares absolute tlog timestamps
(from pymavlink's 8-byte record header) against absolute video
timestamps (CAP_PROP_POS_MSEC, which starts at 0). Anchoring the
synthetic tlog at 1_700_000_000_000_000 us (2023-11-14) produced a
~53-year offset (offset_ms=1699999995666) that always tripped the
AC-9 frame-window match validator at 0% match.
Setting the base to 0 puts the tlog on the same axis as the video
(and matches the CSV's `Time` column, which is seconds since row 0
per `_docs/00_problem/input_data/flight_derkachi/README.md`: "the
video and telemetry align at exactly three video frames per
telemetry row").
Verified on Colima with GPS_DENIED_TIER=2: the offset reported by
the auto-sync coordinator drops from 1699999995666 ms to -4334 ms.
The remaining 4.3 s offset is NOT a synth issue — it's the tlog
take-off detector (no signal in the steady-cruise CSV → defaults to
samples.accel[0][0] == 0) vs the video motion-onset detector (which
fires on a scenery-contrast false positive at ~4.3 s). The synth
cannot fabricate a take-off spike at the right time without knowing
the video motion-onset moment a priori, and the README confirms the
fixture is mid-flight footage with no take-off in either signal.
Resolving the remaining 4.3 s mismatch requires SUT-side work to
honor the documented "manual offset bypasses auto-sync" contract —
that's the scope of AZ-611. Filed as a known limitation in the
commit message; AC-1..AC-6 still red until AZ-611 lands.
Co-authored-by: Cursor <cursoragent@cursor.com>
Three discoveries from on-Jetson build (image builds clean in ~3m18s
after fixes; gtsam-4.3a0, torch 2.4.0+cuda, cv2 4.11.0 all import OK
inside container running --runtime=nvidia):
1. dustynv/l4t-pytorch's /etc/pip.conf bakes in a local Jetson mirror
(jetson.webredirect.org) that's only reachable from the maintainer
LAN. pip's DNS lookup fails everywhere else. Wipe the config and
pin --index-url to upstream PyPI.
2. The image ships pip 24.2. The SUT's `gtsam<5.0,>=4.2` constraint
matches ONLY gtsam-4.3a0 on PyPI (no stable aarch64 wheels), and
pip 24.x rejects pre-releases unless --pre is set. The Colima
image lands on the same wheel because its pip 26.x has explicit
fallback-to-pre-release logic. Bump pip before installing the SUT
to align resolver behavior across both harnesses.
3. Skip the [inference] extra entirely — the base image ships
Tegra-tuned torch / torchvision that re-pip would clobber with
x86 builds lacking cuDNN/cuBLAS for Orin.
Co-authored-by: Cursor <cursoragent@cursor.com>
Operator-reported: `nvcr.io/nvidia/l4t-base:r36.4.0` fails to pull.
Investigation against the live registries confirmed:
* `nvcr.io/nvidia/l4t-base` — deprecated in JetPack 6, no r36 tags
(forum thread "L4T Base docker image for Jetpack 6.2 (r36.4.3)",
GitHub dusty-nv/jetson-containers#883).
* `nvcr.io/nvidia/l4t-pytorch` — no r36 tags at all. Newest is
r35.2.1-pth2.0-py3 (too old for our torch>=2.2 floor).
* `nvcr.io/nvidia/l4t-jetpack:r36.4.0` — exists but ships no PyTorch.
* `dustynv/l4t-pytorch:r36.4.0` (Docker Hub) — exists, ~6.3 GB ARM64,
PyTorch + torchvision + opencv pre-baked, maintained by dusty-nv
(NVIDIA's Jetson containers maintainer).
Switched Dockerfile.jetson base to `dustynv/l4t-pytorch:r36.4.0`.
Forward-compatible with the host's R36.5 BSP (NVIDIA containers
tolerate one minor BSP ahead on the host side).
Setup doc fixes:
* smoke-test command now uses `l4t-jetpack:r36.4.0` (the official
replacement for the deprecated `l4t-base`)
* keygen step explicitly states it produces BOTH halves (private +
.pub) in one go
* ssh-copy-id + ssh config show how to specify a custom port
* troubleshooting table gets a new row for the `l4t-base not found`
case so the next dev hits the answer in 30 seconds
Co-authored-by: Cursor <cursoragent@cursor.com>
C7 inference (PytorchFp16Runtime / TensorRTRuntime / OnnxTrtEpRuntime)
is CUDA-only by design — `model.half().cuda()` is hard-wired with no
CPU fallback. The Colima/Tier-1 smoke harness can never exercise C3
matcher or C7 inference. Once AZ-614 fixes the tlog time-base mismatch
and the pipeline reaches those stages, Colima runs would hard-fail at
`.cuda()` instead of cleanly skipping.
This commit lays down the Jetson companion harness and wires the
existing `tier2` auto-skip:
* tests/e2e/Dockerfile.jetson — l4t-pytorch:r36.4.0-pth2.3-py3 base,
same /opt layout as the Colima image so AC-4 AST scan + bind mounts
work identically. Built ON the Jetson via run-tests-jetson.sh.
* docker-compose.test.jetson.yml — mirrors docker-compose.test.yml
but with `runtime: nvidia`, GPU device exposure, and
GPS_DENIED_TIER=2 (turns OFF the tier2 auto-skip).
* scripts/run-tests-jetson.sh — rsync → ssh build → ssh up,
exit-code-from e2e-runner so the local exit code reflects the
remote test verdict. No credentials in the repo; uses
`ssh jetson-e2e` alias resolved via ~/.ssh/config.
* _docs/03_implementation/jetson_harness_setup.md — one-time SSH
key + alias + sshd hardening + GPU verification steps. Documents
the smoke vs. Reality Gate split + the GPS_DENIED_TIER switch.
AZ-617 (mark heavy ACs with tier2): adds @pytest.mark.tier2 to AC-1,
AC-2, AC-3, AC-5, AC-6 in tests/e2e/replay/test_derkachi_1min.py.
Reuses the existing tier2 marker + auto-skip in tests/conftest.py
(scope revision documented as a comment on AZ-617). AC-4a/4b/AC-7/AC-9
stay unmarked — they don't touch CUDA.
Defers to follow-up Jira:
* AZ-614 — Derkachi tlog synth time-base mismatch (unblocks tier2 ACs
actually reaching the GPU stage on the Jetson)
* AZ-616 — replace mock-sat with real ../satellite-provider service
Not run yet: the harness needs operator-side SSH setup to come online
before scripts/run-tests-jetson.sh can be executed end-to-end. Setup
steps documented in jetson_harness_setup.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
Multi-stage Ubuntu 22.04 e2e-runner image installs gps-denied-onboard
(editable) into /opt/venv so the AZ-404 replay tests can subprocess
gps-denied-replay against the Derkachi fixture. Image layout mirrors
the host repo (/opt/pyproject.toml + /opt/src + /opt/tests bind mount)
so Path(__file__).parents[3] resolves to /opt and AC-4's AST scan
finds the components dir.
Entrypoint now runs `pytest /opt/tests/e2e/` instead of the empty
`scenarios/` dir. The bootstrap harness collects 24 tests vs. 0 before.
Compose: e2e-runner env mirrors the companion service (FullSystemConfig
requirements) plus RUN_REPLAY_E2E=1, BUILD_REPLAY_SINK_JSONL=ON;
bind-mounts the Derkachi fixture dir; adds writable fdr-data /
tile-data volumes the SUT requires.
Reality Gate signal is now real: 17 pass / 5 fail / 1 skip / 1 xfail.
The 5 heavy-AC failures share root cause AZ-614 (tlog synth time-base
mismatch, surfaced by the now-functional harness).
Also archives the replayed leftover entries (csv_reporter -> AZ-601,
harness rehab -> AZ-602 epic + 11 child stories).
Co-authored-by: Cursor <cursoragent@cursor.com>
All FC adapter outbound MAVLink bytes now go through the AZ-401
MavlinkTransport seam (NoopMavlinkTransport in replay,
SerialMavlinkTransport in live). New helpers in
_outbound_mavlink_payloads.py extract encode/pack/seq-bump so the four
AP _send sites and the iNav statustext _send site become
encode -> pack -> transport.write. TlogReplayFcAdapter emits real
AP-shape MAVLink bytes through the injected NoopMavlinkTransport,
satisfying replay protocol Invariant 5 and unblocking AZ-401 AC-9.
Closes AZ-558. Also unskips AZ-401 AC-9 and AZ-404 AC-4b. Live wire
output remains byte-identical (proven via two-instance MAVLink
byte-equivalence tests). AST scan asserts no .mav.<name>_send( calls
remain in the retrofit set (AP / iNav / tlog adapters).
Out of scope (logged in review): GCS adapter retrofit; airborne live
strategy registration that would activate the SerialMavlinkTransport
factory injection path.
Tests: 2110 passed, 92 environmental skips, 1 unrelated pre-existing
macOS cold-start flake deselected.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add the initial source, test, infrastructure, CI, configuration, and evidence-path scaffold so dependent implementation tasks have stable package and runtime boundaries.
Co-authored-by: Cursor <cursoragent@cursor.com>