Skip to main content

u280_evidence_oracle/
lib.rs

1//! Independent cryptographic promotion gate for sealed U280 Phase-B evidence.
2//!
3//! This crate consumes, but never modifies, the archive emitted by the C++ XRT
4//! transport runner. It recomputes every SHA-256-profile WOTS signature with
5//! the software reference implementation pinned by this workspace, verifies
6//! the complete transport envelope, and writes a separately sealed result.
7//! It also verifies and binds the producer's canonical coordination chain and
8//! native-XRT card identity chain. Coordination is a structurally authenticated
9//! procedural ledger assertion, not cryptographic ledger proof or global
10//! nonrollback evidence. A successful result is still not a hardware-completion
11//! claim: route, measured performance, ownership, replay uniqueness, and final
12//! promotion belong to a later combiner.
13//! A distinct entry point in this package consumes only the deliberately
14//! non-hardware mock-HBM source-simulation format and cannot accept this
15//! transport archive in its place.
16#![forbid(unsafe_code)]
17
18mod archive;
19mod model;
20mod sealed_result;
21mod source;
22
23pub use sealed_result::{
24    VerifiedClockObservation, VerifiedHardwareCardIdentity, VerifiedHardwareXclbinIdentity,
25    VerifiedOracleReceiptCapture, VerifiedPerformanceObservations, VerifiedPerformanceSample,
26    VerifiedRequestArtifactBinding, VerifiedResultArtifactBinding, VerifiedResultRequest,
27    VerifiedRoutedClockEvidenceBinding, VerifiedSealedResult, VerifiedXclbinBinding,
28    VerifiedXrtBuildIdentity, completed_result_receipt_line, open_verified_oracle_receipt_capture,
29    verify_typed_sealed_result,
30};
31pub use source::{SourceOracleConfig, SourceOracleFailure, SourceOracleOutcome, execute_source};
32
33use anyhow::{Context, Result, ensure};
34use archive::{
35    ArchiveSnapshot, CheckedFile, FileRecord, ResultDirectory, canonical_transport_root,
36    create_exclusive_file, finish_writer, hex_lower, is_lower_hex, open_verified_file,
37    prepare_result_directory, read_verified_small, seal_result_directory, sha256_bytes,
38    verify_sealed_manifest, verify_transport_archive, verify_transport_archive_again,
39    write_exclusive,
40};
41use hashsigs_reference::{
42    FUSED_OUTPUT_BYTES, HashSigsSha256GenericV1, MessageDigest, PrivateSeed, SIGNATURE_BYTES,
43    Sha256GenericWots,
44};
45use model::{
46    ArtifactBinding, BatchResult, CaptureCoreAcceptance, ClockBinding, CoordinationBurn,
47    CoordinationBurnGrant, CoordinationGrant, CoordinationIntent, CoordinationReceipt, Diagnostics,
48    DigestBinding, FinalHardwareIdentity, HardwarePlatformIdentity, OracleRequest, Percentiles,
49    PerformanceAcceptance, PerformancePolicyBasis, PreRunHardwareIdentity, ProgrammedImageIdentity,
50    RatePercentiles, ReservationReceipt, ResultsEnvelope, RootArtifactBinding, RunMetadata,
51    SummaryDecoded, TimingRecord, TransportContract, ValidationRecord, WholeKernelAcceptance,
52    Word32, Word64, XrtBuildIdentity, XrtPlatformReport, parse_canonical_json, parse_strict_json,
53    parse_xrt_report_json,
54};
55use serde::Serialize;
56use sha2::{Digest as _, Sha256};
57use std::collections::{BTreeMap, BTreeSet, HashSet};
58use std::io::Write;
59use std::path::{Path, PathBuf};
60use std::thread;
61
62const PROFILE: &str = "HASHSIGS_SHA256_GENERIC_V1";
63const REQUEST_KIND: &str = "hashsigs_phase_b_rust_oracle_request";
64const RESULT_KIND: &str = "hashsigs_phase_b_rust_oracle_result";
65/// Schema emitted by a completed Phase-B hardware-oracle comparison.
66pub const PHASE_B_TYPED_RESULT_SCHEMA: u64 = 3;
67/// Closed result contract consumed by [`verify_typed_sealed_result`].
68pub const PHASE_B_TYPED_RESULT_CONTRACT: &str = "hashsigs-u280-phase-b-typed-result-v1";
69/// CLI receipt schema that explicitly binds the typed result contract.
70pub const PHASE_B_ORACLE_RECEIPT_SCHEMA: u64 = 2;
71const INPUT_BYTES_PER_JOB: u64 = 64;
72const INPUT_BYTES_PER_JOB_USIZE: usize = 64;
73const OUTPUT_SLOT_BYTES: u64 = 4_096;
74const OUTPUT_SLOT_BYTES_USIZE: usize = 4_096;
75const OUTPUT_VALID_BYTES: u64 = 2_208;
76const OUTPUT_VALID_BYTES_USIZE: usize = 2_208;
77const OUTPUT_PADDING_BYTES: u64 = 1_888;
78const SUMMARY_BYTES: u64 = 64;
79const MAXIMUM_BATCH: u32 = 65_535;
80const MAXIMUM_JSON_BYTES: u64 = 16 * 1024 * 1024;
81const ABI_WORD: u32 = 0x4853_0001;
82const LAST_STATUS_SUCCESS: u32 = 0x0f00_0000;
83const DEFAULT_KERNEL: &str = "hashsigs_sha256_hbm_kernel:{hashsigs_sha256_hbm_kernel_1}";
84const TIMED_REGION: &str = "xrt::kernel submission through run.wait completion";
85const OUTPUT_POISON: &str = "a5 xor ((offset*131+5b) mod 256)";
86const SUMMARY_POISON: &str = "c3 xor ((offset*29) mod 256)";
87const RATE_POLICY: &str = "requested clock is metadata; routed rates remain non-promotable until the external Rust oracle validates every output";
88const PERFORMANCE_BLOCKING_REASON: &str = "Phase-B transport performance is non-promotable until the independent Rust oracle validates every output and binds the sealed archive";
89const REQUIRED_PERFORMANCE_BATCH: u32 = 16_384;
90const REQUIRED_PERFORMANCE_SAMPLES: usize = 30;
91const PERFORMANCE_CLOCK_BOUND_HZ: u64 = 250_000_000;
92const MAXIMUM_P95_CAPTURE_CYCLES_EXACT: u64 = 500;
93const MAXIMUM_P95_CAPTURE_CYCLES: f64 = 500.0;
94const MINIMUM_SIGNATURES_PER_SECOND_EXACT: u64 = 500_000;
95const MINIMUM_SIGNATURES_PER_SECOND: f64 = 500_000.0;
96const EXPECTED_DEVICE_NAME: &str = "xilinx_u280_gen3x16_xdma_base_1";
97const EXPECTED_DEPLOYMENT_PLATFORM: &str = "xilinx_u280_gen3x16_xdma_1_202211_1";
98const EXPECTED_FPGA_PART: &str = "xcu280-fsvh2892-2L-e";
99const EXPECTED_INTERFACE_UUID: &str = "fb2b2c5a-19ed-6359-3fea-95f51fbc8eb9";
100const EXPECTED_LOGIC_UUID: &str = "283bab8f-654d-8674-968f-4da57f7fa5d7";
101const PRE_RUN_IDENTITY_CONTRACT: &str = "hashsigs-u280-phase-b-hardware-identity-pre-run-v1";
102const FINAL_IDENTITY_CONTRACT: &str = "hashsigs-u280-phase-b-hardware-identity-v1";
103const PRE_RUN_IDENTITY_FILE: &str = "hardware-identity-pre-run.json";
104const FINAL_IDENTITY_FILE: &str = "hardware-identity.json";
105const PLATFORM_PRE_LOAD_FILE: &str = "xrt-platform-pre-load.json";
106const PLATFORM_POST_RUN_FILE: &str = "xrt-platform-post-run.json";
107const XRT_HOST_FILE: &str = "xrt-host.json";
108const MAXIMUM_PLATFORM_REPORT_BYTES: u64 = 4 * 1024 * 1024;
109const MAXIMUM_XRT_HOST_REPORT_BYTES: u64 = 64 * 1024;
110const MAXIMUM_COORDINATION_RECORD_BYTES: u64 = 64 * 1024;
111const COORDINATION_INTENT_FILE: &str = "coordination-intent.json";
112const COORDINATION_BURN_FILE: &str = "coordination-burn.json";
113const COORDINATION_RECEIPT_FILE: &str = "coordination-receipt.json";
114const COORDINATION_INTENT_CONTRACT: &str = "hashsigs-u280-phase-b-coordination-intent-v1";
115const COORDINATION_BURN_CONTRACT: &str = "hashsigs-u280-phase-b-coordination-burn-v1";
116const COORDINATION_RECEIPT_CONTRACT: &str = "hashsigs-u280-phase-b-coordination-receipt-v2";
117const COORDINATION_OWNER: &str = "hashsigs-rhdl";
118const COORDINATION_COMMAND_CLASS: &str = "phase-b-xrt-card";
119const COORDINATION_LOCK_CONTEXT: &str = "descriptor-v1";
120const COORDINATION_LOCK_ACQUISITION: &str = "nonblocking-exclusive-flock";
121const COORDINATION_LOCK_SUFFIX: &str = "/.cache/rhdl-fpga/u280.lock";
122const COORDINATION_LEDGER_IDS: [&str; 2] = [
123    "hashsigs-project-coordination",
124    "ed25519-agent-coordination",
125];
126const RESULT_EVIDENCE_BOUNDARY: &str = "cryptographic payload, immutable transport binding, a structurally authenticated procedural ledger assertion, and unpromoted native-XRT card identity only; this is not cryptographic ledger proof or global nonrollback evidence, and route, performance, ownership, replay uniqueness, and final hardware completion require a later combiner";
127
128const SAMPLE_FILES: [&str; 11] = [
129    "diagnostics.json",
130    "input.bin",
131    "oracle-request.json",
132    "oracle-request.sha256",
133    "output.bin",
134    "reservation.json",
135    "summary-decoded.json",
136    "summary.bin",
137    "timing.json",
138    "transport.json",
139    "validation.json",
140];
141
142/// Configuration for one immutable archive validation.
143#[derive(Clone, Debug)]
144pub struct OracleConfig {
145    /// Directory containing the transport runner's top-level `SHA256SUMS`.
146    pub transport_root: PathBuf,
147    /// Brand-new directory in which the oracle will write separate evidence.
148    pub result_directory: PathBuf,
149    /// Maximum number of scoped software-oracle workers.
150    pub threads: usize,
151}
152
153/// Completed result returned after the separate directory has been sealed.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct OracleOutcome {
156    /// `true` only when every requested job and every padding region matched.
157    pub cryptographic_oracle_valid: bool,
158    /// Number of oracle-request samples processed.
159    pub samples: usize,
160    /// Number of WOTS jobs recomputed.
161    pub jobs: u64,
162    /// SHA-256 of the result directory's own `SHA256SUMS`.
163    pub result_manifest_sha256: String,
164    /// Exact byte count of the result directory's own `SHA256SUMS`.
165    pub result_manifest_bytes: u64,
166    /// Canonical path of the separately created result directory.
167    pub result_directory: PathBuf,
168}
169
170/// A structural validation failure whose separate evidence directory was
171/// successfully sealed and can therefore be anchored by the CLI.
172#[derive(Clone, Debug, Eq, PartialEq)]
173pub struct OracleFailure {
174    /// Canonical path of the separately created result directory.
175    pub result_directory: PathBuf,
176    /// SHA-256 of the result directory's own `SHA256SUMS`.
177    pub result_manifest_sha256: String,
178    /// Exact byte count of the result directory's own `SHA256SUMS`.
179    pub result_manifest_bytes: u64,
180    /// Original structural validation error, rendered with its context chain.
181    pub failure: String,
182}
183
184impl std::fmt::Display for OracleFailure {
185    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(
187            formatter,
188            "{}; failure evidence sealed in {}",
189            self.failure,
190            self.result_directory.display()
191        )
192    }
193}
194
195impl std::error::Error for OracleFailure {}
196
197/// Revalidates every entry in a sealed result and requires the external
198/// manifest byte-count/digest receipt captured when that result was created.
199///
200/// # Errors
201///
202/// Returns an error for a malformed receipt, any manifest/file mismatch, or a
203/// result directory that no longer matches the externally captured receipt.
204pub fn verify_sealed_result(
205    result_directory: &Path,
206    result_manifest_bytes: u64,
207    result_manifest_sha256: &str,
208) -> Result<()> {
209    ensure!(
210        is_lower_hex(result_manifest_sha256, 64),
211        "result manifest SHA-256 is not canonical"
212    );
213    verify_sealed_manifest(
214        result_directory,
215        &FileRecord {
216            bytes: result_manifest_bytes,
217            sha256: result_manifest_sha256.to_owned(),
218        },
219    )
220}
221
222#[derive(Clone, Debug)]
223struct SampleSpec {
224    relative: String,
225    batch: u32,
226    phase: &'static str,
227    index: usize,
228}
229
230#[derive(Clone, Debug)]
231struct JobComputation {
232    transcript: JobTranscript,
233    seed_sha256: [u8; 32],
234    padding_match: bool,
235}
236
237#[derive(Clone, Debug, Serialize)]
238struct JobTranscript {
239    schema: u64,
240    sample: String,
241    job_index: u32,
242    input_sha256: String,
243    expected_sha256: String,
244    observed_sha256: String,
245    r#match: bool,
246}
247
248#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
249struct ManifestBinding {
250    file: String,
251    bytes: u64,
252    sha256: String,
253}
254
255#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
256struct ResultXclbinBinding {
257    path: String,
258    bytes: u64,
259    sha256: String,
260}
261
262#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
263struct ResultRequestArtifactBinding {
264    file: String,
265    logical_bytes: u64,
266    stored_bytes: u64,
267    sha256: String,
268}
269
270#[derive(Clone, Debug, Serialize)]
271struct RequestResult {
272    sample: String,
273    phase: &'static str,
274    sample_index: usize,
275    request_sha256: String,
276    run_id: String,
277    coordination_intent: ManifestBinding,
278    coordination_burn: ManifestBinding,
279    coordination_receipt: ManifestBinding,
280    batch: u32,
281    nonce_hex: String,
282    reservation_record_sha256: String,
283    xclbin: ResultXclbinBinding,
284    hardware_identity_pre_run: ManifestBinding,
285    input: ResultRequestArtifactBinding,
286    output: ResultRequestArtifactBinding,
287    jobs: u32,
288    matched_jobs: u32,
289    padding_valid: bool,
290    cryptographic_oracle_valid: bool,
291}
292
293#[derive(Clone, Debug, Serialize)]
294// Flat booleans preserve the published success-evidence JSON schema.
295#[allow(clippy::struct_excessive_bools)]
296struct SuccessSummary {
297    schema: u64,
298    kind: &'static str,
299    verifier_contract: &'static str,
300    profile: &'static str,
301    producer: &'static str,
302    pinned_hashsigs_rs_commit: &'static str,
303    transport_manifest: ManifestBinding,
304    run_id: String,
305    coordination_intent: ManifestBinding,
306    coordination_burn: ManifestBinding,
307    coordination_receipt: ManifestBinding,
308    final_hardware_identity: ManifestBinding,
309    worker_threads: usize,
310    sample_count: usize,
311    total_jobs: u64,
312    matched_jobs: u64,
313    mismatched_jobs: u64,
314    all_padding_valid: bool,
315    requests: Vec<RequestResult>,
316    cryptographic_oracle_valid: bool,
317    card_identity_promotable: bool,
318    performance_promotable: bool,
319    hardware_completion_promotable: bool,
320    evidence_boundary: &'static str,
321}
322
323#[derive(Clone, Debug, Serialize)]
324struct FailureSummary {
325    schema: u64,
326    kind: &'static str,
327    profile: &'static str,
328    transport_manifest: Option<ManifestBinding>,
329    cryptographic_oracle_valid: Option<bool>,
330    card_identity_promotable: bool,
331    performance_promotable: bool,
332    hardware_completion_promotable: bool,
333    failure: String,
334}
335
336#[derive(Clone, Debug)]
337struct ValidationOutput {
338    summary: SuccessSummary,
339    jobs_record: FileRecord,
340}
341
342#[derive(Clone, Copy, Debug)]
343struct SampleMetrics {
344    batch: u32,
345    kernel_nanoseconds: u64,
346    memory_span: u64,
347    capture_span: u64,
348}
349
350#[derive(Clone, Debug)]
351struct ValidatedHardwareIdentity {
352    pre_run_record: FileRecord,
353    final_record: FileRecord,
354    pre_run: PreRunHardwareIdentity,
355}
356
357#[derive(Clone, Debug)]
358struct ValidatedCoordination {
359    intent: CoordinationIntent,
360    receipt: CoordinationReceipt,
361    intent_record: FileRecord,
362    burn_record: FileRecord,
363    receipt_record: FileRecord,
364}
365
366#[derive(Serialize)]
367struct CoordinationBurnHashBody<'a> {
368    schema: u64,
369    contract: &'a str,
370    sequence: u64,
371    run_id: &'a str,
372    intent: &'a DigestBinding,
373    grants: &'a [CoordinationBurnGrant],
374    previous_sha256: &'a str,
375}
376
377#[derive(Debug)]
378struct ProcessedSamples {
379    jobs_record: FileRecord,
380    request_results: Vec<RequestResult>,
381    measured_metrics: Vec<SampleMetrics>,
382    total_jobs: u64,
383    matched_jobs: u64,
384    all_padding_valid: bool,
385}
386
387#[derive(Clone, Copy, Debug)]
388struct SampleComputationSummary {
389    matched_jobs: u32,
390    padding_valid: bool,
391}
392
393/// Validates a sealed Phase-B archive and creates a distinct sealed result.
394///
395/// # Errors
396///
397/// Returns an error for malformed archives, filesystem races, transport
398/// contract failures, or result-write failures. When the result directory can
399/// be created safely, structural failures leave `failure.json` and a result
400/// `SHA256SUMS` behind for audit and return an [`OracleFailure`] that carries
401/// the external manifest receipt. Cryptographic mismatches produce a complete
402/// summary with `cryptographic_oracle_valid:false` and return `Ok` so callers
403/// can distinguish a completed negative comparison from malformed evidence.
404pub fn execute(config: &OracleConfig) -> Result<OracleOutcome> {
405    ensure!(
406        (1..=256).contains(&config.threads),
407        "worker thread count must be between 1 and 256"
408    );
409    let transport_root = canonical_transport_root(&config.transport_root)?;
410    let result_directory = prepare_result_directory(&transport_root, &config.result_directory)?;
411    let mut transport_manifest = None;
412    let operation: Result<OracleOutcome> = (|| {
413        let snapshot = verify_transport_archive(&transport_root)?;
414        transport_manifest = Some(manifest_binding(&snapshot.manifest));
415        let output = validate_archive(&snapshot, &result_directory, config.threads)?;
416        let valid = output.summary.cryptographic_oracle_valid;
417        let samples = output.summary.sample_count;
418        let jobs = output.summary.total_jobs;
419        let summary = canonical_json_line(&output.summary)?;
420        let summary_record = write_exclusive(&result_directory, "summary.json", &summary)?;
421        let expected = BTreeMap::from([
422            ("jobs.jsonl".to_owned(), output.jobs_record),
423            ("summary.json".to_owned(), summary_record),
424        ]);
425        let result_manifest = seal_result_directory(&result_directory, &expected)?;
426        Ok(OracleOutcome {
427            cryptographic_oracle_valid: valid,
428            samples,
429            jobs,
430            result_manifest_sha256: result_manifest.sha256,
431            result_manifest_bytes: result_manifest.bytes,
432            result_directory: result_directory.path.clone(),
433        })
434    })();
435
436    match operation {
437        Ok(outcome) => Ok(outcome),
438        Err(error) => {
439            for name in ["SHA256SUMS", "jobs.jsonl", "summary.json"] {
440                if let Err(cleanup_error) = result_directory.remove_if_exists(name) {
441                    return Err(error.context(format!(
442                        "also failed to remove partial result {name}: {cleanup_error:#}"
443                    )));
444                }
445            }
446            let failure = FailureSummary {
447                schema: 1,
448                kind: "hashsigs_phase_b_rust_oracle_failure",
449                profile: PROFILE,
450                transport_manifest,
451                cryptographic_oracle_valid: None,
452                card_identity_promotable: false,
453                performance_promotable: false,
454                hardware_completion_promotable: false,
455                failure: format!("{error:#}"),
456            };
457            let write_result = canonical_json_line(&failure).and_then(|bytes| {
458                let record = write_exclusive(&result_directory, "failure.json", &bytes)?;
459                let expected = BTreeMap::from([("failure.json".to_owned(), record)]);
460                seal_result_directory(&result_directory, &expected)
461            });
462            let result_manifest = match write_result {
463                Ok(record) => record,
464                Err(evidence_error) => {
465                    return Err(error.context(format!(
466                        "also failed to seal failure evidence: {evidence_error:#}"
467                    )));
468                }
469            };
470            Err(OracleFailure {
471                result_directory: result_directory.path.clone(),
472                result_manifest_sha256: result_manifest.sha256,
473                result_manifest_bytes: result_manifest.bytes,
474                failure: format!("{error:#}"),
475            }
476            .into())
477        }
478    }
479}
480
481fn validate_archive(
482    snapshot: &ArchiveSnapshot,
483    result_directory: &ResultDirectory,
484    threads: usize,
485) -> Result<ValidationOutput> {
486    let coordination = validate_coordination_records(snapshot)?;
487    let metadata: RunMetadata = parse_archive_json(snapshot, "run-metadata.json")?;
488    validate_run_metadata(&metadata, &coordination)?;
489    let hardware_identity = validate_hardware_identity(snapshot, &metadata)?;
490    validate_coordination_hardware(&coordination, &hardware_identity)?;
491    let results: ResultsEnvelope = parse_archive_json(snapshot, "results.json")?;
492    validate_results(
493        &results,
494        &metadata,
495        &hardware_identity.final_record,
496        &coordination,
497    )?;
498    let samples = expected_layout(snapshot, &metadata)?;
499    let processed = process_samples(
500        snapshot,
501        result_directory,
502        threads,
503        &metadata,
504        &samples,
505        &hardware_identity.pre_run_record,
506        &coordination,
507    )?;
508    validate_results_statistics(&results, &metadata, &processed.measured_metrics)?;
509
510    let after = verify_transport_archive_again(snapshot)?;
511    ensure!(
512        &after == snapshot,
513        "transport archive changed during oracle execution"
514    );
515    let cryptographic_oracle_valid =
516        processed.matched_jobs == processed.total_jobs && processed.all_padding_valid;
517    let mismatched_jobs = processed
518        .total_jobs
519        .checked_sub(processed.matched_jobs)
520        .context("mismatch counter underflow")?;
521    Ok(ValidationOutput {
522        jobs_record: processed.jobs_record,
523        summary: SuccessSummary {
524            schema: PHASE_B_TYPED_RESULT_SCHEMA,
525            kind: RESULT_KIND,
526            verifier_contract: PHASE_B_TYPED_RESULT_CONTRACT,
527            profile: PROFILE,
528            producer: "independent Rust Sha256GenericWots oracle",
529            pinned_hashsigs_rs_commit: "2d315dd4168804b7cbc51c51a1bf7ca27bf74140",
530            transport_manifest: manifest_binding(&snapshot.manifest),
531            run_id: coordination.intent.run_id.clone(),
532            coordination_intent: named_binding(
533                COORDINATION_INTENT_FILE,
534                &coordination.intent_record,
535            ),
536            coordination_burn: named_binding(COORDINATION_BURN_FILE, &coordination.burn_record),
537            coordination_receipt: named_binding(
538                COORDINATION_RECEIPT_FILE,
539                &coordination.receipt_record,
540            ),
541            final_hardware_identity: named_binding(
542                FINAL_IDENTITY_FILE,
543                &hardware_identity.final_record,
544            ),
545            worker_threads: threads,
546            sample_count: samples.len(),
547            total_jobs: processed.total_jobs,
548            matched_jobs: processed.matched_jobs,
549            mismatched_jobs,
550            all_padding_valid: processed.all_padding_valid,
551            requests: processed.request_results,
552            cryptographic_oracle_valid,
553            card_identity_promotable: false,
554            performance_promotable: false,
555            hardware_completion_promotable: false,
556            evidence_boundary: RESULT_EVIDENCE_BOUNDARY,
557        },
558    })
559}
560
561fn process_samples(
562    snapshot: &ArchiveSnapshot,
563    result_directory: &ResultDirectory,
564    threads: usize,
565    metadata: &RunMetadata,
566    samples: &[SampleSpec],
567    pre_run_identity_record: &FileRecord,
568    coordination: &ValidatedCoordination,
569) -> Result<ProcessedSamples> {
570    let mut transcript = create_exclusive_file(result_directory, "jobs.jsonl")?;
571    let mut request_results = Vec::with_capacity(samples.len());
572    let mut validation_state = SampleValidationState {
573        metadata,
574        pre_run_identity_record,
575        coordination,
576        reservation_ids: HashSet::new(),
577        nonces: HashSet::new(),
578        reservation_chain: None,
579    };
580    let mut private_seed_digests = HashSet::new();
581    let mut measured_metrics = Vec::new();
582    let mut total_jobs = 0_u64;
583    let mut matched_jobs = 0_u64;
584    let mut all_padding_valid = true;
585
586    for sample in samples {
587        let validated = validate_sample_contract(snapshot, sample, &mut validation_state)?;
588        let computations = compute_sample_jobs(snapshot, sample, threads)?;
589        let computation =
590            write_sample_computations(&mut transcript, computations, &mut private_seed_digests)?;
591        transcript.flush().context("cannot flush job transcript")?;
592        transcript
593            .get_ref()
594            .sync_all()
595            .context("cannot fsync job transcript")?;
596
597        if sample.phase == "measured" {
598            measured_metrics.push(validated.metrics);
599        }
600        total_jobs = total_jobs
601            .checked_add(u64::from(sample.batch))
602            .context("total job counter overflow")?;
603        matched_jobs = matched_jobs
604            .checked_add(u64::from(computation.matched_jobs))
605            .context("matched job counter overflow")?;
606        all_padding_valid &= computation.padding_valid;
607        request_results.push(sample_request_result(sample, validated, computation));
608    }
609
610    let jobs_record = finish_writer(transcript)?;
611    result_directory.sync()?;
612    Ok(ProcessedSamples {
613        jobs_record,
614        request_results,
615        measured_metrics,
616        total_jobs,
617        matched_jobs,
618        all_padding_valid,
619    })
620}
621
622fn write_sample_computations(
623    transcript: &mut impl Write,
624    computations: Vec<JobComputation>,
625    private_seed_digests: &mut HashSet<[u8; 32]>,
626) -> Result<SampleComputationSummary> {
627    let mut matched_jobs = 0_u32;
628    let mut padding_valid = true;
629    for computation in computations {
630        ensure!(
631            private_seed_digests.insert(computation.seed_sha256),
632            "transport archive reuses a private WOTS seed"
633        );
634        if computation.transcript.r#match {
635            matched_jobs = matched_jobs
636                .checked_add(1)
637                .context("sample match counter overflow")?;
638        }
639        padding_valid &= computation.padding_match;
640        serde_json::to_writer(&mut *transcript, &computation.transcript)
641            .context("cannot serialize job transcript")?;
642        transcript
643            .write_all(b"\n")
644            .context("cannot write job transcript")?;
645    }
646    Ok(SampleComputationSummary {
647        matched_jobs,
648        padding_valid,
649    })
650}
651
652fn sample_request_result(
653    sample: &SampleSpec,
654    validated: ValidatedSample,
655    computation: SampleComputationSummary,
656) -> RequestResult {
657    RequestResult {
658        sample: sample.relative.clone(),
659        phase: sample.phase,
660        sample_index: sample.index,
661        request_sha256: validated.request_sha256,
662        run_id: validated.request.run_id.clone(),
663        coordination_intent: root_binding(&validated.request.coordination_intent),
664        coordination_burn: root_binding(&validated.request.coordination_burn),
665        coordination_receipt: root_binding(&validated.request.coordination_receipt),
666        batch: sample.batch,
667        nonce_hex: validated.request.nonce_hex.clone(),
668        reservation_record_sha256: validated.request.reservation_record_sha256.clone(),
669        xclbin: ResultXclbinBinding {
670            path: validated.request.xclbin.path.clone(),
671            bytes: validated.request.xclbin.bytes,
672            sha256: validated.request.xclbin.sha256.clone(),
673        },
674        hardware_identity_pre_run: root_binding(&validated.request.hardware_identity_pre_run),
675        input: request_artifact_binding(&validated.request.artifacts.input),
676        output: request_artifact_binding(&validated.request.artifacts.full_output_allocation),
677        jobs: sample.batch,
678        matched_jobs: computation.matched_jobs,
679        padding_valid: computation.padding_valid,
680        cryptographic_oracle_valid: computation.matched_jobs == sample.batch
681            && computation.padding_valid,
682    }
683}
684
685fn manifest_binding(record: &FileRecord) -> ManifestBinding {
686    named_binding("SHA256SUMS", record)
687}
688
689fn named_binding(file: &str, record: &FileRecord) -> ManifestBinding {
690    ManifestBinding {
691        file: file.to_owned(),
692        bytes: record.bytes,
693        sha256: record.sha256.clone(),
694    }
695}
696
697fn root_binding(binding: &RootArtifactBinding) -> ManifestBinding {
698    ManifestBinding {
699        file: binding.archive_root_file.clone(),
700        bytes: binding.bytes,
701        sha256: binding.sha256.clone(),
702    }
703}
704
705fn request_artifact_binding(binding: &ArtifactBinding) -> ResultRequestArtifactBinding {
706    ResultRequestArtifactBinding {
707        file: binding.file.clone(),
708        logical_bytes: binding.logical_bytes,
709        stored_bytes: binding.stored_bytes,
710        sha256: binding.sha256.clone(),
711    }
712}
713
714fn canonical_json_line<T: Serialize>(value: &T) -> Result<Vec<u8>> {
715    let mut bytes = serde_json::to_vec(value).context("cannot serialize canonical JSON")?;
716    bytes.push(b'\n');
717    Ok(bytes)
718}
719
720fn parse_archive_json<T>(snapshot: &ArchiveSnapshot, relative: &str) -> Result<T>
721where
722    T: serde::de::DeserializeOwned,
723{
724    let bytes = read_verified_small(snapshot, relative, MAXIMUM_JSON_BYTES)?;
725    parse_strict_json(&bytes, relative)
726}
727
728fn parse_coordination_json<T>(snapshot: &ArchiveSnapshot, relative: &str) -> Result<T>
729where
730    T: serde::de::DeserializeOwned + Serialize,
731{
732    let bytes = read_verified_small(snapshot, relative, MAXIMUM_COORDINATION_RECORD_BYTES)?;
733    parse_canonical_json(&bytes, relative)
734}
735
736fn validate_coordination_records(snapshot: &ArchiveSnapshot) -> Result<ValidatedCoordination> {
737    let intent_record = archive_file_record(snapshot, COORDINATION_INTENT_FILE)?;
738    let burn_record = archive_file_record(snapshot, COORDINATION_BURN_FILE)?;
739    let receipt_record = archive_file_record(snapshot, COORDINATION_RECEIPT_FILE)?;
740    let intent: CoordinationIntent = parse_coordination_json(snapshot, COORDINATION_INTENT_FILE)?;
741    validate_coordination_intent(&intent)?;
742    let burn: CoordinationBurn = parse_coordination_json(snapshot, COORDINATION_BURN_FILE)?;
743    validate_coordination_burn(&burn, &intent, &intent_record)?;
744    let receipt: CoordinationReceipt =
745        parse_coordination_json(snapshot, COORDINATION_RECEIPT_FILE)?;
746    validate_coordination_receipt(
747        snapshot,
748        &receipt,
749        &intent,
750        &burn,
751        &intent_record,
752        &burn_record,
753    )?;
754    Ok(ValidatedCoordination {
755        intent,
756        receipt,
757        intent_record,
758        burn_record,
759        receipt_record,
760    })
761}
762
763fn validate_coordination_intent(intent: &CoordinationIntent) -> Result<()> {
764    ensure!(intent.schema == 1, "coordination intent schema must be 1");
765    ensure!(
766        intent.contract == COORDINATION_INTENT_CONTRACT,
767        "coordination intent contract changed"
768    );
769    ensure!(
770        is_nonzero_lower_hex(&intent.run_id, 32),
771        "coordination run id is not canonical"
772    );
773    ensure!(
774        intent.owner == COORDINATION_OWNER,
775        "coordination intent owner changed"
776    );
777    ensure!(
778        intent.command_class == COORDINATION_COMMAND_CLASS,
779        "coordination intent command class changed"
780    );
781    ensure!(
782        valid_utc(&intent.created_utc),
783        "coordination intent creation time is not canonical UTC"
784    );
785    ensure!(
786        intent.lock.context == COORDINATION_LOCK_CONTEXT
787            && intent.lock.acquisition == COORDINATION_LOCK_ACQUISITION
788            && canonical_coordination_lock_path(&intent.lock.path),
789        "coordination intent lock binding is noncanonical"
790    );
791    ensure!(
792        intent.grants.len() == COORDINATION_LEDGER_IDS.len(),
793        "coordination intent must contain exactly two ordered grants"
794    );
795    for (index, grant) in intent.grants.iter().enumerate() {
796        validate_coordination_grant(grant, COORDINATION_LEDGER_IDS[index], &intent.run_id)?;
797        ensure!(
798            grant.granted_utc.as_str() <= intent.created_utc.as_str(),
799            "coordination grant time is later than intent creation"
800        );
801    }
802    ensure!(
803        intent.grants[0].entry_id != intent.grants[1].entry_id,
804        "coordination intent grant entry ids must differ"
805    );
806    Ok(())
807}
808
809fn validate_coordination_grant(
810    grant: &CoordinationGrant,
811    expected_ledger: &str,
812    run_id: &str,
813) -> Result<()> {
814    ensure!(
815        grant.ledger_id == expected_ledger
816            && canonical_coordination_entry_id(&grant.entry_id)
817            && valid_utc(&grant.granted_utc)
818            && grant.run_id == run_id
819            && grant.entry_bytes > 0
820            && is_nonzero_lower_hex(&grant.entry_sha256, 64),
821        "coordination grant violates its canonical ledger binding"
822    );
823    Ok(())
824}
825
826fn validate_coordination_burn(
827    burn: &CoordinationBurn,
828    intent: &CoordinationIntent,
829    intent_record: &FileRecord,
830) -> Result<()> {
831    ensure!(burn.schema == 1, "coordination burn schema must be 1");
832    ensure!(
833        burn.contract == COORDINATION_BURN_CONTRACT,
834        "coordination burn contract changed"
835    );
836    ensure!(
837        burn.run_id == intent.run_id,
838        "coordination burn run id differs from intent"
839    );
840    validate_digest(&burn.intent, "coordination burn intent")?;
841    ensure!(
842        burn.intent.bytes == intent_record.bytes && burn.intent.sha256 == intent_record.sha256,
843        "coordination burn does not bind the archived intent"
844    );
845    ensure!(
846        burn.grants.len() == intent.grants.len(),
847        "coordination burn must contain exactly two ordered grants"
848    );
849    for (index, (burn_grant, intent_grant)) in burn.grants.iter().zip(&intent.grants).enumerate() {
850        ensure!(
851            burn_grant.ledger_id == COORDINATION_LEDGER_IDS[index]
852                && burn_grant.ledger_id == intent_grant.ledger_id
853                && burn_grant.entry_id == intent_grant.entry_id
854                && burn_grant.entry_bytes == intent_grant.entry_bytes
855                && burn_grant.entry_sha256 == intent_grant.entry_sha256,
856            "coordination burn grant differs from intent"
857        );
858    }
859    ensure!(
860        is_lower_hex(&burn.previous_sha256, 64),
861        "coordination burn previous SHA-256 is not canonical"
862    );
863    let zero_hash = "0".repeat(64);
864    if burn.sequence == 0 {
865        ensure!(
866            burn.previous_sha256.as_str() == zero_hash.as_str(),
867            "initial coordination burn previous hash must be zero"
868        );
869    } else {
870        ensure!(
871            burn.previous_sha256.as_str() != zero_hash.as_str(),
872            "noninitial coordination burn previous hash must be nonzero"
873        );
874    }
875    ensure!(
876        is_lower_hex(&burn.record_sha256, 64),
877        "coordination burn record SHA-256 is not canonical"
878    );
879    let body = CoordinationBurnHashBody {
880        schema: burn.schema,
881        contract: &burn.contract,
882        sequence: burn.sequence,
883        run_id: &burn.run_id,
884        intent: &burn.intent,
885        grants: &burn.grants,
886        previous_sha256: &burn.previous_sha256,
887    };
888    let body_bytes =
889        serde_json::to_vec(&body).context("cannot serialize coordination burn hash body")?;
890    ensure!(
891        sha256_bytes(&body_bytes) == burn.record_sha256,
892        "coordination burn record SHA-256 differs from its canonical body"
893    );
894    Ok(())
895}
896
897fn validate_coordination_receipt(
898    snapshot: &ArchiveSnapshot,
899    receipt: &CoordinationReceipt,
900    intent: &CoordinationIntent,
901    burn: &CoordinationBurn,
902    intent_record: &FileRecord,
903    burn_record: &FileRecord,
904) -> Result<()> {
905    ensure!(receipt.schema == 2, "coordination receipt schema must be 2");
906    ensure!(
907        receipt.contract == COORDINATION_RECEIPT_CONTRACT,
908        "coordination receipt contract changed"
909    );
910    ensure!(
911        receipt.run_id == intent.run_id && receipt.run_id == burn.run_id,
912        "coordination receipt run id differs from intent or burn"
913    );
914    validate_root_binding_against_record(
915        &receipt.intent,
916        COORDINATION_INTENT_FILE,
917        intent_record,
918        "coordination receipt intent",
919    )?;
920    validate_root_binding_against_record(
921        &receipt.coordination_burn,
922        COORDINATION_BURN_FILE,
923        burn_record,
924        "coordination receipt burn",
925    )?;
926    ensure!(
927        receipt.grants == intent.grants,
928        "coordination receipt grants differ from intent"
929    );
930    ensure!(
931        receipt.lock.path == intent.lock.path
932            && receipt.lock.context == intent.lock.context
933            && receipt.lock.acquisition == intent.lock.acquisition
934            && receipt.lock.device > 0
935            && receipt.lock.inode > 0
936            && u32::try_from(receipt.lock.euid).is_ok(),
937        "coordination receipt lock differs from intent or lacks descriptor identity"
938    );
939    ensure!(
940        u32::try_from(receipt.hardware.device_index).is_ok()
941            && canonical_bdf(&receipt.hardware.bdf)
942            && canonical_serial(&receipt.hardware.xmc_serial_number)
943            && receipt.hardware.shell_vbnv == EXPECTED_DEVICE_NAME
944            && receipt.hardware.logic_uuid == EXPECTED_LOGIC_UUID
945            && receipt.hardware.interface_uuid == EXPECTED_INTERFACE_UUID,
946        "coordination receipt hardware identity is noncanonical"
947    );
948    ensure!(
949        receipt.xclbin.bytes > 0
950            && is_lower_hex(&receipt.xclbin.sha256, 64)
951            && canonical_uuid(&receipt.xclbin.uuid)
952            && receipt.xclbin.target == "hw",
953        "coordination receipt xclbin binding is noncanonical"
954    );
955    validate_root_artifact_binding(
956        snapshot,
957        &receipt.hardware_identity_pre_run,
958        PRE_RUN_IDENTITY_FILE,
959        "coordination receipt pre-run hardware identity",
960    )?;
961    ensure!(
962        !receipt.card_identity_promotable
963            && !receipt.performance_promotable
964            && !receipt.hardware_completion_promotable,
965        "coordination receipt makes a premature promotion claim"
966    );
967    Ok(())
968}
969
970fn validate_coordination_hardware(
971    coordination: &ValidatedCoordination,
972    hardware_identity: &ValidatedHardwareIdentity,
973) -> Result<()> {
974    let receipt = &coordination.receipt;
975    let pre_run = &hardware_identity.pre_run;
976    ensure!(
977        receipt.hardware_identity_pre_run.bytes == hardware_identity.pre_run_record.bytes
978            && receipt.hardware_identity_pre_run.sha256 == hardware_identity.pre_run_record.sha256,
979        "coordination receipt pre-run binding differs from parsed identity"
980    );
981    ensure!(
982        receipt.hardware.device_index == pre_run.device.index
983            && receipt.hardware.bdf == pre_run.device.bdf
984            && receipt.hardware.xmc_serial_number == pre_run.platform.xmc_serial_number
985            && receipt.hardware.shell_vbnv == pre_run.platform.shell_vbnv
986            && receipt.hardware.logic_uuid == pre_run.platform.logic_uuid
987            && receipt.hardware.interface_uuid == pre_run.device.interface_uuid
988            && receipt.hardware.interface_uuid == pre_run.pinned_target.interface_uuid,
989        "coordination receipt hardware fields differ from native pre-run identity"
990    );
991    ensure!(
992        receipt.xclbin.bytes == pre_run.xclbin.digest.bytes
993            && receipt.xclbin.sha256 == pre_run.xclbin.digest.sha256
994            && receipt.xclbin.uuid == pre_run.xclbin.uuid
995            && receipt.xclbin.target == pre_run.xclbin.target,
996        "coordination receipt xclbin differs from native pre-run identity"
997    );
998    Ok(())
999}
1000
1001fn validate_coordination_consumer_binding(
1002    run_id: &str,
1003    intent: &RootArtifactBinding,
1004    burn: &RootArtifactBinding,
1005    receipt: &RootArtifactBinding,
1006    coordination: &ValidatedCoordination,
1007    label: &str,
1008) -> Result<()> {
1009    ensure!(
1010        run_id == coordination.intent.run_id,
1011        "{label} run id differs from coordination intent"
1012    );
1013    validate_root_binding_against_record(
1014        intent,
1015        COORDINATION_INTENT_FILE,
1016        &coordination.intent_record,
1017        &format!("{label} coordination intent"),
1018    )?;
1019    validate_root_binding_against_record(
1020        burn,
1021        COORDINATION_BURN_FILE,
1022        &coordination.burn_record,
1023        &format!("{label} coordination burn"),
1024    )?;
1025    validate_root_binding_against_record(
1026        receipt,
1027        COORDINATION_RECEIPT_FILE,
1028        &coordination.receipt_record,
1029        &format!("{label} coordination receipt"),
1030    )?;
1031    Ok(())
1032}
1033
1034fn canonical_coordination_entry_id(value: &str) -> bool {
1035    !value.is_empty()
1036        && value.len() <= 128
1037        && value.bytes().all(|byte| {
1038            byte.is_ascii_lowercase()
1039                || byte.is_ascii_digit()
1040                || matches!(byte, b'.' | b'_' | b':' | b'-')
1041        })
1042}
1043
1044fn canonical_coordination_lock_path(value: &str) -> bool {
1045    value.is_ascii()
1046        && value.starts_with('/')
1047        && value.len() > COORDINATION_LOCK_SUFFIX.len()
1048        && value.ends_with(COORDINATION_LOCK_SUFFIX)
1049        && !value.contains("//")
1050        && value
1051            .split('/')
1052            .skip(1)
1053            .all(|component| !component.is_empty() && component != "." && component != "..")
1054}
1055
1056fn is_nonzero_lower_hex(value: &str, width: usize) -> bool {
1057    is_lower_hex(value, width) && value.bytes().any(|byte| byte != b'0')
1058}
1059
1060fn validate_run_metadata(
1061    metadata: &RunMetadata,
1062    coordination: &ValidatedCoordination,
1063) -> Result<()> {
1064    ensure!(metadata.schema == 2, "run metadata schema must be 2");
1065    ensure!(
1066        metadata.runner == "phase_b_hbm",
1067        "archive is not a Phase-B HBM run"
1068    );
1069    validate_coordination_consumer_binding(
1070        &metadata.run_id,
1071        &metadata.coordination_intent,
1072        &metadata.coordination_burn,
1073        &metadata.coordination_receipt,
1074        coordination,
1075        "run metadata",
1076    )?;
1077    ensure!(
1078        valid_utc(&metadata.created_utc),
1079        "created_utc is not canonical UTC"
1080    );
1081    ensure!(
1082        metadata.created_utc.as_str() >= coordination.intent.created_utc.as_str(),
1083        "run metadata predates coordination intent"
1084    );
1085    ensure!(
1086        u32::try_from(metadata.device_index).is_ok(),
1087        "run metadata device index does not fit u32"
1088    );
1089    ensure!(
1090        metadata.kernel_name == DEFAULT_KERNEL,
1091        "unexpected HBM kernel identity"
1092    );
1093    validate_digest(&metadata.xclbin.digest, "metadata xclbin")?;
1094    ensure!(!metadata.xclbin.path.is_empty(), "xclbin path is empty");
1095    ensure!(
1096        metadata.measured_runs_per_batch > 0,
1097        "measured run count must be positive"
1098    );
1099    ensure!(!metadata.batches.is_empty(), "run metadata has no batches");
1100    ensure!(metadata.timeout_ms > 0, "run timeout must be positive");
1101    validate_clock(&metadata.clock)?;
1102    ensure!(
1103        metadata.cryptographic_control == "Rust/RHDL kernel only",
1104        "metadata cryptographic-control boundary changed"
1105    );
1106    ensure!(
1107        metadata.output_oracle == "external Rust oracle required",
1108        "metadata does not require the external Rust oracle"
1109    );
1110    let mut batches = HashSet::new();
1111    for &batch in &metadata.batches {
1112        ensure!(
1113            batch <= MAXIMUM_BATCH,
1114            "metadata batch exceeds the shell ABI"
1115        );
1116        ensure!(batches.insert(batch), "metadata repeats a batch");
1117    }
1118    Ok(())
1119}
1120
1121fn valid_utc(text: &str) -> bool {
1122    let bytes = text.as_bytes();
1123    bytes.len() == 20 && bytes[19] == b'Z' && valid_calendar_timestamp(&bytes[..19], b'T')
1124}
1125
1126fn validate_hardware_identity(
1127    snapshot: &ArchiveSnapshot,
1128    metadata: &RunMetadata,
1129) -> Result<ValidatedHardwareIdentity> {
1130    let pre_run_record = archive_file_record(snapshot, PRE_RUN_IDENTITY_FILE)?;
1131    let final_record = archive_file_record(snapshot, FINAL_IDENTITY_FILE)?;
1132    let pre_run: PreRunHardwareIdentity = parse_archive_json(snapshot, PRE_RUN_IDENTITY_FILE)?;
1133    let final_identity: FinalHardwareIdentity = parse_archive_json(snapshot, FINAL_IDENTITY_FILE)?;
1134
1135    validate_pre_run_identity(&pre_run, metadata)?;
1136    validate_final_identity(&final_identity, metadata)?;
1137    validate_identity_continuity(&pre_run, &final_identity)?;
1138    validate_identity_report_bindings(snapshot, &pre_run, &final_identity, &pre_run_record)?;
1139    validate_raw_identity_reports(snapshot, &pre_run, &final_identity)?;
1140
1141    Ok(ValidatedHardwareIdentity {
1142        pre_run_record,
1143        final_record,
1144        pre_run,
1145    })
1146}
1147
1148fn validate_identity_continuity(
1149    pre_run: &PreRunHardwareIdentity,
1150    final_identity: &FinalHardwareIdentity,
1151) -> Result<()> {
1152    ensure!(
1153        pre_run.capture == final_identity.capture
1154            && pre_run.pinned_target == final_identity.pinned_target
1155            && pre_run.device == final_identity.device
1156            && pre_run.platform == final_identity.platform
1157            && pre_run.xrt_build == final_identity.xrt_build
1158            && pre_run.xclbin == final_identity.xclbin,
1159        "pre-run and final hardware identity fields differ"
1160    );
1161    ensure!(
1162        pre_run.programmed_image.load_return_uuid
1163            == final_identity.programmed_image.load_return_uuid
1164            && pre_run.programmed_image.post_load_uuid
1165                == final_identity.programmed_image.post_load_uuid,
1166        "programmed-image UUID changed between pre-run and final identity"
1167    );
1168    Ok(())
1169}
1170
1171fn validate_identity_report_bindings(
1172    snapshot: &ArchiveSnapshot,
1173    pre_run: &PreRunHardwareIdentity,
1174    final_identity: &FinalHardwareIdentity,
1175    pre_run_record: &FileRecord,
1176) -> Result<()> {
1177    validate_root_artifact_binding(
1178        snapshot,
1179        &pre_run.raw_reports.platform_pre_load,
1180        PLATFORM_PRE_LOAD_FILE,
1181        "pre-run platform report",
1182    )?;
1183    ensure!(
1184        pre_run.raw_reports.platform_post_run.is_none(),
1185        "pre-run identity unexpectedly binds a post-run platform report"
1186    );
1187    validate_root_artifact_binding(
1188        snapshot,
1189        &pre_run.raw_reports.xrt_host,
1190        XRT_HOST_FILE,
1191        "pre-run XRT host report",
1192    )?;
1193    validate_root_artifact_binding(
1194        snapshot,
1195        &final_identity.raw_reports.platform_pre_load,
1196        PLATFORM_PRE_LOAD_FILE,
1197        "final pre-load platform report",
1198    )?;
1199    let final_post_run = final_identity
1200        .raw_reports
1201        .platform_post_run
1202        .as_ref()
1203        .context("final identity omits its post-run platform report")?;
1204    validate_root_artifact_binding(
1205        snapshot,
1206        final_post_run,
1207        PLATFORM_POST_RUN_FILE,
1208        "final post-run platform report",
1209    )?;
1210    validate_root_artifact_binding(
1211        snapshot,
1212        &final_identity.raw_reports.xrt_host,
1213        XRT_HOST_FILE,
1214        "final XRT host report",
1215    )?;
1216    validate_root_artifact_binding(
1217        snapshot,
1218        &final_identity.pre_run_record,
1219        PRE_RUN_IDENTITY_FILE,
1220        "final pre-run identity record",
1221    )?;
1222    ensure!(
1223        final_identity.pre_run_record.bytes == pre_run_record.bytes
1224            && final_identity.pre_run_record.sha256 == pre_run_record.sha256,
1225        "final identity does not bind the parsed pre-run identity"
1226    );
1227    ensure!(
1228        pre_run.raw_reports.platform_pre_load == final_identity.raw_reports.platform_pre_load
1229            && pre_run.raw_reports.xrt_host == final_identity.raw_reports.xrt_host,
1230        "pre-run and final identity raw-report bindings differ"
1231    );
1232    Ok(())
1233}
1234
1235fn validate_raw_identity_reports(
1236    snapshot: &ArchiveSnapshot,
1237    pre_run: &PreRunHardwareIdentity,
1238    final_identity: &FinalHardwareIdentity,
1239) -> Result<()> {
1240    let platform_pre = parse_xrt_platform_report(snapshot, PLATFORM_PRE_LOAD_FILE)?;
1241    let platform_post = parse_xrt_platform_report(snapshot, PLATFORM_POST_RUN_FILE)?;
1242    ensure!(
1243        platform_pre == pre_run.platform,
1244        "pre-load XRT platform report differs from pre-run identity"
1245    );
1246    ensure!(
1247        platform_post == final_identity.platform,
1248        "post-run XRT platform report differs from final identity"
1249    );
1250    ensure!(
1251        platform_pre == platform_post,
1252        "XRT platform identity changed between pre-load and post-run reports"
1253    );
1254
1255    let host_bytes = read_verified_small(snapshot, XRT_HOST_FILE, MAXIMUM_XRT_HOST_REPORT_BYTES)?;
1256    let raw_xrt_build: XrtBuildIdentity = parse_xrt_report_json(&host_bytes, XRT_HOST_FILE)?;
1257    validate_xrt_build(&raw_xrt_build, false)?;
1258    ensure!(
1259        raw_xrt_build.version == pre_run.xrt_build.version
1260            && raw_xrt_build.branch == pre_run.xrt_build.branch
1261            && raw_xrt_build.hash.to_ascii_lowercase() == pre_run.xrt_build.hash
1262            && raw_xrt_build.build_date == pre_run.xrt_build.build_date,
1263        "raw XRT host report differs from hardware identity"
1264    );
1265    Ok(())
1266}
1267
1268fn archive_file_record(snapshot: &ArchiveSnapshot, file: &str) -> Result<FileRecord> {
1269    snapshot
1270        .files
1271        .get(file)
1272        .cloned()
1273        .with_context(|| format!("archive manifest omits {file}"))
1274}
1275
1276fn validate_root_artifact_binding(
1277    snapshot: &ArchiveSnapshot,
1278    binding: &RootArtifactBinding,
1279    expected_file: &str,
1280    label: &str,
1281) -> Result<()> {
1282    let record = archive_file_record(snapshot, expected_file)?;
1283    validate_root_binding_against_record(binding, expected_file, &record, label)
1284}
1285
1286fn validate_root_binding_against_record(
1287    binding: &RootArtifactBinding,
1288    expected_file: &str,
1289    record: &FileRecord,
1290    label: &str,
1291) -> Result<()> {
1292    ensure!(
1293        binding.archive_root_file == expected_file,
1294        "{label} names the wrong archive-root file"
1295    );
1296    ensure!(binding.bytes > 0, "{label} byte count must be positive");
1297    ensure!(
1298        is_lower_hex(&binding.sha256, 64),
1299        "{label} SHA-256 is not canonical"
1300    );
1301    ensure!(
1302        binding.bytes == record.bytes && binding.sha256 == record.sha256,
1303        "{label} differs from the top-level manifest"
1304    );
1305    Ok(())
1306}
1307
1308fn validate_pre_run_identity(
1309    identity: &PreRunHardwareIdentity,
1310    metadata: &RunMetadata,
1311) -> Result<()> {
1312    validate_identity_fields(
1313        identity.schema,
1314        &identity.contract,
1315        PRE_RUN_IDENTITY_CONTRACT,
1316        &identity.capture,
1317        &identity.pinned_target,
1318        &identity.device,
1319        &identity.platform,
1320        &identity.xrt_build,
1321        &identity.xclbin,
1322        identity.hardware_completion_promotable,
1323        metadata,
1324    )?;
1325    validate_programmed_image(&identity.programmed_image, &identity.xclbin.uuid, false)
1326}
1327
1328fn validate_final_identity(identity: &FinalHardwareIdentity, metadata: &RunMetadata) -> Result<()> {
1329    validate_identity_fields(
1330        identity.schema,
1331        &identity.contract,
1332        FINAL_IDENTITY_CONTRACT,
1333        &identity.capture,
1334        &identity.pinned_target,
1335        &identity.device,
1336        &identity.platform,
1337        &identity.xrt_build,
1338        &identity.xclbin,
1339        identity.hardware_completion_promotable,
1340        metadata,
1341    )?;
1342    validate_programmed_image(&identity.programmed_image, &identity.xclbin.uuid, true)
1343}
1344
1345#[allow(clippy::too_many_arguments)]
1346fn validate_identity_fields(
1347    schema: u64,
1348    contract: &str,
1349    expected_contract: &str,
1350    capture: &model::HardwareIdentityCapture,
1351    pinned: &model::PinnedHardwareTarget,
1352    device: &model::HardwareDeviceIdentity,
1353    platform: &HardwarePlatformIdentity,
1354    xrt_build: &XrtBuildIdentity,
1355    xclbin: &model::HardwareXclbinIdentity,
1356    hardware_completion_promotable: bool,
1357    metadata: &RunMetadata,
1358) -> Result<()> {
1359    ensure!(schema == 1, "hardware identity schema must be 1");
1360    ensure!(
1361        contract == expected_contract,
1362        "hardware identity contract kind mismatch"
1363    );
1364    ensure!(
1365        capture.in_process_xrt_native_cpp && !capture.subprocess_or_sysfs_fallback_accepted,
1366        "hardware identity was not captured by the required in-process XRT API"
1367    );
1368    ensure!(
1369        pinned.device_name == EXPECTED_DEVICE_NAME
1370            && pinned.deployment_platform == EXPECTED_DEPLOYMENT_PLATFORM
1371            && pinned.fpga_part == EXPECTED_FPGA_PART
1372            && pinned.interface_uuid == EXPECTED_INTERFACE_UUID
1373            && pinned.logic_uuid == EXPECTED_LOGIC_UUID,
1374        "hardware identity pinned-target constants differ from the U280 contract"
1375    );
1376    ensure!(
1377        device.index == metadata.device_index,
1378        "hardware identity device index differs from run metadata"
1379    );
1380    ensure!(
1381        canonical_bdf(&device.bdf),
1382        "hardware identity BDF is not canonical"
1383    );
1384    ensure!(
1385        device.name == EXPECTED_DEVICE_NAME && device.interface_uuid == EXPECTED_INTERFACE_UUID,
1386        "typed hardware device identity differs from the pinned U280"
1387    );
1388    validate_platform_identity(platform)?;
1389    validate_xrt_build(xrt_build, true)?;
1390    validate_digest(&xclbin.digest, "hardware identity xclbin")?;
1391    ensure!(
1392        xclbin.path == metadata.xclbin.path && xclbin.digest == metadata.xclbin.digest,
1393        "hardware identity xclbin binding differs from run metadata"
1394    );
1395    ensure!(canonical_uuid(&xclbin.uuid), "xclbin UUID is not canonical");
1396    ensure!(
1397        xclbin.interface_uuid == EXPECTED_INTERFACE_UUID
1398            && xclbin.xsa_name == EXPECTED_DEPLOYMENT_PLATFORM
1399            && xclbin.fpga_part == EXPECTED_FPGA_PART
1400            && xclbin.target == "hw",
1401        "hardware identity xclbin target differs from the pinned U280 hardware image"
1402    );
1403    ensure!(
1404        !hardware_completion_promotable,
1405        "hardware identity makes a premature promotion claim"
1406    );
1407    Ok(())
1408}
1409
1410fn validate_programmed_image(
1411    image: &ProgrammedImageIdentity,
1412    xclbin_uuid: &str,
1413    final_record: bool,
1414) -> Result<()> {
1415    ensure!(
1416        canonical_uuid(&image.load_return_uuid)
1417            && canonical_uuid(&image.post_load_uuid)
1418            && image.load_return_uuid == xclbin_uuid
1419            && image.post_load_uuid == xclbin_uuid
1420            && image.load_post_load_equal,
1421        "load-return/post-load UUID continuity is invalid"
1422    );
1423    if final_record {
1424        ensure!(
1425            image.post_run_uuid.as_deref() == Some(xclbin_uuid)
1426                && image.load_post_load_post_run_equal == Some(true),
1427            "final programmed-image UUID continuity is invalid"
1428        );
1429    } else {
1430        ensure!(
1431            image.post_run_uuid.is_none() && image.load_post_load_post_run_equal.is_none(),
1432            "pre-run identity contains a post-run UUID decision"
1433        );
1434    }
1435    Ok(())
1436}
1437
1438fn parse_xrt_platform_report(
1439    snapshot: &ArchiveSnapshot,
1440    file: &str,
1441) -> Result<HardwarePlatformIdentity> {
1442    let bytes = read_verified_small(snapshot, file, MAXIMUM_PLATFORM_REPORT_BYTES)?;
1443    let report: XrtPlatformReport = parse_xrt_report_json(&bytes, file)?;
1444    ensure!(
1445        report.platforms.len() == 1,
1446        "XRT platform report must describe exactly one platform"
1447    );
1448    let platform = &report.platforms[0];
1449    let identity = HardwarePlatformIdentity {
1450        shell_vbnv: platform.static_region.vbnv.clone(),
1451        logic_uuid: platform.static_region.logic_uuid.to_ascii_lowercase(),
1452        fpga_part: platform.static_region.fpga_name.clone(),
1453        xmc_serial_number: platform
1454            .controller
1455            .card_mgmt_controller
1456            .serial_number
1457            .clone(),
1458    };
1459    validate_platform_identity(&identity)?;
1460    Ok(identity)
1461}
1462
1463fn validate_platform_identity(platform: &HardwarePlatformIdentity) -> Result<()> {
1464    ensure!(
1465        platform.shell_vbnv == EXPECTED_DEVICE_NAME
1466            && platform.logic_uuid == EXPECTED_LOGIC_UUID
1467            && platform.fpga_part == EXPECTED_FPGA_PART,
1468        "hardware platform identity differs from the pinned U280"
1469    );
1470    ensure!(
1471        canonical_serial(&platform.xmc_serial_number),
1472        "XMC serial number is blank, fallback, or noncanonical"
1473    );
1474    Ok(())
1475}
1476
1477fn validate_xrt_build(build: &XrtBuildIdentity, require_lower_hash: bool) -> Result<()> {
1478    let mut version = build.version.split('.');
1479    let version_valid = version.next() == Some("2")
1480        && version.next() == Some("16")
1481        && version.next().is_some_and(|patch| {
1482            !patch.is_empty() && patch.bytes().all(|byte| byte.is_ascii_digit())
1483        })
1484        && version.next().is_none();
1485    let hash_valid = (7..=64).contains(&build.hash.len())
1486        && build.hash.bytes().all(|byte| byte.is_ascii_hexdigit())
1487        && (!require_lower_hash
1488            || build
1489                .hash
1490                .bytes()
1491                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)));
1492    ensure!(
1493        version_valid
1494            && build.branch == "2023.2"
1495            && hash_valid
1496            && valid_xrt_build_date(&build.build_date),
1497        "XRT build identity is incomplete or is not the pinned 2023.2 ABI"
1498    );
1499    Ok(())
1500}
1501
1502fn valid_xrt_build_date(value: &str) -> bool {
1503    valid_calendar_timestamp(value.as_bytes(), b' ')
1504}
1505
1506fn valid_calendar_timestamp(bytes: &[u8], date_time_separator: u8) -> bool {
1507    if bytes.len() != 19
1508        || bytes[4] != b'-'
1509        || bytes[7] != b'-'
1510        || bytes[10] != date_time_separator
1511        || bytes[13] != b':'
1512        || bytes[16] != b':'
1513        || !bytes
1514            .iter()
1515            .enumerate()
1516            .all(|(index, byte)| matches!(index, 4 | 7 | 10 | 13 | 16) || byte.is_ascii_digit())
1517    {
1518        return false;
1519    }
1520    let decimal = |digits: &[u8]| {
1521        digits.iter().try_fold(0_u32, |value, byte| {
1522            value.checked_mul(10)?.checked_add(u32::from(*byte - b'0'))
1523        })
1524    };
1525    let (Some(year), Some(month), Some(day), Some(hour), Some(minute), Some(second)) = (
1526        decimal(&bytes[0..4]),
1527        decimal(&bytes[5..7]),
1528        decimal(&bytes[8..10]),
1529        decimal(&bytes[11..13]),
1530        decimal(&bytes[14..16]),
1531        decimal(&bytes[17..19]),
1532    ) else {
1533        return false;
1534    };
1535    let leap_year =
1536        year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400));
1537    let maximum_day = match month {
1538        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
1539        4 | 6 | 9 | 11 => 30,
1540        2 if leap_year => 29,
1541        2 => 28,
1542        _ => return false,
1543    };
1544    (2000..=2100).contains(&year)
1545        && (1..=maximum_day).contains(&day)
1546        && hour <= 23
1547        && minute <= 59
1548        && second <= 59
1549}
1550
1551fn canonical_uuid(value: &str) -> bool {
1552    let bytes = value.as_bytes();
1553    bytes.len() == 36
1554        && bytes.iter().enumerate().all(|(index, byte)| {
1555            if matches!(index, 8 | 13 | 18 | 23) {
1556                *byte == b'-'
1557            } else {
1558                byte.is_ascii_digit() || (b'a'..=b'f').contains(byte)
1559            }
1560        })
1561        && bytes
1562            .iter()
1563            .enumerate()
1564            .any(|(index, byte)| !matches!(index, 8 | 13 | 18 | 23) && *byte != b'0')
1565}
1566
1567fn canonical_bdf(value: &str) -> bool {
1568    let bytes = value.as_bytes();
1569    if bytes.len() != 12
1570        || bytes[4] != b':'
1571        || bytes[7] != b':'
1572        || bytes[10] != b'.'
1573        || !bytes[11].is_ascii_digit()
1574        || bytes[11] > b'7'
1575        || !bytes.iter().enumerate().all(|(index, byte)| {
1576            matches!(index, 4 | 7 | 10) || byte.is_ascii_digit() || (b'a'..=b'f').contains(byte)
1577        })
1578    {
1579        return false;
1580    }
1581    let hex_nibble = |byte: u8| match byte {
1582        b'0'..=b'9' => byte - b'0',
1583        b'a'..=b'f' => byte - b'a' + 10,
1584        _ => unreachable!("shape check admitted a non-hex BDF digit"),
1585    };
1586    let device = u16::from(hex_nibble(bytes[8])) * 16 + u16::from(hex_nibble(bytes[9]));
1587    device <= 0x1f
1588}
1589
1590fn canonical_serial(value: &str) -> bool {
1591    let lower = value.to_ascii_lowercase();
1592    !value.is_empty()
1593        && value.len() <= 128
1594        && !matches!(lower.as_str(), "n/a" | "na" | "unknown" | "none")
1595        && value.bytes().any(|byte| byte.is_ascii_alphanumeric())
1596        && value
1597            .bytes()
1598            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
1599}
1600
1601fn validate_results(
1602    results: &ResultsEnvelope,
1603    metadata: &RunMetadata,
1604    final_identity_record: &FileRecord,
1605    coordination: &ValidatedCoordination,
1606) -> Result<()> {
1607    ensure!(results.schema == 2, "results schema must be 2");
1608    ensure!(
1609        results.runner == "phase_b_hbm",
1610        "results runner kind mismatch"
1611    );
1612    validate_coordination_consumer_binding(
1613        &results.run_id,
1614        &results.coordination_intent,
1615        &results.coordination_burn,
1616        &results.coordination_receipt,
1617        coordination,
1618        "results",
1619    )?;
1620    ensure!(
1621        results.transport_valid,
1622        "host transport validation is false"
1623    );
1624    ensure!(
1625        results.cryptographic_oracle_valid.is_none(),
1626        "transport results pre-populated the Rust-oracle decision"
1627    );
1628    ensure!(
1629        !results.hardware_signature_evidence_complete,
1630        "transport results make a premature hardware-signature claim"
1631    );
1632    ensure!(
1633        !results.hardware_completion_promotable,
1634        "transport results make a premature hardware-completion claim"
1635    );
1636    validate_root_binding_against_record(
1637        &results.hardware_identity,
1638        FINAL_IDENTITY_FILE,
1639        final_identity_record,
1640        "results final hardware identity",
1641    )?;
1642    ensure!(
1643        results.clock == metadata.clock,
1644        "results clock binding differs from metadata"
1645    );
1646    ensure!(
1647        results.rate_policy == RATE_POLICY,
1648        "results rate policy changed"
1649    );
1650    ensure!(
1651        results
1652            .performance_acceptance
1653            .cryptographic_oracle_valid
1654            .is_none(),
1655        "performance acceptance pre-populated the oracle decision"
1656    );
1657    ensure!(
1658        !results.performance_acceptance.accepted && !results.performance_acceptance.promotable,
1659        "performance acceptance is prematurely promotable"
1660    );
1661    ensure!(
1662        results.batches.len() == metadata.batches.len(),
1663        "results batch list length differs from metadata"
1664    );
1665    for (value, expected_batch) in results.batches.iter().zip(&metadata.batches) {
1666        ensure!(
1667            value.batch == *expected_batch,
1668            "results batch order differs from metadata"
1669        );
1670        ensure!(
1671            value.measured_samples == metadata.measured_runs_per_batch,
1672            "results measured sample count differs from metadata"
1673        );
1674    }
1675    Ok(())
1676}
1677
1678fn nearest_rank(mut values: Vec<f64>, percentile_numerator: usize) -> Option<f64> {
1679    if values.is_empty() || !(1..=100).contains(&percentile_numerator) {
1680        return None;
1681    }
1682    values.sort_by(f64::total_cmp);
1683    let rank = values
1684        .len()
1685        .checked_mul(percentile_numerator)?
1686        .div_ceil(100);
1687    values.get(rank.checked_sub(1)?).copied()
1688}
1689
1690fn percentiles(values: &[f64]) -> Option<Percentiles> {
1691    Some(Percentiles {
1692        p50: nearest_rank(values.to_vec(), 50)?,
1693        p95: nearest_rank(values.to_vec(), 95)?,
1694        p99: nearest_rank(values.to_vec(), 99)?,
1695    })
1696}
1697
1698fn rate_percentiles(values: &[f64]) -> Option<RatePercentiles> {
1699    Some(RatePercentiles {
1700        p5_conservative: nearest_rank(values.to_vec(), 5)?,
1701        p50: nearest_rank(values.to_vec(), 50)?,
1702        p95_fast_tail: nearest_rank(values.to_vec(), 95)?,
1703    })
1704}
1705
1706// These values are serialized as human-readable floating-point rate reports;
1707// exact pass/fail promotion arithmetic belongs to the later hardware promoter.
1708#[allow(clippy::cast_precision_loss)]
1709fn u64_to_f64_for_reporting(value: u64) -> f64 {
1710    value as f64
1711}
1712
1713fn expected_batch_result(
1714    batch: u32,
1715    metrics: &[SampleMetrics],
1716    clock: &ClockBinding,
1717) -> Result<BatchResult> {
1718    let samples = metrics
1719        .iter()
1720        .copied()
1721        .filter(|sample| sample.batch == batch)
1722        .collect::<Vec<_>>();
1723    let mut whole_kernel_rates = Vec::new();
1724    let mut whole_kernel_nanoseconds = Vec::new();
1725    let mut memory_rates = Vec::new();
1726    let mut capture_rates = Vec::new();
1727    let mut memory_cycles = Vec::new();
1728    let mut capture_cycles = Vec::new();
1729    for sample in &samples {
1730        if batch > 0 {
1731            let kernel_nanoseconds = u64_to_f64_for_reporting(sample.kernel_nanoseconds);
1732            whole_kernel_rates.push(f64::from(batch) * 1.0e9 / kernel_nanoseconds);
1733            whole_kernel_nanoseconds.push(kernel_nanoseconds / f64::from(batch));
1734        }
1735        if batch >= 2 {
1736            let intervals = f64::from(batch - 1);
1737            let memory_span = u64_to_f64_for_reporting(sample.memory_span);
1738            let capture_span = u64_to_f64_for_reporting(sample.capture_span);
1739            memory_cycles.push(memory_span / intervals);
1740            capture_cycles.push(capture_span / intervals);
1741            if let Some(clock_hz) = clock.routed_clock_hz {
1742                ensure!(
1743                    sample.memory_span > 0 && sample.capture_span > 0,
1744                    "routed rate span is zero"
1745                );
1746                let clock_hz = u64_to_f64_for_reporting(clock_hz);
1747                memory_rates.push(intervals * clock_hz / memory_span);
1748                capture_rates.push(intervals * clock_hz / capture_span);
1749            }
1750        }
1751    }
1752    let memory_percentiles = percentiles(&memory_cycles);
1753    let capture_percentiles = percentiles(&capture_cycles);
1754    let routed_memory_rate_at_p95_cycles = match (&memory_percentiles, clock.routed_clock_hz) {
1755        (Some(value), Some(clock_hz)) => Some(u64_to_f64_for_reporting(clock_hz) / value.p95),
1756        _ => None,
1757    };
1758    let routed_capture_rate_at_p95_cycles = match (&capture_percentiles, clock.routed_clock_hz) {
1759        (Some(value), Some(clock_hz)) => Some(u64_to_f64_for_reporting(clock_hz) / value.p95),
1760        _ => None,
1761    };
1762    Ok(BatchResult {
1763        batch,
1764        measured_samples: samples.len(),
1765        whole_kernel_nanoseconds_per_result: percentiles(&whole_kernel_nanoseconds),
1766        whole_kernel_signatures_per_second: rate_percentiles(&whole_kernel_rates),
1767        memory_retirement_cycles_per_result: memory_percentiles,
1768        capture_core_cycles_per_result: capture_percentiles,
1769        routed_memory_retirement_signatures_per_second: rate_percentiles(&memory_rates),
1770        routed_memory_rate_at_p95_cycles,
1771        routed_capture_signatures_per_second: rate_percentiles(&capture_rates),
1772        routed_capture_rate_at_p95_cycles,
1773    })
1774}
1775
1776fn expected_performance_acceptance(
1777    metadata: &RunMetadata,
1778    metrics: &[SampleMetrics],
1779) -> Result<PerformanceAcceptance> {
1780    let required_batch_requested = metadata.batches.contains(&REQUIRED_PERFORMANCE_BATCH);
1781    let statistics = expected_batch_result(REQUIRED_PERFORMANCE_BATCH, metrics, &metadata.clock)?;
1782    let capture_p95 = statistics
1783        .capture_core_cycles_per_result
1784        .as_ref()
1785        .map(|value| value.p95);
1786    let capture_rate_at_bound =
1787        capture_p95.map(|cycles| u64_to_f64_for_reporting(PERFORMANCE_CLOCK_BOUND_HZ) / cycles);
1788    let whole_kernel_p95 = statistics
1789        .whole_kernel_nanoseconds_per_result
1790        .as_ref()
1791        .map(|value| value.p95);
1792    let whole_kernel_rate_at_p95 = whole_kernel_p95.map(|nanoseconds| 1.0e9 / nanoseconds);
1793    let capture_cycles_threshold_met =
1794        capture_p95.map(|cycles| cycles <= MAXIMUM_P95_CAPTURE_CYCLES);
1795    let capture_rate_threshold_met =
1796        capture_rate_at_bound.map(|rate| rate >= MINIMUM_SIGNATURES_PER_SECOND);
1797    let whole_kernel_rate_threshold_met =
1798        whole_kernel_rate_at_p95.map(|rate| rate >= MINIMUM_SIGNATURES_PER_SECOND);
1799    let sample_count_requirement_met = statistics.measured_samples >= REQUIRED_PERFORMANCE_SAMPLES;
1800    let threshold_observations_complete = required_batch_requested
1801        && sample_count_requirement_met
1802        && capture_p95.is_some()
1803        && capture_rate_at_bound.is_some()
1804        && whole_kernel_p95.is_some()
1805        && whole_kernel_rate_at_p95.is_some()
1806        && statistics.measured_samples
1807            == metrics
1808                .iter()
1809                .filter(|sample| sample.batch == REQUIRED_PERFORMANCE_BATCH)
1810                .count();
1811    let performance_thresholds_met = threshold_observations_complete
1812        && capture_cycles_threshold_met == Some(true)
1813        && capture_rate_threshold_met == Some(true)
1814        && whole_kernel_rate_threshold_met == Some(true);
1815    Ok(PerformanceAcceptance {
1816        required_positive_large_batch: REQUIRED_PERFORMANCE_BATCH,
1817        required_batch_requested,
1818        required_measured_samples: REQUIRED_PERFORMANCE_SAMPLES,
1819        observed_measured_samples: statistics.measured_samples,
1820        sample_count_requirement_met,
1821        policy_basis: PerformancePolicyBasis {
1822            clock_bound_hz: PERFORMANCE_CLOCK_BOUND_HZ,
1823            maximum_p95_capture_core_cycles_per_result: MAXIMUM_P95_CAPTURE_CYCLES,
1824            minimum_signatures_per_second: MINIMUM_SIGNATURES_PER_SECOND,
1825            clock_bound_is_arithmetic_normalization_not_routed_clock_evidence: true,
1826        },
1827        capture_core: CaptureCoreAcceptance {
1828            p95_cycles_per_result: capture_p95,
1829            rate_at_p95_cycles: capture_rate_at_bound,
1830            cycles_threshold_met: capture_cycles_threshold_met,
1831            rate_threshold_met: capture_rate_threshold_met,
1832        },
1833        whole_kernel: WholeKernelAcceptance {
1834            p95_nanoseconds_per_result: whole_kernel_p95,
1835            rate_at_p95_elapsed: whole_kernel_rate_at_p95,
1836            rate_threshold_met: whole_kernel_rate_threshold_met,
1837        },
1838        threshold_observations_complete,
1839        performance_thresholds_met,
1840        cryptographic_oracle_valid: None,
1841        accepted: false,
1842        promotable: false,
1843        blocking_reason: PERFORMANCE_BLOCKING_REASON.to_owned(),
1844    })
1845}
1846
1847fn validate_results_statistics(
1848    results: &ResultsEnvelope,
1849    metadata: &RunMetadata,
1850    metrics: &[SampleMetrics],
1851) -> Result<()> {
1852    let expected_batches = metadata
1853        .batches
1854        .iter()
1855        .map(|&batch| expected_batch_result(batch, metrics, &metadata.clock))
1856        .collect::<Result<Vec<_>>>()?;
1857    ensure!(
1858        results.batches == expected_batches,
1859        "results batch statistics differ from sealed sample timing and summaries"
1860    );
1861    let expected_acceptance = expected_performance_acceptance(metadata, metrics)?;
1862    ensure!(
1863        results.performance_acceptance == expected_acceptance,
1864        "performance acceptance differs from the exact Phase-B policy recomputation"
1865    );
1866    Ok(())
1867}
1868
1869fn validate_digest(digest: &DigestBinding, label: &str) -> Result<()> {
1870    ensure!(digest.bytes > 0, "{label} byte count must be positive");
1871    ensure!(
1872        is_lower_hex(&digest.sha256, 64),
1873        "{label} SHA-256 is not canonical"
1874    );
1875    Ok(())
1876}
1877
1878fn validate_clock(clock: &ClockBinding) -> Result<()> {
1879    if let Some(requested) = clock.requested_clock_hz_metadata {
1880        ensure!(requested > 0, "requested clock metadata must be positive");
1881    }
1882    ensure!(
1883        clock.routed_clock_hz.is_some() == clock.routed_clock_evidence.is_some(),
1884        "routed clock and timing evidence must appear together"
1885    );
1886    if let Some(routed) = clock.routed_clock_hz {
1887        ensure!(routed > 0, "routed clock must be positive");
1888    }
1889    if let Some(evidence) = &clock.routed_clock_evidence {
1890        ensure!(
1891            !evidence.path.is_empty(),
1892            "routed-clock evidence path is empty"
1893        );
1894        validate_digest(&evidence.digest, "routed-clock evidence")?;
1895    }
1896    ensure!(
1897        clock.supports_hardware_rate_claim == clock.routed_clock_hz.is_some(),
1898        "clock rate-claim flag does not match its evidence"
1899    );
1900    Ok(())
1901}
1902
1903fn expected_layout(snapshot: &ArchiveSnapshot, metadata: &RunMetadata) -> Result<Vec<SampleSpec>> {
1904    let mut expected_files = BTreeSet::from([
1905        COORDINATION_BURN_FILE.to_owned(),
1906        COORDINATION_INTENT_FILE.to_owned(),
1907        COORDINATION_RECEIPT_FILE.to_owned(),
1908        FINAL_IDENTITY_FILE.to_owned(),
1909        PRE_RUN_IDENTITY_FILE.to_owned(),
1910        "results.json".to_owned(),
1911        "run-metadata.json".to_owned(),
1912        PLATFORM_POST_RUN_FILE.to_owned(),
1913        PLATFORM_PRE_LOAD_FILE.to_owned(),
1914        XRT_HOST_FILE.to_owned(),
1915    ]);
1916    let mut expected_directories = BTreeSet::from(["samples".to_owned()]);
1917    let mut samples = Vec::new();
1918    for &batch in &metadata.batches {
1919        let batch_directory = format!("samples/batch-{batch:05}");
1920        expected_directories.insert(batch_directory.clone());
1921        for (phase, count) in [
1922            ("warmup", metadata.warmups_per_batch),
1923            ("measured", metadata.measured_runs_per_batch),
1924        ] {
1925            for index in 0..count {
1926                let relative = format!("{batch_directory}/{phase}-{index:03}");
1927                expected_directories.insert(relative.clone());
1928                for file in SAMPLE_FILES {
1929                    expected_files.insert(format!("{relative}/{file}"));
1930                }
1931                samples.push(SampleSpec {
1932                    relative,
1933                    batch,
1934                    phase,
1935                    index,
1936                });
1937            }
1938        }
1939    }
1940    ensure!(
1941        snapshot.files.keys().cloned().collect::<BTreeSet<_>>() == expected_files,
1942        "successful transport archive has missing or extra files"
1943    );
1944    ensure!(
1945        snapshot.directories == expected_directories,
1946        "successful transport archive has missing, extra, or empty directories"
1947    );
1948    ensure!(
1949        !samples.is_empty(),
1950        "transport archive has no oracle requests"
1951    );
1952    Ok(samples)
1953}
1954
1955struct ValidatedSample {
1956    request: OracleRequest,
1957    request_sha256: String,
1958    metrics: SampleMetrics,
1959}
1960
1961struct SampleValidationState<'a> {
1962    metadata: &'a RunMetadata,
1963    pre_run_identity_record: &'a FileRecord,
1964    coordination: &'a ValidatedCoordination,
1965    reservation_ids: HashSet<String>,
1966    nonces: HashSet<String>,
1967    reservation_chain: Option<(u64, String)>,
1968}
1969
1970fn validate_sample_contract(
1971    snapshot: &ArchiveSnapshot,
1972    sample: &SampleSpec,
1973    state: &mut SampleValidationState<'_>,
1974) -> Result<ValidatedSample> {
1975    let (request, request_sha256) = validate_sample_request(
1976        snapshot,
1977        sample,
1978        state.metadata,
1979        state.pre_run_identity_record,
1980        state.coordination,
1981    )?;
1982    validate_request_artifacts(snapshot, sample, &request)?;
1983    validate_sample_reservation(
1984        snapshot,
1985        sample,
1986        &request,
1987        &mut state.reservation_ids,
1988        &mut state.nonces,
1989        &mut state.reservation_chain,
1990    )?;
1991    let metrics = validate_transport_artifacts(snapshot, sample, &request)?;
1992    Ok(ValidatedSample {
1993        request,
1994        request_sha256,
1995        metrics,
1996    })
1997}
1998
1999fn validate_sample_request(
2000    snapshot: &ArchiveSnapshot,
2001    sample: &SampleSpec,
2002    metadata: &RunMetadata,
2003    pre_run_identity_record: &FileRecord,
2004    coordination: &ValidatedCoordination,
2005) -> Result<(OracleRequest, String)> {
2006    let request_path = sample_file(sample, "oracle-request.json");
2007    let request_bytes = read_verified_small(snapshot, &request_path, MAXIMUM_JSON_BYTES)?;
2008    let request_sha256 = sha256_bytes(&request_bytes);
2009    let request: OracleRequest = parse_strict_json(&request_bytes, &request_path)?;
2010    let sidecar_path = sample_file(sample, "oracle-request.sha256");
2011    let sidecar = read_verified_small(snapshot, &sidecar_path, 256)?;
2012    let expected_sidecar = format!("{request_sha256}  oracle-request.json\n");
2013    ensure!(
2014        sidecar == expected_sidecar.as_bytes(),
2015        "oracle-request sidecar mismatch"
2016    );
2017
2018    ensure!(request.schema == 2, "oracle request schema must be 2");
2019    ensure!(request.kind == REQUEST_KIND, "oracle request kind mismatch");
2020    validate_coordination_consumer_binding(
2021        &request.run_id,
2022        &request.coordination_intent,
2023        &request.coordination_burn,
2024        &request.coordination_receipt,
2025        coordination,
2026        &format!("oracle request {request_path}"),
2027    )?;
2028    ensure!(
2029        request.profile == PROFILE,
2030        "oracle request profile mismatch"
2031    );
2032    ensure!(
2033        request.batch == sample.batch,
2034        "request batch does not match sample path"
2035    );
2036    ensure!(
2037        is_lower_hex(&request.nonce_hex, 16),
2038        "request nonce is not canonical 64-bit hex"
2039    );
2040    ensure!(
2041        u64::from_str_radix(&request.nonce_hex, 16)? == request.nonce_u64,
2042        "request nonce hex and integer differ"
2043    );
2044    ensure!(
2045        is_lower_hex(&request.reservation_record_sha256, 64),
2046        "request reservation digest is not canonical"
2047    );
2048    ensure!(
2049        request.xclbin.path == metadata.xclbin.path
2050            && request.xclbin.bytes == metadata.xclbin.digest.bytes
2051            && request.xclbin.sha256 == metadata.xclbin.digest.sha256,
2052        "request xclbin binding differs from run metadata"
2053    );
2054    ensure!(
2055        request.clock == metadata.clock,
2056        "request clock binding differs from metadata"
2057    );
2058    validate_root_binding_against_record(
2059        &request.hardware_identity_pre_run,
2060        PRE_RUN_IDENTITY_FILE,
2061        pre_run_identity_record,
2062        "oracle request pre-run hardware identity",
2063    )?;
2064    ensure!(
2065        request.cryptographic_oracle_valid.is_none(),
2066        "request pre-populated the Rust-oracle decision"
2067    );
2068    ensure!(
2069        !request.hardware_completion_promotable,
2070        "request is prematurely hardware-promotable"
2071    );
2072    let must_bind = request
2073        .required_oracle_result
2074        .must_bind
2075        .iter()
2076        .map(String::as_str)
2077        .collect::<Vec<_>>();
2078    ensure!(
2079        request.required_oracle_result.producer == "independent Rust oracle"
2080            && must_bind.len() == 7
2081            && must_bind[0] == "oracle-request.json sha256"
2082            && must_bind[1] == "coordination-intent.json sha256"
2083            && must_bind[2] == "coordination-burn.json sha256"
2084            && must_bind[3] == "coordination-receipt.json sha256"
2085            && must_bind[4] == "hardware-identity-pre-run.json sha256"
2086            && must_bind[5] == "top-level SHA256SUMS sha256"
2087            && must_bind[6] == "every input/output job"
2088            && request
2089                .required_oracle_result
2090                .must_not_mutate_transport_archive,
2091        "request's required-oracle contract changed"
2092    );
2093    Ok((request, request_sha256))
2094}
2095
2096fn validate_request_artifacts(
2097    snapshot: &ArchiveSnapshot,
2098    sample: &SampleSpec,
2099    request: &OracleRequest,
2100) -> Result<()> {
2101    let logical_input = u64::from(sample.batch)
2102        .checked_mul(INPUT_BYTES_PER_JOB)
2103        .context("input byte count overflow")?;
2104    let logical_output = u64::from(sample.batch)
2105        .checked_mul(OUTPUT_SLOT_BYTES)
2106        .context("output byte count overflow")?;
2107    let output_allocation = if sample.batch == 0 {
2108        OUTPUT_SLOT_BYTES
2109    } else {
2110        logical_output
2111    };
2112    validate_artifact(
2113        snapshot,
2114        sample,
2115        &request.artifacts.input,
2116        "input.bin",
2117        logical_input,
2118        logical_input,
2119    )?;
2120    validate_artifact(
2121        snapshot,
2122        sample,
2123        &request.artifacts.full_output_allocation,
2124        "output.bin",
2125        output_allocation,
2126        output_allocation,
2127    )?;
2128    validate_artifact(
2129        snapshot,
2130        sample,
2131        &request.artifacts.summary,
2132        "summary.bin",
2133        SUMMARY_BYTES,
2134        SUMMARY_BYTES,
2135    )?;
2136    for (artifact, name) in [
2137        (&request.artifacts.diagnostic_registers, "diagnostics.json"),
2138        (
2139            &request.artifacts.untimed_transport_validation,
2140            "validation.json",
2141        ),
2142        (&request.artifacts.timing, "timing.json"),
2143        (&request.artifacts.transport_contract, "transport.json"),
2144    ] {
2145        let bytes = snapshot
2146            .files
2147            .get(&sample_file(sample, name))
2148            .with_context(|| format!("manifest omits {name}"))?
2149            .bytes;
2150        validate_artifact(snapshot, sample, artifact, name, bytes, bytes)?;
2151    }
2152    ensure!(
2153        request.output_contract.slot_bytes == OUTPUT_SLOT_BYTES
2154            && request.output_contract.valid_bytes_per_job == OUTPUT_VALID_BYTES
2155            && request.output_contract.padding_bytes_per_job == OUTPUT_PADDING_BYTES
2156            && request.output_contract.requested_output_slot_bytes == logical_output
2157            && request.output_contract.full_output_allocation_bytes == output_allocation,
2158        "request output contract has the wrong geometry"
2159    );
2160    Ok(())
2161}
2162
2163fn validate_sample_reservation(
2164    snapshot: &ArchiveSnapshot,
2165    sample: &SampleSpec,
2166    request: &OracleRequest,
2167    reservation_ids: &mut HashSet<String>,
2168    nonces: &mut HashSet<String>,
2169    reservation_chain: &mut Option<(u64, String)>,
2170) -> Result<()> {
2171    let receipt_path = sample_file(sample, "reservation.json");
2172    let receipt_bytes = read_verified_small(snapshot, &receipt_path, MAXIMUM_JSON_BYTES)?;
2173    let receipt: ReservationReceipt = parse_strict_json(&receipt_bytes, &receipt_path)?;
2174    validate_reservation(&receipt, &receipt_bytes, request)?;
2175    advance_reservation_chain(reservation_chain, &receipt)?;
2176    ensure!(
2177        reservation_ids.insert(receipt.reservation_id),
2178        "transport archive repeats a reservation identity"
2179    );
2180    ensure!(
2181        nonces.insert(receipt.nonce),
2182        "transport archive repeats a Phase-B nonce"
2183    );
2184    Ok(())
2185}
2186
2187fn validate_artifact(
2188    snapshot: &ArchiveSnapshot,
2189    sample: &SampleSpec,
2190    artifact: &ArtifactBinding,
2191    expected_file: &str,
2192    expected_logical: u64,
2193    expected_stored: u64,
2194) -> Result<()> {
2195    ensure!(
2196        artifact.file == expected_file,
2197        "oracle artifact filename mismatch"
2198    );
2199    ensure!(
2200        artifact.logical_bytes == expected_logical && artifact.stored_bytes == expected_stored,
2201        "oracle artifact byte geometry mismatch for {expected_file}"
2202    );
2203    ensure!(
2204        is_lower_hex(&artifact.sha256, 64),
2205        "oracle artifact SHA-256 is not canonical"
2206    );
2207    let sealed = snapshot
2208        .files
2209        .get(&sample_file(sample, expected_file))
2210        .with_context(|| format!("manifest omits {expected_file}"))?;
2211    ensure!(
2212        sealed.bytes == artifact.stored_bytes && sealed.sha256 == artifact.sha256,
2213        "oracle artifact binding differs from top manifest for {expected_file}"
2214    );
2215    Ok(())
2216}
2217
2218fn validate_reservation(
2219    receipt: &ReservationReceipt,
2220    raw: &[u8],
2221    request: &OracleRequest,
2222) -> Result<()> {
2223    ensure!(receipt.schema == 1, "reservation schema must be 1");
2224    ensure!(receipt.phase == "phase_b", "reservation is not Phase B");
2225    ensure!(
2226        is_lower_hex(&receipt.reservation_id, 32),
2227        "reservation ID is malformed"
2228    );
2229    ensure!(
2230        receipt.nonce == request.nonce_hex,
2231        "reservation nonce differs from request"
2232    );
2233    ensure!(
2234        receipt.base.is_none(),
2235        "Phase-B reservation unexpectedly has a range base"
2236    );
2237    ensure!(
2238        receipt.batch == request.batch,
2239        "reservation batch differs from request"
2240    );
2241    ensure!(
2242        receipt.input_bytes == request.artifacts.input.stored_bytes,
2243        "reservation input length differs from request"
2244    );
2245    ensure!(
2246        receipt.input_sha256.as_deref() == Some(request.artifacts.input.sha256.as_str()),
2247        "reservation input digest differs from request"
2248    );
2249    ensure!(
2250        receipt.seed_count == request.batch,
2251        "reservation seed count differs from batch"
2252    );
2253    ensure!(
2254        is_lower_hex(&receipt.previous_sha256, 64),
2255        "reservation prior hash is malformed"
2256    );
2257    ensure!(
2258        receipt.record_sha256 == request.reservation_record_sha256,
2259        "reservation record digest differs from request"
2260    );
2261    let body = format!(
2262        "{{\"schema\":1,\"sequence\":{},\"phase\":\"phase_b\",\"reservation_id\":\"{}\",\"nonce\":\"{}\",\"base\":null,\"batch\":{},\"input_bytes\":{},\"input_sha256\":\"{}\",\"seed_count\":{},\"previous_sha256\":\"{}\"}}",
2263        receipt.sequence,
2264        receipt.reservation_id,
2265        receipt.nonce,
2266        receipt.batch,
2267        receipt.input_bytes,
2268        receipt.input_sha256.as_deref().expect("validated Some"),
2269        receipt.seed_count,
2270        receipt.previous_sha256,
2271    );
2272    ensure!(
2273        sha256_bytes(body.as_bytes()) == receipt.record_sha256,
2274        "reservation chain hash mismatch"
2275    );
2276    let mut complete = body;
2277    complete.pop();
2278    complete.push_str(",\"record_sha256\":\"");
2279    complete.push_str(&receipt.record_sha256);
2280    complete.push_str("\"}\n");
2281    ensure!(
2282        raw == complete.as_bytes(),
2283        "reservation receipt is not canonical JSONL"
2284    );
2285    Ok(())
2286}
2287
2288fn advance_reservation_chain(
2289    chain: &mut Option<(u64, String)>,
2290    receipt: &ReservationReceipt,
2291) -> Result<()> {
2292    if let Some((previous_sequence, previous_digest)) = chain.as_ref() {
2293        ensure!(
2294            receipt.sequence
2295                == previous_sequence
2296                    .checked_add(1)
2297                    .context("reservation sequence overflow")?,
2298            "Phase-B reservation sequence is not contiguous in execution order"
2299        );
2300        ensure!(
2301            &receipt.previous_sha256 == previous_digest,
2302            "Phase-B reservation previous digest does not bind the prior sample"
2303        );
2304    }
2305    *chain = Some((receipt.sequence, receipt.record_sha256.clone()));
2306    Ok(())
2307}
2308
2309fn validate_transport_artifacts(
2310    snapshot: &ArchiveSnapshot,
2311    sample: &SampleSpec,
2312    request: &OracleRequest,
2313) -> Result<SampleMetrics> {
2314    let validation: ValidationRecord =
2315        parse_archive_json(snapshot, &sample_file(sample, "validation.json"))?;
2316    ensure!(
2317        validation.schema == 1
2318            && validation.transport_valid
2319            && validation.validated_after_timing
2320            && validation.cryptographic_oracle_valid.is_none()
2321            && validation.external_rust_oracle_required,
2322        "untimed transport validation is not a strict successful handoff"
2323    );
2324
2325    let transport: TransportContract =
2326        parse_archive_json(snapshot, &sample_file(sample, "transport.json"))?;
2327    validate_transport_contract(&transport, request)?;
2328    let diagnostics: Diagnostics =
2329        parse_archive_json(snapshot, &sample_file(sample, "diagnostics.json"))?;
2330    validate_diagnostics(&diagnostics, request, &transport)?;
2331    let timing: TimingRecord = parse_archive_json(snapshot, &sample_file(sample, "timing.json"))?;
2332    validate_timing(&timing)?;
2333    let summary_bytes =
2334        read_verified_small(snapshot, &sample_file(sample, "summary.bin"), SUMMARY_BYTES)?;
2335    ensure!(
2336        summary_bytes.len() == usize::try_from(SUMMARY_BYTES)?,
2337        "summary size mismatch"
2338    );
2339    let summary = decode_summary(&summary_bytes);
2340    let decoded: SummaryDecoded =
2341        parse_archive_json(snapshot, &sample_file(sample, "summary-decoded.json"))?;
2342    ensure!(
2343        summary == decoded,
2344        "decoded summary JSON differs from summary.bin"
2345    );
2346    validate_summary(&summary, &diagnostics, request)?;
2347    Ok(SampleMetrics {
2348        batch: request.batch,
2349        kernel_nanoseconds: timing.kernel_nanoseconds,
2350        memory_span: diagnostics.memory_span.decimal,
2351        capture_span: summary.capture_span_cycles,
2352    })
2353}
2354
2355fn validate_transport_contract(
2356    transport: &TransportContract,
2357    request: &OracleRequest,
2358) -> Result<()> {
2359    let logical_input = u64::from(request.batch) * INPUT_BYTES_PER_JOB;
2360    let logical_output = u64::from(request.batch) * OUTPUT_SLOT_BYTES;
2361    let input_allocation = if request.batch == 0 {
2362        INPUT_BYTES_PER_JOB
2363    } else {
2364        logical_input
2365    };
2366    let output_allocation = if request.batch == 0 {
2367        OUTPUT_SLOT_BYTES
2368    } else {
2369        logical_output
2370    };
2371    ensure!(
2372        transport.schema == 1 && transport.batch == request.batch,
2373        "transport schema/batch mismatch"
2374    );
2375    ensure!(
2376        transport.abi_word == "0x48530001",
2377        "transport ABI word mismatch"
2378    );
2379    let argument_order = transport
2380        .kernel_argument_order
2381        .iter()
2382        .map(String::as_str)
2383        .collect::<Vec<_>>();
2384    ensure!(
2385        argument_order.len() == 6
2386            && argument_order[0] == "inputs"
2387            && argument_order[1] == "outputs"
2388            && argument_order[2] == "summary"
2389            && argument_order[3] == "batch"
2390            && argument_order[4] == "abi_word"
2391            && argument_order[5] == "nonce",
2392        "kernel argument order mismatch"
2393    );
2394    ensure!(
2395        transport.group_ids.inputs >= 0
2396            && transport.group_ids.outputs >= 0
2397            && transport.group_ids.summary >= 0
2398            && transport.group_ids.inputs != transport.group_ids.outputs
2399            && transport.group_ids.outputs == transport.group_ids.summary,
2400        "HBM group contract mismatch"
2401    );
2402    ensure!(
2403        transport.logical_bytes.inputs == logical_input
2404            && transport.logical_bytes.outputs == logical_output
2405            && transport.logical_bytes.summary == SUMMARY_BYTES
2406            && transport.bo_allocation_bytes.inputs == input_allocation
2407            && transport.bo_allocation_bytes.outputs == output_allocation
2408            && transport.bo_allocation_bytes.summary == SUMMARY_BYTES,
2409        "transport byte geometry mismatch"
2410    );
2411    ensure!(
2412        transport.zero_batch_sentinel == (request.batch == 0),
2413        "zero-batch sentinel flag mismatch"
2414    );
2415    ensure!(
2416        transport.reservation_record_sha256 == request.reservation_record_sha256,
2417        "transport reservation binding differs from request"
2418    );
2419    ensure!(
2420        transport.poison.output == OUTPUT_POISON && transport.poison.summary == SUMMARY_POISON,
2421        "transport poison contract mismatch"
2422    );
2423    Ok(())
2424}
2425
2426fn validate_timing(timing: &TimingRecord) -> Result<()> {
2427    ensure!(timing.schema == 1, "timing schema must be 1");
2428    ensure!(
2429        timing.timed_region == TIMED_REGION,
2430        "timed-region label mismatch"
2431    );
2432    // Strict deserialization requires these telemetry fields to be present,
2433    // but schema 1 does not require their out-of-kernel durations to be nonzero.
2434    #[allow(clippy::no_effect_underscore_binding)]
2435    let _outside_timed_region = (
2436        timing.host_to_device_nanoseconds,
2437        timing.device_to_host_nanoseconds,
2438        timing.diagnostic_read_nanoseconds,
2439    );
2440    ensure!(
2441        timing.kernel_nanoseconds > 0,
2442        "kernel timing must be positive"
2443    );
2444    ensure!(
2445        timing.completion_state == 4,
2446        "XRT completion state is not COMPLETED"
2447    );
2448    Ok(())
2449}
2450
2451fn validate_diagnostics(
2452    diagnostics: &Diagnostics,
2453    request: &OracleRequest,
2454    transport: &TransportContract,
2455) -> Result<()> {
2456    ensure!(diagnostics.schema == 1, "diagnostics schema must be 1");
2457    for word in [
2458        &diagnostics.ap_ctrl,
2459        &diagnostics.gie,
2460        &diagnostics.ier,
2461        &diagnostics.isr,
2462        &diagnostics.batch,
2463        &diagnostics.abi_word,
2464        &diagnostics.last_status,
2465        &diagnostics.progress0,
2466        &diagnostics.progress1,
2467        &diagnostics.queue_status,
2468        &diagnostics.first_error_job,
2469        &diagnostics.first_error_detail,
2470    ] {
2471        validate_word32(word)?;
2472    }
2473    for word in [
2474        &diagnostics.input_base,
2475        &diagnostics.output_base,
2476        &diagnostics.summary_base,
2477        &diagnostics.nonce,
2478        &diagnostics.output_stall,
2479        &diagnostics.memory_first,
2480        &diagnostics.memory_last,
2481        &diagnostics.memory_span,
2482    ] {
2483        validate_word64(word)?;
2484    }
2485    let batch = request.batch;
2486    ensure!(
2487        diagnostics.last_status.decimal == LAST_STATUS_SUCCESS
2488            && diagnostics.ap_ctrl.decimal & 0x0c == 0x0c
2489            && diagnostics.batch.decimal == batch
2490            && diagnostics.abi_word.decimal == ABI_WORD
2491            && diagnostics.nonce.decimal == request.nonce_u64
2492            && diagnostics.progress0.decimal & 0xffff == batch
2493            && diagnostics.progress0.decimal >> 16 == batch
2494            && diagnostics.progress1.decimal & 0xffff == batch
2495            && diagnostics.progress1.decimal >> 16 == 0
2496            && diagnostics.queue_status.decimal == 0
2497            && diagnostics.first_error_job.decimal == u32::MAX
2498            && diagnostics.first_error_detail.decimal == 0,
2499        "terminal diagnostic contract mismatch"
2500    );
2501    let output_end = diagnostics
2502        .output_base
2503        .decimal
2504        .checked_add(transport.bo_allocation_bytes.outputs)
2505        .context("output BO address overflow")?;
2506    let summary_end = diagnostics
2507        .summary_base
2508        .decimal
2509        .checked_add(SUMMARY_BYTES)
2510        .context("summary BO address overflow")?;
2511    ensure!(
2512        output_end <= diagnostics.summary_base.decimal
2513            || summary_end <= diagnostics.output_base.decimal,
2514        "output and summary BO ranges overlap"
2515    );
2516    Ok(())
2517}
2518
2519fn validate_word32(word: &Word32) -> Result<()> {
2520    ensure!(
2521        word.hex == format!("0x{:08x}", word.decimal),
2522        "u32 diagnostic hex mismatch"
2523    );
2524    Ok(())
2525}
2526
2527fn validate_word64(word: &Word64) -> Result<()> {
2528    ensure!(
2529        word.hex == format!("0x{:016x}", word.decimal),
2530        "u64 diagnostic hex mismatch"
2531    );
2532    Ok(())
2533}
2534
2535fn decode_summary(bytes: &[u8]) -> SummaryDecoded {
2536    SummaryDecoded {
2537        schema: 1,
2538        magic: format!("0x{:08x}", read_le_u32(bytes, 0)),
2539        revision: read_le_u16(bytes, 4),
2540        profile: bytes[6],
2541        terminal: bytes[7],
2542        nonce: read_le_u64(bytes, 8),
2543        payload_interval_cycles: read_le_u64(bytes, 16),
2544        core_latency_cycles: read_le_u64(bytes, 24),
2545        capture_span_cycles: read_le_u64(bytes, 32),
2546        batch: read_le_u16(bytes, 40),
2547        input_ar: read_le_u16(bytes, 42),
2548        input_r: read_le_u16(bytes, 44),
2549        admissions: read_le_u16(bytes, 46),
2550        frames: read_le_u16(bytes, 48),
2551        signer_errors: read_le_u16(bytes, 50),
2552        payload_aw: read_le_u16(bytes, 52),
2553        payload_b: read_le_u16(bytes, 54),
2554        payload_w: read_le_u32(bytes, 56),
2555        memory_completed: read_le_u16(bytes, 60),
2556        error_flags: read_le_u16(bytes, 62),
2557    }
2558}
2559
2560fn read_le_u16(bytes: &[u8], offset: usize) -> u16 {
2561    u16::from_le_bytes(
2562        bytes[offset..offset + 2]
2563            .try_into()
2564            .expect("fixed summary span"),
2565    )
2566}
2567
2568fn read_le_u32(bytes: &[u8], offset: usize) -> u32 {
2569    u32::from_le_bytes(
2570        bytes[offset..offset + 4]
2571            .try_into()
2572            .expect("fixed summary span"),
2573    )
2574}
2575
2576fn read_le_u64(bytes: &[u8], offset: usize) -> u64 {
2577    u64::from_le_bytes(
2578        bytes[offset..offset + 8]
2579            .try_into()
2580            .expect("fixed summary span"),
2581    )
2582}
2583
2584fn validate_summary(
2585    summary: &SummaryDecoded,
2586    diagnostics: &Diagnostics,
2587    request: &OracleRequest,
2588) -> Result<()> {
2589    let batch16 = u16::try_from(request.batch).context("batch exceeds summary width")?;
2590    ensure!(
2591        summary.schema == 1
2592            && summary.magic == "0x31525348"
2593            && summary.revision == 1
2594            && summary.profile == 1
2595            && summary.terminal == 0
2596            && summary.nonce == request.nonce_u64
2597            && summary.batch == batch16
2598            && summary.input_ar == batch16
2599            && summary.input_r == batch16
2600            && summary.admissions == batch16
2601            && summary.frames == batch16
2602            && summary.signer_errors == 0
2603            && summary.payload_aw == batch16
2604            && summary.payload_b == batch16
2605            && summary.payload_w == 35 * request.batch
2606            && summary.memory_completed == batch16
2607            && summary.error_flags == 0,
2608        "HSR1 summary/counter contract mismatch"
2609    );
2610    match request.batch {
2611        0 => ensure!(
2612            summary.payload_interval_cycles == 1
2613                && summary.core_latency_cycles == 0
2614                && summary.capture_span_cycles == 0
2615                && diagnostics.memory_first.decimal == 0
2616                && diagnostics.memory_last.decimal == 0
2617                && diagnostics.memory_span.decimal == 0,
2618            "zero-batch timing contract mismatch"
2619        ),
2620        1 => ensure!(
2621            summary.payload_interval_cycles > 0
2622                && summary.core_latency_cycles > 0
2623                && summary.capture_span_cycles == 0
2624                && diagnostics.memory_span.decimal == 0
2625                && diagnostics.memory_first.decimal == diagnostics.memory_last.decimal,
2626            "singleton timing contract mismatch"
2627        ),
2628        _ => ensure!(
2629            summary.payload_interval_cycles > 0
2630                && summary.core_latency_cycles > 0
2631                && summary.capture_span_cycles > 0
2632                && diagnostics.memory_span.decimal > 0
2633                && diagnostics.memory_last.decimal >= diagnostics.memory_first.decimal
2634                && diagnostics.memory_span.decimal
2635                    == diagnostics.memory_last.decimal - diagnostics.memory_first.decimal,
2636            "sustained timing contract mismatch"
2637        ),
2638    }
2639    Ok(())
2640}
2641
2642fn compute_sample_jobs(
2643    snapshot: &ArchiveSnapshot,
2644    sample: &SampleSpec,
2645    threads: usize,
2646) -> Result<Vec<JobComputation>> {
2647    if sample.batch == 0 {
2648        let output = open_verified_file(snapshot, &sample_file(sample, "output.bin"))?;
2649        let mut sentinel = [0_u8; OUTPUT_SLOT_BYTES_USIZE];
2650        output.read_exact_at(&mut sentinel, 0)?;
2651        output.finish()?;
2652        ensure!(
2653            sentinel.iter().enumerate().all(|(offset, byte)| {
2654                let offset = u64::try_from(offset).expect("output slot offset fits in u64");
2655                *byte == output_poison(offset)
2656            }),
2657            "zero-batch output sentinel changed"
2658        );
2659        return Ok(Vec::new());
2660    }
2661    let jobs = usize::try_from(sample.batch)?;
2662    let workers = threads.min(jobs);
2663    let chunk = jobs.div_ceil(workers);
2664    let input_relative = sample_file(sample, "input.bin");
2665    let output_relative = sample_file(sample, "output.bin");
2666    let sample_relative = sample.relative.clone();
2667    // Each large artifact is fully manifest-bound exactly once before any
2668    // worker can read it.  FileExt positional reads safely share these retained
2669    // descriptors without reopening an attacker-substitutable pathname.
2670    let input_file = open_verified_file(snapshot, &input_relative)?;
2671    let output_file = open_verified_file(snapshot, &output_relative)?;
2672    let mut ranges = Vec::new();
2673    let mut start = 0_usize;
2674    while start < jobs {
2675        let end = (start + chunk).min(jobs);
2676        ranges.push((start, end));
2677        start = end;
2678    }
2679
2680    let computation = thread::scope(|scope| -> Result<Vec<JobComputation>> {
2681        let mut handles = Vec::with_capacity(ranges.len());
2682        for (start, end) in ranges {
2683            let sample_relative = sample_relative.clone();
2684            let input_file = &input_file;
2685            let output_file = &output_file;
2686            handles.push(scope.spawn(move || {
2687                compute_job_range(&sample_relative, input_file, output_file, start, end)
2688            }));
2689        }
2690        let mut computations = Vec::with_capacity(jobs);
2691        for handle in handles {
2692            let range = handle
2693                .join()
2694                .map_err(|_| anyhow::anyhow!("software-oracle worker panicked"))??;
2695            computations.extend(range);
2696        }
2697        ensure!(
2698            computations.len() == jobs,
2699            "software-oracle worker lost a job"
2700        );
2701        for (index, computation) in computations.iter().enumerate() {
2702            ensure!(
2703                usize::try_from(computation.transcript.job_index)? == index,
2704                "software-oracle results are not in deterministic job order"
2705            );
2706        }
2707        Ok(computations)
2708    });
2709    let input_finish = input_file.finish();
2710    let output_finish = output_file.finish();
2711    input_finish?;
2712    output_finish?;
2713    computation
2714}
2715
2716fn compute_job_range(
2717    sample_relative: &str,
2718    input_file: &CheckedFile,
2719    output_file: &CheckedFile,
2720    start: usize,
2721    end: usize,
2722) -> Result<Vec<JobComputation>> {
2723    let oracle = Sha256GenericWots::new();
2724    let mut computations = Vec::with_capacity(end - start);
2725    for job in start..end {
2726        let job_u64 = u64::try_from(job)?;
2727        let mut input = [0_u8; INPUT_BYTES_PER_JOB_USIZE];
2728        let mut output = [0_u8; OUTPUT_SLOT_BYTES_USIZE];
2729        input_file.read_exact_at(&mut input, job_u64 * INPUT_BYTES_PER_JOB)?;
2730        output_file.read_exact_at(&mut output, job_u64 * OUTPUT_SLOT_BYTES)?;
2731        let private_seed: [u8; 32] = input[..32].try_into().expect("fixed input seed span");
2732        let message: [u8; 32] = input[32..].try_into().expect("fixed input message span");
2733        let seed_sha256: [u8; 32] = Sha256::digest(private_seed).into();
2734        let fused = oracle.sign_and_public_key_from_private_seed(
2735            PrivateSeed::<HashSigsSha256GenericV1>::new(private_seed),
2736            &MessageDigest::new(message),
2737        );
2738        let signature = fused.signature.to_bytes();
2739        let public_key = fused.public_key.to_bytes();
2740        let mut expected = [0_u8; FUSED_OUTPUT_BYTES];
2741        expected[..SIGNATURE_BYTES].copy_from_slice(&signature);
2742        expected[SIGNATURE_BYTES..].copy_from_slice(&public_key);
2743        let observed = &output[..OUTPUT_VALID_BYTES_USIZE];
2744        let payload_match = observed == expected.as_slice();
2745        let padding_match =
2746            output[OUTPUT_VALID_BYTES_USIZE..]
2747                .iter()
2748                .enumerate()
2749                .all(|(index, byte)| {
2750                    let index = u64::try_from(index).expect("output padding index fits in u64");
2751                    let global = job_u64 * OUTPUT_SLOT_BYTES + OUTPUT_VALID_BYTES + index;
2752                    *byte == output_poison(global)
2753                });
2754        computations.push(JobComputation {
2755            transcript: JobTranscript {
2756                schema: 1,
2757                sample: sample_relative.to_owned(),
2758                job_index: u32::try_from(job)?,
2759                input_sha256: hex_lower(&Sha256::digest(input)),
2760                expected_sha256: hex_lower(&Sha256::digest(expected)),
2761                observed_sha256: hex_lower(&Sha256::digest(observed)),
2762                r#match: payload_match,
2763            },
2764            seed_sha256,
2765            padding_match,
2766        });
2767        input.fill(0);
2768        output.fill(0);
2769    }
2770    Ok(computations)
2771}
2772
2773fn output_poison(offset: u64) -> u8 {
2774    let mixed = offset.wrapping_mul(131).wrapping_add(0x5b) & 0xff;
2775    0xa5 ^ u8::try_from(mixed).expect("masked to one byte")
2776}
2777
2778fn sample_file(sample: &SampleSpec, file: &str) -> String {
2779    format!("{}/{file}", sample.relative)
2780}
2781
2782#[cfg(test)]
2783mod tests {
2784    use super::{
2785        TIMED_REGION, advance_reservation_chain, canonical_bdf, canonical_serial, output_poison,
2786        valid_utc, valid_xrt_build_date, validate_timing,
2787    };
2788    use crate::model::{ReservationReceipt, TimingRecord};
2789
2790    #[test]
2791    fn poison_matches_cpp_boundaries() {
2792        assert_eq!(output_poison(0), 0xfe);
2793        assert_eq!(output_poison(2_207), 0x1d);
2794        assert_eq!(output_poison(2_208), 0x9e);
2795        assert_eq!(output_poison(4_095), 0x7d);
2796        assert_eq!(output_poison(4_096), 0xfe);
2797    }
2798
2799    #[test]
2800    fn ancillary_timing_can_be_zero_but_kernel_timing_must_be_positive() {
2801        let timing = TimingRecord {
2802            schema: 1,
2803            timed_region: TIMED_REGION.to_owned(),
2804            host_to_device_nanoseconds: 0,
2805            kernel_nanoseconds: 1,
2806            device_to_host_nanoseconds: 0,
2807            diagnostic_read_nanoseconds: 0,
2808            completion_state: 4,
2809        };
2810        assert!(validate_timing(&timing).is_ok());
2811
2812        let no_kernel_time = TimingRecord {
2813            kernel_nanoseconds: 0,
2814            ..timing
2815        };
2816        assert!(validate_timing(&no_kernel_time).is_err());
2817    }
2818
2819    fn receipt(sequence: u64, previous: &str, record: &str) -> ReservationReceipt {
2820        ReservationReceipt {
2821            schema: 1,
2822            sequence,
2823            phase: "phase_b".to_owned(),
2824            reservation_id: "00".repeat(16),
2825            nonce: "00".repeat(8),
2826            base: None,
2827            batch: 1,
2828            input_bytes: 64,
2829            input_sha256: Some("11".repeat(32)),
2830            seed_count: 1,
2831            previous_sha256: previous.to_owned(),
2832            record_sha256: record.to_owned(),
2833        }
2834    }
2835
2836    #[test]
2837    fn reservation_chain_requires_contiguous_sequence_and_digest() {
2838        let first_digest = "22".repeat(32);
2839        let mut chain = None;
2840        advance_reservation_chain(&mut chain, &receipt(41, &"00".repeat(32), &first_digest))
2841            .unwrap();
2842        advance_reservation_chain(&mut chain, &receipt(42, &first_digest, &"33".repeat(32)))
2843            .unwrap();
2844
2845        let mut bad_sequence = Some((41, first_digest.clone()));
2846        assert!(
2847            advance_reservation_chain(
2848                &mut bad_sequence,
2849                &receipt(43, &first_digest, &"44".repeat(32)),
2850            )
2851            .is_err()
2852        );
2853        let mut bad_digest = Some((41, first_digest));
2854        assert!(
2855            advance_reservation_chain(
2856                &mut bad_digest,
2857                &receipt(42, &"55".repeat(32), &"66".repeat(32)),
2858            )
2859            .is_err()
2860        );
2861    }
2862
2863    #[test]
2864    fn hardware_identity_text_fields_are_semantically_canonical() {
2865        assert!(canonical_bdf("0000:65:00.1"));
2866        assert!(canonical_bdf("ffff:ff:1f.7"));
2867        assert!(!canonical_bdf("0000:65:20.0"));
2868        assert!(!canonical_bdf("0000:65:ff.0"));
2869        assert!(canonical_serial("XMC-SERIAL_001.2"));
2870        assert!(!canonical_serial("---"));
2871        assert!(!canonical_serial("N/A"));
2872        assert!(valid_xrt_build_date("2024-02-29 23:59:59"));
2873        assert!(!valid_xrt_build_date("2023-02-29 12:00:00"));
2874        assert!(!valid_xrt_build_date("2023-13-01 12:00:00"));
2875        assert!(!valid_xrt_build_date("2023-12-01 24:00:00"));
2876    }
2877
2878    #[test]
2879    fn utc_timestamp_requires_a_real_calendar_instant() {
2880        assert!(valid_utc("2024-02-29T23:59:59Z"));
2881        for impossible in [
2882            "2023-02-29T12:00:00Z",
2883            "2100-02-29T12:00:00Z",
2884            "2026-04-31T12:00:00Z",
2885            "2026-00-15T12:00:00Z",
2886            "2026-13-01T12:00:00Z",
2887            "2026-07-15T24:00:00Z",
2888            "2026-07-15T12:60:00Z",
2889            "2026-07-15T12:00:60Z",
2890        ] {
2891            assert!(!valid_utc(impossible), "accepted {impossible}");
2892        }
2893    }
2894}