1#![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";
65pub const PHASE_B_TYPED_RESULT_SCHEMA: u64 = 3;
67pub const PHASE_B_TYPED_RESULT_CONTRACT: &str = "hashsigs-u280-phase-b-typed-result-v1";
69pub 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#[derive(Clone, Debug)]
144pub struct OracleConfig {
145 pub transport_root: PathBuf,
147 pub result_directory: PathBuf,
149 pub threads: usize,
151}
152
153#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct OracleOutcome {
156 pub cryptographic_oracle_valid: bool,
158 pub samples: usize,
160 pub jobs: u64,
162 pub result_manifest_sha256: String,
164 pub result_manifest_bytes: u64,
166 pub result_directory: PathBuf,
168}
169
170#[derive(Clone, Debug, Eq, PartialEq)]
173pub struct OracleFailure {
174 pub result_directory: PathBuf,
176 pub result_manifest_sha256: String,
178 pub result_manifest_bytes: u64,
180 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
197pub 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#[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
393pub 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#[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 #[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 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}