AZ-270: composition root with strategy registry, tier-gated lookup, topo-order construction, all-or-nothing teardown, StrategyNotLinkedError payload. AZ-272: orjson-backed FdrRecord serialise/parse with forward-compat for unknown payload + top-level fields and canonical overrun-record shape. AZ-279: pyproj-backed WGS84/ECEF/ENU + OSM slippy-map tile math with WgsConversionError for shape/range/zoom guards. AZ-281: strict EngineFilenameSchema build/parse/matches_host with anchored regex + enum validation; round-trip identity by construction. AZ-283: dtype-preserving (fp16/fp32) single + batch L2 normaliser with zero-norm safety and descriptor_metric() source-of-truth. pyproject.toml pins pyproj>=3.6 and orjson>=3.9 (named-backend deps per the AZ-272 / AZ-279 contracts). New DTOs LatLonAlt + BoundingBox and EngineCacheKey + HostCapabilities land in _types/ to back the helper contracts. 203 unit tests pass (64 new). Review verdict: PASS_WITH_WARNINGS; findings are perf-NFR deferrals + dep amendment + minor docstring polish. Co-authored-by: Cursor <cursoragent@cursor.com>
9.0 KiB
EngineFilenameSchema Helper Module
Task: AZ-281_engine_filename_schema
Name: EngineFilenameSchema Helper
Description: Implement the shared EngineFilenameSchema helper for the self-describing .engine filename schema (D-C10-7). TensorRT engines are NOT portable across (SM, JetPack, TRT, precision) tuples; encoding the tuple in the filename makes mismatch instantly visible at takeoff load (F2). Used by C7 (writes engines on compile, reads on deserialize_engine) and C10 (compiles engines via C7 and writes them to the cache root). Stateless static-only design.
Complexity: 2 points
Dependencies: AZ-263_initial_structure
Component: shared.helpers.engine_filename_schema (cross-cutting; epic AZ-264 / E-CC-HELPERS)
Tracker: AZ-281
Epic: AZ-264 (E-CC-HELPERS)
Document Dependencies
_docs/02_document/contracts/shared_helpers/engine_filename_schema.md— frozen public interface this task produces._docs/02_document/common-helpers/06_helper_engine_filename_schema.md— design rationale (D-C10-7).
Problem
TensorRT engines are not portable. An engine compiled for SM 87 / JetPack 6.2 / TRT 10.3 / FP16 will fail to deserialize — or, worse, deserialize and silently produce wrong output — on a host with a different (sm, jp, trt, precision) tuple. Without a self-describing filename:
- C7's
deserialize_enginecannot tell whether an engine in the cache root matches the host capabilities until it tries to load it (an expensive, non-cheap, partially-side-effecting operation). - C10 has to maintain an out-of-band sidecar mapping filenames to tuples; that sidecar drifts.
- An operator who copies an engine from a different deployment by mistake gets opaque "deserialize failed" errors at takeoff instead of a clear "engine was built for sm87, host is sm72".
Outcome
- A single
helpers.engine_filename_schemamodule is the only path through which any onboard process composes or parses.enginefilenames. - The schema makes
(model_name, sm, jetpack, trt, precision)part of the filename:{model}__sm{SM}_jp{JP}_trt{TRT}_{precision}.engine. F2 takeoff load usesmatches_hostto decide which engines to deserialize and which to refuse before paying the deserialise cost. - The schema is strict — invalid model names, non-dotted version strings, unknown precisions are rejected at
buildtime; malformed filenames are rejected atparsetime. Both raiseEngineFilenameSchemaErrorwith messages that name the offending field. - Round-trip identity:
parse(build(*args)) == EngineCacheKey(*args)for any valid args. Round-trip is the contract test that catches any future format drift.
Scope
Included
EngineFilenameSchemastatic methods:build,parse,matches_host.EngineFilenameSchemaErrorexception type.- Public interface contract published at
_docs/02_document/contracts/shared_helpers/engine_filename_schema.md.
Excluded
- Schema versioning (no
schema_versionfield) — adding a new tuple dimension is a Plan-phase carryforward. - Engine compilation / compatibility resolution — C7.
- Hot-loading / lazy materialisation — C7.
- Filename collision detection across cache roots — C10's Manifest.
- The
EngineCacheKey/HostCapabilitiestypes themselves — owned by_types/manifests.py(AZ-263).
Acceptance Criteria
AC-1: Reference example builds correctly
Given ("ultravpr", 87, "6.2", "10.3", "fp16")
When build runs
Then the result is exactly "ultravpr__sm87_jp6.2_trt10.3_fp16.engine"
AC-2: Round-trip identity
Given 10 random valid tuples
When each round-trips through parse(build(*args))
Then each produces deep-equal EngineCacheKey outputs
AC-3: Host-match exact
Given a filename built for (sm=87, jp=6.2, trt=10.3) and a HostCapabilities(sm=87, jp=6.2, trt=10.3)
When matches_host runs
Then the result is True
AC-4: Host-mismatch on any tuple element returns False (no exception)
Given a filename built for (sm=87, jp=6.2, trt=10.3) and a host with sm=72
When matches_host runs
Then the result is False (NOT an exception — tuple mismatch is the expected "not a match" path)
AC-5: Precision enum strictness
Given build(..., precision="bf16")
When the call runs
Then EngineFilenameSchemaError is raised mentioning the allowed enum {fp16, int8, mixed}
AC-6: Model-name character set
Given build("UltraVPR", ...) (uppercase letters)
When the call runs
Then EngineFilenameSchemaError is raised mentioning the allowed [a-z0-9_] set
AC-7: Reserved separator collision
Given build("ultra__vpr", ...) (double underscore in model name)
When the call runs
Then EngineFilenameSchemaError is raised mentioning the reserved __ separator
AC-8: Version format strictness
Given build(..., jetpack="6.2.1", ...) (three-segment version)
When the call runs
Then EngineFilenameSchemaError is raised mentioning the dotted <major>.<minor> format
AC-9: Parse rejects malformed filenames
Given parse("not_an_engine_file.bin")
When the call runs
Then EngineFilenameSchemaError is raised
AC-10: Parse requires .engine suffix
Given parse("ultravpr__sm87_jp6.2_trt10.3_fp16") (missing .engine)
When the call runs
Then EngineFilenameSchemaError is raised mentioning the required suffix
AC-11: No upward imports (Layer 1 invariant)
Given the helper module
When a static-import check runs
Then it imports ONLY from _types, re, and stdlib — no gps_denied_onboard.components.* imports anywhere
Non-Functional Requirements
Performance
- No specific latency budget per
_docs/02_document/common-helpers/06_helper_engine_filename_schema.md(consumers are pre-flight / takeoff-load). Sanity bound: each helper call ≤ 50 µs on Tier-2.
Reliability
- Pure deterministic; same input → byte-equal output.
EngineFilenameSchemaErroris the ONLY exception type the public surface raises on validation / parse errors.
Unit Tests
| AC Ref | What to Test | Required Outcome |
|---|---|---|
| AC-1 | reference example | exact filename match |
| AC-2 | round-trip 10 random valid tuples | deep-equal EngineCacheKey outputs |
| AC-3 | matching host | True |
| AC-4 | mismatched sm |
False; no exception |
| AC-5 | precision="bf16" |
EngineFilenameSchemaError; mentions enum |
| AC-6 | uppercase model name | EngineFilenameSchemaError; mentions [a-z0-9_] |
| AC-7 | double-underscore model name | EngineFilenameSchemaError; mentions reserved separator |
| AC-8 | three-segment version | EngineFilenameSchemaError; mentions dotted format |
| AC-9 | malformed filename | EngineFilenameSchemaError |
| AC-10 | missing .engine suffix |
EngineFilenameSchemaError; mentions suffix |
| AC-11 | importlinter / grep gate | no components.* imports |
| NFR-perf | microbench each helper (10k iterations on Tier-2 fixture) | p99 ≤ 50 µs each |
Constraints
- Public surface frozen by
_docs/02_document/contracts/shared_helpers/engine_filename_schema.mdv1.0.0. - Layer 1 Foundation only.
- Static-only design satisfies
coderule.mdc. - No new dependency beyond what AZ-263 / E-BOOT pinned (only
reand stdlib are needed). - The
EngineCacheKey/HostCapabilitiestypes live in_types/manifests.py(AZ-263 responsibility).
Risks & Mitigation
Risk 1: A future format change breaks existing cache roots
- Risk: Adding a tuple dimension (e.g.,
BUILD_*flag combination) requires re-writing every existing.enginefilename; deployments with stale cache roots fail silently. - Mitigation: The contract
Versioning Rulesmandate a major-version bump for any format change. C7'sdeserialize_engineshould also reject unrecognised filename patterns rather than guess; that is C7's responsibility to wire on top of this helper'sparse.
Risk 2: matches_host returns False without explanation
- Risk: An operator copies an engine from a different deployment; takeoff-load skips it; the operator sees "no engine matches host" without knowing which tuple element mismatched.
- Mitigation: This helper is just the predicate. The error-surfacing UX is C7's / C10's responsibility — they call
parseto extract the engine's tuple AND readhost_capabilities, then format an actionable error. The contract documents the predicate's "True iff all tuple elements match" semantics so consumers can produce that message themselves.
Runtime Completeness
- Named capability: self-describing engine filename schema (D-C10-7 /
06_helper_engine_filename_schema.md). - Production code that must exist: real format builder + parser + host-match predicate; real strict validation for all five tuple elements.
- Allowed external stubs: none — pure string parsing on stdlib.
- Unacceptable substitutes:
f"{model}_{sm}_{jp}_{trt}_{precision}.engine"(single underscore separators ambiguatemodelfromsm); silently truncatingjetpack="6.2.1"to6.2; matching host with substring instead of exact-equality.
Contract
This task produces the contract at _docs/02_document/contracts/shared_helpers/engine_filename_schema.md.
Consumers MUST read that file — not this task spec — to discover the interface.