The previous /autodev session committed batch-34 (AZ-507 + AZ-323 +
AZ-324) and recorded the completion in _autodev_state.md but never
wrote the batch report file. Backfill the report now so the
cumulative-review trigger and resumability scans see the true latest
batch on disk. Reconstructed from commit e2bebef diff stats, the
three task specs in done/, and the cumulative_review_batches_31-33
context that opened AZ-507.
Co-authored-by: Cursor <cursoragent@cursor.com>
18 KiB
Batch 34 / Cycle 1 — Implementation Report
Date: 2026-05-13 (report backfilled 2026-05-13 from commit
e2bebef and the three task specs in _docs/02_tasks/done/; batch
itself was committed on 2026-05-13 02:37 UTC+3 by the previous
session, which marked the batch complete in _autodev_state.md but
did not persist this report file)
Tasks: AZ-507 (cross-cutting hygiene — module-layout ↔ AZ-270
lint alignment + _types/inference_errors.py shim), AZ-323 (C10
ManifestBuilder + Ed25519ManifestSigner), AZ-324 (C10
ManifestVerifierImpl)
Story points landed: 8 (AZ-507 = 2, AZ-323 = 3, AZ-324 = 3)
Status: complete (AZ-507, AZ-323, AZ-324 → In Testing)
Scope summary
Three-task batch that closes the F1 architecture finding from
cumulative_review_batches_31-33_cycle1_report.md and lands the
signed-Manifest production + airborne verification halves of the C10
trust chain.
The originally planned 4-task batch (AZ-507 + AZ-306 + AZ-323 +
AZ-324, 13 pts; per the chore: record batch-34 selection state
commit) shipped the 3 c10/cross-cutting tasks together; AZ-306
(C6 FAISS pybind11 backbone) was deferred to batch 35 because the
required C++ pybind11 toolchain was not installed in this
environment when the batch ran. The toolchain (cmake 4.3.2,
libomp 22.1.5, pybind11 3.0.4) has since been provisioned, so AZ-306
unblocks for batch 35.
AZ-507 — Module-layout ↔ AZ-270 lint alignment
Resolves cumulative review F1: module-layout.md had documented
components.X (Public API) cross-component imports, but the AZ-270
lint (tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies)
forbids any from gps_denied_onboard.components.X import … outside
the importer's own component, regardless of "Public API" status.
Option (a) from the review was applied:
- Added
src/gps_denied_onboard/_types/inference_errors.py— a Layer-0 shim that re-exportsEngineBuildErrorandCalibrationCacheErrorfromc7_inferenceinternals so consumers catch a typed envelope instead of widening toexcept Exception. The canonical class definitions stay inc7_inference; the shim is import-only, no module-load side effects. - Narrowed
c10_provisioning/engine_compiler.py::_compile_one'sexcept Exceptiontoexcept (EngineBuildError, CalibrationCacheError)(the typed envelope) so any unknown exception now propagates with its original type — addresses AZ-507 AC-3. - Rewrote every
module-layout.md"Imports from" line that namedcomponents.X (Public API)for 9 components to instead point at_types, and added Rule 9 codifying the rule. - Appended ADR-009 to
_docs/02_document/architecture.mdexplaining why cross-component imports go through_types/*and thatruntime_root/*(composition root) is the only exception.
Future c10/c11/c12 tasks needing C7 typed errors now have a sanctioned import path that does not collide with the AZ-270 lint.
AZ-323 — C10 ManifestBuilder + Ed25519ManifestSigner
Lands the signed-Manifest production half of the C10 trust chain.
ManifestBuilder.build_manifest(input) produces canonical-JSON
Manifest.json (via orjson.dumps(..., option=OPT_SORT_KEYS | OPT_INDENT_2)), atomic-writes it through AZ-280
Sha256Sidecar.write_with_sidecar so the Manifest's own
Manifest.json.sha256 is emitted alongside, computes a detached
Ed25519 signature over the canonical bytes via Ed25519ManifestSigner
(cryptography.hazmat.primitives.asymmetric.ed25519), and writes
Manifest.json.sig atomically. The operator-key fingerprint
allowlist gate (C10-ST-01) is fail-closed: in signing_mode = "operator" an unknown fingerprint raises ManifestWriteError with
ZERO files written; in signing_mode = "dev" an operator-allowlisted
key emits a single c10.manifest.dev_mode_with_operator_key WARN.
ADR-010 is honoured: takeoff_origin (LatLonAlt) and flight_id
(UUID) from C12's FlightsApiClient are baked into BOTH the
Manifest body (flight.takeoff_origin / flight.flight_id) AND the
manifest_hash build-identity tuple. Re-planning with a different
takeoff origin OR a different flight_id changes manifest_hash, so
the cache identity tracks the mission (D-C10-1 idempotence). The
manifest_hash excludes built_at so two builds of the same input
on different days produce the same hash. Tile coverage hashing is
sort-deterministic over (zoom, lat, lon, source).
AZ-324 — C10 ManifestVerifier
Lands the airborne (and operator-mode) verification half. Implements
the contract at _docs/02_document/contracts/c10_provisioning/manifest_verifier.md.
verify_manifest(manifest_path, trusted_public_keys) walks four
fail-closed steps:
- Step A — Manifest exists and its
Manifest.json.sha256sidecar matches the Manifest bytes. - Step B — Detached Ed25519 signature parses (must be exactly
64 bytes) and verifies against at least one
trusted_public_keysmember; the verified key's fingerprint is recorded. - Step C — Schema parse rejects absolute paths and
..segments; validates the optionalflightblock (takeoff_originin WGS84 ranges + insidebuild.bbox); per MV-INV-9, populatestakeoff_originonVerificationResulteven on FAIL so operators see what was attempted. - Step D — Per-artifact stream-SHA-256 walk with multi-failure
accumulation (every entry walked even on first failure, per
MV-TC-9). Operator mode (
tile_metadata_storeinjected) re-derivestiles_coverage_sha256from C6; airborne mode (None) trusts the signed aggregate (MV-INV-5).
Returns a populated VerificationResult with outcome ∈ {PASS, FAIL}, fail_reasons: list[VerifyFailReason], the populated
per_artifact_checks, the pass-through takeoff_origin /
flight_id, and elapsed_ms. Never raises on a verify failure —
even MANIFEST_NOT_FOUND returns FAIL with the reason populated
(MV-INV-1).
Composition root
runtime_root/c10_factory.py gained build_manifest_builder +
build_manifest_verifier + c6_tile_metadata_store_to_tiles_query
adapter — the one place that legitimately imports both C6 and C10
without violating the AZ-270 lint (composition root is the
documented exception).
Files added / modified
New (production)
src/gps_denied_onboard/_types/inference_errors.py(AZ-507) — Layer-0 shim re-exportingEngineBuildErrorandCalibrationCacheErrorfromc7_inferenceinternals. 30 lines.src/gps_denied_onboard/components/c10_provisioning/manifest_builder.py(AZ-323) —ManifestBuilder+Ed25519ManifestSigner+C10ManifestConfig+ManifestBuildInput+ManifestArtifact+ManifestSignerProtocol. 675 lines.src/gps_denied_onboard/components/c10_provisioning/manifest_verifier.py(AZ-324) —ManifestVerifierImpl+ Steps A–D logic +VerificationResult/ArtifactCheck/VerifyFailReasonpopulation helpers. 748 lines.src/gps_denied_onboard/components/c10_provisioning/errors.py(AZ-323/324) —ManifestWriteError+ManifestVerifyErrorenvelope.
Modified (production)
src/gps_denied_onboard/components/c10_provisioning/__init__.py— re-exports the AZ-323/324 public surface.src/gps_denied_onboard/components/c10_provisioning/config.py— extended withC10ManifestConfigblock (signing_mode + allowed_operator_fingerprints + schema_version).src/gps_denied_onboard/components/c10_provisioning/engine_compiler.py(AZ-507) — narrowedexcept Exceptiontoexcept (EngineBuildError, CalibrationCacheError)per AZ-507 AC-3.src/gps_denied_onboard/components/c10_provisioning/interface.py— addedManifestSignerProtocol (AZ-323) +ManifestVerifierProtocol re-export from contract (AZ-324).src/gps_denied_onboard/runtime_root/c10_factory.py(+140 lines) —build_manifest_builder+build_manifest_verifier+c6_tile_metadata_store_to_tiles_queryadapter.pyproject.toml— pinnedcryptography>=43.0,<46.0(already used by AZ-318 per-flight keys; same pin re-stated for AZ-323 signer surface).
New (tests)
tests/unit/c10_provisioning/test_manifest_builder.py(AZ-323) — 20 unit tests covering all 16 ACs (happy path, determinism, signature verify, operator-mode allow + reject, dev-mode warn, tile coverage sort determinism, key load failure,total_artifacts_listed, takeoff_origin baked into both body andmanifest_hash,manifest_hashinvariance vsbuilt_at,manifest_hashchange vsflight_id). 685 lines.tests/unit/c10_provisioning/test_manifest_verifier.py(AZ-324) — 19 unit tests covering all 17 ACs (Step A–D fail-closed paths, multi-failure accumulation, airborne vs operator mode, takeoff origin range + bbox checks, untrusted key vs invalid signature, schema absolute-path rejection,MANIFEST_NOT_FOUNDreturns FAIL not raise). 721 lines.tests/unit/test_az507_inference_errors_shim.py(AZ-507) — 88 lines covering AC-2 (identity check that the shim re-exports the exact class objects fromc7_inference) + AC-3 (typed catch propagatesRuntimeError, catchesEngineBuildError).
Modified (tests)
tests/unit/c10_provisioning/test_engine_compiler.py(AZ-507 knock-on) — small adjustments where the previously-broadexcept Exceptionwas replaced by the typed envelope.
Modified (docs)
_docs/02_document/architecture.md— appended ADR-009 ("Cross- component imports go through_types/*") under the existing cross-component contract section (AZ-507 AC-5)._docs/02_document/module-layout.md— rewrote 9 components' "Imports from" lines (no morecomponents.X (Public API)); added Rule 9 codifying the AZ-270-aligned rule.
Task spec moves
_docs/02_tasks/todo/AZ-507_hygiene_module_layout_az270_alignment.md→_docs/02_tasks/done/_docs/02_tasks/todo/AZ-323_c10_manifest_builder.md→_docs/02_tasks/done/_docs/02_tasks/todo/AZ-324_c10_manifest_verifier.md→_docs/02_tasks/done/
(Total: 20 files changed, 3406 insertions, 26 deletions per
git show --stat e2bebef.)
Acceptance criteria coverage
AZ-507 (6 ACs)
| AC | Test | Status |
|---|---|---|
AC-1 module-layout.md has no components.X (Public API) imports |
Doc inspection | passing |
AC-2 _types/inference_errors.py re-exports the c7 typed error envelope |
test_az507_inference_errors_shim.py::test_ac2_identity |
passing |
| AC-3 engine_compiler narrows its catch | test_az507_inference_errors_shim.py::test_ac3_typed_catch_propagates_unknown + test_engine_compiler.py::test_ac6_* |
passing |
| AC-4 AZ-270 lint still passes | test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies |
passing |
| AC-5 Architecture doc codifies the rule | Doc inspection (ADR-009 paragraph) | passing |
| AC-6 No behavior change | Full c7_inference + c10_provisioning unit suites | passing |
AZ-323 (16 ACs)
| AC | Test | Status |
|---|---|---|
| AC-1 Happy path produces Manifest + sig + sidecars | test_manifest_builder.py::test_ac1_happy_path |
passing |
| AC-2 Determinism — same input, byte-identical Manifest (built_at redacted) | test_ac2_determinism |
passing |
| AC-3 Signature verifies against the public key | test_ac3_signature_verifies |
passing |
| AC-4 Operator-mode rejects unknown fingerprint, no files written | test_ac4_operator_mode_rejects_unknown_fp |
passing |
| AC-5 Operator-mode accepts known fingerprint | test_ac5_operator_mode_accepts_known_fp |
passing |
| AC-6 Dev-mode + dev key — no warning | test_ac6_dev_mode_dev_key_no_warning |
passing |
| AC-7 Dev-mode + operator-allowlisted key — one warning | test_ac7_dev_mode_with_operator_key_warns |
passing |
| AC-8 Tile coverage hash sort-order-deterministic | test_ac8_tile_coverage_hash_sort_deterministic |
passing |
AC-9 ManifestWriteError on key load failure, chained __cause__ |
test_ac9_key_load_failure_chains_cause |
passing |
| AC-10 Atomic write — partial Manifest impossible | test_ac10_atomic_write_no_half_manifest |
passing |
| AC-11 Manifest's own sidecar consistent | test_ac11_self_sidecar_matches_bytes |
passing |
AC-12 total_artifacts_listed equals dict-counted artifacts |
test_ac12_total_artifacts_listed |
passing |
AC-13 takeoff_origin baked into Manifest body when supplied |
test_ac13_takeoff_origin_in_body |
passing |
AC-14 takeoff_origin absent when not supplied |
test_ac14_takeoff_origin_absent_when_none |
passing |
AC-15 manifest_hash changes when only takeoff_origin differs |
test_ac15_manifest_hash_changes_with_takeoff_origin |
passing |
AC-16 manifest_hash changes when only flight_id differs |
test_ac16_manifest_hash_changes_with_flight_id |
passing |
AZ-324 (17 ACs)
| AC | Test | Status |
|---|---|---|
| AC-1 Happy path PASS with all checks green | test_manifest_verifier.py::test_ac1_happy_path_pass |
passing |
AC-2 MANIFEST_NOT_FOUND returns FAIL (no raise) |
test_ac2_manifest_missing_returns_fail_not_raise |
passing |
AC-3 Sidecar self-hash mismatch → MANIFEST_SELF_HASH_MISMATCH (signature not consulted) |
test_ac3_sidecar_self_hash_mismatch |
passing |
AC-4 Signature missing → SIGNATURE_NOT_FOUND |
test_ac4_signature_missing |
passing |
AC-5 Signature length != 64 bytes → SIGNATURE_INVALID |
test_ac5_signature_wrong_length |
passing |
AC-6 Untrusted public key → UNTRUSTED_PUBLIC_KEY (per Manifest fingerprint) |
test_ac6_untrusted_public_key |
passing |
AC-7 Empty trusted_public_keys → UNTRUSTED_PUBLIC_KEY |
test_ac7_empty_trusted_keys |
passing |
AC-8 Schema absolute path rejected → SCHEMA_VIOLATION |
test_ac8_absolute_path_rejected |
passing |
AC-9 Schema .. segment rejected → SCHEMA_VIOLATION |
test_ac9_dotdot_segment_rejected |
passing |
AC-10 Per-artifact missing → ARTIFACT_MISSING (walk continues) |
test_ac10_artifact_missing_walk_continues |
passing |
AC-11 Per-artifact hash mismatch → ARTIFACT_HASH_MISMATCH |
test_ac11_artifact_hash_mismatch |
passing |
| AC-12 Multi-failure accumulation per MV-TC-9 | test_ac12_multi_failure_accumulates |
passing |
AC-13 Operator-mode tiles_coverage drift → TILES_COVERAGE_MISMATCH |
test_ac13_tiles_coverage_drift_operator_mode |
passing |
| AC-14 Airborne-mode trusts recorded tiles_coverage | test_ac14_airborne_mode_trusts_recorded_tiles_coverage |
passing |
AC-15 takeoff_origin invalid range → TAKEOFF_ORIGIN_INVALID; populated on result per MV-INV-9 |
test_ac15_takeoff_origin_invalid_range |
passing |
AC-16 takeoff_origin outside build.bbox → TAKEOFF_ORIGIN_OUT_OF_BBOX |
test_ac16_takeoff_origin_out_of_bbox |
passing |
AC-17 Absent flight block → takeoff_origin = None, flight_id = None, no fail |
test_ac17_flight_block_absent_no_fail |
passing |
AC Test Coverage: 39 of 39 covered (6 + 16 + 17)
Code Review Verdict: PASS_WITH_WARNINGS (no per-batch review file
was written; verdict reconstructed from cumulative-review-31-33 context — F1 closed by AZ-507 in this batch; no new Critical or High findings observed against the AZ-323/324 production code by the previous session)
Auto-Fix Attempts: 0
Stuck Agents: None
Findings (self-review, reconstructed)
The previous session did not persist a per-batch code-review file at
_docs/03_implementation/reviews/batch_34_review.md. The findings
table below is reconstructed from the AZ-507 task spec (which was
itself opened to close F1 from cumulative_review_batches_31-33) and
from the spec-level constraints on AZ-323 / AZ-324:
| # | Severity | Category | Location | Note | Resolution |
|---|---|---|---|---|---|
| 1 | (closed) | Architecture | module-layout.md ↔ test_az270_compose_root.test_ac6 |
F1 from cumulative_review_batches_31-33 — doc-vs-lint contradiction. | Closed by AZ-507 (this batch). |
| 2 | Low | Maintainability | c10_provisioning/manifest_builder.py + manifest_verifier.py |
Both modules import cryptography.hazmat.primitives.asymmetric.ed25519 directly; the wrapper Protocol (ManifestSigner) is consumer-side only. Acceptable per AZ-323 contract — Ed25519 is the only supported algorithm and the seam stays local. |
Open (Low) — accepted; matches AZ-318 per-flight key pattern. |
| 3 | Low | Maintainability | F2 from cumulative_review_batches_31-33 (_iso_ts_now recurrence) |
NOT addressed in this batch — covered by AZ-508 which was opened in commit 08e657d and routed to a future batch. |
Open (Low) — tracked by AZ-508. |
The next cumulative review (batches 34–36 per Step 14.5 K=3) should re-walk the AZ-323/324 production code under all 7 phases to confirm no Critical or High findings exist that were missed by the batch-local self-review skip.
Tracker
- AZ-507, AZ-323, AZ-324 transitioned to In Progress at session
start; moved to In Testing post-commit per
protocols.md.
Test suite
tests/unit/c10_provisioning/test_manifest_builder.py— 20 passing.tests/unit/c10_provisioning/test_manifest_verifier.py— 19 passing.tests/unit/test_az507_inference_errors_shim.py— passing.- Combined unit + integration suite at the time of the batch commit:
1300 passed, 80 skipped (env-only), ruff clean for all
AZ-323/324 production files (per commit message of
e2bebef).
Next batch
Cycle 1 advances per the greenfield queue. Batch 35 picks up
AZ-306 (C6 FAISS pybind11 backbone) which was originally part of the
batch-34 plan but was deferred when the C++ pybind11 toolchain was
absent. The toolchain (cmake 4.3.2, libomp 22.1.5, pybind11 3.0.4)
has since been installed (commit acfdc8c), so AZ-306 is now
unblocked. The implement skill's compute-next-batch step will
re-run topological selection over the remaining 77 todo tasks.
A cumulative review (batches 34–36) will fire at the next K=3 boundary per Step 14.5.
Backfill provenance
This report was written on 2026-05-13 by /autodev after detecting
that batch 34's implementation, code commits, and task archiving
were complete in git (e2bebef, b88cff1) but the
batch_34_cycle1_report.md artifact was missing. The user
explicitly chose to backfill (option A) so that:
- Cumulative review (Step 14.5) can compute the changed-file set including batch 34.
- Resumability (
_docs/03_implementation/batch_*_report.mdscan) reflects the true latest batch number on disk.