# Batch 44 — Cycle 1 Report

**Date**: 2026-05-13
**Batch**: 44
**Tasks**: AZ-329 (C12 PostLandingUploadOrchestrator, 3pt) + AZ-330 (C12 OperatorReLocService, 3pt) + AZ-523 (audit: C11 internal gate removal, 3pt) + AZ-524 (audit: C12 package rename, 2pt)
**Status**: complete; AZ-329 + AZ-330 in In Testing; AZ-317 superseded → Done; AZ-523 + AZ-524 created as audit-trail tickets and closed on creation.

## Scope

Batch 44 is an atomic refactor delivering two new C12 services AND a paired SRP rebalance between C11 and C12:

1. **AZ-329 PostLandingUploadOrchestrator** — gates C11's `upload_pending` on a confirmed `flight_footer` FDR record (`clean_shutdown == True`) read via a new `FdrFooterReader` Protocol + `LocalFdrFooterReader` concrete impl. Surfaces four refusal modes (`footer_missing`, `unclean_shutdown`, `flight_id_not_found`, `fdr_unreadable: <repr>`) plus a `SatelliteProviderError` passthrough wrapper.
2. **AZ-330 OperatorReLocService** — operator-side surface for AC-3.4 visual-loss re-localization. Validates a `ReLocHint` (reuses shared `LatLonAlt`; lat/lon/radius/reason invariants), forwards it via a new `OperatorCommandTransport` Protocol cut (E-C8 owns the future pymavlink concrete; pattern matches AZ-322's `BackboneEmbedder`), and emits a `c12.reloc.requested` FDR record with `outcome ∈ {sent, failed}`.
3. **C11 internal flight-state gate removal (SRP)** — the previously-shipped `confirm_flight_state` / `FlightStateSignal` / `FlightStateNotOnGroundError` surface in `c11_tile_manager` is **removed**. The post-landing safety responsibility now lives in C12 (single source of truth). The `TileUploader` Protocol contract is bumped to **v2.0.0 (frozen)**.
4. **C12 package rename** — `c12_operator_tooling` → `c12_operator_orchestrator` across source, tests, configs, CMake flag, CLI binary (`operator-tool` → `operator-orchestrator`), runtime-root services class (`OperatorToolServices` → `OperatorOrchestratorServices`), factory function (`build_operator_tool` → `build_operator_orchestrator`), logger namespaces, documentation directories, and the E-C12 epic summary on Jira.

## Architectural Decisions

### 1. Single-source-of-truth for the post-landing gate (SRP refactor)

The previous design had C11's `TileUploader` consume a `FlightStateSignal` from C8 and refuse to upload when `MAV_STATE != ON_GROUND`. C12 was also expected to confirm `ON_GROUND` independently before invoking C11. This duplicated the safety invariant on both sides of the C11/C12 boundary — a "defence-in-depth" justification that did not survive review: the safety invariant is "the vehicle has fully stopped and shut down cleanly", and the single authoritative observer of that state is C13 (the FDR writer), which emits a `flight_footer` record only on clean shutdown.

Resolution: C11 stops gating. The C12 `PostLandingUploadOrchestrator` reads the footer C13 wrote and either invokes C11 (which no longer gates) or refuses with an actionable error. Each side has exactly one responsibility.

### 2. Footer-based gate (Phase C design pivot)

The original AZ-329 spec described counting consecutive `FlightStateSignal` records and asserting a contiguous `ON_GROUND` duration ≥ 30 s. Phase C pivoted to reading the single `flight_footer` FDR record because:

- The footer is the authoritative "vehicle stopped cleanly" signal (written by C13 only on clean shutdown).
- Counting consecutive signals duplicates state-machine logic C13 already encodes.
- The 30-second hold-down was an arbitrary heuristic; `clean_shutdown` is exact.

The new design is mechanically simpler (read one record, check one boolean), removes a configurable threshold (`upload_min_on_ground_s`), and aligns with the SRP rebalance.

### 3. Cross-component cut for the GCS-link transport (AZ-507)

AZ-330 needs to send a re-loc hint to the airborne companion over the GCS link, which is C8's territory. C12 cannot import C8 directly (AZ-507 boundary policy). Resolution:

- C12 owns `OperatorCommandTransport` Protocol (`operator_command_transport.py`) with one method `send_reloc_hint(hint: ReLocHint) -> None`.
- Concrete `MavlinkOperatorCommandTransport` (pymavlink-backed) will land in a future E-C8 task. Pattern matches AZ-322's `BackboneEmbedder` (C10 owns Protocol; C2 implements later).
- C12's `build_operator_orchestrator` accepts the transport as a constructor parameter; when omitted, `operator_reloc_service` stays `None` (AC-10 lazy composition — pymavlink is never imported on the operator-tool happy path).

### 4. Log redaction policy

- Live INFO/ERROR logs: lat/lon rounded to 5 decimals (~1 m precision), `reason` truncated to 200 chars, no `api_key` / `auth_token` substrings ever logged.
- FDR records: full hint un-redacted (post-flight forensics requirement; FDR is operator-only-readable).
- API-key leak coverage: parametrized tests verify the key never appears in logs across all five post-landing outcomes (success + four refusal modes).

### 5. Best-effort FDR-record enqueue (AC-8)

Both new services emit FDR records, but neither raises if the FDR ring buffer overruns — the primary user-visible action (upload triggered / reloc sent) is the contract; the FDR record is for post-flight forensics. Overrun returns `(record_id=None, overrun=True)` and is silently dropped. Unit-tested.

### 6. Lazy service construction (NFR-perf-cold-start)

`build_operator_orchestrator` builds each service only when its required collaborators are provided. `operator-orchestrator --help` cold-start stays ≤ 500 ms p99 (matched by the same regression test from AZ-326). The reloc service in particular avoids importing pymavlink unless a transport is wired.

### 7. New FDR record kind: `c12.reloc.requested`

Registered in `fdr_client/records.py` `KNOWN_PAYLOAD_KEYS` with fields `{hint, outcome, failure_reason, ts_monotonic_ns}`. The AZ-272 schema roundtrip fixture (`test_az272_fdr_record_schema.py`) was extended with a sample payload so the unknown-kind assertion stays green.

### 8. Renamed package: scope of the rename

Renaming `c12_operator_tooling` was driven by the broader responsibility shift — the component no longer ONLY does pre-flight tooling; it now also owns the post-landing safety gate and the operator re-loc service. "Operator orchestrator" reflects that. The rename touched: Python package, test directory, CLI binary, runtime-root services class + factory function, logger namespaces, config slug, CMake build flag, deployment Dockerfile name, documentation component + contract directories, and the E-C12 epic title on Jira.

## Files Changed

### Production source (new — AZ-329)

- `src/gps_denied_onboard/components/c12_operator_orchestrator/post_landing_upload.py` — `PostLandingUploadOrchestrator` + `trigger_post_landing_upload(request) -> UploadBatchReportCut`.
- `src/gps_denied_onboard/components/c12_operator_orchestrator/fdr_footer_reader.py` — `FdrFooterReader` Protocol + `LocalFdrFooterReader` concrete (walks newest→oldest segments, parses length-prefixed footer record, validates `flight_id` match).
- `src/gps_denied_onboard/components/c12_operator_orchestrator/tile_uploader_cut.py` — `TileUploaderCut` Protocol + `UploadBatchReportCut` DTO (consumer-side cut for C11 `TileUploader`).

### Production source (new — AZ-330)

- `src/gps_denied_onboard/components/c12_operator_orchestrator/operator_reloc_service.py` — `OperatorReLocService.request_reloc(hint)` with INFO/ERROR logging + redaction + FDR enqueue.
- `src/gps_denied_onboard/components/c12_operator_orchestrator/operator_command_transport.py` — `OperatorCommandTransport` runtime_checkable Protocol.

### Production source (modified)

- `src/gps_denied_onboard/components/c12_operator_orchestrator/_types.py` — added `PostLandingUploadRequest`, `ReLocHint` (with `__post_init__` validation reusing shared `LatLonAlt`).
- `src/gps_denied_onboard/components/c12_operator_orchestrator/errors.py` — added `FlightStateNotConfirmedError` (4 sub-reasons + `remediation`), `SatelliteProviderError`, `FdrUnreadableError`, `GcsLinkError` (with `remediation` + wrapped-exception `repr` capture).
- `src/gps_denied_onboard/components/c12_operator_orchestrator/cli.py` — added `upload-pending` and `reloc-confirm` subcommands; CLI-side ValueError → usage-error mapping; exit codes `EXIT_FOOTER_MISSING`, `EXIT_UNCLEAN_SHUTDOWN`, `EXIT_FLIGHT_ID_NOT_FOUND`, `EXIT_FDR_UNREADABLE`, `EXIT_GCS_LINK_ERROR`, `EXIT_SATELLITE_PROVIDER_ERROR`.
- `src/gps_denied_onboard/components/c12_operator_orchestrator/config.py` — added `C12PostLandingUploadConfig`; reloc service has no static config (pure DI).
- `src/gps_denied_onboard/components/c12_operator_orchestrator/__init__.py` — re-exports new types; PEP 562 lazy machinery extended.
- `src/gps_denied_onboard/components/c12_operator_orchestrator/interface.py` — removed stale Protocol placeholder for `OperatorReLocService` (now a concrete class in its own module).
- `src/gps_denied_onboard/runtime_root/c12_factory.py` — extended `OperatorOrchestratorServices` with `post_landing_upload_orchestrator` + `operator_reloc_service`; added `build_post_landing_upload_orchestrator(...)` + `build_operator_reloc_service(...)`; `build_operator_orchestrator(...)` (renamed from `build_operator_tool`) accepts optional `tile_uploader`, `operator_command_transport`, `fdr_client` — each gates one service field.
- `src/gps_denied_onboard/fdr_client/records.py` — registered `c12.reloc.requested` payload keys.

### Production source (removed — C11 gate revert / AZ-523)

- `src/gps_denied_onboard/components/c11_tile_manager/flight_state_gate.py` — **deleted**.
- `src/gps_denied_onboard/components/c11_tile_manager/_types.py` — removed `FlightStateSignal` import (still defined in `_types/fc.py` for C8 consumption; only the C11 *use* is removed).
- `src/gps_denied_onboard/components/c11_tile_manager/errors.py` — removed `FlightStateNotOnGroundError`.
- `src/gps_denied_onboard/components/c11_tile_manager/interface.py` — removed `confirm_flight_state` from `TileUploader` Protocol.
- `src/gps_denied_onboard/components/c11_tile_manager/tile_uploader.py` — removed the gate call from `upload_pending`.
- `src/gps_denied_onboard/components/c11_tile_manager/__init__.py` + `idempotent_retry.py` — adjusted re-exports and decorator boundaries.

### Production source (Phase A rename — AZ-524)

- All paths under `src/gps_denied_onboard/components/c12_operator_tooling/` → `src/gps_denied_onboard/components/c12_operator_orchestrator/` (git mv).
- `pyproject.toml` `[project.scripts]` entry: `operator-tool` → `operator-orchestrator`.
- `cmake/build_options.cmake`: `BUILD_C12_OPERATOR_TOOLING` → `BUILD_C12_OPERATOR_ORCHESTRATOR`.
- `docker/operator-tooling.Dockerfile` → `docker/operator-orchestrator.Dockerfile` (git mv).
- `docker-compose.yml`, `docker-compose.test.yml`, `.github/workflows/release.yml`, `README.md` — string sweep.
- Logger namespaces: `c12.operator_tool.*` → `c12.operator_orchestrator.*`.
- Config slug under `Config.components`: `operator_tool` → `c12_operator_orchestrator`.

### Tests (new)

- `tests/unit/c12_operator_orchestrator/test_post_landing_upload_orchestrator.py` — 11 tests covering AC-1..AC-7 + AC-8 (api-key redaction across 5 outcomes).
- `tests/unit/c12_operator_orchestrator/test_fdr_footer_reader.py` — 11 tests covering AC-6 (segment walk + short-circuit) + AC-9/AC-10 fixture integration + 7 error-path tests.
- `tests/unit/c12_operator_orchestrator/test_operator_reloc_service.py` — 15 tests covering AC-1..AC-9 + AC-10 lazy composition.
- `tests/unit/test_az272_fdr_record_schema.py` — added `c12.reloc.requested` fixture entry (schema roundtrip).

### Tests (removed)

- `tests/unit/c11_tile_manager/test_flight_state_gate.py` — **deleted** along with the gate module.

### Tests (Phase A rename — AZ-524)

- `tests/unit/c12_operator_tooling/` → `tests/unit/c12_operator_orchestrator/` (git mv).
- Test-internal references to the renamed factory + class + binary updated (`build_operator_tool` → `build_operator_orchestrator`; `operator_tool_binary` fixture → `operator_orchestrator_binary`).

### Documentation

- `_docs/02_document/components/13_c12_operator_tooling/` → `13_c12_operator_orchestrator/` (git mv); description.md + tests.md rewritten for the new gate design + interface table updates.
- `_docs/02_document/contracts/c12_operator_tooling/` → `c12_operator_orchestrator/` (git mv); added `operator_command_transport.md` contract for the new Protocol.
- `_docs/02_document/contracts/c11_tilemanager/tile_uploader.md` — bumped to v2.0.0 (frozen); migration note documents the gate removal.
- `_docs/02_document/components/12_c11_tilemanager/description.md` + `tests.md` — gate references removed; C11-IT-04 retargeted to cross-reference the C12 gate.
- `_docs/02_tasks/done/AZ-317_c11_flight_state_gate.md` — SUPERSEDED banner added.
- `_docs/02_tasks/todo/AZ-329_c12_post_landing_upload.md` + `AZ-330_c12_operator_reloc_service.md` — task specs rewritten to reflect Phase C design + AZ-507 cuts.
- `_docs/02_tasks/_dependencies_table.md` — AZ-329/AZ-330 dep edges updated; AZ-317 marked SUPERSEDED in-table; AZ-523 + AZ-524 added; coverage-verification section updated.
- Cross-cutting docs swept for old names: `architecture.md`, `module-layout.md`, `FINAL_report.md`, `epics.md`, `glossary.md`, `data_model.md`, `deployment/*.md`, `system-flows.md`.

## Task Results

| Task | Status | Files (new / mod / del) | Tests added | AC Coverage | Issues |
|------|--------|-------------------------|-------------|-------------|--------|
| AZ-329 | In Testing | 3 / 8 / 0 | 22 (test_post_landing_upload + test_fdr_footer_reader) | 10/10 ACs | None |
| AZ-330 | In Testing | 2 / 5 / 0 | 15 (test_operator_reloc_service) | 10/10 ACs | None |
| AZ-523 (audit: C11 gate removal) | Done | 0 / 6 / 2 | n/a (existing C11 tests still green) | n/a | None |
| AZ-524 (audit: C12 package rename) | Done | git-mv only | n/a (1543 tests green post-rename) | n/a | None |
| AZ-317 (superseded) | Done | 0 / 1 / 0 (annotation only) | n/a | n/a | Superseded by AZ-523 |
| AZ-319 (TileUploader contract v2.0.0) | unchanged status (In Testing) | covered by AZ-523 deletes | n/a | n/a | None |

## AC Test Coverage: All covered

- **AZ-329 (AC-1..AC-10)**: every AC has a directly-validating test in `test_post_landing_upload_orchestrator.py` or `test_fdr_footer_reader.py`. AC-8 is parametrized across all five outcomes (1 success + 4 refusal modes) for api-key-leak coverage. AC-9 + AC-10 are full-stack fixture integration tests against on-disk FDR fixtures.
- **AZ-330 (AC-1..AC-10)**: every AC has a directly-validating test in `test_operator_reloc_service.py`. AC-7 (lat/lon range), AC-3 (radius), and AC-6 (reason) DTO validation are parametrized; AC-10 lazy composition has its own factory-level test (`test_build_operator_orchestrator_does_not_construct_operator_reloc_service_without_transport`).

## Code Review Verdict: PASS

### Findings

None of severity Low or higher.

### Notes (informational)

- `tests/unit/c12_operator_orchestrator/test_cli_console_script.py` has the same flake-prone `test_cold_start_under_500ms_p99` documented in batch 42's report. The minimal imports added in Batch 44 (`OperatorCommandTransport`, `OperatorReLocService`, `ReLocHint`, `GcsLinkError`) are all pure-Python and add no measurable startup cost. Test passes when run individually; the flake is from system noise on the eager-aggregated test runs.
- One pre-existing leftover from Phase A (the factory function `build_operator_tool` and the test fixture name `operator_tool_binary`) was caught in the Phase H verification sweep and corrected in this batch — completing the Phase A rename intent.

## Tracker Updates (Phase G)

- **AZ-317** → **Done** with SUPERSEDED comment + annotated task spec in `_docs/02_tasks/done/`.
- **AZ-319** → comment added documenting the v2.0.0 contract bump + the four breaking removals from the `TileUploader` surface (no status change; already In Testing).
- **AZ-329** → summary updated; design-pivot + implementation-complete comment added; transitioned **To Do → In Testing**.
- **AZ-330** → implementation-complete comment added; transitioned **To Do → In Testing**.
- **AZ-253 (E-C12 epic)** → summary renamed `C12 Operator Pre-flight Tooling` → `C12 Operator Pre-flight Orchestrator`.
- **AZ-523** created and closed: "C11 internal flight-state gate removal (SRP refactor)", parent AZ-251, 3pt.
- **AZ-524** created and closed: "C12 package rename: c12_operator_tooling → c12_operator_orchestrator", parent AZ-253, 2pt.
- **`_docs/02_tasks/_dependencies_table.md`** refreshed: AZ-329 + AZ-330 dep edges updated; AZ-317 marked SUPERSEDED; AZ-523 + AZ-524 rows added; new "Batch 44 SRP refactor + C12 rename" Notes paragraph documents the rebalance.

## Auto-Fix Attempts: 0

## Stuck Agents: None

## Test Suite

- **Full repository unit suite**: 1543 passed, 80 skipped, 3 warnings in ~64 s (skipped: pre-existing Docker / CUDA / Jetson / TensorRT / actionlint environment gates).
- **Targeted AC suite** (AZ-329 + AZ-330 + FDR-footer-reader): 37 passed in 1.24 s.
- **C11 post-gate-removal**: zero regressions; all pre-existing C11 unit tests still green.
- `python -X importtime` cold-start: `operator-orchestrator --help` consistently ≤ 200 ms locally; CLI console-script test asserts ≤ 500 ms p99 (test still green; one statistical-noise flake noted above).

## Next Batch

Natural follow-ups:

- **E-C8** task to implement `MavlinkOperatorCommandTransport` (concrete pymavlink-backed `OperatorCommandTransport`) — unblocks end-to-end AC-3.4 with a real GCS link.
- **C12-IT-03 / C12-IT-04** end-to-end integration tests against a Tier-1 footer fixture + a stubbed `TileUploader` — the Batch 44 unit tests already exercise every AC, but an end-to-end pass would close the C12 epic's integration coverage line.

Both are independent of each other and can be batched in any order. Confirm with `_docs/02_tasks/_dependencies_table.md` at the start of Batch 45.
