diff --git a/_docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md b/_docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md index 2233856..75528d2 100644 --- a/_docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md +++ b/_docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md @@ -1,112 +1,122 @@ -# Derkachi e2e: wire real satellite-provider + production C10/C11 pipeline into the operator pre-flight fixture +# Derkachi e2e: wire EXISTING parent-suite satellite-provider into the operator pre-flight fixture **Task**: AZ-777_derkachi_c6_reference_fixture -**Name**: Drive the production C10/C11 pre-flight pipeline against a real parent-suite `satellite-provider` service in the e2e harness so the Derkachi clip produces a real FAISS-anchored C4/C5 satellite-fix loop end-to-end -**Description**: Replace the e2e harness's `mock-suite-sat-service` `/healthz`-only stub on the GET tile path with the real `satellite-provider` .NET 8 service (sibling repo at `../satellite-provider`). Seed satellite-provider's Derkachi-bbox tile catalog from a CC-BY-licensed basemap source. Replace the `operator_pre_flight_setup` placeholder fixture in `tests/e2e/replay/conftest.py` with a real fixture that drives the production C11 `HttpTileDownloader` + C10 `DescriptorBatcher` pipeline against the running service, builds C6 (Postgres metadata + filesystem tile store + FAISS HNSW descriptor index), and mounts the populated cache into the e2e-runner container. Un-xfail the Derkachi AC-3 + AZ-699 verdict tests on Tier-2 Jetson; produce the first honest AZ-699 horizontal-error verdict report. -**Complexity**: 8 points (explicit override of the standard 5-pt PBI cap — see decision log entry 2026-05-21 under `_docs/_process_leftovers/2026-05-21_az777_complexity_override.md`; single-ticket containment is preferred over decomposition because the four sub-deliverables only deliver demo-confidence value when shipped together) -**Dependencies**: AZ-776_eskf_open_loop_composition_profile (done — AZ-776 unblocks compose; this task closes the satellite-anchoring loop) +**Name**: Drive the production C10/C11 pre-flight pipeline against the parent-suite `satellite-provider` .NET service ALREADY running in the Jetson e2e harness so the Derkachi clip produces a real FAISS-anchored C4/C5 satellite-fix loop end-to-end +**Description**: The Jetson e2e harness already runs the real `satellite-provider` .NET 8 service (lineage AZ-688 / AZ-691 / AZ-692, services `satellite-provider` + `satellite-provider-postgres` in `docker-compose.test.jetson.yml`), but the e2e-runner still points its `SATELLITE_PROVIDER_URL` at the legacy `mock-sat` fixture and the placeholder `operator_pre_flight_setup` fixture never drives the C10/C11 pipeline. Compounding this, C11's `HttpTileDownloader` path constants (`_LIST_PATH=/api/satellite/tiles`, `_GET_PATH=/api/satellite/tiles/{tile_id}`) do not match the real satellite-provider API surface (`POST /api/satellite/tiles/inventory` for LIST, `GET /tiles/{z}/{x}/{y}` for tile fetch). This task wires the existing service into the e2e-runner, adapts C11 to the real contract, seeds the Derkachi-bbox tile catalog via `POST /api/satellite/request`, replaces the placeholder fixture with a real C10+C11 driver, and un-xfails the Tier-2 Derkachi + AZ-699 verdict tests. +**Complexity**: 8 points (explicit override of the standard 5-pt PBI cap — see decision log entry 2026-05-21 + spec refresh note at `_docs/_process_leftovers/2026-05-21_az777_complexity_override.md`; scope reconciled with reality 2026-05-21 during cycle-3 batch 104. Single-ticket containment preserved — the four sub-deliverables only deliver demo-confidence value when shipped together.) +**Dependencies**: AZ-776 done (eskf open-loop composition profile unblocks the replay graph for Derkachi); relies on prior compose-side work AZ-688 / AZ-691 / AZ-692 (closed in Jira without local task spec files — the `satellite-provider` + `satellite-provider-postgres` services + `.env.test.example` are already present) **Component**: e2e fixtures / c6_tile_cache / c10_provisioning / c11_tile_manager / docker compose **Tracker**: AZ-777 **Epic**: AZ-602 ## Problem -The Derkachi e2e fixture (`_docs/00_problem/input_data/flight_derkachi/`) ships real flight inputs (video, tlog, IMU, camera calibration) but DOES NOT ship the populated C6 tile cache + FAISS descriptor index the replay protocol requires (`replay_protocol.md` Invariant 12: "Real C6 cache in replay: the airborne binary in replay mode reads the same pre-built C6 tile cache the operator built via the normal pre-flight C10/C11/C12 flow"). Two architectural gaps stop the full-protocol C1+C2+C3+C4+C5 pipeline from running against Derkachi today: +The Derkachi e2e fixture (`_docs/00_problem/input_data/flight_derkachi/`) ships real flight inputs but DOES NOT ship the populated C6 tile cache + FAISS descriptor index the replay protocol requires (`replay_protocol.md` Invariant 12). Three architectural gaps stop the full C1+C2+C3+C4+C5 pipeline from running against Derkachi today: -1. **`mock-suite-sat-service` is `/healthz`-only.** The stub at `tests/fixtures/mock-suite-sat-service/main.py` exposes only `GET /healthz` and does NOT implement the `/api/satellite/tiles` contract that C11 `HttpTileDownloader` (production code at `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py`) queries against. Any e2e test that wants to exercise the production tile-download path against the stub gets HTTP 404 the moment C11 calls `_LIST_PATH = "/api/satellite/tiles"`. -2. **`operator_pre_flight_setup` is a placeholder.** The fixture at `tests/e2e/replay/conftest.py` (lines 293-310) `mkdir`s an empty `operator_cache` directory and yields. It does NOT drive C11 download or C10 descriptor-batcher; it does NOT populate C6. The fixture's docstring explicitly calls itself "a stub" pending this ticket. +1. **`e2e-runner` still points at `mock-sat`.** In `docker-compose.test.jetson.yml` the `e2e-runner` env block has `SATELLITE_PROVIDER_URL: http://mock-sat:5100` even though `mock-sat` is no longer defined in that file and the real `satellite-provider` service (https://satellite-provider:8080) IS defined right below. +2. **C11 contract drift.** `c11_tile_manager/tile_downloader.py:61-62` defines `_LIST_PATH = /api/satellite/tiles` and `_GET_PATH = /api/satellite/tiles`. The real satellite-provider exposes `POST /api/satellite/tiles/inventory` (bulk lookup by z/x/y or `locationHashes`) and `GET /tiles/{z:int}/{x:int}/{y:int}` (slippy-map tile fetch) — different paths, different methods, different schemas (`Program.cs:187-209`). +3. **`operator_pre_flight_setup` is a placeholder.** The fixture at `tests/e2e/replay/conftest.py` (lines 293-310) `mkdir`s an empty `operator_cache` directory and yields. It does NOT drive C11 download or C10 descriptor-batcher; it does NOT populate C6. The fixture's docstring explicitly calls itself "a stub" pending this ticket. -The production architecture says (per `architecture.md` Principle #5 + the C10/C11 component descriptions): +Production architecture (per `architecture.md` Principle #5 + the C10/C11 descriptions) requires: - C10 does NOT touch satellite-provider — tile network I/O lives in C11. -- C11 `HttpTileDownloader` is the production path: authenticated GETs against the parent-suite `satellite-provider` .NET 8 REST service (sibling repo at `../satellite-provider/`, real implementation with `SatelliteProvider.Api`, region-onboarding flows, integration tests). -- `satellite-provider` owns the OSM/CARTO tile network I/O + license attribution + multi-flight voting layer — the onboard companion is read-only against it (via C11) during pre-flight and read-only against C6 during flight. -- `mock-suite-sat-service` exists specifically for the D-PROJ-2 ingest (POST upload) endpoint that the parent-suite has not yet shipped — NOT for the GET tile-fetch path. - -The current AZ-777 spec ("write a script under `scripts/build_derkachi_c6_fixture.py` that downloads OSM/CARTO basemap tiles directly") was inconsistent with this architecture: it asked the onboard companion to do network I/O against an external imagery source instead of going through C11→satellite-provider. The corrected scope (this revision) drives the production pipeline end-to-end. +- C11 `HttpTileDownloader` is the production path: authenticated GETs against the parent-suite `satellite-provider`. +- `satellite-provider` owns OSM/CARTO tile network I/O + license attribution + multi-flight voting layer — the onboard companion is read-only against it (via C11) during pre-flight and read-only against C6 during flight. +- `mock-sat` is fully obsolete on Jetson (D-PROJ-2 / `POST /api/satellite/upload` shipped — verified at `Program.cs:211`). Tier-1 (`docker-compose.test.yml`) is deprecated per `_docs/02_document/tests/environment.md` 2026-05-20 active policy and is OUT OF SCOPE. ## Outcome -- The e2e harness `docker-compose.test.yml` runs the real `satellite-provider` .NET 8 service (built from `../satellite-provider/SatelliteProvider.Api/Dockerfile`) alongside the existing `mock-sat` (which is retained only for the D-PROJ-2 POST/upload contract until the parent suite ships it). -- `satellite-provider`'s tile catalog is seeded with the Derkachi bbox (≈50.05–50.15 lat, 36.05–36.15 lon) at the camera-AGL-appropriate zoom levels (15–18) via the service's existing region-onboarding flow (CC-BY-licensed basemap source; license + attribution baked into the seeded catalog's metadata). -- `tests/e2e/replay/conftest.py::operator_pre_flight_setup` is replaced by a real fixture that: - 1. Resolves the Derkachi bbox + camera-derived zoom range from the existing flight fixture. - 2. Invokes C11 `HttpTileDownloader` against the running `satellite-provider` to populate C6 (Postgres metadata + filesystem tile store). - 3. Invokes C10 `DescriptorBatcher` against the populated C6 to build the FAISS HNSW descriptor index via the production NetVLAD backbone (C2 default per `c2_vpr/config.py:67`). - 4. Verifies all three sidecar files (`.index`, `.sha256`, `.meta.json`) per the FAISS sidecar coherence invariant (AZ-306). - 5. Yields the populated cache directory + Postgres connection string for the e2e-runner to mount. -- The populated C6 is mounted into the `e2e-runner` container via named volumes that survive across pytest sessions (so repeated test runs reuse the cache). -- AC-3 (`test_ac3_within_100m_80pct_of_ticks` in `tests/e2e/replay/test_derkachi_1min.py`) un-xfails and passes on Tier-2 Jetson with ≥ 80 % of ticks within 100 m of ground truth. -- AZ-699 verdict test (`test_az699_real_flight_validation_emits_verdict_and_report` in `tests/e2e/replay/test_derkachi_real_tlog.py`) un-xfails and produces the first honest horizontal-error distribution report at `_docs/06_metrics/real_flight_validation_.md`. +- The e2e-runner in `docker-compose.test.jetson.yml` consumes the existing real `satellite-provider` service over `https://satellite-provider:8080` with a self-signed dev cert and a static Bearer `service_api_key` token. `mock-sat` references removed. +- C11 `HttpTileDownloader._LIST_PATH` / `_GET_PATH` adapted to the real satellite-provider API surface (`POST /api/satellite/tiles/inventory` for LIST; `GET /tiles/{z}/{x}/{y}` for tile fetch), with the consumer code in `_do_enumerate` + `_download_one_tile` updated to match. All existing C11 unit tests in `tests/unit/c11_tile_manager/` re-greened against the new contract. +- `satellite-provider`'s tile catalog is seeded with the Derkachi bbox (≈50.05–50.15 lat, 36.05–36.15 lon, zoom 15–18) via `POST /api/satellite/request` (CC-BY-licensed basemap source; license + attribution baked into the seeded catalog's metadata). +- `tests/e2e/replay/conftest.py::operator_pre_flight_setup` replaced by a real fixture that drives adapted C11 + C10 against the seeded catalog and yields a `PopulatedC6Cache` dataclass mounted via named volumes that survive across pytest sessions. +- AC-3 (`test_ac3_within_100m_80pct_of_ticks` in `tests/e2e/replay/test_derkachi_1min.py`) un-xfails on Tier-2 Jetson with ≥ 80 % of ticks within 100 m of ground truth. +- AZ-699 verdict test (`test_az699_real_flight_validation_emits_verdict_and_report`) un-xfails and produces the first honest horizontal-error distribution report at `_docs/06_metrics/real_flight_validation_.md`. ## Scope ### Included -**Phase 1 — satellite-provider stand-up in the e2e harness** +**Phase 1 — wire e2e-runner against existing satellite-provider + C11 contract adaptation** -- `docker-compose.test.yml`: add a `satellite-provider` service that builds from `../satellite-provider/SatelliteProvider.Api/Dockerfile`. Service depends on a `satellite-provider-db` Postgres instance (separate from the existing `db` service for c6 metadata to avoid cross-tenant table collisions). Service exposes port 5101 (`satellite-provider` standard) inside the compose network. -- `e2e-runner` env: replace `SATELLITE_PROVIDER_URL: http://mock-sat:5100` with `SATELLITE_PROVIDER_URL: http://satellite-provider:5101` for the C11 download path. Keep `MOCK_SAT_UPLOAD_URL: http://mock-sat:5100` for the D-PROJ-2 POST stub (until D-PROJ-2 ships). -- `docker-compose.test.jetson.yml`: mirror the same satellite-provider service for Tier-2 (build context unchanged; Jetson uses cross-compiled image once the parent-suite .NET service builds for arm64 — verify in this task whether the existing Dockerfile produces an arm64-capable image, otherwise file a follow-up). -- Smoke test in `tests/e2e/satellite_provider/test_smoke.py`: brings up the docker-compose stack, GETs `/healthz` against the real service, runs a single C11 `HttpTileDownloader.download_for_bbox` call against a 1-tile bbox, asserts the tile arrives in C6 + the metadata row is inserted. Gated by `RUN_REPLAY_E2E=1`. +- `docker-compose.test.jetson.yml` (only the `e2e-runner` service block changes; the existing `satellite-provider` + `satellite-provider-postgres` blocks are unchanged): + - Switch e2e-runner `SATELLITE_PROVIDER_URL: http://mock-sat:5100` → `SATELLITE_PROVIDER_URL: https://satellite-provider:8080`. + - Add `SATELLITE_PROVIDER_TLS_INSECURE: "1"` env var (development-only) so requests accepts the self-signed dev cert. Loud warning + documentation per Risk 2. + - Add `SATELLITE_PROVIDER_API_KEY: ${SATELLITE_PROVIDER_API_KEY}` env sourced from `.env.test` (matches existing `JWT_SECRET` pattern; `.env.test.example` already covers JWT_*, this one extends it with one new variable). + - Add `e2e-runner.depends_on.satellite-provider: { condition: service_healthy }`. + - Remove any residual `mock-sat` reference from the `e2e-runner` env block (the service itself is already gone from the file). +- **C11 contract adaptation** (in `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py`): + - Change `_LIST_PATH = "/api/satellite/tiles"` → `_LIST_PATH = "/api/satellite/tiles/inventory"` and switch `_do_enumerate` from GET-with-query-params to POST-with-JSON-body per AZ-505 / `tile-inventory.md` v1.0.0 (body: `{tiles: [{tileZoom, tileX, tileY}, ...]}` OR `{locationHashes: [...]}`; response order matches request order with `present: true|false`). + - Change `_GET_PATH = "/api/satellite/tiles"` → `_GET_PATH = "/tiles"` and adjust `_download_one_tile` to build `/tiles/{z}/{x}/{y}` from the inventory hit's coordinates instead of `tile_id`. + - Map the response field renames in `TileSummary` construction (existing fields like `tile_id`, `produced_at`, `resolution_m_per_px`, `estimated_bytes` map to whatever the real inventory response uses — verify against `Program.cs` + `tile-inventory.md` and document any per-field adaptation needed). + - Update `tests/unit/c11_tile_manager/test_tile_downloader.py` (and any other unit tests touching the LIST/GET paths) to use the new POST contract + slippy-map GET — these are stubbed-response tests, no live service needed. +- **Smoke test** at `tests/e2e/satellite_provider/test_smoke.py` (new): + - Gated by `RUN_REPLAY_E2E=1` + `@pytest.mark.tier2`. + - Brings up the docker-compose stack (`satellite-provider` + `satellite-provider-postgres` + dependencies). + - TCP-probe `satellite-provider:8080` until healthy. + - Issues one Bearer-authenticated `POST /api/satellite/tiles/inventory` for a 1-tile query (a tile in the Derkachi bbox); asserts a 200 response with the documented schema. + - For an inventory-present tile, fetches via `GET /tiles/{z}/{x}/{y}`; asserts non-empty JPEG bytes return. + - Asserts the C11-adapted code path (`HttpTileDownloader.download_for_bbox` for a 1-tile bbox) successfully writes to C6's tile store + Postgres metadata table. +- `docker-compose.test.yml` (Tier-1) is **NOT** modified. Tier-1 e2e is deprecated per `_docs/02_document/tests/environment.md` 2026-05-20 active policy. +- `.env.test.example` extended with `SATELLITE_PROVIDER_API_KEY=DEV-ONLY-REPLACE-...`. -**Phase 2 — Derkachi tile catalog seeding** +**Phase 2 — Derkachi tile catalog seeding via the real satellite-provider region API** -- `tests/fixtures/derkachi_c6/seed_region.py` (new): a Python helper that calls the real `satellite-provider` region-onboarding endpoint (`/api/regions` or whatever the contract is — verify against the .NET source at `../satellite-provider/SatelliteProvider.Api`) to register the Derkachi bbox + zoom range. The seed run uses CARTO Voyager Basemap as the upstream imagery source (CC-BY-3.0; satellite-provider owns the actual tile download from CARTO and applies the freshness gate). -- `tests/fixtures/derkachi_c6/bbox.yaml`: Derkachi bbox + zoom levels + imagery source + license attribution metadata. The values match the seed script's payload. +- `tests/fixtures/derkachi_c6/seed_region.py` (new): a Python helper that calls `POST /api/satellite/request` against the running satellite-provider to register the Derkachi bbox + zoom range. Body schema verified against `Program.cs::RequestRegion` + the controller source. CARTO Voyager Basemap as the upstream imagery source (CC-BY-3.0; satellite-provider owns the actual tile download from CARTO and applies the freshness gate). +- `tests/fixtures/derkachi_c6/bbox.yaml`: Derkachi bbox + zoom levels + imagery source + license attribution metadata. - `tests/fixtures/derkachi_c6/README.md`: how to re-seed if the satellite-provider DB is wiped; license attribution operators must propagate. **Phase 3 — replace `operator_pre_flight_setup` with a real fixture** - `tests/e2e/replay/conftest.py::operator_pre_flight_setup`: replace the placeholder. The new fixture: - Reads the Derkachi bbox from `tests/fixtures/derkachi_c6/bbox.yaml`. - - Invokes C11 `HttpTileDownloader` against the running satellite-provider service. - - Invokes C10 `DescriptorBatcher` against the populated C6 (NetVLAD backbone per c2_vpr default). + - Invokes the adapted C11 `HttpTileDownloader` against the running satellite-provider service. + - Invokes C10 `DescriptorBatcher` against the populated C6 (NetVLAD backbone per `c2_vpr/config.py:67` default). - Verifies sidecar coherence (`.index` + `.sha256` + `.meta.json` triple-consistency check per AZ-306). - Yields a `PopulatedC6Cache` dataclass that the test bodies consume. -- The fixture's outputs are mounted into the e2e-runner container via named volumes that survive across pytest sessions (so the second test run in the same session reuses the populated cache — re-seeding takes minutes, re-downloading takes longer). +- Outputs mounted into the e2e-runner container via named volumes that survive across pytest sessions. **Phase 4 — un-xfail the Tier-2 tests** -- `tests/e2e/replay/test_derkachi_1min.py::test_ac3_within_100m_80pct_of_ticks`: remove `@pytest.mark.xfail` (still gated by `RUN_REPLAY_E2E=1` env + `tier2` marker — only runs on Tier-2 harness). -- `tests/e2e/replay/test_derkachi_real_tlog.py::test_az699_real_flight_validation_emits_verdict_and_report`: remove `@pytest.mark.xfail`. The test body MUST emit the verdict report to `_docs/06_metrics/real_flight_validation_.md` regardless of PASS/FAIL — the success criterion is that the report exists with the honest distribution, not that the verdict is necessarily green. +- `tests/e2e/replay/test_derkachi_1min.py::test_ac3_within_100m_80pct_of_ticks`: remove `@pytest.mark.xfail` (still gated by `RUN_REPLAY_E2E=1` + `@pytest.mark.tier2`). +- `tests/e2e/replay/test_derkachi_real_tlog.py::test_az699_real_flight_validation_emits_verdict_and_report`: remove `@pytest.mark.xfail`. The test body MUST emit the verdict report regardless of PASS/FAIL — the success criterion is that the report exists with the honest distribution. **Phase 5 — documentation** -- `_docs/02_document/contracts/replay/replay_protocol.md`: Invariant 12 already states "Real C6 cache in replay" — append the AZ-777 / e2e-runner integration detail in a new sub-section under **Composition root extension** describing the operator_pre_flight_setup fixture's behaviour. +- `_docs/02_document/contracts/replay/replay_protocol.md`: extend Invariant 12 with an AZ-777 sub-section describing the operator_pre_flight_setup behaviour against the real satellite-provider. - `_docs/00_problem/input_data/flight_derkachi/README.md`: add a Derkachi C6 section pointing at the seed script + bbox config. -- `_docs/02_document/architecture.md`: append a new sub-section to the existing satellite-provider entry (line ~28) noting that the e2e harness now stands up the real service via `docker-compose.test.yml`; `mock-suite-sat-service` is retained only for the unshipped D-PROJ-2 POST contract. +- `_docs/02_document/architecture.md`: append a sub-section to the existing satellite-provider entry noting that the Jetson e2e harness consumes the real .NET service (AZ-688 / AZ-691 / AZ-692 prior art; AZ-777 closes the C11 contract gap and wires the e2e-runner client). Tier-1 status updated to "deprecated 2026-05-20". ### Excluded -- The D-PROJ-2 POST/upload contract — still gated on the parent-suite design landing. `mock-suite-sat-service` continues to handle the POST stub. -- Multi-flight fixtures — Derkachi only. Other flights each need their own bbox seed and re-run. -- Switching C2 default backbone away from `net_vlad` — out of scope; if the operator wants UltraVPR or DINOv2, they re-run C10 with a different backbone configuration. -- Cross-compilation of satellite-provider for Jetson arm64 if the existing Dockerfile does not produce arm64 — file a follow-up ticket if needed; this task does NOT attempt to land arm64 support in the .NET service. -- Modifying any file under `../satellite-provider/` (sibling repo) — this task is purely additive on the gps-denied-onboard side + docker-compose orchestration. If the .NET service is missing an endpoint the C11 client requires, file a parent-suite ticket and STOP. -- Persisting the populated C6 to git/LFS — the named-volume approach above keeps the cache out of the repo. If repo-committed artifacts become a requirement later, file a follow-up to evaluate LFS size. +- ZERO modifications to `../satellite-provider/`. If a parent-suite gap surfaces beyond C11 adapting to existing endpoints (e.g., inventory response missing fields C11 needs, region-onboarding endpoint rejects the Derkachi payload shape), STOP and file a parent-suite ticket. +- `docker-compose.test.yml` (Tier-1) — OUT OF SCOPE (deprecated 2026-05-20). +- Cross-compile / arm64 follow-up — **CLOSED**: `mcr.microsoft.com/dotnet/aspnet:10.0` has an arm64 manifest (verified 2026-05-21 via `docker manifest inspect`). No follow-up ticket needed. +- `mock-sat` retention — **CLOSED**: already retired from Jetson compose; D-PROJ-2 / `POST /api/satellite/upload` has shipped on the real satellite-provider (`Program.cs:211`). +- Switching C2 default backbone away from `net_vlad` — out of scope. +- Persisting populated C6 to git/LFS — named-volume approach unchanged. ## Acceptance Criteria -**AC-1: Real satellite-provider runs in the e2e harness** -Given `docker-compose.test.yml` with the new `satellite-provider` service -When `docker compose -f docker-compose.test.yml up satellite-provider` is invoked -Then the service builds from `../satellite-provider/SatelliteProvider.Api/Dockerfile`, comes up healthy on port 5101, and `GET /healthz` returns 200 +**AC-1: satellite-provider healthy in Jetson compose** +Given the existing `satellite-provider` + `satellite-provider-postgres` services in `docker-compose.test.jetson.yml` +When `docker compose -f docker-compose.test.jetson.yml up satellite-provider` is invoked +Then both services build, the satellite-provider becomes healthy via TCP probe on port 8080 (per existing healthcheck), and is reachable from any compose-network service via DNS `satellite-provider:8080` -**AC-2: C11 downloads against real satellite-provider succeed** -Given the running satellite-provider service + a seeded Derkachi-bbox tile catalog -When `tests/e2e/satellite_provider/test_smoke.py` runs C11 `HttpTileDownloader.download_for_bbox` for a single tile -Then the tile arrives in the C6 filesystem store, the metadata row is inserted into C6's Postgres, and the freshness label is `fresh` (per the C6 freshness gate) +**AC-2: C11 contract aligns with satellite-provider's actual API** +Given the adapted C11 `_LIST_PATH=/api/satellite/tiles/inventory` (POST) and `_GET_PATH=/tiles/{z}/{x}/{y}` (GET) against the running satellite-provider +When `tests/e2e/satellite_provider/test_smoke.py` runs `HttpTileDownloader.download_for_bbox` for a 1-tile bbox in the Derkachi region (seeded) +Then the inventory POST returns 200 with the documented schema, the tile fetch returns non-empty JPEG bytes, and C6's tile store + Postgres metadata both reflect the tile (freshness label `fresh`) **AC-3: operator_pre_flight_setup drives the production pipeline** Given the running satellite-provider with Derkachi tiles seeded When `tests/e2e/replay/conftest.py::operator_pre_flight_setup` runs -Then C11 `HttpTileDownloader` downloads the Derkachi-bbox tiles into C6, C10 `DescriptorBatcher` builds the FAISS HNSW index over them using the NetVLAD backbone, the three sidecar files (`.index` + `.sha256` + `.meta.json`) pass the AZ-306 triple-consistency check, and the fixture yields a `PopulatedC6Cache` with all three artifact paths populated +Then adapted C11 downloads the Derkachi-bbox tiles into C6, C10 `DescriptorBatcher` builds the FAISS HNSW index using the NetVLAD backbone, the three sidecar files (`.index` + `.sha256` + `.meta.json`) pass the AZ-306 triple-consistency check, and the fixture yields a `PopulatedC6Cache` with all three artifact paths populated -**AC-4: AC-3 Derkachi test un-xfails on Tier-2** -Given AZ-776 landed + the populated C6 from AC-3 mounted into the e2e-runner + the airborne binary configured with `c5_state.strategy = gtsam_isam2` + `c4_pose.enabled = True` +**AC-4: Derkachi AC-3 test un-xfails on Tier-2** +Given AZ-776 landed + the populated C6 from AC-3 mounted into the e2e-runner + `c5_state.strategy = gtsam_isam2` + `c4_pose.enabled = True` When `tests/e2e/replay/test_derkachi_1min.py::test_ac3_within_100m_80pct_of_ticks` runs on Tier-2 Jetson -Then it un-xfails, the test passes (≥ 80 % of ticks within 100 m of ground truth), and the per-frame loop emits `replay.satellite_anchor_inserted` log lines (not the existing `satellite_anchoring_not_wired` warning) +Then it un-xfails, the test passes (≥ 80 % of ticks within 100 m of ground truth), and the per-frame loop emits `replay.satellite_anchor_inserted` log lines (not `satellite_anchoring_not_wired`) **AC-5: AZ-699 verdict report is produced** Given AZ-776 landed + the populated C6 from AC-3 + the real flight video + factory calibration @@ -114,37 +124,38 @@ When `tests/e2e/replay/test_derkachi_real_tlog.py::test_az699_real_flight_valida Then it un-xfails, the test runs to completion within the 15-min NFR budget, and `_docs/06_metrics/real_flight_validation_.md` records the horizontal-error distribution with the honest PASS/FAIL verdict against the ≥ 80 % within 100 m gate (PASS not required for the AC; HONEST report required) **AC-6: Documentation captures the new architecture seam** -Given the rewritten replay protocol doc + the Derkachi fixture README + the architecture sub-section +Given the updated replay protocol doc + Derkachi fixture README + architecture sub-section When a new contributor reads them -Then they understand (i) why the real satellite-provider runs in the e2e harness, (ii) how to re-seed the Derkachi catalog, (iii) which path goes through `mock-sat` vs. real satellite-provider (POST vs. GET), and (iv) what license attribution operators must propagate +Then they understand (i) why the real satellite-provider runs in the Jetson e2e harness, (ii) the C11 contract used against satellite-provider (inventory + slippy-map), (iii) how to re-seed the Derkachi catalog, (iv) what license attribution operators must propagate, and (v) why Tier-1 is deprecated ## Non-Functional Requirements **Performance** - `operator_pre_flight_setup` completes in ≤ 5 minutes on first invocation (cold cache), ≤ 30 seconds on subsequent invocations within the same docker-compose session (warm cache via named volume). -- Built C6 artifacts (tile store + descriptor index) match the airborne C2 lookup latency budget — FAISS HNSW parameters MUST match what production C6 expects (M, efConstruction, efSearch); the index is built once per session, never rebuilt mid-test. +- C11 inventory POST + per-tile GET round-trips MUST stay within the existing C11 retry/backoff schedule (`_DEFAULT_BACKOFF_SCHEDULE_S = (1, 2, 4, 8)`). No new retry budget. **Compatibility** -- Tile on-disk layout `{zoom}/{x}/{y}.jpg` MUST be byte-equivalent to satellite-provider's layout (architecture principle #5) — this is automatic because C11 writes via the C6 production code path. -- FAISS index format MUST be loadable by the airborne `c6_descriptor_index.FaissDescriptorIndex.from_config` impl without code changes — this is automatic because C10 writes via the C6 production code path. -- The .NET satellite-provider service's `/api/satellite/tiles` contract version MUST be compatible with the C11 `HttpTileDownloader._LIST_PATH` / `_GET_PATH` constants (`/api/satellite/tiles`). Mismatch is a parent-suite bug; this task does not patch C11 around it. +- Tile on-disk layout `{zoom}/{x}/{y}.jpg` MUST be byte-equivalent to satellite-provider's layout (architecture principle #5) — automatic via C6 write path. +- FAISS index format MUST be loadable by the airborne `c6_descriptor_index.FaissDescriptorIndex.from_config` impl without code changes — automatic via C6 write path. +- C11 inventory POST schema MUST match `tile-inventory.md` v1.0.0 (AZ-505). Schema mismatch is a parent-suite bug; this task adapts C11 to the documented v1.0.0 contract, no further patches. **Reliability** -- The smoke test (AC-2) MUST fail loud if the satellite-provider service is unreachable, returns malformed responses, or rate-limits — no silent skip. -- The `operator_pre_flight_setup` fixture MUST clean up partial cache state on failure (no half-built FAISS index left around). -- The SHA-256 content-hash gate on the FAISS index (per D-C10-3) MUST be verified at every fixture yield — mismatch raises `IndexUnavailableError`. +- The smoke test (AC-2) MUST fail loud if satellite-provider is unreachable, returns malformed responses, rate-limits, or returns 401/403 (auth failure) — no silent skip. +- `operator_pre_flight_setup` MUST clean up partial cache state on failure (no half-built FAISS index left). +- SHA-256 content-hash gate on the FAISS index (per D-C10-3) verified at every fixture yield — mismatch raises `IndexUnavailableError`. **Security** -- Reference imagery source URLs MUST be HTTPS. License attribution recorded in the seeded catalog's metadata so operators can verify before any derived publication. -- No JWT secrets committed — the satellite-provider service in docker-compose reads `JWT_SECRET` from a `.env.test` file that's `.gitignore`'d; the test environment uses a development-only key. -- C11 download MUST go through the production auth path (Bearer token from satellite-provider's `/api/auth`) — no auth bypass for tests. +- `SATELLITE_PROVIDER_TLS_INSECURE=1` is a **development-only** override. Documented in `.env.test.example` + the smoke test + the architecture sub-section. Production deploys MUST validate against a real CA-issued cert. +- `SATELLITE_PROVIDER_API_KEY` sourced from `.env.test`; never committed; same `.gitignore` pattern as `JWT_SECRET`. +- C11 download goes through the production Bearer-token auth path (`Authorization: Bearer ${SATELLITE_PROVIDER_API_KEY}`) — no auth bypass. ## Unit Tests | AC Ref | What to Test | Required Outcome | |--------|--------------|------------------| -| AC-1 | docker-compose.test.yml validates `satellite-provider` service definition | YAML lints; service has correct build context + port | -| AC-2 | C11 `HttpTileDownloader.download_for_bbox` against a stubbed real satellite-provider response | Returns expected `DownloadBatchReport` with `outcome=SUCCESS` | +| AC-1 | `docker-compose.test.jetson.yml` lints; e2e-runner depends_on satellite-provider | `docker compose -f docker-compose.test.jetson.yml config` exits 0 | +| AC-2 | C11 `_do_enumerate` against a stubbed POST `/api/satellite/tiles/inventory` response | Returns `list[TileSummary]` with correct field mapping | +| AC-2 | C11 `_download_one_tile` against a stubbed GET `/tiles/{z}/{x}/{y}` response | Writes tile bytes + sha256 to C6 adapter | | AC-3 | `operator_pre_flight_setup` fixture yields a `PopulatedC6Cache` with non-empty tile store + FAISS index | All three sidecar files exist + sha256 triple-consistency holds | | AC-3 | Sidecar SHA-256 coherence check inside the fixture | `IndexUnavailableError` raised when one of the three files is tampered | | AC-6 | Fixture README documents the seed invocation | Invocation string + license attribution greps cleanly | @@ -153,44 +164,44 @@ Then they understand (i) why the real satellite-provider runs in the e2e harness | AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References | |--------|------------------------|--------------|-------------------|----------------| -| AC-1 | docker-compose.test.yml + satellite-provider service definition | `docker compose up satellite-provider` | Service comes up healthy in ≤ 60 s | Perf | -| AC-2 | Real satellite-provider running + 1-tile-bbox query | C11 HttpTileDownloader against the live service | Tile arrives in C6 + metadata row inserted + freshness=fresh | Reliability | +| AC-1 | Jetson compose | `docker compose up satellite-provider` | Both services come up healthy in ≤ 60 s | Perf | +| AC-2 | Real satellite-provider running + 1-tile-bbox query | C11 adapted HttpTileDownloader against the live service | Tile arrives in C6 + metadata row inserted + freshness=fresh | Reliability | | AC-3 | Seeded Derkachi catalog + e2e-runner | `operator_pre_flight_setup` cold + warm invocation | Cold ≤ 5 min, warm ≤ 30 s, all three sidecar files coherent | Perf | | AC-4 | AZ-776 landed + populated C6 mounted + full-GTSAM YAML | `test_ac3_within_100m_80pct_of_ticks` un-xfailed on Tier-2 Jetson | Test passes (≥ 80 % within 100 m); `satellite_anchor_inserted` log lines visible | Perf, Compat | | AC-5 | AZ-776 landed + populated C6 mounted + real flight video + factory calibration | `test_az699_real_flight_validation_emits_verdict_and_report` un-xfailed | Test completes ≤ 15 min, verdict report written to `_docs/06_metrics/` | Perf | ## Constraints -- ZERO modifications to files under `../satellite-provider/` (sibling repo). If a parent-suite API gap is discovered (e.g., `/api/satellite/tiles` returns 404 because the endpoint isn't wired), STOP and file a parent-suite ticket; do not work around it on the onboard side. -- Per replay protocol Invariant 5: ZERO outbound network from the e2e-runner once the cache is populated. The cache-population phase needs network (satellite-provider downloads from CARTO upstream), but once the docker-compose `e2e-runner` service is `internal: true`-networked for the airborne replay run, no external host is reachable. Verify with Docker network inspection during AC-4. -- Imagery source MUST be CC-BY-licensed (CARTO Voyager Basemap or equivalent). The seeded catalog records the license + attribution string operators must propagate in any derived publication. -- The seeded Derkachi catalog size budget is 100 MB on the satellite-provider DB side. Over budget → reduce zoom-level coverage; document the trade-off in `bbox.yaml` and `tests/fixtures/derkachi_c6/README.md`. +- ZERO modifications to files under `../satellite-provider/` (sibling repo). If a parent-suite gap is discovered, STOP and file a parent-suite ticket. +- Per replay protocol Invariant 5: ZERO outbound network from the e2e-runner once the cache is populated. The cache-population phase needs network (satellite-provider downloads from CARTO upstream); the airborne replay run is internal-network-only. +- Imagery source MUST be CC-BY-licensed (CARTO Voyager Basemap or equivalent). License attribution recorded in the seeded catalog's metadata. +- The seeded Derkachi catalog size budget is 100 MB on the satellite-provider DB side. Over budget → reduce zoom-level coverage; document in `bbox.yaml`. +- Tier-1 (`docker-compose.test.yml`) is deprecated and MUST NOT be modified by this task. ## Risks & Mitigation -**Risk 1: satellite-provider's `/api/satellite/tiles` contract drifts from what C11 expects** -- *Risk*: C11 `HttpTileDownloader` was implemented against an older satellite-provider contract; recent satellite-provider changes may have moved or renamed the endpoint. -- *Mitigation*: AC-1 smoke test fires the C11 call against the real service before any test depends on it. Any 404/400/contract mismatch surfaces immediately; the failure points at a parent-suite ticket, not an onboard bug. The onboard code path is the standard production code; this task does not modify it. +**Risk 1: C11 inventory response field names drift further from `tile-inventory.md` v1.0.0** +- *Risk*: Even after fixing `_LIST_PATH` + `_GET_PATH`, the response object fields (`tile_id`, `produced_at`, `resolution_m_per_px`, `estimated_bytes`, etc.) may not match the inventory response's actual field names; or the inventory response may not include all the fields C11's `TileSummary` requires. +- *Mitigation*: Phase 1 verifies field mapping against `tile-inventory.md` v1.0.0 + `Program.cs::GetTilesInventory` source. Per-field renames are a gps-denied-onboard side concern (C11 adapter); only fields entirely missing from the inventory response warrant a parent-suite ticket. -**Risk 2: CARTO Voyager basemap residual is too coarse for AC-4** +**Risk 2: Self-signed cert CN/SAN doesn't include `satellite-provider` hostname** +- *Risk*: The dev cert at `../satellite-provider/certs/api.pfx` may be issued for `localhost` only; via compose DNS `satellite-provider:8080` it would fail SSL verification. +- *Mitigation*: Phase 1 introduces `SATELLITE_PROVIDER_TLS_INSECURE=1` env knob — accepted as a **development-only** workaround with prominent warnings in `.env.test.example`, the smoke test, and the architecture doc. Production deploys MUST set this to `0` (default) and use a real cert. Regenerating the dev cert with the right SAN is the cleaner long-term fix but lives on the parent-suite side; file a follow-up ticket if the workaround feels brittle. + +**Risk 3: ~~satellite-provider doesn't build on arm64~~ — CLOSED 2026-05-21** +- `mcr.microsoft.com/dotnet/aspnet:10.0` multi-arch manifest verified via `docker manifest inspect`: arm64, amd64, arm/v7 all present. No follow-up needed. + +**Risk 4: CARTO Voyager basemap residual is too coarse for AC-4** - *Risk*: CC-BY basemap is OSM-derived (street-level features, not satellite features). NetVLAD descriptors may not lock against nadir camera frames well enough for ≥ 80 % within 100 m. -- *Mitigation*: This is an honest discovery surface. AC-4 may fail on accuracy after this task lands — the failure mode shifts from "no anchors at all" (current) to "anchors exist but VPR similarity is too low". The AZ-699 verdict report (AC-5) surfaces the actual horizontal-error distribution; if it lands at e.g. p50 = 250 m, that becomes evidence for a follow-up ticket to seed a satellite-imagery source (Maxar Open Data, Sentinel-2, etc.). The xfail is removed in either case because the test now exercises the real pipeline — the verdict, not the xfail, is the honest signal. - -**Risk 3: satellite-provider doesn't build on arm64 (Jetson)** -- *Risk*: The existing `SatelliteProvider.Api/Dockerfile` uses `mcr.microsoft.com/dotnet/aspnet:10.0` which is amd64-default. Tier-2 Jetson is arm64. -- *Mitigation*: First check whether the multi-arch manifest exists for the dotnet/aspnet image at the pinned version. If yes → no action needed. If no → file a follow-up ticket to multi-arch the satellite-provider Dockerfile; AC-4 + AC-5 stay BLOCKED on Tier-2 until that ticket lands, but Phases 1–3 + AC-1/2/3/6 still complete on Tier-1 in this ticket's scope. - -**Risk 4: docker-compose stand-up flakiness slows down the test suite** -- *Risk*: Cold-bringing up satellite-provider + its Postgres + the gps-denied-onboard companion + e2e-runner across CI pipelines adds wall-clock time. -- *Mitigation*: Named volumes for both the satellite-provider DB and the populated C6 mean only the first run in a CI session pays the cost. Subsequent runs are warm. Document the named volumes in the docker-compose comments + the fixture README so an operator knows to `docker volume prune` if they want to force a re-seed. +- *Mitigation*: This is an honest discovery surface. AC-4 may fail on accuracy after this task lands — the failure mode shifts from "no anchors at all" (current) to "anchors exist but VPR similarity is too low". The AZ-699 verdict report (AC-5) surfaces the actual horizontal-error distribution; if it lands at e.g. p50 = 250 m, that becomes evidence for a follow-up ticket to seed a satellite-imagery source (Maxar Open Data, Sentinel-2, etc.). The xfail is removed in either case — the verdict, not the xfail, is the honest signal. **Risk 5: Single-ticket 8-pt complexity exceeds the standard PBI cap** -- *Risk*: The task is intentionally above the 5-pt cap stated in the project's PBI complexity rule; this can mask the failure mode where a sub-phase blocks and the whole ticket grinds. -- *Mitigation*: The five phases above are explicit handoff points. If Phase 1 (satellite-provider stand-up) fails for reasons outside this ticket's scope (e.g., parent-suite contract drift, arm64 issue), the implementer STOPS at the phase boundary, reports the blocker, and proposes a split into smaller follow-up tickets. The "single ticket" property is preserved as long as the work proceeds linearly; if it grinds at any phase boundary, decomposition is the escape hatch. +- *Risk*: Above the 5-pt cap stated in the project's PBI complexity rule. +- *Mitigation*: The five phases are explicit STOP-gates. If Phase 1 (wiring + C11 adaptation) fails for reasons outside this ticket's scope (e.g., parent-suite contract drift beyond field renames, cert hostname issue requiring parent-suite regen), the implementer STOPS at the phase boundary, files the parent-suite ticket, and proposes a split into smaller follow-up tickets. The "single ticket" property holds as long as work proceeds linearly; if any phase grinds, decomposition is the escape hatch. ### ADR Impact -> Affects ADR-002 (build-time exclusion): unchanged — C11 is already operator-side-only via process-level isolation (architecture Principle #4 + ADR-004); this task just exercises that path against the real upstream. -> Affects ADR-011 (replay is a configuration): unchanged — the per-frame loop is mode-agnostic; this task closes the gap between the live and replay paths' upstream tile source (both now go through the real satellite-provider). +> Affects ADR-002 (build-time exclusion): unchanged — C11 is already operator-side-only via process-level isolation (architecture Principle #4 + ADR-004); this task adapts C11's contract but does not change its build-time isolation. +> Affects ADR-011 (replay is a configuration): unchanged — the per-frame loop is mode-agnostic; this task closes the gap between the live and replay paths' upstream tile source. > Implements architecture principle #5 (satellite-provider on-disk layout) end-to-end against a real flight for the first time. -> No new ADR — the architectural decision is "wire the production C10/C11 pipeline into the e2e harness", which is execution of existing decisions, not a new one. +> No new ADR — the architectural decision is "adapt C11 to the existing satellite-provider contract and wire the e2e harness against the real service", which is execution of existing decisions, not a new one. diff --git a/_docs/_autodev_state.md b/_docs/_autodev_state.md index 6cf0da1..ff78f34 100644 --- a/_docs/_autodev_state.md +++ b/_docs/_autodev_state.md @@ -8,24 +8,8 @@ status: paused sub_step: phase: 7 name: batch-loop - detail: "batch 104 cycle3: AZ-777 spec rewritten (architecture-aligned, 8 pts, single ticket) + Jira synced; In Progress in Jira; Phase 1 (satellite-provider stand-up in docker-compose.test.yml) ready for next /autodev session" + detail: "batch 104 cycle3: AZ-777 spec re-refined against codebase reality + Jira synced; Phase 1 ready (see canonical spec + 2026-05-21 decision-log addendum)" retry_count: 0 cycle: 3 tracker: jira last_completed_batch: 103 -session_handoff: - current_task: AZ-777 - jira_status: in_progress - canonical_spec: _docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md - decision_log: _docs/_process_leftovers/2026-05-21_az777_complexity_override.md - next_session_phase: "Phase 1 — satellite-provider stand-up in docker-compose.test.yml + smoke test at tests/e2e/satellite_provider/test_smoke.py" - parent_suite_paths: - satellite_provider_repo: ../satellite-provider/ - api_dockerfile: ../satellite-provider/SatelliteProvider.Api/Dockerfile - api_port_default: 8080 - integration_test_compose: ../satellite-provider/docker-compose.tests.yml - notes: - - "C2 default backbone is net_vlad (c2_vpr/config.py:67) — Phase 3 fixture uses it." - - "STOP gates apply between phases — see canonical spec Risk 5 + Phase headers." - - "If satellite-provider 's /api/satellite/tiles contract drifts from C11 expectations, STOP and file parent-suite ticket; do not patch C11." - - "Tier-2 arm64 of satellite-provider not yet validated; check multi-arch manifest in Phase 1 or file follow-up." diff --git a/_docs/_process_leftovers/2026-05-21_az777_complexity_override.md b/_docs/_process_leftovers/2026-05-21_az777_complexity_override.md index 9436dd2..2ee5368 100644 --- a/_docs/_process_leftovers/2026-05-21_az777_complexity_override.md +++ b/_docs/_process_leftovers/2026-05-21_az777_complexity_override.md @@ -44,3 +44,24 @@ The "single ticket" property is preserved as long as work proceeds linearly. If This is NOT a tracker write blocker — Jira is reachable and the AZ-777 description + story points update is being made in the same /autodev turn that this decision log is being written. This file is the AUDIT TRAIL for the override, not a deferred-write record. No replay action required on subsequent /autodev invocations. The file can be deleted once AZ-777 is moved to `done/`, but it's small enough that keeping it as historical documentation of the decision is fine. + +## 2026-05-21 spec-refresh addendum (cycle-3 batch 104) + +The /autodev session that was supposed to execute Phase 1 instead discovered material drift between the prior session's rewritten spec and current codebase reality. Findings: + +- **Tier-1 is deprecated** per `_docs/02_document/tests/environment.md` 2026-05-20 active policy. The original Phase 1 explicitly modified `docker-compose.test.yml` — that file is now out of scope. +- **Jetson compose already has the real satellite-provider service** (`satellite-provider` + `satellite-provider-postgres`, lineage AZ-688 / AZ-691 / AZ-692; no local task spec files for those tickets — they were closed Jira-side without local /decompose output). Original spec said "add service" — already there. +- **Port / protocol mismatch**: original spec said port 5101 HTTP; actual is 8080 HTTPS with self-signed dev cert (per Dockerfile `EXPOSE 8080` + Jetson compose `ASPNETCORE_URLS: https://+:8080`). +- **DB naming**: original spec said `satellite-provider-db`; existing convention is `satellite-provider-postgres`. +- **`mock-sat` already retired** from Jetson compose. D-PROJ-2 / `POST /api/satellite/upload` has shipped on the real satellite-provider (`Program.cs:211`). `MOCK_SAT_UPLOAD_URL` env var that the original spec proposed retaining doesn't exist in source code at all. +- **C11 contract drift surfaced**: C11's `_LIST_PATH = /api/satellite/tiles` and `_GET_PATH = /api/satellite/tiles` constants in `tile_downloader.py:61-62` do NOT match the real satellite-provider API. Actual endpoints (`Program.cs:187-209`): + - `POST /api/satellite/tiles/inventory` (bulk lookup by `(z,x,y)` or `locationHashes` per `tile-inventory.md` v1.0.0) + - `GET /tiles/{z}/{x}/{y}` (slippy-map tile fetch) + Phase 1 now includes C11 contract adaptation — this is the largest single sub-deliverable of the refreshed Phase 1 and explains why the 8-pt budget stays appropriate even after dropping the Tier-1 mods. +- **arm64 manifest verified**: `mcr.microsoft.com/dotnet/aspnet:10.0` has a multi-arch manifest including arm64 (per `docker manifest inspect`). Risk 3 of the original spec (cross-compile follow-up) is **CLOSED** — no follow-up ticket needed. + +The user chose option A from the spec-reconciliation Choose block: refresh the spec to match reality, re-sync Jira, then proceed with the corrected Phase 1 in a fresh session. + +The 8-pt complexity stays. Phase boundaries (STOP gates between phases) preserved. Single-ticket containment preserved. + +Both this addendum and the canonical local spec at `_docs/02_tasks/todo/AZ-777_derkachi_c6_reference_fixture.md` were updated in the same /autodev turn that synced the refresh to Jira.