# 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_engine` cannot 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_schema` module is the only path through which any onboard process composes or parses `.engine` filenames.
- 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 uses `matches_host` to 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 `build` time; malformed filenames are rejected at `parse` time. Both raise `EngineFilenameSchemaError` with 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

- `EngineFilenameSchema` static methods: `build`, `parse`, `matches_host`.
- `EngineFilenameSchemaError` exception type.
- Public interface contract published at `_docs/02_document/contracts/shared_helpers/engine_filename_schema.md`.

### Excluded

- Schema versioning (no `schema_version` field) — 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` / `HostCapabilities` types 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.
- `EngineFilenameSchemaError` is 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.md` v1.0.0.
- Layer 1 Foundation only.
- Static-only design satisfies `coderule.mdc`.
- No new dependency beyond what AZ-263 / E-BOOT pinned (only `re` and stdlib are needed).
- The `EngineCacheKey` / `HostCapabilities` types 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 `.engine` filename; deployments with stale cache roots fail silently.
- *Mitigation*: The contract `Versioning Rules` mandate a major-version bump for any format change. C7's `deserialize_engine` should also reject unrecognised filename patterns rather than guess; that is C7's responsibility to wire on top of this helper's `parse`.

**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 `parse` to extract the engine's tuple AND read `host_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 ambiguate `model` from `sm`); silently truncating `jetpack="6.2.1"` to `6.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.
