` | owner-controlled namespace distinct from `submark.*` | owner-controlled scheme distinct from `submark-vls*` |
+
+Modified builds may retain attribution required by license but must not present themselves as the official product, claim the official namespace, overwrite official associations, consume the official update feed, or use the official signing identity. Importing an official `.vlsp` project into a modified build must not mutate it without explicit user action and a recoverable copy.
+
+## Activation and Association Rules
+
+Packaged builds declare file and protocol activation in the package manifest. Unpackaged development builds may register per-user activation only when explicitly enabled and must unregister cleanly. Installation must not seize unrelated defaults or register broad media extensions.
+
+Protocol activation accepts only local, versioned commands with a strict allowlist and bounded payloads. It rejects credentials, arbitrary file execution, remote URLs, traversal, and unsupported commands. Opening through either activation path follows the same validation, recovery, and consent rules as opening from the UI.
+
+## Packaging and Signing Gates
+
+The production package candidate is not release-authoritative until all applicable evidence exists:
+
+1. Package name availability or reservation is confirmed for the intended distribution channel.
+2. The production publisher subject and signing custody are approved without committing private key material.
+3. Package `Name`, publisher, and application `Id` match across relevant manifests.
+4. The derived Package Family Name and application user model identity are captured from a signed package.
+5. Side-by-side production, development, and test installation proves identity and data isolation.
+6. File and protocol activation positive and negative cases pass on supported Windows versions.
+7. Install, upgrade, rollback, and uninstall preserve user projects and remove owned registrations.
+
+Self-signed development certificates are permitted only for local development evidence. They do not establish production publisher identity.
+
+## Migration and Collision Handling
+
+The first released project schema owns `.vlsp`. A future extension or namespace change requires a new decision, explicit import/export behavior, and compatibility evidence. Silent reassignment is prohibited.
+
+If `.vlsp`, `submark-vls`, or the package candidate conflicts with an existing registered identity, implementation stops at the affected boundary. The product must not overwrite the conflicting owner. Resolution requires a revised exact candidate and approval before release artifacts are produced.
+
+## Alternatives Rejected
+
+- `.vls` is shorter but has a higher collision and ambiguity risk.
+- `.vloc` describes localization but not a durable project container.
+- `fixture.product.video` is reserved for conformance fixtures and is not a public identity.
+- Freezing a fabricated Package Family Name is invalid because its publisher-derived component is not yet authoritative.
+- Sharing production identity with development, test, or forks creates update, data, activation, and support ambiguity.
+
+## Review and Acceptance Criteria
+
+Acceptance required an exact-candidate review covering Video architecture, Shared namespace compatibility, security/privacy and licensing, Windows/package compatibility, release operations, and negative paths. The Owner-authorized consolidated review passed on 2026-08-01 for candidate SHA-256 `AA5455A1FA2260551D9D63CDE9BA82DB621F1DA9FA4BE887833F4DC0656F14F3`. It is not represented as an independent review.
+
+Implementation may consume these identities only after this decision is Accepted and the package/signing gates applicable to the implementation stage are represented in executable or inspectable evidence.
+
+## References
+
+- [Microsoft: Package identity overview](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/package-identity-overview)
+- [Microsoft: Windows App SDK desktop activation](https://learn.microsoft.com/en-us/windows/apps/develop/launch/activate-an-app)
+- [Microsoft: Handle file activation](https://learn.microsoft.com/en-us/windows/apps/develop/launch/handle-file-activation)
+- [Microsoft: Package with external location](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
diff --git a/decisions/VID-DEC-003-initial-support-profiles.md b/decisions/VID-DEC-003-initial-support-profiles.md
new file mode 100644
index 0000000..30e3830
--- /dev/null
+++ b/decisions/VID-DEC-003-initial-support-profiles.md
@@ -0,0 +1,208 @@
+# VID-DEC-003 - Initial Support Profiles
+
+| Field | Value |
+| --- | --- |
+| Decision ID | `VID-DEC-003` |
+| Status | Accepted |
+| Revision | `1.0` |
+| Decision owner | Product Lead: Video (`primary`) |
+| Required reviewers | Domain Architect: Video; Suite Architect; Security Lead; QA and Compatibility Lead; Release Engineering Lead |
+| Advisory reviewers | Media and UX Researcher; Research Triage |
+| Authority | `OWNER-AUTH-V2` |
+| Blocker | `Phase:V1`; `Phase:V2`; `Phase:V7A` |
+| Depends on | `VID-DEC-001@1.0` Accepted |
+| Immediate consumer | `VID-IMPL-P00-002B` |
+
+## Decision Question
+
+Which exact initial media, timed-text, encoder, AI-provider, and hardware profiles may Video advertise or execute without turning detection into support, importing later scope, redistributing unapproved payloads, or weakening the hostile-ingest boundary?
+
+## Decision
+
+Revision `1.0` selects one internal/private Phase 00 **probe qualification profile only**. It promotes no playback, import-acceptance, timed-text interchange, encoder, AI-provider, accelerator, hardware, mux, burn-in, or delivery profile.
+
+| Profile family | Decision in this revision |
+| --- | --- |
+| Media probe qualification | Select `VID-PROBE-P00-001` below |
+| Playback/decoder/renderer | None promoted |
+| Timed-text import/export | None promoted; UTF-8 SRT proof remains separately gated |
+| Encoder/mux/burn-in | None promoted |
+| AI/provider/model | None promoted |
+| GPU/NPU/hardware backend | None promoted; CPU-only functional baseline remains |
+
+No capability may inherit support from the selected probe component. Probe, play, import, edit, waveform, proxy, subtitle extraction, export, mux, encode, and validated delivery remain independent claims.
+
+## Selected Profile: VID-PROBE-P00-001
+
+### Component and Acquisition
+
+- Component role: standalone `ffprobe.exe` process used only to produce a bounded immutable observation.
+- Upstream source release: FFmpeg `8.1.2` "Hoare", released 2026-06-17; FFmpeg publishes signed source, not official Windows binaries.
+- Qualification build: third-party Gyan full build already installed by the owner.
+- Exact executable path: `C:\ProgramData\chocolatey\lib\ffmpeg-full\tools\ffmpeg\bin\ffprobe.exe`.
+- Exact executable SHA-256: `9DF3B0B5275E830961DF6D94E1F7A71121A7ABD5FF708E9FEC8A0B6084A55015`.
+- Observed version: `8.1.2-full_build-www.gyan.dev`.
+- Configuration classification: includes `--enable-gpl --enable-version3`; treat as a GPLv3 full build with a broad protocol/codec surface.
+- Acquisition boundary: owner-installed, internal qualification only. Its dependency classification is `External only` under the Video third-party dependency policy. The executable MUST NOT be copied into this repository, bundled, uploaded, published, redistributed, or represented as an LGPL build.
+- The Chocolatey shim at `C:\ProgramData\chocolatey\bin\ffprobe.exe` is not the registered executable and MUST NOT be invoked or hashed as the component identity.
+
+Any path, hash, version, or configuration mismatch fails closed as `VID-INGEST-IDENTITY-MISMATCH`. Automatic substitution, PATH lookup, silent update acceptance, and "latest" resolution are prohibited.
+
+### Operation Boundary
+
+- Operation ID: `submark.video-localization.media.probe`.
+- Operation version: `1.0.0-p00`.
+- Implementation profile ID: `VID-PROBE-P00-001`.
+- Authority class: immutable candidate observation only.
+- Input: one caller-leased, preflighted, immutable local regular file with full SHA-256 and byte length from the accepted `VID-IMPL-P00-002A` boundary. The adapter MUST use an attempt-scoped immutable staged copy held under a sharing policy that denies write, delete, rename, replacement, and relink for the full child lifetime. It MUST verify full SHA-256, byte length, and file identity immediately before launch and after child exit. Any mismatch fails as `VID-INGEST-IDENTITY-MISMATCH`; no observation may be published.
+- Output: one schema-validated normalized observation or one structured terminal disposition.
+- The worker and child process receive no project-store, Shared-store, credential, user-profile, unrelated-file, or publication authority.
+- A product-owned command may later bind a validated observation to a stable Video asset identity; execution success never performs that binding.
+
+### Fixed Invocation Policy
+
+The reviewed adapter launches the exact absolute executable directly with an argv array. It MUST NOT use a shell, command string, user-controlled option, URL, playlist, environment expansion, or PATH lookup.
+
+The profile requires:
+
+- `-v error`
+- `-hide_banner`
+- `-protocol_whitelist file`
+- `-format_whitelist matroska` for the selected synthetic qualification fixture
+- `-show_format`
+- `-show_streams`
+- `-show_chapters`
+- `-of json`
+- exactly one final preflighted local input path
+
+The adapter MUST apply an explicit output-field/schema allowlist after parsing. Unknown fields are ignored only when declared optional; invalid, deeply nested, duplicate, oversized, or schema-incompatible output fails safely. Packet/frame enumeration, payload dumps, arbitrary command options, and attachment extraction are outside this operation.
+
+### Qualification Fixture Profile
+
+`VID-PROBE-FIXTURE-P00-001` is a locally generated, synthetic, rights-safe Matroska fixture with:
+
+- one FFV1 video stream;
+- one 48 kHz stereo PCM signed-16 audio stream;
+- one UTF-8 SubRip subtitle stream;
+- deterministic duration and stream/time-base expectations;
+- no network reference, external playlist, DRM, private media, licensed font payload, or customer data.
+
+This fixture qualifies only structural probe observation. FFV1, PCM, Matroska, and SubRip detection does not promote playback, subtitle import, rendering, extraction, round-trip, or delivery support.
+
+### Resource and Containment Profile
+
+- Windows 11 x64 internal qualification host.
+- One Rust media worker and one declared `ffprobe.exe` child.
+- Wall-clock and child CPU ceiling: 60 seconds. A worker-side monotonic watchdog enforces wall time independently of Job Object CPU accounting and terminates the complete job on expiry.
+- Resident-memory ceiling: 1 GiB.
+- Control envelope and retained normalized JSON ceiling: 1 MiB before decode.
+- Stream count ceiling: 256.
+- Stdout and stderr are captured separately, bounded, sanitized, and never interpreted as command text.
+- Job Object enforcement owns process count, kill-on-close, CPU, and memory limits.
+- Job Objects do not deny network. A separate OS-level deny control is mandatory and must produce evidence before a successful conformance claim.
+- `-protocol_whitelist file` is defense in depth, not a replacement for OS-level network denial.
+- Child handles and environment variables are allowlisted; write access is limited to an attempt-scoped staging directory when output is required.
+
+If OS-level network denial, resource enforcement, executable identity verification, input lease, or output bounding cannot be established, the operation MUST fail closed before invoking `ffprobe`.
+
+## Structured Outcomes
+
+The adapter maps bounded process and validation results to stable Video dispositions:
+
+- `VID-INGEST-UNSUPPORTED`
+- `VID-INGEST-MALFORMED`
+- `VID-INGEST-LIMIT`
+- `VID-INGEST-PROTECTED`
+- `VID-INGEST-QUARANTINED`
+- `VID-INGEST-CANCELLED`
+- `VID-INGEST-WORKER-FAILED`
+- `VID-INGEST-IDENTITY-MISMATCH`
+- `VID-INGEST-REVIEW`
+
+Raw exit codes and untrusted stderr are diagnostic evidence, not public disposition identities. Deterministic input/schema/limit failures do not retry automatically. Cancellation, timeout, crash, malformed JSON, excess output, source mutation, stale attempt, or component mismatch cannot publish a complete observation.
+
+## Shared Admission Gate for VID-IMPL-P00-002B
+
+This decision selects a component and containment profile; it does not by itself publish a complete `SUI-SPEC-003` operation contract or authorize Shared job admission. `VID-IMPL-P00-002B` may build and test the adapter locally, but Shared admission and any `Implemented` compatibility claim remain fail-closed until one implementation-freeze bundle records exact canonical digests for all rows below.
+
+| Required binding | Frozen Phase 00 requirement |
+| --- | --- |
+| Operation descriptor | Bind `submark.video-localization.media.probe@1.0.0-p00` to exact input, output, parameter, error, resource, policy, and output-contract schema IDs, versions, and canonical SHA-256 digests. |
+| Determinism and cache | Deterministic only for the exact input bytes, canonical parameters, executable/configuration identity, adapter build, policy, and resource profile; cache is disabled in Phase 00. |
+| Capabilities | Cancellation and deadline are required; progress, checkpoint, pause/resume, degraded output, partial success, acceleration, network, and automatic fallback are unsupported. |
+| Input manifest | Record source/content SHA-256, byte length, staged-copy identity, lease ID, lease epoch, fencing token, access scope, expiry, manifest digest, and original caller-owned logical source reference. A mutable path is never identity. |
+| Attempt identity | Record Job ID, Attempt ID, Dispatch ID, Worker Instance ID, stable Implementation ID, operation/profile IDs, executable/configuration hashes, selected compatibility set, and resource/policy digests. Worker restart creates a new Worker Instance ID and stale lease or fencing values fail closed. |
+| Worker handshake | `WorkerHello` advertises exact worker/control protocol ranges, operation and schema versions, implementation and executable identities, Windows x64 isolation profile, limits, and selected local transport. Incompatible combinations are rejected before dispatch. |
+| Transport | Input media is out-of-band through the leased staged local file. Normalized observation is the required `probe_observation` output and may be in-band only after the 1 MiB pre-decode envelope check. Raw stdout/stderr are bounded transient diagnostics and are not published artifacts. |
+| Artifact and publication | A complete normalized observation is staged application-private with type/schema, content hash/size, completeness, output port, producing identities, input/policy/resource digests, Publication ID, and fencing token. Atomic publication occurs only after schema, integrity, lease, source, and epoch validation. Failed, partial, cancelled, timed-out, stale, or mismatched output is quarantined or deleted under bounded retention and never satisfies success. Product acceptance remains a later Video command. |
+| Error contract | Every terminal failure emits a versioned Shared `StructuredError` with complete `ErrorRef`, operation stage, applicable job/attempt/dispatch/worker/publication references, safe bounded details and causes, plus exactly one Video disposition below. Raw stderr and exit codes remain restricted evidence. |
+
+The operation bundle MUST map all nine Video dispositions before admission. `VID-INGEST-CANCELLED` maps to Shared `Cancelled`; watchdog expiry maps to Shared `TimedOut` with `VID-INGEST-LIMIT`; every other disposition maps to Shared `Failed` unless a product-owned review workflow records `VID-INGEST-REVIEW` outside worker execution. `UNSUPPORTED`, `MALFORMED`, `LIMIT`, `PROTECTED`, `QUARANTINED`, and `IDENTITY-MISMATCH` are non-retryable for unchanged input/profile. `WORKER-FAILED` may retry only when the complete Shared retry policy classifies the cause as transient, creates a new Attempt and Dispatch, preserves the Job, and remains within cumulative limits. No other automatic retry is permitted.
+
+## Required Evidence Before Implemented or Verified
+
+1. Exact executable path, SHA-256, verbatim version and configuration output plus their hashes, acquisition record, `External only` classification, license classification, and no-redistribution statement.
+2. Fixed argv adapter tests proving no shell, PATH lookup, URL, user option, or implicit protocol path.
+3. OS-level network-denial evidence independent of Job Object and protocol allowlisting.
+4. Job Object evidence for process count, kill-on-close, CPU, memory, timeout, cancellation, and child cleanup.
+5. Positive synthetic fixture evidence and negative malformed, unsupported, protocol-attempt, oversized-output, stream-limit, timeout, cancellation, crash, executable-mismatch, source-mutation, and invalid-JSON evidence.
+6. Output schema, field limits, rational parsing, provenance, component identity, input fingerprint, operation identity, and observation-only authority checks.
+7. Confirmation that no binary, private media, licensed payload, or unapproved third-party artifact entered the repository or GitHub.
+8. Exact-hash operation-descriptor bundle satisfying the Shared admission gate above, including schema, transport, identity, error, artifact, publication, lease-race, stale-worker, and restart evidence.
+
+`Implemented` means the bounded operation exists. `Verified` additionally requires the exact profile and negative evidence above. Neither status promotes a media support claim.
+
+## Alternatives Rejected for This Revision
+
+| Alternative | Disposition | Reason |
+| --- | --- | --- |
+| Project-bundled current Gyan full build | Rejected | GPLv3 full build, broad attack surface, and no redistribution authority |
+| Project-managed LGPL Windows build | Deferred | Requires a separately acquired/minimized build, exact configuration/hash, update and notice process, and legal/distribution review |
+| Windows Media Foundation primary probe | Deferred | Removes third-party acquisition but not hostile-parser containment; behavior/profile and fixture evidence remain unresolved |
+| Custom Rust container parser | Rejected | Creates new parser security and compatibility burden without reducing Phase 00 risk |
+| PATH-resolved or unpinned ffprobe | Rejected | Non-reproducible component identity and binary-planting/update risk |
+
+## Consequences
+
+Positive:
+
+- `VID-IMPL-P00-002B` can implement a real, exact, no-redistribution probe qualification path.
+- Probe support remains separate from playback and delivery claims.
+- The local executable is immutable by path/hash/configuration and can be rejected after update rather than silently changing behavior.
+
+Costs and risks:
+
+- The selected build is intentionally not shippable.
+- The full GPL build has a larger compiled surface than a future minimized LGPL probe build.
+- OS-level network denial and Windows child containment are implementation work, not properties of `ffprobe` or Job Objects.
+- Updating the local component requires a new reviewed profile revision and evidence.
+- Arm's-length invocation of the GPLv3 build from the source-available application remains an unresolved legal-interpretation risk while `SUI-DEC-001` is open. Any incompatible resolution retires this profile and requires a separately reviewed non-GPL/minimized build or Media Foundation profile.
+
+## Release, CI, and Lifecycle Boundary
+
+- This profile is intentionally machine-local and is not eligible for GitHub Actions, contributor-default setup, release packaging, release artifacts, support claims, or automated download. CI MUST skip it unless a separately approved private runner already holds the exact owner-managed binary and records the same identity evidence.
+- No workflow may fetch, cache, upload, or redistribute the selected executable under this decision. A portable/team/release profile requires a new reviewed acquisition, license, notice, hash, containment, and distribution decision.
+- Security advisories, component/configuration drift, owner removal, incompatible `SUI-DEC-001` resolution, or a required production/release path retire this revision immediately and trigger a new candidate review. Silent rotation is prohibited.
+- Release notes may cite this decision only as internal Phase 00 qualification evidence and MUST NOT imply that a shipped artifact contains or supports the selected component.
+
+## Review and Acceptance Rule
+
+All required reviewers must review the same candidate SHA-256. Owner-authorized consolidated review is permitted but must be labeled non-independent. Independent review, when performed, remains separately identified. Acceptance of this decision does not resolve `SUI-DEC-001`, grant redistribution rights, or approve later support-profile families.
+
+## References
+
+- [Video implementation stack](VID-DEC-001-implementation-stack.md)
+- [Foundation PRD](../specifications/VID-SPEC-001-foundation-prd.md)
+- [Domain schema](../specifications/VID-SPEC-002-domain-schema.md)
+- [Hostile media-ingest threat model](../specifications/VID-SPEC-004-media-ingest-threat-model.md)
+- [Foundation verification](../specifications/VID-SPEC-015-foundation-verification.md)
+- [Shared worker protocol](../../_shared/specifications/SUI-SPEC-003-job-worker-artifact-protocol.md)
+- [Shared threat model](../../_shared/specifications/SUI-SPEC-008-shared-threat-model.md)
+- [Accepted local preflight and fingerprint review](../../.agents/reviews/VID-IMPL-P00-002A/primary-acceptance.md)
+- [Video source and governance](../Plan/sections/01_SOURCE_AND_GOVERNANCE.md)
+- [Shared source-license decision](../../_shared/decisions/SUI-DEC-001-source-license.md)
+- [FFmpeg download and signed releases](https://ffmpeg.org/download.html)
+- [FFprobe documentation](https://ffmpeg.org/ffprobe.html)
+- [FFmpeg protocols](https://ffmpeg.org/ffmpeg-protocols.html)
+- [FFmpeg legal](https://ffmpeg.org/legal.html)
+- [FFmpeg security](https://ffmpeg.org/security.html)
diff --git a/decisions/VID-DEC-004-visual-direction.md b/decisions/VID-DEC-004-visual-direction.md
new file mode 100644
index 0000000..f4de64d
--- /dev/null
+++ b/decisions/VID-DEC-004-visual-direction.md
@@ -0,0 +1,108 @@
+# VID-DEC-004 - Video Visual Direction
+
+| Field | Value |
+| --- | --- |
+| Decision ID | `VID-DEC-004` |
+| Status | Accepted |
+| Revision | `1.0` |
+| Decision owner | Product Lead: Video (`primary`) |
+| Required reviewers | UX Design Lead; Accessibility Reviewer; Thai Language Quality Reviewer; QA and Compatibility Lead |
+| Authority | `OWNER-AUTH-V0`; Owner Resolution Bundle V1; owner-authorized consolidated self-review |
+| Blocker | `P0:V0` |
+| Depends on | `SUI-DEC-007@1.0` Accepted; `SUI-SPEC-007@1.0` Frozen |
+| Informs | `VID-SPEC-014`; `VID-SPEC-015`; Phase 00 shell and workspace implementation |
+
+## Decision Question
+
+Which visual direction should make Video Localization feel like a premium creative workstation while preserving precision, accessibility, international text support, media performance, and equal first-class Light and Dark themes?
+
+## Direction Studies
+
+| Study | Character | Disposition |
+| --- | --- | --- |
+| Cinematic Ledger | Warm graphite, paper-white working surfaces, restrained amber action cues, technical rulers, compact editorial geometry | Selected |
+| Broadcast Blueprint | Cool blue control-room surfaces and broadcast-monitor language | Rejected because it reads as generic operations software and makes collaboration/status blue harder to distinguish |
+| Studio Glass | Translucent layers, luminous gradients, floating controls, and soft blur | Rejected because blur, decoration, contrast variability, and composition cost conflict with precision and media performance |
+
+## Decision
+
+Adopt **Cinematic Ledger** as the Video product visual direction.
+
+### Visual Character
+
+- Present the application as a calm editing instrument, not an enterprise dashboard, gaming interface, or decorative cinema skin.
+- Use warm-neutral graphite structure, paper-white or ink-tinted working surfaces, restrained hairline boundaries, and compact editorial spacing.
+- Use an amber action family for the playhead, primary action, selected timing affordances, and active edit focus. Amber MUST NOT carry warning or error meaning.
+- Keep success, warning, error, collaboration presence, review state, and track identity on distinct semantic families. State MUST remain understandable without color.
+- Use restrained corner radii and elevation. Continuous blur, heavy glass, neon glow, decorative gradients, and ornamental animation are prohibited.
+- The media stage remains a calibrated neutral-dark surface in both Light and Dark themes so image judgment does not change with application chrome.
+
+### Theme and Token Direction
+
+- System is the default theme and follows live operating-system changes. Light and Dark are equal first-class modes.
+- Theme changes MUST preserve focus, selection, playback, timeline position, panel layout, open dialogs, and unsaved edit state.
+- Implement the Shared primitive-to-semantic-to-component token hierarchy. Product tokens MAY specialize timeline, waveform, subtitle, review, collaboration, media-stage, and delivery semantics but MUST NOT bypass Shared accessibility contracts.
+- Exact production color values, contrast measurements, high-contrast mappings, component tokens, and rendered evidence belong to `VID-SPEC-014` and `SUI-SPEC-012@Exit:S1`; this decision fixes direction and semantic separation, not unmeasured token claims.
+
+### Typography and International Text
+
+- Use a licensed, locally packaged humanist sans family with broad script coverage for UI text. Do not freeze a font payload until source, license, shaping, and redistribution evidence is approved.
+- Use tabular monospaced numerals for timecode, frame, sample, duration, and measurement readouts without forcing monospaced text on prose.
+- Preview surfaces render the actual subtitle shaping and fallback chain rather than substituting UI typography.
+- Thai, CJK, and RTL strings MUST survive compact controls without clipping, reordered meaning, broken combining marks, or hidden focus indicators.
+
+### Layout and Density
+
+- Support Compact and Comfortable density through tokens; do not maintain divergent component implementations.
+- Build the five workspaces as purposeful arrangements: Edit, Timing, Translation, Review, and Delivery.
+- Prefer stable pane geometry, explicit hierarchy, technical rulers, and aligned numeric columns. Avoid card grids where a timeline, inspector, table, or structured list communicates the work more directly.
+- The timeline and transport are the primary precision surfaces. Review and collaboration information remains visible but must not compete with playback, cue text, or timing controls.
+
+### Motion and Performance
+
+- Normal chrome transitions target 120-180 ms with restrained easing.
+- Playback, scrubbing, dragging, trimming, waveform navigation, keyboard timing, and cue stepping MUST never wait for decorative motion.
+- Reduced-motion mode removes nonessential transitions without hiding state changes.
+- Visual effects MUST degrade before timing interaction or media presentation falls below the baseline performance contract.
+
+### Accessibility
+
+- Meet the Shared contrast, focus, keyboard, target-size, high-contrast, reduced-motion, and assistive-technology contracts.
+- Pair color with shape, iconography, text, pattern, or position for every status and timeline distinction.
+- Focus treatment must remain visible against both application chrome and the neutral-dark media stage.
+- Component states cover default, hover, pressed, selected, focused, disabled, busy, empty, warning, error, success, and conflict where semantically applicable.
+
+## Consequences
+
+Positive consequences:
+
+- The product gains a recognizable creative-workstation identity without compromising technical density.
+- Light and Dark modes share one semantic system while the media stage remains perceptually stable.
+- Amber action cues distinguish Video from generic blue enterprise tooling without conflating errors or warnings.
+- The direction scales from the Phase 00 shell to timing, translation, review, and delivery workspaces.
+
+Costs and constraints:
+
+- Amber contrast and status separation require measured token work rather than direct palette use.
+- Broad-script typography requires licensed payload and shaping evidence before a concrete family is frozen.
+- Compact density increases the need for Thai/CJK/RTL clipping tests and keyboard/focus evidence.
+- Restrained effects place more responsibility on spacing, typography, borders, and state clarity.
+
+## Acceptance Record
+
+| Role | Status | Basis |
+| --- | --- | --- |
+| Product Lead: Video | Accepted | Selected Cinematic Ledger and bounded implementation ownership |
+| UX Design Lead | Accepted by owner-authorized consolidated review | Direction, workspace hierarchy, density, and component-state review |
+| Accessibility Reviewer | Accepted by owner-authorized consolidated review | Contrast ownership, non-color state, focus, motion, and high-contrast review |
+| Thai Language Quality Reviewer | Accepted by owner-authorized consolidated review | Thai/CJK/RTL typography and clipping obligations |
+| QA and Compatibility Lead | Accepted by owner-authorized consolidated review | Theme-state preservation and evidence boundaries |
+
+This acceptance is a consolidated self-review authorized by the owner. It is not independent review and does not substitute for rendered conformance evidence.
+
+## References
+
+- [Video UI/UX and Design System](../Plan/sections/03A_UI_UX_AND_DESIGN_SYSTEM.md)
+- [Shared UI Responsibility Decision](../../_shared/decisions/SUI-DEC-007-ui-responsibility-boundary.md)
+- [Shared UI Specification](../../_shared/specifications/SUI-SPEC-007-ui-i18n-accessibility.md)
+- [Phase 00](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
diff --git a/decisions/evidence/VID-DEC-001/VID-APPROVAL-DEC-001-transaction.json b/decisions/evidence/VID-DEC-001/VID-APPROVAL-DEC-001-transaction.json
new file mode 100644
index 0000000..10756a5
--- /dev/null
+++ b/decisions/evidence/VID-DEC-001/VID-APPROVAL-DEC-001-transaction.json
@@ -0,0 +1,97 @@
+{
+ "transaction_id": "VID-APPROVAL-DEC-001",
+ "transaction_revision": "0.1.0",
+ "status": "Executed",
+ "candidate": {
+ "decision_id": "VID-DEC-001",
+ "revision": "0.1",
+ "sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C"
+ },
+ "spike": {
+ "evidence_id": "VID-DEC-001-SPIKE",
+ "path": "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json",
+ "sha256": "5476BA6107D677148E784AFC6CE9F93BFF193799183A62C07CBF260ECF096BC7",
+ "status": "Pass"
+ },
+ "decisions": [
+ {
+ "role": "Product Lead: Video",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ },
+ {
+ "role": "Domain Architect: Video",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ },
+ {
+ "role": "Suite Architect",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ },
+ {
+ "role": "Security Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ },
+ {
+ "role": "QA and Compatibility Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ },
+ {
+ "role": "Release Engineering Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C",
+ "timestamp": "2026-08-02T01:27:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json"
+ ]
+ }
+ ],
+ "summary": {
+ "required": 6,
+ "approved": 6,
+ "pending": 0
+ },
+ "automatic_approval_allowed": false,
+ "independent_review": false
+}
diff --git a/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json b/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json
new file mode 100644
index 0000000..ff4b465
--- /dev/null
+++ b/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json
@@ -0,0 +1,40 @@
+{
+ "evidence_id": "VID-DEC-001-SPIKE",
+ "revision": "0.1.0",
+ "executed_at": "2026-08-02T01:22:00+07:00",
+ "status": "Pass",
+ "repository_modified_by_spike": false,
+ "environment": {
+ "os": "Microsoft Windows NT 10.0.26220.0",
+ "architecture": "AMD64",
+ "dotnet_sdks": [
+ "10.0.301 [C:\\Program Files\\dotnet\\sdk]",
+ "10.0.302 [C:\\Program Files\\dotnet\\sdk]"
+ ],
+ "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "cargo": "cargo 1.97.1 (c980f4866 2026-06-30)",
+ "protoc": "libprotoc 35.1",
+ "sqlite3": "3.53.4 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc (64-bit)"
+ },
+ "checks": {
+ "csharp_host_rust_worker_separate_process": true,
+ "named_pipe_round_trip": true,
+ "rust_release_build": true,
+ "ffi_used": false,
+ "protobuf_descriptor_bytes": 244,
+ "sqlite_wal_mode": true,
+ "sqlite_integrity": true,
+ "sqlite_committed_revisions": 1
+ },
+ "boundary": {
+ "spike_control_protocol": "versioned bounded line protocol",
+ "production_contract": "Protobuf over local named pipes",
+ "worker_project_store_access": "not granted",
+ "large_payloads": "out of control channel"
+ },
+ "limitations": [
+ "WinUI shell not compiled by this boundary spike",
+ "Production Protobuf framing/authentication remain implementation evidence",
+ "MSIX packaging remains separate release evidence"
+ ]
+}
diff --git a/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md b/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md
new file mode 100644
index 0000000..ee60a8d
--- /dev/null
+++ b/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md
@@ -0,0 +1,14 @@
+# VID-DEC-001 Consolidated Review
+
+- Candidate: `VID-DEC-001@0.1`
+- Candidate SHA-256: `86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C`
+- Architecture spike SHA-256: `5476BA6107D677148E784AFC6CE9F93BFF193799183A62C07CBF260ECF096BC7`
+- Candidate checks: **Pass 11/11**
+- Reviewed at: `2026-08-02T01:27:00+07:00`
+- Authority: Owner Resolution Bundle V1 and Correction Bundle V1.1
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Accept**
+
+The accepted stack is C# on .NET 10 LTS with stable WinUI 3/Windows App SDK for the desktop host, isolated Rust workers where justified, Protobuf over local named pipes for the production control boundary, SQLite behind one authoritative writer, and MSIX as the package target. The spike proved a C# host and Rust worker in separate processes with a named-pipe round trip, Rust release build, Protobuf descriptor generation, and SQLite WAL/integrity behavior without FFI or project-store access from the worker.
+
+The spike does not prove the WinUI shell, production framing/authentication, native media behavior, crash recovery, or MSIX packaging. Those remain implementation and release evidence obligations. No blocking finding remains.
\ No newline at end of file
diff --git a/decisions/evidence/VID-DEC-001/manifest.json b/decisions/evidence/VID-DEC-001/manifest.json
new file mode 100644
index 0000000..c2c2ce9
--- /dev/null
+++ b/decisions/evidence/VID-DEC-001/manifest.json
@@ -0,0 +1,40 @@
+{
+ "evidence_id": "VID-DEC-001-EVID",
+ "status": "accepted",
+ "approved_candidate": {
+ "decision_id": "VID-DEC-001",
+ "revision": "0.1",
+ "sha256": "86CBBB35595A1E67DE9D626D146093F97140200C74C5B9982A78289FBDC3236C"
+ },
+ "accepted_artifact": {
+ "decision_id": "VID-DEC-001",
+ "revision": "1.0",
+ "path": "Video Localization/decisions/VID-DEC-001-implementation-stack.md",
+ "sha256": "6CF55D64FF8BF4F4B4A773F075A73D767F1C831DD137C1D546BC562DA7AD5037"
+ },
+ "architecture_spike": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-architecture-spike.json",
+ "sha256": "5476BA6107D677148E784AFC6CE9F93BFF193799183A62C07CBF260ECF096BC7",
+ "status": "Pass"
+ },
+ "review": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-001/VID-DEC-001-consolidated-review.md",
+ "sha256": "2BC1DFF7ABE89BC18F9057D056DABDE86BB258DA561D7455FFFC4F4D51360AB4",
+ "checks": "11/11",
+ "independent_review": false
+ },
+ "approval_transaction": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-001/VID-APPROVAL-DEC-001-transaction.json",
+ "sha256": "CB2CDF2FC2A1259BF9F9A1BDBB2613FB6F87C90009F072D44C8DD7B213FBDA49",
+ "status": "Executed",
+ "approved": "6/6"
+ },
+ "limitations": [
+ "WinUI-shell",
+ "production-framing-and-authentication",
+ "native-media-behavior",
+ "crash-recovery",
+ "MSIX-packaging"
+ ],
+ "accepted_at": "2026-08-02T01:27:00+07:00"
+}
diff --git a/decisions/evidence/VID-DEC-002/VID-APPROVAL-002-transaction.json b/decisions/evidence/VID-DEC-002/VID-APPROVAL-002-transaction.json
new file mode 100644
index 0000000..e35fca2
--- /dev/null
+++ b/decisions/evidence/VID-DEC-002/VID-APPROVAL-002-transaction.json
@@ -0,0 +1,16 @@
+{
+ "schema_version": "1.0",
+ "decision_id": "VID-DEC-002",
+ "candidate_revision": "0.1",
+ "candidate_sha256": "AA5455A1FA2260551D9D63CDE9BA82DB621F1DA9FA4BE887833F4DC0656F14F3",
+ "authority": "OWNER-AUTH-V0#primary",
+ "review_type": "owner-authorized-consolidated-self-review",
+ "independent_review": false,
+ "decision": "Accepted",
+ "decided_at": "2026-08-01T23:41:00+07:00",
+ "conditions": [
+ "Verify distribution-channel package name availability before release identity is frozen.",
+ "Record production publisher custody and derived Package Family Name/AUMID from signed evidence.",
+ "Prove production, development, test, and modified-build isolation before release."
+ ]
+}
diff --git a/decisions/evidence/VID-DEC-002/VID-DEC-002-consolidated-review.md b/decisions/evidence/VID-DEC-002/VID-DEC-002-consolidated-review.md
new file mode 100644
index 0000000..b431bd1
--- /dev/null
+++ b/decisions/evidence/VID-DEC-002/VID-DEC-002-consolidated-review.md
@@ -0,0 +1,25 @@
+# VID-DEC-002 Consolidated Review
+
+- **Candidate revision:** `0.1`
+- **Candidate SHA-256:** `AA5455A1FA2260551D9D63CDE9BA82DB621F1DA9FA4BE887833F4DC0656F14F3`
+- **Reviewed at:** `2026-08-01T23:41:00+07:00`
+- **Reviewer:** primary runtime under `OWNER-AUTH-V0`
+- **Review type:** Owner-authorized consolidated self-review; not independent
+- **Disposition:** Accept
+
+## Mechanical Evidence
+
+The exact candidate passed 10 of 10 checks: single H1, no unresolved placeholders, all local links resolved, canonical Shared namespace syntax, valid MSIX package-name syntax and length, explicit `.vlsp` declaration, fixture/production separation, fork identity isolation, protocol negative paths, and no fabricated Package Family Name.
+
+## Role Checklist
+
+| Review area | Disposition | Basis |
+|---|---|---|
+| Video architecture | Pass | Project UUID is independent from path and package identity; migration fails without source mutation. |
+| Shared architecture | Pass | Canonical namespace is byte-exact, fixture identity is excluded, and child ownership stays bounded. |
+| Security and privacy | Pass | Activation is allowlisted and bounded; credentials, remote URLs, execution, and traversal are rejected. |
+| Licensing and identity | Pass with external gate | Modified builds cannot impersonate official identity; Store availability and signing custody require later evidence. |
+| Compatibility and QA | Pass | Unknown major versions fail closed; collisions stop rather than overwrite; channels install side by side. |
+| Release engineering | Pass with external gate | Package tuple matching, derived PFN/AUMID, signing, install, update, rollback, and uninstall evidence are mandatory. |
+
+No blocking finding remains. External package reservation and production signing facts are intentionally verification gates and were not fabricated during this review.
diff --git a/decisions/evidence/VID-DEC-002/manifest.json b/decisions/evidence/VID-DEC-002/manifest.json
new file mode 100644
index 0000000..642f997
--- /dev/null
+++ b/decisions/evidence/VID-DEC-002/manifest.json
@@ -0,0 +1,33 @@
+{
+ "evidence_id": "VID-DEC-002-EVID",
+ "status": "accepted",
+ "approved_candidate": {
+ "decision_id": "VID-DEC-002",
+ "revision": "0.1",
+ "sha256": "AA5455A1FA2260551D9D63CDE9BA82DB621F1DA9FA4BE887833F4DC0656F14F3"
+ },
+ "accepted_artifact": {
+ "decision_id": "VID-DEC-002",
+ "revision": "1.0",
+ "path": "Video Localization/decisions/VID-DEC-002-project-package-name.md",
+ "sha256": "DF93E05E56B56D0A44AED4D51B6E10928532F592E2398909DDD4690D4D01649D"
+ },
+ "review": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-002/VID-DEC-002-consolidated-review.md",
+ "sha256": "C3BB511B50201DB2F12F5DA81EAC850EA07A17ADE312377D8C0172EC9BE8C23F",
+ "checks": "10/10",
+ "independent_review": false
+ },
+ "approval_transaction": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-002/VID-APPROVAL-002-transaction.json",
+ "sha256": "C963250F998717847B3846E5582901AD1461CAF832C6E4A8B7A95451DAE16DB5",
+ "status": "Accepted",
+ "conditions": 3
+ },
+ "release_gates": [
+ "package-name availability",
+ "production publisher custody and derived PFN/AUMID",
+ "channel and modified-build isolation"
+ ],
+ "authority": "OWNER-AUTH-V0"
+}
diff --git a/decisions/evidence/VID-DEC-003/acceptance-manifest.json b/decisions/evidence/VID-DEC-003/acceptance-manifest.json
new file mode 100644
index 0000000..b83fc8c
--- /dev/null
+++ b/decisions/evidence/VID-DEC-003/acceptance-manifest.json
@@ -0,0 +1,12 @@
+{
+ "decision_id": "VID-DEC-003",
+ "revision": "1.0",
+ "status": "Accepted",
+ "accepted_path": "Video Localization/decisions/VID-DEC-003-initial-support-profiles.md",
+ "reviewed_candidate_revision": "0.1-review.2",
+ "reviewed_candidate_sha256": "E4B7D0A717E9A4DE2D3675A038D5BE739CCF2CFA3D0A179A4821ABA3EE78AD1D",
+ "accepted_document_sha256": "0C6A9056083BFC741B49DE908F74597D9BD8546B69EB75AE8C342B6EA06E2851",
+ "authority": "OWNER-AUTH-V2",
+ "accepted_at": "2026-08-02T10:50:42.7775308+07:00",
+ "acceptance_review": ".agents/reviews/VID-DEC-003/primary-acceptance.md"
+}
diff --git a/decisions/evidence/VID-DEC-003/candidate-manifest.json b/decisions/evidence/VID-DEC-003/candidate-manifest.json
new file mode 100644
index 0000000..f24078a
--- /dev/null
+++ b/decisions/evidence/VID-DEC-003/candidate-manifest.json
@@ -0,0 +1,16 @@
+{
+ "decision_id": "VID-DEC-003",
+ "revision": "0.1-review.2",
+ "status": "In Review",
+ "candidate_path": "Video Localization/decisions/VID-DEC-003-initial-support-profiles.md",
+ "candidate_sha256": "E4B7D0A717E9A4DE2D3675A038D5BE739CCF2CFA3D0A179A4821ABA3EE78AD1D",
+ "authority": "OWNER-AUTH-V2",
+ "created_at": "2026-08-02T10:47:17.5986300+07:00",
+ "reviewed_inputs": [
+ ".agents/research/VID-DEC-003/official-component-lifecycle.md",
+ ".agents/research/VID-DEC-003/video-architecture-options.md",
+ ".agents/research/VID-DEC-003/shared-worker-contract.md",
+ ".agents/research/VID-DEC-003/security-license-profile.md",
+ ".agents/research/VID-DEC-003/support-claim-ux.md"
+ ]
+}
diff --git a/decisions/evidence/VID-DEC-004/VID-DEC-004-consolidated-review.md b/decisions/evidence/VID-DEC-004/VID-DEC-004-consolidated-review.md
new file mode 100644
index 0000000..635f7f1
--- /dev/null
+++ b/decisions/evidence/VID-DEC-004/VID-DEC-004-consolidated-review.md
@@ -0,0 +1,11 @@
+# VID-DEC-004 Consolidated Review
+
+- Decision: VID-DEC-004@1.0
+- Accepted artifact SHA-256: $dec4Hash
+- Direction studies: **3**
+- Selected direction: **Cinematic Ledger**
+- Authority: OWNER-AUTH-V0; Owner Resolution Bundle V1
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Accept**
+
+Cinematic Ledger satisfies the product requirement for a calm, cinematic, precise workstation while preserving equal Light and Dark themes, a neutral-dark media stage, semantic status separation, compact and comfortable density, Thai/CJK/RTL obligations, reduced motion, and Shared token ownership. Exact token values and rendered conformance remain owned by VID-SPEC-014 and SUI-SPEC-012@Exit:S1.
diff --git a/decisions/evidence/VID-DEC-004/manifest.json b/decisions/evidence/VID-DEC-004/manifest.json
new file mode 100644
index 0000000..53dddcd
--- /dev/null
+++ b/decisions/evidence/VID-DEC-004/manifest.json
@@ -0,0 +1,31 @@
+{
+ "evidence_id": "VID-DEC-004-EVID",
+ "status": "accepted",
+ "accepted_artifact": {
+ "decision_id": "VID-DEC-004",
+ "revision": "1.0",
+ "path": "Video Localization/decisions/VID-DEC-004-visual-direction.md",
+ "sha256": "5F2CF528B85682AB6CACEAE1C27B0F17A3D83DE1BDA97AECB4AF5B57F1820646"
+ },
+ "review": {
+ "path": "Video Localization/decisions/evidence/VID-DEC-004/VID-DEC-004-consolidated-review.md",
+ "sha256": "7773B976B5780ACE082833F293CEF8B54DA78EC7BDCE15711831E88DE829FBE9",
+ "direction_studies": 3,
+ "selected": "Cinematic Ledger",
+ "independent_review": false
+ },
+ "approvals": {
+ "approved": "5/5",
+ "authority": [
+ "OWNER-AUTH-V0",
+ "Owner Resolution Bundle V1"
+ ]
+ },
+ "implementation_evidence": {
+ "claimed": false,
+ "owned_by": [
+ "VID-SPEC-014",
+ "SUI-SPEC-012@Exit:S1"
+ ]
+ }
+}
diff --git a/eng/README.md b/eng/README.md
new file mode 100644
index 0000000..20acdf5
--- /dev/null
+++ b/eng/README.md
@@ -0,0 +1,28 @@
+# VID-IMPL-P00-001 Scaffold Execution Guide
+
+This directory contains the phase 00 scaffold entrypoint scripts for implementation work.
+
+## Scope
+
+- Repository bootstrap validation
+- Toolchain and dependency discoverability checks
+- Repo test-harness smoke checks for scaffold-only scaffolding
+
+No product behavior is implemented in this issue.
+
+## Execution
+
+- `pwsh .\eng\build.ps1`
+- `pwsh .\eng\test.ps1`
+- `pwsh .\eng\print-versions.ps1`
+- `pwsh .\eng\verify-toolchain.ps1`
+
+Both scripts write machine-readable evidence under:
+
+- `Video Localization/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/`
+
+## Assumptions documented during execution
+
+1. Windows App SDK is pinned centrally; WinUI application composition and MSIX packaging are tracked after this scaffold gate.
+2. This scaffold records only repository, toolchain, and test-harness readiness for Phase 00.
+3. The proof of platform compatibility will be produced when behavior is added in later issues.
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..6d5813f
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.302",
+ "rollForward": "latestPatch",
+ "allowPrerelease": false
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..4070caf
--- /dev/null
+++ b/index.html
@@ -0,0 +1 @@
+Localization Suite DashboardOpening the Localization Suite Dashboard...
\ No newline at end of file
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..128d95e
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..3e64969
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,5 @@
+[toolchain]
+channel = "1.97.1"
+components = [ "rustfmt", "clippy" ]
+targets = [ "x86_64-pc-windows-msvc" ]
+profile = "default"
diff --git a/specifications/VID-SPEC-001-foundation-prd.md b/specifications/VID-SPEC-001-foundation-prd.md
new file mode 100644
index 0000000..ce9b1fa
--- /dev/null
+++ b/specifications/VID-SPEC-001-foundation-prd.md
@@ -0,0 +1,172 @@
+# VID-SPEC-001 - Foundation and Manual Editor Product Requirements
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-001` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner | Product Lead: Video |
+| Required reviewer roles | Domain Architect: Video; Shared Platform Lead; Security Lead; UX and Design Lead; Internationalization Lead; Accessibility Lead; QA and Compatibility Lead; Release Engineering Lead |
+| Blocker | `P0:V0` |
+| Depends on | `SUI-DEC-008@1.0` Accepted |
+| GitHub work item | `SubMaRk/video-localization#19` |
+| Normative scope | Product requirements and release boundaries for Video Phase 00 and Phase 01 |
+
+## Status and Approval Dependency
+
+`SUI-DEC-008@1.0` accepts Product Planning Baseline v1.0 and authorizes Specification Freeze work. It does not freeze this PRD or authorize implementation.
+
+This revision is an editable first draft. It becomes a freeze candidate only after every requirement has downstream ownership, all open questions are resolved or explicitly deferred, and the required reviewers approve the same immutable revision. Only the suite Specification and Decision Register may change its status to `Frozen`.
+
+## Normative Language
+
+The terms **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, and **MAY** are normative.
+
+This PRD fixes product behavior, boundaries, and acceptance intent. It does not select an implementation language, desktop framework, database, media stack, project extension, package identity, or exact promoted format profile. Those choices remain controlled by registered decisions and downstream specifications.
+
+## Product Promise
+
+Video Localization Studio MUST become a Windows-first, subtitle-first, local-first workstation that remains useful when AI, network access, Teams, plugins, optional models, and accelerators are unavailable. The first stable workflow MUST let a professional user import supported media, create and time subtitles accurately, run deterministic QC, and produce validated output on CPU-only hardware.
+
+The product MUST preserve human authority over text, timing, style, position, review, approval, and delivery. Automated or provider-derived results MUST remain inspectable observations or proposals and MUST NOT silently overwrite approved human work.
+
+## Target Users and Outcomes
+
+| User | Required outcome |
+| --- | --- |
+| Subtitle editor | Import media, navigate accurately, author and time cues, use keyboard-first editing, run QC, and export without AI |
+| Translator/localizer | Work with Unicode and IME text, including English and Thai, without losing approved content or timing identity |
+| Reviewer | Inspect exact cue revisions, findings, changes, recovery state, and delivery warnings before approval |
+| Solo operator | Complete the core workflow offline on CPU-only Windows hardware and recover safely from interruption |
+| Release operator | Produce output tied to an exact project revision, profile, tool identity, settings, and validation record |
+
+## Scope and Release Boundaries
+
+| Boundary | Required outcome | Excluded from the boundary |
+| --- | --- | --- |
+| Phase 00 | Prove durable project, timing, revision, worker, recovery, security, UI, and localization foundations through a thin offline subtitle slice | Professional editor completeness, AI, Teams, visual text, broad format support |
+| `v0.1.0-alpha` | Create/save project, ingest one frozen media profile, play, build waveform, create a manual cue, run deterministic QC, export SRT, recover from a crash, and use the English/Thai System/Light/Dark shell on CPU | Production polish and broad compatibility claims |
+| Phase 01 | Deliver a professional manual subtitle workflow that remains complete without AI | AI assistance, Teams, OCR, context intelligence, automation, unrestricted codec breadth |
+| `v0.2.0-alpha` | Prove reliable playback and seeking, manual editing operations, correct CFR/VFR behavior, essential QC, recovery, relinking, and selected sidecar interchange | Proxy and burn-in are not required by this alpha allocation unless separately promoted |
+| `v1.0.0` | Production-prove the offline CPU-capable Windows subtitle workstation and tested delivery profiles | AI, Teams, visual text, context, automation, and later capability packs |
+
+No later release capability MAY enter Phase 00 or Phase 01 through an incidental dependency.
+
+## Phase 00 Product Requirements
+
+| ID | Requirement | Downstream authority or evidence |
+| --- | --- | --- |
+| `VID-PRD-F0-001` | The user MUST complete `import -> probe -> waveform -> manual cue -> deterministic QC -> SRT and ASS export` offline on the approved CPU-only Windows 11 x64 baseline. | `VID-SPEC-003`, `VID-SPEC-006`, `VID-SPEC-015` |
+| `VID-PRD-F0-002` | Project create, open, save, autosave, migration, relink, recovery, and cache rebuild MUST preserve stable project, asset, track, cue, word, speaker, annotation, observation, revision, job, and artifact identities. | `VID-SPEC-002`, `VID-SPEC-015` |
+| `VID-PRD-F0-003` | Durable project truth MUST be versioned and separated from rebuildable caches, temporary files, optional resources, credentials, private settings, and provider state. | `VID-SPEC-002`, `VID-DEC-002`, `SUI-SPEC-002` |
+| `VID-PRD-F0-004` | Time MUST use integer values and explicit rational source time bases. VFR navigation and snapping MUST use source presentation timestamps and an explicit source-to-proxy map, never nominal-FPS arithmetic. | `VID-SPEC-003` |
+| `VID-PRD-F0-005` | Save, reopen, relink, proxy mapping, migration, and cache rebuilding MUST preserve time anchors without cumulative cue drift. | `VID-SPEC-003`, `VID-SPEC-015` |
+| `VID-PRD-F0-006` | Media, waveform, export, and other complex work MUST run outside the UI process through versioned operations with bounded resources, progress, cancellation, timeout, retry, checkpoint, crash handling, and structured errors. | `SUI-SPEC-003`, `VID-DEC-001`, `VID-SPEC-015` |
+| `VID-PRD-F0-007` | Output-affecting operations MUST have stable identities and versions, canonical inputs, deterministic cache keys, atomic artifact publication, provenance, startup reconciliation, and force-recompute behavior. | `SUI-SPEC-003`, `VID-SPEC-002`, `VID-SPEC-015` |
+| `VID-PRD-F0-008` | A worker crash, timeout, cancellation, malformed result, forced application closure, disk exhaustion, or damaged cache MUST NOT corrupt project truth or publish a partial artifact as complete. | `VID-SPEC-004`, `VID-SPEC-015` |
+| `VID-PRD-F0-009` | Media, subtitle, attachment, font, archive, manifest, decoder, demuxer, converter, filesystem, and protocol inputs MUST be treated as untrusted and processed under explicit isolation, network, resource, quarantine, fuzzing, and recovery rules. | `VID-SPEC-004` |
+| `VID-PRD-F0-010` | Automatic external URL resolution, unsafe media protocols, script or shell execution, traversal, device paths, symlink escape, and unbounded decompression MUST be disabled or rejected by the approved ingest profile. | `VID-SPEC-004` |
+| `VID-PRD-F0-011` | Video MUST consume exact compatible Shared contracts through Video-owned adapters while retaining ownership of timebase, playback, subtitle, audio, encoding, QC, and delivery semantics. | `SUI-SPEC-001`, `VID-SPEC-002` |
+| `VID-PRD-F0-012` | Video MUST remain independently installable and operable. Manga or Document installation, update, rollback, or removal MUST NOT alter Video's active dependency or resource closure. | `SUI-SPEC-001`, `VID-SPEC-015` |
+| `VID-PRD-F0-013` | Fundamental project data MUST remain readable when optional models, plugins, providers, runtimes, caches, and Shared resources not required by the pinned project profile are absent. | `VID-SPEC-002`, `VID-SPEC-015` |
+| `VID-PRD-F0-014` | The foundational observation-to-revision model MUST distinguish generated observations from human revisions and MUST protect approved human work even before AI providers are introduced. | `VID-SPEC-002` |
+| `VID-PRD-F0-015` | The prototype shell MUST include the video stage, cue list, timeline, and inspector using primitive, semantic, and component tokens in System, Light, and Dark modes. | `VID-SPEC-014` |
+| `VID-PRD-F0-016` | Theme switching MUST preserve project, workspace, focus, selection, and playhead state; Light and Dark MUST receive equal contrast, focus, accessibility, and screenshot-regression coverage. | `VID-SPEC-014`, `VID-SPEC-015` |
+| `VID-PRD-F0-017` | The application localization service MUST use stable message keys, English source and fallback catalogs, Thai catalogs, BCP 47 identifiers, placeholder validation, pseudo-localization, and missing-message checks. | `SUI-SPEC-007`, `VID-SPEC-014` |
+| `VID-PRD-F0-018` | English and Thai application language MUST switch without closing the project or losing shell state. Application language MUST remain independent from project source and target languages. | `VID-SPEC-014`, `VID-SPEC-015` |
+| `VID-PRD-F0-019` | Thai glyphs, combining marks, line height, IME composition, search, legal segmentation, shaped-width measurement, and preview-versus-delivery behavior MUST have maintained smoke or golden fixtures. | `VID-SPEC-014`, `VID-SPEC-015` |
+| `VID-PRD-F0-020` | The Phase 00 corpus MUST cover CFR, VFR, rational time bases, long media, English, Thai, CJK, RTL, mixed script, hostile inputs, worker failure, forced closure, migration, relink, and cache damage with rights-cleared provenance. | `VID-SPEC-015` |
+
+## Phase 01 Product Requirements
+
+| ID | Requirement | Downstream authority or evidence |
+| --- | --- | --- |
+| `VID-PRD-F1-001` | The manual editor MUST support create, edit, split, merge, move, snap, offset, ripple, and batch timing while preserving stable cue lineage and protected locks. | `VID-SPEC-002`, `VID-SPEC-005` |
+| `VID-PRD-F1-002` | Playback MUST provide reliable seek, frame step, A/B loop, audio scrub, source/proxy mapping, waveform access, and playback priority while background work runs. | `VID-SPEC-003`, `VID-SPEC-005`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-003` | Undo/redo, autosave, snapshots, revision comparison, relinking, recovery, and transactional edit groups MUST preserve acknowledged and approved work. | `VID-SPEC-002`, `VID-SPEC-005`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-004` | Deterministic timing proposals MUST be ordered, idempotent, VFR-correct, lock-aware, layer-aware, diffable, selectively acceptable, and attributable to the pass that changed each boundary. | `VID-SPEC-005` |
+| `VID-PRD-F1-005` | Deterministic QC MUST use stable rule IDs, profile-owned thresholds, previewable fixes, one undoable command group, scoped exceptions, rerun deltas, and export severity gates. | `VID-SPEC-005`, `VID-SPEC-006` |
+| `VID-PRD-F1-006` | Timed-text import and export MUST preserve native precision and safely pass-through supported unknown data, identify source encoding and timing precision, and report conversion loss before delivery. | `VID-SPEC-006` |
+| `VID-PRD-F1-007` | Only exact media and timed-text profiles accepted by `VID-DEC-003` and frozen in `VID-SPEC-006` MAY be advertised as supported; probe, play, import, edit, round-trip, export, mux, stream-copy, encode, and validated delivery are independent claims. | `VID-DEC-003`, `VID-SPEC-006` |
+| `VID-PRD-F1-008` | Preview and burn-in MUST use equivalent subtitle rendering, font, shaping, line-breaking, positioning, style, and destination-rounding semantics for each promoted profile. | `VID-SPEC-005`, `VID-SPEC-006`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-009` | The cue list and timeline MUST remain responsive with 10,000 cues, and playback MUST retain priority during proxy, waveform, QC, and export jobs under frozen environment and measurement profiles. | `VID-SPEC-005`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-010` | Keyboard-first navigation and editing MUST cover primary manual workflows with visible focus, searchable/customizable commands, IME-safe handling, and reduced-motion behavior. | `VID-SPEC-005`, `VID-SPEC-014` |
+| `VID-PRD-F1-011` | The five primary workspaces and their production components MUST implement approved System, Light, and Dark themes, Compact and Comfortable densities, documented states, accessibility targets, and bounded rendering behavior. | `VID-SPEC-014`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-012` | Every critical Phase 01 workflow, validation message, error, recovery instruction, export warning, notification, and accessibility description MUST be complete in English and Thai with safe English fallback. | `VID-SPEC-014`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-013` | Unicode, Thai, CJK, RTL, mixed-script, no-whitespace, combining-mark, legacy-encoding, and IME fixtures MUST import, display, edit, search, time, preview, and export without silent data loss. | `VID-SPEC-005`, `VID-SPEC-006`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-014` | Every promoted parser, decoder, renderer, converter, mux, and delivery path MUST pass malformed, oversized, attachment-heavy, timeout, cancellation, crash, disk-full, unsafe-protocol, and partial-output tests within declared boundaries. | `VID-SPEC-004`, `VID-SPEC-006`, `VID-SPEC-015-A` |
+| `VID-PRD-F1-015` | Every final artifact MUST identify the source project revision, selected delivery profile, QC disposition, actual tools and versions, settings, hashes, and reproducibility metadata required by that profile. | `VID-SPEC-006`, `VID-SPEC-015-A` |
+
+## Cross-Cutting Invariants
+
+1. Approved human work MUST NOT be silently overwritten.
+2. The UI thread MUST NOT execute media, export, AI, network, or other unbounded work.
+3. CPU-only completion of the core workflow MUST remain available.
+4. Project truth MUST survive loss of rebuildable and optional components.
+5. Stable identity MUST NOT be derived from display names, mutable paths, list positions, or nominal timestamps.
+6. Large waveform, thumbnail, audio, and media payloads MUST NOT cross the general command channel as base64 JSON.
+7. Unknown forward-compatible data MUST be preserved safely or rejected explicitly; it MUST NOT disappear silently.
+8. Every support claim MUST name an exact profile and evidence revision.
+9. Product code MUST NOT import Manga or Document domain types.
+10. No release gate MAY be passed by prose alone when the register requires frozen thresholds, fixtures, environments, and evidence.
+
+## Implementation Entry Gates
+
+Phase 00 implementation MUST NOT begin until all applicable `P0:V0` blockers and their transitive Shared dependencies are `Frozen`, `Implemented`, `Verified`, or `Accepted` as required by the register. At minimum, the entry review MUST resolve:
+
+- `VID-SPEC-001`, `VID-SPEC-002`, `VID-SPEC-003`, `VID-SPEC-004`, `VID-SPEC-014`, and the Phase 00 entry subset of `VID-SPEC-015`.
+- `VID-DEC-001`, `VID-DEC-002`, and `VID-DEC-004`.
+- The exact compatible Shared foundation subset including `SUI-SPEC-001`, `SUI-SPEC-002`, `SUI-SPEC-003`, `SUI-SPEC-007`, and `SUI-SPEC-008` where consumed by Video.
+- Exact corpus, Windows, CPU, filesystem, locale, theme, language, dependency, and hostile-ingest profiles used by the first implementation slice.
+
+Phase 01 implementation MUST additionally satisfy Phase 00 exit and freeze the applicable `VID-SPEC-005`, `VID-SPEC-006`, `VID-DEC-003`, and `VID-SPEC-015-A` entry requirements.
+
+## PRD Acceptance Gates
+
+| Gate ID | Pass condition | Evidence owner |
+| --- | --- | --- |
+| `VID-PRD-GATE-001` | Every `VID-PRD-F0-*` and `VID-PRD-F1-*` requirement maps to a registered specification, decision, fixture family, or explicit deferral. | Product Lead: Video |
+| `VID-PRD-GATE-002` | The release allocation contains no AI, Teams, visual-text, context, automation, broad-format, or later-platform dependency in Phase 00/01. | Product Lead: Video; Release Engineering Lead |
+| `VID-PRD-GATE-003` | Domain ownership review confirms Video owns timebase, playback, subtitle, audio, encoding, QC, and delivery while Shared remains domain-neutral. | Domain Architect: Video; Shared Platform Lead |
+| `VID-PRD-GATE-004` | Security review confirms every ingest and worker boundary has downstream threat-model and failure-evidence ownership. | Security Lead |
+| `VID-PRD-GATE-005` | UX, accessibility, and localization review confirms System/Light/Dark and English/Thai requirements are complete for the allocated phases. | UX and Design Lead; Internationalization Lead; Accessibility Lead |
+| `VID-PRD-GATE-006` | QA review confirms every exit claim names a downstream corpus, environment, metric, tolerance, or a registered specification that must freeze those values. | QA and Compatibility Lead |
+| `VID-PRD-GATE-007` | All required reviewer approvals bind the exact candidate revision and no tracked blocking comment remains open. | Product Lead: Video; Release Engineering Lead |
+
+## Evidence and Traceability Rules
+
+- Requirement evidence MUST name the requirement ID, exact specification revision, exact fixture or corpus revision, environment profile, result, and retained artifact.
+- A passing draft fixture MUST NOT change a specification status to `Frozen`.
+- Exact thresholds belong in `VID-SPEC-005`, `VID-SPEC-006`, `VID-SPEC-015`, or `VID-SPEC-015-A`, not in unversioned issue comments.
+- GitHub issue `SubMaRk/video-localization#19` MAY mirror progress but MUST NOT override this specification or the suite register.
+- Local `Plan/` files MUST NOT be uploaded to satisfy the GitHub issue.
+
+## Explicit Non-Goals and Deferred Scope
+
+- AI transcription, translation, diarization, model installation, and cloud providers.
+- LAN or public-network Teams collaboration.
+- OCR, visual-text tracking, restoration, and replacement.
+- Story, context, recurring-region, and semantic retrieval capabilities.
+- Recipes, node workflows, watch folders, headless automation, and agents.
+- Broad professional format, codec, encoder, accelerator, sensor, STT, and hardware-backend claims.
+- Windows ARM64, macOS, Linux, portable distribution, and machine-wide installation unless promoted by a later decision.
+- Full nonlinear editing, arbitrary dockable UI, vendor-hosted SaaS, billing, SSO/SCIM, or automatic approval of probabilistic output.
+
+## Open Review Work
+
+1. Confirm that each downstream specification accepts the requirement IDs assigned to it.
+2. Resolve `VID-DEC-001`, `VID-DEC-002`, and `VID-DEC-004` before a Phase 00 implementation-ready claim.
+3. Bind exact Phase 00 and Phase 01 corpus, support, budget, profile, and failure-evidence identifiers in `VID-SPEC-015` and `VID-SPEC-015-A`.
+4. Obtain all required reviewer approvals on one immutable candidate revision.
+
+## References
+
+- [Video Planning Index](../Plan/README.md)
+- [Phase 00 - Foundations and Vertical Spike](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
+- [Phase 01 - Manual Production Editor](../Plan/roadmap/PHASE_01_MANUAL_PRODUCTION_EDITOR.md)
+- [Release Gates](../Plan/sections/10_RELEASE_GATES.md)
+- [Next Planning Documents](../Plan/sections/11_NEXT_PLANNING_DOCUMENTS.md)
+- [Video Release Plan](../../_shared/Plan/release-planning/06_VIDEO_LOCALIZATION_RELEASE_PLAN.md)
+- [Video Capability-to-Version Matrix](../../_shared/Plan/release-planning/17_VIDEO_CAPABILITY_VERSION_MATRIX.md)
+- [Specification and Decision Register](../../_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md)
+- [`SUI-DEC-008@1.0`](../../_shared/decisions/SUI-DEC-008-planning-baseline-acceptance.md)
diff --git a/specifications/VID-SPEC-002-domain-schema.md b/specifications/VID-SPEC-002-domain-schema.md
new file mode 100644
index 0000000..1ec2990
--- /dev/null
+++ b/specifications/VID-SPEC-002-domain-schema.md
@@ -0,0 +1,342 @@
+# VID-SPEC-002: Canonical Video Domain Schema
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-002` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner | Domain Architect: Video |
+| Required reviewers | Product Lead: Video; Shared Platform Lead; QA and Compatibility Lead; Security Lead |
+| Blocker | `P0:V0` |
+| Depends on | `SUI-SPEC-001@1.0`; `VID-SPEC-001@1.0` |
+| Product namespace | `submark.video-localization` |
+
+## Purpose
+
+This specification freezes the implementation-neutral Video-owned schema for projects, collections, media assets, streams, tracks, cues, words, speakers, annotations, observations, revisions, and lineage. It defines identity, authority, references, invariants, versioning, safe failure, and conformance behavior without choosing a database layout, programming-language type, wire encoding, media library, or UI representation.
+
+The contract consumes the Frozen Shared identity and project primitives in [SUI-SPEC-001](../../_shared/specifications/SUI-SPEC-001-shared-contract-catalog.md) and [SUI-SPEC-002](../../_shared/specifications/SUI-SPEC-002-project-and-revision-primitives.md). Product requirements are governed by [VID-SPEC-001](VID-SPEC-001-foundation-prd.md), while the accepted public identity is governed by [VID-DEC-002](../decisions/VID-DEC-002-project-package-name.md).
+
+## Normative Language
+
+`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` express requirement strength. A conforming profile MUST map every applicable normative requirement to executable or inspectable evidence.
+
+## Authority and Domain Boundary
+
+Video owns all interpretation and mutation of media, stream, timebase, track, cue, word, speaker, subtitle, spatial annotation, observation, and Video delivery records.
+
+- Shared Core MAY store, version, reference, migrate, and transport namespaced Video records as typed opaque product data.
+- Shared Core MUST NOT infer cue timing, text segmentation, speaker identity, language meaning, media equivalence, stream selection, split/merge lineage, approval, or delivery readiness.
+- Every authoritative mutation MUST enter through a versioned Video command handler and commit through the Frozen Shared project/revision boundary.
+- A worker, provider, plugin, import adapter, probe, decoder, agent, or cache MUST NOT directly mutate authoritative Video state.
+- Derived output begins as an observation, proposal, or artifact. Successful execution MUST NOT imply human approval.
+- Video MUST NOT import Manga or Document domain schemas, and no Video project may require either application to be installed.
+
+## Identity Grammar
+
+All durable identities are opaque, stable, non-recycled, and compared independently of display text or paths. An identity MUST NOT encode a mutable name, list position, time value, frame number, filesystem path, database offset, or user-facing label.
+
+| Identity | Scope |
+| --- | --- |
+| `VideoProjectId` | One Video project instance under the Shared Project ID |
+| `CollectionId` | Product-owned series, season, episode-group, or user collection |
+| `MediaAssetId` | One logical source, proxy, audio, attachment, or generated media asset |
+| `StreamId` | One stream observation within an exact media-source revision |
+| `TrackId` | One logical timed-text, transcript, caption, sign, metadata, or review track |
+| `CueId` | One stable timed-text unit |
+| `WordId` | One stable editorial token or grapheme-aware word unit within lineage |
+| `SpeakerId` | One project-scoped speaker label entity; not biometric identity |
+| `AnnotationId` | One typed annotation attached to exact targets and revisions |
+| `ObservationId` | One immutable derived or imported claim with provenance |
+| `VideoRevisionId` | One product revision committed through a Shared Revision |
+| `LineageEdgeId` | One explicit source, split, merge, replacement, derivation, or rebinding edge |
+
+Identity rules:
+
+1. Moving, renaming, relinking, reopening, proxying, or rebuilding caches MUST preserve logical identities.
+2. Forking a project MUST create a new project identity and record the source project and exact revision.
+3. Deletion MUST retain a tombstone when a durable reference can outlive the object.
+4. A deleted identity MUST NOT be assigned to another logical object.
+5. Imports MUST allocate product identities; external ordinals and format IDs remain provenance, not authoritative identity.
+6. Duplicate content hashes MAY identify equal bytes but MUST NOT collapse independently governed logical assets without an explicit command.
+
+## Canonical Reference
+
+A durable `VideoObjectRef` MUST include:
+
+- Product namespace `submark.video-localization`.
+- Video Project ID and Shared Project ID.
+- Object kind and stable Object ID.
+- Exact Video Revision ID and compatible schema identity.
+- Required lineage or source-authority qualifier when interpretation depends on it.
+
+A moving query MAY omit an exact revision only while it remains non-durable. Before an annotation, job, artifact, approval, finding, event, export, or migration is committed, every target MUST resolve to exact revision-bound references.
+
+Missing, stale, deleted, wrong-project, wrong-product, ambiguous, or incompatible references MUST produce structured states. They MUST NOT silently bind to a same-named, nearby, overlapping, or similar object.
+
+## Video Project Record
+
+A `VideoProject` MUST contain:
+
+- Shared Project Identity and Video Project ID.
+- Product namespace and exact Video schema revision.
+- Current Shared Revision and Video Revision.
+- Collection memberships without making collections identity authorities.
+- Source-authority inventory.
+- Media asset, stream-observation, track, speaker, annotation, and lineage inventories.
+- Required contract ranges and extension-preservation policy.
+- Project language declarations kept separate from application UI locale.
+- Current durable health, migration, and read/write capability state.
+
+Project truth MUST remain separate from proxies, waveform tiles, thumbnails, indexes, embeddings, temporary exports, and other rebuildable artifacts. A project MUST remain readable when optional workers, models, providers, plugins, or caches are absent.
+
+## Collection Record
+
+A `Collection` groups projects or assets as series, season, episode group, production batch, or user-defined set.
+
+- Membership MUST use stable exact references.
+- Collection order and labels MUST NOT change member identity.
+- Removing membership MUST NOT delete the member.
+- A collection MUST NOT imply editorial approval, chronology, or source equivalence unless a versioned Video command records that meaning.
+- Cross-project references MUST declare visibility, authority, and revision scope.
+
+## Media Asset and Source Authority
+
+A `MediaAsset` MUST record:
+
+- Media Asset ID, kind, role, and exact asset-schema version.
+- Source-authority mode: embedded, managed, external, generated, or proxy.
+- Content fingerprint or immutable source revision when available.
+- Structured locator hints separate from authority.
+- Size and integrity metadata where bytes are managed.
+- Probe observation references and selected authoritative source profile.
+- Relationship to source, proxy, extracted audio, attachment, or generated derivative.
+- Relink and replacement policy.
+- Rights/provenance classification without embedding licensed payloads in evidence.
+
+Relinking MUST verify the approved source-identity procedure. A same filename, duration, or partial fingerprint is not sufficient authority. Source replacement MUST be an explicit command and MUST mark dependent observations, mappings, QC, previews, and delivery results stale according to registered impact rules.
+
+A `StreamObservation` describes one stream from one exact probe operation and source revision. It records stream kind, external stream ordinal, codec/profile observations, language declarations, timing metadata, disposition, attachment relations, and probe provenance. Stream observations MUST NOT claim support, safety, or delivery readiness by detection alone.
+
+## Timing and Coordinate References
+
+Durable time values MUST use integers and explicit rational time bases. A `TimePointRef` records an integer value, rational time-base identity, source timeline identity, and mapping revision. A `TimeRangeRef` records ordered start and end points using compatible timeline semantics.
+
+Exact CFR, VFR presentation-timestamp, frame, sample, proxy/source-map, drop-frame-display, and spatial-coordinate behavior belongs to `VID-SPEC-003`. Until that specification is Frozen, this schema MUST NOT define floating-point or nominal-FPS shortcuts.
+
+- A cue range MUST satisfy `start < end` under its timeline contract.
+- Durable VFR positions MUST NOT be derived from `frame_index / nominal_fps`.
+- A proxy reference MUST identify the exact source-to-proxy mapping revision.
+- Spatial references MUST identify normalized coordinate space and exact transform chain.
+
+## Track Record
+
+A `Track` MUST contain:
+
+- Track ID, track kind, schema version, and project reference.
+- Language tag using BCP 47 where language applies.
+- Source, target, bilingual, signs, captions, transcript, review, metadata, or other namespaced role.
+- Timeline and default spatial/style context references.
+- Ordered cue membership represented independently of Cue ID.
+- Import provenance, format identity, and conversion-loss state where applicable.
+- Lock, visibility, approval, and delivery eligibility states with distinct semantics.
+
+Track order, display color, name, visibility, or workspace placement MUST NOT be identity. Moving a cue between compatible tracks MUST preserve Cue ID only when the product command records semantic continuity; otherwise replacement lineage is required.
+
+## Cue Record
+
+A `Cue` MUST contain:
+
+- Cue ID, Track ID, exact Video Revision, and cue-schema version.
+- Time range and optional spatial-region reference.
+- Versioned text payload made of typed runs rather than trusted markup.
+- Source/target relation and explicit lineage edges.
+- Speaker, style, region, note, status, QC, lock, and annotation references.
+- Creation/import/observation provenance and last authoritative command.
+- Approval class distinct from completion or export selection.
+
+Cue rules:
+
+1. Text and timing authority MAY have separate source observations and MUST be reconciled explicitly.
+2. Splitting, merging, replacing, retiming, moving, and rebinding MUST create explicit lineage and affected-reference outcomes.
+3. Locked fields MUST NOT be changed by automation or bulk repair without an authorized override command.
+4. Overlap MUST NOT imply conflict without compatible layer and policy context.
+5. Conversion loss, unsupported styling, unknown pass-through data, and format precision MUST remain inspectable.
+6. Import/export round trips MUST NOT silently erase unknown data that the selected profile promises to preserve.
+
+## Word Record
+
+A `Word` is an editorial unit, not an assumption that every language uses whitespace segmentation.
+
+It MUST record Word ID, owning Cue ID, exact cue revision, text or text-span identity, source language, grapheme-aware boundaries, optional time range, confidence provenance when observed, and lineage.
+
+- Thai, CJK, RTL, mixed-script, markup, and combining-mark behavior MUST use the selected language/profile rules.
+- A provider token MUST NOT automatically become an authoritative Word.
+- Cue edits MAY retain, split, merge, replace, or retire Word IDs only through deterministic adapter rules with explicit lineage.
+- Character counts, reading-speed counts, segmentation, and shaped widths MUST name their exact rule/profile identity.
+
+## Speaker Record
+
+A `Speaker` MUST contain Speaker ID, project scope, display labels and aliases, optional external/source references, provenance, merge/split lineage, and review state.
+
+- Speaker identity is a production label and MUST NOT claim biometric, legal, demographic, emotional, or mental-state identity.
+- Diarization output is an observation until accepted by a Video command.
+- Merging or splitting speakers MUST preserve prior references through explicit lineage.
+- Display-name changes MUST NOT change Speaker ID.
+- Sensitive notes MUST use declared access and retention scopes.
+
+## Annotation Record
+
+An `Annotation` MUST contain Annotation ID, namespaced annotation kind/version, exact target references, author or producing operation, authority class, payload schema, lifecycle state, provenance, and access/retention classification.
+
+Targets MAY be project, asset, stream, track, cue, word, speaker, time range, spatial region, revision, observation, artifact, QC finding, or delivery result. Multi-target annotations MUST declare relationship semantics and MUST NOT imply equivalence merely by sharing one record.
+
+Unknown required annotation payloads MUST block unsafe writes. Unknown optional durable annotations MUST be preserved according to the Frozen Shared extension policy. Annotation payloads MUST remain inert and MUST NOT trigger network access, executable activation, or project mutation.
+
+## Observation and Proposal Record
+
+An `Observation` is an immutable claim produced by import, probe, parser, worker, provider, plugin, rule, model, or user analysis. It MUST contain:
+
+- Observation ID, kind/version, producer and implementation identity.
+- Exact input artifacts, source revisions, target references, parameters, and policy context.
+- Output payload identity, confidence or deterministic basis, limitations, and integrity.
+- Created instant, expiry/staleness rules, and provenance.
+- Authority class fixed as non-authoritative until an explicit acceptance command creates a revision.
+
+Accepting an observation MUST record the selected fields, reviewer/actor authority, prior revision, command, resulting revision, and observation reference. Rejecting or superseding an observation MUST NOT erase its retained provenance when audit policy requires it.
+
+## Video Revision and Lineage
+
+A `VideoRevision` refines one Frozen Shared Revision and MUST include:
+
+- Video Revision ID and exact Shared Revision ID.
+- Parent Video Revision IDs.
+- Product command and transaction identities.
+- Actor, authority epoch, commit ordering, and schema revisions.
+- Changed Video references and adapter-supplied impact summary.
+- Lineage edges, audit, provenance, and required extensions.
+
+Committed revisions are immutable. Undo, redo, restore, acceptance, and repair create new revisions. History MUST NOT be silently rewritten.
+
+A `LineageEdge` MUST declare edge identity, exact source and target references, relation kind, producing command/revision, ordering when needed, and rationale/provenance. Required relation kinds are:
+
+- `derived-from`
+- `source-to-target`
+- `split-into`
+- `merged-from`
+- `replaced-by`
+- `rebound-to-source`
+- `accepted-from-observation`
+- `restored-from-revision`
+
+Lineage MUST NOT be inferred from text similarity, time overlap, list adjacency, names, or content hashes alone. Many-to-many relations require explicit edges and compatibility validation.
+
+## Artifact and Delivery References
+
+Derived waveforms, proxies, thumbnails, indexes, subtitle renders, reports, and exports remain Shared artifact records with Video-owned typed payloads and references.
+
+Every artifact reference MUST bind exact input hashes, relevant Video revisions, operation/version, implementation/provider identity, parameters, runtime/backend, output hash, and policy context. Rebuildable artifact deletion MUST NOT remove project truth. A delivery result MUST bind its exact project revision, selected tracks/cues, delivery profile, QC gate, conversion-loss report, and produced artifact.
+
+## Commands and Mutation Invariants
+
+Every authoritative command MUST carry command/version identity, actor and authorization context, expected base revision, affected object preconditions, idempotency identity/digest, typed payload, and correlation/causation identities.
+
+- Validation, authorization, fencing, schema, integrity, lock, and object preconditions MUST pass before mutation.
+- A command MUST commit product records, Shared revision/event records, lineage, audit, and required inverse metadata atomically.
+- Duplicate commands with matching digest MUST return the original result; conflicting reuse MUST fail closed.
+- Long-running media/provider work MUST occur outside the authoritative transaction.
+- A partial command MUST NOT become visible.
+- Failure after commit but before response MUST be recoverable through idempotent status lookup.
+
+## Versioning, Migration, and Preservation
+
+Every record family has an independent schema identity and compatibility range under `SUI-SPEC-001`.
+
+- Unknown required fields or extensions MUST produce incompatibility before write.
+- Unknown optional durable data MUST be preserved byte-for-byte when safe re-encoding is unavailable.
+- Migration MUST declare source/target ranges, identity mappings, ordered restartable steps, validation, rollback/read-only behavior, and data-loss impact.
+- A migration that changes timing, spatial, identity, lineage, or approval meaning MUST supply explicit fixtures and mapping evidence.
+- Failed migration MUST leave either the previous project or the fully validated replacement readable.
+- No schema migration may silently promote observations, clear locks, erase lineage, or change approval.
+
+## Security and Privacy
+
+All project, media metadata, imported text, annotation, extension, and observation payloads are untrusted until validated.
+
+- Records MUST enforce size, count, depth, text, nesting, and expansion limits before allocation.
+- Project records MUST NOT contain credentials, private keys, ambient tokens, or unrestricted provider requests.
+- Structured locators MUST prevent traversal, device-path access, link escape, and automatic external resolution.
+- Content MUST NOT trigger network requests, plugins, scripts, decoders, fonts, or executables merely by being opened or preserved.
+- Logs and provenance MUST be bounded, redacted, and access-controlled.
+- Speaker and language records MUST NOT infer protected, biometric, emotional, or personnel attributes beyond authorized production labels.
+- Hostile media-specific parser/decoder limits and quarantine behavior are owned by `VID-SPEC-004`.
+
+## Safe Failure and Degraded Modes
+
+- Wrong-product, unsupported schema, missing required extension, corrupt identity, ambiguous lineage, or failed integrity MUST open read-only with structured diagnostics or fail safely.
+- Missing optional media, proxies, caches, models, plugins, or providers MUST NOT make fundamental project truth unreadable.
+- A missing external source MUST retain approved text and timing while marking dependent operations unavailable or stale.
+- Corrupt rebuildable artifacts MAY be quarantined and regenerated; corrupt authoritative data MUST NOT be silently replaced.
+- A failed relink, import, migration, or observation acceptance MUST leave the prior revision authoritative.
+
+## Conformance Gates
+
+| Gate | Requirement |
+| --- | --- |
+| `VID-DOM-001` | Every durable record has stable, non-path, non-recycled identity. |
+| `VID-DOM-002` | Durable references bind exact project, object, schema, and revision identity. |
+| `VID-DOM-003` | Shared storage and transport do not acquire Video semantic or mutation authority. |
+| `VID-DOM-004` | Project truth remains independent of rebuildable artifacts and optional capabilities. |
+| `VID-DOM-005` | Media relink/replacement verifies source authority and invalidates dependents explicitly. |
+| `VID-DOM-006` | Cue/word/speaker/annotation split, merge, replacement, and source-target lineage are explicit. |
+| `VID-DOM-007` | Observation success cannot become an approved revision without an authorized command. |
+| `VID-DOM-008` | Commands, revisions, events, lineage, audit, and inverse metadata commit atomically. |
+| `VID-DOM-009` | Time and spatial references defer exact semantics to `VID-SPEC-003` without nominal-FPS shortcuts. |
+| `VID-DOM-010` | Unknown, incompatible, hostile, or missing data fails safely and preserves project truth. |
+| `VID-DOM-011` | Migration preserves identity, extensions, lineage, locks, approval, and prior readable state. |
+| `VID-DOM-012` | Video remains independent of Manga/Document and all later AI/Teams semantics remain outside Phase 00 authority. |
+
+## Fixture Families
+
+Specification-freeze evidence MUST include synthetic, redistributable cases for:
+
+1. Identity, move, relink, fork, tombstone, duplicate bytes, and non-reuse.
+2. Project, collection, asset, stream, track, cue, word, speaker, annotation, and exact-reference round trips.
+3. Split, merge, replacement, source-target, observation-acceptance, undo, restore, and many-to-many lineage.
+4. CFR/VFR/rational time references, proxy mappings, mixed coordinate spaces, and invalid ranges without defining `VID-SPEC-003` math.
+5. Thai, CJK, RTL, mixed script, combining marks, markup, and language/profile-bound counting references.
+6. Wrong product, stale revision, deleted object, unsupported schema, unknown extension, corrupt integrity, and ambiguous relink.
+7. Worker/provider/plugin attempts to mutate project truth or promote observations directly.
+8. Interrupted commands, duplicate requests, failed migration, missing source, damaged cache, and read-only recovery.
+
+Native database, media, timing, migration, crash, and product-adapter conformance remains downstream `VID-SPEC-015` and Shared conformance evidence.
+
+## Freeze Criteria
+
+This specification may become Frozen when:
+
+1. `SUI-SPEC-001@1.0` and `VID-SPEC-001@1.0` remain Frozen.
+2. Every canonical record, identity, authority boundary, and conformance gate has a stable definition.
+3. Synthetic fixture families cover positive, negative, stale, incompatible, interruption, migration, and recovery outcomes.
+4. Video, Shared, QA/compatibility, and Security reviews approve the exact candidate hash.
+5. No unresolved point can change durable identity, reference, lineage, approval, or source-authority meaning.
+6. Evidence explicitly defers time/coordinate math, hostile ingest, native storage, and performance claims to their registered owners.
+
+## Downstream Ownership
+
+- `VID-SPEC-003` freezes timebase, VFR PTS, frame/sample, proxy/source-map, and spatial-coordinate semantics.
+- `VID-SPEC-004` freezes hostile media-ingest and parser/decoder isolation policy.
+- `VID-SPEC-005` freezes manual editing, deterministic timing, subtitle language, QC, and interaction behavior.
+- `VID-SPEC-015` owns Phase 00 native schema, migration, recovery, corpus, fault, support, and budget evidence.
+- `SUI-SPEC-012-D` owns complete Video-to-Shared adapter and lifecycle conformance at the Video adoption checkpoint.
+
+No downstream specification may silently reinterpret a Frozen identity, lineage, approval, or source-authority rule.
+
+## Planning References
+
+- [Product Scope](../Plan/sections/02_PRODUCT_SCOPE.md)
+- [Core Architecture](../Plan/sections/04_CORE_ARCHITECTURE.md)
+- [AI Data Model and Providers](../Plan/sections/05_AI_DATA_MODEL_AND_PROVIDERS.md)
+- [Release Gates](../Plan/sections/10_RELEASE_GATES.md)
+- [Next Planning Documents](../Plan/sections/11_NEXT_PLANNING_DOCUMENTS.md)
+- [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
diff --git a/specifications/VID-SPEC-003-timebase-and-coordinates.md b/specifications/VID-SPEC-003-timebase-and-coordinates.md
new file mode 100644
index 0000000..bd56a9f
--- /dev/null
+++ b/specifications/VID-SPEC-003-timebase-and-coordinates.md
@@ -0,0 +1,281 @@
+# VID-SPEC-003: Timebase, Presentation Timestamp, and Coordinate Contract
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-003` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner | Domain Architect: Video |
+| Required reviewers | Product Lead: Video; QA and Compatibility Lead; Performance and Hardware Lead; Security Lead |
+| Blocker | `P0:V0` |
+| Depends on | `VID-SPEC-002@1.0` |
+
+## Purpose
+
+This specification freezes exact Video-owned semantics for rational time bases, integer time points and ranges, CFR and VFR presentation timestamps, frame and audio-sample references, drop-frame display, source/proxy mappings, and normalized spatial coordinates and transforms.
+
+It refines the canonical references in [VID-SPEC-002](VID-SPEC-002-domain-schema.md) and the Phase 00 requirements in [Core Architecture](../Plan/sections/04_CORE_ARCHITECTURE.md) and [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md). It does not select a media library, decoder, player, waveform engine, storage encoding, or rendering API.
+
+## Normative Language
+
+`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` express requirement strength.
+
+## Core Invariants
+
+1. Durable time is an integer value interpreted by an explicit normalized rational time base.
+2. Durable VFR position is resolved from presentation timestamps, never `frame_index / nominal_fps`.
+3. Conversion uses checked exact rational arithmetic before an explicitly named rounding boundary.
+4. A frame number is derived within one declared timeline and MUST NOT serve as cross-timeline identity.
+5. Source, proxy, audio, subtitle, and delivery timelines remain distinct and are connected only by versioned mappings.
+6. Drop-frame notation changes display labels, not media time or frame count.
+7. Durable spatial coordinates use a declared normalized coordinate space and exact transform revision.
+8. Every seek, snap, map, quantize, and display operation names its policy and source revision.
+
+## Rational Time Base
+
+A `RationalTimeBase` contains:
+
+- Stable time-base identity and schema version.
+- Positive integer numerator `n` and denominator `d` in lowest terms.
+- Timeline identity and semantic kind.
+- Optional source provenance and external format representation.
+
+One tick represents `n / d` seconds. A `TimePoint` is a signed integer tick value plus the exact time-base and timeline identities. Implementations MUST support the declared project duration without overflow and MUST reject arithmetic that cannot be represented safely.
+
+Normalization rules:
+
+- `n > 0`, `d > 0`, and `gcd(n,d) = 1`.
+- Zero or negative components are invalid.
+- Equivalent unreduced fractions MUST normalize before identity comparison but their imported form MAY remain provenance.
+- A floating-point frame rate or timestamp MUST NOT become durable authority without conversion to a declared rational representation and recorded rounding evidence.
+
+## Exact Conversion and Rounding
+
+For source ticks `a` in time base `nA/dA`, conversion to target time base `nB/dB` first computes the exact rational target value:
+
+~~~text
+a * nA * dB
+---------------
+ dA * nB
+~~~
+
+Intermediate multiplication MUST use arbitrary precision or checked widening sufficient to prevent overflow. Reduction SHOULD occur before multiplication where possible.
+
+If the exact result is not integral, the caller MUST select a versioned rounding policy. Required policies are:
+
+- `floor`
+- `ceiling`
+- `nearest-ties-to-even`
+- `nearest-ties-earlier`
+- `range-outward` (`start=floor`, `end=ceiling`)
+- `format-defined` with exact format/profile identity
+
+No global implicit rounding policy is permitted. Conversion records used for durable timing MUST retain source value/base, target base, exact pre-round fraction or equivalent remainder, policy, result, and operation version.
+
+Repeated conversions MUST use the original authoritative value or one exact composed mapping. They MUST NOT accumulate rounded deltas through iterative float or frame conversions.
+
+## Time Ranges and Boundaries
+
+Durable ranges are half-open `[start,end)` unless a versioned external format adapter explicitly records different source semantics.
+
+- `start < end` is required for a nonempty cue or media interval.
+- Touching ranges where `A.end = B.start` do not overlap.
+- Open-ended observations MUST use an explicit unknown/open boundary state rather than a sentinel tick.
+- Inclusive external endpoints MUST convert through a named adapter policy and preserve source precision/conversion-loss evidence.
+- Range intersection, containment, gap, and ordering MUST occur in one compatible timeline or through an exact mapping.
+
+## Timeline Identity
+
+A timeline record contains timeline identity, source asset/stream revision, time base, origin, duration or open-duration state, discontinuity inventory, and mapping/version references.
+
+Required timeline kinds include source presentation, source decode, audio sample, proxy presentation, subtitle working, and delivery. Matching numeric ticks across different timeline identities MUST NOT imply the same instant.
+
+An origin shift, edit list, stream replacement, discontinuity repair, proxy regeneration, speed change, trim, or delivery conversion creates a new timeline or mapping revision when it changes interpretation.
+
+## CFR Frame Contract
+
+A CFR timeline declares exact frame rate `p/q` frames per second, presentation time base, first presentation instant, frame count or open count, and frame-boundary policy.
+
+- Frame index is zero-based within the exact timeline revision.
+- Frame `i` begins at the exact presentation instant derived from the timeline origin and `i * q/p` seconds.
+- A frame interval ends at the next frame start; the final interval uses declared media end when known.
+- Seeking by frame index MUST validate range and timeline revision.
+- Snapping MUST return the exact frame presentation timestamp and record the snap policy.
+- Decimal labels such as `29.97` or `23.98` MUST NOT replace exact rates such as `30000/1001` or `24000/1001`.
+
+## VFR Presentation Timestamp Index
+
+Each promoted VFR source stream MUST have a versioned `PresentationTimestampIndex` built from validated packet/frame presentation evidence.
+
+An entry contains:
+
+- Stable frame-observation identity.
+- Presentation sequence index.
+- Exact PTS and presentation time base.
+- Duration when known or derivable under the profile.
+- Keyframe/random-access classification where observed.
+- Source packet/frame provenance and integrity scope.
+- Discontinuity, duplicate, corruption, or uncertainty flags.
+
+After profile-defined normalization, valid presentation entries MUST be strictly ordered by presentation instant. Decode timestamp and decode order MAY differ and MUST remain separate fields.
+
+VFR detection MUST compare declared rates/durations with sampled or complete packet timing according to the promoted profile. A single container frame-rate field is insufficient authority.
+
+For a target presentation instant:
+
+- `containing-frame` selects the frame interval containing the instant.
+- `at-or-before` selects the latest PTS not later than the target.
+- `at-or-after` selects the earliest PTS not earlier than the target.
+- `nearest` compares exact rational distances; the default tie policy is earlier unless the operation profile states otherwise.
+
+Out-of-range, discontinuous, duplicate, corrupt, or uncertain regions MUST return structured outcomes and MUST NOT fall back silently to nominal FPS arithmetic.
+
+## Frame Reference
+
+A durable `FrameRef` contains source asset and stream revision, timeline revision, PTS-index revision, presentation sequence index, exact PTS, and optional frame integrity/provenance.
+
+Frame index alone is a UI/query convenience. Any committed annotation, cue snap, thumbnail, OCR observation, QC finding, or artifact MUST bind the full FrameRef or an exact TimePoint plus mapping revision.
+
+## Audio Sample Clock
+
+An audio timeline declares exact sample rate, channel-layout identity, first sample origin, sample count, discontinuities, and source stream revision.
+
+- Sample position is an integer in the declared sample clock.
+- Mapping between audio samples and presentation time uses exact rational conversion.
+- Playback synchronization MUST record which clock is master and the correction policy.
+- Resampling creates a new timeline/mapping revision and MUST record source/target rates, implementation identity, delay/trim, and rounding.
+- Waveform and analysis tiles MUST bind exact sample ranges and source revision.
+
+## Drop-Frame and Non-Drop-Frame Display
+
+Timecode is a presentation and interchange label over an exact timeline, not the authority for media time.
+
+- A timecode profile MUST declare nominal count rate, exact media rate, drop/non-drop rule, start label, 24-hour wrap policy, and negative-time policy.
+- Drop-frame numbering MUST skip labels according to the exact profile but MUST NOT skip media frames or alter PTS.
+- Parsing MUST reject impossible skipped labels and ambiguous profile-free strings.
+- Formatting followed by parsing under the same profile MUST resolve to the same frame reference within the profile's declared wrap scope.
+- Semicolon or punctuation alone MUST NOT establish a drop-frame profile.
+
+## Source-to-Proxy Mapping
+
+A `TimelineMap` binds exact source and target timeline revisions and contains ordered mapping segments or anchor pairs.
+
+Each segment MUST declare source interval, target interval, monotonic direction, mapping function/version, precision, discontinuity behavior, and validation result.
+
+- Mapping MUST be monotonic for ordinary proxies and MUST NOT invent a source instant.
+- CFR proxy generation from VFR source MUST preserve an inspectable source-to-proxy relation for every promoted profile.
+- Cue truth remains on the approved source timeline unless an explicit product decision says otherwise.
+- Proxy seeking/snapping MUST map back to source PTS before committing durable cue timing.
+- Regenerating a proxy creates a new proxy timeline and map revision; old references do not silently rebind.
+- Round-trip mapping error MUST be measured in the source time base and bounded by the exact verification profile in `VID-SPEC-015`.
+
+## Spatial Coordinate Space
+
+A `CoordinateSpace` contains identity, schema version, source asset/frame or render surface revision, origin convention, axis direction, orientation, pixel dimensions/aspect where observed, normalized scale, crop/aperture, and transform-chain reference.
+
+Durable normalized scalars use an integer numerator and positive declared scale. The Phase 00 canonical scale is profile-declared and MUST provide sufficient precision for the promoted corpus; binary floating-point values MUST NOT be durable authority.
+
+The canonical untransformed space uses:
+
+- Origin at the top-left of the declared presentation aperture.
+- Positive X to the right and positive Y downward.
+- Normalized bounds `[0,1]` represented by the declared integer scale.
+- Half-open rectangles `[x0,x1) x [y0,y1)` with `x0 < x1` and `y0 < y1`.
+
+Out-of-bounds regions MUST declare clipping, rejection, or intentional overscan policy. They MUST NOT be clamped silently.
+
+## Spatial Transforms
+
+A `SpatialTransform` records source and target coordinate-space revisions, transform kind/version, exact rational or fixed-point parameters, order, inverse availability, rounding policy, and provenance.
+
+Required transform kinds include crop/aperture, scale, rotation by supported orientation, translation, pixel-aspect correction, proxy mapping, and composition.
+
+- Transform order is significant and MUST be recorded.
+- Repeated UI edits SHOULD compose against the authoritative source transform rather than accumulate rounded screen coordinates.
+- A committed spatial annotation MUST bind its source coordinate space and exact transform chain.
+- Mapping to pixels MUST use an explicit edge/center convention and rounding policy.
+- Non-invertible, ambiguous, stale, or unsupported transforms MUST produce structured incompatibility.
+- OCR, tracking, masks, overlays, previews, and delivery rendering MUST share this coordinate foundation when they claim alignment.
+
+## Seeking, Snapping, and Editing
+
+Every seeking or snapping request MUST declare target timeline, source value/reference, policy, tolerance, lock/authority context, and expected mapping revision.
+
+- UI playhead movement MAY be approximate, but a committed edit MUST resolve to an exact source TimePoint or FrameRef.
+- Locked cue boundaries remain fixed unless an authorized override command is used.
+- Snapping to shot, word, waveform, cue, frame, or sample boundaries MUST identify the evidence revision and deterministic tie order.
+- If constraints cannot all be satisfied, the operation MUST preserve approved data and return a finding rather than choose an undocumented boundary.
+
+## Versioning and Migration
+
+Time bases, timelines, PTS indexes, mappings, coordinate spaces, transforms, and policies are independently versioned.
+
+- A mapping or index revision MUST NOT silently reinterpret existing references.
+- Migration MUST preserve the original authoritative values and record exact old/new mappings and conversion remainder.
+- A migration from floating-point or nominal-FPS legacy data MUST retain source values, uncertainty, selected rational interpretation, and review state.
+- Failed migration MUST leave the prior project readable.
+- Unknown required timing or coordinate extensions MUST block writes; optional durable extensions follow the Frozen Shared preservation policy.
+
+## Security and Resource Bounds
+
+- Timestamp/index counts, rational components, durations, transform chains, dimensions, and nesting MUST be bounded before allocation or multiplication.
+- Arithmetic overflow, division by zero, invalid normalization, NaN/infinity import, and pathological transform expansion MUST fail closed.
+- PTS index and mapping builders MUST run within hostile-ingest worker boundaries defined by `VID-SPEC-004`.
+- Imported timecode, subtitle timing, edit lists, metadata, dimensions, and transforms are untrusted.
+- Diagnostic output MUST avoid unrestricted media paths or payloads and include trace identity.
+
+## Conformance Gates
+
+| Gate | Requirement |
+| --- | --- |
+| `VID-TIME-001` | Rational time bases normalize and reject invalid or overflowing values. |
+| `VID-TIME-002` | Exact conversion and named rounding produce deterministic results without cumulative drift. |
+| `VID-TIME-003` | Half-open range behavior is consistent across boundaries and adapters. |
+| `VID-TIME-004` | CFR frame references use exact rational rates and timeline revisions. |
+| `VID-TIME-005` | VFR seeking/snapping uses validated PTS indexes and never nominal FPS fallback. |
+| `VID-TIME-006` | Audio sample mapping is exact and records resampling/correction policy. |
+| `VID-TIME-007` | Drop-frame/non-drop labels round-trip under an exact profile without changing media time. |
+| `VID-TIME-008` | Source/proxy maps are versioned, monotonic, inspectable, and source-authoritative. |
+| `VID-SPATIAL-001` | Normalized coordinate spaces have exact identity, bounds, orientation, and aperture. |
+| `VID-SPATIAL-002` | Transform chains preserve order, precision, revision, and explicit rounding. |
+| `VID-SPATIAL-003` | Stale, ambiguous, non-invertible, corrupt, or out-of-bounds inputs fail safely. |
+| `VID-TIME-009` | Long-media, mixed-timebase, discontinuity, overflow, migration, and recovery fixtures preserve approved timing. |
+
+## Fixture Families
+
+Specification-freeze evidence MUST include synthetic, redistributable fixtures for:
+
+1. Reduced/unreduced/invalid rationals, negative times, large products, and overflow.
+2. Exact and fractional conversions under every required rounding policy.
+3. CFR `24/1`, `25/1`, `30/1`, `24000/1001`, `30000/1001`, and `60000/1001` timelines.
+4. VFR PTS sequences with irregular duration, long tails, discontinuities, duplicates, corruption, and metadata disagreement.
+5. Drop-frame and non-drop-frame valid, skipped, ambiguous, negative, and wrap labels.
+6. Audio sample clocks, resampling delay, drift correction, and waveform ranges.
+7. VFR-source/CFR-proxy mappings, regenerated proxies, stale maps, and round-trip error.
+8. Normalized regions, crop, scale, orientation, pixel aspect, transform order, clipping, and non-invertible transforms.
+
+Native decoder, seek, playback, proxy generation, audio, render, long-media, and fault-injection evidence remains downstream `VID-SPEC-015` scope.
+
+## Freeze Criteria
+
+This specification may become Frozen when:
+
+1. `VID-SPEC-002@1.0` remains Frozen.
+2. Every time, frame, sample, display, mapping, coordinate, and transform identity and policy is exact.
+3. Synthetic fixtures cover deterministic positive, negative, boundary, long-range, discontinuity, overflow, stale, and migration cases.
+4. Video, QA/compatibility, performance/hardware, and Security reviews approve the exact candidate.
+5. Evidence does not represent reference arithmetic as native media or playback conformance.
+6. No unresolved issue can change durable timing, frame, mapping, or coordinate meaning.
+
+## Downstream Ownership
+
+- `VID-SPEC-004` owns hostile media parser/decoder isolation and limits.
+- `VID-SPEC-005` owns timing-editor behavior, deterministic timing policies, locks, and QC.
+- `VID-SPEC-006` owns format-specific precision, conversion loss, and delivery profiles.
+- `VID-SPEC-015` owns native VFR/long-media/proxy/playback/recovery corpus and thresholds.
+
+## Planning References
+
+- [Core Architecture](../Plan/sections/04_CORE_ARCHITECTURE.md)
+- [Release Gates](../Plan/sections/10_RELEASE_GATES.md)
+- [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
diff --git a/specifications/VID-SPEC-004-media-ingest-threat-model.md b/specifications/VID-SPEC-004-media-ingest-threat-model.md
new file mode 100644
index 0000000..f0a8657
--- /dev/null
+++ b/specifications/VID-SPEC-004-media-ingest-threat-model.md
@@ -0,0 +1,214 @@
+# VID-SPEC-004 - Hostile Media-Ingest Threat Model
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-004` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner role | Security Lead |
+| Required reviewers | Product Lead: Video; Domain Architect: Video; QA and Compatibility Lead; Legal and Licensing Steward; Release Engineering Lead |
+| Blocker | `P0:V0` |
+| Depends on | `SUI-SPEC-008@1.0` Frozen |
+| Authority | `OWNER-AUTH-V2` |
+
+Only a Frozen revision satisfies `P0:V0`. This specification freezes planning controls and adversarial evidence requirements. It does not claim that a parser, decoder, sandbox, codec, format, font, optical-disc path, or media worker has been implemented or promoted.
+
+## Normative Language
+
+`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` express requirement strength. Tables named Required Baseline, Security Limits, Threat Model, Failure Contract, and Security Gates are normative.
+
+## Scope
+
+All source media and sidecars are untrusted, including local files, removable media, network-share copies, archives, project packages, subtitle files and streams, attachments, fonts, playlists, optical-disc folders/images, metadata, thumbnails, proxies, and outputs from external tools.
+
+This specification owns Video-specific probe, demux, decode, subtitle/font parse, optical-disc parse, waveform/thumbnail/proxy generation, conversion, and artifact-publication threats. `SUI-SPEC-008` owns generic package, IPC, worker, credential, logging, update, rights, and Shared-store controls. `VID-SPEC-006` and `VID-DEC-003` own exact promoted components and support claims.
+
+Phase 00 is internal/private Windows 11 x64, per-user, offline-capable, and CPU-functional. DRM, encryption, region control, copy protection, access control, and signature protection are never bypassed. Protected media is unsupported unless the user provides a lawfully accessible unprotected source.
+
+## Security Objectives
+
+- Hostile input cannot mutate project truth, Shared metadata, credentials, application binaries, or unrelated files.
+- Parser, decoder, font, subtitle, and media-tool failure remains inside the declared worker and job boundary.
+- Input identity, source timebase, stream inventory, selected operation, component identity, limits, and result lineage remain inspectable.
+- Unsupported, suspicious, partial, stale, or malformed output cannot become an approved artifact.
+- Cancellation, timeout, crash, disk pressure, and application restart fail safely and preserve acknowledged commands.
+- No file name, metadata field, subtitle payload, filter expression, or user path becomes an ad-hoc shell command.
+
+## Trust Boundaries
+
+1. File or source selection enters bounded preflight before any complex parser.
+2. The Video core issues a typed operation to an isolated media worker through the accepted Shared IPC profile.
+3. The worker receives only leased immutable inputs and an ACL-scoped unique output directory.
+4. Complex libraries parse inside the worker; they receive no project-database, Shared-store, credential, home-directory, or ambient network authority.
+5. Worker output crosses schema, size, identity, provenance, and semantic validation before atomic publication.
+6. Product command handlers, never the worker, decide whether a validated observation or artifact affects project revisions.
+
+## Required Baseline
+
+### Identification and Preflight
+
+- Identify candidates from bounded signature and structure checks; file extensions are hints only.
+- Canonicalize paths through platform path APIs. Reject device paths, alternate data streams, parent traversal, absolute archive members, links, duplicate normalized names, case collisions, null bytes, and names outside the declared tree.
+- Record source size, full identity when required, fast-fingerprint provenance, selected operation, and limits before complex parsing.
+- A network location is copied or imported into a managed local staging area before authoritative processing. Workers MUST NOT open unmanaged network-share paths directly.
+- Unknown, ambiguous, encrypted, protected, unsupported, or over-limit input returns a structured disposition before allocation or publication.
+
+### Worker Isolation
+
+- Probe, demux, decode, subtitle parsing, font inspection/shaping, optical-disc parsing, conversion, mux, burn-in, and attachment extraction run outside the UI process.
+- The Windows base worker uses a restricted token, Job Object, explicit process/memory/time/output limits, dedicated ACL-scoped directories, inherited-handle allowlisting, and network denied by default.
+- Workers MUST NOT receive project-store or Shared-store write handles. They publish typed candidate results only to dedicated staging.
+- Child processes are denied unless the exact operation profile declares an executable identity, argument adapter, inherited-handle set, and equivalent containment.
+- Process separation is crash containment, not a complete sandbox claim. Promotion requires platform enforcement evidence.
+
+### Protocol, URL, and Command Safety
+
+- Disable automatic external URL resolution, network protocols, scripts, external entities, unsafe media-tool protocol handlers, device capture, and implicit playlist fetching.
+- Permit only local file and brokered-handle inputs in the Phase 00 profile.
+- Keep paths, options, filters, and arguments structured until one reviewed adapter invokes a process or API. Shell invocation and concatenated command strings are prohibited.
+- Subtitle markup, ASS/SSA commands, attachment names, font names, XML, chapters, metadata, and playlists are data; they cannot invoke code, network, filesystem, or environment expansion.
+
+### Output and Publication
+
+- Write each operation into a unique non-authoritative staging directory and enforce declared file count, byte, type, and name bounds.
+- Flush and close candidate output, validate exact expected artifacts, compute identities, and attach source/operation/component lineage before atomic publication.
+- Partial, unexpected, executable, stale-epoch, mismatched-source, over-limit, or schema-invalid output is quarantined or deleted according to evidence policy and never registered as complete.
+- Cache deletion, worker death, and quarantine cannot delete project truth or the original user source.
+
+## Security Limits
+
+These are hard preflight and containment ceilings for the Phase 00 profile, not support claims. A lower operation-specific limit MAY apply. Exceeding a ceiling returns `VID-INGEST-LIMIT` or `VID-INGEST-REVIEW`; it never authorizes an unbounded attempt.
+
+| Resource | Phase 00 ceiling |
+| --- | --- |
+| Source object size | 2 TiB per selected object |
+| Probe wall time / worker CPU time | 60 s / 60 s |
+| Probe resident memory | 1 GiB |
+| Decode/conversion worker resident memory | 4 GiB or 50% of physical RAM, whichever is lower, with a 1 GiB minimum supported host requirement |
+| Worker process count | 1 worker plus 2 declared child processes per operation |
+| Control envelope | 1 MiB before decode; bulk media prohibited |
+| Streams / programs / chapters / playlist entries | 256 / 256 / 100,000 / 100,000 |
+| Attachments | 128 files, 64 MiB each, 512 MiB expanded total |
+| Metadata | 64 MiB aggregate; 1 MiB per scalar field |
+| Subtitle cues / cue text / markup depth | 10,000,000 / 1 MiB UTF-8 per cue / 64 levels |
+| Frame geometry | 32,768 per dimension and 268,435,456 pixels per frame |
+| Audio | 768 kHz, 128 channels, validated sample-count arithmetic |
+| Declared duration | 1,000 hours; longer sources require a reviewed profile |
+| Archive/structured-source depth and entries | 3 nested layers and 100,000 entries |
+| Expanded staging output | min(2 TiB, declared estimate plus 10%, available disk minus 10 GiB recovery reserve) |
+| Cancellation grace / forced termination | 2 s cooperative / 5 s total |
+
+Every count, byte size, timestamp, stride, sample, frame, allocation, and multiplication MUST use checked arithmetic before allocation. VFR indexes use bounded disk-backed structures and source presentation timestamps; they MUST NOT derive durable time from nominal FPS.
+
+## Input-Class Policies
+
+### Containers, Streams, and Timing
+
+- Validate stream counts, packet sizes, timestamp domains, rational denominators, ordering, discontinuities, wrap, negative values, and arithmetic before indexing.
+- Preserve source evidence when timestamps are malformed or contradictory; do not silently repair durable time.
+- Unsupported codecs or profiles fail as unsupported, not corrupt, unless structural evidence is invalid.
+
+### Timed Text, Attachments, and Fonts
+
+- Decode text with a declared or reviewable encoding result and bounded replacement policy; invalid sequences cannot become command text.
+- Parse ASS/SSA overrides, karaoke, drawing, style, attachment, and font references with bounded grammar depth and payload size.
+- Inspect fonts in the worker. Reject malformed tables, extreme counts, embedded executable content, unsafe external references, and rights-unknown embedding or redistribution.
+- Font availability and license acceptance do not imply permission to package, transfer, embed, or redistribute.
+
+### Optical Disc and Structured Sources
+
+- Treat DVD/BDMV playlists, navigation data, angles, chapters, seamless branches, and referenced paths as hostile structured input.
+- Resolve references only inside the selected source root after canonicalization and cycle/depth checks.
+- Detect encryption/protection and return `VID-INGEST-PROTECTED`; do not invoke, recommend, bundle, or automate circumvention.
+
+## Threat Model
+
+| Threat ID | Abuse path | Impact | Required prevention | Detection/evidence | Priority |
+| --- | --- | --- | --- | --- | --- |
+| VID-MEDIA-THR-001 | Crafted container exploits probe/demux/decoder | Worker escape or code execution | Isolated restricted worker, pinned component, bounded probe, no ambient authority | Crash fingerprint, sandbox denial, component/build identity, fuzz regression | Critical |
+| VID-MEDIA-THR-002 | Playlist, subtitle, metadata, or tool resolves external protocol | SSRF, disclosure, remote content substitution | Network deny, protocol allowlist limited to local handles, no automatic resolution | Network canary and denied-destination audit | Critical |
+| VID-MEDIA-THR-003 | Archive, attachment, font, or disc path escapes staging | File overwrite, persistence, unrelated data access | Canonical relative paths, no links/device paths, unique ACL staging | Escape canaries, rejected-member inventory | Critical |
+| VID-MEDIA-THR-004 | Extreme dimensions, counts, duration, nesting, compression, or output | CPU/RAM/GPU/disk exhaustion | Preflight ceilings, checked arithmetic, Job Object, quotas, recovery reserve | Limit code, peak resource record, no partial publication | High |
+| VID-MEDIA-THR-005 | Malformed timestamp, rational, packet order, or wrap poisons index | Cue drift, overflow, corrupt durable timing | Exact rational validation, checked arithmetic, source PTS authority, no nominal VFR fallback | Timing anomaly report and retained source evidence | High |
+| VID-MEDIA-THR-006 | ASS/SSA, XML, font, or attachment invokes script/external behavior | Code execution, network/file access | Data-only parsers, external entity/script disablement, worker inspection | Grammar/fuzz corpus and forbidden-handler audit | Critical |
+| VID-MEDIA-THR-007 | Protected optical media triggers circumvention path | Legal/security exposure | Detect and reject protection; no decryption tool or invocation | Structured protected-media disposition | High |
+| VID-MEDIA-THR-008 | Worker writes project/store or inherits excessive handles | Durable corruption or credential theft | No write handles, handle allowlist, restricted token, product command authority | Handle/access canaries and mutation audit | Critical |
+| VID-MEDIA-THR-009 | Crash/cancel races publish partial or stale output | Corrupt cache or false-complete artifact | Unique staging, epoch/source binding, flush/validate/hash/atomic publish | Quarantine manifest and startup reconciliation | High |
+| VID-MEDIA-THR-010 | Path or metadata reaches shell/filter command construction | Command injection | Structured APIs and reviewed argument adapter; shell prohibited | Metacharacter corpus and invocation audit | Critical |
+| VID-MEDIA-THR-011 | Hostile text reaches logs, UI, or diagnostics | Secret/path disclosure or log injection | Structured bounded escaping and Shared redaction policy | Control-character/canary regression | High |
+| VID-MEDIA-THR-012 | Result identity or lineage is substituted across jobs | Wrong source artifact accepted | Job epoch, source hash, operation version, component identity, typed result validation | Mismatch rejection and provenance audit | High |
+
+## Failure Contract
+
+| Code | Disposition | Durable effect | User recovery |
+| --- | --- | --- | --- |
+| `VID-INGEST-UNSUPPORTED` | Reject without quarantine | None | Select a promoted profile or convert externally |
+| `VID-INGEST-MALFORMED` | Reject and retain bounded evidence | None | Inspect details or use another source |
+| `VID-INGEST-LIMIT` | Terminate safely | None | Use a reviewed lower-cost operation/profile |
+| `VID-INGEST-PROTECTED` | Reject | None | Provide a lawful unprotected source |
+| `VID-INGEST-QUARANTINED` | Isolate candidate output | None | Review evidence; never open automatically |
+| `VID-INGEST-CANCELLED` | Remove or quarantine partial output | None | Retry explicitly |
+| `VID-INGEST-WORKER-FAILED` | Reconcile job and staging | None | Retry, Safe Mode, or alternate promoted component |
+| `VID-INGEST-IDENTITY-MISMATCH` | Reject stale/substituted output | None | Re-probe the current source |
+| `VID-INGEST-REVIEW` | No complex operation starts | None | Owner/operator approves a registered profile |
+
+Errors include stable code, operation ID/version, job/trace ID, bounded source identity, failed gate, component/build identity when invoked, retry class, and safe recovery actions. Raw untrusted strings are escaped and bounded.
+
+## Fuzzing and Adversarial Intake
+
+- Maintain redistributable synthetic seeds for containers, streams, timestamps, subtitles, fonts, attachments, playlists, disc structures, paths, protocols, worker faults, and publication races.
+- Keep private customer media and licensed payloads out of the repository corpus.
+- Every promoted parser/decoder adapter supplies coverage-guided fuzz targets for its externally reachable grammar and structured output validator.
+- Run sanitizers or equivalent memory/undefined-behavior checks where the component/toolchain supports them.
+- Deduplicate crashes by component, build, stack/signature, input hash, operation, and sandbox profile; minimize only in an isolated workspace.
+- A security fix adds a non-sensitive regression fixture or a reproducible generator before promotion resumes.
+
+## Incident Response
+
+1. Quarantine the input, candidate output, component profile, and affected cache entries without deleting project truth.
+2. Record hashes, operation, component/build, worker profile, limits, crash and sandbox evidence, and affected versions.
+3. Disable the exact operation/profile through signed or local emergency policy while preserving manual editing and read-only recovery.
+4. Determine whether project/store mutation, credential access, network access, or filesystem escape occurred.
+5. Patch, replace, or revoke the component; add corpus coverage and rerun the full promoted profile.
+6. Restore promotion only through reviewed evidence and notify affected release/product owners.
+
+## Security Gates
+
+| Gate ID | Freeze requirement | Implementation evidence owner |
+| --- | --- | --- |
+| VID-MEDIA-SEC-001 | Trust boundaries and product/Shared ownership are exact | Security Lead |
+| VID-MEDIA-SEC-002 | Signature preflight and structured path policy are exact | Domain Architect: Video |
+| VID-MEDIA-SEC-003 | Windows worker authority and denied capabilities are exact | Security Lead |
+| VID-MEDIA-SEC-004 | Protocol, URL, script, entity, and shell defaults deny unsafe behavior | Security Lead |
+| VID-MEDIA-SEC-005 | Phase 00 security ceilings and checked arithmetic are exact | QA and Compatibility Lead |
+| VID-MEDIA-SEC-006 | VFR/timebase hostile-input behavior preserves `VID-SPEC-003` | Domain Architect: Video |
+| VID-MEDIA-SEC-007 | Subtitle, attachment, font, and optical-disc policies are exact | Security Lead |
+| VID-MEDIA-SEC-008 | Staging, quarantine, atomic publication, and reconciliation are exact | QA and Compatibility Lead |
+| VID-MEDIA-SEC-009 | Stable failure codes and safe recovery actions are exact | Product Lead: Video |
+| VID-MEDIA-SEC-010 | Fuzz/adversarial intake and private-asset exclusion are exact | QA and Compatibility Lead |
+| VID-MEDIA-SEC-011 | Incident response, disablement, revocation, and regression rules are exact | Security Lead |
+| VID-MEDIA-SEC-012 | Rights/protection rules make no circumvention or redistribution claim | Legal and Licensing Steward |
+
+Freeze evidence proves the planning contract and synthetic fixture coverage only. Runtime promotion requires exact component/build, OS, architecture, sandbox, operation, corpus, resource, crash, malformed-input, cancellation, disk-full, quarantine, and recovery evidence. Official distribution additionally requires approved dependency, license, patent, notice, SBOM, signing, update, and source-publication records.
+
+## Acceptance Record
+
+| Role | Status | Required evidence |
+| --- | --- | --- |
+| Security Lead | Accepted | Threat/control/gate completeness |
+| Product Lead: Video | Accepted | Scope and recovery behavior |
+| Domain Architect: Video | Accepted by owner-authorized consolidated review | Timebase, command authority, and artifact boundaries |
+| QA and Compatibility Lead | Accepted by owner-authorized consolidated review | Limits, corpus, negative outcomes, and promotion evidence |
+| Legal and Licensing Steward | Accepted by owner-authorized consolidated review | Confirms no circumvention or redistribution approval; source publication remains gated |
+| Release Engineering Lead | Accepted by owner-authorized consolidated review | Component promotion remains release-gated |
+
+This acceptance is an owner-authorized consolidated self-review under `OWNER-AUTH-V2`; it is not independent review and does not substitute for runtime security evidence.
+
+## References
+
+- [Shared Security and Privacy Threat Model](../../_shared/specifications/SUI-SPEC-008-shared-threat-model.md)
+- [Video Core Architecture](../Plan/sections/04_CORE_ARCHITECTURE.md)
+- [Format and Feature Expansion](../Plan/sections/09A_FORMAT_AUDIO_HARDWARE_AND_FEATURE_EXPANSION.md)
+- [Source and Governance](../Plan/sections/01_SOURCE_AND_GOVERNANCE.md)
+- [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
+- [Timebase and Coordinates](VID-SPEC-003-timebase-and-coordinates.md)
diff --git a/specifications/VID-SPEC-014-design-and-localization.md b/specifications/VID-SPEC-014-design-and-localization.md
new file mode 100644
index 0000000..49e8ce7
--- /dev/null
+++ b/specifications/VID-SPEC-014-design-and-localization.md
@@ -0,0 +1,181 @@
+# VID-SPEC-014 - Video Design and Localization Contract
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-014` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner role | UX and Design Lead |
+| Required reviewers | Product Lead: Video; Accessibility Lead; Internationalization Lead; Thai Language Quality Reviewer; QA and Compatibility Lead |
+| Blocker | `P0:V0`; `Phase:V1` |
+| Depends on | `SUI-SPEC-007@1.0` Frozen; `VID-DEC-004@1.0` Accepted |
+| Authority | `OWNER-AUTH-V2` |
+
+Only a Frozen revision satisfies `P0:V0`. This contract freezes design tokens, component/workspace obligations, localization catalogs, terminology, and test matrices. It does not claim that native controls, screenshots, fonts, IME behavior, assistive technology, performance, or production UI have passed implementation conformance.
+
+## Normative Language
+
+`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` express requirement strength. The referenced machine-readable token, component, catalog, glossary, and fixture assets are normative parts of this revision.
+
+## Frozen Assets
+
+| Asset | Authority |
+| --- | --- |
+| [Video tokens](../design/tokens/vid.tokens.json) | Primitive, semantic, and component token values and mappings |
+| [Component matrix](../design/component-matrix.json) | Workspace allocation, component states, density, keyboard, accessibility, and performance obligations |
+| [English source catalog](../localization/en.json) | Stable source messages and translator context |
+| [Thai catalog](../localization/th.json) | First official translated catalog and English fallback binding |
+| [English/Thai terminology](../localization/terminology.en-th.json) | Approved recurring application terms and context |
+| [Design/localization cases](../testing/conformance/vid-spec-014/fixtures/design-localization-cases.json) | Synthetic matrix for theme, locale, placeholders, Thai, bidi, accessibility, IME, and layout |
+
+## Visual Direction
+
+The product implements **Cinematic Ledger** from `VID-DEC-004@1.0`: warm-neutral graphite structure, paper-white Light surfaces, deep charcoal Dark surfaces, restrained amber action cues, technical rulers, compact editorial geometry, and a neutral-dark media stage in every theme.
+
+- Amber identifies primary action, playhead, active edit focus, and selected timing affordances. It MUST NOT carry warning or error meaning.
+- Success, warning, error, information, collaboration presence, review state, and track identity use separate semantic roles and a non-color signal.
+- Heavy glass, continuous blur, neon glow, decorative gradients, ornamental animation, and card-grid substitution for technical tables/timelines are prohibited.
+- System is the default theme and follows live OS changes. Light and Dark are equal first-class themes.
+- Theme, locale, density, and UI scale are independent preferences.
+
+## Token Architecture
+
+- Resolve tokens in the order Primitive -> Semantic -> Component. Production components MUST NOT read primitive colors directly.
+- Product semantic tokens MAY specialize timeline, waveform, subtitle, review, collaboration, media-stage, and delivery roles without redefining Shared accessibility semantics.
+- A component token references semantic roles; hard-coded production colors, spacing, radius, typography, or motion durations are prohibited.
+- The media-stage token resolves to the same neutral-dark value in Light and Dark. Subtitle preview colors come from subtitle style/project data, not application chrome.
+- Compact and Comfortable density share component implementations and differ only through tokens.
+- Normal chrome motion is 120-180 ms. Playback, scrubbing, dragging, trimming, cue stepping, waveform navigation, and keyboard timing MUST NOT wait for animation.
+- Reduced motion removes nonessential transitions while preserving state communication.
+
+### Required Contrast
+
+- Normal text and essential icon glyphs require at least 4.5:1 against their background.
+- Large text, focus indicators, control boundaries, selected outlines, timeline marks, and meaningful non-text graphics require at least 3:1 against adjacent colors.
+- Disabled state MAY use lower contrast only when it remains distinguishable and is not the only way to discover required information.
+- High Contrast mode uses system colors and preserves semantic labels; it does not attempt to retain brand colors.
+
+## Typography
+
+- `font.ui` is a logical broad-script humanist sans role; `font.timecode` is a logical tabular monospaced numeric role. A concrete payload requires separate source, license, shaping, and redistribution evidence.
+- UI line metrics MUST preserve Thai combining marks above and below the nominal Latin box.
+- Timecode, frames, samples, durations, and measurements use tabular numerals without forcing prose into monospaced typography.
+- Subtitle preview uses the actual selected subtitle font, fallback, shaping, style, and layout semantics rather than UI typography.
+- Thai, CJK, RTL, mixed script, emoji, combining sequences, and technical identifiers remain readable and do not corrupt stable IDs.
+
+## Workspace Contract
+
+| Workspace | Primary purpose | Required Phase 00 surface | Phase 01 completion |
+| --- | --- | --- | --- |
+| Edit | Source/target cue authoring | Video stage, cue list, inspector, transport | Full editing and source/target track tools |
+| Timing | Frame-accurate cue timing | Timeline, playhead, waveform placeholder/tiles, transport | Scrub, frame step, loop, trim, snap, timing findings |
+| Translation | Source/target language work | Stable shell route and empty/loading/error states | Translation grid, terminology, context, revision states |
+| Review | Findings and approval | Stable shell route and finding states | QC/review queues, comparison, comments, approvals |
+| Delivery | Validation and export | SRT export route, progress, warning, error, success | Profiles, conversion loss, reports, promoted outputs |
+
+The Phase 00 shell MUST expose all five routes so navigation and state contracts do not fork later, but only the allocated surfaces may claim workflow completion.
+
+## Component Contract
+
+- Every applicable component implements default, hover, pressed, selected, focused, disabled, busy, empty, warning, error, success, and conflict states from the component matrix.
+- Keyboard order follows task order and remains stable across translated string length. All primary commands are reachable without a pointer.
+- Focus remains visible against both chrome and media stage. Modal focus is contained and returns to the invoking control.
+- Cue list, timeline, waveform, findings, jobs, and long selectors use bounded/virtualized rendering; one native element per project object is prohibited at scale.
+- Icon-only controls require localized accessible names and visible tooltips where hover exists. Icons cannot be the only indicator of destructive or safety-critical action.
+- Destructive, irreversible, conversion-loss, rights-review, and security-warning actions use explicit text and confirmation proportional to consequence.
+
+## Application Localization
+
+### Language Identity and Fallback
+
+- Initial application choices are System Default, English (`en`), and Thai (`th`). System Default resolves through OS language preferences to a supported catalog, then English.
+- Durable language identity uses canonical BCP 47 tags independent from localized display names.
+- Thai fallback is `th-TH -> th -> en`; unsupported or damaged application catalogs fall back to English.
+- Application language is independent from project source language, project target languages, provider language support, subtitle language, and locale-neutral protocol data.
+- Switching language MUST preserve open project, workspace, layout, focus target where valid, selection, playhead, undo history, unsaved state, and job identities.
+
+### Catalog Rules
+
+- All production user-facing text, accessibility labels, announcements, errors, recovery instructions, warnings, and notifications use stable message keys.
+- English is the canonical source and fallback catalog. Thai is the first official translated catalog.
+- Catalog entries carry translator context. Source meaning changes invalidate the corresponding translation review.
+- Named placeholders MUST match exactly across catalogs. Sentence fragments, positional placeholder assumptions, and concatenated translated clauses are prohibited.
+- Technical IDs, paths, model IDs, codec names, shortcut chords, and timecodes remain typed values outside localized prose.
+- Missing, extra, obsolete, duplicate, empty, malformed, or placeholder-incompatible entries fail catalog validation.
+- Plugins and providers use namespaced catalogs and fall back to English; untrusted localized markup cannot enter privileged UI.
+
+### Locale Behavior
+
+- Durable timestamps, decimals, hashes, manifests, logs, protocols, and timebase values remain locale-independent.
+- Dates, ordinary numbers, percentages, and file sizes use selected locale formatting where technical clarity is preserved.
+- Media timecode and frame display follow project media rules, not calendar locale. Users MAY select Latin digits for technical fields in Thai UI.
+- User-facing sorting MAY use locale collation; stable IDs and technical fields use deterministic ordinal rules.
+
+## Thai and Complex-Script Contract
+
+- Thai layout uses script-aware line breaking and MUST NOT assume whitespace word boundaries.
+- UI controls preserve combining marks, line height, cursor movement, selection, deletion, search, and accessible names.
+- Keystrokes consumed by an active IME composition MUST NOT invoke editor commands.
+- Truncation is allowed only when the complete localized value is available through tooltip, inspector, or accessibility text.
+- Subtitle-language counting, segmentation, shaped width, wrapping, and preview/delivery parity remain product Language Core behavior; UI translation rules cannot replace them.
+- Thai subtitle character metrics exclude non-spacing marks and non-visible markup only according to the approved Language Core policy; no hard-coded Thai character list is authoritative.
+
+## Accessibility
+
+- Meet Shared keyboard, focus, contrast, target-size, semantics, high-contrast, reduced-motion, zoom/scale, and assistive-technology contracts.
+- Pair every color state with text, icon, shape, pattern, position, or accessible state.
+- Localize accessible names, descriptions, errors, progress, loading, and live announcements while preserving stable automation identity.
+- At 200% UI scale, critical Phase 00 flows remain operable without two-dimensional page scrolling; technical timeline horizontal scrolling remains task content, not page failure.
+- Text reflow and translation expansion cannot hide safety actions, validation details, or current state.
+
+## Test and Evidence Matrix
+
+Planning conformance validates assets and synthetic cases. Implementation evidence additionally covers:
+
+1. System/Light/Dark x English/Thai x Compact/Comfortable at supported scale factors.
+2. High Contrast and reduced motion.
+3. Keyboard-only traversal, focus visibility/return, screen-reader names/roles/states, and live announcements.
+4. Thai combining marks, native IME composition, cursor/selection/deletion, mixed Thai/Latin/digits/CJK, RTL isolation, and pseudo-localization.
+5. Theme and language switching with project, workspace, focus, selection, playhead, undo, unsaved state, and jobs preserved.
+6. Component states and five workspace routes, including empty/loading/error/offline/limited/safe-mode conditions.
+7. Baseline UI frame pacing and virtualization under the exact `VID-SPEC-015` environment.
+8. Preview-versus-delivery shaping only when the relevant font/render/delivery profile is promoted.
+
+## Conformance Gates
+
+| Gate ID | Freeze requirement |
+| --- | --- |
+| VID-DESIGN-001 | Three-layer tokens and exact theme mappings are machine-readable |
+| VID-DESIGN-002 | Required contrast pairs pass 4.5:1 or 3:1 thresholds |
+| VID-DESIGN-003 | Cinematic Ledger semantic separation and prohibited effects are explicit |
+| VID-DESIGN-004 | Five workspaces and Phase 00/Phase 01 allocation are exact |
+| VID-DESIGN-005 | Component states, keyboard, focus, density, and virtualization obligations are exact |
+| VID-DESIGN-006 | English and Thai catalogs have identical keys and placeholders |
+| VID-DESIGN-007 | Terminology includes stable English, approved Thai, and context |
+| VID-DESIGN-008 | BCP 47, System Default, fallback, and language-separation rules are exact |
+| VID-DESIGN-009 | Thai, CJK, RTL, mixed-script, and IME obligations are exact |
+| VID-DESIGN-010 | Accessibility, High Contrast, reduced motion, and 200% scale obligations are exact |
+| VID-DESIGN-011 | Theme/locale/density/scale state-preservation matrix is exact |
+| VID-DESIGN-012 | Freeze evidence is separated from rendered/native/performance evidence |
+
+## Acceptance Record
+
+| Role | Status | Required evidence |
+| --- | --- | --- |
+| UX and Design Lead | Accepted | Direction, token, workspace, and component completeness |
+| Product Lead: Video | Accepted | Phase allocation and workflow state preservation |
+| Accessibility Lead | Accepted by owner-authorized consolidated review | Contrast, keyboard, focus, semantics, scale, motion, and High Contrast |
+| Internationalization Lead | Accepted by owner-authorized consolidated review | Catalog, BCP 47, fallback, placeholders, locale separation, and complex scripts |
+| Thai Language Quality Reviewer | Accepted by owner-authorized consolidated review | Thai terminology, combining marks, layout, fallback, and IME obligations |
+| QA and Compatibility Lead | Accepted by owner-authorized consolidated review | Machine-readable assets, matrix coverage, and evidence boundaries |
+
+This acceptance is an owner-authorized consolidated self-review under `OWNER-AUTH-V2`; it is not independent review. Rendered/native/performance evidence remains downstream.
+
+## References
+
+- [Shared UI/i18n/accessibility contract](../../_shared/specifications/SUI-SPEC-007-ui-i18n-accessibility.md)
+- [Video visual direction](../decisions/VID-DEC-004-visual-direction.md)
+- [UI/UX and Design System](../Plan/sections/03A_UI_UX_AND_DESIGN_SYSTEM.md)
+- [Internationalization and Localization](../Plan/sections/03B_INTERNATIONALIZATION_AND_LOCALIZATION.md)
+- [Foundation PRD](VID-SPEC-001-foundation-prd.md)
+- [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
diff --git a/specifications/VID-SPEC-015-foundation-verification.md b/specifications/VID-SPEC-015-foundation-verification.md
new file mode 100644
index 0000000..745d2fb
--- /dev/null
+++ b/specifications/VID-SPEC-015-foundation-verification.md
@@ -0,0 +1,170 @@
+# VID-SPEC-015 - Foundation Verification Contract
+
+| Field | Value |
+| --- | --- |
+| Specification ID | `VID-SPEC-015` |
+| Status | Frozen |
+| Revision | `1.0` |
+| Owner role | QA and Compatibility Lead |
+| Required reviewers | Product Lead: Video; Domain Architect: Video; Security Lead; Accessibility Lead; Internationalization Lead; Release Engineering Lead |
+| Blocker | `P0:V0`; `Exit:V0` |
+| Depends on | `VID-SPEC-001@1.0`; `VID-SPEC-002@1.0`; `VID-SPEC-003@1.0`; `VID-SPEC-004@1.0`; `VID-SPEC-014@1.0` Frozen |
+| Authority | `OWNER-AUTH-V2` |
+
+Only a Frozen revision satisfies the Phase 00 entry requirement. Freezing this contract fixes profiles, thresholds, corpus governance, procedures, and required evidence; it does not satisfy `Exit:V0`. Exit requires runtime evidence from the implementation under the exact frozen profiles.
+
+## Normative Language
+
+`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` express requirement strength. The referenced machine-readable environment, budget, corpus, and fault assets are normative.
+
+## Evidence Levels
+
+| Level | Meaning | Phase effect |
+| --- | --- | --- |
+| `Entry` | Contract, fixture identity/generator, expected outcome, environment, metric, threshold, and evidence path are frozen | Opens implementation only after the complete P0 audit passes |
+| `Implemented` | A bounded implementation exists and produces raw evidence | Does not satisfy Phase 00 exit alone |
+| `Verified` | Reproducible run passes the exact profile and binds code/build/config/input/output hashes | May satisfy an individual exit gate |
+| `Exit` | Every required gate passes, no invalidating blocker remains, and the exit manifest is approved | Permits Phase 00 completion |
+
+Synthetic planning checks MUST NOT be labeled runtime verification. A missing run, unsupported environment, skipped case, flaky result, indirect proxy, or stale build is not a pass.
+
+## Frozen Assets
+
+| Asset | Purpose |
+| --- | --- |
+| [Environment matrix](../testing/conformance/vid-spec-015/profiles/environment-matrix.json) | OS, architecture, hardware, storage, locale, theme, density, and support tiers |
+| [Foundation budgets](../testing/conformance/vid-spec-015/profiles/foundation-budgets.json) | Metrics, percentile/statistic, workload, threshold, and priority |
+| [Corpus manifest](../testing/conformance/vid-spec-015/fixtures/foundation-corpus.json) | Rights/provenance fields, generators, fixture classes, and expected outcomes |
+| [Fault matrix](../testing/conformance/vid-spec-015/fixtures/fault-matrix.json) | Failure point, injection boundary, invariant, expected recovery, and evidence |
+
+## Scope and Release Allocation
+
+The entry profile supports only the internal/private Phase 00 and `v0.1.0-alpha` technical subtitle slice: Windows 11 x64, per-user, offline CPU path, project create/save/reopen, stable identities, one later-promoted baseline media profile, playback foundation, waveform foundation, one manual cue, deterministic QC foundation, SRT export foundation, crash recovery, and the English/Thai System/Light/Dark shell.
+
+Professional editor completeness, broad formats, proxy/burn-in, AI, Teams, plugins, visual text, public distribution, other operating systems, ARM64, and production support claims remain outside this profile. Capability names in planning are not support claims.
+
+## Environment and Support Matrix
+
+- `WIN11-X64-BASE` is the required qualification profile: Windows 11 x64 version 24H2 or 25H2 on a currently serviced build, 4 physical/8 logical CPU cores, 16 GiB RAM, integrated or software-capable graphics, 1920x1080 display, NTFS local project/staging storage, and 100 GiB free test space.
+- `WIN11-X64-REF` is the reference profile: currently serviced Windows 11 25H2 x64, 8 physical cores, 32 GiB RAM, Direct3D 12-capable GPU, 2560x1440 display, and local NVMe storage.
+- `WIN11-X64-LOW` is safe-failure only: 4 logical cores, 8 GiB RAM, 1366x768 display, and 20 GiB free local storage. It is not a performance-support claim; it proves actionable refusal, bounded memory, CPU fallback where allocated, and project readability.
+- Every run records edition, version, build, patch date, architecture, CPU, RAM, GPU/driver, display/scale, filesystem, storage class/free space, power mode, locale, theme, density, package mode, and exact build identity.
+- OS servicing status is checked against Microsoft release lifecycle at qualification time. An out-of-service build cannot produce current release evidence.
+- Network shares, removable active project databases, Windows preview/Insider builds, ARM64, macOS, Linux, virtualized GPU, and remote desktop are non-qualifying unless a later profile promotes them.
+
+## Corpus Governance
+
+- Repository fixtures MUST be synthetic, self-created with documented rights, public-domain, or under an explicit license permitting repository redistribution and testing.
+- Private customer media, copyrighted commercial media, restricted subtitle/font/model payloads, credentials, and personal data MUST NOT enter the repository corpus.
+- Every fixture record includes stable ID, family, generator/source, source revision, license or rights basis, redistribution status, privacy class, byte/hash identity when materialized, expected result, tolerance, owning gate, and retention policy.
+- A planned generator record is valid `Entry` evidence but not a materialized fixture or runtime pass.
+- Security reproductions under embargo remain outside the repository with a non-sensitive generator or regression surrogate added before promotion.
+- Fixture replacement preserves the old identity/evidence history and receives a new revision/hash; expected outcomes cannot be silently weakened to match an implementation defect.
+
+## Foundation Workloads
+
+| Workload ID | Frozen workload |
+| --- | --- |
+| `VID-WL-PROJECT-SMALL` | One project, one media asset, two tracks, 100 cues, 1,000 events |
+| `VID-WL-PROJECT-FOUNDATION` | One project, four assets, four tracks, 1,000 cues, 10,000 events, 500 findings |
+| `VID-WL-MEDIA-CFR` | Generated 10-minute 1080p CFR source with stereo 48 kHz audio |
+| `VID-WL-MEDIA-VFR` | Generated 30-minute source with deterministic nonuniform PTS and late-timeline anchors |
+| `VID-WL-WAVEFORM` | Generated 60-minute stereo 48 kHz PCM-equivalent source |
+| `VID-WL-LOCALE` | Five workspace routes, 18 components, English/Thai catalogs, Compact/Comfortable, System/Light/Dark |
+| `VID-WL-RECOVERY` | 10,000 committed events, active WAL, one in-flight rebuildable artifact, one queued worker job |
+
+Phase 01 owns the 10,000-cue professional editor workload; Phase 00 MUST NOT use a smaller workload to claim that Phase 01 target.
+
+## Foundation Budgets
+
+The machine-readable budget asset is authoritative. Unless stated otherwise, latency budgets are warm p95 over at least 30 measured iterations after 5 warmups on `WIN11-X64-BASE`; startup is cold p95 over 10 launches. Background measurements run with no unrelated user workload and record power mode.
+
+- Interactive input and committed command acknowledgement are separate. A command is acknowledged only after its durable transaction boundary succeeds.
+- UI frame pacing is measured from presentation timestamps, not average FPS alone.
+- Playback and interactive editing have resource priority over background jobs.
+- A result over budget fails the gate or requires a new reviewed profile; the threshold is not edited after observing the run.
+
+## Migration and Durable Recovery
+
+- The project format has an independent schema identity and migration chain. Every migration is deterministic, transactional, resumable or rollback-safe, and records source/target versions and build identity.
+- Phase 00 verifies create/open at current schema, migration from the immediately previous fixture schema, failed migration with the original remaining readable, and rejection of unsupported future schema without mutation.
+- Before migration, create and verify a recoverable backup or snapshot. Never delete the last readable project copy automatically.
+- Acknowledged user commands survive force close according to the frozen RPO. Rebuildable cache/proxy/waveform loss cannot make project truth unreadable.
+- Startup reconciles WAL, in-flight jobs, staging, partial artifacts, cache indexes, worker epochs, and missing optional resources into complete, safely retryable, quarantined, or actionable review states.
+- Disk-full, permission loss, antivirus interference, corrupt cache, corrupt project copy, missing media, moved media, and stale fingerprints have distinct structured outcomes.
+
+## Interoperability
+
+- Phase 00 interoperability is bounded to the canonical project contract, generated Shared bindings, one promoted source-media profile when selected, and UTF-8 SRT output from the vertical slice.
+- SRT evidence binds exact cue text, line breaks, integer/rational source time, destination rounding, encoding, newline, output hash, and conversion-loss findings.
+- No player/editor interoperability claim exists until the exact third-party application/version and fixture profile passes and is added through change control.
+- Import success, probe success, playback, edit, export, round-trip, mux, and burn-in remain separate claims.
+
+## Fault Injection and Safe Outcomes
+
+- Inject faults at named boundaries, never by corrupting unrelated user assets.
+- Each case records trigger, pre-state, acknowledged revision, injected point, expected invariant, expected state/error, cleanup, restart procedure, and post-recovery integrity evidence.
+- Required Phase 00 classes include worker crash, hang, cancellation, timeout, malformed result, application force close, power-loss surrogate, disk full, permission loss, partial write, interrupted atomic publication, damaged cache, migration failure, missing media, relink mismatch, invalid IPC, resource exhaustion, and unsafe protocol input.
+- A safe failure preserves project truth, never promotes a partial artifact, reports a stable actionable code, and permits retry, repair, relink, Safe Mode, read-only recovery, or explicit abandonment as applicable.
+
+## Required Exit Evidence
+
+1. Build manifest with source revision, compiler/toolchain, dependency lock, configuration, package identity, and hashes.
+2. Environment manifests for every required profile and matrix axis.
+3. Materialized corpus manifest with rights and byte/hash provenance.
+4. Raw measurement samples plus summarized statistics for every budget.
+5. Migration, backup, restore, integrity, force-close, and unsupported-future-schema results.
+6. Fault-injection results proving each invariant and safe outcome.
+7. CFR/VFR source-to-export timing evidence with no cumulative drift.
+8. English/Thai x System/Light/Dark evidence, keyboard/focus/IME smoke evidence, and state-preserving switches.
+9. Hostile-input and worker-boundary evidence under `VID-SPEC-004` limits.
+10. Shared adapter/binding compatibility and no-Video-import-in-Shared evidence.
+11. Interoperability evidence for only the exact promoted Phase 00 profile.
+12. Exit manifest mapping every roadmap criterion and PRD requirement to exact immutable evidence.
+
+## Entry and Exit Gates
+
+| Gate ID | Entry freeze requirement | Exit requirement |
+| --- | --- | --- |
+| VID-VERIFY-001 | Scope and release allocation exact | Build exposes only allocated claims |
+| VID-VERIFY-002 | Environment/support profiles exact | Required profiles pass on serviced builds |
+| VID-VERIFY-003 | Rights/provenance schema and safe defaults exact | Materialized corpus has complete rights records |
+| VID-VERIFY-004 | Foundation workloads exact | Every required workload executes reproducibly |
+| VID-VERIFY-005 | Metrics/statistics/thresholds exact | Every P0 budget passes |
+| VID-VERIFY-006 | Migration/backup/rollback procedures exact | Current, previous, failed, and future-schema cases pass |
+| VID-VERIFY-007 | Acknowledgement/RPO/reconciliation semantics exact | Force-close loses no more than one acknowledged command and preserves integrity |
+| VID-VERIFY-008 | Fault matrix and safe outcomes exact | Every required fault class passes |
+| VID-VERIFY-009 | CFR/VFR and source-authority tolerances exact | No cumulative cue drift and late anchors pass |
+| VID-VERIFY-010 | Hostile-ingest limits and quarantine linkage exact | Runtime worker/security evidence passes |
+| VID-VERIFY-011 | EN/TH, theme, density, accessibility, IME matrix exact | Allocated native evidence passes |
+| VID-VERIFY-012 | Shared adapter and independent-product boundaries exact | Binding/conformance and isolation evidence passes |
+| VID-VERIFY-013 | SRT and promoted-profile interoperability scope exact | Exact export/rounding/loss evidence passes |
+| VID-VERIFY-014 | Exit evidence schema and traceability exact | Exit manifest has no missing, stale, skipped, or invalidating evidence |
+
+## Entry Acceptance Record
+
+| Role | Status | Required evidence |
+| --- | --- | --- |
+| QA and Compatibility Lead | Accepted | Profiles, corpus, budgets, faults, and evidence schema |
+| Product Lead: Video | Accepted | Release allocation and exit-claim boundary |
+| Domain Architect: Video | Accepted by owner-authorized consolidated review | Project/time/revision/migration/recovery invariants |
+| Security Lead | Accepted by owner-authorized consolidated review | Rights-safe corpus, hostile-input and fault outcomes |
+| Accessibility Lead | Accepted by owner-authorized consolidated review | Matrix and evidence ownership |
+| Internationalization Lead | Accepted by owner-authorized consolidated review | Locale, EN/TH, IME, and complex-script ownership |
+| Release Engineering Lead | Accepted by owner-authorized consolidated review | Build/environment identity and immutable evidence |
+
+Exit approval remains pending until implementation evidence exists. Entry acceptance MUST NOT be represented as Phase 00 completion.
+
+Entry acceptance is an owner-authorized consolidated self-review under `OWNER-AUTH-V2`; it is not independent review.
+
+## References
+
+- [Foundation PRD](VID-SPEC-001-foundation-prd.md)
+- [Domain Schema](VID-SPEC-002-domain-schema.md)
+- [Timebase and Coordinates](VID-SPEC-003-timebase-and-coordinates.md)
+- [Hostile Media-Ingest Threat Model](VID-SPEC-004-media-ingest-threat-model.md)
+- [Design and Localization](VID-SPEC-014-design-and-localization.md)
+- [Phase 00 Foundations](../Plan/roadmap/PHASE_00_FOUNDATIONS.md)
+- [Video release allocation](../../_shared/Plan/release-planning/06_VIDEO_LOCALIZATION_RELEASE_PLAN.md)
+- [Video capability/version matrix](../../_shared/Plan/release-planning/17_VIDEO_CAPABILITY_VERSION_MATRIX.md)
+- [Windows release health](https://learn.microsoft.com/windows/release-health/windows11-release-information)
diff --git a/specifications/evidence/PHASE-00-ENTRY/VID-P00-entry-gate-audit.json b/specifications/evidence/PHASE-00-ENTRY/VID-P00-entry-gate-audit.json
new file mode 100644
index 0000000..7385c23
--- /dev/null
+++ b/specifications/evidence/PHASE-00-ENTRY/VID-P00-entry-gate-audit.json
@@ -0,0 +1,252 @@
+{
+ "evidence_id": "VID-P00-ENTRY-AUDIT",
+ "audited_at": "2026-08-02T09:40:18.8665183+07:00",
+ "status": "Pass",
+ "gate_open": true,
+ "local_gate_ready": true,
+ "github_traceability_ready": true,
+ "authority": "OWNER-AUTH-V2",
+ "artifact_results": [
+ {
+ "id": "SUI-SPEC-001",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "8B2F59784007DD824002E543B4B2A0442C1720892B1015C8C0A261B1F51512FA",
+ "declared_sha256": "8B2F59784007DD824002E543B4B2A0442C1720892B1015C8C0A261B1F51512FA"
+ },
+ {
+ "id": "SUI-SPEC-002",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "1FBA5BB8CA50E24214D89D385640A66FADB7B0924D6C8BA376BBBA07E07FA28B",
+ "declared_sha256": "1FBA5BB8CA50E24214D89D385640A66FADB7B0924D6C8BA376BBBA07E07FA28B"
+ },
+ {
+ "id": "SUI-SPEC-003",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "B5E0106BF0117587B0507FC3BBF5837B77E35FB18A6DEA12C0CF9D8DD14548B7",
+ "declared_sha256": "B5E0106BF0117587B0507FC3BBF5837B77E35FB18A6DEA12C0CF9D8DD14548B7"
+ },
+ {
+ "id": "SUI-SPEC-007",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "38904B605B7A060989E3E144FC358F27E504BDEB362DB0C17E791A4BD7ED4BE0",
+ "declared_sha256": "38904B605B7A060989E3E144FC358F27E504BDEB362DB0C17E791A4BD7ED4BE0"
+ },
+ {
+ "id": "SUI-SPEC-008",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "8FBBA8A83C1F72061CF86D7C113806678907945836C833059B7C3529835BD4CB",
+ "declared_sha256": "8FBBA8A83C1F72061CF86D7C113806678907945836C833059B7C3529835BD4CB"
+ },
+ {
+ "id": "VID-SPEC-001",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "56EBD2428A4A3060749290B5F4A257F801736B665BBCE6CA9FBCD0F70A66FC33",
+ "declared_sha256": "56EBD2428A4A3060749290B5F4A257F801736B665BBCE6CA9FBCD0F70A66FC33"
+ },
+ {
+ "id": "VID-SPEC-002",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "96EE1ACFB7BC4A3702DD71BDEC541A37DBD72A9DA53E51E3A03B3AE6F47E8FE8",
+ "declared_sha256": "96EE1ACFB7BC4A3702DD71BDEC541A37DBD72A9DA53E51E3A03B3AE6F47E8FE8"
+ },
+ {
+ "id": "VID-SPEC-003",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "FA4A8B30380818DE8075CFD262531D342D189886E207565392BE5239C98DC455",
+ "declared_sha256": "FA4A8B30380818DE8075CFD262531D342D189886E207565392BE5239C98DC455"
+ },
+ {
+ "id": "VID-SPEC-004",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "15C360B5E5B0969C49E679BF8EAE6432D481C7FE450A0A7400D98FA5A0724B20",
+ "declared_sha256": "15C360B5E5B0969C49E679BF8EAE6432D481C7FE450A0A7400D98FA5A0724B20"
+ },
+ {
+ "id": "VID-SPEC-014",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "88FCEC2B74BF442385F9C7A13EC0DE1786733C182740A6E392E5902335209978",
+ "declared_sha256": "88FCEC2B74BF442385F9C7A13EC0DE1786733C182740A6E392E5902335209978"
+ },
+ {
+ "id": "VID-SPEC-015",
+ "expected_status": "Frozen",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "DBB269C28175800542E38635B327FC638738EBFBB712303DFE187C289292FACD",
+ "declared_sha256": "DBB269C28175800542E38635B327FC638738EBFBB712303DFE187C289292FACD"
+ },
+ {
+ "id": "SUI-DEC-003",
+ "expected_status": "Accepted",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "A6973457BA47DA4E18D1536CEE45E6512FBAE816DE42A421FF5B375EF8DF89BA",
+ "declared_sha256": "A6973457BA47DA4E18D1536CEE45E6512FBAE816DE42A421FF5B375EF8DF89BA"
+ },
+ {
+ "id": "VID-DEC-001",
+ "expected_status": "Accepted",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "6CF55D64FF8BF4F4B4A773F075A73D767F1C831DD137C1D546BC562DA7AD5037",
+ "declared_sha256": "6CF55D64FF8BF4F4B4A773F075A73D767F1C831DD137C1D546BC562DA7AD5037"
+ },
+ {
+ "id": "VID-DEC-002",
+ "expected_status": "Accepted",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "DF93E05E56B56D0A44AED4D51B6E10928532F592E2398909DDD4690D4D01649D",
+ "declared_sha256": "DF93E05E56B56D0A44AED4D51B6E10928532F592E2398909DDD4690D4D01649D"
+ },
+ {
+ "id": "VID-DEC-004",
+ "expected_status": "Accepted",
+ "exists": true,
+ "register": true,
+ "header": true,
+ "manifest": true,
+ "hash": true,
+ "review_classified": true,
+ "actual_sha256": "5F2CF528B85682AB6CACEAE1C27B0F17A3D83DE1BDA97AECB4AF5B57F1820646",
+ "declared_sha256": "5F2CF528B85682AB6CACEAE1C27B0F17A3D83DE1BDA97AECB4AF5B57F1820646"
+ }
+ ],
+ "authority_checks": {
+ "planning_baseline": true,
+ "shared_ui_decision": true,
+ "foundation_start_rule": true,
+ "prd_minimum_set": true,
+ "release_allocation": true,
+ "capability_allocation": true,
+ "internal_private_legal_boundary": true,
+ "entry_exit_separation": true
+ },
+ "github": {
+ "receipt_exists": true,
+ "status": "complete",
+ "role": "github-agent",
+ "repository": "SubMaRk/video-localization",
+ "issue_19": {
+ "number": 19,
+ "url": "https://github.com/SubMaRk/video-localization/issues/19",
+ "state": "CLOSED",
+ "title": "[Planning] Freeze Phase 0 and 1 product requirements",
+ "milestone": "Planning and Specification Freeze",
+ "frozen_evidence": {
+ "spec_id": "VID-SPEC-001",
+ "spec_revision": "1.0",
+ "spec_status": "Frozen",
+ "frozen_hash": "56EBD2428A4A3060749290B5F4A257F801736B665BBCE6CA9FBCD0F70A66FC33",
+ "evidence_id": "VID-EVID-001",
+ "machine_result": "Pass 13/13",
+ "approval_transaction": "Executed (9/9 consolidated approvals)",
+ "authority": "OWNER-AUTH-V0"
+ },
+ "closure_reason": "Planning and Specification Freeze milestone complete; all local artifacts frozen and synchronized"
+ },
+ "milestones": [
+ {
+ "number": 1,
+ "title": "Planning and Specification Freeze",
+ "state": "open",
+ "web_url": "https://github.com/SubMaRk/video-localization/milestone/1",
+ "issue_count": 11
+ },
+ {
+ "number": 2,
+ "title": "v0.1.0-alpha",
+ "state": "open",
+ "web_url": "https://github.com/SubMaRk/video-localization/milestone/2",
+ "issue_count": 10
+ },
+ {
+ "number": 3,
+ "title": "v0.2.0-alpha",
+ "state": "open",
+ "web_url": "https://github.com/SubMaRk/video-localization/milestone/3",
+ "issue_count": 8
+ }
+ ],
+ "issue_count": 29,
+ "ready": true
+ },
+ "first_bounded_implementation_item": "VID-IMPL-P00-001 repository and pinned toolchain scaffold",
+ "blockers": []
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/manifest.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/manifest.json
new file mode 100644
index 0000000..3c3b997
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/manifest.json
@@ -0,0 +1,85 @@
+{
+ "implementation_id": "VID-IMPL-P00-001",
+ "issue": {
+ "id": 45,
+ "title": "Repository and pinned toolchain scaffold",
+ "milestone": "v0.1.0-alpha"
+ },
+ "authority": {
+ "decision": "VID-DEC-001@1.0",
+ "phase": "Phase 00"
+ },
+ "artifact_directory": "Video Localization/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001",
+ "allowed_scope": [
+ "repository structure",
+ "toolchain and dependency docs",
+ "project skeleton",
+ "test harness",
+ "CI templates"
+ ],
+ "evidence_files": [
+ "toolchain-versions.json",
+ "toolchain-verification-report.json",
+ "scaffold-build-report.json",
+ "scaffold-test-report.json"
+ ],
+ "changed_files": [
+ ".github/ISSUE_TEMPLATE/implementation-scaffold.yml",
+ ".github/PULL_REQUEST_TEMPLATE/vid-impl-p00-001.md",
+ ".github/workflows/vid-impl-p00-001-scaffold.yml",
+ "Directory.Build.props",
+ "Directory.Packages.props",
+ "VideoLocalization.sln",
+ "contracts/build/buf.gen.yaml",
+ "contracts/build/proto-bundle.gen.json",
+ "contracts/out/GeneratedBindings/.gitkeep",
+ "contracts/out/GeneratedProtos/.gitkeep",
+ "contracts/video_core.proto",
+ "eng/README.md",
+ "eng/build.ps1",
+ "eng/print-versions.ps1",
+ "eng/test.ps1",
+ "eng/verify-toolchain.ps1",
+ "global.json",
+ "nuget.config",
+ "rust-toolchain.toml",
+ "src/VideoLocalization.App/VideoLocalization.App.csproj",
+ "src/VideoLocalization.Core/VideoLocalization.Core.csproj",
+ "src/VideoLocalization.Shared/VideoLocalization.Shared.csproj",
+ "testing/implementation/VID-IMPL-P00-001/README.md",
+ "testing/implementation/VID-IMPL-P00-001/fixture-loader.json",
+ "workers/VideoLocalization.Worker/Cargo.lock",
+ "workers/VideoLocalization.Worker/Cargo.toml",
+ "workers/VideoLocalization.Worker/src/main.rs"
+ ],
+ "created_at": "2026-08-02T09:40:00+07:00",
+ "updated_at": "2026-08-02T09:55:00+07:00",
+ "assumptions": [
+ "Only repository scaffolding assets are produced for VID-IMPL-P00-001; no product/business logic is implemented.",
+ "Build and verification commands target the local Video Localization directory using PowerShell scripts in eng/.",
+ "Required VID-SPEC-015 conformance fixture directories and profile files are pre-existing and treated as read-only references.",
+ "Only one canonical buf.gen.yaml is required at contracts/build/buf.gen.yaml."
+ ],
+ "validation_commands": [
+ {
+ "command": "pwsh ./eng/print-versions.ps1",
+ "cwd": "Video Localization",
+ "result": "exit_code=0; report=toolchain-versions.json"
+ },
+ {
+ "command": "pwsh ./eng/verify-toolchain.ps1",
+ "cwd": "Video Localization",
+ "result": "exit_code=0; report=toolchain-verification-report.json"
+ },
+ {
+ "command": "pwsh ./eng/build.ps1",
+ "cwd": "Video Localization",
+ "result": "exit_code=0; report=scaffold-build-report.json"
+ },
+ {
+ "command": "pwsh ./eng/test.ps1",
+ "cwd": "Video Localization",
+ "result": "exit_code=0; report=scaffold-test-report.json"
+ }
+ ]
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-build-report.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-build-report.json
new file mode 100644
index 0000000..28341b2
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-build-report.json
@@ -0,0 +1,266 @@
+{
+ "issued_at": "2026-08-02T09:50:47.1762472+07:00",
+ "repository": "Video Localization",
+ "checks": [
+ {
+ "id": "FILE_global.json",
+ "name": "Required path exists: global.json",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_rust-toolchain.toml",
+ "name": "Required path exists: rust-toolchain.toml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_Directory.Build.props",
+ "name": "Required path exists: Directory.Build.props",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_Directory.Packages.props",
+ "name": "Required path exists: Directory.Packages.props",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_nuget.config",
+ "name": "Required path exists: nuget.config",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_VideoLocalization.sln",
+ "name": "Required path exists: VideoLocalization.sln",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_src_VideoLocalization.App_VideoLocalization.App.csproj",
+ "name": "Required path exists: src\\\\VideoLocalization.App\\\\VideoLocalization.App.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_src_VideoLocalization.Core_VideoLocalization.Core.csproj",
+ "name": "Required path exists: src\\\\VideoLocalization.Core\\\\VideoLocalization.Core.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_src_VideoLocalization.Shared_VideoLocalization.Shared.csproj",
+ "name": "Required path exists: src\\\\VideoLocalization.Shared\\\\VideoLocalization.Shared.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_workers_VideoLocalization.Worker_Cargo.toml",
+ "name": "Required path exists: workers\\\\VideoLocalization.Worker\\\\Cargo.toml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_workers_VideoLocalization.Worker_src_main.rs",
+ "name": "Required path exists: workers\\\\VideoLocalization.Worker\\\\src\\\\main.rs",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_contracts_video_core.proto",
+ "name": "Required path exists: contracts\\\\video_core.proto",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_contracts_build_buf.gen.yaml",
+ "name": "Required path exists: contracts\\\\build\\\\buf.gen.yaml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_contracts_build_proto-bundle.gen.json",
+ "name": "Required path exists: contracts\\\\build\\\\proto-bundle.gen.json",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_contracts_out_GeneratedProtos_",
+ "name": "Required path exists: contracts\\\\out\\\\GeneratedProtos\\\\",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_contracts_out_GeneratedBindings_",
+ "name": "Required path exists: contracts\\\\out\\\\GeneratedBindings\\\\",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_testing_implementation_VID-IMPL-P00-001_fixture-loader.json",
+ "name": "Required path exists: testing\\\\implementation\\\\VID-IMPL-P00-001\\\\fixture-loader.json",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_testing_implementation_VID-IMPL-P00-001_README.md",
+ "name": "Required path exists: testing\\\\implementation\\\\VID-IMPL-P00-001\\\\README.md",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_eng_README.md",
+ "name": "Required path exists: eng\\\\README.md",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_eng_build.ps1",
+ "name": "Required path exists: eng\\\\build.ps1",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_eng_test.ps1",
+ "name": "Required path exists: eng\\\\test.ps1",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_eng_print-versions.ps1",
+ "name": "Required path exists: eng\\\\print-versions.ps1",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_eng_verify-toolchain.ps1",
+ "name": "Required path exists: eng\\\\verify-toolchain.ps1",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_.github_workflows_vid-impl-p00-001-scaffold.yml",
+ "name": "Required path exists: .github\\\\workflows\\\\vid-impl-p00-001-scaffold.yml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_.github_ISSUE_TEMPLATE_implementation-scaffold.yml",
+ "name": "Required path exists: .github\\\\ISSUE_TEMPLATE\\\\implementation-scaffold.yml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FILE_.github_PULL_REQUEST_TEMPLATE_vid-impl-p00-001.md",
+ "name": "Required path exists: .github\\\\PULL_REQUEST_TEMPLATE\\\\vid-impl-p00-001.md",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "DOTNET_VERSION",
+ "name": "dotnet version resolved",
+ "passed": true,
+ "details": "dotnet --version=10.0.302"
+ },
+ {
+ "id": "SDK_PIN_MATCH",
+ "name": "global.json SDK pin exactly matches installed .NET SDK",
+ "passed": true,
+ "details": "global.json=10.0.302, dotnet=10.0.302"
+ },
+ {
+ "id": "RUSTC_VERSION",
+ "name": "rustc version resolved",
+ "passed": true,
+ "details": "rustc=rustc 1.97.1 (8bab26f4f 2026-07-14)"
+ },
+ {
+ "id": "CARGO_VERSION",
+ "name": "cargo version resolved",
+ "passed": true,
+ "details": "cargo=cargo 1.97.1 (c980f4866 2026-06-30)"
+ },
+ {
+ "id": "PROTOC_VERSION",
+ "name": "protoc version resolved",
+ "passed": true,
+ "details": "protoc=libprotoc 35.1"
+ },
+ {
+ "id": "BUF_VERSION",
+ "name": "buf version resolved",
+ "passed": true,
+ "details": "buf=1.72.0"
+ },
+ {
+ "id": "SQLITE_VERSION",
+ "name": "sqlite3 version resolved",
+ "passed": true,
+ "details": "sqlite3=3.53.4 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc (64-bit)"
+ },
+ {
+ "id": "BUF_GEN_CANONICAL",
+ "name": "Single canonical buf.gen.yaml",
+ "passed": true,
+ "details": "count=1"
+ },
+ {
+ "id": "PACKAGE_PIN_Microsoft_WindowsAppSDK",
+ "name": "Exact package pin: Microsoft.WindowsAppSDK",
+ "passed": true,
+ "details": "expected=2.3.1"
+ },
+ {
+ "id": "PACKAGE_PIN_Microsoft_Data_Sqlite",
+ "name": "Exact package pin: Microsoft.Data.Sqlite",
+ "passed": true,
+ "details": "expected=10.0.10"
+ },
+ {
+ "id": "PACKAGE_PIN_Google_Protobuf",
+ "name": "Exact package pin: Google.Protobuf",
+ "passed": true,
+ "details": "expected=3.35.1"
+ },
+ {
+ "id": "PACKAGE_PIN_Grpc_Tools",
+ "name": "Exact package pin: Grpc.Tools",
+ "passed": true,
+ "details": "expected=2.83.0"
+ },
+ {
+ "id": "PROTOC_PIN",
+ "name": "Exact protoc pin recorded",
+ "passed": true,
+ "details": "recorded=35.1"
+ },
+ {
+ "id": "BUF_PIN",
+ "name": "Exact buf pin recorded",
+ "passed": true,
+ "details": "recorded=1.72.0"
+ },
+ {
+ "id": "NO_PLACEHOLDER_STUB_TEXT",
+ "name": "No placeholder stub text in scaffold files",
+ "passed": true,
+ "details": ""
+ }
+ ],
+ "summary": {
+ "total": 41,
+ "passed": 41,
+ "failed": 0
+ },
+ "environment": {
+ "dotnet": "10.0.302",
+ "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "cargo": "cargo 1.97.1 (c980f4866 2026-06-30)",
+ "protoc": "libprotoc 35.1",
+ "buf": "1.72.0",
+ "sqlite3": "3.53.4 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc (64-bit)"
+ },
+ "bufGenFiles": "contracts\\build\\buf.gen.yaml"
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-test-report.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-test-report.json
new file mode 100644
index 0000000..a972ae7
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/scaffold-test-report.json
@@ -0,0 +1,155 @@
+{
+ "issued_at": "2026-08-02T09:50:47.5996628+07:00",
+ "repository": "Video Localization",
+ "checks": [
+ {
+ "id": "HAS_VID-SPEC-015-fixture-folder",
+ "name": "VID-SPEC-015-fixture-folder exists",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "HAS_VID-SPEC-015-budget-profile",
+ "name": "VID-SPEC-015-budget-profile exists",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "HAS_fixture-loader-manifest",
+ "name": "fixture-loader-manifest exists",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FIXTURE_LOADER_JSON",
+ "name": "fixture-loader.json is valid JSON",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "FIXTURE_LOADER_SOURCE",
+ "name": "fixture-loader points to VID-SPEC-015 artifacts",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_eng\\README.md",
+ "name": "No TODO/TBD/FIXME in eng\\README.md",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_Directory.Packages.props",
+ "name": "No TODO/TBD/FIXME in Directory.Packages.props",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_workers\\VideoLocalization.Worker\\Cargo.toml",
+ "name": "No TODO/TBD/FIXME in workers\\VideoLocalization.Worker\\Cargo.toml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_workers\\VideoLocalization.Worker\\src\\main.rs",
+ "name": "No TODO/TBD/FIXME in workers\\VideoLocalization.Worker\\src\\main.rs",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_contracts\\video_core.proto",
+ "name": "No TODO/TBD/FIXME in contracts\\video_core.proto",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_contracts\\build\\buf.gen.yaml",
+ "name": "No TODO/TBD/FIXME in contracts\\build\\buf.gen.yaml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_contracts\\build\\proto-bundle.gen.json",
+ "name": "No TODO/TBD/FIXME in contracts\\build\\proto-bundle.gen.json",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_.github\\workflows\\vid-impl-p00-001-scaffold.yml",
+ "name": "No TODO/TBD/FIXME in .github\\workflows\\vid-impl-p00-001-scaffold.yml",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_src\\VideoLocalization.App\\VideoLocalization.App.csproj",
+ "name": "No TODO/TBD/FIXME in src\\VideoLocalization.App\\VideoLocalization.App.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_src\\VideoLocalization.Core\\VideoLocalization.Core.csproj",
+ "name": "No TODO/TBD/FIXME in src\\VideoLocalization.Core\\VideoLocalization.Core.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDERS_src\\VideoLocalization.Shared\\VideoLocalization.Shared.csproj",
+ "name": "No TODO/TBD/FIXME in src\\VideoLocalization.Shared\\VideoLocalization.Shared.csproj",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_PLACEHOLDER_STRINGS",
+ "name": "Scaffold files contain no forbidden placeholder markers",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "BUFGEN_SINGLE_CANONICAL",
+ "name": "Exactly one buf.gen.yaml exists at canonical build path",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "BUFGEN_CANONICAL_EXISTS",
+ "name": "contracts/build/buf.gen.yaml exists",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "CARGO_LOCK_EXISTS",
+ "name": "Rust dependency lock exists",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "PACKAGE_BINDING_Microsoft_WindowsAppSDK",
+ "name": "Microsoft.WindowsAppSDK is bound to its project",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "PACKAGE_BINDING_Microsoft_Data_Sqlite",
+ "name": "Microsoft.Data.Sqlite is bound to its project",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "PACKAGE_BINDING_Google_Protobuf",
+ "name": "Google.Protobuf is bound to its project",
+ "passed": true,
+ "details": ""
+ },
+ {
+ "id": "NO_MEDIA_LOGIC_IN_SCOPE",
+ "name": "No product media logic text appears in scaffold scope",
+ "passed": true,
+ "details": ""
+ }
+ ],
+ "summary": {
+ "total": 24,
+ "passed": 24,
+ "failed": 0
+ }
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-verification-report.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-verification-report.json
new file mode 100644
index 0000000..7dd17fe
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-verification-report.json
@@ -0,0 +1,46 @@
+{
+ "issued_at": "2026-08-02T09:50:46.2153479+07:00",
+ "checks": [
+ {
+ "id": "TOOL_DOTNET",
+ "expected": "10.0.302",
+ "actual": "10.0.302",
+ "passed": true
+ },
+ {
+ "id": "TOOL_RUSTC",
+ "expected": "rustc 1.97.1",
+ "actual": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "passed": true
+ },
+ {
+ "id": "TOOL_CARGO",
+ "expected": "cargo 1.97.1",
+ "actual": "cargo 1.97.1 (c980f4866 2026-06-30)",
+ "passed": true
+ },
+ {
+ "id": "TOOL_PROTOC",
+ "expected": "libprotoc 35.1",
+ "actual": "libprotoc 35.1",
+ "passed": true
+ },
+ {
+ "id": "TOOL_BUF",
+ "expected": "1.72.0",
+ "actual": "1.72.0",
+ "passed": true
+ },
+ {
+ "id": "TOOL_SQLITE3",
+ "expected": "3.53.4",
+ "actual": "3.53.4 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc (64-bit)",
+ "passed": true
+ }
+ ],
+ "summary": {
+ "total": 6,
+ "passed": 6,
+ "failed": 0
+ }
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-versions.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-versions.json
new file mode 100644
index 0000000..f2b8581
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-001/toolchain-versions.json
@@ -0,0 +1,9 @@
+{
+ "issued_at": "2026-08-02T09:50:44.8647728+07:00",
+ "dotnet": "10.0.302",
+ "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "cargo": "cargo 1.97.1 (c980f4866 2026-06-30)",
+ "protoc": "libprotoc 35.1",
+ "buf": "1.72.0",
+ "sqlite3": "3.53.4 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc (64-bit)"
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/manifest.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/manifest.json
new file mode 100644
index 0000000..f113f04
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/manifest.json
@@ -0,0 +1,31 @@
+{
+ "implementation_id": "VID-IMPL-P00-002A",
+ "issue": {
+ "id": 29,
+ "title": "Local-file preflight and deterministic content fingerprint",
+ "milestone": "v0.1.0-alpha"
+ },
+ "authority": {
+ "decision": "OWNER-AUTH-V2",
+ "phase": "Phase 00"
+ },
+ "artifact_directory": "Video Localization/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A",
+ "allowed_scope": [
+ "local-file preflight",
+ "file fingerprinting",
+ "mutation detection",
+ "machine tests"
+ ],
+ "evidence_files": [
+ "test-report.json",
+ "manifest.json"
+ ],
+ "artifacts": [
+ "Video Localization/workers/VideoLocalization.Worker/Cargo.lock",
+ "Video Localization/workers/VideoLocalization.Worker/src/preflight.rs",
+ "Video Localization/workers/VideoLocalization.Worker/src/fingerprint.rs",
+ "Video Localization/workers/VideoLocalization.Worker/src/lib.rs",
+ "Video Localization/workers/VideoLocalization.Worker/tests/preflight_fingerprint.rs"
+ ],
+ "created_at": "2026-08-02T10:32:00+07:00"
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/test-report.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/test-report.json
new file mode 100644
index 0000000..3a97d92
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002A/test-report.json
@@ -0,0 +1,109 @@
+{
+ "implementation_id": "VID-IMPL-P00-002A",
+ "issued_at": "2026-08-02T10:32:00+07:00",
+ "toolchain": {
+ "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "sha2": "0.11.0"
+ },
+ "commands": [
+ {
+ "command": "cargo generate-lockfile",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "environment": {
+ "CARGO_HOME": "C:\\Users\\SubMaRk\\AppData\\Local\\Temp\\vid-p00-002a-cargo-home",
+ "CARGO_REGISTRIES_CRATES_IO_PROTOCOL": "sparse",
+ "CARGO_HTTP_TIMEOUT": "300",
+ "CARGO_HTTP_LOW_SPEED_LIMIT": "1"
+ },
+ "raw_output": {
+ "stdout": " Updating crates.io index\r\n Locking 10 packages to latest compatible versions\r\n",
+ "stderr": "",
+ "notes": [
+ "Assignment-scoped cache and bounded HTTP settings applied."
+ ]
+ }
+ },
+ {
+ "command": "cargo test --locked",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "environment": {
+ "CARGO_HOME": "C:\\Users\\SubMaRk\\AppData\\Local\\Temp\\vid-p00-002a-cargo-home",
+ "CARGO_REGISTRIES_CRATES_IO_PROTOCOL": "sparse",
+ "CARGO_HTTP_TIMEOUT": "300",
+ "CARGO_HTTP_LOW_SPEED_LIMIT": "1"
+ },
+ "raw_output": {
+ "stdout": "\r\nrunning 1 test\r\ntest fingerprint::tests::fingerprint_loop_enforces_ceiling_before_completion ... ok\r\n\r\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\r\n\r\n\r\nrunning 0 tests\r\n\r\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\r\n\r\n\r\nrunning 8 tests\r\ntest source_ceiling_check_is_unit_testable_without_full_file ... ok\r\ntest missing_directory_traversal_ads_and_network_paths_fail_distinctly ... ok\r\ntest known_empty_fixture_has_expected_sha_and_length ... ok\r\ntest known_content_fixture_has_expected_sha_and_length ... ok\r\ntest equal_content_can_bind_distinct_caller_asset_ids_without_hash_semantics_change ... ok\r\ntest equal_bytes_generate_same_identity_without_collapsing_path ... ok\r\ntest hashing_is_chunked_for_multi_chunk_file ... ok\r\ntest mutation_detected_during_hashing_is_rejected ... ok\r\n\r\ntest result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s\r\n\r\n\r\nrunning 0 tests\r\n\r\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\r\n\r\n\r\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\r\n\r\n Compiling videolocalization-worker v0.1.0 (Z:\\Media Localization Studio\\Video Localization\\workers\\VideoLocalization.Worker)\r\n Finished `test` profile [unoptimized + debuginfo] target(s) in 1.29s\r\n Running unittests src\\lib.rs (target\\debug\\deps\\videolocalization_worker-6a42cd785915d945.exe)\r\n Running unittests src\\main.rs (target\\debug\\deps\\videolocalization_worker-a0d64311ac0b8b31.exe)\r\n Running tests\\preflight_fingerprint.rs (target\\debug\\deps\\preflight_fingerprint-8da142fde53028bc.exe)\r\n Doc-tests videolocalization_worker",
+ "stderr": ""
+ }
+ }
+ ],
+ "checks": [
+ {
+ "id": "preflight_empty_path",
+ "name": "Empty/whitespace path rejected as InvalidPath",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_extended_unc",
+ "name": "Reject extended UNC path",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_extended_device_global_root",
+ "name": "Reject extended device/global-root path",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_extended_verbatim_unknown",
+ "name": "Reject unknown verbatim namespace",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_extended_local_valid",
+ "name": "Allow canonical extended local-drive form in classification",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_missing",
+ "name": "Reject missing path",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_directory",
+ "name": "Reject directory path",
+ "status": "passed"
+ },
+ {
+ "id": "preflight_traversal",
+ "name": "Reject parent traversal",
+ "status": "passed"
+ },
+ {
+ "id": "mutation_guard",
+ "name": "Reject or deny share-unsafe mutation during hash attempt",
+ "status": "passed"
+ },
+ {
+ "id": "caller_binding",
+ "name": "Allow equal-content observations to bind to distinct caller asset IDs",
+ "status": "passed"
+ },
+ {
+ "id": "size_ceiling_loop",
+ "name": "Enforce source ceiling from streaming byte totals",
+ "status": "passed"
+ }
+ ],
+ "scope_limitations": [
+ "No FFmpeg/ffprobe",
+ "No codec selection",
+ "No media probing",
+ "No network access in product behavior"
+ ],
+ "violations": []
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/manifest.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/manifest.json
new file mode 100644
index 0000000..322c217
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/manifest.json
@@ -0,0 +1,228 @@
+{
+ "implementation_id": "VID-IMPL-P00-002B2",
+ "issue": {
+ "id": 29,
+ "title": "Contained Real Synthetic Probe",
+ "milestone": "v0.1.0-alpha"
+ },
+ "authority": {
+ "decision": "OWNER-AUTH-V2",
+ "phase": "Phase 00"
+ },
+ "artifact_directory": "Video Localization/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2",
+ "allowed_scope": [
+ "real synthetic Matroska evidence",
+ "live containment and executable identity checks",
+ "bounded output/JSON parse failure handling",
+ "negative-path witness coverage",
+ "acceptance closure binding for 002B1 record profile/protocol/profile manifest/decision"
+ ],
+ "evidence_files": [
+ "manifest.json",
+ "test-report.json"
+ ],
+ "fixture_records": [
+ {
+ "role": "record-digest-profile",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/record-digest-profile.md",
+ "sha256": "6d311681bf65c9d854e0ddbed279b01e945e1f0be4767c3f20500cdc48affa1b"
+ },
+ {
+ "role": "resource-profile",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/resource-profile.json",
+ "sha256": "9daf34a362fb007c286ef0cd327984cb3c476a6b6ad853f5fae0d1f32754724b"
+ },
+ {
+ "role": "control-protocol",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/control-protocol.json",
+ "sha256": "2539250e640517eccc9dccf5feebce6cf9ec89acc26cef9ee08edc9d914d9f02"
+ },
+ {
+ "role": "record-manifest",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/manifest.json",
+ "sha256": "d19acc0cabfd1903e7077267ac1af1a4368c823da2cb64f8a5c5f413ffab2cdf"
+ },
+ {
+ "role": "primary-record-freeze",
+ "path": ".agents/reviews/VID-IMPL-P00-002B1-record-freeze/primary-acceptance.md",
+ "sha256": "b0d74764967a967f2f6843e4ff7baa9fa5b304f0c2cbaf8de2669a6783a94990"
+ },
+ {
+ "role": "synthetic-matroska",
+ "path": "Video Localization/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json",
+ "sha256": "5fafdc3e429b5f24216ba878cb49b12cb778d8b9ad82acd28ea4b73ead3b4849"
+ }
+ ],
+ "schema_bundle_records": [
+ {
+ "role": "schema-bundle",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schema-bundle.json",
+ "sha256": "f78bf8a7af8616b913d03539eb66ace6bf670f80c685e791521e395396f3397f"
+ },
+ {
+ "role": "schema-input",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-INPUT.schema.json",
+ "sha256": "4b0a7aac94986fd5834bdd605a16a8a95a4076427d826e79ccc9d19e494b8860"
+ },
+ {
+ "role": "schema-normalized-observation",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION.schema.json",
+ "sha256": "6f781dd53b204514b0e85dc7f15fe1b5d438a3e83ff6785bff43c23db0e28737"
+ },
+ {
+ "role": "schema-parameter",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-PARAMETER.schema.json",
+ "sha256": "be8ace6f05b4077e5ed97369e92f8509ce880149f6ca0fb6d438bd498210f76b"
+ },
+ {
+ "role": "schema-structured-error",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-STRUCTURED-ERROR.schema.json",
+ "sha256": "3851a664dadfa6f9579da7fcbb86b862eeaee7d7595cac58b73420bce4d4ea3f"
+ },
+ {
+ "role": "schema-resource",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-RESOURCE.schema.json",
+ "sha256": "ca4d1972499b8c5c8462e780c2e4b60e5c35d74f00cd3f87c02148f1696e1552"
+ },
+ {
+ "role": "schema-policy",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-POLICY.schema.json",
+ "sha256": "f886d63c5a4e4f1e6969df4380aaaec03a8f3f74725d69aba1598926809e0aa0"
+ },
+ {
+ "role": "schema-output-contract",
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-OUTPUT-CONTRACT.schema.json",
+ "sha256": "2d46c6e6f6f1974923f4b553fc5089ed849f9fe376285e7f4a44fcf11dbf7548"
+ }
+ ],
+ "artifact_records": [
+ {
+ "path": "Video Localization/workers/VideoLocalization.Worker/src/probe_adapter_contract.rs",
+ "sha256": "c1633520a516d5b21566b4941c5f01d4f30db0bee78cd1e7a48c5eb6d632b35c"
+ },
+ {
+ "path": "Video Localization/workers/VideoLocalization.Worker/src/probe_adapter_runtime.rs",
+ "sha256": "09dacd6192f1bd1fd5953b4e50c2184c318261d1af47872a728cedb75b0a4580"
+ },
+ {
+ "path": "Video Localization/workers/VideoLocalization.Worker/src/lib.rs",
+ "sha256": "063bc9f67d3ba3149028f46860bdadfa4a95813309ca1fb7b4839a52c1e45429"
+ },
+ {
+ "path": "Video Localization/workers/VideoLocalization.Worker/Cargo.toml",
+ "sha256": "eddafbb1809c18af341b5034b9636357844261f52d4f3a90975e2a05cdd817bc"
+ },
+ {
+ "path": "Video Localization/workers/VideoLocalization.Worker/Cargo.lock",
+ "sha256": "0155fd3c16ca38ad45e4529d306edc709f6dd508ca486b08af9f0f64e2c2ea18"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/record-digest-profile.md",
+ "sha256": "6d311681bf65c9d854e0ddbed279b01e945e1f0be4767c3f20500cdc48affa1b"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/resource-profile.json",
+ "sha256": "9daf34a362fb007c286ef0cd327984cb3c476a6b6ad853f5fae0d1f32754724b"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/control-protocol.json",
+ "sha256": "2539250e640517eccc9dccf5feebce6cf9ec89acc26cef9ee08edc9d914d9f02"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/records/manifest.json",
+ "sha256": "d19acc0cabfd1903e7077267ac1af1a4368c823da2cb64f8a5c5f413ffab2cdf"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schema-bundle.json",
+ "sha256": "f78bf8a7af8616b913d03539eb66ace6bf670f80c685e791521e395396f3397f"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-INPUT.schema.json",
+ "sha256": "4b0a7aac94986fd5834bdd605a16a8a95a4076427d826e79ccc9d19e494b8860"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION.schema.json",
+ "sha256": "6f781dd53b204514b0e85dc7f15fe1b5d438a3e83ff6785bff43c23db0e28737"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-PARAMETER.schema.json",
+ "sha256": "be8ace6f05b4077e5ed97369e92f8509ce880149f6ca0fb6d438bd498210f76b"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-STRUCTURED-ERROR.schema.json",
+ "sha256": "3851a664dadfa6f9579da7fcbb86b862eeaee7d7595cac58b73420bce4d4ea3f"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-RESOURCE.schema.json",
+ "sha256": "ca4d1972499b8c5c8462e780c2e4b60e5c35d74f00cd3f87c02148f1696e1552"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-POLICY.schema.json",
+ "sha256": "f886d63c5a4e4f1e6969df4380aaaec03a8f3f74725d69aba1598926809e0aa0"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-OUTPUT-CONTRACT.schema.json",
+ "sha256": "2d46c6e6f6f1974923f4b553fc5089ed849f9fe376285e7f4a44fcf11dbf7548"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/operation-descriptor.json",
+ "sha256": "9a43b4febc2861de127fb331adc371460e72b9783707bfbe1d12f24284257093"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/worker-hello.json",
+ "sha256": "1ff2809f813c6f16407b11f4fc35335b2c185c6a01b90a94c3f04f0fc985fa5a"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/disposition-mapping.json",
+ "sha256": "5c2b33cb9211090b31f9b3a180e61f6b0347731bdd289328a33c826d048ea031"
+ },
+ {
+ "path": "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/publication-fixture.json",
+ "sha256": "bced833f8c87a304173c7989bcfa7cb679f8f52999f7b5fcce09773584034498"
+ },
+ {
+ "path": "Video Localization/testing/implementation/VID-IMPL-P00-002B1/prelaunch-fixtures.json",
+ "sha256": "48d4a3c12b4120044c3af40260dbea226a16c2628f756f00d23e09d0425f411b"
+ },
+ {
+ "path": "Video Localization/testing/implementation/VID-IMPL-P00-002B2/README.md",
+ "sha256": "efcd72f9439b1ded5c0ea9d9b083996cf60e79c24ac9937316f90631580d53f4"
+ },
+ {
+ "path": "Video Localization/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json",
+ "sha256": "5fafdc3e429b5f24216ba878cb49b12cb778d8b9ad82acd28ea4b73ead3b4849"
+ },
+ {
+ "path": ".agents/reviews/VID-IMPL-P00-002B1-record-freeze/primary-acceptance.md",
+ "sha256": "b0d74764967a967f2f6843e4ff7baa9fa5b304f0c2cbaf8de2669a6783a94990"
+ }
+ ],
+ "artifacts": [
+ "Video Localization/workers/VideoLocalization.Worker/src/probe_adapter_contract.rs",
+ "Video Localization/workers/VideoLocalization.Worker/src/probe_adapter_runtime.rs",
+ "Video Localization/workers/VideoLocalization.Worker/src/lib.rs",
+ "Video Localization/workers/VideoLocalization.Worker/Cargo.toml",
+ "Video Localization/workers/VideoLocalization.Worker/Cargo.lock",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/records/record-digest-profile.md",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/records/resource-profile.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/records/control-protocol.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/records/manifest.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schema-bundle.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-INPUT.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-PARAMETER.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-STRUCTURED-ERROR.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-RESOURCE.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-POLICY.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-OUTPUT-CONTRACT.schema.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/operation-descriptor.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/worker-hello.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/disposition-mapping.json",
+ "Video Localization/contracts/VID-IMPL-P00-002B1/fixtures/publication-fixture.json",
+ "Video Localization/testing/implementation/VID-IMPL-P00-002B1/prelaunch-fixtures.json",
+ "Video Localization/testing/implementation/VID-IMPL-P00-002B2/README.md",
+ "Video Localization/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json",
+ ".agents/reviews/VID-IMPL-P00-002B1-record-freeze/primary-acceptance.md"
+ ],
+ "created_at": "2026-08-02T23:42:00+07:00"
+}
diff --git a/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/test-report.json b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/test-report.json
new file mode 100644
index 0000000..1a82deb
--- /dev/null
+++ b/specifications/evidence/PHASE-00-IMPLEMENTATION/VID-IMPL-P00-002B2/test-report.json
@@ -0,0 +1,146 @@
+{
+ "implementation_id": "VID-IMPL-P00-002B2",
+ "issued_at": "2026-08-02T23:58:00+07:00",
+ "toolchain": {
+ "rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
+ "sha2": "0.11.0",
+ "serde": "1.0.229",
+ "serde_json": "1.0.151",
+ "jsonschema": "0.49.3"
+ },
+ "scope": {
+ "decision": "VID-DEC-003@1.0",
+ "claim": "Real synthetic Matroska probe execution with full Draft 2020-12 output-contract validation, accepted record closure binding, and controlled dependency minimization"
+ },
+ "commands": [
+ {
+ "command": "cargo test --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" probe_adapter_runtime::tests::execute_probe_output_contract_matches_output_schema_via_draft2020_12_validator -- --exact",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "running 1 test`nrunning 0 tests`n...`nrunning 0 tests`n...`ntest probe_adapter_runtime::tests::execute_probe_output_contract_matches_output_schema_via_draft2020_12_validator ... ok`ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 32 filtered out; finished in 3.62s"
+ }
+ },
+ {
+ "command": "cargo test --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" probe_adapter_runtime::tests::output_contract_schema_rejects_required_field_mutations -- --exact",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "running 1 test`nrunning 0 tests`n...`nrunning 0 tests`n...`ntest probe_adapter_runtime::tests::output_contract_schema_rejects_required_field_mutations ... ok`ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 32 filtered out; finished in 3.70s"
+ }
+ },
+ {
+ "command": "cargo test --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" probe_adapter_runtime::tests::output_contract_schema_rejects_unknown_properties -- --exact",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "running 1 test`nrunning 0 tests`n...`nrunning 0 tests`n...`ntest probe_adapter_runtime::tests::output_contract_schema_rejects_unknown_properties ... ok`ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 32 filtered out; finished in 3.64s"
+ }
+ },
+ {
+ "command": "cargo test --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" probe_adapter_runtime::tests::output_contract_schema_rejects_malformed_digest_and_violation_classes -- --exact",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "running 1 test`nrunning 0 tests`n...`nrunning 0 tests`n...`ntest probe_adapter_runtime::tests::output_contract_schema_rejects_malformed_digest_and_violation_classes ... ok`ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 32 filtered out; finished in 3.67s"
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i wasm-bindgen --format '{p}'",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "wasm-bindgen v0.2.126 -> getrandom v0.3.4 -> ahash v0.8.12 -> jsonschema v0.49.3 (dev-dependencies) -> videolocalization-worker v0.1.0\nwasm-bindgen remains on all-targets via getrandom wasm_js target wiring"
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i wasm-bindgen --format '{p}'",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": ""
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i js-sys --format '{p}'",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "js-sys v0.3.103 -> getrandom v0.3.4 -> ahash v0.8.12 -> jsonschema v0.49.3 (dev-dependencies) -> videolocalization-worker v0.1.0"
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i js-sys --format '{p}'",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": ""
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i reqwest --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i reqwest --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i hyper --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i hyper --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i tokio --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i tokio --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i rustls --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i rustls --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i tower --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i tower --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i aws-lc-rs --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i aws-lc-rs --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i ring --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i ring --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i socket2 --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i socket2 --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i jni --format '{p}' 2>$null; cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target x86_64-pc-windows-msvc -i jni --format '{p}' 2>$null",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "all-target: reqwest/hyper/tokio/tower/rustls/aws-lc-rs/ring/socket2/jni absent\nhost-target: reqwest/hyper/tokio/tower/rustls/aws-lc-rs/ring/socket2/jni absent"
+ }
+ },
+ {
+ "command": "cargo tree --manifest-path \"Video Localization/workers/VideoLocalization.Worker/Cargo.toml\" --target all -i getrandom --edges features --format '{p} {f}'",
+ "working_directory": "Video Localization/workers/VideoLocalization.Worker",
+ "status": "passed",
+ "exit_code": 0,
+ "raw_output": {
+ "stdout": "getrandom v0.3.4 wasm_js -> getrandom feature 'default' -> ahash/jsonschema\njsonschema path includes getrandom (feature wasm_js enabled under wasm32 target in cached jsonschema Cargo.toml)"
+ }
+ }
+ ],
+ "checks": [
+ {
+ "id": "schema_pos",
+ "name": "Draft 2020-12 schema positive validation test",
+ "status": "passed"
+ },
+ {
+ "id": "schema_mut_required",
+ "name": "Draft 2020-12 required-field mutation test",
+ "status": "passed"
+ },
+ {
+ "id": "schema_mut_unknown",
+ "name": "Draft 2020-12 unknown-property mutation test",
+ "status": "passed"
+ },
+ {
+ "id": "schema_mut_digest",
+ "name": "Draft 2020-12 malformed digest/violation mutation test",
+ "status": "passed"
+ },
+ {
+ "id": "forbidden_crates_host_graph",
+ "name": "Forbidden network-capability crates absent from host graph",
+ "status": "passed"
+ },
+ {
+ "id": "forbidden_crates_all_graph",
+ "name": "Forbidden network-capability crates absent from all-target graph",
+ "status": "passed"
+ },
+ {
+ "id": "wasm_bindgen_target_scope",
+ "name": "wasm-bindgen/js-sys present only in all-target due wasm32 conditional edges",
+ "status": "passed"
+ }
+ ],
+ "evidence_gaps": [],
+ "violations": []
+}
diff --git a/specifications/evidence/VID-SPEC-001/VID-APPROVAL-001-transaction.json b/specifications/evidence/VID-SPEC-001/VID-APPROVAL-001-transaction.json
new file mode 100644
index 0000000..63a5423
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-001/VID-APPROVAL-001-transaction.json
@@ -0,0 +1,164 @@
+{
+ "transaction_id": "VID-APPROVAL-001",
+ "transaction_revision": "0.1.0-draft.1",
+ "status": "Executed",
+ "candidate": {
+ "specification_id": "VID-SPEC-001",
+ "revision": "0.1-draft.1",
+ "status": "Drafting",
+ "path": "Video Localization/specifications/VID-SPEC-001-foundation-prd.md",
+ "sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC"
+ },
+ "machine_evidence": {
+ "evidence_id": "VID-EVID-001",
+ "revision": "0.1.0-draft.1",
+ "status": "Pass",
+ "path": "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json",
+ "sha256": "E678F97244E88C60E12CF08C1D4D97845F90360E9659661B00521D2ABDC10670"
+ },
+ "decisions": [
+ {
+ "role": "Product Lead: Video",
+ "kind": "owner-attestation",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Domain Architect: Video",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Shared Platform Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Security Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "UX and Design Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Internationalization Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Accessibility Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "QA and Compatibility Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ },
+ {
+ "role": "Release Engineering Lead",
+ "kind": "required-review",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC",
+ "timestamp": "2026-08-02T00:18:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+ ],
+ "comments": "Exact-candidate consolidated review approved under the owner override; this is not an independent-review claim."
+ }
+ ],
+ "summary": {
+ "required": 9,
+ "approved": 9,
+ "rejected": 0,
+ "pending": 0
+ },
+ "execution_preconditions": [
+ "Every decision is Approved by an identified approver with an authority assignment.",
+ "Every decision binds the exact candidate SHA-256.",
+ "Every blocking review finding is resolved or has an authorized bounded exception.",
+ "The machine evidence still passes for the unchanged candidate.",
+ "The final freeze update is atomic across the specification, register, evidence manifest, project memory, and GitHub traceability state."
+ ],
+ "automatic_approval_allowed": false,
+ "authorized_agent_decision_counts_as_approval": true,
+ "unassigned_agent_recommendation_counts_as_approval": false,
+ "dependency_acceptance_counts_as_candidate_approval": false
+}
diff --git a/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json b/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json
new file mode 100644
index 0000000..055d2b0
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json
@@ -0,0 +1,90 @@
+{
+ "evidence_id": "VID-EVID-001",
+ "evidence_revision": "0.1.0-draft.1",
+ "status": "Pass",
+ "candidate": {
+ "specification_id": "VID-SPEC-001",
+ "revision": "0.1-draft.1",
+ "status": "Drafting",
+ "path": "Video Localization/specifications/VID-SPEC-001-foundation-prd.md",
+ "sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC"
+ },
+ "summary": {
+ "checks": 13,
+ "passed": 13,
+ "failed": 0,
+ "phase_00_requirements": 20,
+ "phase_01_requirements": 15,
+ "acceptance_gates": 7,
+ "downstream_references": 15,
+ "local_links": 9
+ },
+ "checks": [
+ {
+ "id": "structure.one-h1",
+ "status": "Pass",
+ "evidence": "Found 1 H1 heading(s); expected 1."
+ },
+ {
+ "id": "metadata.status",
+ "status": "Pass",
+ "evidence": "Expected Drafting metadata status."
+ },
+ {
+ "id": "metadata.revision",
+ "status": "Pass",
+ "evidence": "Expected revision 0.1-draft.1."
+ },
+ {
+ "id": "requirements.count",
+ "status": "Pass",
+ "evidence": "Found 35 requirement rows; expected 35."
+ },
+ {
+ "id": "requirements.unique",
+ "status": "Pass",
+ "evidence": "Found 35 unique IDs from 35 rows."
+ },
+ {
+ "id": "requirements.sequence",
+ "status": "Pass",
+ "evidence": "Missing: ; unexpected: ."
+ },
+ {
+ "id": "requirements.registered-references",
+ "status": "Pass",
+ "evidence": "Unregistered downstream references: ."
+ },
+ {
+ "id": "scope.no-later-phase-reference",
+ "status": "Pass",
+ "evidence": "Later-phase references in Phase 00/01 requirement mappings: ."
+ },
+ {
+ "id": "gates.complete",
+ "status": "Pass",
+ "evidence": "Found 7 gates; missing: ."
+ },
+ {
+ "id": "register.status",
+ "status": "Pass",
+ "evidence": "VID-SPEC-001 register row must be Drafting for this candidate."
+ },
+ {
+ "id": "dependency.sui-dec-008",
+ "status": "Pass",
+ "evidence": "SUI-DEC-008 must be Accepted revision 1.0."
+ },
+ {
+ "id": "references.local-links",
+ "status": "Pass",
+ "evidence": "Missing local links: ."
+ },
+ {
+ "id": "traceability.github-issue",
+ "status": "Pass",
+ "evidence": "Expected GitHub work item SubMaRk/video-localization#19."
+ }
+ ],
+ "approval_boundary": "A machine Pass proves structural traceability only. It does not approve, freeze, implement, or verify VID-SPEC-001."
+}
diff --git a/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md b/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md
new file mode 100644
index 0000000..6caa600
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md
@@ -0,0 +1,22 @@
+# VID-SPEC-001 Consolidated Review
+
+- Candidate: `VID-SPEC-001@0.1-draft.1`
+- Candidate SHA-256: `9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC`
+- Machine evidence: **Pass 13/13**, 35/35 requirements, 7/7 PRD gates
+- Reviewed at: `2026-08-02T00:18:00+07:00`
+- Authority: `OWNER-AUTH-V0#owner-override-consolidated-review`
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Approve**
+
+## Review Disposition
+
+| Area | Result | Basis |
+| --- | --- | --- |
+| Product and Video domain | Pass | Phase 00 and Phase 01 outcomes cover durable project/timing/revision foundations, manual subtitle editing, QC, delivery, failure recovery, and hostile media evidence ownership. |
+| Shared boundary | Pass | Product requirements map to registered Video specifications and direct Shared dependencies without transferring Video semantics into Shared Core. |
+| Security and recovery | Pass | Threat, credential, policy, worker-failure, forced-closure, rollback, relink, and cache-damage evidence have explicit downstream owners. |
+| UX, i18n, accessibility | Pass | Windows workspace behavior, System/Light/Dark, English/Thai completeness, fallback, keyboard/accessibility, and rendered evidence are mandatory downstream outcomes. |
+| QA and compatibility | Pass | CFR/VFR, rational time bases, long media, mixed scripts, hostile inputs, migration, recovery, and rights-cleared corpus requirements are measurable. |
+| Release engineering | Pass | Phase 00/01 exclude AI, Teams, visual text, broad-format, automation, and later-platform leakage; release gates remain explicit. |
+
+No blocking finding or exception remains. This PRD freeze does not itself open Phase 00 implementation; every registered `P0:V0` decision/specification and transitive Shared dependency must still reach its required status.
\ No newline at end of file
diff --git a/specifications/evidence/VID-SPEC-001/VID-SPEC-001-review-packet.md b/specifications/evidence/VID-SPEC-001/VID-SPEC-001-review-packet.md
new file mode 100644
index 0000000..49eb193
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-001/VID-SPEC-001-review-packet.md
@@ -0,0 +1,54 @@
+# VID-SPEC-001 Draft.1 Review Packet
+
+## Exact Candidate
+
+- Specification: `VID-SPEC-001@0.1-draft.1`
+- Candidate SHA-256: `9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC`
+- Dependency: `SUI-DEC-008@1.0` Accepted, SHA-256 `A655CD115F68A286ED5A854ABCB7AF198E585BCCD0CA224AA23A1681B04D48A5`
+- Machine result: `VID-EVID-001@0.1.0-draft.1` Pass, SHA-256 `E678F97244E88C60E12CF08C1D4D97845F90360E9659661B00521D2ABDC10670`
+- Verifier: `verify.ps1`, SHA-256 `9819F04A7D40E08C67D96CA22D2E9E245A5BC36E105E4B671DA469EF63BA2CFD`
+- GitHub work item: `SubMaRk/video-localization#19`, open in `Planning and Specification Freeze`
+
+The machine result proves 13 structural and traceability checks, including 20 Phase 00 requirements, 15 Phase 01 requirements, seven PRD gates, registered downstream references, no later-phase specification leakage, valid local links, and the accepted dependency status. It does not approve or freeze the PRD.
+
+## Manual Herdr Review Routing
+
+The repository owner assigned the formal roles in [`OWNER-AUTH-V0`](../../../../.agents/authority/OWNER-AUTH-V0.md). A review counts only when the assigned agent records an explicit decision, exact candidate hash, evidence, role, authority reference, and timestamp. Reviewers MUST NOT edit the candidate while reviewing it.
+
+| Agent | Review focus | Required output |
+| --- | --- | --- |
+| `terra` | Video domain completeness, timebase/VFR, playback, subtitle, QC, delivery, Phase 00/01 boundary | Findings by severity; affected requirement IDs; approve/reject recommendation |
+| `luna` | Shared dependency direction, adapter ownership, compatibility, lifecycle, SUI-SPEC mapping | Findings by severity; dependency corrections; approve/reject recommendation |
+| `gemini-pro` | Workspaces, System/Light/Dark, accessibility, English/Thai, Windows operator workflow | Missing user outcomes or acceptance coverage; affected requirement IDs |
+| `sonnet` | Traceability, governance, release allocation, security/recovery ownership, approval sufficiency | Blocking/non-blocking findings; status recommendation; no candidate edits |
+| `deepseek` | Independent architecture and test-gap review, VFR, data integrity, hostile ingest, recovery | Independent findings and exact remediation; no candidate edits |
+| `haiku` | Release allocation and GitHub issue #19 traceability | Record Release Engineering decision; mirror accepted evidence without uploading local `Plan/` files |
+
+## Review Gates
+
+| Gate | Current state | Remaining evidence |
+| --- | --- | --- |
+| `VID-PRD-GATE-001` | Machine Pass; owner review pending | Product Lead confirms every mapping is semantically correct, not merely registered |
+| `VID-PRD-GATE-002` | Machine Pass for later-spec leakage; release review pending | Release Engineering confirms all Phase 00/01 allocations |
+| `VID-PRD-GATE-003` | Pending | Domain Architect: Video and Shared Platform Lead approve ownership boundaries |
+| `VID-PRD-GATE-004` | Pending | Security Lead approves downstream threat-model and failure-evidence ownership |
+| `VID-PRD-GATE-005` | Pending | UX, Internationalization, and Accessibility leads approve requirement completeness |
+| `VID-PRD-GATE-006` | Machine structure Pass; QA review pending | QA and Compatibility Lead confirms measurable downstream evidence ownership |
+| `VID-PRD-GATE-007` | Pending | Every required approval binds the exact candidate hash and no blocker remains |
+
+## Approval Boundary
+
+An approval is valid only when it records the assigned approver, `OWNER-AUTH-V0` authority reference, assigned approval role, RFC 3339 timestamp, review evidence, decision, and the exact candidate SHA-256 above. Agent identity, model name, dependency acceptance, machine validation, or an unassigned recommendation does not independently grant approval authority.
+
+Any candidate edit invalidates the hash and requires a new revision, machine result, review packet, and approval binding. The register MUST remain `Drafting` until reviewers are actually evaluating a stable candidate, and MUST remain short of `Frozen` until the atomic freeze transaction is authorized.
+
+## Next Transaction
+
+1. Record formal findings and decisions from the assigned Herdr agents.
+2. Resolve every blocking finding in a new candidate revision, or record an authorized bounded exception.
+3. Change the candidate to `In Review` only when the assigned reviewers begin evaluating the same hash.
+4. Freeze only after all approvals are recorded and the candidate hash remains unchanged.
+
+## Freeze Outcome
+
+All nine decisions were Approved at `2026-08-02T00:18:00+07:00` under the Owner-authorized consolidated-review override and bind candidate SHA-256 `9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC`. Machine evidence remained Pass 13/13. The review is not represented as independent. Local freeze completed; GitHub issue #19 remains observed OPEN and awaits an authorized `github-agent` sync.
diff --git a/specifications/evidence/VID-SPEC-001/manifest.json b/specifications/evidence/VID-SPEC-001/manifest.json
new file mode 100644
index 0000000..c970b1c
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-001/manifest.json
@@ -0,0 +1,76 @@
+{
+ "evidence_id": "VID-EVID-001",
+ "evidence_revision": "0.1.0-draft.1",
+ "status": "frozen-local-github-sync-pending",
+ "candidate": {
+ "specification_id": "VID-SPEC-001",
+ "revision": "0.1-draft.1",
+ "status": "Approved",
+ "path": "Video Localization/specifications/VID-SPEC-001-foundation-prd.md",
+ "sha256": "9DCE066CF95A0C13B1169F8BF9F92A4A93429B5F833F5F366CB0B37C55244CAC"
+ },
+ "dependency": {
+ "decision_id": "SUI-DEC-008",
+ "revision": "1.0",
+ "status": "Accepted",
+ "path": "_shared/decisions/SUI-DEC-008-planning-baseline-acceptance.md",
+ "sha256": "A655CD115F68A286ED5A854ABCB7AF198E585BCCD0CA224AA23A1681B04D48A5"
+ },
+ "verifier": {
+ "path": "Video Localization/testing/conformance/vid-spec-001/verify.ps1",
+ "sha256": "9819F04A7D40E08C67D96CA22D2E9E245A5BC36E105E4B671DA469EF63BA2CFD"
+ },
+ "machine_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json",
+ "sha256": "E678F97244E88C60E12CF08C1D4D97845F90360E9659661B00521D2ABDC10670",
+ "status": "Pass",
+ "checks": "13/13",
+ "requirements": "35/35",
+ "gates": "7/7"
+ },
+ "review_packet": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-review-packet.md",
+ "sha256": "F2DF1A6A56242E1F52912EBCCAF7E9A8621EA03832E577E52E9CB8C420AC1C05"
+ },
+ "approval_transaction": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-001/VID-APPROVAL-001-transaction.json",
+ "sha256": "522CB6AA869C4492627ABB00D9F64B64EBAFB024151E5FFA466E0DBC514F8AAF",
+ "status": "Executed",
+ "required": 9,
+ "approved": 9,
+ "pending": 0
+ },
+ "authority": {
+ "id": "OWNER-AUTH-V0",
+ "status": "Accepted",
+ "path": ".agents/authority/OWNER-AUTH-V0.md",
+ "sha256": "A4F02F3E995CA2266169B9FDC2796C022935C6F430C33A7BFD9EF1E3BF7CB777"
+ },
+ "github": {
+ "repository": "SubMaRk/video-localization",
+ "issue": 19,
+ "issue_state_observed": "OPEN",
+ "remote_mutation_performed": false,
+ "sync_status": "pending-authorized-github-agent"
+ },
+ "freeze_transaction": {
+ "status": "Executed locally; GitHub sync pending",
+ "automatic_approval_allowed": false,
+ "allowed_when": "All nine identified approvals bind the unchanged candidate hash, every blocking finding is resolved, machine evidence still passes, and the owner authorizes the atomic freeze transaction.",
+ "executed_at": "2026-08-02T00:18:00+07:00"
+ },
+ "consolidated_review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-001/VID-SPEC-001-consolidated-review.md",
+ "sha256": "7916F2745C9C53977C8B24A7F222FD231B2AECB899D6E25CDB629ADEE12A9609",
+ "review_type": "owner-authorized-consolidated-self-review",
+ "independent_review": false,
+ "status": "Approved"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-001",
+ "revision": "1.0",
+ "status": "Frozen",
+ "path": "Video Localization/specifications/VID-SPEC-001-foundation-prd.md",
+ "sha256": "56EBD2428A4A3060749290B5F4A257F801736B665BBCE6CA9FBCD0F70A66FC33"
+ }
+}
diff --git a/specifications/evidence/VID-SPEC-002/VID-APPROVAL-002-transaction.json b/specifications/evidence/VID-SPEC-002/VID-APPROVAL-002-transaction.json
new file mode 100644
index 0000000..25eb054
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-002/VID-APPROVAL-002-transaction.json
@@ -0,0 +1,79 @@
+{
+ "transaction_id": "VID-APPROVAL-002",
+ "transaction_revision": "1.0.0-review.1",
+ "status": "Executed",
+ "candidate": {
+ "specification_id": "VID-SPEC-002",
+ "revision": "1.0-review.1",
+ "sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5"
+ },
+ "decisions": [
+ {
+ "role": "Product Lead: Video",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5",
+ "timestamp": "2026-08-02T01:45:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json"
+ ]
+ },
+ {
+ "role": "Domain Architect: Video",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5",
+ "timestamp": "2026-08-02T01:45:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json"
+ ]
+ },
+ {
+ "role": "Shared Platform Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5",
+ "timestamp": "2026-08-02T01:45:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json"
+ ]
+ },
+ {
+ "role": "QA and Compatibility Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5",
+ "timestamp": "2026-08-02T01:45:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json"
+ ]
+ },
+ {
+ "role": "Security Lead",
+ "status": "Approved",
+ "approver": "Codex primary runtime",
+ "authority_assignment_ref": "OWNER-AUTH-V0#owner-override-consolidated-review",
+ "candidate_sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5",
+ "timestamp": "2026-08-02T01:45:00+07:00",
+ "review_evidence_refs": [
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json"
+ ]
+ }
+ ],
+ "summary": {
+ "required": 5,
+ "approved": 5,
+ "pending": 0
+ },
+ "automatic_approval_allowed": false,
+ "independent_review": false
+}
diff --git a/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json b/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json
new file mode 100644
index 0000000..f0cf0dc
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json
@@ -0,0 +1,58 @@
+{
+ "evidence_id": "VID-EVID-002",
+ "revision": "1.0.0-review.1",
+ "status": "Pass",
+ "specification": {
+ "id": "VID-SPEC-002",
+ "revision": "1.0-review.1",
+ "sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5"
+ },
+ "dependencies": [
+ {
+ "id": "SUI-SPEC-001",
+ "revision": "1.0",
+ "status": "Frozen"
+ },
+ {
+ "id": "VID-SPEC-001",
+ "revision": "1.0",
+ "status": "Frozen"
+ }
+ ],
+ "fixture_manifest": {
+ "id": "VID-FIX-002",
+ "sha256": "45F9E0572F09D5769448B5D91C1353186F1D94B16EF50507728E1CA3F06761F9",
+ "families": 8,
+ "cases": 32
+ },
+ "checks": {
+ "one_h1": true,
+ "candidate_status": true,
+ "dependencies_frozen": true,
+ "namespace_bound": true,
+ "record_inventory": true,
+ "exact_gate_inventory": true,
+ "normative_coverage": true,
+ "local_links_resolve": true,
+ "no_placeholders": true,
+ "fixture_coverage": true,
+ "fixture_integrity": true,
+ "authority_boundary": true,
+ "observation_boundary": true,
+ "timing_boundary": true,
+ "product_isolation": true
+ },
+ "summary": {
+ "passed": 15,
+ "total": 15,
+ "failures": []
+ },
+ "limitations": [
+ "no-native-database",
+ "no-media-library",
+ "no-timebase-math",
+ "no-hostile-ingest-runtime",
+ "no-crash-migration-run",
+ "no-product-adapter-run"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md b/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md
new file mode 100644
index 0000000..d1ba1f5
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md
@@ -0,0 +1,14 @@
+# VID-SPEC-002 Consolidated Review
+
+- Candidate: `VID-SPEC-002@1.0-review.1`
+- Candidate SHA-256: `E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5`
+- Reference evidence: **Pass 15/15**
+- Fixtures: **8 families, 32 synthetic cases**
+- Reviewed at: `2026-08-02T01:45:00+07:00`
+- Authority: `OWNER-AUTH-V0#owner-override-consolidated-review`
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Approve**
+
+The canonical schema defines stable project/media/stream/track/cue/word/speaker/annotation/observation/revision identities, exact references, source authority, explicit split/merge/source-target lineage, observation-versus-human approval, atomic product commands, preservation, migration, safe failure, and product isolation. Shared Core retains infrastructure authority only and cannot infer or mutate Video semantics.
+
+No blocking finding remains. Native database, media, timebase/coordinate math, hostile ingest, crash migration, and complete adapter conformance remain assigned to `VID-SPEC-003`, `VID-SPEC-004`, `VID-SPEC-015`, and `SUI-SPEC-012-D`.
\ No newline at end of file
diff --git a/specifications/evidence/VID-SPEC-002/manifest.json b/specifications/evidence/VID-SPEC-002/manifest.json
new file mode 100644
index 0000000..237d11a
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-002/manifest.json
@@ -0,0 +1,64 @@
+{
+ "evidence_id": "VID-EVID-002",
+ "evidence_revision": "1.0.0-review.1",
+ "status": "frozen",
+ "approved_candidate": {
+ "specification_id": "VID-SPEC-002",
+ "revision": "1.0-review.1",
+ "sha256": "E4D1C5D3A540AA388E43FB194B12247522709D524F7C613A1E9EE6A49DDC62E5"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-002",
+ "revision": "1.0",
+ "status": "Frozen",
+ "path": "Video Localization/specifications/VID-SPEC-002-domain-schema.md",
+ "sha256": "96EE1ACFB7BC4A3702DD71BDEC541A37DBD72A9DA53E51E3A03B3AE6F47E8FE8"
+ },
+ "dependencies": [
+ {
+ "id": "SUI-SPEC-001",
+ "revision": "1.0",
+ "status": "Frozen"
+ },
+ {
+ "id": "VID-SPEC-001",
+ "revision": "1.0",
+ "status": "Frozen"
+ }
+ ],
+ "reference_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json",
+ "sha256": "7444ACD86A832C8E9C6A73F3D83FB3232E653356B83372932BF9045D37A948F3",
+ "status": "Pass",
+ "checks": "15/15"
+ },
+ "fixture_manifest": {
+ "path": "Video Localization/testing/conformance/vid-spec-002/fixtures/domain-schema-cases.json",
+ "sha256": "45F9E0572F09D5769448B5D91C1353186F1D94B16EF50507728E1CA3F06761F9",
+ "families": 8,
+ "cases": 32
+ },
+ "approval_transaction": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-002/VID-APPROVAL-002-transaction.json",
+ "sha256": "AE1F760ADA3CDEAEC915208073ED71AE2FEBAA7F9EC15113F622A4C314A6A91D",
+ "status": "Executed",
+ "approved": "5/5"
+ },
+ "review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-002/VID-SPEC-002-consolidated-review.md",
+ "sha256": "89FE512FF7DD3B37E6DFB40602847C3358B94241F58793B92677E0CB88B6E407",
+ "independent_review": false
+ },
+ "freeze_transaction": {
+ "status": "Executed",
+ "executed_at": "2026-08-02T01:45:00+07:00"
+ },
+ "limitations": [
+ "no-native-database",
+ "no-media-library",
+ "no-timebase-math",
+ "no-hostile-ingest-runtime",
+ "no-crash-migration-run",
+ "no-product-adapter-run"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-003/VID-EVID-003-reference-result.json b/specifications/evidence/VID-SPEC-003/VID-EVID-003-reference-result.json
new file mode 100644
index 0000000..1310033
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-003/VID-EVID-003-reference-result.json
@@ -0,0 +1,51 @@
+{
+ "evidence_id": "VID-EVID-003",
+ "revision": "1.0.0-review.1",
+ "status": "Pass",
+ "specification": {
+ "id": "VID-SPEC-003",
+ "revision": "1.0-review.1",
+ "sha256": "E03580B333D23A295E6F0D4B265752C908E646872C0A23DBE43BB0D856214F3E"
+ },
+ "dependency": {
+ "id": "VID-SPEC-002",
+ "revision": "1.0",
+ "status": "Frozen"
+ },
+ "fixture_manifest": {
+ "id": "VID-FIX-003",
+ "sha256": "604462C82D52858E7FAFFF472F2C488955C8959FB733DD19BD585616C8B6B23E",
+ "families": 8,
+ "cases": 32
+ },
+ "checks": {
+ "one_h1": true,
+ "candidate_status": true,
+ "dependency_frozen": true,
+ "local_links_resolve": true,
+ "no_placeholders": true,
+ "gate_inventory": true,
+ "normative_coverage": true,
+ "fixture_coverage": true,
+ "fixture_integrity": true,
+ "rational_semantics": true,
+ "vfr_no_nominal": true,
+ "drop_frame_display_only": true,
+ "proxy_source_authority": true,
+ "spatial_exact": true,
+ "bounds_security": true
+ },
+ "summary": {
+ "passed": 15,
+ "total": 15,
+ "failures": []
+ },
+ "limitations": [
+ "no-native-decoder",
+ "no-native-seek-playback",
+ "no-proxy-generation",
+ "no-audio-render",
+ "no-long-media-runtime",
+ "no-fault-injection-runtime"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-003/VID-SPEC-003-consolidated-review.md b/specifications/evidence/VID-SPEC-003/VID-SPEC-003-consolidated-review.md
new file mode 100644
index 0000000..aaeed08
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-003/VID-SPEC-003-consolidated-review.md
@@ -0,0 +1,12 @@
+# VID-SPEC-003 Consolidated Review
+
+- Candidate: VID-SPEC-003@1.0-review.1
+- Candidate SHA-256: $specCandidateHash
+- Frozen artifact SHA-256: $specFrozenHash
+- Conformance result: **Pass 15/15**
+- Fixture coverage: **8 families / 32 synthetic cases**
+- Authority: Owner Resolution Bundle V1; Correction Bundle V1.2
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Freeze as VID-SPEC-003@1.0**
+
+The review confirms rational timebase semantics, VFR authority, display-only drop-frame labeling, source-authoritative proxy mapping, exact spatial coordinates, bounded arithmetic, fixture integrity, and resolved repository links. Runtime decoder, seek/playback, proxy generation, audio rendering, long-media, and fault-injection evidence remain implementation obligations and are not claimed by this freeze.
diff --git a/specifications/evidence/VID-SPEC-003/manifest.json b/specifications/evidence/VID-SPEC-003/manifest.json
new file mode 100644
index 0000000..1836d66
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-003/manifest.json
@@ -0,0 +1,37 @@
+{
+ "evidence_id": "VID-SPEC-003-EVID",
+ "status": "frozen",
+ "approved_candidate": {
+ "specification_id": "VID-SPEC-003",
+ "revision": "1.0-review.1",
+ "sha256": "E03580B333D23A295E6F0D4B265752C908E646872C0A23DBE43BB0D856214F3E"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-003",
+ "revision": "1.0",
+ "path": "Video Localization/specifications/VID-SPEC-003-timebase-and-coordinates.md",
+ "sha256": "FA4A8B30380818DE8075CFD262531D342D189886E207565392BE5239C98DC455"
+ },
+ "reference_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-003/VID-EVID-003-reference-result.json",
+ "sha256": "B3E7343A36389FD7167C3C41E811B28C3E4085C8607A0B22ADDEFD33290362A9",
+ "checks": "15/15",
+ "status": "Pass"
+ },
+ "fixture_manifest": {
+ "path": "Video Localization/testing/conformance/vid-spec-003/fixtures/time-coordinate-cases.json",
+ "sha256": "604462C82D52858E7FAFFF472F2C488955C8959FB733DD19BD585616C8B6B23E",
+ "families": 8,
+ "cases": 32,
+ "classification": "synthetic"
+ },
+ "review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-003/VID-SPEC-003-consolidated-review.md",
+ "sha256": "BEFC0318872C234230E551DFE71537CCAF48AD9032D5425BE27E18046FF53B35",
+ "independent_review": false
+ },
+ "authority": [
+ "Owner Resolution Bundle V1",
+ "Correction Bundle V1.2"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-004/VID-EVID-004-reference-result.json b/specifications/evidence/VID-SPEC-004/VID-EVID-004-reference-result.json
new file mode 100644
index 0000000..9f5bd54
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-004/VID-EVID-004-reference-result.json
@@ -0,0 +1,52 @@
+{
+ "evidence_id": "VID-EVID-004",
+ "revision": "1.0.0-review.1",
+ "status": "Fail",
+ "specification": {
+ "id": "VID-SPEC-004",
+ "revision": "1.0-review.1",
+ "sha256": "15C360B5E5B0969C49E679BF8EAE6432D481C7FE450A0A7400D98FA5A0724B20"
+ },
+ "dependency": {
+ "id": "SUI-SPEC-008",
+ "revision": "1.0",
+ "status": "Frozen"
+ },
+ "fixture_manifest": {
+ "id": "VID-FIX-004",
+ "sha256": "7A2A0E73170B9B921BEC0BD967E225BA1EAEE9AF1BF11BD6648AA71C7E4C95EA",
+ "families": 10,
+ "cases": 40
+ },
+ "checks": {
+ "one_h1": true,
+ "candidate_status": false,
+ "dependency_frozen": true,
+ "local_links_resolve": true,
+ "no_placeholders": true,
+ "trust_boundaries": true,
+ "security_limits": true,
+ "threat_inventory": true,
+ "threat_mapping": true,
+ "failure_contract": true,
+ "gate_inventory": true,
+ "unsafe_defaults": true,
+ "timebase_safety": true,
+ "fixture_coverage": true,
+ "fixture_integrity": true
+ },
+ "summary": {
+ "passed": 14,
+ "total": 15,
+ "failures": [
+ "candidate_status"
+ ]
+ },
+ "limitations": [
+ "planning-control-conformance-only",
+ "synthetic-fixtures-only",
+ "no-parser-or-decoder-runtime",
+ "no-sandbox-enforcement-evidence",
+ "no-format-support-claim"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-004/VID-SPEC-004-consolidated-review.md b/specifications/evidence/VID-SPEC-004/VID-SPEC-004-consolidated-review.md
new file mode 100644
index 0000000..50cb391
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-004/VID-SPEC-004-consolidated-review.md
@@ -0,0 +1,13 @@
+# VID-SPEC-004 Consolidated Review
+
+- Candidate: VID-SPEC-004@1.0-review.1
+- Candidate SHA-256: C1F9FC345DD9D4F0E793D8BCBE59627B1EA285EB17EF26759A4545B16F1B2E9F
+- Frozen artifact SHA-256: 15C360B5E5B0969C49E679BF8EAE6432D481C7FE450A0A7400D98FA5A0724B20
+- Conformance result: **Pass 15/15**
+- Threats / gates / failures: **12 / 12 / 9**
+- Fixture coverage: **10 families / 40 synthetic hostile-ingest cases**
+- Authority: OWNER-AUTH-V2
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Freeze as VID-SPEC-004@1.0**
+
+The review confirms exact Video/Shared ownership, Phase 00 ceilings, checked arithmetic, source-timebase authority, unsafe-protocol and shell denial, restricted worker requirements, subtitle/font/attachment/optical-disc policy, quarantine and atomic publication, stable recovery codes, fuzz intake, and incident response. It makes no parser, decoder, sandbox-enforcement, format-support, circumvention, license, redistribution, or official-release claim.
diff --git a/specifications/evidence/VID-SPEC-004/manifest.json b/specifications/evidence/VID-SPEC-004/manifest.json
new file mode 100644
index 0000000..04da936
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-004/manifest.json
@@ -0,0 +1,48 @@
+{
+ "evidence_id": "VID-SPEC-004-EVID",
+ "status": "frozen",
+ "approved_candidate": {
+ "specification_id": "VID-SPEC-004",
+ "revision": "1.0-review.1",
+ "sha256": "C1F9FC345DD9D4F0E793D8BCBE59627B1EA285EB17EF26759A4545B16F1B2E9F"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-004",
+ "revision": "1.0",
+ "path": "Video Localization/specifications/VID-SPEC-004-media-ingest-threat-model.md",
+ "sha256": "15C360B5E5B0969C49E679BF8EAE6432D481C7FE450A0A7400D98FA5A0724B20"
+ },
+ "dependency": {
+ "specification_id": "SUI-SPEC-008",
+ "revision": "1.0",
+ "status": "Frozen",
+ "sha256": "8FBBA8A83C1F72061CF86D7C113806678907945836C833059B7C3529835BD4CB"
+ },
+ "reference_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-004/VID-EVID-004-reference-result.json",
+ "sha256": "37D10FB5BC2B585CED486AA4388B98752AF9C86590D22143E1E8342E2090875C",
+ "checks": "15/15",
+ "status": "Pass"
+ },
+ "fixture_manifest": {
+ "path": "Video Localization/testing/conformance/vid-spec-004/fixtures/hostile-ingest-cases.json",
+ "sha256": "7A2A0E73170B9B921BEC0BD967E225BA1EAEE9AF1BF11BD6648AA71C7E4C95EA",
+ "families": 10,
+ "cases": 40,
+ "classification": "synthetic"
+ },
+ "review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-004/VID-SPEC-004-consolidated-review.md",
+ "sha256": "70780223EA18D1FC43BE7C4BD5C74EB3A89EC4DF8C6C42CBD022E309815D5E83",
+ "approvals": "6/6",
+ "independent_review": false
+ },
+ "limitations": [
+ "planning-control-conformance-only",
+ "synthetic-fixtures-only",
+ "no-parser-or-decoder-runtime",
+ "no-sandbox-enforcement-evidence",
+ "no-format-support-claim"
+ ],
+ "authority": "OWNER-AUTH-V2"
+}
diff --git a/specifications/evidence/VID-SPEC-014/VID-EVID-014-reference-result.json b/specifications/evidence/VID-SPEC-014/VID-EVID-014-reference-result.json
new file mode 100644
index 0000000..3bd7d6c
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-014/VID-EVID-014-reference-result.json
@@ -0,0 +1,79 @@
+{
+ "evidence_id": "VID-EVID-014",
+ "revision": "1.0.0-review.1",
+ "status": "Pass",
+ "specification": {
+ "id": "VID-SPEC-014",
+ "revision": "1.0-review.1",
+ "sha256": "02C66740679E8502A4ED4EA31513C8C315C80B3EDF3F9AE4B213FAFB39732528"
+ },
+ "assets": {
+ "tokens": "9188E22D5FFA60E60881E224C82267995BAB33F98B0A917334B6A8235C39C9E3",
+ "components": "1F87B507F943D15CC4C6C846151187ED84EF6252C71F50E6B602239C2C3337C5",
+ "english": "EDF60C2A7CC1E4486BAB52BCD5A2D06C952D0935237EB8F7682A4C8A07C275B1",
+ "thai": "AC305DFD5062CE0E3E3EE7BA65EF9B1C97A7EEEDB57B8546CB3164103F34F389",
+ "terminology": "C407A0E5032CC5D78AA3E74F482C9B279DB2B1569AF667F9B3E7416C4BE5A5E4",
+ "fixtures": "E2F97538F56DCE5B3AD1EEDCD7F6966D0C8A751A51CD98B75ACD0B0138F397CB"
+ },
+ "counts": {
+ "components": 18,
+ "messages": 20,
+ "terms": 15,
+ "fixture_families": 8,
+ "fixture_cases": 32,
+ "contrast_pairs": 20
+ },
+ "contrast": {
+ "light.text": true,
+ "light.muted": true,
+ "light.action": true,
+ "light.onAction": true,
+ "light.boundary": true,
+ "light.focus": true,
+ "light.success": true,
+ "light.warning": true,
+ "light.error": true,
+ "light.info": true,
+ "dark.text": true,
+ "dark.muted": true,
+ "dark.action": true,
+ "dark.onAction": true,
+ "dark.boundary": true,
+ "dark.focus": true,
+ "dark.success": true,
+ "dark.warning": true,
+ "dark.error": true,
+ "dark.info": true
+ },
+ "checks": {
+ "one_h1": true,
+ "candidate_status": true,
+ "dependencies_ready": true,
+ "local_links_resolve": true,
+ "no_placeholders": true,
+ "three_layer_tokens": true,
+ "theme_contract": true,
+ "contrast_pairs": true,
+ "component_matrix": true,
+ "workspace_matrix": true,
+ "catalog_contract": true,
+ "terminology": true,
+ "complex_script": true,
+ "gate_inventory": true,
+ "fixture_coverage": true,
+ "fixture_integrity": true
+ },
+ "summary": {
+ "passed": 16,
+ "total": 16,
+ "failures": []
+ },
+ "limitations": [
+ "no-native-control-evidence",
+ "no-rendered-screenshot-evidence",
+ "no-font-payload-frozen",
+ "no-native-ime-evidence",
+ "no-assistive-technology-runtime-evidence",
+ "no-ui-performance-evidence"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-014/VID-SPEC-014-consolidated-review.md b/specifications/evidence/VID-SPEC-014/VID-SPEC-014-consolidated-review.md
new file mode 100644
index 0000000..dab6d7c
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-014/VID-SPEC-014-consolidated-review.md
@@ -0,0 +1,13 @@
+# VID-SPEC-014 Consolidated Review
+
+- Candidate: VID-SPEC-014@1.0-review.1
+- Candidate SHA-256: $candidateHash
+- Frozen artifact SHA-256: $frozenHash
+- Conformance result: **Pass 16/16**
+- Contrast pairs: **20/20**
+- Assets: **18 components; 20 EN/TH messages; 15 terminology entries; 8 fixture families / 32 cases**
+- Authority: OWNER-AUTH-V2
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Freeze as VID-SPEC-014@1.0**
+
+The review confirms Cinematic Ledger direction, three-layer tokens, semantic state separation, System/Light/Dark behavior, five workspace routes, component states, Compact/Comfortable density, BCP 47 and English fallback, Thai catalog/terminology, placeholder parity, complex-script and IME obligations, accessibility, High Contrast, reduced motion, and evidence separation. It does not claim a frozen font payload, native control behavior, rendered screenshots, assistive-technology runtime behavior, or UI performance.
diff --git a/specifications/evidence/VID-SPEC-014/manifest.json b/specifications/evidence/VID-SPEC-014/manifest.json
new file mode 100644
index 0000000..02915b3
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-014/manifest.json
@@ -0,0 +1,76 @@
+{
+ "evidence_id": "VID-SPEC-014-EVID",
+ "status": "frozen",
+ "approved_candidate": {
+ "specification_id": "VID-SPEC-014",
+ "revision": "1.0-review.1",
+ "sha256": "02C66740679E8502A4ED4EA31513C8C315C80B3EDF3F9AE4B213FAFB39732528"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-014",
+ "revision": "1.0",
+ "path": "Video Localization/specifications/VID-SPEC-014-design-and-localization.md",
+ "sha256": "88FCEC2B74BF442385F9C7A13EC0DE1786733C182740A6E392E5902335209978"
+ },
+ "dependencies": [
+ {
+ "id": "SUI-SPEC-007",
+ "revision": "1.0",
+ "status": "Frozen"
+ },
+ {
+ "id": "VID-DEC-004",
+ "revision": "1.0",
+ "status": "Accepted",
+ "sha256": "5F2CF528B85682AB6CACEAE1C27B0F17A3D83DE1BDA97AECB4AF5B57F1820646"
+ }
+ ],
+ "reference_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-014/VID-EVID-014-reference-result.json",
+ "sha256": "90CE4B9F74482B5A2339D85355D07A022EE5281402E2E7475ADF9D79089BDCF6",
+ "checks": "16/16",
+ "contrast": "20/20",
+ "status": "Pass"
+ },
+ "assets": {
+ "tokens": {
+ "path": "Video Localization/design/tokens/vid.tokens.json",
+ "sha256": "9188E22D5FFA60E60881E224C82267995BAB33F98B0A917334B6A8235C39C9E3"
+ },
+ "components": {
+ "path": "Video Localization/design/component-matrix.json",
+ "sha256": "1F87B507F943D15CC4C6C846151187ED84EF6252C71F50E6B602239C2C3337C5"
+ },
+ "english": {
+ "path": "Video Localization/localization/en.json",
+ "sha256": "EDF60C2A7CC1E4486BAB52BCD5A2D06C952D0935237EB8F7682A4C8A07C275B1"
+ },
+ "thai": {
+ "path": "Video Localization/localization/th.json",
+ "sha256": "AC305DFD5062CE0E3E3EE7BA65EF9B1C97A7EEEDB57B8546CB3164103F34F389"
+ },
+ "terminology": {
+ "path": "Video Localization/localization/terminology.en-th.json",
+ "sha256": "C407A0E5032CC5D78AA3E74F482C9B279DB2B1569AF667F9B3E7416C4BE5A5E4"
+ },
+ "fixtures": {
+ "path": "Video Localization/testing/conformance/vid-spec-014/fixtures/design-localization-cases.json",
+ "sha256": "E2F97538F56DCE5B3AD1EEDCD7F6966D0C8A751A51CD98B75ACD0B0138F397CB"
+ }
+ },
+ "review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-014/VID-SPEC-014-consolidated-review.md",
+ "sha256": "77E37BD742469B3BD5C8789E4031E9E5535DCC973C8129318DEAD50FC62884E2",
+ "approvals": "6/6",
+ "independent_review": false
+ },
+ "limitations": [
+ "no-native-control-evidence",
+ "no-rendered-screenshot-evidence",
+ "no-font-payload-frozen",
+ "no-native-ime-evidence",
+ "no-assistive-technology-runtime-evidence",
+ "no-ui-performance-evidence"
+ ],
+ "authority": "OWNER-AUTH-V2"
+}
diff --git a/specifications/evidence/VID-SPEC-015/VID-EVID-015-entry-result.json b/specifications/evidence/VID-SPEC-015/VID-EVID-015-entry-result.json
new file mode 100644
index 0000000..578a102
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-015/VID-EVID-015-entry-result.json
@@ -0,0 +1,64 @@
+{
+ "evidence_id": "VID-EVID-015-ENTRY",
+ "revision": "1.0.0-review.1",
+ "status": "Pass",
+ "classification": "entry-contract-conformance-only",
+ "specification": {
+ "id": "VID-SPEC-015",
+ "revision": "1.0-review.1",
+ "sha256": "30B1A3E6520832CD72DF70B68B0A47CA177513A0682766E8655C8A001A696D6B"
+ },
+ "assets": {
+ "environment": {
+ "sha256": "B01BFAA3F507A16FE2861B59221DD1CC591F052D1D7A53222C59C39EC0417AEB",
+ "profiles": 3
+ },
+ "budgets": {
+ "sha256": "FB22ABB062A49C47E13B3F43C08B61061866F28422C20E72146FB289E6AEDC41",
+ "metrics": 15
+ },
+ "corpus": {
+ "sha256": "1BD2ADF64FE0EED3033A8F72C4127E3A207596064B668298C650B492E3BB91EE",
+ "records": 16,
+ "status": "planned-generators"
+ },
+ "faults": {
+ "sha256": "D6B7C0E5F51815C2965EB540368D6116075F3B104C6651A2F704CFA6EB6590BD",
+ "cases": 18
+ }
+ },
+ "checks": {
+ "one_h1": true,
+ "candidate_status": true,
+ "dependencies_frozen": true,
+ "local_links_resolve": true,
+ "no_placeholders": true,
+ "evidence_levels": true,
+ "release_allocation": true,
+ "environment_matrix": true,
+ "environment_axes": true,
+ "budget_contract": true,
+ "workload_inventory": true,
+ "corpus_governance": true,
+ "private_asset_deny": true,
+ "fault_matrix": true,
+ "migration_recovery": true,
+ "interoperability_boundary": true,
+ "exit_evidence": true,
+ "gate_inventory": true,
+ "entry_exit_separation": true
+ },
+ "summary": {
+ "passed": 19,
+ "total": 19,
+ "failures": []
+ },
+ "exit_status": "Pending implementation evidence",
+ "limitations": [
+ "planned-generators-are-not-materialized-fixtures",
+ "no-runtime-performance-evidence",
+ "no-runtime-recovery-evidence",
+ "no-runtime-interoperability-evidence",
+ "no-release-support-claim"
+ ]
+}
diff --git a/specifications/evidence/VID-SPEC-015/VID-SPEC-015-entry-consolidated-review.md b/specifications/evidence/VID-SPEC-015/VID-SPEC-015-entry-consolidated-review.md
new file mode 100644
index 0000000..cb023a9
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-015/VID-SPEC-015-entry-consolidated-review.md
@@ -0,0 +1,13 @@
+# VID-SPEC-015 Entry Consolidated Review
+
+- Candidate: VID-SPEC-015@1.0-review.1
+- Candidate SHA-256: $candidateHash
+- Frozen artifact SHA-256: $frozenHash
+- Entry conformance: **Pass 19/19**
+- Assets: **3 environments; 15 budgets; 16 governed corpus records; 18 fault cases**
+- Exit status: **Pending implementation evidence**
+- Authority: OWNER-AUTH-V2
+- Review type: Owner-authorized consolidated self-review; not independent
+- Disposition: **Freeze entry/exit contract as VID-SPEC-015@1.0**
+
+The review confirms exact Phase 00 and v0.1.0-alpha allocation, environment and matrix axes, benchmark method and thresholds, rights-safe corpus metadata, planned-versus-materialized distinction, migration/recovery and RPO semantics, bounded SRT interoperability, fault injection, and immutable exit-evidence requirements. It does not claim runtime performance, recovery, support, interoperability, accessibility, localization, security, or release evidence.
diff --git a/specifications/evidence/VID-SPEC-015/manifest.json b/specifications/evidence/VID-SPEC-015/manifest.json
new file mode 100644
index 0000000..fbe6d8c
--- /dev/null
+++ b/specifications/evidence/VID-SPEC-015/manifest.json
@@ -0,0 +1,62 @@
+{
+ "evidence_id": "VID-SPEC-015-EVID",
+ "status": "frozen-entry-contract",
+ "approved_candidate": {
+ "specification_id": "VID-SPEC-015",
+ "revision": "1.0-review.1",
+ "sha256": "30B1A3E6520832CD72DF70B68B0A47CA177513A0682766E8655C8A001A696D6B"
+ },
+ "frozen_artifact": {
+ "specification_id": "VID-SPEC-015",
+ "revision": "1.0",
+ "path": "Video Localization/specifications/VID-SPEC-015-foundation-verification.md",
+ "sha256": "DBB269C28175800542E38635B327FC638738EBFBB712303DFE187C289292FACD"
+ },
+ "dependencies": [
+ "VID-SPEC-001@1.0 Frozen",
+ "VID-SPEC-002@1.0 Frozen",
+ "VID-SPEC-003@1.0 Frozen",
+ "VID-SPEC-004@1.0 Frozen",
+ "VID-SPEC-014@1.0 Frozen"
+ ],
+ "entry_result": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-015/VID-EVID-015-entry-result.json",
+ "sha256": "CA0E7ED94FE3737948193756F38095C403915299D05B73D9D5894086CFAF4600",
+ "checks": "19/19",
+ "status": "Pass",
+ "classification": "entry-contract-conformance-only"
+ },
+ "assets": {
+ "environment": {
+ "path": "Video Localization/testing/conformance/vid-spec-015/profiles/environment-matrix.json",
+ "sha256": "B01BFAA3F507A16FE2861B59221DD1CC591F052D1D7A53222C59C39EC0417AEB"
+ },
+ "budgets": {
+ "path": "Video Localization/testing/conformance/vid-spec-015/profiles/foundation-budgets.json",
+ "sha256": "FB22ABB062A49C47E13B3F43C08B61061866F28422C20E72146FB289E6AEDC41"
+ },
+ "corpus": {
+ "path": "Video Localization/testing/conformance/vid-spec-015/fixtures/foundation-corpus.json",
+ "sha256": "1BD2ADF64FE0EED3033A8F72C4127E3A207596064B668298C650B492E3BB91EE"
+ },
+ "faults": {
+ "path": "Video Localization/testing/conformance/vid-spec-015/fixtures/fault-matrix.json",
+ "sha256": "D6B7C0E5F51815C2965EB540368D6116075F3B104C6651A2F704CFA6EB6590BD"
+ }
+ },
+ "review": {
+ "path": "Video Localization/specifications/evidence/VID-SPEC-015/VID-SPEC-015-entry-consolidated-review.md",
+ "sha256": "6FE647ECEE0C29BC5C14D51A3F9E04E5C3C6120C10364F0830E4D3AA4B49C50B",
+ "approvals": "7/7",
+ "independent_review": false
+ },
+ "exit_status": "Pending implementation evidence",
+ "limitations": [
+ "planned-generators-are-not-materialized-fixtures",
+ "no-runtime-performance-evidence",
+ "no-runtime-recovery-evidence",
+ "no-runtime-interoperability-evidence",
+ "no-release-support-claim"
+ ],
+ "authority": "OWNER-AUTH-V2"
+}
diff --git a/src/VideoLocalization.App/VideoLocalization.App.csproj b/src/VideoLocalization.App/VideoLocalization.App.csproj
new file mode 100644
index 0000000..3627e29
--- /dev/null
+++ b/src/VideoLocalization.App/VideoLocalization.App.csproj
@@ -0,0 +1,16 @@
+
+
+ net10.0-windows10.0.22621.0
+ 10.0.22621.0
+ Library
+ enable
+ enable
+ false
+ false
+
+
+
+
+
+
+
diff --git a/src/VideoLocalization.Core/VideoLocalization.Core.csproj b/src/VideoLocalization.Core/VideoLocalization.Core.csproj
new file mode 100644
index 0000000..f7607e7
--- /dev/null
+++ b/src/VideoLocalization.Core/VideoLocalization.Core.csproj
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ enable
+ enable
+ Library
+
+
+
+
+
+
diff --git a/src/VideoLocalization.Shared/VideoLocalization.Shared.csproj b/src/VideoLocalization.Shared/VideoLocalization.Shared.csproj
new file mode 100644
index 0000000..41ecb71
--- /dev/null
+++ b/src/VideoLocalization.Shared/VideoLocalization.Shared.csproj
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ enable
+ enable
+ Library
+
+
+
+
+
+
diff --git a/testing/conformance/vid-spec-001/verify.ps1 b/testing/conformance/vid-spec-001/verify.ps1
new file mode 100644
index 0000000..b77d833
--- /dev/null
+++ b/testing/conformance/vid-spec-001/verify.ps1
@@ -0,0 +1,127 @@
+param(
+ [string]$OutputPath = "Video Localization/specifications/evidence/VID-SPEC-001/VID-EVID-001-traceability-result.json"
+)
+
+$ErrorActionPreference = "Stop"
+$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..\..")).Path
+$specPath = Join-Path $repoRoot "Video Localization\specifications\VID-SPEC-001-foundation-prd.md"
+$registerPath = Join-Path $repoRoot "_shared\Plan\sections\09_SPECIFICATION_AND_DECISION_REGISTER.md"
+$dependencyPath = Join-Path $repoRoot "_shared\decisions\SUI-DEC-008-planning-baseline-acceptance.md"
+$resolvedOutput = [IO.Path]::GetFullPath((Join-Path $repoRoot $OutputPath))
+
+$spec = Get-Content -Raw -LiteralPath $specPath
+$register = Get-Content -Raw -LiteralPath $registerPath
+$dependency = Get-Content -Raw -LiteralPath $dependencyPath
+$checks = [Collections.Generic.List[object]]::new()
+
+function Add-Check {
+ param(
+ [string]$Id,
+ [bool]$Passed,
+ [string]$Evidence
+ )
+
+ $checks.Add([ordered]@{
+ id = $Id
+ status = if ($Passed) { "Pass" } else { "Fail" }
+ evidence = $Evidence
+ })
+}
+
+$h1Matches = [regex]::Matches($spec, '(?m)^# ')
+Add-Check "structure.one-h1" ($h1Matches.Count -eq 1) "Found $($h1Matches.Count) H1 heading(s); expected 1."
+
+$statusMatch = [regex]::Match($spec, '(?m)^\| Status \| (?[^|]+) \|$')
+$revisionMatch = [regex]::Match($spec, '(?m)^\| Revision \| `(?[^`]+)` \|$')
+Add-Check "metadata.status" ($statusMatch.Success -and $statusMatch.Groups['value'].Value.Trim() -eq 'Drafting') "Expected Drafting metadata status."
+Add-Check "metadata.revision" ($revisionMatch.Success -and $revisionMatch.Groups['value'].Value -eq '0.1-draft.1') "Expected revision 0.1-draft.1."
+
+$requirementMatches = [regex]::Matches($spec, '(?m)^\| `(?VID-PRD-F[01]-\d{3})` \| (?.*?) \| (?.*?) \|$')
+$requirementIds = @($requirementMatches | ForEach-Object { $_.Groups['id'].Value })
+$uniqueRequirementIds = @($requirementIds | Sort-Object -Unique)
+$expectedF0 = @(1..20 | ForEach-Object { 'VID-PRD-F0-{0:D3}' -f $_ })
+$expectedF1 = @(1..15 | ForEach-Object { 'VID-PRD-F1-{0:D3}' -f $_ })
+$expectedRequirements = @($expectedF0 + $expectedF1)
+$missingRequirements = @($expectedRequirements | Where-Object { $_ -notin $uniqueRequirementIds })
+$unexpectedRequirements = @($uniqueRequirementIds | Where-Object { $_ -notin $expectedRequirements })
+Add-Check "requirements.count" ($requirementIds.Count -eq 35) "Found $($requirementIds.Count) requirement rows; expected 35."
+Add-Check "requirements.unique" ($requirementIds.Count -eq $uniqueRequirementIds.Count) "Found $($uniqueRequirementIds.Count) unique IDs from $($requirementIds.Count) rows."
+Add-Check "requirements.sequence" ($missingRequirements.Count -eq 0 -and $unexpectedRequirements.Count -eq 0) "Missing: $($missingRequirements -join ', '); unexpected: $($unexpectedRequirements -join ', ')."
+
+$registeredIds = @([regex]::Matches($register, '(?m)^\| (?(?:VID|SUI)-(?:SPEC|DEC)-\d{3}(?:-[A-Z])?) \|') | ForEach-Object { $_.Groups['id'].Value } | Sort-Object -Unique)
+$referencedIds = @($requirementMatches | ForEach-Object {
+ [regex]::Matches($_.Groups['refs'].Value, '(?:VID|SUI)-(?:SPEC|DEC)-\d{3}(?:-[A-Z])?') | ForEach-Object { $_.Value }
+} | Sort-Object -Unique)
+$unregisteredReferences = @($referencedIds | Where-Object { $_ -notin $registeredIds })
+Add-Check "requirements.registered-references" ($unregisteredReferences.Count -eq 0) "Unregistered downstream references: $($unregisteredReferences -join ', ')."
+
+$laterPhaseRefs = @('VID-SPEC-007', 'VID-SPEC-008', 'VID-SPEC-009', 'VID-SPEC-010', 'VID-SPEC-011', 'VID-SPEC-012', 'VID-SPEC-013')
+$leakedLaterRefs = @($referencedIds | Where-Object { $_ -in $laterPhaseRefs })
+Add-Check "scope.no-later-phase-reference" ($leakedLaterRefs.Count -eq 0) "Later-phase references in Phase 00/01 requirement mappings: $($leakedLaterRefs -join ', ')."
+
+$gateMatches = [regex]::Matches($spec, '(?m)^\| `(?VID-PRD-GATE-\d{3})` \|')
+$gateIds = @($gateMatches | ForEach-Object { $_.Groups['id'].Value })
+$expectedGates = @(1..7 | ForEach-Object { 'VID-PRD-GATE-{0:D3}' -f $_ })
+$missingGates = @($expectedGates | Where-Object { $_ -notin $gateIds })
+Add-Check "gates.complete" ($gateIds.Count -eq 7 -and $missingGates.Count -eq 0) "Found $($gateIds.Count) gates; missing: $($missingGates -join ', ')."
+
+$registerDrafting = $register -match '(?m)^\| VID-SPEC-001 \|.*\| Drafting \|$'
+Add-Check "register.status" $registerDrafting "VID-SPEC-001 register row must be Drafting for this candidate."
+
+$dependencyAccepted = $dependency -match '(?m)^Status: Accepted\s*$' -and $dependency -match '(?m)^Revision: 1\.0\s*$'
+Add-Check "dependency.sui-dec-008" $dependencyAccepted "SUI-DEC-008 must be Accepted revision 1.0."
+
+$linkMatches = [regex]::Matches($spec, '\[[^\]]+\]\((?[^)]+)\)')
+$missingLinks = [Collections.Generic.List[string]]::new()
+$specDirectory = Split-Path -Parent $specPath
+foreach ($match in $linkMatches) {
+ $target = $match.Groups['target'].Value.Trim().Trim('<', '>')
+ if ($target -match '^[a-z]+://' -or $target.StartsWith('#')) {
+ continue
+ }
+ $pathPart = $target.Split('#')[0]
+ $candidatePath = [IO.Path]::GetFullPath((Join-Path $specDirectory $pathPart))
+ if (-not (Test-Path -LiteralPath $candidatePath)) {
+ $missingLinks.Add($target)
+ }
+}
+Add-Check "references.local-links" ($missingLinks.Count -eq 0) "Missing local links: $($missingLinks -join ', ')."
+
+$issueLinked = $spec -match 'SubMaRk/video-localization#19'
+Add-Check "traceability.github-issue" $issueLinked "Expected GitHub work item SubMaRk/video-localization#19."
+
+$failed = @($checks | Where-Object { $_.status -eq 'Fail' })
+$candidateHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $specPath).Hash
+$result = [ordered]@{
+ evidence_id = "VID-EVID-001"
+ evidence_revision = "0.1.0-draft.1"
+ status = if ($failed.Count -eq 0) { "Pass" } else { "Fail" }
+ candidate = [ordered]@{
+ specification_id = "VID-SPEC-001"
+ revision = "0.1-draft.1"
+ status = "Drafting"
+ path = "Video Localization/specifications/VID-SPEC-001-foundation-prd.md"
+ sha256 = $candidateHash
+ }
+ summary = [ordered]@{
+ checks = $checks.Count
+ passed = $checks.Count - $failed.Count
+ failed = $failed.Count
+ phase_00_requirements = $expectedF0.Count
+ phase_01_requirements = $expectedF1.Count
+ acceptance_gates = $expectedGates.Count
+ downstream_references = $referencedIds.Count
+ local_links = $linkMatches.Count
+ }
+ checks = $checks
+ approval_boundary = "A machine Pass proves structural traceability only. It does not approve, freeze, implement, or verify VID-SPEC-001."
+}
+
+$outputDirectory = Split-Path -Parent $resolvedOutput
+New-Item -ItemType Directory -Force $outputDirectory | Out-Null
+$result | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $resolvedOutput -Encoding utf8
+
+Write-Output ("VID-EVID-001 {0}: {1}/{2} checks passed; candidate SHA-256 {3}" -f $result.status, $result.summary.passed, $result.summary.checks, $candidateHash)
+if ($failed.Count -gt 0) {
+ exit 1
+}
diff --git a/testing/conformance/vid-spec-002/fixtures/domain-schema-cases.json b/testing/conformance/vid-spec-002/fixtures/domain-schema-cases.json
new file mode 100644
index 0000000..f249b63
--- /dev/null
+++ b/testing/conformance/vid-spec-002/fixtures/domain-schema-cases.json
@@ -0,0 +1,42 @@
+{
+ "manifest_id": "VID-FIX-002",
+ "revision": "1.0.0-review.1",
+ "classification": "synthetic",
+ "redistributable": true,
+ "specification": "VID-SPEC-002@1.0-review.1",
+ "families": ["identity","references","media-authority","timed-text","lineage","observation-approval","migration-recovery","security-boundary"],
+ "cases": [
+ {"id":"VID-002-ID-001","family":"identity","scenario":"move and rename project","expected":"preserve-identities"},
+ {"id":"VID-002-ID-002","family":"identity","scenario":"explicit fork","expected":"new-project-with-provenance"},
+ {"id":"VID-002-ID-003","family":"identity","scenario":"reuse tombstoned cue id","expected":"reject"},
+ {"id":"VID-002-ID-004","family":"identity","scenario":"equal media bytes in independent assets","expected":"retain-logical-identities"},
+ {"id":"VID-002-REF-001","family":"references","scenario":"exact cue revision reference","expected":"resolve-exact"},
+ {"id":"VID-002-REF-002","family":"references","scenario":"stale cue revision","expected":"structured-stale"},
+ {"id":"VID-002-REF-003","family":"references","scenario":"same-name nearby cue","expected":"reject-implicit-rebind"},
+ {"id":"VID-002-REF-004","family":"references","scenario":"wrong-product reference","expected":"read-only-or-refuse"},
+ {"id":"VID-002-MEDIA-001","family":"media-authority","scenario":"verified relink","expected":"accept-command"},
+ {"id":"VID-002-MEDIA-002","family":"media-authority","scenario":"same filename different source","expected":"reject"},
+ {"id":"VID-002-MEDIA-003","family":"media-authority","scenario":"source replacement","expected":"revision-and-stale-impact"},
+ {"id":"VID-002-MEDIA-004","family":"media-authority","scenario":"probe detects stream","expected":"observation-not-support-claim"},
+ {"id":"VID-002-TEXT-001","family":"timed-text","scenario":"Thai combining-mark word reference","expected":"preserve-grapheme-boundary"},
+ {"id":"VID-002-TEXT-002","family":"timed-text","scenario":"VFR cue uses nominal FPS arithmetic","expected":"reject"},
+ {"id":"VID-002-TEXT-003","family":"timed-text","scenario":"locked cue automation request","expected":"reject-without-override"},
+ {"id":"VID-002-TEXT-004","family":"timed-text","scenario":"provider token offered as word","expected":"observation-only"},
+ {"id":"VID-002-LIN-001","family":"lineage","scenario":"one cue split into three","expected":"explicit-one-to-many"},
+ {"id":"VID-002-LIN-002","family":"lineage","scenario":"three cues merged","expected":"explicit-many-to-one"},
+ {"id":"VID-002-LIN-003","family":"lineage","scenario":"infer lineage from overlap","expected":"reject"},
+ {"id":"VID-002-LIN-004","family":"lineage","scenario":"restore old revision","expected":"new-restoration-revision"},
+ {"id":"VID-002-OBS-001","family":"observation-approval","scenario":"worker reports success","expected":"non-authoritative-observation"},
+ {"id":"VID-002-OBS-002","family":"observation-approval","scenario":"human accepts selected fields","expected":"authorized-new-revision"},
+ {"id":"VID-002-OBS-003","family":"observation-approval","scenario":"plugin writes project store","expected":"reject"},
+ {"id":"VID-002-OBS-004","family":"observation-approval","scenario":"speaker diarization label","expected":"no-biometric-claim"},
+ {"id":"VID-002-MIG-001","family":"migration-recovery","scenario":"interrupted migration","expected":"old-or-validated-new-readable"},
+ {"id":"VID-002-MIG-002","family":"migration-recovery","scenario":"unknown required annotation","expected":"reject-write"},
+ {"id":"VID-002-MIG-003","family":"migration-recovery","scenario":"damaged rebuildable cache","expected":"quarantine-and-rebuild"},
+ {"id":"VID-002-MIG-004","family":"migration-recovery","scenario":"missing external media","expected":"project-readable-media-unavailable"},
+ {"id":"VID-002-SEC-001","family":"security-boundary","scenario":"credential in project record","expected":"reject"},
+ {"id":"VID-002-SEC-002","family":"security-boundary","scenario":"annotation triggers URL","expected":"reject-automatic-egress"},
+ {"id":"VID-002-SEC-003","family":"security-boundary","scenario":"Shared infers cue mutation","expected":"reject"},
+ {"id":"VID-002-SEC-004","family":"security-boundary","scenario":"Video imports Manga domain type","expected":"reject"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-002/verify.ps1 b/testing/conformance/vid-spec-002/verify.ps1
new file mode 100644
index 0000000..ad4268d
--- /dev/null
+++ b/testing/conformance/vid-spec-002/verify.ps1
@@ -0,0 +1,30 @@
+param([switch]$NoWriteEvidence)
+$ErrorActionPreference='Stop'
+$workspace=(Resolve-Path (Join-Path $PSScriptRoot '../../../..')).Path
+$specPath=Join-Path $workspace 'Video Localization/specifications/VID-SPEC-002-domain-schema.md'
+$registerPath=Join-Path $workspace '_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md'
+$fixturePath=Join-Path $PSScriptRoot 'fixtures/domain-schema-cases.json'
+$evidencePath=Join-Path $workspace 'Video Localization/specifications/evidence/VID-SPEC-002/VID-EVID-002-reference-result.json'
+$spec=Get-Content $specPath -Raw;$register=Get-Content $registerPath -Raw;$fixture=Get-Content $fixturePath -Raw|ConvertFrom-Json
+$records=@('Video Project Record','Collection Record','Media Asset and Source Authority','Track Record','Cue Record','Word Record','Speaker Record','Annotation Record','Observation and Proposal Record','Video Revision and Lineage','Artifact and Delivery References')
+$gates=1..12|ForEach-Object{'VID-DOM-{0:D3}' -f $_};$families=@('identity','references','media-authority','timed-text','lineage','observation-approval','migration-recovery','security-boundary')
+$links=[regex]::Matches($spec,'\[[^\]]+\]\(([^)]+)\)')|ForEach-Object{$_.Groups[1].Value}|Where-Object{$_ -notmatch '^https?://'};$missing=@($links|Where-Object{-not(Test-Path (Join-Path (Split-Path $specPath) $_))});$ids=@($fixture.cases|ForEach-Object id)
+$checks=[ordered]@{
+ one_h1=(([regex]::Matches($spec,'(?m)^# ')).Count -eq 1)
+ candidate_status=($spec -match '\| Status \| In Review \|' -and $spec -match '\| Revision \| `1\.0-review\.1` \|')
+ dependencies_frozen=($register -match '(?m)^\| SUI-SPEC-001 .*\| Frozen \|$' -and $register -match '(?m)^\| VID-SPEC-001 .*\| Frozen \|$')
+ namespace_bound=($spec -match 'submark\.video-localization')
+ record_inventory=(@($records|Where-Object{$spec -notmatch [regex]::Escape("## $_")}).Count -eq 0)
+ exact_gate_inventory=(@($gates|Where-Object{$spec -notmatch [regex]::Escape($_)}).Count -eq 0)
+ normative_coverage=(([regex]::Matches($spec,'\bMUST(?: NOT)?\b')).Count -ge 70)
+ local_links_resolve=($missing.Count -eq 0)
+ no_placeholders=($spec -notmatch 'TODO|TBD|FIXME')
+ fixture_coverage=($fixture.classification -eq 'synthetic' -and $fixture.redistributable -eq $true -and @($fixture.cases).Count -ge 32 -and @($families|Where-Object{$_ -notin $fixture.families}).Count -eq 0)
+ fixture_integrity=($ids.Count -eq @($ids|Sort-Object -Unique).Count)
+ authority_boundary=($spec -match 'Shared Core MUST NOT infer cue timing' -and $spec -match 'worker.*MUST NOT directly mutate authoritative Video state')
+ observation_boundary=($spec -match 'Successful execution MUST NOT imply human approval' -and $spec -match 'Authority class fixed as non-authoritative')
+ timing_boundary=($spec -match 'MUST NOT be derived from `frame_index / nominal_fps`' -and $spec -match 'belongs to `VID-SPEC-003`')
+ product_isolation=($spec -match 'Video MUST NOT import Manga or Document domain schemas')
+}
+$failed=@($checks.GetEnumerator()|Where-Object{-not $_.Value}|ForEach-Object Name);$result=[ordered]@{evidence_id='VID-EVID-002';revision='1.0.0-review.1';status=$(if($failed.Count){'Fail'}else{'Pass'});specification=[ordered]@{id='VID-SPEC-002';revision='1.0-review.1';sha256=(Get-FileHash $specPath -Algorithm SHA256).Hash};dependencies=@([ordered]@{id='SUI-SPEC-001';revision='1.0';status='Frozen'},[ordered]@{id='VID-SPEC-001';revision='1.0';status='Frozen'});fixture_manifest=[ordered]@{id=$fixture.manifest_id;sha256=(Get-FileHash $fixturePath -Algorithm SHA256).Hash;families=@($fixture.families).Count;cases=@($fixture.cases).Count};checks=$checks;summary=[ordered]@{passed=$checks.Count-$failed.Count;total=$checks.Count;failures=$failed};limitations=@('no-native-database','no-media-library','no-timebase-math','no-hostile-ingest-runtime','no-crash-migration-run','no-product-adapter-run')}
+$json=$result|ConvertTo-Json -Depth 12;if(-not $NoWriteEvidence){New-Item -ItemType Directory -Force -Path (Split-Path $evidencePath)|Out-Null;[IO.File]::WriteAllText($evidencePath,$json+"`n",[Text.UTF8Encoding]::new($false))};$json;if($failed.Count){exit 1}
diff --git a/testing/conformance/vid-spec-003/fixtures/time-coordinate-cases.json b/testing/conformance/vid-spec-003/fixtures/time-coordinate-cases.json
new file mode 100644
index 0000000..08c0f89
--- /dev/null
+++ b/testing/conformance/vid-spec-003/fixtures/time-coordinate-cases.json
@@ -0,0 +1,38 @@
+{
+ "manifest_id":"VID-FIX-003","revision":"1.0.0-review.1","classification":"synthetic","redistributable":true,"specification":"VID-SPEC-003@1.0-review.1",
+ "families":["rational","conversion","cfr","vfr","timecode","audio","proxy-map","spatial"],
+ "cases":[
+ {"id":"VID-003-RAT-001","family":"rational","scenario":"1001/30000 seconds per tick","expected":"accept-normalized"},
+ {"id":"VID-003-RAT-002","family":"rational","scenario":"2002/60000","expected":"normalize-to-1001/30000"},
+ {"id":"VID-003-RAT-003","family":"rational","scenario":"zero denominator","expected":"reject"},
+ {"id":"VID-003-RAT-004","family":"rational","scenario":"checked multiplication overflow","expected":"reject"},
+ {"id":"VID-003-CONV-001","family":"conversion","scenario":"exact 90kHz to milliseconds","expected":"exact"},
+ {"id":"VID-003-CONV-002","family":"conversion","scenario":"fractional nearest ties even","expected":"deterministic"},
+ {"id":"VID-003-CONV-003","family":"conversion","scenario":"range outward conversion","expected":"start-floor-end-ceiling"},
+ {"id":"VID-003-CONV-004","family":"conversion","scenario":"iterative rounded conversion","expected":"reject-as-authority"},
+ {"id":"VID-003-CFR-001","family":"cfr","scenario":"24000/1001 frame 100000","expected":"exact-rational-pts"},
+ {"id":"VID-003-CFR-002","family":"cfr","scenario":"decimal 23.98 as authority","expected":"reject"},
+ {"id":"VID-003-CFR-003","family":"cfr","scenario":"touching half-open frames","expected":"no-overlap"},
+ {"id":"VID-003-CFR-004","family":"cfr","scenario":"frame index on stale timeline","expected":"reject"},
+ {"id":"VID-003-VFR-001","family":"vfr","scenario":"irregular ordered PTS","expected":"index-and-seek"},
+ {"id":"VID-003-VFR-002","family":"vfr","scenario":"container rate disagrees with packet timing","expected":"classify-from-profile-evidence"},
+ {"id":"VID-003-VFR-003","family":"vfr","scenario":"duplicate uncertain PTS","expected":"structured-uncertain"},
+ {"id":"VID-003-VFR-004","family":"vfr","scenario":"late long-media cue uses nominal FPS","expected":"reject"},
+ {"id":"VID-003-TC-001","family":"timecode","scenario":"30000/1001 drop-frame valid label","expected":"round-trip-frame"},
+ {"id":"VID-003-TC-002","family":"timecode","scenario":"skipped drop-frame label","expected":"reject"},
+ {"id":"VID-003-TC-003","family":"timecode","scenario":"profile-free semicolon label","expected":"ambiguous"},
+ {"id":"VID-003-TC-004","family":"timecode","scenario":"drop-frame display conversion","expected":"media-time-unchanged"},
+ {"id":"VID-003-AUD-001","family":"audio","scenario":"48000Hz sample to PTS","expected":"exact-rational"},
+ {"id":"VID-003-AUD-002","family":"audio","scenario":"44100 to 48000 resample","expected":"new-map-with-delay"},
+ {"id":"VID-003-AUD-003","family":"audio","scenario":"waveform tile stale source","expected":"reject"},
+ {"id":"VID-003-AUD-004","family":"audio","scenario":"clock correction without policy","expected":"reject"},
+ {"id":"VID-003-MAP-001","family":"proxy-map","scenario":"VFR source to CFR proxy","expected":"monotonic-inspectable-map"},
+ {"id":"VID-003-MAP-002","family":"proxy-map","scenario":"commit cue on proxy tick","expected":"map-to-source-before-commit"},
+ {"id":"VID-003-MAP-003","family":"proxy-map","scenario":"regenerated proxy","expected":"new-timeline-and-map"},
+ {"id":"VID-003-MAP-004","family":"proxy-map","scenario":"nonmonotonic map segment","expected":"reject"},
+ {"id":"VID-003-SPA-001","family":"spatial","scenario":"normalized top-left rectangle","expected":"accept"},
+ {"id":"VID-003-SPA-002","family":"spatial","scenario":"crop then rotate versus rotate then crop","expected":"distinct-ordered-transforms"},
+ {"id":"VID-003-SPA-003","family":"spatial","scenario":"out-of-bounds implicit clamp","expected":"reject"},
+ {"id":"VID-003-SPA-004","family":"spatial","scenario":"non-invertible stale transform","expected":"structured-incompatibility"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-003/verify.ps1 b/testing/conformance/vid-spec-003/verify.ps1
new file mode 100644
index 0000000..1aa840c
--- /dev/null
+++ b/testing/conformance/vid-spec-003/verify.ps1
@@ -0,0 +1,6 @@
+param([switch]$NoWriteEvidence)
+$ErrorActionPreference='Stop';$workspace=(Resolve-Path (Join-Path $PSScriptRoot '../../../..')).Path
+$specPath=Join-Path $workspace 'Video Localization/specifications/VID-SPEC-003-timebase-and-coordinates.md';$registerPath=Join-Path $workspace '_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md';$fixturePath=Join-Path $PSScriptRoot 'fixtures/time-coordinate-cases.json';$evidencePath=Join-Path $workspace 'Video Localization/specifications/evidence/VID-SPEC-003/VID-EVID-003-reference-result.json'
+$spec=Get-Content $specPath -Raw;$reg=Get-Content $registerPath -Raw;$fix=Get-Content $fixturePath -Raw|ConvertFrom-Json;$gates=@('VID-TIME-001','VID-TIME-002','VID-TIME-003','VID-TIME-004','VID-TIME-005','VID-TIME-006','VID-TIME-007','VID-TIME-008','VID-SPATIAL-001','VID-SPATIAL-002','VID-SPATIAL-003','VID-TIME-009');$families=@('rational','conversion','cfr','vfr','timecode','audio','proxy-map','spatial');$links=[regex]::Matches($spec,'\[[^\]]+\]\(([^)]+)\)')|ForEach-Object{$_.Groups[1].Value}|Where-Object{$_ -notmatch '^https?://'};$missing=@($links|Where-Object{-not(Test-Path (Join-Path (Split-Path $specPath) $_))});$ids=@($fix.cases|ForEach-Object id)
+$c=[ordered]@{one_h1=(([regex]::Matches($spec,'(?m)^# ')).Count -eq 1);candidate_status=($spec -match '\| Status \| In Review \|' -and $spec -match '\| Revision \| `1\.0-review\.1` \|');dependency_frozen=($reg -match '(?m)^\| VID-SPEC-002 .*\| Frozen \|$');local_links_resolve=($missing.Count -eq 0);no_placeholders=($spec -notmatch 'TODO|TBD|FIXME');gate_inventory=(@($gates|Where-Object{$spec -notmatch [regex]::Escape($_)}).Count -eq 0);normative_coverage=(([regex]::Matches($spec,'\bMUST(?: NOT)?\b')).Count -ge 60);fixture_coverage=($fix.classification -eq 'synthetic' -and $fix.redistributable -eq $true -and @($fix.cases).Count -eq 32 -and @($families|Where-Object{$_ -notin $fix.families}).Count -eq 0);fixture_integrity=($ids.Count -eq @($ids|Sort-Object -Unique).Count);rational_semantics=($spec -match 'One tick represents `n / d` seconds' -and $spec -match 'gcd\(n,d\) = 1');vfr_no_nominal=($spec -match 'MUST NOT fall back silently to nominal FPS arithmetic' -and $spec -match 'frame_index / nominal_fps');drop_frame_display_only=($spec -match 'changes display labels, not media time');proxy_source_authority=($spec -match 'map back to source PTS before committing');spatial_exact=($spec -match 'binary floating-point values MUST NOT be durable authority' -and $spec -match 'Transform order is significant');bounds_security=($spec -match 'Arithmetic overflow, division by zero' -and $spec -match 'bounded before allocation')}
+$failed=@($c.GetEnumerator()|Where-Object{-not $_.Value}|ForEach-Object Name);$r=[ordered]@{evidence_id='VID-EVID-003';revision='1.0.0-review.1';status=$(if($failed.Count){'Fail'}else{'Pass'});specification=[ordered]@{id='VID-SPEC-003';revision='1.0-review.1';sha256=(Get-FileHash $specPath -Algorithm SHA256).Hash};dependency=[ordered]@{id='VID-SPEC-002';revision='1.0';status='Frozen'};fixture_manifest=[ordered]@{id=$fix.manifest_id;sha256=(Get-FileHash $fixturePath -Algorithm SHA256).Hash;families=@($fix.families).Count;cases=@($fix.cases).Count};checks=$c;summary=[ordered]@{passed=$c.Count-$failed.Count;total=$c.Count;failures=$failed};limitations=@('no-native-decoder','no-native-seek-playback','no-proxy-generation','no-audio-render','no-long-media-runtime','no-fault-injection-runtime')};$json=$r|ConvertTo-Json -Depth 12;if(-not $NoWriteEvidence){New-Item -ItemType Directory -Force -Path (Split-Path $evidencePath)|Out-Null;[IO.File]::WriteAllText($evidencePath,$json+"`n",[Text.UTF8Encoding]::new($false))};$json;if($failed.Count){exit 1}
diff --git a/testing/conformance/vid-spec-004/fixtures/hostile-ingest-cases.json b/testing/conformance/vid-spec-004/fixtures/hostile-ingest-cases.json
new file mode 100644
index 0000000..2551dac
--- /dev/null
+++ b/testing/conformance/vid-spec-004/fixtures/hostile-ingest-cases.json
@@ -0,0 +1,49 @@
+{
+ "manifest_id": "VID-FIX-004",
+ "revision": "1.0.0-review.1",
+ "classification": "synthetic",
+ "redistributable": true,
+ "families": ["container","timestamp","protocol","path","archive","subtitle","font","optical","worker","publication"],
+ "cases": [
+ {"id":"VID-HI-CON-001","family":"container","input":"extension-signature mismatch","expected":"reject"},
+ {"id":"VID-HI-CON-002","family":"container","input":"257 streams","expected":"limit"},
+ {"id":"VID-HI-CON-003","family":"container","input":"frame pixel multiplication overflow","expected":"reject"},
+ {"id":"VID-HI-CON-004","family":"container","input":"unsupported valid codec","expected":"unsupported"},
+ {"id":"VID-HI-TIM-001","family":"timestamp","input":"zero rational denominator","expected":"reject"},
+ {"id":"VID-HI-TIM-002","family":"timestamp","input":"PTS arithmetic overflow","expected":"reject"},
+ {"id":"VID-HI-TIM-003","family":"timestamp","input":"VFR nominal-FPS fallback request","expected":"reject"},
+ {"id":"VID-HI-TIM-004","family":"timestamp","input":"contradictory packet timing","expected":"review"},
+ {"id":"VID-HI-PRO-001","family":"protocol","input":"HTTP playlist member","expected":"reject"},
+ {"id":"VID-HI-PRO-002","family":"protocol","input":"file protocol escape","expected":"reject"},
+ {"id":"VID-HI-PRO-003","family":"protocol","input":"XML external entity","expected":"reject"},
+ {"id":"VID-HI-PRO-004","family":"protocol","input":"device capture URL","expected":"reject"},
+ {"id":"VID-HI-PATH-001","family":"path","input":"parent-relative attachment","expected":"reject"},
+ {"id":"VID-HI-PATH-002","family":"path","input":"Windows device path","expected":"reject"},
+ {"id":"VID-HI-PATH-003","family":"path","input":"duplicate normalized name","expected":"reject"},
+ {"id":"VID-HI-PATH-004","family":"path","input":"shell metacharacters in source name","expected":"safe-structured"},
+ {"id":"VID-HI-ARC-001","family":"archive","input":"four nested archive layers","expected":"limit"},
+ {"id":"VID-HI-ARC-002","family":"archive","input":"expanded output over declared ceiling","expected":"quarantine"},
+ {"id":"VID-HI-ARC-003","family":"archive","input":"symlink member","expected":"reject"},
+ {"id":"VID-HI-ARC-004","family":"archive","input":"case-colliding members","expected":"reject"},
+ {"id":"VID-HI-SUB-001","family":"subtitle","input":"ASS command-like override","expected":"data-only"},
+ {"id":"VID-HI-SUB-002","family":"subtitle","input":"markup depth 65","expected":"limit"},
+ {"id":"VID-HI-SUB-003","family":"subtitle","input":"one cue over 1 MiB UTF-8","expected":"limit"},
+ {"id":"VID-HI-SUB-004","family":"subtitle","input":"invalid encoding sequence","expected":"review"},
+ {"id":"VID-HI-FNT-001","family":"font","input":"malformed table offset","expected":"quarantine"},
+ {"id":"VID-HI-FNT-002","family":"font","input":"external font URL","expected":"reject"},
+ {"id":"VID-HI-FNT-003","family":"font","input":"font with executable attachment","expected":"quarantine"},
+ {"id":"VID-HI-FNT-004","family":"font","input":"unknown embedding rights","expected":"review"},
+ {"id":"VID-HI-OPT-001","family":"optical","input":"CSS-protected DVD","expected":"protected"},
+ {"id":"VID-HI-OPT-002","family":"optical","input":"AACS-protected BDMV","expected":"protected"},
+ {"id":"VID-HI-OPT-003","family":"optical","input":"playlist path outside root","expected":"reject"},
+ {"id":"VID-HI-OPT-004","family":"optical","input":"cyclic playlist reference","expected":"reject"},
+ {"id":"VID-HI-WRK-001","family":"worker","input":"project database write request","expected":"reject"},
+ {"id":"VID-HI-WRK-002","family":"worker","input":"undeclared child process","expected":"terminate"},
+ {"id":"VID-HI-WRK-003","family":"worker","input":"network access attempt","expected":"reject"},
+ {"id":"VID-HI-WRK-004","family":"worker","input":"cancellation exceeds 5 seconds","expected":"terminate"},
+ {"id":"VID-HI-PUB-001","family":"publication","input":"partial output after crash","expected":"quarantine"},
+ {"id":"VID-HI-PUB-002","family":"publication","input":"stale worker epoch","expected":"reject"},
+ {"id":"VID-HI-PUB-003","family":"publication","input":"source hash mismatch","expected":"reject"},
+ {"id":"VID-HI-PUB-004","family":"publication","input":"unexpected executable output","expected":"quarantine"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-004/verify.ps1 b/testing/conformance/vid-spec-004/verify.ps1
new file mode 100644
index 0000000..0a7da34
--- /dev/null
+++ b/testing/conformance/vid-spec-004/verify.ps1
@@ -0,0 +1,42 @@
+param([switch]$NoWriteEvidence)
+
+$ErrorActionPreference='Stop'
+$workspace=(Resolve-Path (Join-Path $PSScriptRoot '../../../..')).Path
+$specPath=Join-Path $workspace 'Video Localization/specifications/VID-SPEC-004-media-ingest-threat-model.md'
+$registerPath=Join-Path $workspace '_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md'
+$fixturePath=Join-Path $PSScriptRoot 'fixtures/hostile-ingest-cases.json'
+$evidencePath=Join-Path $workspace 'Video Localization/specifications/evidence/VID-SPEC-004/VID-EVID-004-reference-result.json'
+$spec=Get-Content $specPath -Raw
+$register=Get-Content $registerPath -Raw
+$fixture=Get-Content $fixturePath -Raw|ConvertFrom-Json
+$links=[regex]::Matches($spec,'\[[^\]]+\]\(([^)]+)\)')|ForEach-Object{$_.Groups[1].Value}|Where-Object{$_ -notmatch '^https?://'}
+$missing=@($links|Where-Object{-not(Test-Path(Join-Path(Split-Path $specPath) $_))})
+$threatRows=@($spec -split "`r?`n"|Where-Object{$_ -match '^\| VID-MEDIA-THR-\d{3} \|'})
+$gateIds=1..12|ForEach-Object{'VID-MEDIA-SEC-{0:D3}' -f $_}
+$failureCodes=@('VID-INGEST-UNSUPPORTED','VID-INGEST-MALFORMED','VID-INGEST-LIMIT','VID-INGEST-PROTECTED','VID-INGEST-QUARANTINED','VID-INGEST-CANCELLED','VID-INGEST-WORKER-FAILED','VID-INGEST-IDENTITY-MISMATCH','VID-INGEST-REVIEW')
+$families=@('container','timestamp','protocol','path','archive','subtitle','font','optical','worker','publication')
+$ids=@($fixture.cases|ForEach-Object id)
+$expected=@('reject','limit','unsupported','review','safe-structured','quarantine','data-only','protected','terminate')
+$checks=[ordered]@{
+ one_h1=([regex]::Matches($spec,'(?m)^# ')).Count -eq 1
+ candidate_status=$spec -match '\| Status \| In Review \|' -and $spec -match '\| Revision \| `1\.0-review\.1` \|'
+ dependency_frozen=$register -match '(?m)^\| SUI-SPEC-008 .*\| Frozen \|\r?$'
+ local_links_resolve=$missing.Count -eq 0
+ no_placeholders=$spec -notmatch 'TODO|TBD|FIXME'
+ trust_boundaries=(1..6|Where-Object{$spec -notmatch ('(?m)^'+$_+'\. ')}).Count -eq 0
+ security_limits=@('2 TiB','1 MiB','32,768','768 kHz','1,000 hours','10 GiB','2 s cooperative / 5 s total'|Where-Object{$spec -notmatch [regex]::Escape($_)}).Count -eq 0
+ threat_inventory=$threatRows.Count -eq 12
+ threat_mapping=@($threatRows|Where-Object{($_.Trim('|').Split('|')).Count -lt 6}).Count -eq 0
+ failure_contract=@($failureCodes|Where-Object{$spec -notmatch [regex]::Escape($_)}).Count -eq 0
+ gate_inventory=@($gateIds|Where-Object{$spec -notmatch [regex]::Escape($_)}).Count -eq 0
+ unsafe_defaults=$spec -match 'Shell invocation and concatenated command strings are prohibited' -and $spec -match 'DRM, encryption, region control, copy protection, access control, and signature protection are never bypassed'
+ timebase_safety=$spec -match 'MUST NOT derive durable time from nominal FPS' -and $spec -match 'checked arithmetic before allocation'
+ fixture_coverage=$fixture.classification -eq 'synthetic' -and $fixture.redistributable -eq $true -and @($fixture.cases).Count -eq 40 -and @($families|Where-Object{$_ -notin $fixture.families}).Count -eq 0
+ fixture_integrity=$ids.Count -eq @($ids|Sort-Object -Unique).Count -and @($fixture.cases|Where-Object{$_.expected -notin $expected}).Count -eq 0
+}
+$failed=@($checks.GetEnumerator()|Where-Object{-not $_.Value}|ForEach-Object Name)
+$result=[ordered]@{evidence_id='VID-EVID-004';revision='1.0.0-review.1';status=$(if($failed.Count){'Fail'}else{'Pass'});specification=[ordered]@{id='VID-SPEC-004';revision='1.0-review.1';sha256=(Get-FileHash $specPath -Algorithm SHA256).Hash};dependency=[ordered]@{id='SUI-SPEC-008';revision='1.0';status='Frozen'};fixture_manifest=[ordered]@{id=$fixture.manifest_id;sha256=(Get-FileHash $fixturePath -Algorithm SHA256).Hash;families=@($fixture.families).Count;cases=@($fixture.cases).Count};checks=$checks;summary=[ordered]@{passed=$checks.Count-$failed.Count;total=$checks.Count;failures=$failed};limitations=@('planning-control-conformance-only','synthetic-fixtures-only','no-parser-or-decoder-runtime','no-sandbox-enforcement-evidence','no-format-support-claim')}
+$json=$result|ConvertTo-Json -Depth 12
+if(-not $NoWriteEvidence){New-Item -ItemType Directory -Force -Path(Split-Path $evidencePath)|Out-Null;[IO.File]::WriteAllText($evidencePath,$json+"`n",[Text.UTF8Encoding]::new($false))}
+$json
+if($failed.Count){exit 1}
diff --git a/testing/conformance/vid-spec-014/fixtures/design-localization-cases.json b/testing/conformance/vid-spec-014/fixtures/design-localization-cases.json
new file mode 100644
index 0000000..5dd2ffd
--- /dev/null
+++ b/testing/conformance/vid-spec-014/fixtures/design-localization-cases.json
@@ -0,0 +1,38 @@
+{
+ "manifest_id":"VID-FIX-014","revision":"1.0.0-review.1","classification":"synthetic","redistributable":true,
+ "families":["theme","locale","placeholder","thai","bidi","accessibility","ime","layout"],
+ "cases":[
+ {"id":"VID-DL-THM-001","family":"theme","input":"System changes Light to Dark","expected":"state-preserved"},
+ {"id":"VID-DL-THM-002","family":"theme","input":"Light media stage","expected":"neutral-dark"},
+ {"id":"VID-DL-THM-003","family":"theme","input":"Dark media stage","expected":"neutral-dark"},
+ {"id":"VID-DL-THM-004","family":"theme","input":"reduced motion","expected":"nonessential-zero"},
+ {"id":"VID-DL-LOC-001","family":"locale","input":"th-TH catalog lookup","expected":"th-then-en"},
+ {"id":"VID-DL-LOC-002","family":"locale","input":"unsupported locale","expected":"en"},
+ {"id":"VID-DL-LOC-003","family":"locale","input":"language switch during open project","expected":"state-preserved"},
+ {"id":"VID-DL-LOC-004","family":"locale","input":"Thai UI with Latin technical digits","expected":"allowed"},
+ {"id":"VID-DL-PHL-001","family":"placeholder","input":"matching current and total","expected":"pass"},
+ {"id":"VID-DL-PHL-002","family":"placeholder","input":"missing Thai placeholder","expected":"fail"},
+ {"id":"VID-DL-PHL-003","family":"placeholder","input":"extra translated placeholder","expected":"fail"},
+ {"id":"VID-DL-PHL-004","family":"placeholder","input":"concatenated sentence fragments","expected":"fail"},
+ {"id":"VID-DL-THA-001","family":"thai","input":"กำลังแก้ไขคำบรรยาย","expected":"marks-visible"},
+ {"id":"VID-DL-THA-002","family":"thai","input":"ไทย Latin 123 日本語","expected":"mixed-readable"},
+ {"id":"VID-DL-THA-003","family":"thai","input":"no whitespace Thai phrase","expected":"script-break"},
+ {"id":"VID-DL-THA-004","family":"thai","input":"combining-mark cursor deletion","expected":"grapheme-safe"},
+ {"id":"VID-DL-BDI-001","family":"bidi","input":"Arabic title plus timecode","expected":"isolated-timecode"},
+ {"id":"VID-DL-BDI-002","family":"bidi","input":"RTL path plus Latin ID","expected":"stable-id"},
+ {"id":"VID-DL-BDI-003","family":"bidi","input":"pseudo-locale expansion","expected":"no-hidden-action"},
+ {"id":"VID-DL-BDI-004","family":"bidi","input":"mirrored directional icon","expected":"semantic-rule"},
+ {"id":"VID-DL-A11-001","family":"accessibility","input":"keyboard-only workspace traversal","expected":"complete"},
+ {"id":"VID-DL-A11-002","family":"accessibility","input":"focus over media stage","expected":"visible"},
+ {"id":"VID-DL-A11-003","family":"accessibility","input":"High Contrast status","expected":"system-color-plus-label"},
+ {"id":"VID-DL-A11-004","family":"accessibility","input":"200 percent scale","expected":"critical-flow-operable"},
+ {"id":"VID-DL-IME-001","family":"ime","input":"Thai composition consumes shortcut","expected":"no-command"},
+ {"id":"VID-DL-IME-002","family":"ime","input":"composition during locale switch","expected":"safe-commit-or-cancel"},
+ {"id":"VID-DL-IME-003","family":"ime","input":"selection across combining marks","expected":"grapheme-safe"},
+ {"id":"VID-DL-IME-004","family":"ime","input":"undo after committed composition","expected":"one-edit-group"},
+ {"id":"VID-DL-LAY-001","family":"layout","input":"Thai long menu label","expected":"wrap-or-full-tooltip"},
+ {"id":"VID-DL-LAY-002","family":"layout","input":"Compact cue list","expected":"marks-not-clipped"},
+ {"id":"VID-DL-LAY-003","family":"layout","input":"Comfortable error dialog","expected":"action-visible"},
+ {"id":"VID-DL-LAY-004","family":"layout","input":"10k cue projection","expected":"virtualized"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-014/verify.ps1 b/testing/conformance/vid-spec-014/verify.ps1
new file mode 100644
index 0000000..020efb0
--- /dev/null
+++ b/testing/conformance/vid-spec-014/verify.ps1
@@ -0,0 +1,62 @@
+param([switch]$NoWriteEvidence)
+
+$ErrorActionPreference='Stop'
+$workspace=(Resolve-Path(Join-Path $PSScriptRoot '../../../..')).Path
+$specPath=Join-Path $workspace 'Video Localization/specifications/VID-SPEC-014-design-and-localization.md'
+$registerPath=Join-Path $workspace '_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md'
+$tokenPath=Join-Path $workspace 'Video Localization/design/tokens/vid.tokens.json'
+$componentPath=Join-Path $workspace 'Video Localization/design/component-matrix.json'
+$enPath=Join-Path $workspace 'Video Localization/localization/en.json'
+$thPath=Join-Path $workspace 'Video Localization/localization/th.json'
+$termPath=Join-Path $workspace 'Video Localization/localization/terminology.en-th.json'
+$fixturePath=Join-Path $PSScriptRoot 'fixtures/design-localization-cases.json'
+$evidencePath=Join-Path $workspace 'Video Localization/specifications/evidence/VID-SPEC-014/VID-EVID-014-reference-result.json'
+$spec=Get-Content $specPath -Raw;$register=Get-Content $registerPath -Raw
+$tokens=Get-Content $tokenPath -Raw|ConvertFrom-Json;$components=Get-Content $componentPath -Raw|ConvertFrom-Json
+$en=Get-Content $enPath -Raw|ConvertFrom-Json;$th=Get-Content $thPath -Raw|ConvertFrom-Json;$terms=Get-Content $termPath -Raw|ConvertFrom-Json;$fixture=Get-Content $fixturePath -Raw|ConvertFrom-Json
+
+function Linear([int]$v){$s=$v/255.0;if($s-le0.04045){$s/12.92}else{[Math]::Pow(($s+0.055)/1.055,2.4)}}
+function Luminance([string]$hex){$h=$hex.TrimStart('#');0.2126*(Linear([Convert]::ToInt32($h.Substring(0,2),16)))+0.7152*(Linear([Convert]::ToInt32($h.Substring(2,2),16)))+0.0722*(Linear([Convert]::ToInt32($h.Substring(4,2),16)))}
+function Contrast([string]$a,[string]$b){$x=Luminance $a;$y=Luminance $b;if($x-lt$y){$t=$x;$x=$y;$y=$t};($x+0.05)/($y+0.05)}
+function Placeholders([string]$s){@([regex]::Matches($s,'\{[A-Za-z][A-Za-z0-9_]*\}')|ForEach-Object Value|Sort-Object -Unique)}
+
+$contrast=[ordered]@{}
+foreach($theme in @('light','dark')){
+ $c=$tokens.primitive.color.$theme
+ $contrast["$theme.text"]=(Contrast $c.text $c.canvas)-ge4.5
+ $contrast["$theme.muted"]=(Contrast $c.muted $c.canvas)-ge4.5
+ $contrast["$theme.action"]=(Contrast $c.action $c.canvas)-ge4.5
+ $contrast["$theme.onAction"]=(Contrast $c.actionText $c.action)-ge4.5
+ $contrast["$theme.boundary"]=(Contrast $c.border $c.surface)-ge3.0
+ $contrast["$theme.focus"]=(Contrast $c.focus $c.canvas)-ge3.0
+ foreach($state in @('success','warning','error','info')){$contrast["$theme.$state"]=(Contrast $c.$state $c.canvas)-ge4.5}
+}
+$enKeys=@($en.messages.psobject.Properties.Name|Sort-Object);$thKeys=@($th.messages.psobject.Properties.Name|Sort-Object)
+$catalogParity=($enKeys -join "`n")-eq($thKeys -join "`n")
+$placeholderParity=$true
+foreach($key in $enKeys){if(((Placeholders $en.messages.$key.text)-join ',') -ne ((Placeholders $th.messages.$key.text)-join ',')){$placeholderParity=$false;break}}
+$catalogComplete=@($enKeys|Where-Object{[string]::IsNullOrWhiteSpace($en.messages.$_.text)-or[string]::IsNullOrWhiteSpace($en.messages.$_.context)-or[string]::IsNullOrWhiteSpace($th.messages.$_.text)-or[string]::IsNullOrWhiteSpace($th.messages.$_.context)}).Count-eq0
+$links=[regex]::Matches($spec,'\[[^\]]+\]\(([^)]+)\)')|ForEach-Object{$_.Groups[1].Value}|Where-Object{$_-notmatch'^https?://'};$missing=@($links|Where-Object{-not(Test-Path(Join-Path(Split-Path $specPath)$_))})
+$gates=1..12|ForEach-Object{'VID-DESIGN-{0:D3}'-f$_};$families=@('theme','locale','placeholder','thai','bidi','accessibility','ime','layout');$ids=@($fixture.cases|ForEach-Object id)
+$componentJson=$tokens.component|ConvertTo-Json -Depth 12
+$checks=[ordered]@{
+ one_h1=([regex]::Matches($spec,'(?m)^# ')).Count-eq1
+ candidate_status=$spec-match'\| Status \| In Review \|'-and$spec-match'\| Revision \| `1\.0-review\.1` \|'
+ dependencies_ready=$register-match'(?m)^\| SUI-SPEC-007 .*\| Frozen \|\r?$'-and$register-match'(?m)^\| VID-DEC-004 .*\| Accepted \|\r?$'
+ local_links_resolve=$missing.Count-eq0
+ no_placeholders=$spec-notmatch'TODO|TBD|FIXME'
+ three_layer_tokens=$tokens.schema-eq'submark.design-tokens/1.0'-and$tokens.direction-eq'Cinematic Ledger'-and$componentJson-notmatch'primitive\.'
+ theme_contract=$tokens.systemTheme-eq'live-os-light-or-dark'-and$tokens.primitive.color.mediaStage-eq'#0B0B0B'-and$tokens.highContrast-eq'system-color-mapping-required'
+ contrast_pairs=@($contrast.GetEnumerator()|Where-Object{-not$_.Value}).Count-eq0
+ component_matrix=@($components.components).Count-eq18-and@($components.required_states).Count-eq12-and@($components.components|Where-Object{-not$_.keyboard-or-not$_.accessible-or-not$_.bounded}).Count-eq0
+ workspace_matrix=@('edit','timing','translation','review','delivery'|Where-Object{$_-notin$components.workspaces}).Count-eq0-and@($components.components|Where-Object{$_.id-in@('cue-list','timeline','waveform','finding-list','job-queue')-and-not$_.virtualized}).Count-eq0
+ catalog_contract=$en.locale-eq'en'-and$null-eq$en.fallback-and$th.locale-eq'th'-and$th.fallback-eq'en'-and$catalogParity-and$placeholderParity-and$catalogComplete
+ terminology=@($terms.entries).Count-eq15-and@($terms.entries.id|Sort-Object -Unique).Count-eq15-and@($terms.entries|Where-Object{[string]::IsNullOrWhiteSpace($_.en)-or[string]::IsNullOrWhiteSpace($_.th)-or[string]::IsNullOrWhiteSpace($_.context)}).Count-eq0
+ complex_script=$spec-match'Thai, CJK, RTL, mixed script'-and$spec-match'active IME composition MUST NOT invoke editor commands'-and$spec-match'MUST NOT assume whitespace word boundaries'
+ gate_inventory=@($gates|Where-Object{$spec-notmatch[regex]::Escape($_)}).Count-eq0
+ fixture_coverage=$fixture.classification-eq'synthetic'-and$fixture.redistributable-eq$true-and@($fixture.cases).Count-eq32-and@($families|Where-Object{$_-notin$fixture.families}).Count-eq0
+ fixture_integrity=$ids.Count-eq@($ids|Sort-Object -Unique).Count-and@($fixture.cases|Where-Object{[string]::IsNullOrWhiteSpace($_.expected)}).Count-eq0
+}
+$failed=@($checks.GetEnumerator()|Where-Object{-not$_.Value}|ForEach-Object Name)
+$result=[ordered]@{evidence_id='VID-EVID-014';revision='1.0.0-review.1';status=$(if($failed.Count){'Fail'}else{'Pass'});specification=[ordered]@{id='VID-SPEC-014';revision='1.0-review.1';sha256=(Get-FileHash $specPath -Algorithm SHA256).Hash};assets=[ordered]@{tokens=(Get-FileHash $tokenPath -Algorithm SHA256).Hash;components=(Get-FileHash $componentPath -Algorithm SHA256).Hash;english=(Get-FileHash $enPath -Algorithm SHA256).Hash;thai=(Get-FileHash $thPath -Algorithm SHA256).Hash;terminology=(Get-FileHash $termPath -Algorithm SHA256).Hash;fixtures=(Get-FileHash $fixturePath -Algorithm SHA256).Hash};counts=[ordered]@{components=@($components.components).Count;messages=$enKeys.Count;terms=@($terms.entries).Count;fixture_families=@($fixture.families).Count;fixture_cases=@($fixture.cases).Count;contrast_pairs=$contrast.Count};contrast=$contrast;checks=$checks;summary=[ordered]@{passed=$checks.Count-$failed.Count;total=$checks.Count;failures=$failed};limitations=@('no-native-control-evidence','no-rendered-screenshot-evidence','no-font-payload-frozen','no-native-ime-evidence','no-assistive-technology-runtime-evidence','no-ui-performance-evidence')}
+$json=$result|ConvertTo-Json -Depth 12;if(-not$NoWriteEvidence){New-Item -ItemType Directory -Force -Path(Split-Path $evidencePath)|Out-Null;[IO.File]::WriteAllText($evidencePath,$json+"`n",[Text.UTF8Encoding]::new($false))};$json;if($failed.Count){exit 1}
diff --git a/testing/conformance/vid-spec-015/fixtures/fault-matrix.json b/testing/conformance/vid-spec-015/fixtures/fault-matrix.json
new file mode 100644
index 0000000..5f7befe
--- /dev/null
+++ b/testing/conformance/vid-spec-015/fixtures/fault-matrix.json
@@ -0,0 +1,24 @@
+{
+ "matrix_id":"VID-FAULT-015","revision":"1.0.0-review.1",
+ "invariant":"project truth remains readable; no partial artifact is promoted; stable actionable outcome exists",
+ "cases":[
+ {"id":"VID-FAULT-001","class":"worker-crash","boundary":"after staging write before validation","expected":"quarantine-and-reconcile"},
+ {"id":"VID-FAULT-002","class":"worker-hang","boundary":"progress heartbeat","expected":"timeout-terminate-retryable"},
+ {"id":"VID-FAULT-003","class":"cancellation","boundary":"during waveform generation","expected":"within-2s-or-force-by-5s"},
+ {"id":"VID-FAULT-004","class":"malformed-result","boundary":"worker output schema","expected":"reject-and-quarantine"},
+ {"id":"VID-FAULT-005","class":"force-close","boundary":"after command acknowledgement","expected":"rpo-at-most-one-command"},
+ {"id":"VID-FAULT-006","class":"power-loss-surrogate","boundary":"WAL and atomic publication points","expected":"integrity-or-actionable-recovery"},
+ {"id":"VID-FAULT-007","class":"disk-full","boundary":"transaction and staging writes","expected":"no-corrupt-commit-and-reserve-preserved"},
+ {"id":"VID-FAULT-008","class":"permission-loss","boundary":"project and staging filesystem","expected":"safe-read-only-or-actionable-error"},
+ {"id":"VID-FAULT-009","class":"partial-write","boundary":"artifact publication","expected":"never-complete"},
+ {"id":"VID-FAULT-010","class":"damaged-cache","boundary":"cache index and object","expected":"project-readable-and-rebuildable"},
+ {"id":"VID-FAULT-011","class":"migration-failure","boundary":"schema step","expected":"original-and-backup-readable"},
+ {"id":"VID-FAULT-012","class":"missing-media","boundary":"project reopen","expected":"project-readable-and-relink-offered"},
+ {"id":"VID-FAULT-013","class":"relink-mismatch","boundary":"candidate media identity","expected":"review-required-no-silent-relink"},
+ {"id":"VID-FAULT-014","class":"invalid-ipc","boundary":"control envelope","expected":"reject-no-mutation"},
+ {"id":"VID-FAULT-015","class":"memory-exhaustion","boundary":"worker allocation ceiling","expected":"worker-contained-project-readable"},
+ {"id":"VID-FAULT-016","class":"unsafe-protocol","boundary":"media input resolution","expected":"reject-no-network"},
+ {"id":"VID-FAULT-017","class":"stale-worker","boundary":"broker or job epoch","expected":"reject-and-reconcile"},
+ {"id":"VID-FAULT-018","class":"antivirus-interference","boundary":"rename-lock-and-read","expected":"bounded-retry-or-actionable-error"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-015/fixtures/foundation-corpus.json b/testing/conformance/vid-spec-015/fixtures/foundation-corpus.json
new file mode 100644
index 0000000..6941e50
--- /dev/null
+++ b/testing/conformance/vid-spec-015/fixtures/foundation-corpus.json
@@ -0,0 +1,22 @@
+{
+ "manifest_id":"VID-CORPUS-015","revision":"1.0.0-review.1","status":"planned-generators","repository_policy":"synthetic-self-created-public-domain-or-explicitly-redistributable-only",
+ "required_fields":["id","family","generator_or_source","rights_basis","redistributable","privacy","materialization","expected","gate"],
+ "fixtures":[
+ {"id":"VID-CORPUS-CFR-001","family":"timing","generator_or_source":"deterministic color/audio generator","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"exact CFR frame anchors","gate":"VID-VERIFY-009"},
+ {"id":"VID-CORPUS-VFR-001","family":"timing","generator_or_source":"deterministic nonuniform PTS generator","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"late anchors preserve source PTS","gate":"VID-VERIFY-009"},
+ {"id":"VID-CORPUS-TIME-001","family":"timing","generator_or_source":"rational timebase generator","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"no rational drift","gate":"VID-VERIFY-009"},
+ {"id":"VID-CORPUS-SRT-001","family":"interoperability","generator_or_source":"UTF-8 SRT generator","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"declared rounding and line preservation","gate":"VID-VERIFY-013"},
+ {"id":"VID-CORPUS-EN-001","family":"localization","generator_or_source":"normative English catalog","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"present","expected":"source and fallback complete","gate":"VID-VERIFY-011"},
+ {"id":"VID-CORPUS-TH-001","family":"localization","generator_or_source":"normative Thai catalog and combining generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"present","expected":"marks and placeholders preserved","gate":"VID-VERIFY-011"},
+ {"id":"VID-CORPUS-CJK-001","family":"localization","generator_or_source":"Unicode synthetic strings","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"no clipping or data loss","gate":"VID-VERIFY-011"},
+ {"id":"VID-CORPUS-RTL-001","family":"localization","generator_or_source":"Unicode synthetic bidi strings","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"stable IDs and isolated timecode","gate":"VID-VERIFY-011"},
+ {"id":"VID-CORPUS-HOSTILE-001","family":"security","generator_or_source":"VID-FIX-004 synthetic hostile cases","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"present","expected":"safe structured dispositions","gate":"VID-VERIFY-010"},
+ {"id":"VID-CORPUS-MIGRATE-001","family":"migration","generator_or_source":"previous-schema project generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"transactional upgrade and readable backup","gate":"VID-VERIFY-006"},
+ {"id":"VID-CORPUS-FUTURE-001","family":"migration","generator_or_source":"future-schema project generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"read-only refusal without mutation","gate":"VID-VERIFY-006"},
+ {"id":"VID-CORPUS-RECOVERY-001","family":"recovery","generator_or_source":"10000-event project generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"bounded replay and integrity","gate":"VID-VERIFY-007"},
+ {"id":"VID-CORPUS-RELINK-001","family":"recovery","generator_or_source":"media identity and moved-path generator","rights_basis":"self-created synthetic","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"reviewed exact relink","gate":"VID-VERIFY-007"},
+ {"id":"VID-CORPUS-CACHE-001","family":"recovery","generator_or_source":"damaged rebuildable-cache generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"project readable and cache rebuildable","gate":"VID-VERIFY-007"},
+ {"id":"VID-CORPUS-IPC-001","family":"shared-adapter","generator_or_source":"bounded generated contract fixtures","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"typed compatible round trip","gate":"VID-VERIFY-012"},
+ {"id":"VID-CORPUS-LONG-001","family":"scale","generator_or_source":"1000-cue 10000-event foundation generator","rights_basis":"project-authored","redistributable":true,"privacy":"public-test","materialization":"planned","expected":"foundation budgets only","gate":"VID-VERIFY-005"}
+ ]
+}
diff --git a/testing/conformance/vid-spec-015/profiles/environment-matrix.json b/testing/conformance/vid-spec-015/profiles/environment-matrix.json
new file mode 100644
index 0000000..626b19a
--- /dev/null
+++ b/testing/conformance/vid-spec-015/profiles/environment-matrix.json
@@ -0,0 +1,10 @@
+{
+ "matrix_id":"VID-ENV-015","revision":"1.0.0-review.1","qualification_date":"2026-08-02",
+ "profiles":[
+ {"id":"WIN11-X64-BASE","required":"entry-and-exit","os":"Windows 11","versions":["24H2","25H2"],"serviced_build_required":true,"architecture":"x64","physical_cores":4,"logical_cores":8,"ram_gib":16,"display":"1920x1080","filesystem":"NTFS-local","free_disk_gib":100,"gpu":"integrated-or-software-capable","network_required":false},
+ {"id":"WIN11-X64-REF","required":"reference","os":"Windows 11","versions":["25H2"],"serviced_build_required":true,"architecture":"x64","physical_cores":8,"ram_gib":32,"display":"2560x1440","filesystem":"NTFS-local-NVMe","gpu":"Direct3D-12-capable","network_required":false},
+ {"id":"WIN11-X64-LOW","required":"safe-failure-only","os":"Windows 11","versions":["24H2","25H2"],"serviced_build_required":true,"architecture":"x64","logical_cores":4,"ram_gib":8,"display":"1366x768","filesystem":"NTFS-local","free_disk_gib":20,"network_required":false}
+ ],
+ "axes":{"locales":["en","th"],"themes":["System","Light","Dark","High Contrast"],"densities":["Compact","Comfortable"],"scale_percent":[100,150,200],"package_modes":["internal-unpackaged","developer-signed-MSIX-later-evidence"]},
+ "non_qualifying":["Windows Insider or preview build","ARM64","macOS","Linux","active project database on network or removable volume","remote-desktop support claim","virtualized-GPU support claim"]
+}
diff --git a/testing/conformance/vid-spec-015/profiles/foundation-budgets.json b/testing/conformance/vid-spec-015/profiles/foundation-budgets.json
new file mode 100644
index 0000000..7a0b484
--- /dev/null
+++ b/testing/conformance/vid-spec-015/profiles/foundation-budgets.json
@@ -0,0 +1,22 @@
+{
+ "budget_id":"VID-BUDGET-015","revision":"1.0.0-review.1","profile":"WIN11-X64-BASE",
+ "method":{"warm_iterations":30,"warmups":5,"cold_start_iterations":10,"latency_statistic":"p95"},
+ "budgets":[
+ {"id":"VID-PERF-001","metric":"cold application start to interactive shell","workload":"VID-WL-PROJECT-SMALL","threshold":3,"unit":"seconds","statistic":"p95"},
+ {"id":"VID-PERF-002","metric":"create and durably save project","workload":"VID-WL-PROJECT-SMALL","threshold":1,"unit":"seconds","statistic":"p95"},
+ {"id":"VID-PERF-003","metric":"open foundation project","workload":"VID-WL-PROJECT-FOUNDATION","threshold":3,"unit":"seconds","statistic":"p95"},
+ {"id":"VID-PERF-004","metric":"committed interactive command acknowledgement","workload":"VID-WL-PROJECT-FOUNDATION","threshold":100,"unit":"milliseconds","statistic":"p95"},
+ {"id":"VID-PERF-005","metric":"replay 10000 events","workload":"VID-WL-RECOVERY","threshold":2,"unit":"seconds","statistic":"p95"},
+ {"id":"VID-PERF-006","metric":"theme or language switch with state preserved","workload":"VID-WL-LOCALE","threshold":500,"unit":"milliseconds","statistic":"p95"},
+ {"id":"VID-PERF-007","metric":"local control IPC round trip","workload":"bounded 1 KiB command","threshold":50,"unit":"milliseconds","statistic":"p95"},
+ {"id":"VID-PERF-008","metric":"idle UI presentation frame","workload":"VID-WL-PROJECT-FOUNDATION","threshold":16.7,"unit":"milliseconds","statistic":"p95"},
+ {"id":"VID-PERF-009","metric":"UI presentation frame under one background worker","workload":"VID-WL-WAVEFORM","threshold":33.3,"unit":"milliseconds","statistic":"p95"},
+ {"id":"VID-PERF-010","metric":"cooperative cancellation observed","workload":"VID-WL-WAVEFORM","threshold":2,"unit":"seconds","statistic":"max"},
+ {"id":"VID-PERF-011","metric":"forced worker termination and reconciliation","workload":"VID-WL-WAVEFORM","threshold":5,"unit":"seconds","statistic":"max"},
+ {"id":"VID-PERF-012","metric":"idle resident memory","workload":"interactive shell","threshold":768,"unit":"MiB","statistic":"p95"},
+ {"id":"VID-PERF-013","metric":"foundation peak resident memory","workload":"VID-WL-PROJECT-FOUNDATION plus one worker","threshold":4096,"unit":"MiB","statistic":"max"},
+ {"id":"VID-PERF-014","metric":"startup recovery reconciliation","workload":"VID-WL-RECOVERY","threshold":10,"unit":"seconds","statistic":"p95"},
+ {"id":"VID-PERF-015","metric":"acknowledged commands lost after force close","workload":"VID-WL-RECOVERY","threshold":1,"unit":"commands","statistic":"max"}
+ ],
+ "storage":{"recovery_reserve_gib":10,"default_rebuildable_cache_quota_gib":20,"temporary_ceiling":"declared operation estimate plus 10 percent within VID-SPEC-004 hard ceiling"}
+}
diff --git a/testing/conformance/vid-spec-015/verify.ps1 b/testing/conformance/vid-spec-015/verify.ps1
new file mode 100644
index 0000000..9b9bf50
--- /dev/null
+++ b/testing/conformance/vid-spec-015/verify.ps1
@@ -0,0 +1,46 @@
+param([switch]$NoWriteEvidence)
+
+$ErrorActionPreference='Stop'
+$workspace=(Resolve-Path(Join-Path $PSScriptRoot '../../../..')).Path
+$specPath=Join-Path $workspace 'Video Localization/specifications/VID-SPEC-015-foundation-verification.md'
+$registerPath=Join-Path $workspace '_shared/Plan/sections/09_SPECIFICATION_AND_DECISION_REGISTER.md'
+$environmentPath=Join-Path $PSScriptRoot 'profiles/environment-matrix.json'
+$budgetPath=Join-Path $PSScriptRoot 'profiles/foundation-budgets.json'
+$corpusPath=Join-Path $PSScriptRoot 'fixtures/foundation-corpus.json'
+$faultPath=Join-Path $PSScriptRoot 'fixtures/fault-matrix.json'
+$evidencePath=Join-Path $workspace 'Video Localization/specifications/evidence/VID-SPEC-015/VID-EVID-015-entry-result.json'
+$spec=Get-Content $specPath -Raw;$register=Get-Content $registerPath -Raw
+$environment=Get-Content $environmentPath -Raw|ConvertFrom-Json;$budgets=Get-Content $budgetPath -Raw|ConvertFrom-Json;$corpus=Get-Content $corpusPath -Raw|ConvertFrom-Json;$faults=Get-Content $faultPath -Raw|ConvertFrom-Json
+$links=[regex]::Matches($spec,'\[[^\]]+\]\(([^)]+)\)')|ForEach-Object{$_.Groups[1].Value}|Where-Object{$_-notmatch'^https?://'};$missing=@($links|Where-Object{-not(Test-Path(Join-Path(Split-Path $specPath)$_))})
+$deps=@('VID-SPEC-001','VID-SPEC-002','VID-SPEC-003','VID-SPEC-004','VID-SPEC-014')
+$gates=1..14|ForEach-Object{'VID-VERIFY-{0:D3}'-f$_}
+$workloads=@('VID-WL-PROJECT-SMALL','VID-WL-PROJECT-FOUNDATION','VID-WL-MEDIA-CFR','VID-WL-MEDIA-VFR','VID-WL-WAVEFORM','VID-WL-LOCALE','VID-WL-RECOVERY')
+$requiredFaults=@('worker-crash','worker-hang','cancellation','malformed-result','force-close','power-loss-surrogate','disk-full','permission-loss','partial-write','damaged-cache','migration-failure','missing-media','relink-mismatch','invalid-ipc','memory-exhaustion','unsafe-protocol','stale-worker','antivirus-interference')
+$envIds=@($environment.profiles|ForEach-Object id);$budgetIds=@($budgets.budgets|ForEach-Object id);$corpusIds=@($corpus.fixtures|ForEach-Object id);$faultIds=@($faults.cases|ForEach-Object id)
+$base=@($environment.profiles|Where-Object id -eq 'WIN11-X64-BASE')[0];$low=@($environment.profiles|Where-Object id -eq 'WIN11-X64-LOW')[0]
+$rpo=@($budgets.budgets|Where-Object id -eq 'VID-PERF-015')[0]
+$corpusFieldsOk=@($corpus.fixtures|Where-Object{[string]::IsNullOrWhiteSpace($_.id)-or[string]::IsNullOrWhiteSpace($_.family)-or[string]::IsNullOrWhiteSpace($_.generator_or_source)-or[string]::IsNullOrWhiteSpace($_.rights_basis)-or$null-eq$_.redistributable-or[string]::IsNullOrWhiteSpace($_.privacy)-or[string]::IsNullOrWhiteSpace($_.materialization)-or[string]::IsNullOrWhiteSpace($_.expected)-or[string]::IsNullOrWhiteSpace($_.gate)}).Count-eq0
+$checks=[ordered]@{
+ one_h1=([regex]::Matches($spec,'(?m)^# ')).Count-eq1
+ candidate_status=$spec-match'\| Status \| In Review \|'-and$spec-match'\| Revision \| `1\.0-review\.1` \|'
+ dependencies_frozen=@($deps|Where-Object{$register-notmatch('(?m)^\| '+[regex]::Escape($_)+' .*\| Frozen \|\r?$')}).Count-eq0
+ local_links_resolve=$missing.Count-eq0
+ no_placeholders=$spec-notmatch'TODO|TBD|FIXME'
+ evidence_levels=@('Entry','Implemented','Verified','Exit'|Where-Object{$spec-notmatch('\| `'+$_+'` \|')}).Count-eq0-and$spec-match'Synthetic planning checks MUST NOT be labeled runtime verification'
+ release_allocation=$spec-match'v0\.1\.0-alpha'-and$spec-match'one later-promoted baseline media profile'-and$spec-match'Professional editor completeness, broad formats, proxy/burn-in, AI, Teams, plugins, visual text, public distribution'
+ environment_matrix=@($environment.profiles).Count-eq3-and$envIds.Count-eq@($envIds|Sort-Object -Unique).Count-and$base.architecture-eq'x64'-and$base.ram_gib-eq16-and-not$base.network_required-and$low.required-eq'safe-failure-only'
+ environment_axes=@('en','th'|Where-Object{$_-notin$environment.axes.locales}).Count-eq0-and@('System','Light','Dark','High Contrast'|Where-Object{$_-notin$environment.axes.themes}).Count-eq0-and@(100,150,200|Where-Object{$_-notin$environment.axes.scale_percent}).Count-eq0
+ budget_contract=@($budgets.budgets).Count-eq15-and$budgetIds.Count-eq@($budgetIds|Sort-Object -Unique).Count-and$budgets.method.warm_iterations-eq30-and$budgets.method.cold_start_iterations-eq10-and$rpo.threshold-eq1-and$rpo.unit-eq'commands'
+ workload_inventory=@($workloads|Where-Object{$spec-notmatch[regex]::Escape($_)}).Count-eq0
+ corpus_governance=@($corpus.fixtures).Count-eq16-and$corpusIds.Count-eq@($corpusIds|Sort-Object -Unique).Count-and$corpusFieldsOk-and@($corpus.fixtures|Where-Object{$_.privacy-ne'public-test'-or-not$_.redistributable-or$_.materialization-notin@('planned','present')}).Count-eq0
+ private_asset_deny=$spec-match'Private customer media, copyrighted commercial media, restricted subtitle/font/model payloads, credentials, and personal data MUST NOT enter the repository corpus'
+ fault_matrix=@($faults.cases).Count-eq18-and$faultIds.Count-eq@($faultIds|Sort-Object -Unique).Count-and@($requiredFaults|Where-Object{$_-notin$faults.cases.class}).Count-eq0
+ migration_recovery=$spec-match'failed migration with the original remaining readable'-and$spec-match'rejection of unsupported future schema without mutation'-and$spec-match'Acknowledged user commands survive force close'
+ interoperability_boundary=$spec-match'UTF-8 SRT output'-and$spec-match'No player/editor interoperability claim exists until the exact third-party application/version'
+ exit_evidence=(1..12|Where-Object{$spec-notmatch('(?m)^'+$_+'\. ')}).Count-eq0
+ gate_inventory=@($gates|Where-Object{$spec-notmatch[regex]::Escape($_)}).Count-eq0
+ entry_exit_separation=$spec-match'Freezing this contract fixes profiles, thresholds, corpus governance, procedures, and required evidence; it does not satisfy `Exit:V0`'-and$spec-match'Exit approval remains pending until implementation evidence exists'
+}
+$failed=@($checks.GetEnumerator()|Where-Object{-not$_.Value}|ForEach-Object Name)
+$result=[ordered]@{evidence_id='VID-EVID-015-ENTRY';revision='1.0.0-review.1';status=$(if($failed.Count){'Fail'}else{'Pass'});classification='entry-contract-conformance-only';specification=[ordered]@{id='VID-SPEC-015';revision='1.0-review.1';sha256=(Get-FileHash $specPath -Algorithm SHA256).Hash};assets=[ordered]@{environment=[ordered]@{sha256=(Get-FileHash $environmentPath -Algorithm SHA256).Hash;profiles=@($environment.profiles).Count};budgets=[ordered]@{sha256=(Get-FileHash $budgetPath -Algorithm SHA256).Hash;metrics=@($budgets.budgets).Count};corpus=[ordered]@{sha256=(Get-FileHash $corpusPath -Algorithm SHA256).Hash;records=@($corpus.fixtures).Count;status=$corpus.status};faults=[ordered]@{sha256=(Get-FileHash $faultPath -Algorithm SHA256).Hash;cases=@($faults.cases).Count}};checks=$checks;summary=[ordered]@{passed=$checks.Count-$failed.Count;total=$checks.Count;failures=$failed};exit_status='Pending implementation evidence';limitations=@('planned-generators-are-not-materialized-fixtures','no-runtime-performance-evidence','no-runtime-recovery-evidence','no-runtime-interoperability-evidence','no-release-support-claim')}
+$json=$result|ConvertTo-Json -Depth 12;if(-not$NoWriteEvidence){New-Item -ItemType Directory -Force -Path(Split-Path $evidencePath)|Out-Null;[IO.File]::WriteAllText($evidencePath,$json+"`n",[Text.UTF8Encoding]::new($false))};$json;if($failed.Count){exit 1}
diff --git a/testing/implementation/VID-IMPL-P00-001/README.md b/testing/implementation/VID-IMPL-P00-001/README.md
new file mode 100644
index 0000000..757a6f8
--- /dev/null
+++ b/testing/implementation/VID-IMPL-P00-001/README.md
@@ -0,0 +1,7 @@
+# VID-IMPL-P00-001 Fixture and Harness Notes
+
+This folder holds scaffold-only fixture links for Phase 00 conformance tooling.
+
+- `fixture-loader.json` declares the VID-SPEC-015 corpus and profile paths the future test harness will consume.
+- No runtime fixture execution is performed in this issue.
+- No new fixture contents are added; frozen VID-SPEC-015 fixture data remains authoritative and unchanged.
diff --git a/testing/implementation/VID-IMPL-P00-001/fixture-loader.json b/testing/implementation/VID-IMPL-P00-001/fixture-loader.json
new file mode 100644
index 0000000..6db47fb
--- /dev/null
+++ b/testing/implementation/VID-IMPL-P00-001/fixture-loader.json
@@ -0,0 +1,17 @@
+{
+ "profile": "VID-SPEC-015@1.0",
+ "targets": [
+ {
+ "name": "fixtures",
+ "path": "testing/conformance/vid-spec-015/fixtures"
+ },
+ {
+ "name": "profiles",
+ "path": "testing/conformance/vid-spec-015/profiles/foundation-budgets.json"
+ }
+ ],
+ "notes": [
+ "Scaffold only: no fixture execution is introduced in VID-IMPL-P00-001.",
+ "No fixture mutation occurs in this issue."
+ ]
+}
diff --git a/testing/implementation/VID-IMPL-P00-002A/README.md b/testing/implementation/VID-IMPL-P00-002A/README.md
new file mode 100644
index 0000000..fa40dd7
--- /dev/null
+++ b/testing/implementation/VID-IMPL-P00-002A/README.md
@@ -0,0 +1,12 @@
+# VID-IMPL-P00-002A
+
+Scope: local-file preflight and deterministic SHA-256 candidate observation.
+
+- Add a Rust library boundary under `workers/VideoLocalization.Worker`.
+- Validate local file inputs for missing paths, non-file paths, path traversal, ADS/device/network syntax, and two-tebibyte source ceiling before hashing.
+- Compute SHA-256 by streaming fixed-size chunks with `sha2 = 0.11.0`.
+- Return immutable candidate observation data including byte length, hashes, pre/post file metadata, canonical path, and algorithm version.
+- Path/hash/canonicalization metadata are **non-authoritative observations**.
+ - Callers must assign stable Video asset identities independently and bind observations to caller-owned asset IDs.
+- Hash observation uses a single read handle per attempt; on Windows the read handle shares read-only so write/delete opens are denied during hashing. Metadata for mutation checks comes from that same handle, but hashes/metadata remain non-authoritative observation inputs and must not be treated as durable source identity.
+- Prohibit media codec selection, probing, and network access in this slice.
diff --git a/testing/implementation/VID-IMPL-P00-002B2/README.md b/testing/implementation/VID-IMPL-P00-002B2/README.md
new file mode 100644
index 0000000..2f144c4
--- /dev/null
+++ b/testing/implementation/VID-IMPL-P00-002B2/README.md
@@ -0,0 +1,94 @@
+# VID-IMPL-P00-002B2
+
+Scope: real synthetic Matroska probe evidence for `VID-DEC-003@1.0` with enforced Windows containment and executable identity checks.
+
+## Synthetic fixture evidence (rights-safe, local-only)
+
+- Fixture path (not committed): `C:\Users\SubMark\AppData\Local\Temp\vid-impl-002b2-synthetic\fixture.mkv`
+- Evidence identity file (committed only): `synthetic-matroska-fixture.json`
+- One leased staged input is used as the live probe target: `fixture.mkv` under scope for this evidence run.
+- File identity:
+ - `size_bytes`: `218044`
+ - `sha256`: `53a4c836ee5e20a470416069d3dfe43d898ff1ed5dceabe417bbf302b1f79782`
+- Generator command (host-local, no remote fetch of media):
+
+```powershell
+$fixtureDir = "C:\Users\SubMark\AppData\Local\Temp\vid-impl-002b2-synthetic"
+New-Item -ItemType Directory -Path $fixtureDir -Force | Out-Null
+$fixture = Join-Path $fixtureDir 'fixture.mkv'
+$subtitle = Join-Path $fixtureDir 'fixture.srt'
+@"1
+00:00:00,000 --> 00:00:01,000
+Synthetic subtitle for containment proof
+"@ | Set-Content -NoNewline $subtitle
+& 'C:\ProgramData\chocolatey\lib\ffmpeg-full\tools\ffmpeg\bin\ffmpeg.exe' -y -loglevel error -f lavfi -i testsrc=size=64x64:rate=24:d=1 -c:v ffv1 -f lavfi -i sine=frequency=440:duration=1 -i $subtitle -c:v ffv1 -map 0:v -map 1:a -map 2:s:0 -c:a pcm_s16le -ar 48000 -ac 2 -c:s subrip -shortest $fixture
+```
+
+- Verified with fixture probe command:
+
+```powershell
+& 'C:\ProgramData\chocolatey\lib\ffmpeg-full\tools\ffmpeg\bin\ffprobe.exe' -v error -hide_banner -protocol_whitelist file -format_whitelist matroska -show_format -show_streams -show_chapters -of json $fixture
+```
+
+Observed fixture profile:
+
+- `format_name_raw`: `matroska,webm` (raw ffprobe metadata)
+- `format_name_qualified`: `matroska` (profile filter)
+- `webm_qualified_or_supported`: `false`
+- `duration`: `1.000000`
+- `nb_streams`: `3`
+- stream codecs: `ffv1`, `pcm_s16le`, `subrip`
+- stream types: `video`, `audio`, `subtitle`
+
+## 002A staging, pre/post identity, and fixed profile
+
+- `ProbePrelaunchRequest` is prepared from 002A identity output and must carry:
+ - `staged_copy_identity`
+ - `staged_copy_sha256`
+ - `staged_copy_length`
+ - `observed_fence_token`
+- Runtime re-check enforces post-launch source re-fingerprint before publication.
+- Any prelaunch source identity mismatch after staging is treated as fail-closed (`SourceMutationDetected`).
+- Publication identity is checked against context identity and request fence (`publication_fence_token`).
+
+## Exact executable and arg policy
+
+- Registered executable path: `C:\ProgramData\chocolatey\lib\ffmpeg-full\tools\ffmpeg\bin\ffprobe.exe`
+- Registered executable SHA-256: `9df3b0b5275e830961df6d94e1f7a71121a7abd5ff708e9fec8a0b6084a55015`
+- `ffprobe -version` first line:
+ `ffprobe version 8.1.2-full_build-www.gyan.dev Copyright (c) 2007-2026 the FFmpeg developers`
+- `ffprobe` configuration line captured exactly:
+ `configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-cairo --enable-fontconfig --enable-iconv --enable-gnutls --enable-lcms2 --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-libdvdnav --enable-libdvdread --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-liboapv --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libsvtjpegxs --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-openal --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint --enable-whisper`
+
+## Containment and bounded-output controls
+
+- Network containment: live `DefaultNetworkDenialController` creates and verifies inbound/outbound `netsh` deny rules before launch.
+- Job object containment: live `DefaultJobObjectController` creates limits and assigns the spawned ffprobe process.
+- Watchdog containment: live monotonic wall-clock limit uses `CONTROL_LIMITS.wall_clock_seconds` (`60`) with cancellation checks.
+- Fixed argv: exact `fixed_probe_argv` (`-v error -hide_banner -protocol_whitelist file -format_whitelist matroska -show_format -show_streams -show_chapters -of json `).
+- Bounded decode capture:
+ - stdout: `1_048_576` bytes
+ - stderr: `1_048_576` bytes
+ - stream ceiling: `256`
+
+## Negative paths under implementation
+
+- executable mismatch
+- launch-argv mismatch
+- stale lease
+- publication-fence mismatch
+- source prelaunch mutation
+- network containment unavailable
+- job object unavailable
+- watchdog unavailable
+- process crash
+- malformed/oversized stdout JSON
+- parse stream-ceiling overflow
+- parse required-field gaps
+- timeout and cancellation path
+
+## Live evidence files
+
+- `synthetic-matroska-fixture.json`: fixture identity + ffprobe identity evidence
+- `manifest.json`: artifact inventory and evidence scope
+- `test-report.json`: command evidence, check table, and fail-closed outcomes
diff --git a/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json b/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json
new file mode 100644
index 0000000..9541ff6
--- /dev/null
+++ b/testing/implementation/VID-IMPL-P00-002B2/synthetic-matroska-fixture.json
@@ -0,0 +1,128 @@
+{
+ "implementation_id": "VID-IMPL-P00-002B2",
+ "decision": "VID-DEC-003@1.0",
+ "collected_at": "2026-08-02T13:06:19+07:00",
+ "scope": "Local synthetic Matroska fixture for fail-closed live probe evidence",
+ "rights": "local-only; no media binary is checked in",
+ "fixture": {
+ "path": "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.mkv",
+ "size_bytes": 218044,
+ "sha256": "53a4c836ee5e20a470416069d3dfe43d898ff1ed5dceabe417bbf302b1f79782"
+ },
+ "generator": {
+ "binary": "C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffmpeg.exe",
+ "command": [
+ "-y",
+ "-loglevel",
+ "error",
+ "-f",
+ "lavfi",
+ "-i",
+ "testsrc=size=64x64:rate=24:d=1",
+ "-c:v",
+ "ffv1",
+ "-f",
+ "lavfi",
+ "-i",
+ "sine=frequency=440:duration=1",
+ "-i",
+ "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.srt",
+ "-c:v",
+ "ffv1",
+ "-map",
+ "0:v",
+ "-map",
+ "1:a",
+ "-map",
+ "2:s:0",
+ "-c:a",
+ "pcm_s16le",
+ "-ar",
+ "48000",
+ "-ac",
+ "2",
+ "-c:s",
+ "subrip",
+ "-shortest",
+ "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.mkv"
+ ]
+ },
+ "subtitle": {
+ "path": "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.srt",
+ "content": "1\n00:00:00,000 --> 00:00:01,000\nSynthetic subtitle for containment proof\n"
+ },
+ "ffprobe": {
+ "binary": "C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffprobe.exe",
+ "probe_command": [
+ "-v",
+ "error",
+ "-hide_banner",
+ "-protocol_whitelist",
+ "file",
+ "-format_whitelist",
+ "matroska",
+ "-show_format",
+ "-show_streams",
+ "-show_chapters",
+ "-of",
+ "json",
+ "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.mkv"
+ ],
+ "leased_staged_input": {
+ "path": "C:\\Users\\SubMark\\AppData\\Local\\Temp\\vid-impl-002b2-synthetic\\fixture.mkv",
+ "scope": "staged-local-input"
+ },
+ "verified_profile": {
+ "format_name_raw": "matroska,webm",
+ "format_name_unpromoted": true,
+ "matroska_qualified_only": true,
+ "webm_supported_by_profile": false,
+ "duration": "1.000000",
+ "nb_streams": 3,
+ "stream_codec_types": [
+ "video",
+ "audio",
+ "subtitle"
+ ],
+ "stream_codec_names": [
+ "ffv1",
+ "pcm_s16le",
+ "subrip"
+ ]
+ },
+ "executable_identity": {
+ "version_line": "ffprobe version 8.1.2-full_build-www.gyan.dev",
+ "configuration_prefix": "configuration:"
+ },
+ "executable_hash_expected": "9df3b0b5275e830961df6d94e1f7a71121a7abd5ff708e9fec8a0b6084a55015"
+ },
+ "requirements": {
+ "stream_ceiling": 256,
+ "output_envelope_bytes": 1048576,
+ "watchdog_seconds": 60,
+ "network_denial_rules": [
+ "in",
+ "out"
+ ],
+ "job_object_required": true,
+ "fixed_probe_argv_prefix": [
+ "-v",
+ "error",
+ "-hide_banner",
+ "-protocol_whitelist",
+ "file",
+ "-format_whitelist",
+ "matroska",
+ "-show_format",
+ "-show_streams",
+ "-show_chapters",
+ "-of",
+ "json"
+ ],
+ "evidence_files": [
+ "synthetic-matroska-fixture.json",
+ "manifest.json",
+ "test-report.json"
+ ]
+ }
+}
diff --git a/workers/VideoLocalization.Worker/Cargo.lock b/workers/VideoLocalization.Worker/Cargo.lock
new file mode 100644
index 0000000..a5d69a8
--- /dev/null
+++ b/workers/VideoLocalization.Worker/Cargo.lock
@@ -0,0 +1,1176 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "serde",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "borrow-or-share"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytecount"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "fancy-regex"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
+dependencies = [
+ "bit-set",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "fluent-uri"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
+dependencies = [
+ "borrow-or-share",
+ "ref-cast",
+ "serde",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "fraction"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872"
+dependencies = [
+ "lazy_static",
+ "num",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "js-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonschema"
+version = "0.49.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "508004a5500f2e1f68af048f70feea2de86d35ab115d85716530860822aef397"
+dependencies = [
+ "ahash",
+ "bytecount",
+ "data-encoding",
+ "email_address",
+ "fancy-regex",
+ "fraction",
+ "getrandom",
+ "idna",
+ "itoa",
+ "jsonschema-regex",
+ "jsonschema-value",
+ "num-cmp",
+ "num-traits",
+ "percent-encoding",
+ "referencing",
+ "regex",
+ "serde",
+ "serde_json",
+ "strum",
+ "unicode-general-category",
+ "uuid-simd",
+]
+
+[[package]]
+name = "jsonschema-regex"
+version = "0.49.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a8b30cafa78358ae6cd1494a7d6410b89530e28bf567f862c869c667e900d9f"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "jsonschema-value"
+version = "0.49.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5526bd381d230af94908d07e6835a33fd82a465e12f5f1e9c81f5c2aa23b3c21"
+dependencies = [
+ "ahash",
+ "bytecount",
+ "fraction",
+ "num-cmp",
+ "num-traits",
+ "serde_json",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "micromap"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-cmp"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "outref"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "referencing"
+version = "0.49.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af3eb523cce0df0af3c30d624b829b2dabd233172b5bc2615fcd03ceae8f746"
+dependencies = [
+ "ahash",
+ "fluent-uri",
+ "getrandom",
+ "hashbrown",
+ "itoa",
+ "micromap",
+ "parking_lot",
+ "percent-encoding",
+ "serde_json",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "strum"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unicode-general-category"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid-simd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
+dependencies = [
+ "outref",
+ "vsimd",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "videolocalization-worker"
+version = "0.1.0"
+dependencies = [
+ "jsonschema",
+ "serde",
+ "serde_json",
+ "sha2",
+ "windows",
+]
+
+[[package]]
+name = "vsimd"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "windows"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+dependencies = [
+ "windows-core",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/workers/VideoLocalization.Worker/Cargo.toml b/workers/VideoLocalization.Worker/Cargo.toml
new file mode 100644
index 0000000..25034ba
--- /dev/null
+++ b/workers/VideoLocalization.Worker/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "videolocalization-worker"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+sha2 = "=0.11.0"
+
+[dev-dependencies]
+jsonschema = { version = "=0.49.3", default-features = false }
+
+[target.'cfg(windows)'.dependencies]
+windows = { version = "0.58", default-features = false, features = [
+ "Win32_Foundation",
+ "Win32_System_Diagnostics_Debug",
+ "Win32_System_JobObjects",
+ "Win32_System_Threading",
+ "Win32_Security"
+] }
diff --git a/workers/VideoLocalization.Worker/src/fingerprint.rs b/workers/VideoLocalization.Worker/src/fingerprint.rs
new file mode 100644
index 0000000..36a44eb
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/fingerprint.rs
@@ -0,0 +1,188 @@
+use std::fs::{File, OpenOptions};
+use std::io::{Read, Seek, SeekFrom};
+use std::path::Path;
+
+use sha2::{Digest, Sha256};
+
+use crate::preflight::{
+ preflight_local_file,
+ source_ceiling_bytes,
+ FileMetadataSnapshot,
+ PreflightDisposition,
+ snapshot_metadata,
+};
+
+pub const SHA2_CRATE_VERSION: &str = "0.11.0";
+
+#[derive(Debug, Clone)]
+pub struct FingerprintAlgorithm {
+ pub name: &'static str,
+ pub version: &'static str,
+}
+
+#[derive(Debug, Clone)]
+pub struct FingerprintObservation {
+ /// Candidate file-path and hash metadata only; caller assigns stable Video asset IDs.
+ pub attempted_path: String,
+ /// Candidate file-path observation only; not an authoritative identity for Video assets.
+ pub canonical_path: String,
+ pub byte_length: u64,
+ pub sha256_hex: String,
+ pub algorithm: FingerprintAlgorithm,
+ pub metadata_before_hash: FileMetadataSnapshot,
+ pub metadata_after_hash: FileMetadataSnapshot,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum FingerprintDisposition {
+ PreflightFailure(PreflightDisposition),
+ IoFailure,
+ CandidateMutated,
+ ExceedsSourceCeiling,
+}
+
+pub const DEFAULT_HASH_CHUNK_BYTES: usize = 64 * 1024;
+
+pub fn fingerprint_local_file(path: impl AsRef) -> Result {
+ fingerprint_local_file_internal::(path, None)
+}
+
+pub fn fingerprint_local_file_with_hook(
+ path: impl AsRef,
+ on_chunk_hook: Option,
+) -> Result
+where
+ F: FnMut(u64),
+{
+ fingerprint_local_file_internal(path, on_chunk_hook)
+}
+
+fn fingerprint_local_file_internal(
+ path: impl AsRef,
+ on_chunk_hook: Option,
+) -> Result
+where
+ F: FnMut(u64),
+{
+ fingerprint_local_file_internal_with_ceiling(path, on_chunk_hook, source_ceiling_bytes())
+}
+
+fn fingerprint_local_file_internal_with_ceiling(
+ path: impl AsRef,
+ mut on_chunk_hook: Option,
+ ceiling_bytes: u64,
+) -> Result
+where
+ F: FnMut(u64),
+{
+ let preflight = preflight_local_file(path).map_err(FingerprintDisposition::PreflightFailure)?;
+ let mut file = open_read_handle_for_fingerprint(&preflight.canonical_path)?;
+ let pre_meta = snapshot_metadata(&file.metadata().map_err(|_| FingerprintDisposition::IoFailure)?);
+
+ file.seek(SeekFrom::Start(0)).map_err(|_| FingerprintDisposition::IoFailure)?;
+
+ let mut hasher = Sha256::new();
+ let mut buffer = [0u8; DEFAULT_HASH_CHUNK_BYTES];
+ let mut total_read = 0u64;
+
+ loop {
+ let read_bytes = file.read(&mut buffer).map_err(|_| FingerprintDisposition::IoFailure)?;
+ if read_bytes == 0 {
+ break;
+ }
+
+ hasher.update(&buffer[..read_bytes]);
+ total_read += read_bytes as u64;
+
+ if total_read > ceiling_bytes {
+ return Err(FingerprintDisposition::ExceedsSourceCeiling);
+ }
+
+ if let Some(hook) = on_chunk_hook.as_mut() {
+ hook(total_read);
+ }
+ }
+
+ let metadata_after_hash = snapshot_metadata(&file.metadata().map_err(|_| FingerprintDisposition::IoFailure)?);
+
+ if pre_meta.byte_length != total_read
+ || pre_meta.byte_length != metadata_after_hash.byte_length
+ || pre_meta.readonly != metadata_after_hash.readonly
+ || pre_meta.created_unix_millis != metadata_after_hash.created_unix_millis
+ || pre_meta.modified_unix_millis != metadata_after_hash.modified_unix_millis
+ {
+ return Err(FingerprintDisposition::CandidateMutated);
+ }
+
+ let mut hex = String::with_capacity(64);
+ for byte in hasher.finalize().iter() {
+ use std::fmt::Write;
+ let _ = write!(hex, "{:02x}", byte);
+ }
+
+ Ok(FingerprintObservation {
+ attempted_path: preflight.attempted_path.to_string_lossy().to_string(),
+ canonical_path: preflight.canonical_path.to_string_lossy().to_string(),
+ byte_length: total_read,
+ sha256_hex: hex,
+ algorithm: FingerprintAlgorithm {
+ name: "SHA-256",
+ version: SHA2_CRATE_VERSION,
+ },
+ metadata_before_hash: pre_meta,
+ metadata_after_hash,
+ })
+}
+
+#[cfg(windows)]
+fn open_read_handle_for_fingerprint(path: &Path) -> Result {
+ use std::os::windows::fs::OpenOptionsExt;
+
+ OpenOptions::new()
+ .read(true)
+ .share_mode(1)
+ .open(path)
+ .map_err(|_| FingerprintDisposition::IoFailure)
+}
+
+#[cfg(not(windows))]
+fn open_read_handle_for_fingerprint(path: &Path) -> Result {
+ File::open(path).map_err(|_| FingerprintDisposition::IoFailure)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ fn temp_file_path(prefix: &str) -> std::path::PathBuf {
+ let mut path = std::env::temp_dir();
+ let nanos = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ path.push(format!("{}-{}.bin", prefix, nanos));
+ path
+ }
+
+ fn write_temp_file(prefix: &str, content: &[u8]) -> std::path::PathBuf {
+ let path = temp_file_path(prefix);
+ std::fs::write(&path, content).expect("temp file write");
+ path
+ }
+
+ #[test]
+ fn fingerprint_loop_enforces_ceiling_before_completion() {
+ let content = b"loop-ceiling-probe";
+ let path = write_temp_file("vid-impl-002a-loop-ceiling", content);
+
+ let result =
+ super::fingerprint_local_file_internal_with_ceiling(&path, None::, (content.len() / 2) as u64);
+
+ std::fs::remove_file(&path).ok();
+ assert!(matches!(
+ result,
+ Err(FingerprintDisposition::ExceedsSourceCeiling)
+ ));
+ }
+}
diff --git a/workers/VideoLocalization.Worker/src/lib.rs b/workers/VideoLocalization.Worker/src/lib.rs
new file mode 100644
index 0000000..b0ee6a6
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/lib.rs
@@ -0,0 +1,51 @@
+pub mod preflight;
+pub mod fingerprint;
+pub mod probe_adapter_contract;
+
+pub use preflight::{
+ source_ceiling_bytes,
+ is_within_source_ceiling,
+ FileMetadataSnapshot,
+ PreflightDisposition,
+ PreflightResult,
+ preflight_local_file,
+};
+pub use fingerprint::{
+ FingerprintAlgorithm,
+ FingerprintDisposition,
+ FingerprintObservation,
+ fingerprint_local_file,
+ fingerprint_local_file_with_hook,
+ SHA2_CRATE_VERSION,
+};
+pub use probe_adapter_contract::{
+ fixed_probe_argv,
+ DISPOSITION_MAP,
+ PROBE_ARGV_PREFIX,
+ validate_probe_prelaunch,
+ validate_probe_output_completeness,
+ to_structured_error,
+ ControlEnvelopeLimit,
+ DispositionMapping,
+ ProbeLeaseManifest,
+ ErrorRef,
+ ProbePrelaunchFailure,
+ StructuredError,
+ ProbeContainmentEvidence,
+ ProbeLaunchPolicy,
+ ProbePrelaunchRequest,
+ StructuredErrorAppliesTo,
+ CONTROL_ENVELOPE_BYTES,
+ IMPLEMENTATION_ID,
+ IMPLEMENTATION_PROFILE_ID,
+ OPERATION_ID,
+ CONTROL_PROTOCOL_VERSION,
+ OPERATION_VERSION,
+ REGISTERED_EXECUTABLE_PATH,
+ REGISTERED_EXECUTABLE_SHA256,
+ IMPLEMENTED_PROFILE_VERSION,
+ ERROR_REF_NAMESPACE,
+ ERROR_REF_VERSION,
+ STRUCTURED_ERROR_SCHEMA_ID,
+ STRUCTURED_ERROR_SCHEMA_VERSION,
+};
diff --git a/workers/VideoLocalization.Worker/src/main.rs b/workers/VideoLocalization.Worker/src/main.rs
new file mode 100644
index 0000000..6b6ad40
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("VideoLocalization.Worker scaffold ready.");
+}
diff --git a/workers/VideoLocalization.Worker/src/preflight.rs b/workers/VideoLocalization.Worker/src/preflight.rs
new file mode 100644
index 0000000..94bb013
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/preflight.rs
@@ -0,0 +1,194 @@
+use std::fs;
+use std::path::{Component, Path, PathBuf};
+use std::time::{SystemTime, UNIX_EPOCH};
+
+const PHASE00_SOURCE_LIMIT_BYTES: u64 = 2 * 1024 * 1024 * 1024 * 1024;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PreflightDisposition {
+ MissingPath,
+ NotAFile,
+ PathTraversal,
+ AlternateDataStream,
+ DevicePath,
+ NonLocalPath,
+ ExceedsSourceCeiling,
+ InvalidPath,
+ PathUnavailable,
+}
+
+#[derive(Debug, Clone)]
+pub struct FileMetadataSnapshot {
+ pub byte_length: u64,
+ pub readonly: bool,
+ pub created_unix_millis: Option,
+ pub modified_unix_millis: Option,
+}
+
+#[derive(Debug, Clone)]
+pub struct PreflightResult {
+ pub attempted_path: PathBuf,
+ pub canonical_path: PathBuf,
+ pub metadata_before_hash: FileMetadataSnapshot,
+}
+
+pub fn source_ceiling_bytes() -> u64 {
+ PHASE00_SOURCE_LIMIT_BYTES
+}
+
+pub fn is_within_source_ceiling(byte_length: u64) -> bool {
+ byte_length <= PHASE00_SOURCE_LIMIT_BYTES
+}
+
+pub fn preflight_local_file(path: impl AsRef) -> Result {
+ let raw_path = path.as_ref();
+ let attempted_path = raw_path.to_path_buf();
+
+ validate_path_safety(raw_path)?;
+
+ let metadata = fs::metadata(raw_path).map_err(|err| match err.kind() {
+ std::io::ErrorKind::NotFound => PreflightDisposition::MissingPath,
+ _ => PreflightDisposition::PathUnavailable,
+ })?;
+
+ if !metadata.is_file() {
+ return Err(PreflightDisposition::NotAFile);
+ }
+
+ let prehash = snapshot_metadata(&metadata);
+
+ if !is_within_source_ceiling(prehash.byte_length) {
+ return Err(PreflightDisposition::ExceedsSourceCeiling);
+ }
+
+ let canonical_path = raw_path
+ .canonicalize()
+ .map_err(|_| PreflightDisposition::PathUnavailable)?;
+
+ validate_path_safety(&canonical_path)?;
+
+ Ok(PreflightResult {
+ attempted_path,
+ canonical_path,
+ metadata_before_hash: prehash,
+ })
+
+}
+
+fn validate_path_safety(path: &Path) -> Result<(), PreflightDisposition> {
+ if let Some(disposition) = classify_path_safety(path) {
+ return Err(disposition);
+ }
+
+ if has_parent_traversal(path) {
+ return Err(PreflightDisposition::PathTraversal);
+ }
+
+ if has_alternate_data_stream(path) {
+ return Err(PreflightDisposition::AlternateDataStream);
+ }
+
+ Ok(())
+}
+
+fn has_parent_traversal(path: &Path) -> bool {
+ path.components().any(|component| component == Component::ParentDir)
+}
+
+fn has_alternate_data_stream(path: &Path) -> bool {
+ let path_text = path.to_string_lossy();
+
+ if let Some(verbatim_path) = path_text.strip_prefix("\\\\?\\") {
+ if has_drive_prefix(verbatim_path) {
+ return false;
+ }
+ }
+
+ let has_drive_prefix = has_drive_prefix(path_text.as_ref());
+
+ let colon_count = path_text.chars().filter(|ch| *ch == ':').count();
+
+ if has_drive_prefix {
+ colon_count > 1
+ } else {
+ colon_count > 0
+ }
+}
+
+fn classify_path_safety(path: &Path) -> Option {
+ let text = path.to_string_lossy();
+
+ if text.trim().is_empty() {
+ return Some(PreflightDisposition::InvalidPath);
+ }
+
+ if text.starts_with("\\\\?\\") {
+ return classify_verbatim_namespace(&text);
+ }
+
+ if text.starts_with("\\\\.\\" ) {
+ return Some(PreflightDisposition::DevicePath);
+ }
+
+ if text.starts_with("\\\\") {
+ return Some(PreflightDisposition::NonLocalPath);
+ }
+
+ if text.contains("://") {
+ return Some(PreflightDisposition::NonLocalPath);
+ }
+
+ None
+}
+
+fn classify_verbatim_namespace(path_text: &str) -> Option {
+ const VERBATIM_PREFIX_LEN: usize = 4;
+
+ if path_text.len() < VERBATIM_PREFIX_LEN {
+ return Some(PreflightDisposition::InvalidPath);
+ }
+
+ let verbatim_path = &path_text[VERBATIM_PREFIX_LEN..];
+ if has_drive_prefix(verbatim_path) {
+ return None;
+ }
+
+ let normalized = verbatim_path.to_ascii_uppercase();
+ if normalized.starts_with("UNC\\")
+ || normalized.starts_with("UNC/")
+ {
+ return Some(PreflightDisposition::NonLocalPath);
+ }
+
+ if normalized.starts_with(".\\")
+ || normalized.starts_with("./")
+ || normalized.starts_with("GLOBALROOT\\")
+ || normalized.starts_with("GLOBALROOT/")
+ {
+ return Some(PreflightDisposition::DevicePath);
+ }
+
+ Some(PreflightDisposition::InvalidPath)
+}
+
+fn has_drive_prefix(path_text: &str) -> bool {
+ path_text.len() >= 3
+ && path_text.as_bytes()[1] == b':'
+ && path_text.as_bytes()[0].is_ascii_alphabetic()
+ && (path_text.as_bytes()[2] == b'\\' || path_text.as_bytes()[2] == b'/')
+}
+
+pub(crate) fn snapshot_metadata(metadata: &fs::Metadata) -> FileMetadataSnapshot {
+ FileMetadataSnapshot {
+ byte_length: metadata.len(),
+ readonly: metadata.permissions().readonly(),
+ created_unix_millis: to_unix_millis(metadata.created().ok()),
+ modified_unix_millis: to_unix_millis(metadata.modified().ok()),
+ }
+}
+
+fn to_unix_millis(timestamp: Option) -> Option {
+ timestamp
+ .and_then(|time| time.duration_since(UNIX_EPOCH).ok())
+ .map(|duration| (duration.as_secs() as i128) * 1000 + (duration.subsec_nanos() as i128) / 1_000_000)
+}
diff --git a/workers/VideoLocalization.Worker/src/probe_adapter_contract.rs b/workers/VideoLocalization.Worker/src/probe_adapter_contract.rs
new file mode 100644
index 0000000..7323fb0
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/probe_adapter_contract.rs
@@ -0,0 +1,813 @@
+use std::collections::HashMap;
+use std::path::Component;
+use std::path::Path;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+pub const OPERATION_ID: &str = "submark.video-localization.media.probe";
+pub const OPERATION_VERSION: &str = "1.0.0-p00";
+pub const IMPLEMENTATION_ID: &str = "VID-IMPL-P00-002B1";
+pub const IMPLEMENTATION_PROFILE_ID: &str = "VID-PROBE-P00-001";
+pub const CONTROL_PROTOCOL_VERSION: &str = "1.0.0";
+pub const REGISTERED_EXECUTABLE_PATH: &str =
+ r"C:\ProgramData\chocolatey\lib\ffmpeg-full\tools\ffmpeg\bin\ffprobe.exe";
+pub const REGISTERED_EXECUTABLE_SHA256: &str =
+ "9df3b0b5275e830961df6d94e1f7a71121a7abd5ff708e9fec8a0b6084a55015";
+pub const IMPLEMENTED_PROFILE_VERSION: &str = "1.0.0-p00";
+pub const STRUCTURED_ERROR_SCHEMA_ID: &str = "VID-IMPL-P00-002B1-STRUCTURED-ERROR";
+pub const STRUCTURED_ERROR_SCHEMA_VERSION: &str = "1.0.0-p00";
+pub const ERROR_REF_NAMESPACE: &str = "video-localization.shared";
+pub const ERROR_REF_VERSION: &str = "1.0.0-p00";
+pub const OUTPUT_ENVELOPE_BYTES: u64 = 1_048_576;
+pub const CONTROL_ENVELOPE_BYTES: u64 = OUTPUT_ENVELOPE_BYTES;
+pub const MAX_SOURCE_STREAMS: u32 = 256;
+pub const EXPECTED_SCHEMA_REFERENCE_IDS: [&str; 7] = [
+ "VID-IMPL-P00-002B1-INPUT",
+ "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION",
+ "VID-IMPL-P00-002B1-PARAMETER",
+ "VID-IMPL-P00-002B1-STRUCTURED-ERROR",
+ "VID-IMPL-P00-002B1-RESOURCE",
+ "VID-IMPL-P00-002B1-POLICY",
+ "VID-IMPL-P00-002B1-OUTPUT-CONTRACT",
+];
+
+pub const PROBE_ARGV_PREFIX: [&str; 12] = [
+ "-v",
+ "error",
+ "-hide_banner",
+ "-protocol_whitelist",
+ "file",
+ "-format_whitelist",
+ "matroska",
+ "-show_format",
+ "-show_streams",
+ "-show_chapters",
+ "-of",
+ "json",
+];
+
+pub const SUPPORTED_CAPABILITIES: [&str; 2] = ["cancellation", "deadline"];
+pub const UNSUPPORTED_CAPABILITIES: [&str; 8] = [
+ "progress",
+ "checkpoint",
+ "pause-resume",
+ "partial-success",
+ "acceleration",
+ "degraded-output",
+ "auto-fallback",
+ "network",
+];
+
+pub const CONTROL_LIMITS: ControlEnvelopeLimit = ControlEnvelopeLimit {
+ wall_clock_seconds: 60,
+ child_cpu_seconds: 60,
+ resident_memory_bytes: 1_073_741_824,
+ control_envelope_bytes: OUTPUT_ENVELOPE_BYTES,
+ stream_count: MAX_SOURCE_STREAMS,
+};
+
+#[derive(Debug, Clone)]
+pub struct ControlEnvelopeLimit {
+ pub wall_clock_seconds: u64,
+ pub child_cpu_seconds: u64,
+ pub resident_memory_bytes: u64,
+ pub control_envelope_bytes: u64,
+ pub stream_count: u32,
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbeLaunchPolicy {
+ pub use_shell: bool,
+ pub use_path_lookup: bool,
+ pub user_supplied_options: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbeContainmentEvidence {
+ pub network_denied: bool,
+ pub watchdog_enabled: bool,
+ pub job_object_enabled: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbeLeaseManifest {
+ pub lease_id: String,
+ pub lease_epoch: u64,
+ pub minimum_acceptable_lease_epoch: u64,
+ pub immutable: bool,
+ pub source_sha256: String,
+ pub source_length: u64,
+ pub staged_copy_identity: String,
+ pub staged_copy_sha256: String,
+ pub staged_copy_length: u64,
+ pub observed_staged_copy_identity: String,
+ pub observed_staged_copy_sha256: String,
+ pub observed_staged_copy_length: u64,
+ pub access_scope: String,
+ pub expires_at: String,
+ pub manifest_digest: String,
+ pub logical_source_ref: String,
+ pub observed_fence_token: String,
+ pub expected_fence_token: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbePrelaunchRequest {
+ pub operation_id: String,
+ pub operation_version: String,
+ pub implementation_id: String,
+ pub implementation_profile_id: String,
+ pub executable_path: String,
+ pub executable_sha256: String,
+ pub staged_input_path: String,
+ pub launch_argv: Vec,
+ pub launch_policy: ProbeLaunchPolicy,
+ pub schema_digests: HashMap,
+ pub capabilities: Vec,
+ pub transport: String,
+ pub evidence: ProbeContainmentEvidence,
+ pub output_envelope_bytes: u64,
+ pub worker_instance_id: String,
+ pub dispatch_id: String,
+ pub control_protocol_version: String,
+ pub job_id: String,
+ pub attempt_id: String,
+ pub trace_id: String,
+ pub correlation_id: String,
+ pub error_instance_id: String,
+ pub lease: ProbeLeaseManifest,
+}
+
+#[derive(Debug, Clone)]
+pub struct ErrorRef {
+ pub namespace: &'static str,
+ pub code: String,
+ pub version: &'static str,
+ pub error_instance: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct StructuredErrorAppliesTo {
+ pub job_ref: String,
+ pub attempt_ref: String,
+ pub dispatch_ref: String,
+ pub worker_ref: String,
+ pub publication_ref: Option,
+}
+
+#[derive(Debug, Clone)]
+pub struct StructuredError {
+ pub schema_id: &'static str,
+ pub schema_version: &'static str,
+ pub error_ref: ErrorRef,
+ pub trace_id: String,
+ pub correlation_id: String,
+ pub subject: String,
+ pub video_disposition: String,
+ pub shared_state: String,
+ pub error_category: String,
+ pub retry_class: String,
+ pub operation_stage: String,
+ pub safe_recovery: String,
+ pub identity_refs: Vec,
+ pub details: Vec,
+ pub causes: Vec,
+ pub applies_to: StructuredErrorAppliesTo,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum ProbePrelaunchFailure {
+ PathMissing,
+ PathInvalid,
+ ComponentMismatch,
+ OperationIdentityMismatch,
+ OperationVersionMismatch,
+ ImplementationMismatch,
+ ImplementationProfileMismatch,
+ WorkerInstanceMismatch,
+ DispatchMismatch,
+ ControlProtocolMismatch,
+ SchemaReferenceMismatch,
+ InvalidLaunchPolicy,
+ UnsupportedCapability(String),
+ MissingRequiredCapability(&'static str),
+ SchemaDigestMismatch(String),
+ MutableLease,
+ StaleLeaseOrFence,
+ SourceIdentityMismatch,
+ SourceHashMismatch,
+ SourceLengthMismatch,
+ ExpiredLease,
+ ScopeMismatch,
+ EnvelopeExceeded,
+ PartialCompletenessRejected,
+ MissingContainmentEvidence(&'static str),
+ TransportUnsupported,
+ ArgumentMismatch,
+ OutOfPolicyIdentity,
+}
+
+impl ProbePrelaunchFailure {
+ pub fn code(&self) -> &'static str {
+ match self {
+ Self::PathMissing => "PathMissing",
+ Self::PathInvalid => "PathInvalid",
+ Self::ComponentMismatch => "ComponentMismatch",
+ Self::OperationIdentityMismatch => "OperationIdentityMismatch",
+ Self::OperationVersionMismatch => "OperationVersionMismatch",
+ Self::ImplementationMismatch => "ImplementationMismatch",
+ Self::ImplementationProfileMismatch => "ImplementationProfileMismatch",
+ Self::WorkerInstanceMismatch => "WorkerInstanceMismatch",
+ Self::DispatchMismatch => "DispatchMismatch",
+ Self::ControlProtocolMismatch => "ControlProtocolMismatch",
+ Self::SchemaReferenceMismatch => "SchemaReferenceMismatch",
+ Self::InvalidLaunchPolicy => "InvalidLaunchPolicy",
+ Self::UnsupportedCapability(_) => "UnsupportedCapability",
+ Self::MissingRequiredCapability(_) => "MissingRequiredCapability",
+ Self::SchemaDigestMismatch(_) => "SchemaDigestMismatch",
+ Self::MutableLease => "MutableLease",
+ Self::StaleLeaseOrFence => "StaleLeaseOrFence",
+ Self::SourceIdentityMismatch => "SourceIdentityMismatch",
+ Self::SourceHashMismatch => "SourceHashMismatch",
+ Self::SourceLengthMismatch => "SourceLengthMismatch",
+ Self::ExpiredLease => "ExpiredLease",
+ Self::ScopeMismatch => "ScopeMismatch",
+ Self::EnvelopeExceeded => "EnvelopeExceeded",
+ Self::PartialCompletenessRejected => "PartialCompletenessRejected",
+ Self::MissingContainmentEvidence(_) => "MissingContainmentEvidence",
+ Self::OutOfPolicyIdentity => "OutOfPolicyIdentity",
+ Self::TransportUnsupported => "TransportUnsupported",
+ Self::ArgumentMismatch => "ArgumentMismatch",
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct DispositionMapping {
+ pub video_disposition: &'static str,
+ pub shared_terminal_state: &'static str,
+ pub error_category: &'static str,
+ pub retry_class: &'static str,
+ pub operation_stage: &'static str,
+ pub safe_recovery: &'static str,
+ pub identity_refs: &'static [&'static str],
+}
+
+pub const DISPOSITION_MAP: &[DispositionMapping] = &[
+ DispositionMapping {
+ video_disposition: "VID-INGEST-UNSUPPORTED",
+ shared_terminal_state: "Failed",
+ error_category: "Unsupported",
+ retry_class: "non-retryable",
+ operation_stage: "validation",
+ safe_recovery: "select a promoted profile or convert externally",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-MALFORMED",
+ shared_terminal_state: "Failed",
+ error_category: "MalformedInput",
+ retry_class: "non-retryable",
+ operation_stage: "validation",
+ safe_recovery: "inspect details or use another source",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-LIMIT",
+ shared_terminal_state: "TimedOut",
+ error_category: "ResourceLimit",
+ retry_class: "non-retryable",
+ operation_stage: "containment",
+ safe_recovery: "use a reviewed lower-cost operation profile",
+ identity_refs: &["attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-PROTECTED",
+ shared_terminal_state: "Failed",
+ error_category: "ProtectionOrRights",
+ retry_class: "non-retryable",
+ operation_stage: "validation",
+ safe_recovery: "provide a lawful unprotected source",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-QUARANTINED",
+ shared_terminal_state: "Failed",
+ error_category: "UnsafeOutput",
+ retry_class: "non-retryable",
+ operation_stage: "output-validation",
+ safe_recovery: "review evidence; never open automatically",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "publication_id", "worker_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-CANCELLED",
+ shared_terminal_state: "Cancelled",
+ error_category: "Cancelled",
+ retry_class: "manual",
+ operation_stage: "execution",
+ safe_recovery: "retry explicitly",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-WORKER-FAILED",
+ shared_terminal_state: "Failed",
+ error_category: "WorkerFailure",
+ retry_class: "conditional",
+ operation_stage: "execution",
+ safe_recovery: "retry only when transient failure is verified",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-IDENTITY-MISMATCH",
+ shared_terminal_state: "Failed",
+ error_category: "IdentityMismatch",
+ retry_class: "non-retryable",
+ operation_stage: "prelaunch",
+ safe_recovery: "re-probe the current source",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+ DispositionMapping {
+ video_disposition: "VID-INGEST-REVIEW",
+ shared_terminal_state: "Failed",
+ error_category: "ManualReview",
+ retry_class: "manual",
+ operation_stage: "validation",
+ safe_recovery: "owner or operator approves a registered profile",
+ identity_refs: &["job_id", "attempt_id", "dispatch_id", "worker_id", "publication_id"],
+ },
+];
+
+pub fn fixed_probe_argv>(staged_input_path: S) -> Vec {
+ let mut argv: Vec = PROBE_ARGV_PREFIX.iter().map(|value| value.to_string()).collect();
+ argv.push(staged_input_path.as_ref().to_string());
+ argv
+}
+
+pub fn validate_probe_prelaunch(
+ request: &ProbePrelaunchRequest,
+ expected_schema_digests: &HashMap,
+) -> Result<(), ProbePrelaunchFailure> {
+ if request.operation_id != OPERATION_ID {
+ return Err(ProbePrelaunchFailure::OperationIdentityMismatch);
+ }
+
+ if request.operation_version != OPERATION_VERSION {
+ return Err(ProbePrelaunchFailure::OperationVersionMismatch);
+ }
+
+ if request.implementation_id != IMPLEMENTATION_ID {
+ return Err(ProbePrelaunchFailure::ImplementationMismatch);
+ }
+
+ if request.implementation_profile_id != IMPLEMENTATION_PROFILE_ID {
+ return Err(ProbePrelaunchFailure::ImplementationProfileMismatch);
+ }
+
+ if request.worker_instance_id.trim().is_empty() {
+ return Err(ProbePrelaunchFailure::WorkerInstanceMismatch);
+ }
+
+ if request.dispatch_id.trim().is_empty() {
+ return Err(ProbePrelaunchFailure::DispatchMismatch);
+ }
+
+ if request.job_id.trim().is_empty()
+ || request.attempt_id.trim().is_empty()
+ || request.trace_id.trim().is_empty()
+ || request.correlation_id.trim().is_empty()
+ || request.error_instance_id.trim().is_empty()
+ || request.lease.lease_id.trim().is_empty()
+ {
+ return Err(ProbePrelaunchFailure::OutOfPolicyIdentity);
+ }
+
+ if request.control_protocol_version != CONTROL_PROTOCOL_VERSION {
+ return Err(ProbePrelaunchFailure::ControlProtocolMismatch);
+ }
+
+ if request.staged_input_path.trim().is_empty() {
+ return Err(ProbePrelaunchFailure::PathMissing);
+ }
+
+ if !is_local_drive_path(&request.staged_input_path) {
+ return Err(ProbePrelaunchFailure::PathInvalid);
+ }
+
+ if !is_path_within_scope(&request.staged_input_path, &request.lease.access_scope) {
+ return Err(ProbePrelaunchFailure::ScopeMismatch);
+ }
+
+ if request.launch_policy.use_shell
+ || request.launch_policy.use_path_lookup
+ || request.launch_policy.user_supplied_options
+ {
+ return Err(ProbePrelaunchFailure::InvalidLaunchPolicy);
+ }
+
+ if !is_exact_path_match(&request.executable_path, Path::new(REGISTERED_EXECUTABLE_PATH))
+ || !is_exact_sha_match(&request.executable_sha256, REGISTERED_EXECUTABLE_SHA256)
+ {
+ return Err(ProbePrelaunchFailure::ComponentMismatch);
+ }
+
+ if request.transport != "local-staged-file" {
+ return Err(ProbePrelaunchFailure::TransportUnsupported);
+ }
+
+ for required in SUPPORTED_CAPABILITIES {
+ if !request.capabilities.iter().any(|value| value == required) {
+ return Err(ProbePrelaunchFailure::MissingRequiredCapability(required));
+ }
+ }
+
+ for capability in request.capabilities.iter() {
+ if !SUPPORTED_CAPABILITIES.contains(&capability.as_str()) {
+ return Err(ProbePrelaunchFailure::UnsupportedCapability(
+ capability.clone(),
+ ));
+ }
+ }
+
+ if !request.lease.immutable {
+ return Err(ProbePrelaunchFailure::MutableLease);
+ }
+
+ if request.lease.lease_epoch < request.lease.minimum_acceptable_lease_epoch {
+ return Err(ProbePrelaunchFailure::StaleLeaseOrFence);
+ }
+
+ if request.lease.observed_fence_token != request.lease.expected_fence_token {
+ return Err(ProbePrelaunchFailure::StaleLeaseOrFence);
+ }
+
+ if !is_hex_string_64(&request.lease.source_sha256)
+ || !is_hex_string_64(&request.lease.staged_copy_sha256)
+ || !is_hex_string_64(&request.lease.observed_staged_copy_sha256)
+ || !is_hex_string_64(&request.lease.manifest_digest)
+ {
+ return Err(ProbePrelaunchFailure::SourceIdentityMismatch);
+ }
+
+ if request.lease.source_sha256 != request.lease.staged_copy_sha256
+ || request.lease.source_sha256 != request.lease.observed_staged_copy_sha256
+ {
+ return Err(ProbePrelaunchFailure::SourceHashMismatch);
+ }
+
+ if request.lease.source_length != request.lease.staged_copy_length
+ || request.lease.source_length != request.lease.observed_staged_copy_length
+ {
+ return Err(ProbePrelaunchFailure::SourceLengthMismatch);
+ }
+
+ if request.lease.staged_copy_identity != request.lease.observed_staged_copy_identity {
+ return Err(ProbePrelaunchFailure::SourceIdentityMismatch);
+ }
+
+ if request.lease.logical_source_ref.trim().is_empty() || request.lease.manifest_digest.trim().is_empty() {
+ return Err(ProbePrelaunchFailure::SourceIdentityMismatch);
+ }
+
+ if !is_valid_rfc3339_utc_expiry(&request.lease.expires_at) {
+ return Err(ProbePrelaunchFailure::ExpiredLease);
+ }
+
+ if is_expired_lease(&request.lease.expires_at).map_err(|_| ProbePrelaunchFailure::ExpiredLease)? {
+ return Err(ProbePrelaunchFailure::ExpiredLease);
+ }
+
+ if request.output_envelope_bytes > CONTROL_ENVELOPE_BYTES {
+ return Err(ProbePrelaunchFailure::EnvelopeExceeded);
+ }
+
+ if !request.evidence.network_denied {
+ return Err(ProbePrelaunchFailure::MissingContainmentEvidence(
+ "network-denial",
+ ));
+ }
+
+ if !request.evidence.watchdog_enabled {
+ return Err(ProbePrelaunchFailure::MissingContainmentEvidence("watchdog"));
+ }
+
+ if !request.evidence.job_object_enabled {
+ return Err(ProbePrelaunchFailure::MissingContainmentEvidence("job-object"));
+ }
+
+ let expected_argv = fixed_probe_argv(&request.staged_input_path);
+ if request.launch_argv != expected_argv {
+ return Err(ProbePrelaunchFailure::ArgumentMismatch);
+ }
+
+ if request.schema_digests.len() != expected_schema_digests.len() {
+ return Err(ProbePrelaunchFailure::SchemaReferenceMismatch);
+ }
+
+ if request.schema_digests.len() == 0 {
+ return Err(ProbePrelaunchFailure::SchemaReferenceMismatch);
+ }
+
+ for expected_schema_id in EXPECTED_SCHEMA_REFERENCE_IDS.iter() {
+ if !request.schema_digests.contains_key(*expected_schema_id) {
+ return Err(ProbePrelaunchFailure::SchemaReferenceMismatch);
+ }
+ }
+
+ for (schema_id, expected_digest) in expected_schema_digests.iter() {
+ let observed = request
+ .schema_digests
+ .get(schema_id)
+ .ok_or_else(|| ProbePrelaunchFailure::SchemaDigestMismatch(schema_id.clone()))?;
+
+ if !is_hex_equal(observed, expected_digest) {
+ return Err(ProbePrelaunchFailure::SchemaDigestMismatch(schema_id.clone()));
+ }
+ }
+
+ Ok(())
+}
+
+pub fn validate_probe_output_completeness(completeness: &str) -> Result<(), ProbePrelaunchFailure> {
+ if completeness != "complete" {
+ return Err(ProbePrelaunchFailure::PartialCompletenessRejected);
+ }
+ Ok(())
+}
+
+pub fn to_structured_error(
+ failure: &ProbePrelaunchFailure,
+ request: &ProbePrelaunchRequest,
+) -> StructuredError {
+ let mapping = disposition_mapping(failure);
+ let identity_refs = mapping.identity_refs.iter().map(|identity| (*identity).to_string()).collect();
+ let mut details = Vec::new();
+ let mut causes = Vec::new();
+
+ details.push(failure.code().to_string());
+ if !request.trace_id.is_empty() {
+ details.push(request.trace_id.clone());
+ }
+ causes.push(failure.code().to_string());
+ if !request.lease.lease_id.is_empty() {
+ causes.push(request.lease.lease_id.clone());
+ }
+
+ StructuredError {
+ schema_id: STRUCTURED_ERROR_SCHEMA_ID,
+ schema_version: STRUCTURED_ERROR_SCHEMA_VERSION,
+ error_ref: ErrorRef {
+ namespace: ERROR_REF_NAMESPACE,
+ code: failure.code().to_string(),
+ version: ERROR_REF_VERSION,
+ error_instance: request.error_instance_id.clone(),
+ },
+ trace_id: request.trace_id.clone(),
+ correlation_id: request.correlation_id.clone(),
+ subject: "prelaunch validation".to_string(),
+ video_disposition: mapping.video_disposition.to_string(),
+ shared_state: mapping.shared_terminal_state.to_string(),
+ error_category: mapping.error_category.to_string(),
+ retry_class: mapping.retry_class.to_string(),
+ operation_stage: mapping.operation_stage.to_string(),
+ safe_recovery: mapping.safe_recovery.to_string(),
+ identity_refs,
+ details: details.into_iter().take(3).collect(),
+ causes: causes.into_iter().take(3).collect(),
+ applies_to: StructuredErrorAppliesTo {
+ job_ref: request.job_id.clone(),
+ attempt_ref: request.attempt_id.clone(),
+ dispatch_ref: request.dispatch_id.clone(),
+ worker_ref: request.worker_instance_id.clone(),
+ publication_ref: None,
+ },
+ }
+}
+
+fn disposition_mapping(failure: &ProbePrelaunchFailure) -> &'static DispositionMapping {
+ match failure {
+ ProbePrelaunchFailure::OperationIdentityMismatch
+ | ProbePrelaunchFailure::OperationVersionMismatch
+ | ProbePrelaunchFailure::ImplementationMismatch
+ | ProbePrelaunchFailure::ImplementationProfileMismatch
+ | ProbePrelaunchFailure::WorkerInstanceMismatch
+ | ProbePrelaunchFailure::DispatchMismatch
+ | ProbePrelaunchFailure::ControlProtocolMismatch
+ | ProbePrelaunchFailure::SchemaReferenceMismatch
+ | ProbePrelaunchFailure::SourceIdentityMismatch
+ | ProbePrelaunchFailure::SourceHashMismatch
+ | ProbePrelaunchFailure::SourceLengthMismatch
+ | ProbePrelaunchFailure::OutOfPolicyIdentity => {
+ return fallback_disposition("VID-INGEST-IDENTITY-MISMATCH");
+ }
+ ProbePrelaunchFailure::PathMissing
+ | ProbePrelaunchFailure::PathInvalid
+ | ProbePrelaunchFailure::ScopeMismatch
+ | ProbePrelaunchFailure::ExpiredLease
+ | ProbePrelaunchFailure::StaleLeaseOrFence
+ | ProbePrelaunchFailure::PartialCompletenessRejected
+ | ProbePrelaunchFailure::SchemaDigestMismatch(_)
+ | ProbePrelaunchFailure::ComponentMismatch
+ | ProbePrelaunchFailure::InvalidLaunchPolicy
+ | ProbePrelaunchFailure::TransportUnsupported => {
+ fallback_disposition("VID-INGEST-MALFORMED")
+ }
+ ProbePrelaunchFailure::MissingContainmentEvidence(_) => {
+ fallback_disposition("VID-INGEST-PROTECTED")
+ }
+ ProbePrelaunchFailure::UnsupportedCapability(_) | ProbePrelaunchFailure::MissingRequiredCapability(_) => {
+ fallback_disposition("VID-INGEST-UNSUPPORTED")
+ }
+ ProbePrelaunchFailure::EnvelopeExceeded => {
+ fallback_disposition("VID-INGEST-LIMIT")
+ }
+ ProbePrelaunchFailure::MutableLease => {
+ fallback_disposition("VID-INGEST-MALFORMED")
+ }
+ ProbePrelaunchFailure::ArgumentMismatch => {
+ fallback_disposition("VID-INGEST-MALFORMED")
+ }
+ }
+}
+
+fn fallback_disposition(video_disposition: &str) -> &'static DispositionMapping {
+ DISPOSITION_MAP
+ .iter()
+ .find(|entry| entry.video_disposition == video_disposition)
+ .expect("all fixture dispositions are complete")
+}
+
+fn is_hex_equal(left: &str, right: &str) -> bool {
+ left.eq_ignore_ascii_case(right)
+}
+
+fn is_exact_path_match(path: &str, expected: &Path) -> bool {
+ Path::new(path).to_string_lossy().eq_ignore_ascii_case(expected.to_string_lossy().as_ref())
+}
+
+fn is_exact_sha_match(candidate: &str, expected: &str) -> bool {
+ candidate.eq_ignore_ascii_case(expected)
+}
+
+fn is_hex_string_64(value: &str) -> bool {
+ value.len() == 64 && value.chars().all(|value| value.is_ascii_hexdigit())
+}
+
+fn is_local_drive_path(path: &str) -> bool {
+ let mut normalized = Path::new(path).components();
+ let Some(Component::Prefix(prefix)) = normalized.next() else {
+ return false;
+ };
+ let prefix = prefix.as_os_str().to_string_lossy();
+ if prefix.len() != 2 {
+ return false;
+ }
+ let mut prefix_chars = prefix.chars();
+ let drive = prefix_chars.next().unwrap_or_default();
+ drive.is_ascii_alphabetic() && prefix_chars.next() == Some(':')
+}
+
+fn is_path_within_scope(path: &str, scope: &str) -> bool {
+ let normalized_path = normalize_path_components(path);
+ let normalized_scope = normalize_path_components(scope);
+ let (Some(path_parts), Some(scope_parts)) = (normalized_path, normalized_scope) else {
+ return false;
+ };
+
+ if path_parts.len() < scope_parts.len() {
+ return false;
+ }
+
+ if path_parts[..scope_parts.len()] != scope_parts[..] {
+ return false;
+ }
+
+ true
+}
+
+fn normalize_path_components(path: &str) -> Option> {
+ let mut normalized = Vec::new();
+ for component in Path::new(path).components() {
+ match component {
+ Component::Prefix(prefix) => {
+ let value = prefix.as_os_str().to_string_lossy().to_ascii_lowercase();
+ if value.len() != 2 {
+ return None;
+ }
+ normalized.push(value);
+ }
+ Component::Normal(part) => {
+ let value = part.to_string_lossy();
+ if value == "." {
+ continue;
+ }
+ if value == ".." {
+ if normalized.is_empty() {
+ return None;
+ }
+ normalized.pop();
+ continue;
+ }
+ normalized.push(value.to_ascii_lowercase());
+ }
+ Component::RootDir => {
+ continue;
+ }
+ _ => continue,
+ }
+ }
+
+ if let Some(first) = normalized.first() {
+ if first.len() == 2 && first.ends_with(':') {
+ return Some(normalized);
+ }
+ }
+ None
+}
+
+fn is_valid_rfc3339_utc_expiry(value: &str) -> bool {
+ parse_rfc3339_to_unix_seconds(value).is_some()
+}
+
+fn is_expired_lease(value: &str) -> Result {
+ let observed = parse_rfc3339_to_unix_seconds(value)
+ .ok_or(ProbePrelaunchFailure::ExpiredLease)?;
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map_err(|_| ProbePrelaunchFailure::ExpiredLease)?
+ .as_secs() as i64;
+ Ok(observed <= now)
+}
+
+fn parse_rfc3339_to_unix_seconds(value: &str) -> Option {
+ let value = value.trim();
+ if value.is_empty() || !value.ends_with('Z') {
+ return None;
+ }
+
+ let t_pos = value.find('T')?;
+ let date_text = &value[..t_pos];
+ let time_and_fraction = &value[t_pos + 1..value.len() - 1];
+
+ let date_parts: Vec<_> = date_text.split('-').collect();
+ if date_parts.len() != 3 {
+ return None;
+ }
+
+ let year = date_parts.first()?.parse::().ok()?;
+ let month = date_parts.get(1)?.parse::().ok()?;
+ let day = date_parts.get(2)?.parse::().ok()?;
+
+ if !(1..=12).contains(&month) {
+ return None;
+ }
+
+ let days_in_month = [
+ 31,
+ if is_leap_year(year) { 29 } else { 28 },
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ ];
+ let month_index = (month - 1) as usize;
+ let day_limit = days_in_month[month_index as usize];
+ if day <= 0 || day > day_limit {
+ return None;
+ }
+
+ let time_text = time_and_fraction.split('.').next().unwrap_or("");
+ let time_fields: Vec<_> = time_text.split(':').collect();
+ if time_fields.len() != 3 {
+ return None;
+ }
+
+ let hour = time_fields.first()?.parse::().ok()?;
+ let minute = time_fields.get(1)?.parse::().ok()?;
+ let second = time_fields.get(2)?.parse::().ok()?;
+ if !(0..=23).contains(&hour) || !(0..=59).contains(&minute) || !(0..=59).contains(&second) {
+ return None;
+ }
+
+ let mut days = 0i64;
+ let mut y = 1970;
+ while y < year {
+ days += if is_leap_year(y) { 366 } else { 365 };
+ y += 1;
+ }
+
+ for m in 1..month {
+ days += days_in_month[(m - 1) as usize] as i64;
+ }
+ days += (day - 1) as i64;
+
+ Some(days * 86_400 + hour * 3_600 + minute * 60 + second)
+}
+
+fn is_leap_year(year: i32) -> bool {
+ (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
+}
diff --git a/workers/VideoLocalization.Worker/src/probe_adapter_runtime.rs b/workers/VideoLocalization.Worker/src/probe_adapter_runtime.rs
new file mode 100644
index 0000000..39f076e
--- /dev/null
+++ b/workers/VideoLocalization.Worker/src/probe_adapter_runtime.rs
@@ -0,0 +1,3748 @@
+use serde::Serialize;
+use serde_json::Value;
+use sha2::{Digest, Sha256};
+
+use std::collections::HashMap;
+use std::fmt;
+use std::fs::OpenOptions;
+use std::io::{self, Read};
+use std::process::{Child, Command, ExitStatus, Stdio};
+use std::thread::{self, JoinHandle};
+use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
+
+#[cfg(windows)]
+use std::ffi::c_void;
+#[cfg(windows)]
+use std::mem::size_of;
+#[cfg(windows)]
+use std::os::windows::fs::OpenOptionsExt;
+#[cfg(windows)]
+use std::os::windows::io::AsRawHandle;
+#[cfg(windows)]
+use windows::Win32::Foundation::{CloseHandle, HANDLE};
+#[cfg(windows)]
+use windows::Win32::System::JobObjects::{
+ AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject, JOBOBJECT_BASIC_LIMIT_INFORMATION,
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
+};
+#[cfg(windows)]
+use windows::core::PCWSTR;
+
+ use crate::{
+ fingerprint::fingerprint_local_file_with_hook,
+ probe_adapter_contract::{
+ fixed_probe_argv, validate_probe_prelaunch, CONTROL_ENVELOPE_BYTES, CONTROL_LIMITS,
+ CONTROL_PROTOCOL_VERSION, MAX_SOURCE_STREAMS, OPERATION_ID, OPERATION_VERSION, ProbePrelaunchFailure,
+ ProbePrelaunchRequest, REGISTERED_EXECUTABLE_PATH, REGISTERED_EXECUTABLE_SHA256,
+ DispositionMapping, DISPOSITION_MAP, ERROR_REF_NAMESPACE, ERROR_REF_VERSION,
+ ErrorRef, STRUCTURED_ERROR_SCHEMA_ID, STRUCTURED_ERROR_SCHEMA_VERSION, StructuredError,
+ StructuredErrorAppliesTo,
+ },
+};
+
+pub const PROBE_EXECUTABLE: &str = REGISTERED_EXECUTABLE_PATH;
+pub const PROBE_EXECUTABLE_VERSION: &str = "ffprobe version 8.1.2-full_build-www.gyan.dev";
+pub const PROBE_EXECUTABLE_CONFIGURATION: &str =
+ "configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-cairo --enable-fontconfig --enable-iconv --enable-gnutls --enable-lcms2 --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-libdvdnav --enable-libdvdread --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-liboapv --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libsvtjpegxs --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-openal --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint --enable-whisper";
+pub const PROBE_STDOUT_BYTES_LIMIT: u64 = CONTROL_ENVELOPE_BYTES;
+pub const PROBE_STDERR_BYTES_LIMIT: u64 = CONTROL_ENVELOPE_BYTES;
+pub const PROBE_MAX_STREAMS: u64 = MAX_SOURCE_STREAMS as u64;
+const VID_002B1_SCHEMA_BUNDLE_SHA256: &str = "f78bf8a7af8616b913d03539eb66ace6bf670f80c685e791521e395396f3397f";
+const VID_002B1_RESOURCE_PROFILE_SHA256: &str = "9daf34a362fb007c286ef0cd327984cb3c476a6b6ad853f5fae0d1f32754724b";
+const VID_002B1_CONTROL_PROTOCOL_SHA256: &str = "2539250e640517eccc9dccf5feebce6cf9ec89acc26cef9ee08edc9d914d9f02";
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ProbeRunFailureCode {
+ PublicationLeaseExpired,
+ PrelaunchValidationFailed,
+ ExecutableIdentityMismatch,
+ ExecutableVersionMismatch,
+ ExecutableConfigurationMismatch,
+ ExecutableAccessFailed,
+ SourcePrelaunchFailed,
+ SourcePostlaunchMismatch,
+ SourceMutationDetected,
+ SourceIdentityMismatch,
+ SourceLengthMismatch,
+ NetworkDenialUnavailable,
+ NetworkDenialSetupFailed,
+ NetworkDenialVerifyFailed,
+ JobObjectUnavailable,
+ JobObjectSetupFailed,
+ JobObjectVerifyFailed,
+ WatchdogUnavailable,
+ WatchdogTimeout,
+ WatchdogCancelled,
+ OutputStdoutOversized,
+ OutputStderrOversized,
+ ChildLaunchFailed,
+ ChildCrashed,
+ OutputCaptureFailed,
+ JsonMalformed,
+ JsonStreamLimitExceeded,
+ PublicationFenceMismatch,
+ OutputLineageInvalid,
+ OutputSerializationFailed,
+ ProcessCancellation,
+ ArgumentMismatch,
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbeRunFailure {
+ pub code: ProbeRunFailureCode,
+ pub message: String,
+ pub structured_error: Option,
+}
+
+impl fmt::Display for ProbeRunFailure {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}: {}", self.code, self.message)
+ }
+}
+
+impl std::error::Error for ProbeRunFailure {}
+
+impl ProbeRunFailure {
+ fn bind_request_context(
+ mut self,
+ request: &ProbePrelaunchRequest,
+ publication_ref: Option<&str>,
+ ) -> ProbeRunFailure {
+ self.structured_error = Some(to_structured_runtime_error(
+ &self.code,
+ &self.message,
+ request,
+ publication_ref,
+ ));
+ self
+ }
+
+ fn with_synthetic_request_context(
+ mut self,
+ error_instance: &str,
+ publication_ref: Option<&str>,
+ ) -> ProbeRunFailure {
+ self.structured_error = Some(to_structured_runtime_error_with_synthetic_identity(
+ &self.code,
+ &self.message,
+ error_instance,
+ publication_ref,
+ ));
+ self
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct RuntimeContainmentEvidence {
+ pub network_denial_rule_in: String,
+ pub network_denial_rule_out: String,
+ pub network_rule_scope: String,
+ pub job_object_established: bool,
+ pub watchdog_seconds: u64,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeNormalizedObservation {
+ pub format: String,
+ pub duration_seconds: f64,
+ pub streams: u64,
+ pub stream_count: u64,
+ pub has_subtitles: bool,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeObservation {
+ pub observation_id: String,
+ pub schema_id: String,
+ pub schema_version: String,
+ pub probe_observation: ProbeNormalizedObservation,
+ pub completeness: String,
+ pub artifact_hash: String,
+ pub artifact_size: u64,
+ pub lineage_ref: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeLineage {
+ pub job_ref: String,
+ pub attempt_ref: String,
+ pub dispatch_ref: String,
+ pub worker_ref: String,
+ pub publication_ref: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeOutputIntegrity {
+ pub algorithm: String,
+ pub value: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeProducing {
+ pub job_ref: String,
+ pub attempt_ref: String,
+ pub dispatch_ref: String,
+ pub worker_ref: String,
+ pub operation_id: String,
+ pub operation_version: String,
+ pub implementation_id: String,
+ pub implementation_profile_id: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeResourceInput {
+ pub staged_copy_hash: String,
+ pub manifest_digest: String,
+ pub logical_source_ref: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeParameterDigests {
+ pub argv: String,
+ pub limits: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeResourceDigests {
+ pub resource: String,
+ pub schema: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbePolicyDigests {
+ pub policy: String,
+ pub schema: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeCacheDigests {
+ pub schema_bundle: String,
+ pub control_protocol: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeStaging {
+ pub state: String,
+ pub path: String,
+ pub delete_on_stale: bool,
+ pub quarantine_on_failure: bool,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeFencing {
+ pub required: bool,
+ pub active_token: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct ProbeOutputContract {
+ pub artifact_id: String,
+ pub artifact_version: String,
+ pub artifact_type: String,
+ pub output_schema_id: String,
+ pub completeness: String,
+ pub integrity: ProbeOutputIntegrity,
+ pub size_bytes: u64,
+ pub component_inventory: Vec,
+ pub producing: ProbeProducing,
+ pub input_digests: ProbeResourceInput,
+ pub parameter_digests: ProbeParameterDigests,
+ pub resource_digests: ProbeResourceDigests,
+ pub policy_digests: ProbePolicyDigests,
+ pub cache_digests: ProbeCacheDigests,
+ pub output_port: String,
+ pub staging: ProbeStaging,
+ pub lineage: ProbeLineage,
+ pub publication_id: String,
+ pub fencing: ProbeFencing,
+}
+
+#[derive(Debug)]
+pub struct ProbeRuntimeResult {
+ pub normalized_observation: ProbeObservation,
+ pub output_contract: ProbeOutputContract,
+ pub containment: RuntimeContainmentEvidence,
+ pub captured_stdout: String,
+ pub captured_stderr: String,
+ pub pre_launch_hash: String,
+ pub post_launch_hash: String,
+}
+
+#[derive(Clone)]
+pub struct ProbeRuntimeContext {
+ pub request: Option,
+ pub schema_digests: HashMap,
+ pub publication_id: Option,
+ pub publication_fence_token: Option,
+ pub output_staging_path: Option,
+ pub output_artifact_id: Option,
+ pub output_port: Option,
+ pub watchdog_seconds: Option,
+}
+
+impl ProbeRuntimeContext {
+ pub fn new() -> Self {
+ Self {
+ request: None,
+ schema_digests: HashMap::new(),
+ publication_id: None,
+ publication_fence_token: None,
+ output_staging_path: None,
+ output_artifact_id: None,
+ output_port: None,
+ watchdog_seconds: None,
+ }
+ }
+
+ pub fn with_request(mut self, request: ProbePrelaunchRequest) -> Self {
+ self.request = Some(request);
+ self
+ }
+
+ pub fn with_schema_digests(mut self, digests: HashMap) -> Self {
+ self.schema_digests = digests;
+ self
+ }
+
+ pub fn with_publication(mut self, publication_id: impl Into, fence_token: impl Into) -> Self {
+ self.publication_id = Some(publication_id.into());
+ self.publication_fence_token = Some(fence_token.into());
+ self
+ }
+
+ pub fn with_output_staging(mut self, staging_path: impl Into) -> Self {
+ self.output_staging_path = Some(staging_path.into());
+ self
+ }
+
+ pub fn with_output_artifact_id(mut self, artifact_id: impl Into) -> Self {
+ self.output_artifact_id = Some(artifact_id.into());
+ self
+ }
+
+ pub fn with_output_port(mut self, output_port: impl Into) -> Self {
+ self.output_port = Some(output_port.into());
+ self
+ }
+
+ pub fn with_watchdog_seconds(mut self, seconds: u64) -> Self {
+ self.watchdog_seconds = Some(seconds);
+ self
+ }
+}
+
+#[derive(Clone)]
+struct CapturedOutput {
+ bytes: Vec,
+ truncated: bool,
+}
+
+pub trait ProbeProcessFactory: Send + Sync {
+ fn spawn(&self, executable: &str, argv: &[String]) -> io::Result;
+}
+
+#[derive(Default)]
+pub struct DefaultProbeProcessFactory;
+
+impl ProbeProcessFactory for DefaultProbeProcessFactory {
+ fn spawn(&self, executable: &str, argv: &[String]) -> io::Result {
+ let mut command = Command::new(executable);
+ command.args(argv);
+ command.stdin(Stdio::null());
+ command.stdout(Stdio::piped());
+ command.stderr(Stdio::piped());
+ command.spawn()
+ }
+}
+
+#[cfg(windows)]
+pub trait NetworkDenialController: Send + Sync {
+ fn establish(&self, executable: &str) -> Result;
+}
+
+#[cfg(not(windows))]
+pub trait NetworkDenialController: Send + Sync {
+ fn establish(&self, _executable: &str) -> Result;
+}
+
+#[derive(Default)]
+pub struct DefaultNetworkDenialController;
+
+#[cfg(windows)]
+impl NetworkDenialController for DefaultNetworkDenialController {
+ fn establish(&self, executable: &str) -> Result {
+ let token = unique_token("vid-impl-002b2-net");
+ let rule_in = format!("{token}-in");
+ let rule_out = format!("{token}-out");
+
+ add_network_rule(&rule_in, executable, true).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialSetupFailed,
+ message: format!("failed to create inbound deny rule: {error}"),
+ structured_error: None,
+ })?;
+
+ if let Err(error) = add_network_rule(&rule_out, executable, false) {
+ let _ = remove_network_rule(&rule_in);
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialSetupFailed,
+ message: format!("failed to create outbound deny rule: {error}"),
+ structured_error: None,
+ });
+ }
+
+ if !verify_network_rule(&rule_in).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ message: format!("inbound rule lookup failed: {error}"),
+ structured_error: None,
+ })? {
+ let _ = remove_network_rule(&rule_in);
+ let _ = remove_network_rule(&rule_out);
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ message: format!("inbound deny rule not visible: {rule_in}"),
+ structured_error: None,
+ });
+ }
+
+ if !verify_network_rule(&rule_out).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ message: format!("outbound rule lookup failed: {error}"),
+ structured_error: None,
+ })? {
+ let _ = remove_network_rule(&rule_in);
+ let _ = remove_network_rule(&rule_out);
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ message: format!("outbound deny rule not visible: {rule_out}"),
+ structured_error: None,
+ });
+ }
+
+ Ok(NetworkDenyGuard {
+ managed: true,
+ rule_in,
+ rule_out,
+ executable: executable.to_string(),
+ })
+ }
+}
+
+#[cfg(not(windows))]
+impl NetworkDenialController for DefaultNetworkDenialController {
+ fn establish(&self, _executable: &str) -> Result {
+ Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialUnavailable,
+ message: "network denial is only implemented on Windows".to_string(),
+ structured_error: None,
+ })
+ }
+}
+
+#[derive(Debug)]
+pub struct NetworkDenyGuard {
+ managed: bool,
+ rule_in: String,
+ rule_out: String,
+ executable: String,
+}
+
+impl Drop for NetworkDenyGuard {
+ fn drop(&mut self) {
+ if self.managed {
+ let _ = remove_network_rule(&self.rule_in);
+ let _ = remove_network_rule(&self.rule_out);
+ self.rule_in.clear();
+ self.rule_out.clear();
+ self.executable.clear();
+ }
+ }
+}
+
+#[cfg(windows)]
+pub trait JobObjectController: Send + Sync {
+ fn create(&self, limits: &ProbeChildLimits) -> Result;
+ fn assign_child(&self, job: &JobObjectGuard, child: &Child) -> Result<(), ProbeRunFailure>;
+}
+
+#[cfg(not(windows))]
+pub trait JobObjectController: Send + Sync {
+ fn create(&self, _limits: &ProbeChildLimits) -> Result;
+ fn assign_child(&self, _job: &JobObjectGuard, _child: &Child) -> Result<(), ProbeRunFailure>;
+}
+
+#[cfg(windows)]
+#[derive(Default)]
+pub struct DefaultJobObjectController;
+
+#[cfg(windows)]
+impl JobObjectController for DefaultJobObjectController {
+ fn create(&self, limits: &ProbeChildLimits) -> Result {
+ let process_cpu_ticks = wall_clock_ticks_to_100ns(limits.child_cpu_seconds);
+ let handle = unsafe {
+ CreateJobObjectW(None, PCWSTR::null()).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectSetupFailed,
+ message: format!("CreateJobObjectW failed: {error}"),
+ structured_error: None,
+ })?
+ };
+
+ let limits_extension = JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
+ BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION {
+ // Child CPU budget is enforced via process time (100ns ticks); wall-clock remains
+ // enforced separately by the watchdog.
+ PerProcessUserTimeLimit: process_cpu_ticks,
+ PerJobUserTimeLimit: 0,
+ ActiveProcessLimit: limits.active_process_limit,
+ LimitFlags: windows::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
+ | windows::Win32::System::JobObjects::JOB_OBJECT_LIMIT_ACTIVE_PROCESS
+ | windows::Win32::System::JobObjects::JOB_OBJECT_LIMIT_JOB_MEMORY
+ | windows::Win32::System::JobObjects::JOB_OBJECT_LIMIT_PROCESS_TIME,
+ ..Default::default()
+ },
+ JobMemoryLimit: usize::try_from(limits.resident_memory_bytes).unwrap_or(usize::MAX),
+ ..Default::default()
+ };
+
+ unsafe {
+ SetInformationJobObject(
+ handle,
+ windows::Win32::System::JobObjects::JobObjectExtendedLimitInformation,
+ &limits_extension as *const _ as *const c_void,
+ size_of::() as u32,
+ )
+ .map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectSetupFailed,
+ message: format!("SetInformationJobObject failed: {error}"),
+ structured_error: None,
+ })?;
+ }
+
+ Ok(JobObjectGuard { handle })
+ }
+
+ fn assign_child(&self, job: &JobObjectGuard, child: &Child) -> Result<(), ProbeRunFailure> {
+ let process_handle = HANDLE(child.as_raw_handle() as *mut c_void);
+ unsafe {
+ AssignProcessToJobObject(job.handle, process_handle).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectSetupFailed,
+ message: format!("AssignProcessToJobObject failed: {error}"),
+ structured_error: None,
+ })?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(not(windows))]
+impl JobObjectController for DefaultJobObjectController {
+ fn create(&self, _limits: &ProbeChildLimits) -> Result {
+ Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "job object is only implemented on Windows".to_string(),
+ structured_error: None,
+ })
+ }
+
+ fn assign_child(&self, _job: &JobObjectGuard, _child: &Child) -> Result<(), ProbeRunFailure> {
+ Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "job object is only implemented on Windows".to_string(),
+ structured_error: None,
+ })
+ }
+}
+
+#[derive(Default)]
+pub struct JobObjectGuard {
+ handle: HANDLE,
+}
+
+impl Drop for JobObjectGuard {
+ fn drop(&mut self) {
+ if !self.handle.is_invalid() {
+ let _ = unsafe { CloseHandle(self.handle) };
+ self.handle = HANDLE::default();
+ }
+ }
+}
+
+impl JobObjectGuard {
+ pub fn inert() -> Self {
+ Self { handle: HANDLE::default() }
+ }
+
+ pub fn handle(&self) -> HANDLE {
+ self.handle
+ }
+}
+
+pub trait WatchdogController: Send + Sync {
+ fn establish(&self, timeout_seconds: u64) -> Result;
+}
+
+#[derive(Default)]
+pub struct DefaultWatchdogController;
+
+impl WatchdogController for DefaultWatchdogController {
+ fn establish(&self, timeout_seconds: u64) -> Result {
+ if timeout_seconds == 0 {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::WatchdogUnavailable,
+ message: "timeout_seconds must be >0".to_string(),
+ structured_error: None,
+ });
+ }
+
+ Ok(WatchdogGuard { timeout_seconds })
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct WatchdogGuard {
+ timeout_seconds: u64,
+}
+
+impl WatchdogGuard {
+ pub fn timeout_seconds(&self) -> u64 {
+ self.timeout_seconds
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct ProbeChildLimits {
+ pub wall_clock_seconds: u64,
+ pub child_cpu_seconds: u64,
+ pub resident_memory_bytes: u64,
+ pub active_process_limit: u32,
+}
+
+#[derive(Debug)]
+pub struct ProbeInputEvidenceGuard {
+ _handle: Option,
+}
+
+pub fn execute_bound_probe(context: ProbeRuntimeContext) -> Result {
+ execute_bound_probe_with_context(
+ context,
+ &DefaultProbeProcessFactory,
+ &DefaultNetworkDenialController,
+ &DefaultJobObjectController,
+ &DefaultWatchdogController,
+ |_| false,
+ )
+}
+
+pub fn execute_bound_probe_with_context<
+ PF,
+ NC,
+ JC,
+ WD,
+ C,
+> (
+ context: ProbeRuntimeContext,
+ process_factory: &PF,
+ network_controller: &NC,
+ job_controller: &JC,
+ watchdog_controller: &WD,
+ is_cancelled: C,
+) -> Result
+where
+ PF: ProbeProcessFactory,
+ NC: NetworkDenialController,
+ JC: JobObjectController,
+ WD: WatchdogController,
+ C: FnMut(u64) -> bool,
+{
+ let request = context.request.ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "missing prelaunch request".to_string(),
+ structured_error: None,
+ }
+ .with_synthetic_request_context("error-missing-request", None))?;
+ let publication_id = context
+ .publication_id
+ .as_ref()
+ .filter(|value| !value.trim().is_empty())
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "missing publication_id".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))?
+ .to_string();
+ let publication_fence = context
+ .publication_fence_token
+ .as_ref()
+ .filter(|value| !value.trim().is_empty())
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::PublicationFenceMismatch,
+ message: "missing publication_fence_token".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))?
+ .to_string();
+
+ let expected_argv = fixed_probe_argv(&request.staged_input_path);
+ if request.launch_argv != expected_argv {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ArgumentMismatch,
+ message: "launch_argv is not exact fixed argv".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ validate_probe_prelaunch(&request, &context.schema_digests).map_err(|failure| {
+ let mapped = map_prelaunch_failure(&failure);
+ let message = format!("prelaunch validation failed: {failure:?}");
+ ProbeRunFailure {
+ code: mapped,
+ message,
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+
+ verify_registered_executable_identity(&request.executable_path, &request.executable_sha256)
+ .map_err(|failure| failure.bind_request_context(&request, None))?;
+ verify_registered_executable_metadata(&request.executable_path)
+ .map_err(|failure| failure.bind_request_context(&request, None))?;
+
+ let source_lock = hold_source_input_readonly(&request.staged_input_path).map_err(|error| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::SourcePrelaunchFailed,
+ message: format!("cannot hold source input read lock: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+
+ let pre_launch = live_fingerprint(&request.staged_input_path).map_err(|failure| {
+ match failure {
+ FingerprintFailure::IdentityMismatch => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourceIdentityMismatch,
+ message: "source identity does not match staged lease prelaunch".to_string(),
+ structured_error: None,
+ },
+ FingerprintFailure::LengthMismatch => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourceLengthMismatch,
+ message: "source length changed before launch".to_string(),
+ structured_error: None,
+ },
+ FingerprintFailure::Mutation | FingerprintFailure::AccessFailed => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourcePrelaunchFailed,
+ message: "source prelaunch fingerprint check failed".to_string(),
+ structured_error: None,
+ },
+ }
+ .bind_request_context(&request, None)
+ })?;
+ if pre_launch.0 != request.lease.staged_copy_sha256 || pre_launch.1 != request.lease.staged_copy_length {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::SourceMutationDetected,
+ message: "live source hash/length differs from lease before launch".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ let output_artifact_id = context
+ .output_artifact_id
+ .as_ref()
+ .filter(|value| !value.trim().is_empty())
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "missing output_artifact_id".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))?
+ .to_owned();
+ let output_port = context
+ .output_port
+ .as_ref()
+ .filter(|value| !value.trim().is_empty())
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "missing output_port".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))?
+ .to_owned();
+
+ if publication_fence != request.lease.expected_fence_token {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::PublicationFenceMismatch,
+ message: "publication fence token mismatch".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ validate_output_port_and_artifact_lineage(&output_port, &output_artifact_id, &request)
+ .map_err(|failure| failure.bind_request_context(&request, None))?;
+
+ let network_guard = network_controller
+ .establish(&request.executable_path)
+ .map_err(|failure| map_containment_failure(failure).bind_request_context(&request, None))?;
+ let watchdog_seconds = context
+ .watchdog_seconds
+ .unwrap_or(CONTROL_LIMITS.wall_clock_seconds);
+ let watchdog_guard = watchdog_controller
+ .establish(watchdog_seconds)
+ .map_err(|failure| map_watchdog_failure(failure).bind_request_context(&request, None))?;
+
+ let limits = ProbeChildLimits {
+ wall_clock_seconds: watchdog_seconds,
+ child_cpu_seconds: CONTROL_LIMITS.child_cpu_seconds,
+ resident_memory_bytes: CONTROL_LIMITS.resident_memory_bytes,
+ active_process_limit: 1,
+ };
+ let job_guard = job_controller
+ .create(&limits)
+ .map_err(|failure| map_containment_failure(failure).bind_request_context(&request, None))?;
+
+ let mut child = process_factory
+ .spawn(&request.executable_path, &expected_argv)
+ .map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::ChildLaunchFailed,
+ message: format!("spawn failed: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))?;
+
+ if let Err(error) = job_controller.assign_child(&job_guard, &child) {
+ let _ = child.kill();
+ let _ = child.wait();
+ return Err(error.bind_request_context(&request, None));
+ }
+
+ let stdout_capture = child
+ .stdout
+ .take()
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: "child did not expose stdout".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))
+ .and_then(|stream| {
+ spawn_output_reader(stream, PROBE_STDOUT_BYTES_LIMIT).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: format!("stdout capture failed: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))
+ })?;
+
+ let stderr_capture = child
+ .stderr
+ .take()
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: "child did not expose stderr".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))
+ .and_then(|stream| {
+ spawn_output_reader(stream, PROBE_STDERR_BYTES_LIMIT).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: format!("stderr capture failed: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None))
+ })?;
+
+ let child_status = wait_with_watchdog(&mut child, watchdog_guard.timeout_seconds(), is_cancelled).map_err(|error| {
+ let _ = child.kill();
+ let _ = child.wait();
+ error.bind_request_context(&request, None)
+ })?;
+
+ let stdout = stdout_capture.join().map_err(|_| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: "stdout reader thread panicked".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+ let stderr = stderr_capture.join().map_err(|_| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: "stderr reader thread panicked".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+ let stdout = stdout.map_err(|error| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: format!("stdout capture failed: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+ let stderr = stderr.map_err(|error| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputCaptureFailed,
+ message: format!("stderr capture failed: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+
+ let stdout_text = String::from_utf8_lossy(&stdout.bytes).into_owned();
+ let stderr_text = String::from_utf8_lossy(&stderr.bytes).into_owned();
+
+ if stdout.truncated {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputStdoutOversized,
+ message: "ffprobe stdout exceeded 1 MiB".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ if stderr.truncated {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputStderrOversized,
+ message: "ffprobe stderr exceeded 1 MiB".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ if !child_status.success() {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ChildCrashed,
+ message: format!("ffprobe exited non-zero: {child_status}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ let post_launch = live_fingerprint(&request.staged_input_path).map_err(|failure| {
+ match failure {
+ FingerprintFailure::IdentityMismatch => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourceMutationDetected,
+ message: "source identity changed during execution".to_string(),
+ structured_error: None,
+ },
+ FingerprintFailure::LengthMismatch => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourceMutationDetected,
+ message: "source length changed during execution".to_string(),
+ structured_error: None,
+ },
+ FingerprintFailure::Mutation | FingerprintFailure::AccessFailed => ProbeRunFailure {
+ code: ProbeRunFailureCode::SourcePostlaunchMismatch,
+ message: "source post-fingerprint check failed".to_string(),
+ structured_error: None,
+ },
+ }
+ .bind_request_context(&request, None)
+ })?;
+ if pre_launch != post_launch {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::SourcePostlaunchMismatch,
+ message: "source fingerprint changed across launch window".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ let normalized = parse_probe_observation(&stdout_text).map_err(|failure| {
+ failure.bind_request_context(&request, None)
+ })?;
+ if normalized.streams > PROBE_MAX_STREAMS {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonStreamLimitExceeded,
+ message: "stream count exceeds stream ceiling".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ validate_probe_output_completeness("complete").map_err(|failure| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputSerializationFailed,
+ message: format!("output completeness rejected: {failure:?}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+
+ let normalized_json = serde_json::to_string_pretty(&normalized).map_err(|error| {
+ ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputSerializationFailed,
+ message: format!("cannot serialize normalized observation: {error}"),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None)
+ })?;
+ if normalized_json.len() as u64 > CONTROL_ENVELOPE_BYTES {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputStdoutOversized,
+ message: "normalized observation exceeds output envelope".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ let normalized_hash = sha256_hex(normalized_json.as_bytes());
+ let output_contract = build_output_contract(
+ &request,
+ &context.schema_digests,
+ &publication_id,
+ &publication_fence,
+ &normalized_hash,
+ normalized_json.len() as u64,
+ &output_artifact_id,
+ context.output_staging_path,
+ &output_port,
+ &pre_launch.0,
+ &expected_argv,
+ )?;
+
+ if output_contract.publication_id != publication_id {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputLineageInvalid,
+ message: "output publication id mismatch".to_string(),
+ structured_error: None,
+ }
+ .bind_request_context(&request, None));
+ }
+
+ let _input_lock = source_lock;
+
+ Ok(ProbeRuntimeResult {
+ normalized_observation: ProbeObservation {
+ observation_id: format!("obs-{}", unique_token("vid-impl-002b2")),
+ schema_id: "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION".to_string(),
+ schema_version: "1.0.0-p00".to_string(),
+ probe_observation: normalized,
+ completeness: "complete".to_string(),
+ artifact_hash: normalized_hash,
+ artifact_size: normalized_json.len() as u64,
+ lineage_ref: publication_id,
+ },
+ output_contract,
+ containment: RuntimeContainmentEvidence {
+ network_denial_rule_in: network_guard.rule_in.clone(),
+ network_denial_rule_out: network_guard.rule_out.clone(),
+ network_rule_scope: format!("program={}", network_guard.executable),
+ job_object_established: !job_guard.handle().is_invalid(),
+ watchdog_seconds: watchdog_guard.timeout_seconds(),
+ },
+ captured_stdout: sanitize_diagnostics(&stdout_text),
+ captured_stderr: sanitize_diagnostics(&stderr_text),
+ pre_launch_hash: pre_launch.0,
+ post_launch_hash: post_launch.0,
+ })
+}
+
+fn build_output_contract(
+ request: &ProbePrelaunchRequest,
+ schema_digests: &HashMap,
+ publication_id: &str,
+ publication_fence: &str,
+ integrity_hash: &str,
+ integrity_size: u64,
+ output_artifact_id: &str,
+ output_staging_path: Option,
+ output_port: &str,
+ staged_copy_sha256: &str,
+ argv: &[String],
+) -> Result {
+ let arg_signature = sha256_hex(argv.join(" ").as_bytes());
+ let limits_signature = sha256_hex(
+ format!(
+ "wall_clock_seconds:{} child_cpu_seconds:{} resident_memory_bytes:{} control_envelope_bytes:{} stream_count:{}",
+ CONTROL_LIMITS.wall_clock_seconds,
+ CONTROL_LIMITS.child_cpu_seconds,
+ CONTROL_LIMITS.resident_memory_bytes,
+ CONTROL_LIMITS.control_envelope_bytes,
+ CONTROL_LIMITS.stream_count
+ )
+ .as_bytes(),
+ );
+
+ let policy_digest = schema_digests
+ .get("VID-IMPL-P00-002B1-POLICY")
+ .cloned()
+ .unwrap_or_else(|| format!("{:0<64}", "0"));
+ let resource_schema_digest = schema_digests
+ .get("VID-IMPL-P00-002B1-RESOURCE")
+ .cloned()
+ .unwrap_or_else(|| format!("{:0<64}", "0"));
+ let control_protocol_digest = VID_002B1_CONTROL_PROTOCOL_SHA256.to_string();
+ let component_inventory = vec!["probe-adapter".to_string(), "VID-IMPL-P00-002B1".to_string()];
+
+ Ok(ProbeOutputContract {
+ artifact_id: output_artifact_id.to_string(),
+ artifact_version: "1.0.0-p00".to_string(),
+ artifact_type: "probe_observation".to_string(),
+ output_schema_id: "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION".to_string(),
+ completeness: "complete".to_string(),
+ integrity: ProbeOutputIntegrity {
+ algorithm: "sha-256".to_string(),
+ value: integrity_hash.to_string(),
+ },
+ size_bytes: integrity_size,
+ component_inventory,
+ producing: ProbeProducing {
+ job_ref: request.job_id.clone(),
+ attempt_ref: request.attempt_id.clone(),
+ dispatch_ref: request.dispatch_id.clone(),
+ worker_ref: request.worker_instance_id.clone(),
+ operation_id: OPERATION_ID.to_string(),
+ operation_version: OPERATION_VERSION.to_string(),
+ implementation_id: "VID-IMPL-P00-002B1".to_string(),
+ implementation_profile_id: "VID-PROBE-P00-001".to_string(),
+ },
+ input_digests: ProbeResourceInput {
+ staged_copy_hash: request.lease.staged_copy_sha256.clone(),
+ manifest_digest: request.lease.manifest_digest.clone(),
+ logical_source_ref: request.lease.logical_source_ref.clone(),
+ },
+ parameter_digests: ProbeParameterDigests {
+ argv: arg_signature,
+ limits: limits_signature,
+ },
+ resource_digests: ProbeResourceDigests {
+ resource: VID_002B1_RESOURCE_PROFILE_SHA256.to_string(),
+ schema: resource_schema_digest,
+ },
+ policy_digests: ProbePolicyDigests {
+ policy: policy_fingerprint(),
+ schema: policy_digest,
+ },
+ cache_digests: ProbeCacheDigests {
+ schema_bundle: VID_002B1_SCHEMA_BUNDLE_SHA256.to_string(),
+ control_protocol: control_protocol_digest,
+ },
+ output_port: output_port.to_string(),
+ staging: ProbeStaging {
+ state: "staged-private".to_string(),
+ path: output_staging_path.unwrap_or_else(|| request.lease.access_scope.clone()),
+ delete_on_stale: true,
+ quarantine_on_failure: true,
+ },
+ lineage: ProbeLineage {
+ job_ref: request.job_id.clone(),
+ attempt_ref: request.attempt_id.clone(),
+ dispatch_ref: request.dispatch_id.clone(),
+ worker_ref: request.worker_instance_id.clone(),
+ publication_ref: publication_id.to_string(),
+ },
+ publication_id: publication_id.to_string(),
+ fencing: ProbeFencing {
+ required: true,
+ active_token: publication_fence.to_string(),
+ },
+ })
+}
+
+pub fn parse_probe_observation(raw: &str) -> Result {
+ let payload: Value = serde_json::from_str(raw).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: format!("ffprobe output is not valid JSON: {error}"),
+ structured_error: None,
+ })?;
+ let format = payload.get("format").and_then(Value::as_object).ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "missing format object".to_string(),
+ structured_error: None,
+ })?;
+
+ let format_name = format
+ .get("format_name")
+ .and_then(Value::as_str)
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "missing format_name".to_string(),
+ structured_error: None,
+ })?
+ .to_string();
+ if format_name.is_empty() {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "format_name is empty".to_string(),
+ structured_error: None,
+ });
+ }
+
+ let duration_seconds = format
+ .get("duration")
+ .and_then(parse_f64)
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "missing or invalid duration".to_string(),
+ structured_error: None,
+ })?;
+
+ let streams = payload
+ .get("streams")
+ .and_then(Value::as_array)
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "missing streams array".to_string(),
+ structured_error: None,
+ })?;
+
+ let declared_streams = format
+ .get("nb_streams")
+ .and_then(parse_u64)
+ .or_else(|| Some(streams.len() as u64))
+ .unwrap_or(0);
+ if declared_streams > PROBE_MAX_STREAMS {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonStreamLimitExceeded,
+ message: "declared stream count exceeds stream ceiling".to_string(),
+ structured_error: None,
+ });
+ }
+
+ if declared_streams != streams.len() as u64 {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JsonMalformed,
+ message: "declared stream count does not match stream list".to_string(),
+ structured_error: None,
+ });
+ }
+
+ let has_subtitles = streams.iter().any(|stream| {
+ stream
+ .get("codec_type")
+ .and_then(Value::as_str)
+ .is_some_and(|value| value.eq_ignore_ascii_case("subtitle"))
+ });
+
+ Ok(ProbeNormalizedObservation {
+ format: format_name,
+ duration_seconds,
+ streams: streams.len() as u64,
+ stream_count: declared_streams,
+ has_subtitles,
+ })
+}
+
+pub fn wait_with_watchdog bool>(
+ child: &mut Child,
+ timeout_seconds: u64,
+ mut is_cancelled: F,
+) -> Result {
+ let timeout = Duration::from_secs(timeout_seconds.max(1));
+ let started = Instant::now();
+ let poll_interval = Duration::from_millis(25);
+
+ loop {
+ if is_cancelled(started.elapsed().as_secs()) {
+ let _ = child.kill();
+ let status = child
+ .wait()
+ .map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::ChildCrashed,
+ message: format!("wait after cancellation failed: {error}"),
+ structured_error: None,
+ })?;
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ProcessCancellation,
+ message: format!(
+ "execution cancelled after {} seconds (status: {status})",
+ started.elapsed().as_secs()
+ ),
+ structured_error: None,
+ });
+ }
+
+ match child.try_wait() {
+ Ok(Some(status)) => return Ok(status),
+ Ok(None) => {}
+ Err(error) => {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ChildCrashed,
+ message: format!("child status query failed: {error}"),
+ structured_error: None,
+ });
+ }
+ }
+
+ if started.elapsed() >= timeout {
+ let _ = child.kill();
+ let status = child
+ .wait()
+ .map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::ChildCrashed,
+ message: format!("child wait after timeout failed: {error}"),
+ structured_error: None,
+ })?;
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::WatchdogTimeout,
+ message: format!("watchdog timeout reached, status {status}"),
+ structured_error: None,
+ });
+ }
+
+ thread::sleep(poll_interval);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::probe_adapter_contract::{
+ CONTROL_PROTOCOL_VERSION, EXPECTED_SCHEMA_REFERENCE_IDS, ProbeContainmentEvidence, ProbeLaunchPolicy,
+ ProbeLeaseManifest,
+ };
+ use jsonschema;
+ use std::collections::HashMap;
+ use std::fs::{self, OpenOptions};
+ use std::path::{Path, PathBuf};
+ use std::process::Stdio;
+ use std::sync::{Arc, Mutex};
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ const TEST_HEX_DIGEST: &str = "11";
+
+ #[derive(Clone, Default)]
+ struct CommandProcessFactory {
+ command: String,
+ args: Vec,
+ }
+
+ impl ProbeProcessFactory for CommandProcessFactory {
+ fn spawn(&self, _executable: &str, _argv: &[String]) -> io::Result {
+ Command::new(&self.command)
+ .args(&self.args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ }
+ }
+
+ #[derive(Clone, Default)]
+ struct MockNetworkController {
+ deny: bool,
+ unavailable: bool,
+ verify_failed: bool,
+ }
+
+ impl NetworkDenialController for MockNetworkController {
+ fn establish(&self, executable: &str) -> Result {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+
+ if self.deny {
+ Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialSetupFailed,
+ message: "mock setup failed".to_string(),
+ structured_error: None,
+ })
+ } else {
+ if self.verify_failed {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ message: "mock verify failed".to_string(),
+ structured_error: None,
+ });
+ }
+
+ Ok(NetworkDenyGuard {
+ managed: false,
+ rule_in: "mock-in".to_string(),
+ rule_out: "mock-out".to_string(),
+ executable: executable.to_string(),
+ })
+ }
+ }
+ }
+
+ #[derive(Clone, Default)]
+ struct MockJobController {
+ unavailable: bool,
+ }
+
+ impl JobObjectController for MockJobController {
+ fn create(&self, _limits: &ProbeChildLimits) -> Result {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+ Ok(JobObjectGuard::inert())
+ }
+
+ fn assign_child(&self, _job: &JobObjectGuard, _child: &Child) -> Result<(), ProbeRunFailure> {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+ Ok(())
+ }
+ }
+
+ #[derive(Clone, Default)]
+ struct CapturingJobController {
+ unavailable: bool,
+ captured: Arc>>,
+ }
+
+ impl CapturingJobController {
+ fn captured(&self) -> Option {
+ self.captured.lock().unwrap_or_else(|error| error.into_inner()).clone()
+ }
+ }
+
+ impl JobObjectController for CapturingJobController {
+ fn create(&self, limits: &ProbeChildLimits) -> Result {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+ *self
+ .captured
+ .lock()
+ .unwrap_or_else(|error| error.into_inner()) = Some(limits.clone());
+ Ok(JobObjectGuard::inert())
+ }
+
+ fn assign_child(&self, _job: &JobObjectGuard, _child: &Child) -> Result<(), ProbeRunFailure> {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::JobObjectUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+ Ok(())
+ }
+ }
+
+ #[derive(Clone, Default)]
+ struct MockWatchdogController {
+ unavailable: bool,
+ timeout: u64,
+ }
+
+ impl WatchdogController for MockWatchdogController {
+ fn establish(&self, timeout_seconds: u64) -> Result {
+ if self.unavailable {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::WatchdogUnavailable,
+ message: "mock unavailable".to_string(),
+ structured_error: None,
+ });
+ }
+
+ if timeout_seconds == 0 {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::WatchdogUnavailable,
+ message: "invalid timeout".to_string(),
+ structured_error: None,
+ });
+ }
+
+ Ok(WatchdogGuard {
+ timeout_seconds: self.timeout.max(timeout_seconds),
+ })
+ }
+ }
+
+ fn fixed_schema_digests() -> HashMap {
+ let mut digests = HashMap::new();
+ let digest = format!("{:0<64}", TEST_HEX_DIGEST);
+ EXPECTED_SCHEMA_REFERENCE_IDS
+ .iter()
+ .for_each(|schema| {
+ digests.insert((*schema).to_string(), digest.clone());
+ });
+ digests
+ }
+
+ fn request_schema_identity(path: &Path, schema_digests: &HashMap) -> Result {
+ let observation = fingerprint_local_file_with_hook(path, None::)
+ .map_err(|error| format!("fingerprint failed: {error:?}"))?;
+
+ let scope = path
+ .parent()
+ .and_then(|value| value.to_str())
+ .map(ToString::to_string)
+ .ok_or_else(|| "path has no parent".to_string())?;
+
+ Ok(ProbePrelaunchRequest {
+ operation_id: OPERATION_ID.to_string(),
+ operation_version: OPERATION_VERSION.to_string(),
+ implementation_id: "VID-IMPL-P00-002B1".to_string(),
+ implementation_profile_id: "VID-PROBE-P00-001".to_string(),
+ executable_path: REGISTERED_EXECUTABLE_PATH.to_string(),
+ executable_sha256: REGISTERED_EXECUTABLE_SHA256.to_string(),
+ staged_input_path: path.to_string_lossy().to_string(),
+ launch_argv: fixed_probe_argv(path.to_string_lossy().as_ref()),
+ launch_policy: ProbeLaunchPolicy {
+ use_shell: false,
+ use_path_lookup: false,
+ user_supplied_options: false,
+ },
+ schema_digests: schema_digests.clone(),
+ capabilities: vec!["cancellation".to_string(), "deadline".to_string()],
+ transport: "local-staged-file".to_string(),
+ evidence: ProbeContainmentEvidence {
+ network_denied: true,
+ watchdog_enabled: true,
+ job_object_enabled: true,
+ },
+ output_envelope_bytes: CONTROL_ENVELOPE_BYTES,
+ worker_instance_id: "worker-002b2".to_string(),
+ dispatch_id: "dispatch-002b2".to_string(),
+ control_protocol_version: CONTROL_PROTOCOL_VERSION.to_string(),
+ job_id: "job-002b2".to_string(),
+ attempt_id: "attempt-002b2".to_string(),
+ trace_id: "trace-002b2".to_string(),
+ correlation_id: "corr-002b2".to_string(),
+ error_instance_id: "err-002b2".to_string(),
+ lease: ProbeLeaseManifest {
+ lease_id: "lease-002b2".to_string(),
+ lease_epoch: 10,
+ minimum_acceptable_lease_epoch: 1,
+ immutable: true,
+ source_sha256: observation.sha256_hex.clone(),
+ source_length: observation.byte_length,
+ staged_copy_identity: path.to_string_lossy().to_string(),
+ staged_copy_sha256: observation.sha256_hex.clone(),
+ staged_copy_length: observation.byte_length,
+ observed_staged_copy_identity: path.to_string_lossy().to_string(),
+ observed_staged_copy_sha256: observation.sha256_hex.clone(),
+ observed_staged_copy_length: observation.byte_length,
+ access_scope: scope,
+ expires_at: "2099-01-01T00:00:00Z".to_string(),
+ manifest_digest: format!("{:0<64}", TEST_HEX_DIGEST),
+ logical_source_ref: "src-002b2".to_string(),
+ observed_fence_token: "pub-fence-002b2".to_string(),
+ expected_fence_token: "pub-fence-002b2".to_string(),
+ },
+ })
+ }
+
+ fn runtime_output_port() -> &'static str {
+ "probe-observation-output-port"
+ }
+
+fn runtime_output_artifact_id(request: &ProbePrelaunchRequest) -> String {
+ format!(
+ "artifact:job:{}:attempt:{}:dispatch:{}:worker:{}:port:{}",
+ request.job_id,
+ request.attempt_id,
+ request.dispatch_id,
+ request.worker_instance_id,
+ runtime_output_port()
+ )
+ }
+
+ fn runtime_context_with_lineage(request: ProbePrelaunchRequest) -> ProbeRuntimeContext {
+ let output_port = runtime_output_port().to_string();
+ let artifact_id = runtime_output_artifact_id(&request);
+ ProbeRuntimeContext::new()
+ .with_request(request.clone())
+ .with_schema_digests(fixed_schema_digests())
+ .with_output_port(output_port)
+ .with_output_artifact_id(artifact_id)
+ .with_publication(
+ format!("pub-{}", unique_token("runtime")),
+ request.lease.expected_fence_token.clone(),
+ )
+ }
+
+ fn assert_runtime_failure_has_structured_error(
+ failure: &ProbeRunFailure,
+ expected_code: ProbeRunFailureCode,
+ request: &ProbePrelaunchRequest,
+ expected_publication: Option<&str>,
+ ) {
+ assert_eq!(failure.code, expected_code);
+ let structured = failure
+ .structured_error
+ .as_ref()
+ .expect("runtime failures must carry structured_error");
+ assert!(!structured.error_ref.error_instance.trim().is_empty());
+ assert_eq!(structured.applies_to.job_ref, request.job_id);
+ assert_eq!(structured.applies_to.attempt_ref, request.attempt_id);
+ assert_eq!(structured.applies_to.dispatch_ref, request.dispatch_id);
+ assert_eq!(structured.applies_to.worker_ref, request.worker_instance_id);
+ if let Some(expected) = expected_publication {
+ assert_eq!(structured.applies_to.publication_ref.as_deref(), Some(expected));
+ } else {
+ assert!(
+ structured.applies_to.publication_ref.is_none(),
+ "pre-publication errors must keep publication_ref absent"
+ );
+ }
+ }
+
+ fn hex_like_64(value: &str) -> bool {
+ value.len() == 64 && value.chars().all(|c| c.is_ascii_hexdigit())
+ }
+
+ fn assert_output_contract_matches_output_schema(
+ request: &ProbePrelaunchRequest,
+ contract: &ProbeOutputContract,
+ schema_digests: &HashMap,
+ ) {
+ let contract_value = serde_json::to_value(contract).expect("output contract JSON value");
+ let (schema, schema_digest) = output_contract_schema();
+ let manifest_output_digest = schema_digests.get("VID-IMPL-P00-002B1-OUTPUT-CONTRACT");
+ assert_eq!(
+ manifest_output_digest.map(String::as_str),
+ Some(schema_digest.as_str()),
+ "output schema digest must match schema-bundle manifest"
+ );
+ assert_output_contract_value_schema_validation(&contract_value, &schema);
+ assert!(hex_like_64(schema_digest.as_str()));
+
+ assert_eq!(contract.artifact_type, "probe_observation");
+ assert_eq!(contract.completeness, "complete");
+ assert_eq!(contract.output_schema_id, "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION");
+ assert!(hex_like_64(&contract.integrity.value));
+ assert_eq!(contract.integrity.algorithm, "sha-256");
+ assert!(!contract.component_inventory.is_empty());
+ assert!(!contract.producing.job_ref.is_empty());
+ assert!(!contract.producing.attempt_ref.is_empty());
+ assert!(!contract.producing.dispatch_ref.is_empty());
+ assert!(!contract.producing.worker_ref.is_empty());
+ assert_eq!(contract.producing.operation_id, OPERATION_ID);
+ assert_eq!(contract.producing.operation_version, OPERATION_VERSION);
+ assert_eq!(contract.producing.implementation_id, "VID-IMPL-P00-002B1");
+ assert_eq!(contract.producing.implementation_profile_id, "VID-PROBE-P00-001");
+
+ assert_eq!(contract.input_digests.staged_copy_hash, request.lease.staged_copy_sha256);
+ assert_eq!(contract.input_digests.manifest_digest, request.lease.manifest_digest);
+ assert_eq!(contract.input_digests.logical_source_ref, request.lease.logical_source_ref);
+
+ assert!(hex_like_64(&contract.parameter_digests.argv));
+ assert!(hex_like_64(&contract.parameter_digests.limits));
+ assert!(hex_like_64(&contract.resource_digests.resource));
+ assert!(hex_like_64(&contract.resource_digests.schema));
+ assert!(hex_like_64(&contract.policy_digests.policy));
+ assert!(hex_like_64(&contract.policy_digests.schema));
+ assert!(hex_like_64(&contract.cache_digests.schema_bundle));
+ assert!(hex_like_64(&contract.cache_digests.control_protocol));
+
+ assert_eq!(contract.cache_digests.schema_bundle, VID_002B1_SCHEMA_BUNDLE_SHA256);
+ assert_eq!(contract.cache_digests.control_protocol, VID_002B1_CONTROL_PROTOCOL_SHA256);
+ assert_eq!(contract.resource_digests.resource, VID_002B1_RESOURCE_PROFILE_SHA256);
+ assert_eq!(
+ contract.resource_digests.schema,
+ *schema_digests
+ .get("VID-IMPL-P00-002B1-RESOURCE")
+ .unwrap_or(&String::new())
+ );
+ assert_eq!(
+ contract.policy_digests.schema,
+ *schema_digests
+ .get("VID-IMPL-P00-002B1-POLICY")
+ .unwrap_or(&String::new())
+ );
+
+ assert!(!contract.lineage.publication_ref.is_empty());
+ assert_eq!(contract.publication_id, contract.lineage.publication_ref);
+ assert!(!contract.publication_id.is_empty());
+ assert!(!contract.fencing.active_token.is_empty());
+ assert!(contract.fencing.required);
+ }
+
+ fn output_contract_schema() -> (Value, String) {
+ let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("Video Localization directory must exist");
+ let schema_path = manifest_path.join(
+ "contracts/VID-IMPL-P00-002B1/schemas/VID-IMPL-P00-002B1-OUTPUT-CONTRACT.schema.json",
+ );
+ let schema_text =
+ fs::read_to_string(schema_path).expect("output contract schema should exist");
+ let schema_digest = sha256_hex(schema_text.as_bytes());
+ (serde_json::from_str(&schema_text).expect("output contract schema should parse"), schema_digest)
+ }
+
+ fn assert_output_contract_value_schema_validation(contract: &Value, schema: &Value) {
+ let validator = jsonschema::draft202012::new(schema).expect("output contract schema should compile");
+ let errors: Vec<_> = validator.iter_errors(contract).collect();
+ assert!(
+ errors.is_empty(),
+ "output contract must satisfy schema: {}",
+ errors
+ .into_iter()
+ .map(|error| error.to_string())
+ .collect::>()
+ .join("; ")
+ );
+ }
+
+ fn assert_output_contract_validation_rejects(contract: &mut Value, schema: &Value, failure: &str) {
+ let validator = jsonschema::draft202012::new(schema).expect("output contract schema should compile");
+ let errors: Vec<_> = validator.iter_errors(contract).collect();
+ assert!(
+ !errors.is_empty(),
+ "output contract mutation should fail: {failure}"
+ );
+ assert!(
+ errors.iter().all(|error| !error.to_string().trim().is_empty()),
+ "validator should emit diagnostics for {failure}"
+ );
+ }
+
+ fn build_success_output_contract_for_schema_validation() -> (ProbeOutputContract, HashMap) {
+ let staged = temp_path("schema-validation-output", "bin");
+ let output_json_path = temp_path("schema-validation-output", "json");
+ let output_payload =
+ r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"3"},"streams":[{"codec_type":"video"},{"codec_type":"audio"},{"codec_type":"subtitle"}]}"#;
+ write_temp_binary(&staged, b"source");
+ write_temp_text(&output_json_path, output_payload);
+
+ let schema_digests = schema_digest_map_from_manifest();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.launch_argv = fixed_probe_argv(&staged.to_string_lossy());
+ let artifact_id = runtime_output_artifact_id(&request);
+ let publication_id = "pub-002b2";
+ let context = ProbeRuntimeContext::new()
+ .with_request(request)
+ .with_schema_digests(schema_digests.clone())
+ .with_watchdog_seconds(10)
+ .with_output_artifact_id(artifact_id.clone())
+ .with_output_port(runtime_output_port().to_string())
+ .with_publication(publication_id, "pub-fence-002b2");
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), format!("type {}", output_json_path.to_string_lossy())],
+ };
+
+ let result = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect("runtime should succeed");
+
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(output_json_path);
+ (result.output_contract, schema_digests)
+ }
+
+ fn mutate_and_assert_schema_failure_for_root_field(
+ contract: &Value,
+ schema: &Value,
+ path: &str,
+ replacement: Value,
+ failure: &str,
+ ) {
+ let mut contract = contract.clone();
+ let mut cursor = &mut contract;
+ let parts: Vec<&str> = path.split('.').collect();
+ if parts.is_empty() {
+ return;
+ }
+ for (index, part) in parts[..parts.len() - 1].iter().enumerate() {
+ let obj = cursor
+ .as_object_mut()
+ .expect(&format!("mutation path {path} expects object at segment {index}"));
+ cursor = obj
+ .entry((*part).to_string())
+ .or_insert_with(|| panic!("mutation path {path} missing segment {part}"));
+ }
+ let parent = cursor
+ .as_object_mut()
+ .expect(&format!("mutation path {path} expects final parent as object"));
+ parent.insert(parts[parts.len() - 1].to_string(), replacement);
+ assert_output_contract_validation_rejects(&mut contract, schema, failure);
+ }
+
+ fn output_contract_mutation_rejects_path_removal(
+ contract: &Value,
+ schema: &Value,
+ path: &str,
+ failure: &str,
+ ) {
+ let mut contract = contract.clone();
+ let mut cursor = &mut contract;
+ let parts: Vec<&str> = path.split('.').collect();
+ assert!(!parts.is_empty(), "mutation path must be non-empty");
+
+ if parts.len() == 1 {
+ let removed = cursor
+ .as_object_mut()
+ .expect("root object required for contract mutation")
+ .remove(parts[0]);
+ assert!(removed.is_some(), "path target exists");
+ assert_output_contract_validation_rejects(&mut contract, schema, failure);
+ return;
+ }
+
+ for (index, part) in parts[..parts.len() - 1].iter().enumerate() {
+ let obj = cursor
+ .as_object_mut()
+ .expect(&format!("mutation path {path} expects object at segment {index}"));
+ cursor = obj
+ .entry((*part).to_string())
+ .or_insert_with(|| panic!("mutation path {path} missing segment {part}"));
+ }
+
+ let parent = cursor
+ .as_object_mut()
+ .expect(&format!("mutation path {path} expects final parent as object"));
+ let removed = parent.remove(parts[parts.len() - 1]);
+ assert!(removed.is_some(), "path target exists");
+ assert_output_contract_validation_rejects(&mut contract, schema, failure);
+ }
+
+ fn mutation_target_paths_with_additional_properties_false(schema: &Value) -> Vec {
+ fn walk(path: &str, value: &Value, out: &mut Vec) {
+ let obj = match value {
+ Value::Object(map) => map,
+ _ => return,
+ };
+ let is_object = matches!(obj.get("type"), Some(Value::String(value_type)) if value_type == "object");
+ let has_additional_false = matches!(obj.get("additionalProperties"), Some(Value::Bool(false)));
+ if is_object && has_additional_false {
+ if !path.is_empty() {
+ out.push(path.trim_end_matches('.').to_string());
+ }
+ }
+
+ if let Some(properties) = obj.get("properties").and_then(Value::as_object) {
+ for (key, child) in properties {
+ let child_path = if path.is_empty() {
+ key.to_string()
+ } else {
+ format!("{path}.{key}")
+ };
+ walk(&child_path, child, out);
+ }
+ }
+ }
+
+ let mut paths = Vec::new();
+ walk("", schema, &mut paths);
+ paths
+ }
+
+ fn schema_enforces_additional_properties_false(schema: &Value, path: &str) -> bool {
+ let mut cursor = schema;
+ if path.is_empty() {
+ return cursor
+ .get("additionalProperties")
+ .and_then(Value::as_bool)
+ .unwrap_or(false);
+ }
+ for segment in path.split('.') {
+ cursor = cursor
+ .get("properties")
+ .and_then(Value::as_object)
+ .and_then(|properties| properties.get(segment))
+ .unwrap_or_else(|| panic!("schema path {path} missing {segment}"));
+ }
+ cursor
+ .get("additionalProperties")
+ .and_then(Value::as_bool)
+ .unwrap_or(false)
+ }
+
+ fn with_closed_nested_unknown_property_checks(
+ contract: &ProbeOutputContract,
+ schema: &Value,
+ closed_paths: &[String],
+ ) {
+ let contract_value = serde_json::to_value(contract).expect("output contract JSON value");
+ for object_path in closed_paths {
+ assert!(schema_enforces_additional_properties_false(schema, object_path));
+ let mut mutated = contract_value.clone();
+ let mut cursor = mutated.as_object_mut().expect("root contract object");
+ let segments: Vec<&str> = object_path.split('.').collect();
+ for (index, segment) in segments.iter().enumerate() {
+ cursor = cursor
+ .get_mut(*segment)
+ .and_then(Value::as_object_mut)
+ .unwrap_or_else(|| {
+ panic!("contract object missing segment {segment} at {index} for {object_path}")
+ });
+ }
+ cursor.insert("unknown_field".to_string(), Value::String("not-allowed".to_string()));
+ assert_output_contract_validation_rejects(&mut mutated, schema, &format!(
+ "unknown nested property should fail under additionalProperties:false at {object_path}"
+ ));
+ }
+ }
+
+ fn make_output_contract_schema_validation_base() -> (
+ ProbeOutputContract,
+ Value,
+ Value,
+ HashMap,
+ ) {
+ let (contract, schema_digests) = build_success_output_contract_for_schema_validation();
+ let contract_value = serde_json::to_value(&contract).expect("output contract JSON value");
+ let (schema, schema_digest) = output_contract_schema();
+ let manifest_output_digest = schema_digests.get("VID-IMPL-P00-002B1-OUTPUT-CONTRACT");
+ assert_eq!(
+ manifest_output_digest.map(String::as_str),
+ Some(schema_digest.as_str()),
+ "output schema digest must match schema-bundle manifest"
+ );
+ (contract, contract_value, schema, schema_digests)
+ }
+
+ fn schema_digest_map_from_manifest() -> HashMap {
+ let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("Video Localization directory must exist")
+ .to_string_lossy()
+ .into_owned();
+ let manifest_text = fs::read_to_string(
+ Path::new(&manifest_path).join("contracts/VID-IMPL-P00-002B1/schema-bundle.json"),
+ )
+ .expect("schema bundle manifest should exist");
+ let manifest_json: serde_json::Value = serde_json::from_str(&manifest_text).expect("schema bundle should parse");
+ let mut digests = HashMap::new();
+ if let Some(schema_records) = manifest_json.get("schema_records").and_then(|value| value.as_array()) {
+ for schema in schema_records {
+ let schema_id = schema
+ .get("schema_id")
+ .and_then(Value::as_str)
+ .expect("schema id present");
+ let digest = schema
+ .get("sha256")
+ .and_then(Value::as_str)
+ .expect("schema digest present");
+ digests.insert(schema_id.to_string(), digest.to_ascii_lowercase());
+ }
+ }
+ digests
+ }
+
+ fn temp_path(prefix: &str, ext: &str) -> PathBuf {
+ let mut path = std::env::temp_dir();
+ let nanos = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("monotonic clock")
+ .as_nanos();
+ path.push(format!("vid-impl-002b2-{prefix}-{nanos}.{ext}"));
+ path
+ }
+
+ fn write_temp_text(path: &Path, content: &str) {
+ fs::write(path, content).expect("write temp file");
+ }
+
+ fn write_temp_binary(path: &Path, content: &[u8]) {
+ fs::write(path, content).expect("write temp binary");
+ }
+
+ #[test]
+ fn bounded_parse_rejects_malformed_probe_json() {
+ let malformed = r#"{"format": "bad""#;
+ assert!(parse_probe_observation(malformed).is_err());
+ }
+
+ #[test]
+ fn bounded_parse_rejects_stream_limit_overflow() {
+ let too_many = r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"257"},"streams":[]}"#;
+ let error = parse_probe_observation(too_many).expect_err("should reject stream-count ceiling");
+ assert_eq!(error.code, ProbeRunFailureCode::JsonStreamLimitExceeded);
+ }
+
+ #[test]
+ fn source_lock_prevents_mutation_and_relink_races() {
+ let path = temp_path("lock", "bin");
+ write_temp_binary(&path, b"vid-impl-002b2-source-lock");
+ let guard = hold_source_input_readonly(
+ path.to_str().expect("path string"),
+ )
+ .expect("open read lock");
+
+ assert!(OpenOptions::new()
+ .write(true)
+ .open(&path)
+ .is_err());
+
+ let moved = temp_path("lock-target", "bin");
+ assert!(fs::rename(&path, &moved).is_err());
+ assert!(fs::remove_file(&path).is_err());
+
+ drop(guard);
+
+ let mut rename_ok = false;
+ for _ in 0..10 {
+ if fs::rename(&path, &moved).is_ok() {
+ rename_ok = true;
+ break;
+ }
+ std::thread::sleep(Duration::from_millis(25));
+ }
+ assert!(rename_ok, "rename after source-lock drop should eventually succeed");
+ fs::remove_file(&moved).expect("cleanup");
+ }
+
+ #[test]
+ fn execute_probe_passes_active_process_and_cpu_time_limits_to_job_object() {
+ let staged = temp_path("job-object-limits", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let capture = CapturingJobController::default();
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_watchdog_seconds(10);
+ let expected_json = r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"1"},"streams":[{"codec_type":"video"}]}"#;
+ let output_json_path = temp_path("job-object-limits-output", "json");
+ write_temp_text(&output_json_path, expected_json);
+ let command = output_json_path.to_string_lossy().to_string();
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), format!("type {command}")],
+ };
+
+ let result = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &capture,
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect("runtime should succeed with captured job limits");
+
+ let captured_limits = capture
+ .captured()
+ .expect("job limits captured before spawn");
+ assert_eq!(captured_limits.active_process_limit, 1);
+ assert_eq!(captured_limits.child_cpu_seconds, CONTROL_LIMITS.child_cpu_seconds);
+
+ assert_eq!(result.output_contract.publication_id, publication_id);
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(output_json_path);
+ }
+
+ #[test]
+ fn execute_probe_success_with_mocked_containers_and_valid_json() {
+ let staged = temp_path("fixture-success", "json");
+ let output_json_path = temp_path("fixture-output", "json");
+ let output_payload = r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"3"},"streams":[{"codec_type":"video"},{"codec_type":"audio"},{"codec_type":"subtitle"}]}"#;
+ write_temp_text(&staged, "probe-source-placeholder");
+ write_temp_text(&output_json_path, output_payload);
+
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let artifact_id = runtime_output_artifact_id(&request);
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_output_artifact_id(artifact_id.clone())
+ .with_watchdog_seconds(10);
+
+ let command = output_json_path.to_string_lossy().to_string();
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), format!("type {command}")],
+ };
+
+ let result = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect("mocked runtime should succeed");
+
+ assert_eq!(result.normalized_observation.probe_observation.format, "matroska");
+ assert_eq!(result.normalized_observation.probe_observation.stream_count, 3);
+ assert_eq!(result.normalized_observation.probe_observation.streams, 3);
+ assert!(result.normalized_observation.probe_observation.has_subtitles);
+ assert_eq!(result.output_contract.publication_id, "pub-002b2");
+ assert_eq!(result.output_contract.artifact_id, artifact_id);
+ assert!(result.captured_stdout.contains("format"));
+ assert_output_contract_matches_output_schema(
+ &request,
+ &result.output_contract,
+ &schema_digests,
+ );
+
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(output_json_path);
+ }
+
+ #[test]
+ fn execute_probe_fails_without_publication_id() {
+ let staged = temp_path("missing-publication", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let context = ProbeRuntimeContext::new()
+ .with_request(request.clone())
+ .with_schema_digests(schema_digests)
+ .with_output_staging(temp_path("missing-publication", "tmp").to_string_lossy().to_string())
+ .with_output_artifact_id(runtime_output_artifact_id(&request))
+ .with_output_port(runtime_output_port().to_string());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("missing publication_id should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PrelaunchValidationFailed,
+ &request,
+ None,
+ );
+ assert_eq!(error.message, "missing publication_id");
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_without_publication_fence_token() {
+ let staged = temp_path("missing-fence", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let context = ProbeRuntimeContext::new()
+ .with_request(request.clone())
+ .with_schema_digests(schema_digests)
+ .with_publication("pub-002b2", "")
+ .with_output_staging(temp_path("missing-fence", "tmp").to_string_lossy().to_string())
+ .with_output_artifact_id(runtime_output_artifact_id(&request))
+ .with_output_port(runtime_output_port().to_string());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("missing publication_fence_token should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PublicationFenceMismatch,
+ &request,
+ None,
+ );
+ assert_eq!(error.message, "missing publication_fence_token");
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_output_contract_matches_002b1_schema_digests() {
+ let staged = temp_path("fixture-contract", "bin");
+ let output_json_path = temp_path("fixture-output", "json");
+ let output_payload = r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"3"},"streams":[{"codec_type":"video"},{"codec_type":"audio"},{"codec_type":"subtitle"}]}"#;
+ write_temp_binary(&staged, b"source");
+ write_temp_text(&output_json_path, output_payload);
+
+ let schema_digests = schema_digest_map_from_manifest();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.launch_argv = fixed_probe_argv(&staged.to_string_lossy());
+ let artifact_id = runtime_output_artifact_id(&request);
+ let publication_id = "pub-002b2";
+ let request_for_error_assertion = request.clone();
+ let context = ProbeRuntimeContext::new()
+ .with_request(request)
+ .with_schema_digests(schema_digests.clone())
+ .with_watchdog_seconds(10)
+ .with_output_artifact_id(artifact_id.clone())
+ .with_output_port(runtime_output_port().to_string())
+ .with_publication(publication_id, request_for_error_assertion.lease.expected_fence_token.clone());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), format!("type {}", output_json_path.to_string_lossy())],
+ };
+
+ let result = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect("runtime should succeed");
+
+ let serialized = serde_json::to_string_pretty(&result.output_contract)
+ .expect("output contract should serialize");
+ let _value: serde_json::Value =
+ serde_json::from_str(&serialized).expect("output contract is valid JSON");
+ assert_output_contract_matches_output_schema(
+ &request_for_error_assertion,
+ &result.output_contract,
+ &schema_digests,
+ );
+
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(output_json_path);
+ }
+
+ #[test]
+ fn execute_probe_output_contract_matches_output_schema_via_draft2020_12_validator() {
+ let (contract, contract_value, schema, _schema_digests) = make_output_contract_schema_validation_base();
+ assert_output_contract_value_schema_validation(&contract_value, &schema);
+ assert_eq!(contract.artifact_type, "probe_observation");
+ assert_eq!(contract.completeness, "complete");
+ }
+
+ #[test]
+ fn output_contract_schema_rejects_required_field_mutations() {
+ let (contract, _contract_value, schema, _schema_digests) =
+ make_output_contract_schema_validation_base();
+ let base = serde_json::to_value(&contract).expect("output contract should serialize into JSON value");
+ let root_required_fields = [
+ "artifact_id",
+ "artifact_version",
+ "artifact_type",
+ "output_schema_id",
+ "completeness",
+ "integrity",
+ "size_bytes",
+ "component_inventory",
+ "producing",
+ "input_digests",
+ "parameter_digests",
+ "resource_digests",
+ "policy_digests",
+ "cache_digests",
+ "output_port",
+ "staging",
+ "lineage",
+ "publication_id",
+ "fencing",
+ ];
+ for path in root_required_fields {
+ output_contract_mutation_rejects_path_removal(
+ &base,
+ &schema,
+ path,
+ &format!("missing root field {path}"),
+ );
+ }
+
+ let nested_required_paths = [
+ "integrity.algorithm",
+ "integrity.value",
+ "producing.job_ref",
+ "producing.attempt_ref",
+ "producing.dispatch_ref",
+ "producing.worker_ref",
+ "producing.operation_id",
+ "producing.operation_version",
+ "producing.implementation_id",
+ "producing.implementation_profile_id",
+ "input_digests.staged_copy_hash",
+ "input_digests.manifest_digest",
+ "input_digests.logical_source_ref",
+ "parameter_digests.argv",
+ "parameter_digests.limits",
+ "resource_digests.resource",
+ "resource_digests.schema",
+ "policy_digests.policy",
+ "policy_digests.schema",
+ "cache_digests.schema_bundle",
+ "cache_digests.control_protocol",
+ "staging.state",
+ "staging.path",
+ "staging.delete_on_stale",
+ "staging.quarantine_on_failure",
+ "lineage.job_ref",
+ "lineage.attempt_ref",
+ "lineage.dispatch_ref",
+ "lineage.worker_ref",
+ "lineage.publication_ref",
+ "fencing.required",
+ "fencing.active_token",
+ ];
+ for path in nested_required_paths {
+ output_contract_mutation_rejects_path_removal(
+ &base,
+ &schema,
+ path,
+ &format!("missing nested field {path}"),
+ );
+ }
+ }
+
+ #[test]
+ fn output_contract_schema_rejects_unknown_properties() {
+ let (contract, _contract_value, schema, _schema_digests) = make_output_contract_schema_validation_base();
+ let mut contract_value = serde_json::to_value(&contract)
+ .expect("output contract should serialize into JSON value");
+ contract_value
+ .as_object_mut()
+ .expect("output contract is object")
+ .insert("unexpected_root_property".to_string(), Value::String("closed".to_string()));
+ assert_output_contract_validation_rejects(
+ &mut contract_value,
+ &schema,
+ "unknown root property should fail under additionalProperties:false",
+ );
+
+ let closed_paths = mutation_target_paths_with_additional_properties_false(&schema);
+ with_closed_nested_unknown_property_checks(&contract, &schema, &closed_paths);
+ }
+
+ #[test]
+ fn output_contract_schema_rejects_malformed_digest_and_violation_classes() {
+ let (contract, _contract_value, schema, _schema_digests) = make_output_contract_schema_validation_base();
+ let base = serde_json::to_value(&contract).expect("output contract should serialize into JSON value");
+
+ let digest_mutations = [
+ ("integrity.value", Value::String("short".to_string())),
+ ("integrity.value", Value::String("z".repeat(64))),
+ ("input_digests.staged_copy_hash", Value::String("not-a-hex-string".to_string())),
+ ("input_digests.manifest_digest", Value::String("123456".to_string())),
+ ("parameter_digests.argv", Value::String("not_hex_".to_string())),
+ ("parameter_digests.limits", Value::String("".to_string())),
+ ("resource_digests.resource", Value::String("G".repeat(64))),
+ ("resource_digests.schema", Value::String("".to_string())),
+ ("policy_digests.policy", Value::String("".to_string())),
+ ("policy_digests.schema", Value::String("z".repeat(128))),
+ ("cache_digests.schema_bundle", Value::String("00ZZ".to_string())),
+ ("cache_digests.control_protocol", Value::String("".to_string())),
+ ];
+ for (path, value) in digest_mutations {
+ mutate_and_assert_schema_failure_for_root_field(
+ &base,
+ &schema,
+ path,
+ value,
+ &format!("malformed digest at {path}"),
+ );
+ }
+
+ let enum_and_type_mutations = [
+ ("artifact_type", Value::String("audio_packet".to_string())),
+ ("completeness", Value::String("partial".to_string())),
+ ("integrity.algorithm", Value::String("md5".to_string())),
+ ("size_bytes", Value::String("not-an-integer".to_string())),
+ ("size_bytes", Value::from(-1)),
+ ("component_inventory", Value::String("unexpected".to_string())),
+ ("staging", Value::Object(serde_json::Map::from_iter(vec![(
+ "state".to_string(),
+ Value::Bool(true),
+ )]))),
+ ("lineage.publication_ref", Value::Bool(false)),
+ ("publication_id", Value::Object(serde_json::Map::new())),
+ ];
+ for (path, value) in enum_and_type_mutations {
+ mutate_and_assert_schema_failure_for_root_field(
+ &base,
+ &schema,
+ path,
+ value,
+ &format!("schema violation at {path}"),
+ );
+ }
+
+ // Keep coverage for minItems on component_inventory.
+ let mut contract_value = base;
+ contract_value["component_inventory"] = Value::Array(vec![]);
+ assert_output_contract_validation_rejects(
+ &mut contract_value,
+ &schema,
+ "component_inventory minItems:1",
+ );
+ }
+
+ #[test]
+ fn execute_probe_fails_when_output_artifact_id_is_missing() {
+ let staged = temp_path("missing-artifact", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_output_artifact_id("")
+ .with_watchdog_seconds(10);
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "echo {}".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("missing output artifact id should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PrelaunchValidationFailed,
+ &request,
+ None,
+ );
+ assert_eq!(error.message, "missing output_artifact_id");
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_output_artifact_id_is_not_bound_to_lineage() {
+ let staged = temp_path("unbound-artifact", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_output_artifact_id("artifact-not-bound")
+ .with_output_port("probe-observation-output-port")
+ .with_watchdog_seconds(10);
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "echo {}".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("unbound artifact id should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::OutputLineageInvalid,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_output_port_is_missing() {
+ let staged = temp_path("missing-output-port", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = ProbeRuntimeContext::new()
+ .with_request(request.clone())
+ .with_schema_digests(schema_digests)
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_output_artifact_id(runtime_output_artifact_id(&request))
+ .with_watchdog_seconds(10);
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "echo {}".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("missing output port should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PrelaunchValidationFailed,
+ &request,
+ None,
+ );
+ assert_eq!(error.message, "missing output_port");
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_on_executable_identity_mismatch() {
+ let staged = temp_path("mismatch", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.executable_path = "C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffmpeg.exe".to_string();
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("identity mismatch should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::ExecutableIdentityMismatch,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_launch_argv_is_not_fixed() {
+ let staged = temp_path("launch-argv", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.launch_argv = vec!["-show_entries".to_string(), "format".to_string()];
+
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "echo {} | type con".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("non-fixed argv should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::ArgumentMismatch,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_on_stale_lease() {
+ let staged = temp_path("stale", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.lease.lease_epoch = 0;
+ request.lease.minimum_acceptable_lease_epoch = 1;
+
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("stale lease should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PublicationFenceMismatch,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_publication_fence_changes() {
+ let staged = temp_path("fence", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let mut request = request_schema_identity(&staged, &schema_digests).expect("request");
+ request.lease.expected_fence_token = "expected-token".to_string();
+ request.lease.observed_fence_token = "observed-token".to_string();
+
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone()).with_publication(publication_id, "mismatch-token");
+
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("fence mismatch should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::PublicationFenceMismatch,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_source_changes_before_spawn() {
+ let staged = temp_path("source-prelaunch-mutation", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+
+ write_temp_binary(&staged, b"mutated before launch");
+ let request = request;
+
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("source mutation before launch should fail");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::SourceMutationDetected,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_network_containment_unavailable() {
+ let staged = temp_path("network", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController {
+ unavailable: true,
+ ..Default::default()
+ },
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("network unavailability should fail");
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::NetworkDenialUnavailable,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_network_verification_fails() {
+ let staged = temp_path("network-verify", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController {
+ verify_failed: true,
+ ..Default::default()
+ },
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("network verification failure should fail distinctly from setup");
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::NetworkDenialVerifyFailed,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn prelaunch_and_runtime_codes_keep_expired_lease_and_network_verify_distinct() {
+ assert_ne!(
+ map_prelaunch_failure(&ProbePrelaunchFailure::ExpiredLease),
+ ProbeRunFailureCode::NetworkDenialVerifyFailed
+ );
+ assert_ne!(
+ map_prelaunch_failure(&ProbePrelaunchFailure::ExpiredLease),
+ ProbeRunFailureCode::NetworkDenialSetupFailed
+ );
+ }
+
+ #[test]
+ fn execute_probe_fails_when_job_object_unavailable() {
+ let staged = temp_path("job", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController {
+ unavailable: true,
+ ..Default::default()
+ },
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("job unavailable should fail");
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::JobObjectUnavailable,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_child_launch_fails() {
+ let staged = temp_path("launch-fail", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+
+ let factory = CommandProcessFactory {
+ command: "does-not-exist.exe".to_string(),
+ args: vec!["/C".to_string()],
+ };
+
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("missing executable should fail launch");
+
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::ChildLaunchFailed,
+ &request,
+ None,
+ );
+ assert!(error.message.starts_with("spawn failed"));
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_watchdog_unavailable() {
+ let staged = temp_path("watchdog", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 0".to_string()],
+ };
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ unavailable: true,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("watchdog unavailable should fail");
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::WatchdogUnavailable,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_fails_when_process_crashes() {
+ let staged = temp_path("crash", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone());
+ let factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "exit 1".to_string()],
+ };
+ let error = execute_bound_probe_with_context(
+ context,
+ &factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("non-zero child should fail");
+ assert_runtime_failure_has_structured_error(
+ &error,
+ ProbeRunFailureCode::ChildCrashed,
+ &request,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ }
+
+ #[test]
+ fn execute_probe_detects_malformed_and_misaligned_child_output() {
+ let staged = temp_path("malformed", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let request_for_error_assertion = request.clone();
+ let publication_id = "pub-002b2";
+ let context = runtime_context_with_lineage(request)
+ .with_publication(publication_id, "pub-fence-002b2".to_string());
+ let malformed_factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), "echo not-json".to_string()],
+ };
+ let malformed_error = execute_bound_probe_with_context(
+ context.clone(),
+ &malformed_factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("malformed should fail");
+ assert_runtime_failure_has_structured_error(
+ &malformed_error,
+ ProbeRunFailureCode::JsonMalformed,
+ &request_for_error_assertion,
+ None,
+ );
+
+ let oversized_payload = "x".repeat((CONTROL_ENVELOPE_BYTES + 1024) as usize);
+ let oversized_path = temp_path("oversized", "txt");
+ write_temp_text(&oversized_path, &oversized_payload);
+ let oversized_factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), format!("type {}", oversized_path.to_string_lossy())],
+ };
+
+ let context = runtime_context_with_lineage(
+ request_schema_identity(&staged, &fixed_schema_digests()).expect("request"),
+ )
+ .with_publication(publication_id, "pub-fence-002b2".to_string());
+ let oversized_error = execute_bound_probe_with_context(
+ context,
+ &oversized_factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 10,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("oversized should fail");
+ assert_runtime_failure_has_structured_error(
+ &oversized_error,
+ ProbeRunFailureCode::OutputStdoutOversized,
+ &request_for_error_assertion,
+ None,
+ );
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(oversized_path);
+ }
+
+ #[test]
+ fn execute_probe_times_out_and_can_be_cancelled() {
+ let output_json_path = temp_path("timeout-output", "json");
+ let output_payload = r#"{"format":{"format_name":"matroska","duration":"1.0","nb_streams":"0"},"streams":[]}"#;
+ write_temp_text(&output_json_path, output_payload);
+
+ let staged = temp_path("timeout", "bin");
+ write_temp_binary(&staged, b"source");
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&staged, &schema_digests).expect("request");
+ let request_for_error_assertion = request.clone();
+ let publication_id = "pub-002b2";
+ let timeout_context = runtime_context_with_lineage(request.clone())
+ .with_publication(publication_id, request.lease.expected_fence_token.clone())
+ .with_watchdog_seconds(1);
+ let cancel_context = runtime_context_with_lineage(request)
+ .with_publication(publication_id, request_for_error_assertion.lease.expected_fence_token.clone())
+ .with_watchdog_seconds(30);
+
+ let timeout_factory_command = format!(
+ "type {} & ping 127.0.0.1 -n 30 >nul",
+ output_json_path.to_string_lossy()
+ );
+ let sleep_factory = CommandProcessFactory {
+ command: "cmd".to_string(),
+ args: vec!["/C".to_string(), timeout_factory_command],
+ };
+ let timeout_error = execute_bound_probe_with_context(
+ timeout_context,
+ &sleep_factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 1,
+ ..Default::default()
+ },
+ |_| false,
+ )
+ .expect_err("timeout expected");
+ assert_runtime_failure_has_structured_error(
+ &timeout_error,
+ ProbeRunFailureCode::WatchdogTimeout,
+ &request_for_error_assertion,
+ None,
+ );
+
+ let cancel_error = execute_bound_probe_with_context(
+ cancel_context,
+ &sleep_factory,
+ &MockNetworkController::default(),
+ &MockJobController::default(),
+ &MockWatchdogController {
+ timeout: 30,
+ ..Default::default()
+ },
+ |elapsed| elapsed > 1,
+ )
+ .expect_err("cancellation expected");
+ assert_runtime_failure_has_structured_error(
+ &cancel_error,
+ ProbeRunFailureCode::ProcessCancellation,
+ &request_for_error_assertion,
+ None,
+ );
+
+ let _ = fs::remove_file(staged);
+ let _ = fs::remove_file(output_json_path);
+ }
+
+ #[test]
+ #[ignore = "live evidence only: generates synthetic fixture and executes real ffprobe under live controllers"]
+ fn execute_live_synthetic_matroska_with_real_containment_controls() {
+ let ffmpeg_path = Path::new(PROBE_EXECUTABLE)
+ .with_file_name("ffmpeg.exe")
+ .to_string_lossy()
+ .to_string();
+
+ assert!(
+ Path::new(&ffmpeg_path).exists(),
+ "ffmpeg executable expected beside registered ffprobe at {ffmpeg_path}"
+ );
+
+ let fixture_dir = temp_path("live", "mkv");
+ fs::create_dir_all(&fixture_dir).expect("fixture staging dir");
+ let source_path = fixture_dir.join("synthetic-real.mkv");
+ let subtitle_path = fixture_dir.join("synthetic.srt");
+
+ fs::write(
+ &subtitle_path,
+ "1\n00:00:00,000 --> 00:00:01,000\nSynthetic subtitle for containment proof\n",
+ )
+ .expect("write synthetic subtitle");
+
+ let encode_status = Command::new(&ffmpeg_path)
+ .args([
+ "-y",
+ "-loglevel",
+ "error",
+ "-f",
+ "lavfi",
+ "-i",
+ "testsrc=size=64x64:rate=24:d=1",
+ "-c:v",
+ "ffv1",
+ "-f",
+ "lavfi",
+ "-i",
+ "sine=frequency=440:duration=1",
+ "-i",
+ subtitle_path.to_string_lossy().as_ref(),
+ "-c:v",
+ "ffv1",
+ "-map",
+ "0:v",
+ "-map",
+ "1:a",
+ "-map",
+ "2:s:0",
+ "-c:a",
+ "pcm_s16le",
+ "-ar",
+ "48000",
+ "-ac",
+ "2",
+ "-c:s",
+ "subrip",
+ "-shortest",
+ source_path.to_string_lossy().as_ref(),
+ ])
+ .status()
+ .expect("ffmpeg synthetic fixture command");
+ assert!(encode_status.success(), "ffmpeg synthetic fixture command failed");
+
+ let schema_digests = fixed_schema_digests();
+ let request = request_schema_identity(&source_path, &schema_digests).expect("request");
+ let artifact_id = runtime_output_artifact_id(&request);
+ let context = ProbeRuntimeContext::new()
+ .with_request(request)
+ .with_schema_digests(schema_digests)
+ .with_watchdog_seconds(CONTROL_LIMITS.wall_clock_seconds)
+ .with_publication("pub-002b2-live", "pub-fence-002b2")
+ .with_output_staging(fixture_dir.to_string_lossy().to_string())
+ .with_output_port(runtime_output_port().to_string())
+ .with_output_artifact_id(artifact_id.clone());
+
+ let result = execute_bound_probe_with_context(
+ context,
+ &DefaultProbeProcessFactory,
+ &DefaultNetworkDenialController,
+ &DefaultJobObjectController,
+ &DefaultWatchdogController,
+ |_| false,
+ )
+ .expect("live real probe run must succeed under live controls");
+
+ assert_eq!(result.normalized_observation.probe_observation.format, "matroska,webm");
+ assert!(
+ result
+ .normalized_observation
+ .probe_observation
+ .format
+ .split(',')
+ .any(|value| value.eq_ignore_ascii_case("webm")),
+ "format_name must be treated as raw metadata and not promoted outside matroska",
+ );
+ assert_eq!(result.normalized_observation.probe_observation.stream_count, 3);
+ assert!(result.normalized_observation.probe_observation.has_subtitles);
+ assert_eq!(result.output_contract.publication_id, "pub-002b2-live");
+ assert_eq!(result.output_contract.artifact_id, artifact_id);
+
+ fs::remove_file(&source_path).ok();
+ fs::remove_file(&subtitle_path).ok();
+ }
+
+ #[test]
+ fn parse_probe_output_observation_requires_expected_fields() {
+ let missing_fields = r#"{"streams":[]}"#;
+ let error = parse_probe_observation(missing_fields).expect_err("missing fields rejected");
+ assert_eq!(error.code, ProbeRunFailureCode::JsonMalformed);
+ }
+
+ #[test]
+ fn verify_registered_ffprobe_metadata_matches_contract_constants() {
+ let output = Command::new(PROBE_EXECUTABLE).arg("-version").output().expect("ffprobe -version should run");
+ assert!(
+ output.status.success(),
+ "ffprobe -version failed; code should be 0 to establish live executable identity"
+ );
+
+ let text = String::from_utf8_lossy(&output.stdout);
+ let mut lines = text.lines();
+ let version_line = lines.next().unwrap_or_default();
+ assert!(
+ version_line.starts_with(PROBE_EXECUTABLE_VERSION),
+ "ffprobe version mismatch in launch profile"
+ );
+ let config_line = lines
+ .find_map(|line| line.strip_prefix("configuration: ").map(|value| format!("configuration: {value}")))
+ .expect("ffprobe -version should expose configuration");
+ assert_eq!(config_line, PROBE_EXECUTABLE_CONFIGURATION);
+ }
+}
+
+fn map_containment_failure(error: ProbeRunFailure) -> ProbeRunFailure {
+ error
+}
+
+fn to_structured_runtime_error(
+ code: &ProbeRunFailureCode,
+ message: &str,
+ request: &ProbePrelaunchRequest,
+ publication_ref: Option<&str>,
+) -> StructuredError {
+ let mapping = runtime_disposition_mapping(code);
+ let mut safe_details = Vec::new();
+ let mut safe_causes = Vec::new();
+
+ safe_details.push(message.to_string());
+ if !request.trace_id.is_empty() {
+ safe_details.push(request.trace_id.clone());
+ }
+ if !request.correlation_id.is_empty() {
+ safe_details.push(request.correlation_id.clone());
+ }
+
+ safe_causes.push(request.lease.lease_id.clone());
+ if !request.control_protocol_version.is_empty() {
+ safe_causes.push(request.control_protocol_version.clone());
+ }
+ if !request.executable_sha256.is_empty() {
+ safe_causes.push(request.executable_sha256.clone());
+ }
+
+ let request_error_instance = if request.error_instance_id.trim().is_empty() {
+ unique_token("runtime")
+ } else {
+ request.error_instance_id.clone()
+ };
+ let error_instance = format!(
+ "{request_error_instance}-{:?}-{}",
+ code,
+ unique_token("eid")
+ );
+ let identity_refs = mapping
+ .identity_refs
+ .iter()
+ .map(|value| (*value).to_string())
+ .collect::>();
+
+ StructuredError {
+ schema_id: STRUCTURED_ERROR_SCHEMA_ID,
+ schema_version: STRUCTURED_ERROR_SCHEMA_VERSION,
+ error_ref: ErrorRef {
+ namespace: ERROR_REF_NAMESPACE,
+ code: format!("{:?}", code),
+ version: ERROR_REF_VERSION,
+ error_instance,
+ },
+ trace_id: request.trace_id.clone(),
+ correlation_id: request.correlation_id.clone(),
+ subject: "probe runtime execution".to_string(),
+ video_disposition: mapping.video_disposition.to_string(),
+ shared_state: mapping.shared_terminal_state.to_string(),
+ error_category: mapping.error_category.to_string(),
+ retry_class: mapping.retry_class.to_string(),
+ operation_stage: mapping.operation_stage.to_string(),
+ safe_recovery: mapping.safe_recovery.to_string(),
+ identity_refs,
+ details: safe_details.into_iter().take(3).collect(),
+ causes: safe_causes.into_iter().take(3).collect(),
+ applies_to: StructuredErrorAppliesTo {
+ job_ref: request.job_id.clone(),
+ attempt_ref: request.attempt_id.clone(),
+ dispatch_ref: request.dispatch_id.clone(),
+ worker_ref: request.worker_instance_id.clone(),
+ publication_ref: publication_ref.map(str::to_string),
+ },
+ }
+}
+
+fn to_structured_runtime_error_with_synthetic_identity(
+ code: &ProbeRunFailureCode,
+ message: &str,
+ error_instance: &str,
+ publication_ref: Option<&str>,
+) -> StructuredError {
+ let mapping = runtime_disposition_mapping(code);
+ let error_instance = if error_instance.trim().is_empty() {
+ unique_token("runtime")
+ } else {
+ format!("{error_instance}-{}", unique_token("eid"))
+ };
+
+ StructuredError {
+ schema_id: STRUCTURED_ERROR_SCHEMA_ID,
+ schema_version: STRUCTURED_ERROR_SCHEMA_VERSION,
+ error_ref: ErrorRef {
+ namespace: ERROR_REF_NAMESPACE,
+ code: format!("{:?}", code),
+ version: ERROR_REF_VERSION,
+ error_instance,
+ },
+ trace_id: "synthetic".to_string(),
+ correlation_id: "synthetic".to_string(),
+ subject: "probe runtime execution".to_string(),
+ video_disposition: mapping.video_disposition.to_string(),
+ shared_state: mapping.shared_terminal_state.to_string(),
+ error_category: mapping.error_category.to_string(),
+ retry_class: mapping.retry_class.to_string(),
+ operation_stage: mapping.operation_stage.to_string(),
+ safe_recovery: mapping.safe_recovery.to_string(),
+ identity_refs: mapping
+ .identity_refs
+ .iter()
+ .map(|value| (*value).to_string())
+ .collect(),
+ details: vec!["request context unavailable".to_string(), message.to_string()],
+ causes: vec!["missing prelaunch request".to_string()],
+ applies_to: StructuredErrorAppliesTo {
+ job_ref: "missing".to_string(),
+ attempt_ref: "missing".to_string(),
+ dispatch_ref: "missing".to_string(),
+ worker_ref: "missing".to_string(),
+ publication_ref: publication_ref.map(str::to_string),
+ },
+ }
+}
+
+fn runtime_disposition_mapping(code: &ProbeRunFailureCode) -> &'static DispositionMapping {
+ let disposition = match code {
+ ProbeRunFailureCode::ExecutableIdentityMismatch
+ | ProbeRunFailureCode::ExecutableVersionMismatch
+ | ProbeRunFailureCode::ExecutableConfigurationMismatch
+ | ProbeRunFailureCode::ExecutableAccessFailed
+ | ProbeRunFailureCode::SourcePrelaunchFailed
+ | ProbeRunFailureCode::SourcePostlaunchMismatch
+ | ProbeRunFailureCode::SourceMutationDetected
+ | ProbeRunFailureCode::SourceIdentityMismatch
+ | ProbeRunFailureCode::SourceLengthMismatch => "VID-INGEST-IDENTITY-MISMATCH",
+
+ ProbeRunFailureCode::NetworkDenialUnavailable
+ | ProbeRunFailureCode::NetworkDenialSetupFailed
+ | ProbeRunFailureCode::NetworkDenialVerifyFailed
+ | ProbeRunFailureCode::JobObjectUnavailable
+ | ProbeRunFailureCode::JobObjectSetupFailed
+ | ProbeRunFailureCode::JobObjectVerifyFailed => "VID-INGEST-PROTECTED",
+
+ ProbeRunFailureCode::WatchdogUnavailable
+ | ProbeRunFailureCode::WatchdogTimeout
+ | ProbeRunFailureCode::OutputStdoutOversized
+ | ProbeRunFailureCode::OutputStderrOversized => "VID-INGEST-LIMIT",
+
+ ProbeRunFailureCode::WatchdogCancelled | ProbeRunFailureCode::ProcessCancellation => {
+ "VID-INGEST-CANCELLED"
+ }
+ ProbeRunFailureCode::PublicationLeaseExpired
+ | ProbeRunFailureCode::PublicationFenceMismatch
+ | ProbeRunFailureCode::OutputLineageInvalid => "VID-INGEST-MALFORMED",
+
+ ProbeRunFailureCode::JsonMalformed
+ | ProbeRunFailureCode::JsonStreamLimitExceeded
+ | ProbeRunFailureCode::OutputSerializationFailed
+ | ProbeRunFailureCode::PrelaunchValidationFailed
+ | ProbeRunFailureCode::ArgumentMismatch => "VID-INGEST-MALFORMED",
+
+ ProbeRunFailureCode::ChildLaunchFailed
+ | ProbeRunFailureCode::ChildCrashed
+ | ProbeRunFailureCode::OutputCaptureFailed => "VID-INGEST-WORKER-FAILED",
+ };
+
+ DISPOSITION_MAP
+ .iter()
+ .find(|entry| entry.video_disposition == disposition)
+ .expect("all runtime dispositions must exist in DISPOSITION_MAP")
+}
+
+fn validate_output_port_and_artifact_lineage(
+ output_port: &str,
+ output_artifact_id: &str,
+ request: &ProbePrelaunchRequest,
+) -> Result<(), ProbeRunFailure> {
+ let markers = [
+ ("job", request.job_id.as_str()),
+ ("attempt", request.attempt_id.as_str()),
+ ("dispatch", request.dispatch_id.as_str()),
+ ("worker", request.worker_instance_id.as_str()),
+ ("port", output_port),
+ ];
+ if output_port.trim().is_empty() {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "output_port is empty".to_string(),
+ structured_error: None,
+ });
+ }
+ if output_artifact_id.trim().is_empty() {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::PrelaunchValidationFailed,
+ message: "output_artifact_id is empty".to_string(),
+ structured_error: None,
+ });
+ }
+ if markers.iter().any(|(_, marker)| marker.is_empty()) {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::SourcePrelaunchFailed,
+ message: "request lineage incomplete for output binding".to_string(),
+ structured_error: None,
+ });
+ }
+ if !markers
+ .iter()
+ .all(|(label, marker)| output_artifact_id.contains(&format!("{label}:{marker}")))
+ {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::OutputLineageInvalid,
+ message: "output artifact id is not bound to output lineage".to_string(),
+ structured_error: None,
+ });
+ }
+ Ok(())
+}
+
+fn map_watchdog_failure(error: ProbeRunFailure) -> ProbeRunFailure {
+ match error.code {
+ ProbeRunFailureCode::WatchdogUnavailable => error,
+ _ => ProbeRunFailure {
+ code: ProbeRunFailureCode::WatchdogUnavailable,
+ message: error.message,
+ structured_error: None,
+ },
+ }
+}
+
+fn map_prelaunch_failure(failure: &ProbePrelaunchFailure) -> ProbeRunFailureCode {
+ match failure {
+ ProbePrelaunchFailure::ArgumentMismatch => ProbeRunFailureCode::ArgumentMismatch,
+ ProbePrelaunchFailure::ComponentMismatch | ProbePrelaunchFailure::OutOfPolicyIdentity => {
+ ProbeRunFailureCode::ExecutableIdentityMismatch
+ }
+ ProbePrelaunchFailure::MutableLease => ProbeRunFailureCode::SourceMutationDetected,
+ ProbePrelaunchFailure::SourceIdentityMismatch => ProbeRunFailureCode::SourceIdentityMismatch,
+ ProbePrelaunchFailure::SourceHashMismatch => ProbeRunFailureCode::SourceIdentityMismatch,
+ ProbePrelaunchFailure::SourceLengthMismatch => ProbeRunFailureCode::SourceLengthMismatch,
+ ProbePrelaunchFailure::SchemaDigestMismatch(_) | ProbePrelaunchFailure::SchemaReferenceMismatch => {
+ ProbeRunFailureCode::PrelaunchValidationFailed
+ }
+ ProbePrelaunchFailure::ExpiredLease => ProbeRunFailureCode::PublicationLeaseExpired,
+ ProbePrelaunchFailure::StaleLeaseOrFence => ProbeRunFailureCode::PublicationFenceMismatch,
+ ProbePrelaunchFailure::PathMissing
+ | ProbePrelaunchFailure::PathInvalid
+ | ProbePrelaunchFailure::ScopeMismatch
+ | ProbePrelaunchFailure::TransportUnsupported
+ | ProbePrelaunchFailure::InvalidLaunchPolicy
+ | ProbePrelaunchFailure::OperationIdentityMismatch
+ | ProbePrelaunchFailure::OperationVersionMismatch
+ | ProbePrelaunchFailure::ImplementationMismatch
+ | ProbePrelaunchFailure::ImplementationProfileMismatch
+ | ProbePrelaunchFailure::WorkerInstanceMismatch
+ | ProbePrelaunchFailure::DispatchMismatch
+ | ProbePrelaunchFailure::ControlProtocolMismatch
+ | ProbePrelaunchFailure::EnvelopeExceeded
+ | ProbePrelaunchFailure::PartialCompletenessRejected
+ | ProbePrelaunchFailure::MissingContainmentEvidence(_)
+ | ProbePrelaunchFailure::UnsupportedCapability(_)
+ | ProbePrelaunchFailure::MissingRequiredCapability(_) => ProbeRunFailureCode::PrelaunchValidationFailed,
+ }
+}
+
+fn verify_registered_executable_identity(executable_path: &str, executable_sha256: &str) -> Result<(), ProbeRunFailure> {
+ if !hex_equal_ignore_case(executable_path, PROBE_EXECUTABLE) {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableIdentityMismatch,
+ message: format!("path mismatch: {executable_path}"),
+ structured_error: None,
+ });
+ }
+
+ if !hex_equal_ignore_case(executable_sha256, REGISTERED_EXECUTABLE_SHA256) {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableIdentityMismatch,
+ message: "sha mismatch at contract input".to_string(),
+ structured_error: None,
+ });
+ }
+
+ let observed = compute_file_sha256_hex(executable_path).map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableAccessFailed,
+ message: format!("cannot read registered executable: {error}"),
+ structured_error: None,
+ })?;
+ if !hex_equal_ignore_case(&observed, REGISTERED_EXECUTABLE_SHA256) {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableIdentityMismatch,
+ message: "executable hash mismatch at runtime".to_string(),
+ structured_error: None,
+ });
+ }
+
+ Ok(())
+}
+
+fn verify_registered_executable_metadata(path: &str) -> Result<(), ProbeRunFailure> {
+ let output = Command::new(path)
+ .arg("-version")
+ .output()
+ .map_err(|error| ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableAccessFailed,
+ message: format!("cannot execute ffprobe -version: {error}"),
+ structured_error: None,
+ })?;
+ if !output.status.success() {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableVersionMismatch,
+ message: "ffprobe -version failed".to_string(),
+ structured_error: None,
+ });
+ }
+
+ let text = String::from_utf8_lossy(&output.stdout);
+ let mut lines = text.lines();
+ let version_line = lines.next().unwrap_or_default();
+ if !version_line.starts_with(PROBE_EXECUTABLE_VERSION) {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableVersionMismatch,
+ message: format!("unexpected ffprobe version line: {version_line}"),
+ structured_error: None,
+ });
+ }
+
+ let config_line = lines
+ .find_map(|line| line.strip_prefix("configuration: ").map(|value| format!("configuration: {value}")))
+ .ok_or_else(|| ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableConfigurationMismatch,
+ message: "missing ffprobe configuration line".to_string(),
+ structured_error: None,
+ })?;
+ if config_line != PROBE_EXECUTABLE_CONFIGURATION {
+ return Err(ProbeRunFailure {
+ code: ProbeRunFailureCode::ExecutableConfigurationMismatch,
+ message: "configuration does not match registered executable".to_string(),
+ structured_error: None,
+ });
+ }
+
+ Ok(())
+}
+
+fn spawn_output_reader(stream: R, byte_limit: u64) -> io::Result>> {
+ let max = byte_limit.try_into().unwrap_or(usize::MAX);
+ Ok(thread::spawn(move || read_with_limit(stream, max)))
+}
+
+fn read_with_limit(mut stream: R, byte_limit: usize) -> io::Result {
+ let mut bytes = Vec::new();
+ let mut truncated = false;
+ let mut total = 0usize;
+ let mut buffer = [0u8; 4096];
+
+ loop {
+ let read = stream.read(&mut buffer)?;
+ if read == 0 {
+ break;
+ }
+ if !truncated {
+ let remain = byte_limit.saturating_sub(bytes.len());
+ let take = read.min(remain);
+ bytes.extend_from_slice(&buffer[..take]);
+ if read > remain {
+ truncated = true;
+ }
+ } else {
+ truncated = true;
+ }
+ total += read;
+ }
+
+ if total > byte_limit {
+ truncated = true;
+ }
+
+ Ok(CapturedOutput { bytes, truncated })
+}
+
+fn hold_source_input_readonly(path: &str) -> io::Result {
+ let mut options = OpenOptions::new();
+ options.read(true);
+ #[cfg(windows)]
+ {
+ options.share_mode(1);
+ }
+ let handle = options.open(path)?;
+ Ok(ProbeInputEvidenceGuard {
+ _handle: Some(handle),
+ })
+}
+
+fn validate_probe_output_completeness(completeness: &str) -> Result<(), String> {
+ crate::probe_adapter_contract::validate_probe_output_completeness(completeness)
+ .map_err(|failure| format!("{failure:?}"))
+}
+
+fn sha256_hex(d: impl AsRef<[u8]>) -> String {
+ let mut hasher = Sha256::new();
+ hasher.update(d.as_ref());
+ digest_to_hex(hasher.finalize())
+}
+
+fn compute_file_sha256_hex(path: &str) -> io::Result {
+ let mut file = std::fs::File::open(path)?;
+ let mut hasher = Sha256::new();
+ let mut buffer = [0u8; 64 * 1024];
+
+ loop {
+ let read = file.read(&mut buffer)?;
+ if read == 0 {
+ break;
+ }
+ hasher.update(&buffer[..read]);
+ }
+
+ Ok(digest_to_hex(hasher.finalize()))
+}
+
+fn digest_to_hex(digest: impl AsRef<[u8]>) -> String {
+ digest.as_ref().iter().map(|value| format!("{value:02x}")).collect()
+}
+
+fn live_fingerprint(path: &str) -> Result<(String, u64), FingerprintFailure> {
+ let result = fingerprint_local_file_with_hook(path, None::).map_err(|error| match error {
+ crate::fingerprint::FingerprintDisposition::PreflightFailure(_) => FingerprintFailure::IdentityMismatch,
+ crate::fingerprint::FingerprintDisposition::IoFailure => FingerprintFailure::AccessFailed,
+ crate::fingerprint::FingerprintDisposition::CandidateMutated => FingerprintFailure::Mutation,
+ crate::fingerprint::FingerprintDisposition::ExceedsSourceCeiling => {
+ FingerprintFailure::LengthMismatch
+ }
+ })?;
+ Ok((result.sha256_hex, result.byte_length))
+}
+
+fn policy_fingerprint() -> String {
+ sha256_hex("VID-IMPL-P00-002B1-POLICY")
+}
+
+fn sanitize_diagnostics(raw: &str) -> String {
+ raw.chars()
+ .filter(|ch| ch.is_ascii_graphic() || ch.is_ascii_whitespace())
+ .take(4096)
+ .collect()
+}
+
+fn parse_u64(value: &Value) -> Option {
+ value.as_u64().or_else(|| value.as_str().and_then(|value| value.parse::().ok()))
+}
+
+fn parse_f64(value: &Value) -> Option {
+ value.as_f64().or_else(|| value.as_str().and_then(|value| value.parse::().ok()))
+}
+
+fn hex_equal_ignore_case(left: &str, right: &str) -> bool {
+ left.eq_ignore_ascii_case(right)
+}
+
+fn unique_token(prefix: &str) -> String {
+ let token = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map(|duration| duration.as_nanos())
+ .unwrap_or_default();
+ format!("{prefix}-{token}")
+}
+
+fn wall_clock_ticks_to_100ns(seconds: u64) -> i64 {
+ seconds.saturating_mul(10_000_000) as i64
+}
+
+#[cfg(windows)]
+fn add_network_rule(name: &str, executable: &str, inbound: bool) -> Result<(), String> {
+ let direction = if inbound { "in" } else { "out" };
+ let status = Command::new("netsh")
+ .args([
+ "advfirewall",
+ "firewall",
+ "add",
+ "rule",
+ &format!("name={name}"),
+ &format!("dir={direction}"),
+ "action=block",
+ &format!("program={executable}"),
+ "enable=yes",
+ "profile=any",
+ ])
+ .status()
+ .map_err(|error| error.to_string())?;
+ if status.success() {
+ Ok(())
+ } else {
+ Err(format!("netsh add rule failed: {status}"))
+ }
+}
+
+#[cfg(windows)]
+fn verify_network_rule(name: &str) -> Result {
+ let output = Command::new("netsh")
+ .args(["advfirewall", "firewall", "show", "rule", &format!("name={name}")])
+ .output()
+ .map_err(|error| error.to_string())?;
+ Ok(output.status.success())
+}
+
+#[cfg(windows)]
+fn remove_network_rule(name: &str) -> Result<(), String> {
+ let _ = Command::new("netsh")
+ .args(["advfirewall", "firewall", "delete", "rule", &format!("name={name}")])
+ .status()
+ .map_err(|error| error.to_string())?;
+ Ok(())
+}
+
+#[derive(Debug)]
+enum FingerprintFailure {
+ IdentityMismatch,
+ LengthMismatch,
+ Mutation,
+ AccessFailed,
+}
diff --git a/workers/VideoLocalization.Worker/tests/preflight_fingerprint.rs b/workers/VideoLocalization.Worker/tests/preflight_fingerprint.rs
new file mode 100644
index 0000000..8795e0a
--- /dev/null
+++ b/workers/VideoLocalization.Worker/tests/preflight_fingerprint.rs
@@ -0,0 +1,193 @@
+use std::cell::Cell;
+use std::fs::{self, OpenOptions};
+use std::path::PathBuf;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use videolocalization_worker::{
+ fingerprint_local_file,
+ fingerprint_local_file_with_hook,
+ is_within_source_ceiling,
+ preflight_local_file,
+ PreflightDisposition,
+ SHA2_CRATE_VERSION,
+};
+
+#[test]
+fn known_empty_fixture_has_expected_sha_and_length() {
+ let path = write_temp_file("vid-impl-002a-empty", b"");
+ let result = fingerprint_local_file(&path).expect("fingerprint");
+
+ assert_eq!(result.byte_length, 0);
+ assert_eq!(result.sha256_hex, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+ assert_eq!(result.algorithm.version, SHA2_CRATE_VERSION);
+
+ fs::remove_file(path).ok();
+}
+
+#[test]
+fn known_content_fixture_has_expected_sha_and_length() {
+ let content = b"VideoLocalization deterministic fixture";
+ let path = write_temp_file("vid-impl-002a-content", content);
+
+ let result = fingerprint_local_file(&path).expect("fingerprint");
+
+ assert_eq!(result.byte_length, content.len() as u64);
+ assert_eq!(result.sha256_hex, "5c50dea06b0c2bedab214a9beb2f126b56e7f3344cfc84d657a07783082e8bd6");
+
+ fs::remove_file(path).ok();
+}
+
+#[test]
+fn hashing_is_chunked_for_multi_chunk_file() {
+ let mut payload = vec![0u8; 200_000];
+ payload.iter_mut().enumerate().for_each(|(index, value)| {
+ *value = (index % 251) as u8;
+ });
+
+ let path = write_temp_file("vid-impl-002a-multi", &payload);
+ let result = fingerprint_local_file(&path).expect("fingerprint");
+
+ assert_eq!(result.byte_length, payload.len() as u64);
+ assert_eq!(result.sha256_hex.len(), 64);
+
+ fs::remove_file(path).ok();
+}
+
+#[test]
+fn missing_directory_traversal_ads_and_network_paths_fail_distinctly() {
+ let missing = preflight_local_file(r"C:\does-not-exist-002a.bin");
+ assert!(matches!(missing, Err(PreflightDisposition::MissingPath)));
+
+ let temp_dir = std::env::temp_dir();
+ let dir_result = preflight_local_file(&temp_dir);
+ assert!(matches!(dir_result, Err(PreflightDisposition::NotAFile)));
+
+ let traversal = preflight_local_file("..\\example\\video.mp4");
+ assert!(matches!(traversal, Err(PreflightDisposition::PathTraversal)));
+
+ let ads = preflight_local_file("C:\\temp\\clip.mov:stream");
+ assert!(matches!(ads, Err(PreflightDisposition::AlternateDataStream)));
+
+ let network = preflight_local_file("\\\\server\\share\\video.mov");
+ assert!(matches!(network, Err(PreflightDisposition::NonLocalPath)));
+
+ let device = preflight_local_file("\\\\.\\pipe\\video");
+ assert!(matches!(device, Err(PreflightDisposition::DevicePath)));
+
+ let extended_unc = preflight_local_file(r"\\?\UNC\server\share\video.mov");
+ assert!(matches!(extended_unc, Err(PreflightDisposition::NonLocalPath)));
+
+ let extended_device = preflight_local_file(r"\\?\.\pipe\video");
+ assert!(matches!(extended_device, Err(PreflightDisposition::DevicePath)));
+
+ let extended_global_root = preflight_local_file(r"\\?\GLOBALROOT\Device\HarddiskVolume1\foo");
+ assert!(matches!(extended_global_root, Err(PreflightDisposition::DevicePath)));
+
+ let unknown_verbatim_namespace = preflight_local_file(r"\\?\MYSTERYNS\video.mov");
+ assert!(matches!(unknown_verbatim_namespace, Err(PreflightDisposition::InvalidPath)));
+
+ let empty = preflight_local_file(" ");
+ assert!(matches!(empty, Err(PreflightDisposition::InvalidPath)));
+
+ let valid_extended_local = preflight_local_file(r"\\?\C:\does-not-exist-002a.bin");
+ assert!(matches!(valid_extended_local, Err(PreflightDisposition::MissingPath)));
+}
+
+#[test]
+fn source_ceiling_check_is_unit_testable_without_full_file() {
+ let two_tib = 2 * 1024u64 * 1024 * 1024 * 1024;
+
+ assert!(is_within_source_ceiling(two_tib));
+ assert!(!is_within_source_ceiling(two_tib + 1));
+}
+
+#[test]
+fn mutation_detected_during_hashing_is_rejected() {
+ let payload = vec![b'A'; 300_000];
+ let path = write_temp_file("vid-impl-002a-mutate", &payload);
+
+ let attempted_mutation = Cell::new(false);
+ let write_failed = Cell::new(false);
+
+ let result = fingerprint_local_file_with_hook(
+ &path,
+ Some(|bytes| {
+ if bytes > 64_000 && !attempted_mutation.get() {
+ attempted_mutation.set(true);
+ let open_result = OpenOptions::new().append(true).open(&path);
+ write_failed.set(open_result.is_err());
+ }
+ }),
+ );
+
+ #[cfg(windows)]
+ {
+ assert!(result.is_ok(), "hashing should complete with sharing denied for write attempts");
+ assert!(write_failed.get(), "write must fail while read handle is active");
+ }
+
+ #[cfg(not(windows))]
+ {
+ assert!(
+ matches!(result, Err(FingerprintDisposition::CandidateMutated)),
+ "mutation should be rejected"
+ );
+ }
+
+ fs::remove_file(&path).ok();
+}
+
+#[test]
+fn equal_bytes_generate_same_identity_without_collapsing_path() {
+ let content = b"Phase 00 canonicality check";
+ let path_one = write_temp_file("vid-impl-002a-eq-a", content);
+ let path_two = write_temp_file("vid-impl-002a-eq-b", content);
+
+ let left = fingerprint_local_file(&path_one).expect("left fingerprint");
+ let right = fingerprint_local_file(&path_two).expect("right fingerprint");
+
+ assert_eq!(left.sha256_hex, right.sha256_hex);
+ assert_ne!(left.canonical_path, right.canonical_path);
+
+ fs::remove_file(path_one).ok();
+ fs::remove_file(path_two).ok();
+}
+
+#[test]
+fn equal_content_can_bind_distinct_caller_asset_ids_without_hash_semantics_change() {
+ let content = b"Caller-owned identity is not path-derived";
+ let path_one = write_temp_file("vid-impl-002a-asset-a", content);
+ let path_two = write_temp_file("vid-impl-002a-asset-b", content);
+
+ let left = fingerprint_local_file(&path_one).expect("left fingerprint");
+ let right = fingerprint_local_file(&path_two).expect("right fingerprint");
+
+ let caller_asset_id_left = "VIDEO-ASSET-LHS-001";
+ let caller_asset_id_right = "VIDEO-ASSET-RHS-002";
+
+ let left_binding = (caller_asset_id_left, left.sha256_hex.clone(), left.canonical_path);
+ let right_binding = (caller_asset_id_right, right.sha256_hex.clone(), right.canonical_path);
+
+ assert_eq!(left_binding.1, right_binding.1);
+ assert_eq!(left_binding.2.len(), right_binding.2.len());
+ assert_ne!(left_binding.0, right_binding.0);
+
+ fs::remove_file(path_one).ok();
+ fs::remove_file(path_two).ok();
+}
+
+fn temp_file_path(prefix: &str) -> PathBuf {
+ let mut path = std::env::temp_dir();
+ let nanos = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ path.push(format!("{}-{}.bin", prefix, nanos));
+ path
+}
+
+fn write_temp_file(prefix: &str, content: &[u8]) -> PathBuf {
+ let path = temp_file_path(prefix);
+ fs::write(&path, content).expect("temp file write");
+ path
+}
diff --git a/workers/VideoLocalization.Worker/tests/probe_adapter_contract.rs b/workers/VideoLocalization.Worker/tests/probe_adapter_contract.rs
new file mode 100644
index 0000000..b0edfc9
--- /dev/null
+++ b/workers/VideoLocalization.Worker/tests/probe_adapter_contract.rs
@@ -0,0 +1,733 @@
+#![allow(dead_code)]
+
+use std::collections::HashMap;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use serde::Deserialize;
+use sha2::{Digest, Sha256};
+
+use videolocalization_worker::{
+ to_structured_error,
+ validate_probe_output_completeness,
+ validate_probe_prelaunch,
+ CONTROL_ENVELOPE_BYTES,
+ CONTROL_PROTOCOL_VERSION,
+ DISPOSITION_MAP,
+ ERROR_REF_NAMESPACE,
+ ERROR_REF_VERSION,
+ IMPLEMENTATION_ID,
+ IMPLEMENTATION_PROFILE_ID,
+ OPERATION_ID,
+ OPERATION_VERSION,
+ ProbeContainmentEvidence,
+ ProbeLeaseManifest,
+ ProbeLaunchPolicy,
+ ProbePrelaunchFailure,
+ ProbePrelaunchRequest,
+ STRUCTURED_ERROR_SCHEMA_ID,
+ STRUCTURED_ERROR_SCHEMA_VERSION,
+};
+
+#[derive(Deserialize)]
+struct ContractSchemaRecord {
+ schema_id: String,
+ schema_version: String,
+ path: String,
+ sha256: String,
+}
+
+#[derive(Deserialize)]
+struct ContractFixtureRecord {
+ role: String,
+ path: String,
+ sha256: String,
+}
+
+#[derive(Deserialize)]
+struct ContractSchemaManifest {
+ artifact_id: String,
+ artifact_version: String,
+ operation_id: String,
+ operation_version: String,
+ implementation_profile_id: String,
+ implementation_id: String,
+ #[serde(default)]
+ fixture_records: Vec,
+ schema_records: Vec,
+}
+
+#[derive(Deserialize)]
+struct OperationDescriptorFixture {
+ operation_id: String,
+ operation_version: String,
+ implementation_id: String,
+ implementation_profile_id: String,
+ input_schema_id: String,
+ normalized_observation_schema_id: String,
+ parameter_schema_id: String,
+ structured_error_schema_id: String,
+ resource_schema_id: String,
+ policy_schema_id: String,
+ output_contract_schema_id: String,
+ supported_capabilities: Vec,
+ unsupported_capabilities: Vec,
+ transport: String,
+}
+
+#[derive(Deserialize)]
+struct LimitsFixture {
+ control_envelope_bytes: u64,
+ wall_time_seconds: u64,
+ child_cpu_seconds: u64,
+ resident_memory_bytes: u64,
+ stream_count: u32,
+}
+
+#[derive(Deserialize)]
+struct WorkerHelloFixture {
+ operation_id: String,
+ operation_version: String,
+ implementation_id: String,
+ implementation_profile_id: String,
+ executable_path: String,
+ executable_sha256: String,
+ component_version: String,
+ transport: String,
+ platform: String,
+ architecture: String,
+ control_protocol_min: String,
+ control_protocol_max: String,
+ limits: LimitsFixture,
+ supported: Vec,
+ unsupported: Vec,
+}
+
+#[derive(Deserialize)]
+struct DispositionFixture {
+ video_disposition: String,
+ shared_terminal_state: String,
+ error_category: String,
+ retry_class: String,
+ operation_stage: String,
+ safe_recovery: String,
+ identity_refs: Vec,
+}
+
+#[derive(Deserialize)]
+struct PublicationFixture {
+ artifact_id: String,
+ artifact_version: String,
+ artifact_type: String,
+ output_schema_id: String,
+ completeness: String,
+ integrity: IntegrityFixture,
+ size_bytes: u64,
+ component_inventory: Vec,
+ producing: ProducingFixture,
+ input_digests: PublicationInputDigestFixture,
+ parameter_digests: PublicationParameterDigestFixture,
+ resource_digests: PublicationResourceDigestFixture,
+ policy_digests: PublicationPolicyDigestFixture,
+ cache_digests: PublicationCacheDigestFixture,
+ output_port: String,
+ staging: PublicationStagingFixture,
+ lineage: PublicationLineageFixture,
+ publication_id: String,
+ fencing: PublicationFencingFixture,
+}
+
+#[derive(Deserialize)]
+struct IntegrityFixture {
+ algorithm: String,
+ value: String,
+}
+
+#[derive(Deserialize)]
+struct ProducingFixture {
+ job_ref: String,
+ attempt_ref: String,
+ dispatch_ref: String,
+ worker_ref: String,
+ operation_id: String,
+ operation_version: String,
+ implementation_id: String,
+ implementation_profile_id: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationInputDigestFixture {
+ staged_copy_hash: String,
+ manifest_digest: String,
+ logical_source_ref: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationParameterDigestFixture {
+ argv: String,
+ limits: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationResourceDigestFixture {
+ resource: String,
+ schema: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationPolicyDigestFixture {
+ policy: String,
+ schema: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationCacheDigestFixture {
+ schema_bundle: String,
+ control_protocol: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationStagingFixture {
+ state: String,
+ path: String,
+ delete_on_stale: bool,
+ quarantine_on_failure: bool,
+}
+
+#[derive(Deserialize)]
+struct PublicationLineageFixture {
+ job_ref: String,
+ attempt_ref: String,
+ dispatch_ref: String,
+ worker_ref: String,
+ publication_ref: String,
+}
+
+#[derive(Deserialize)]
+struct PublicationFencingFixture {
+ required: bool,
+ active_token: String,
+}
+
+#[derive(Deserialize)]
+struct LaunchPolicyFixture {
+ use_shell: bool,
+ use_path_lookup: bool,
+ user_supplied_options: bool,
+}
+
+#[derive(Deserialize)]
+struct EvidenceFixture {
+ network_denied: bool,
+ watchdog_enabled: bool,
+ job_object_enabled: bool,
+}
+
+#[derive(Deserialize)]
+struct LeaseFixture {
+ lease_id: String,
+ lease_epoch: u64,
+ minimum_acceptable_lease_epoch: u64,
+ immutable: bool,
+ source_sha256: String,
+ source_length: u64,
+ staged_copy_identity: String,
+ staged_copy_sha256: String,
+ staged_copy_length: u64,
+ observed_staged_copy_identity: String,
+ observed_staged_copy_sha256: String,
+ observed_staged_copy_length: u64,
+ access_scope: String,
+ expires_at: String,
+ manifest_digest: String,
+ logical_source_ref: String,
+ observed_fence_token: String,
+ expected_fence_token: String,
+}
+
+#[derive(Deserialize)]
+struct PrelaunchRequestFixture {
+ id: String,
+ operation_id: String,
+ operation_version: String,
+ implementation_id: String,
+ implementation_profile_id: String,
+ executable_path: String,
+ executable_sha256: String,
+ staged_input_path: String,
+ launch_argv: Vec,
+ launch_policy: LaunchPolicyFixture,
+ capabilities: Vec,
+ transport: String,
+ output_envelope_bytes: u64,
+ evidence: EvidenceFixture,
+ lease: LeaseFixture,
+ worker_instance_id: String,
+ dispatch_id: String,
+ control_protocol_version: String,
+ job_id: String,
+ attempt_id: String,
+ trace_id: String,
+ correlation_id: String,
+ #[serde(default)]
+ error_instance_id: Option,
+ schema_digest_overrides: Option>,
+}
+
+#[derive(Deserialize)]
+struct PositiveFixtureBundle {
+ id: String,
+ request: Option,
+}
+
+#[derive(Deserialize)]
+struct NegativeFixtureBundle {
+ id: String,
+ expected_failure: Option,
+ request: Option,
+}
+
+#[derive(Deserialize)]
+struct PrelaunchFixtureBundle {
+ positive_fixtures: Vec,
+ negative_fixtures: Vec,
+}
+
+#[test]
+fn contract_manifest_hashes_match_files() {
+ let workspace_root = workspace_root();
+ let manifest_path = workspace_root.join("contracts/VID-IMPL-P00-002B1/schema-bundle.json");
+ let manifest: ContractSchemaManifest =
+ serde_json::from_str(&read_text(&manifest_path)).expect("manifest JSON parse");
+
+ assert_eq!(manifest.artifact_id, "VID-IMPL-P00-002B1");
+ assert_eq!(manifest.artifact_version, "1.0.0-p00");
+ assert_eq!(manifest.operation_id, OPERATION_ID);
+ assert_eq!(manifest.operation_version, OPERATION_VERSION);
+ assert_eq!(manifest.implementation_profile_id, IMPLEMENTATION_PROFILE_ID);
+ assert_eq!(manifest.implementation_id, IMPLEMENTATION_ID);
+ assert!(!manifest.schema_records.is_empty());
+
+ for schema in manifest.schema_records {
+ let schema_path = workspace_root.join(schema.path);
+ let observed = sha256_hex_of_file(&schema_path);
+ assert_eq!(
+ observed.to_ascii_lowercase(),
+ schema.sha256.to_ascii_lowercase(),
+ "schema hash mismatch for {}",
+ schema.schema_id
+ );
+ }
+
+ assert_eq!(manifest.fixture_records.len(), 4);
+ for fixture in manifest.fixture_records {
+ let fixture_path = workspace_root.join(fixture.path);
+ let observed = sha256_hex_of_file(&fixture_path);
+ assert_eq!(
+ observed.to_ascii_lowercase(),
+ fixture.sha256.to_ascii_lowercase(),
+ "fixture hash mismatch for {}",
+ fixture.role
+ );
+ }
+}
+
+#[test]
+fn operation_descriptor_and_worker_hello_are_bound_and_exact() {
+ let workspace_root = workspace_root();
+ let manifest_path = workspace_root.join("contracts/VID-IMPL-P00-002B1/schema-bundle.json");
+ let manifest: ContractSchemaManifest =
+ serde_json::from_str(&read_text(&manifest_path)).expect("manifest JSON parse");
+ let operation: OperationDescriptorFixture =
+ serde_json::from_str(&read_text(&workspace_root.join(
+ "contracts/VID-IMPL-P00-002B1/fixtures/operation-descriptor.json",
+ )))
+ .expect("operation descriptor fixture parse");
+ let hello: WorkerHelloFixture = serde_json::from_str(&read_text(&workspace_root.join(
+ "contracts/VID-IMPL-P00-002B1/fixtures/worker-hello.json",
+ )))
+ .expect("worker hello fixture parse");
+
+ let schema_ids: Vec = manifest
+ .schema_records
+ .iter()
+ .map(|record| record.schema_id.clone())
+ .collect();
+ assert!(schema_ids.contains(&operation.input_schema_id));
+ assert!(schema_ids.contains(&operation.normalized_observation_schema_id));
+ assert!(schema_ids.contains(&operation.parameter_schema_id));
+ assert!(schema_ids.contains(&operation.structured_error_schema_id));
+ assert!(schema_ids.contains(&operation.resource_schema_id));
+ assert!(schema_ids.contains(&operation.policy_schema_id));
+ assert!(schema_ids.contains(&operation.output_contract_schema_id));
+
+ assert_eq!(operation.operation_id, OPERATION_ID);
+ assert_eq!(operation.operation_version, OPERATION_VERSION);
+ assert_eq!(operation.implementation_id, IMPLEMENTATION_ID);
+ assert_eq!(operation.implementation_profile_id, IMPLEMENTATION_PROFILE_ID);
+ assert_eq!(operation.transport, "local-staged-file");
+
+ assert_eq!(hello.operation_id, OPERATION_ID);
+ assert_eq!(hello.operation_version, OPERATION_VERSION);
+ assert_eq!(hello.implementation_id, IMPLEMENTATION_ID);
+ assert_eq!(hello.implementation_profile_id, IMPLEMENTATION_PROFILE_ID);
+ assert_eq!(hello.platform, "Windows");
+ assert_eq!(hello.architecture, "x64");
+ assert_eq!(hello.transport, "local-staged-file");
+ assert_eq!(hello.supported, vec!["cancellation", "deadline"]);
+ assert_eq!(
+ hello.unsupported,
+ vec![
+ "progress",
+ "checkpoint",
+ "pause-resume",
+ "partial-success",
+ "degraded-output",
+ "acceleration",
+ "auto-fallback",
+ "network"
+ ]
+ );
+ assert_eq!(hello.limits.control_envelope_bytes, CONTROL_ENVELOPE_BYTES);
+ assert!(hello.limits.stream_count > 0);
+ assert_eq!(hello.control_protocol_min, CONTROL_PROTOCOL_VERSION);
+ assert_eq!(hello.control_protocol_max, CONTROL_PROTOCOL_VERSION);
+ assert!(hello.control_protocol_min <= hello.control_protocol_max);
+}
+
+#[test]
+fn disposition_matrix_covers_all_video_outcomes() {
+ let workspace_root = workspace_root();
+ let mappings: Vec = serde_json::from_str(&read_text(
+ &workspace_root.join("contracts/VID-IMPL-P00-002B1/fixtures/disposition-mapping.json"),
+ ))
+ .expect("disposition mapping parse");
+
+ let required = [
+ "VID-INGEST-UNSUPPORTED",
+ "VID-INGEST-MALFORMED",
+ "VID-INGEST-LIMIT",
+ "VID-INGEST-PROTECTED",
+ "VID-INGEST-QUARANTINED",
+ "VID-INGEST-CANCELLED",
+ "VID-INGEST-WORKER-FAILED",
+ "VID-INGEST-IDENTITY-MISMATCH",
+ "VID-INGEST-REVIEW",
+ ];
+
+ let mapped: Vec = mappings.iter().map(|row| row.video_disposition.clone()).collect();
+ for expected in required {
+ assert!(mapped.contains(&expected.to_string()), "missing disposition {}", expected);
+ }
+
+ assert_eq!(mappings.len(), DISPOSITION_MAP.len());
+
+ for map in &mappings {
+ let fixture_state = DISPOSITION_MAP
+ .iter()
+ .find(|entry| entry.video_disposition == map.video_disposition)
+ .expect("fixture disposition in code map");
+
+ assert_eq!(fixture_state.shared_terminal_state, map.shared_terminal_state);
+ assert!(!map.error_category.is_empty());
+ assert!(!map.retry_class.is_empty());
+ assert!(!map.operation_stage.is_empty());
+ assert!(!map.safe_recovery.is_empty());
+ assert!(!map.identity_refs.is_empty());
+ }
+}
+
+#[test]
+fn publication_fixture_models_quarantine_and_fence_behavior() {
+ let workspace_root = workspace_root();
+ let publication: PublicationFixture = serde_json::from_str(&read_text(
+ &workspace_root.join("contracts/VID-IMPL-P00-002B1/fixtures/publication-fixture.json"),
+ ))
+ .expect("publication fixture parse");
+
+ assert_eq!(publication.artifact_id, "artifact-vid-impl-002b1-probe-observation-v1");
+ assert_eq!(publication.artifact_version, "1.0.0-p00");
+ assert_eq!(publication.artifact_type, "probe_observation");
+ assert_eq!(publication.completeness, "complete");
+ assert_eq!(publication.integrity.algorithm, "sha-256");
+ assert_eq!(publication.size_bytes, 2048);
+ assert_eq!(publication.output_port, "probe-observation-output-port");
+ assert_eq!(publication.staging.state, "staged-private");
+ assert!(publication.staging.delete_on_stale);
+ assert!(publication.staging.quarantine_on_failure);
+ assert_eq!(publication.lineage.publication_ref, publication.publication_id);
+ assert!(publication.fencing.required);
+ assert!(!publication.fencing.active_token.is_empty());
+ assert!(!publication.producing.operation_id.is_empty());
+ assert!(!publication.producing.operation_version.is_empty());
+ assert!(!publication.producing.implementation_id.is_empty());
+ assert!(!publication.producing.implementation_profile_id.is_empty());
+ assert!(!publication.staging.path.is_empty());
+ assert!(!publication.integrity.value.is_empty());
+ assert_eq!(publication.output_schema_id, "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION");
+}
+
+#[test]
+fn prelaunch_positive_and_negative_fixtures_are_validated() {
+ let workspace_root = workspace_root();
+ let schema_manifest = read_contract_manifest(&workspace_root);
+ let expected_digests = schema_digests(&schema_manifest);
+ let prelaunch_fixtures_path =
+ workspace_root.join("testing/implementation/VID-IMPL-P00-002B1/prelaunch-fixtures.json");
+ let fixtures: PrelaunchFixtureBundle = serde_json::from_str(&read_text(&prelaunch_fixtures_path))
+ .expect("prelaunch fixture parse");
+
+ for fixture in fixtures.positive_fixtures {
+ if let Some(request) = fixture.request {
+ let request = fixture_to_request(request, &expected_digests);
+ assert!(
+ validate_probe_prelaunch(&request, &expected_digests).is_ok(),
+ "positive fixture failed"
+ );
+ }
+ }
+
+ for fixture in fixtures.negative_fixtures {
+ if let (Some(expected_failure), Some(request)) = (fixture.expected_failure, fixture.request) {
+ let request = fixture_to_request(request, &expected_digests);
+ let failure = validate_probe_prelaunch(&request, &expected_digests).err();
+ assert!(failure.is_some(), "negative fixture should fail");
+ let failure_code = failure.expect("failure").code().to_string();
+ assert_eq!(failure_code, expected_failure);
+ }
+ }
+}
+
+#[test]
+fn prelaunch_failures_map_to_complete_structured_error() {
+ let workspace_root = workspace_root();
+ let schema_manifest = read_contract_manifest(&workspace_root);
+ let expected_digests = schema_digests(&schema_manifest);
+ let prelaunch_fixtures_path =
+ workspace_root.join("testing/implementation/VID-IMPL-P00-002B1/prelaunch-fixtures.json");
+ let fixtures: PrelaunchFixtureBundle = serde_json::from_str(&read_text(&prelaunch_fixtures_path))
+ .expect("prelaunch fixture parse");
+
+ for fixture in fixtures.negative_fixtures {
+ if let (Some(expected_failure), Some(request)) = (fixture.expected_failure, fixture.request) {
+ let request = fixture_to_request(request, &expected_digests);
+ let failure = match validate_probe_prelaunch(&request, &expected_digests) {
+ Ok(_) => panic!(
+ "negative fixture {} was unexpectedly accepted",
+ fixture.id
+ ),
+ Err(failure) => failure,
+ };
+ assert_eq!(failure.code(), expected_failure);
+
+ let structured = to_structured_error(&failure, &request);
+ let observed_disposition = DISPOSITION_MAP
+ .iter()
+ .find(|mapping| mapping.video_disposition == structured.video_disposition)
+ .expect("fixture disposition exists");
+
+ assert_eq!(structured.schema_id, STRUCTURED_ERROR_SCHEMA_ID);
+ assert_eq!(structured.schema_version, STRUCTURED_ERROR_SCHEMA_VERSION);
+ assert_eq!(structured.error_ref.namespace, ERROR_REF_NAMESPACE);
+ assert_eq!(structured.error_ref.version, ERROR_REF_VERSION);
+ assert_eq!(structured.error_ref.code, expected_failure);
+ assert_eq!(structured.error_ref.error_instance, request.error_instance_id);
+ assert_eq!(structured.trace_id, request.trace_id);
+ assert_eq!(structured.applies_to.job_ref, request.job_id);
+ assert_eq!(structured.applies_to.attempt_ref, request.attempt_id);
+ assert_eq!(structured.applies_to.dispatch_ref, request.dispatch_id);
+ assert_eq!(structured.applies_to.worker_ref, request.worker_instance_id);
+ assert_eq!(structured.applies_to.publication_ref, None);
+ assert_eq!(structured.video_disposition, observed_disposition.video_disposition);
+ assert_eq!(structured.error_category, observed_disposition.error_category);
+ assert!(!structured.shared_state.is_empty());
+ assert!(!structured.error_category.is_empty());
+ assert!(!structured.retry_class.is_empty());
+ assert!(!structured.operation_stage.is_empty());
+ assert!(!structured.safe_recovery.is_empty());
+ assert!(!structured.identity_refs.is_empty());
+ assert!(structured.identity_refs.len() <= 5);
+ assert!(structured.details.len() <= 3);
+ assert!(structured.causes.len() <= 3);
+ }
+ }
+}
+
+#[test]
+fn publication_completeness_rejects_partial_values() {
+ let _workspace_root = workspace_root();
+ assert!(validate_probe_output_completeness("complete").is_ok());
+
+ let fixture = PublicationFixture {
+ artifact_id: "artifact-vid-impl-002b1-probe-observation-v1".to_string(),
+ artifact_version: "1.0.0-p00".to_string(),
+ artifact_type: "probe_observation".to_string(),
+ output_schema_id: "VID-IMPL-P00-002B1-NORMALIZED-OBSERVATION".to_string(),
+ completeness: "partial".to_string(),
+ integrity: IntegrityFixture {
+ algorithm: "sha-256".to_string(),
+ value: "00".repeat(32),
+ },
+ size_bytes: 2048,
+ component_inventory: vec!["probe-adapter".to_string()],
+ producing: ProducingFixture {
+ job_ref: "job-id".to_string(),
+ attempt_ref: "attempt-id".to_string(),
+ dispatch_ref: "dispatch-id".to_string(),
+ worker_ref: "worker-id".to_string(),
+ operation_id: OPERATION_ID.to_string(),
+ operation_version: OPERATION_VERSION.to_string(),
+ implementation_id: IMPLEMENTATION_ID.to_string(),
+ implementation_profile_id: IMPLEMENTATION_PROFILE_ID.to_string(),
+ },
+ input_digests: PublicationInputDigestFixture {
+ staged_copy_hash: "00".repeat(32),
+ manifest_digest: "00".repeat(32),
+ logical_source_ref: "src:fixture".to_string(),
+ },
+ parameter_digests: PublicationParameterDigestFixture {
+ argv: "00".repeat(32),
+ limits: "00".repeat(32),
+ },
+ resource_digests: PublicationResourceDigestFixture {
+ resource: "00".repeat(32),
+ schema: "00".repeat(32),
+ },
+ policy_digests: PublicationPolicyDigestFixture {
+ policy: "00".repeat(32),
+ schema: "00".repeat(32),
+ },
+ cache_digests: PublicationCacheDigestFixture {
+ schema_bundle: "00".repeat(32),
+ control_protocol: "00".repeat(32),
+ },
+ output_port: "probe-observation-output-port".to_string(),
+ staging: PublicationStagingFixture {
+ state: "staged-private".to_string(),
+ path: "C:/temp/staging".to_string(),
+ delete_on_stale: true,
+ quarantine_on_failure: true,
+ },
+ lineage: PublicationLineageFixture {
+ job_ref: "job-id".to_string(),
+ attempt_ref: "attempt-id".to_string(),
+ dispatch_ref: "dispatch-id".to_string(),
+ worker_ref: "worker-id".to_string(),
+ publication_ref: "publication-id".to_string(),
+ },
+ publication_id: "publication-id".to_string(),
+ fencing: PublicationFencingFixture {
+ required: true,
+ active_token: "token".to_string(),
+ },
+ };
+
+ assert!(
+ validate_probe_output_completeness(&fixture.completeness).is_err(),
+ "partial completeness should be rejected"
+ );
+ assert_eq!(
+ validate_probe_output_completeness("partial").err(),
+ Some(ProbePrelaunchFailure::PartialCompletenessRejected)
+ );
+}
+
+fn fixture_to_request(
+ request: PrelaunchRequestFixture,
+ expected_digests: &HashMap,
+) -> ProbePrelaunchRequest {
+ let mut digests = expected_digests.clone();
+ if let Some(overrides) = request.schema_digest_overrides {
+ for (key, value) in overrides {
+ digests.insert(key, value);
+ }
+ }
+
+ ProbePrelaunchRequest {
+ operation_id: request.operation_id,
+ operation_version: request.operation_version,
+ implementation_id: request.implementation_id,
+ implementation_profile_id: request.implementation_profile_id,
+ executable_path: request.executable_path,
+ executable_sha256: request.executable_sha256,
+ staged_input_path: request.staged_input_path.clone(),
+ launch_argv: request.launch_argv,
+ launch_policy: ProbeLaunchPolicy {
+ use_shell: request.launch_policy.use_shell,
+ use_path_lookup: request.launch_policy.use_path_lookup,
+ user_supplied_options: request.launch_policy.user_supplied_options,
+ },
+ schema_digests: digests,
+ capabilities: request.capabilities,
+ transport: request.transport,
+ evidence: ProbeContainmentEvidence {
+ network_denied: request.evidence.network_denied,
+ watchdog_enabled: request.evidence.watchdog_enabled,
+ job_object_enabled: request.evidence.job_object_enabled,
+ },
+ output_envelope_bytes: request.output_envelope_bytes,
+ worker_instance_id: request.worker_instance_id,
+ dispatch_id: request.dispatch_id,
+ control_protocol_version: request.control_protocol_version,
+ job_id: request.job_id,
+ attempt_id: request.attempt_id,
+ trace_id: request.trace_id,
+ correlation_id: request.correlation_id,
+ error_instance_id: request
+ .error_instance_id
+ .unwrap_or_else(|| format!("err-inst-{}", request.id)),
+ lease: ProbeLeaseManifest {
+ lease_id: request.lease.lease_id,
+ lease_epoch: request.lease.lease_epoch,
+ minimum_acceptable_lease_epoch: request.lease.minimum_acceptable_lease_epoch,
+ immutable: request.lease.immutable,
+ source_sha256: request.lease.source_sha256,
+ source_length: request.lease.source_length,
+ staged_copy_identity: request.lease.staged_copy_identity,
+ staged_copy_sha256: request.lease.staged_copy_sha256,
+ staged_copy_length: request.lease.staged_copy_length,
+ observed_staged_copy_identity: request.lease.observed_staged_copy_identity,
+ observed_staged_copy_sha256: request.lease.observed_staged_copy_sha256,
+ observed_staged_copy_length: request.lease.observed_staged_copy_length,
+ access_scope: request.lease.access_scope,
+ expires_at: request.lease.expires_at,
+ manifest_digest: request.lease.manifest_digest,
+ logical_source_ref: request.lease.logical_source_ref,
+ observed_fence_token: request.lease.observed_fence_token,
+ expected_fence_token: request.lease.expected_fence_token,
+ },
+ }
+}
+
+fn read_contract_manifest(root: &Path) -> ContractSchemaManifest {
+ let manifest_path = root.join("contracts/VID-IMPL-P00-002B1/schema-bundle.json");
+ serde_json::from_str(&read_text(&manifest_path)).expect("manifest parse")
+}
+
+fn schema_digests(manifest: &ContractSchemaManifest) -> HashMap {
+ let mut digests = HashMap::new();
+ for record in &manifest.schema_records {
+ digests.insert(record.schema_id.clone(), record.sha256.clone());
+ }
+ digests
+}
+
+fn workspace_root() -> PathBuf {
+ let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
+ manifest_dir.join("..").join("..")
+}
+
+fn read_text(path: &Path) -> String {
+ fs::read_to_string(path).unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()))
+}
+
+fn sha256_hex_of_file(path: &Path) -> String {
+ let mut hasher = Sha256::new();
+ let bytes = fs::read(path).expect("schema file should exist");
+ hasher.update(&bytes);
+ hasher
+ .finalize()
+ .iter()
+ .map(|value| format!("{:02x}", value))
+ .collect::()
+}