Skip to main content

ferrum_cli/commands/
vnext_determinism.rs

1//! CUDA vNext determinism evidence collection.
2//!
3//! The command is intentionally a product binary entrypoint rather than a
4//! test-only harness. It resolves the same immutable model package and creates
5//! the same concrete executor as `run` and `serve`, while keeping evidence
6//! assembly separate from the external bounded-runner receipt.
7
8use std::collections::{BTreeMap, BTreeSet};
9use std::path::{Path, PathBuf};
10
11use clap::{Args, ValueEnum};
12#[cfg(feature = "cuda")]
13use ferrum_models::{
14    VNextDeterminismExecutionMode, VNextDeterminismExecutionSpec, VNextDeterminismPhase,
15};
16#[cfg(any(feature = "cuda", test))]
17use ferrum_models::{
18    VNextDeterminismInitialState, VNextDeterminismParticipantSpec, VNextDeterminismWorkspacePoison,
19    MAX_VNEXT_DETERMINISM_PARTICIPANTS,
20};
21use ferrum_types::{FerrumError, Result};
22
23const PRIMARY_MODEL_KEYS: [&str; 3] = ["m1-qwen35-4b", "m2-qwen35-35b-a3b", "m3-qwen3-30b-a3b"];
24const M1_MODEL_KEYS: [&str; 1] = ["m1-qwen35-4b"];
25#[cfg(feature = "cuda")]
26const EXECUTIONS_PER_MODE: usize = 6;
27#[cfg(any(feature = "cuda", test))]
28const RELEASE_EXPECTED_CASES: usize = 72;
29#[cfg(any(feature = "cuda", test))]
30const M1_S2_FOCUSED_EXPECTED_CASES: usize = 20;
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
33pub enum VNextDeterminismScope {
34    #[default]
35    #[value(name = "release-full")]
36    ReleaseFull,
37    #[value(name = "m1-s2-focused")]
38    M1S2Focused,
39}
40
41impl VNextDeterminismScope {
42    const fn as_str(self) -> &'static str {
43        match self {
44            Self::ReleaseFull => "release-full",
45            Self::M1S2Focused => "m1-s2-focused",
46        }
47    }
48
49    const fn model_keys(self) -> &'static [&'static str] {
50        match self {
51            Self::ReleaseFull => &PRIMARY_MODEL_KEYS,
52            Self::M1S2Focused => &M1_MODEL_KEYS,
53        }
54    }
55
56    #[cfg(any(feature = "cuda", test))]
57    const fn expected_case_count(self) -> usize {
58        match self {
59            Self::ReleaseFull => RELEASE_EXPECTED_CASES,
60            Self::M1S2Focused => M1_S2_FOCUSED_EXPECTED_CASES,
61        }
62    }
63}
64
65#[derive(Args, Clone, Debug)]
66pub struct VNextDeterminismCommand {
67    /// Immutable three-model lock generated from the checked-in release catalog.
68    #[arg(long, value_name = "PATH")]
69    pub models_lock: PathBuf,
70
71    /// Existing artifact root containing `hardware-probe/probe.json`.
72    #[arg(long, value_name = "DIR")]
73    pub artifact_root: PathBuf,
74
75    /// Evidence denominator: full three-model release matrix or the bounded M1 S2 witness.
76    #[arg(long, value_enum, default_value_t = VNextDeterminismScope::ReleaseFull)]
77    pub scope: VNextDeterminismScope,
78
79    /// Primary model binding in `MODEL_KEY=/absolute/model/directory` form.
80    #[arg(
81        long = "model",
82        value_name = "MODEL_KEY=DIR",
83        action = clap::ArgAction::Append
84    )]
85    pub models: Vec<String>,
86}
87
88#[derive(Clone, Debug, PartialEq, Eq)]
89struct ModelBinding {
90    key: String,
91    directory: PathBuf,
92}
93
94#[cfg(any(feature = "cuda", test))]
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96enum ShapePhase {
97    Prefill,
98    Decode,
99}
100
101#[cfg(any(feature = "cuda", test))]
102impl ShapePhase {
103    const fn as_str(self) -> &'static str {
104        match self {
105            Self::Prefill => "prefill",
106            Self::Decode => "decode",
107        }
108    }
109
110    #[cfg(feature = "cuda")]
111    const fn execution_phase(self) -> VNextDeterminismPhase {
112        match self {
113            Self::Prefill => VNextDeterminismPhase::Prefill,
114            Self::Decode => VNextDeterminismPhase::Decode,
115        }
116    }
117}
118
119#[cfg(any(feature = "cuda", test))]
120#[derive(Clone, Debug, PartialEq, Eq)]
121struct ParticipantFixture {
122    token_ids: Vec<u32>,
123    immediate_start: usize,
124}
125
126#[cfg(any(feature = "cuda", test))]
127impl ParticipantFixture {
128    fn immediate_end(&self) -> usize {
129        self.token_ids.len()
130    }
131
132    fn to_spec(&self) -> Result<VNextDeterminismParticipantSpec> {
133        VNextDeterminismParticipantSpec::new(
134            self.token_ids.clone(),
135            self.immediate_start..self.immediate_end(),
136            self.immediate_end().saturating_add(8),
137        )
138    }
139}
140
141#[cfg(any(feature = "cuda", test))]
142#[derive(Clone, Debug, PartialEq, Eq)]
143struct ShapeFixture {
144    phase: ShapePhase,
145    partition: &'static str,
146    participants: Vec<ParticipantFixture>,
147}
148
149#[cfg(any(feature = "cuda", test))]
150impl ShapeFixture {
151    #[cfg(feature = "cuda")]
152    fn execution_spec(
153        &self,
154        initial_state: VNextDeterminismInitialState,
155        workspace_poison: VNextDeterminismWorkspacePoison,
156        mode: VNextDeterminismExecutionMode,
157    ) -> Result<VNextDeterminismExecutionSpec> {
158        VNextDeterminismExecutionSpec::new(
159            self.phase.execution_phase(),
160            self.participants
161                .iter()
162                .map(ParticipantFixture::to_spec)
163                .collect::<Result<Vec<_>>>()?,
164            initial_state,
165            workspace_poison,
166            mode,
167        )
168    }
169}
170
171pub async fn execute(command: VNextDeterminismCommand) -> Result<()> {
172    let bindings = validate_command(&command)?;
173    #[cfg(feature = "cuda")]
174    {
175        return cuda::collect(command, bindings).await;
176    }
177    #[cfg(not(feature = "cuda"))]
178    {
179        let _ = bindings;
180        Err(FerrumError::unsupported(
181            "ferrum vnext-determinism requires a binary built with the cuda feature",
182        ))
183    }
184}
185
186fn validate_command(command: &VNextDeterminismCommand) -> Result<Vec<ModelBinding>> {
187    require_regular_file(&command.models_lock, "--models-lock")?;
188    if !command.artifact_root.is_dir() {
189        return Err(FerrumError::invalid_parameter(format!(
190            "--artifact-root is not a directory: {}",
191            command.artifact_root.display()
192        )));
193    }
194    require_regular_file(
195        &command.artifact_root.join("hardware-probe/probe.json"),
196        "hardware-probe/probe.json",
197    )?;
198    parse_model_bindings(&command.models, command.scope, true)
199}
200
201fn require_regular_file(path: &Path, label: &str) -> Result<()> {
202    let metadata = path.symlink_metadata().map_err(|error| {
203        FerrumError::invalid_parameter(format!(
204            "{label} is not a readable regular file at {}: {error}",
205            path.display()
206        ))
207    })?;
208    if metadata.file_type().is_symlink() || !metadata.is_file() {
209        return Err(FerrumError::invalid_parameter(format!(
210            "{label} must be a real regular file: {}",
211            path.display()
212        )));
213    }
214    Ok(())
215}
216
217fn parse_model_bindings(
218    values: &[String],
219    scope: VNextDeterminismScope,
220    require_directories: bool,
221) -> Result<Vec<ModelBinding>> {
222    let model_keys = scope.model_keys();
223    let expected = model_keys.iter().copied().collect::<BTreeSet<_>>();
224    let mut indexed = BTreeMap::new();
225    for value in values {
226        let (key, raw_directory) = value.split_once('=').ok_or_else(|| {
227            FerrumError::invalid_parameter(format!(
228                "--model must use MODEL_KEY=DIR form, got {value:?}"
229            ))
230        })?;
231        if key.is_empty() || raw_directory.is_empty() || !expected.contains(key) {
232            return Err(FerrumError::invalid_parameter(format!(
233                "--model has an unknown key or empty directory: {value:?}"
234            )));
235        }
236        let directory = PathBuf::from(raw_directory);
237        if require_directories && (!directory.is_absolute() || !directory.is_dir()) {
238            return Err(FerrumError::invalid_parameter(format!(
239                "--model {key} must name an existing absolute directory: {}",
240                directory.display()
241            )));
242        }
243        if indexed.insert(key.to_owned(), directory).is_some() {
244            return Err(FerrumError::invalid_parameter(format!(
245                "--model duplicates primary model key {key}"
246            )));
247        }
248    }
249    let actual = indexed.keys().map(String::as_str).collect::<BTreeSet<_>>();
250    if actual != expected {
251        let missing = expected.difference(&actual).copied().collect::<Vec<_>>();
252        return Err(FerrumError::invalid_parameter(format!(
253            "--model must bind exactly the {} model set for scope {}; missing {missing:?}",
254            model_keys.len(),
255            scope.as_str(),
256        )));
257    }
258    Ok(model_keys
259        .iter()
260        .map(|key| ModelBinding {
261            key: (*key).to_owned(),
262            directory: indexed
263                .remove(*key)
264                .expect("exact primary model set was checked"),
265        })
266        .collect())
267}
268
269#[cfg(any(feature = "cuda", test))]
270fn shape_fixtures(scope: VNextDeterminismScope) -> Vec<ShapeFixture> {
271    let prefill = |partition, token_count, immediate_start| ShapeFixture {
272        phase: ShapePhase::Prefill,
273        partition,
274        participants: vec![ParticipantFixture {
275            token_ids: deterministic_tokens(0, token_count),
276            immediate_start,
277        }],
278    };
279    let decode = |partition, participant_count| ShapeFixture {
280        phase: ShapePhase::Decode,
281        partition,
282        participants: (0..participant_count)
283            .map(|participant| ParticipantFixture {
284                token_ids: deterministic_tokens(participant, 9),
285                immediate_start: 8,
286            })
287            .collect(),
288    };
289    let mut fixtures = vec![
290        prefill("single_token", 1, 0),
291        prefill("multi_token", 4, 0),
292        prefill("chunk_boundary", 8, 4),
293        decode("c1", 1),
294        decode("multi_participant", 4),
295    ];
296    if scope == VNextDeterminismScope::ReleaseFull {
297        fixtures.push(decode("c32", MAX_VNEXT_DETERMINISM_PARTICIPANTS));
298    }
299    fixtures
300}
301
302#[cfg(any(feature = "cuda", test))]
303fn deterministic_tokens(participant: usize, count: usize) -> Vec<u32> {
304    let base = 100_u32.saturating_add(
305        u32::try_from(participant)
306            .unwrap_or(u32::MAX)
307            .saturating_mul(16),
308    );
309    (0..count)
310        .map(|offset| base.saturating_add(u32::try_from(offset).unwrap_or(u32::MAX)))
311        .collect()
312}
313
314#[cfg(feature = "cuda")]
315mod cuda {
316    use std::fs::{self, File, OpenOptions};
317    use std::io::{Read, Write};
318    use std::sync::Arc;
319
320    use ferrum_engine::vnext_determinism::{
321        create_cuda_vnext_determinism_collector, CudaVNextDeterminismCollector,
322    };
323    use ferrum_interfaces::vnext::{
324        CapabilityCatalog, ContractVersion, ExecutionDeterminismEvidenceDenominator,
325        ExecutionDeterminismProviderCoverage, ProviderReplayEquivalence, ResolvedModelPlan,
326        SubmissionWaveDeterminismArtifactExecution, SubmissionWaveDeterminismArtifactWitness,
327        VNextError,
328    };
329    use ferrum_models::vnext::{
330        open_registered_colocated_safetensors, resolve_registered_model_from_sources,
331        PreparedProductionModel,
332    };
333    use ferrum_types::{Device, EngineConfig, ModelId};
334    use serde::{Deserialize, Serialize};
335    use sha2::{Digest, Sha256};
336    use uuid::Uuid;
337
338    use super::*;
339
340    const ARTIFACT_TYPE: &str = "runtime_vnext_cuda_determinism_collector";
341
342    #[derive(Debug, Serialize)]
343    struct TokenShapeArtifact {
344        partition: String,
345        participant_count: usize,
346        immediate_tokens: Vec<usize>,
347        source_start_tokens: Vec<usize>,
348        source_end_tokens: Vec<usize>,
349    }
350
351    impl TokenShapeArtifact {
352        fn from_fixture(fixture: &ShapeFixture) -> Self {
353            Self {
354                partition: fixture.partition.to_owned(),
355                participant_count: fixture.participants.len(),
356                immediate_tokens: fixture
357                    .participants
358                    .iter()
359                    .map(|participant| {
360                        participant
361                            .immediate_end()
362                            .saturating_sub(participant.immediate_start)
363                    })
364                    .collect(),
365                source_start_tokens: fixture
366                    .participants
367                    .iter()
368                    .map(|participant| participant.immediate_start)
369                    .collect(),
370                source_end_tokens: fixture
371                    .participants
372                    .iter()
373                    .map(ParticipantFixture::immediate_end)
374                    .collect(),
375            }
376        }
377    }
378
379    #[derive(Debug, Serialize)]
380    struct InitializationArtifact {
381        input_sha256: String,
382        rng_sha256: String,
383        initial_state_kind: String,
384        initial_state_sha256: String,
385        workspace_poison: String,
386    }
387
388    #[derive(Clone, Debug, Serialize)]
389    struct CoverageTargetArtifact {
390        operation_id: String,
391        operation_version: ContractVersion,
392        operation_fingerprint: String,
393        provider_id: String,
394        provider_version: ContractVersion,
395        provider_implementation_fingerprint: String,
396        provider_execution_contract_fingerprint: String,
397        replay_equivalence: String,
398        witness_plan_fingerprint: String,
399        node_ids: Vec<String>,
400    }
401
402    #[derive(Debug, Serialize)]
403    struct ComparisonArtifact {
404        kind: String,
405        ordinal: usize,
406        left_execution_id: String,
407        right_execution_id: String,
408        relation: &'static str,
409        first_mismatch: Option<String>,
410    }
411
412    #[derive(Debug, Serialize)]
413    struct CaseArtifact {
414        schema_version: u32,
415        case_id: String,
416        denominator_fingerprint: String,
417        binary_sha256: String,
418        device_runtime_implementation_fingerprint: String,
419        device_fingerprint: String,
420        model_key: String,
421        resolved_plan_fingerprint: String,
422        plan_hash: String,
423        phase: String,
424        token_shape: TokenShapeArtifact,
425        dtype: String,
426        quantization: String,
427        initialization: InitializationArtifact,
428        coverage_targets: Vec<CoverageTargetArtifact>,
429        executions: Vec<SubmissionWaveDeterminismArtifactExecution>,
430        comparisons: Vec<ComparisonArtifact>,
431        first_mismatch: Option<String>,
432    }
433
434    struct PendingCase {
435        case_id: String,
436        model_key: String,
437        phase: String,
438        token_shape: TokenShapeArtifact,
439        dtype: String,
440        quantization: String,
441        initial_state_kind: String,
442        workspace_poison: String,
443        executions: Vec<SubmissionWaveDeterminismArtifactExecution>,
444        comparisons: Vec<ComparisonArtifact>,
445    }
446
447    #[derive(Debug, Serialize)]
448    struct CaseProgressArtifact<'a> {
449        schema_version: u32,
450        artifact_type: &'static str,
451        status: &'static str,
452        case_id: &'a str,
453        model_key: &'a str,
454        phase: &'a str,
455        token_shape: &'a TokenShapeArtifact,
456        dtype: &'a str,
457        quantization: &'a str,
458        initial_state_kind: &'a str,
459        workspace_poison: &'a str,
460        initialization_identity:
461            &'a ferrum_interfaces::vnext::SubmissionWaveDeterminismArtifactInitializationIdentity,
462        execution_count: usize,
463        comparison_count: usize,
464        canonical_witness_count: usize,
465        canonical_witnesses_sha256: String,
466        replayed_segment_count: usize,
467    }
468
469    struct CollectedModel {
470        key: String,
471        directory: PathBuf,
472        plan: ResolvedModelPlan,
473        dtype: String,
474        quantization: String,
475        cases: Vec<PendingCase>,
476    }
477
478    #[derive(Debug, Deserialize)]
479    struct HardwareProbeIdentity {
480        schema_version: u32,
481        fingerprint: String,
482    }
483
484    #[derive(Debug, Serialize)]
485    struct FileReference {
486        path: String,
487        sha256: String,
488        size_bytes: u64,
489    }
490
491    #[derive(Debug, Serialize)]
492    struct DenominatorReference {
493        path: String,
494        sha256: String,
495        size_bytes: u64,
496        fingerprint: String,
497    }
498
499    #[derive(Debug, Serialize)]
500    struct CollectorModelSummary {
501        model_key: String,
502        model_dir: String,
503        resolved_plan_fingerprint: String,
504        plan_hash: String,
505        dtype: String,
506        quantization: String,
507        case_count: usize,
508    }
509
510    #[derive(Debug, Serialize)]
511    struct CollectorManifest {
512        schema_version: u32,
513        artifact_type: &'static str,
514        status: &'static str,
515        backend: &'static str,
516        scope: &'static str,
517        models_lock: FileReference,
518        hardware_probe: FileReference,
519        device_fingerprint: String,
520        binary: FileReference,
521        denominator: DenominatorReference,
522        models: Vec<CollectorModelSummary>,
523        cases: Vec<FileReference>,
524        case_count: usize,
525        execution_count: usize,
526        comparison_count: usize,
527        pass_line: String,
528    }
529
530    #[derive(Debug, Serialize)]
531    struct RejectionArtifact {
532        schema_version: u32,
533        artifact_type: &'static str,
534        status: &'static str,
535        failure_class: &'static str,
536        message: String,
537    }
538
539    pub(super) async fn collect(
540        command: VNextDeterminismCommand,
541        bindings: Vec<ModelBinding>,
542    ) -> Result<()> {
543        match collect_inner(&command, &bindings).await {
544            Ok(pass_line) => {
545                println!("{pass_line}");
546                Ok(())
547            }
548            Err(error) => {
549                let rejection = RejectionArtifact {
550                    schema_version: 1,
551                    artifact_type: ARTIFACT_TYPE,
552                    status: "reject",
553                    failure_class: "collector_failure",
554                    message: error.to_string(),
555                };
556                let rejection_path = command.artifact_root.join("collector.reject.json");
557                let _ = write_json_exclusive(&rejection_path, &rejection);
558                Err(error)
559            }
560        }
561    }
562
563    async fn collect_inner(
564        command: &VNextDeterminismCommand,
565        bindings: &[ModelBinding],
566    ) -> Result<String> {
567        let fixtures = shape_fixtures(command.scope);
568        let expected_case_count = command.scope.expected_case_count();
569        let cases_per_model = fixtures.len() * 4;
570        reject_existing_outputs(&command.artifact_root)?;
571        let models_lock = file_reference(
572            &command.artifact_root,
573            &command.models_lock,
574            "models.lock.json",
575        )?;
576        let probe_path = command.artifact_root.join("hardware-probe/probe.json");
577        let hardware_probe = read_hardware_probe(&probe_path)?;
578        let hardware_probe_ref = file_reference(
579            &command.artifact_root,
580            &probe_path,
581            "hardware-probe/probe.json",
582        )?;
583        let current_exe = std::env::current_exe().map_err(|error| {
584            FerrumError::io(format!("cannot resolve current ferrum binary: {error}"))
585        })?;
586        let binary = absolute_file_reference(&current_exe)?;
587        let progress_root = command.artifact_root.join("collector-progress");
588        let progress_cases = progress_root.join("cases");
589        fs::create_dir(&progress_root).map_err(|error| {
590            FerrumError::io(format!(
591                "cannot create determinism progress directory {}: {error}",
592                progress_root.display()
593            ))
594        })?;
595        fs::create_dir(&progress_cases).map_err(|error| {
596            FerrumError::io(format!(
597                "cannot create determinism progress case directory {}: {error}",
598                progress_cases.display()
599            ))
600        })?;
601
602        let mut canonical_catalog: Option<CapabilityCatalog> = None;
603        let mut canonical_catalog_fingerprint: Option<String> = None;
604        let mut collected_models = Vec::with_capacity(bindings.len());
605        for (model_ordinal, binding) in bindings.iter().enumerate() {
606            println!(
607                "FERRUM VNEXT DETERMINISM PROGRESS model={} stage=load ordinal={}/{}",
608                binding.key,
609                model_ordinal + 1,
610                bindings.len()
611            );
612            let sources = Arc::new(
613                open_registered_colocated_safetensors(&binding.directory).map_err(|error| {
614                    FerrumError::model(format!(
615                        "cannot open registered source for {} at {}: {error}",
616                        binding.key,
617                        binding.directory.display()
618                    ))
619                })?,
620            );
621            let registration = resolve_registered_model_from_sources(sources.as_ref())
622                .and_then(|registration| registration.into_required())
623                .map_err(|error| {
624                    FerrumError::model(format!(
625                        "cannot require vNext registration for {}: {error}",
626                        binding.key
627                    ))
628                })?;
629            let prepared = registration
630                .prepare_from_sources(sources)
631                .map_err(|error| {
632                    FerrumError::model(format!(
633                        "cannot prepare registered vNext model {}: {error}",
634                        binding.key
635                    ))
636                })?;
637            let capabilities = prepared.model_capabilities()?;
638            let dtype = prepared.descriptor().execution_dtype().to_string();
639            let quantization = capabilities
640                .quantization
641                .unwrap_or_else(|| "none".to_owned());
642            let mut engine = determinism_engine_config(binding, &prepared);
643            engine.backend.dtype = prepared.descriptor().execution_dtype();
644            let collector = create_cuda_vnext_determinism_collector(&engine, &prepared, 0)
645                .map_err(|error| {
646                    FerrumError::backend(format!(
647                        "cannot create CUDA determinism collector for {}: {error}",
648                        binding.key
649                    ))
650                })?;
651            let catalog_fingerprint = collector
652                .capability_catalog()
653                .fingerprint()
654                .map_err(vnext_backend_error)?;
655            match canonical_catalog_fingerprint.as_deref() {
656                Some(expected) if expected != catalog_fingerprint => {
657                    return Err(FerrumError::backend(format!(
658                        "CUDA capability catalog drifted between primary models: expected {expected}, got {catalog_fingerprint} for {}",
659                        binding.key
660                    )));
661                }
662                None => {
663                    canonical_catalog = Some(collector.capability_catalog().clone());
664                    canonical_catalog_fingerprint = Some(catalog_fingerprint);
665                }
666                Some(_) => {}
667            }
668            let plan = collector.resolved_model_plan().clone();
669            collector.prepare().await.map_err(|error| {
670                FerrumError::backend(format!(
671                    "cannot prepare CUDA determinism collector for {}: {error}",
672                    binding.key
673                ))
674            })?;
675            println!(
676                "FERRUM VNEXT DETERMINISM PROGRESS model={} stage=prepared",
677                binding.key
678            );
679            let cases = collect_model_cases(
680                &collector,
681                &binding.key,
682                &dtype,
683                &quantization,
684                &fixtures,
685                model_ordinal * cases_per_model,
686                expected_case_count,
687                &progress_cases,
688            )
689            .await?;
690            drop(collector);
691            drop(prepared);
692            println!(
693                "FERRUM VNEXT DETERMINISM PROGRESS model={} stage=released cases={}",
694                binding.key,
695                cases.len()
696            );
697            collected_models.push(CollectedModel {
698                key: binding.key.clone(),
699                directory: binding.directory.clone(),
700                plan,
701                dtype,
702                quantization,
703                cases,
704            });
705        }
706
707        let catalog = canonical_catalog.ok_or_else(|| {
708            FerrumError::internal("CUDA determinism collector produced no capability catalog")
709        })?;
710        let plan_refs = collected_models
711            .iter()
712            .map(|model| (model.key.as_str(), &model.plan))
713            .collect::<Vec<_>>();
714        let provider_coverage = match command.scope {
715            VNextDeterminismScope::ReleaseFull => {
716                ExecutionDeterminismProviderCoverage::AllCatalogProviders
717            }
718            VNextDeterminismScope::M1S2Focused => {
719                ExecutionDeterminismProviderCoverage::SelectedPlanProviders
720            }
721        };
722        let denominator =
723            ExecutionDeterminismEvidenceDenominator::from_catalog_and_resolved_plans_with_provider_coverage(
724                &catalog,
725                &plan_refs,
726                provider_coverage,
727            )
728            .map_err(vnext_backend_error)?;
729        let denominator_bytes = denominator.to_json().map_err(vnext_backend_error)?;
730        let denominator_fingerprint = denominator.fingerprint().map_err(vnext_backend_error)?;
731        let device_runtime_fingerprint = denominator
732            .coverage()
733            .device_runtime_implementation_fingerprint()
734            .to_owned();
735
736        let stage = command.artifact_root.join(format!(
737            ".vnext-determinism-stage-{}-{}",
738            std::process::id(),
739            Uuid::new_v4()
740        ));
741        fs::create_dir(&stage).map_err(|error| {
742            FerrumError::io(format!(
743                "cannot create determinism staging directory {}: {error}",
744                stage.display()
745            ))
746        })?;
747        let stage_cases = stage.join("cases");
748        fs::create_dir(&stage_cases).map_err(|error| {
749            FerrumError::io(format!(
750                "cannot create determinism case staging directory {}: {error}",
751                stage_cases.display()
752            ))
753        })?;
754
755        let staged = stage_collection(
756            &stage,
757            collected_models,
758            &denominator,
759            &denominator_bytes,
760            &denominator_fingerprint,
761            &device_runtime_fingerprint,
762            &hardware_probe.fingerprint,
763            command.scope,
764            expected_case_count,
765            models_lock,
766            hardware_probe_ref,
767            binary,
768        );
769        let manifest = match staged {
770            Ok(manifest) => manifest,
771            Err(error) => {
772                let _ = fs::remove_dir_all(&stage);
773                return Err(error);
774            }
775        };
776        publish_stage(&stage, &command.artifact_root)?;
777        let pass_line = manifest.pass_line.clone();
778        Ok(pass_line)
779    }
780
781    fn determinism_engine_config(
782        binding: &ModelBinding,
783        prepared: &PreparedProductionModel,
784    ) -> EngineConfig {
785        let mut engine = EngineConfig::default();
786        engine.model.model_id = ModelId::new(binding.key.clone());
787        engine.backend.device = Device::CUDA(0);
788        engine.backend.dtype = prepared.descriptor().execution_dtype();
789        engine.backend.enable_reusable_execution = true;
790        engine.scheduler.max_running_requests = MAX_VNEXT_DETERMINISM_PARTICIPANTS;
791        engine.batching.max_batch_size = MAX_VNEXT_DETERMINISM_PARTICIPANTS;
792        engine.batching.max_num_batched_tokens = MAX_VNEXT_DETERMINISM_PARTICIPANTS;
793        engine.runtime.model_path = Some(binding.directory.display().to_string());
794        engine
795    }
796
797    async fn collect_model_cases(
798        collector: &CudaVNextDeterminismCollector,
799        model_key: &str,
800        dtype: &str,
801        quantization: &str,
802        fixtures: &[ShapeFixture],
803        completed_before_model: usize,
804        expected_case_count: usize,
805        progress_cases: &Path,
806    ) -> Result<Vec<PendingCase>> {
807        let mut cases = Vec::with_capacity(fixtures.len() * 4);
808        for fixture in fixtures {
809            for (initial_state, initial_state_kind) in [
810                (VNextDeterminismInitialState::Zero, "zero"),
811                (VNextDeterminismInitialState::Nonzero, "nonzero"),
812            ] {
813                let zero = collect_case(
814                    collector,
815                    model_key,
816                    &fixture,
817                    dtype,
818                    quantization,
819                    initial_state,
820                    initial_state_kind,
821                    VNextDeterminismWorkspacePoison::Zero,
822                    "00",
823                )
824                .await?;
825                write_case_progress(progress_cases, &zero)?;
826                print_case_progress(
827                    completed_before_model + cases.len() + 1,
828                    expected_case_count,
829                    &zero,
830                );
831                let a5 = collect_case(
832                    collector,
833                    model_key,
834                    &fixture,
835                    dtype,
836                    quantization,
837                    initial_state,
838                    initial_state_kind,
839                    VNextDeterminismWorkspacePoison::A5,
840                    "a5",
841                )
842                .await?;
843                write_case_progress(progress_cases, &a5)?;
844                print_case_progress(
845                    completed_before_model + cases.len() + 2,
846                    expected_case_count,
847                    &a5,
848                );
849                ensure_poison_equivalence(&zero, &a5)?;
850                cases.extend([zero, a5]);
851            }
852        }
853        Ok(cases)
854    }
855
856    fn write_case_progress(progress_cases: &Path, case: &PendingCase) -> Result<()> {
857        let first = case.executions.first().ok_or_else(|| {
858            FerrumError::internal(format!(
859                "determinism case {} contains no execution",
860                case.case_id
861            ))
862        })?;
863        let witness_bytes = serde_json::to_vec(first.witnesses())
864            .map_err(|error| FerrumError::serialization(error.to_string()))?;
865        let progress = CaseProgressArtifact {
866            schema_version: 1,
867            artifact_type: "runtime_vnext_cuda_determinism_case_progress",
868            status: "case_comparisons_pass",
869            case_id: &case.case_id,
870            model_key: &case.model_key,
871            phase: &case.phase,
872            token_shape: &case.token_shape,
873            dtype: &case.dtype,
874            quantization: &case.quantization,
875            initial_state_kind: &case.initial_state_kind,
876            workspace_poison: &case.workspace_poison,
877            initialization_identity: first.initialization_identity(),
878            execution_count: case.executions.len(),
879            comparison_count: case.comparisons.len(),
880            canonical_witness_count: first.witnesses().len(),
881            canonical_witnesses_sha256: format!("{:x}", Sha256::digest(&witness_bytes)),
882            replayed_segment_count: case
883                .executions
884                .iter()
885                .find(|execution| execution.mode() == "replay")
886                .map(|execution| execution.replayed_segments().len())
887                .unwrap_or(0),
888        };
889        write_json_exclusive(
890            &progress_cases.join(format!("{}.json", case.case_id)),
891            &progress,
892        )
893    }
894
895    fn print_case_progress(completed: usize, expected_case_count: usize, case: &PendingCase) {
896        println!(
897            "FERRUM VNEXT DETERMINISM PROGRESS case={} complete={}/{}",
898            case.case_id, completed, expected_case_count
899        );
900    }
901
902    async fn collect_case(
903        collector: &CudaVNextDeterminismCollector,
904        model_key: &str,
905        fixture: &ShapeFixture,
906        dtype: &str,
907        quantization: &str,
908        initial_state: VNextDeterminismInitialState,
909        initial_state_kind: &str,
910        workspace_poison: VNextDeterminismWorkspacePoison,
911        workspace_poison_label: &str,
912    ) -> Result<PendingCase> {
913        let case_id = format!(
914            "{model_key}.{}.{}.{}.{}",
915            fixture.phase.as_str(),
916            fixture.partition,
917            initial_state_kind,
918            workspace_poison_label
919        );
920        let mut executions = Vec::with_capacity(EXECUTIONS_PER_MODE * 2);
921        for (mode, mode_label) in [
922            (VNextDeterminismExecutionMode::Eager, "eager"),
923            (VNextDeterminismExecutionMode::Replayed, "replay"),
924        ] {
925            for repeat in 0..EXECUTIONS_PER_MODE {
926                let spec = fixture.execution_spec(initial_state, workspace_poison, mode)?;
927                let execution_id = format!("{mode_label}-{repeat:02}");
928                let evidence = collector.collect_execution(&spec).await.map_err(|error| {
929                    FerrumError::backend(format!(
930                        "determinism execution failed for case {case_id} execution {execution_id}: {error}"
931                    ))
932                })?;
933                executions.push(
934                    evidence
935                        .into_artifact_execution(execution_id.clone())
936                        .map_err(|error| {
937                            FerrumError::backend(format!(
938                                "determinism artifact projection failed for case {case_id} execution {execution_id}: {error}"
939                            ))
940                        })?,
941                );
942            }
943        }
944        executions.sort_by(|left, right| left.execution_id().cmp(right.execution_id()));
945        let comparisons = compare_case_executions(&executions)?;
946        Ok(PendingCase {
947            case_id,
948            model_key: model_key.to_owned(),
949            phase: fixture.phase.as_str().to_owned(),
950            token_shape: TokenShapeArtifact::from_fixture(fixture),
951            dtype: dtype.to_owned(),
952            quantization: quantization.to_owned(),
953            initial_state_kind: initial_state_kind.to_owned(),
954            workspace_poison: workspace_poison_label.to_owned(),
955            executions,
956            comparisons,
957        })
958    }
959
960    fn compare_case_executions(
961        executions: &[SubmissionWaveDeterminismArtifactExecution],
962    ) -> Result<Vec<ComparisonArtifact>> {
963        let by_id = executions
964            .iter()
965            .map(|execution| (execution.execution_id(), execution))
966            .collect::<BTreeMap<_, _>>();
967        let mut comparisons = Vec::with_capacity(15);
968        for (kind, left_mode, right_mode) in [
969            ("eager_eager", "eager", "eager"),
970            ("eager_replay", "eager", "replay"),
971            ("replay_replay", "replay", "replay"),
972        ] {
973            for ordinal in 0..5 {
974                let left_id = format!("{left_mode}-{ordinal:02}");
975                let right_ordinal = if kind == "eager_replay" {
976                    ordinal
977                } else {
978                    ordinal + 1
979                };
980                let right_id = format!("{right_mode}-{right_ordinal:02}");
981                let left = by_id.get(left_id.as_str()).ok_or_else(|| {
982                    FerrumError::internal(format!(
983                        "determinism comparison lacks execution {left_id}"
984                    ))
985                })?;
986                let right = by_id.get(right_id.as_str()).ok_or_else(|| {
987                    FerrumError::internal(format!(
988                        "determinism comparison lacks execution {right_id}"
989                    ))
990                })?;
991                ensure_execution_equivalence(left, right, kind)?;
992                if kind == "replay_replay"
993                    && (left.compute_path_requirement() != right.compute_path_requirement()
994                        || left.reusable_program_fingerprint()
995                            != right.reusable_program_fingerprint()
996                        || left.declared_eager_boundary_node_ids()
997                            != right.declared_eager_boundary_node_ids()
998                        || left.replayed_segments() != right.replayed_segments())
999                {
1000                    return Err(FerrumError::backend(format!(
1001                        "{kind} replay shape mismatch between {left_id} and {right_id}"
1002                    )));
1003                }
1004                comparisons.push(ComparisonArtifact {
1005                    kind: kind.to_owned(),
1006                    ordinal,
1007                    left_execution_id: left_id,
1008                    right_execution_id: right_id,
1009                    relation: "bitwise_equal",
1010                    first_mismatch: None,
1011                });
1012            }
1013        }
1014        Ok(comparisons)
1015    }
1016
1017    fn ensure_execution_equivalence(
1018        left: &SubmissionWaveDeterminismArtifactExecution,
1019        right: &SubmissionWaveDeterminismArtifactExecution,
1020        comparison_kind: &str,
1021    ) -> Result<()> {
1022        let left_initialization = left.initialization_identity();
1023        let right_initialization = right.initialization_identity();
1024        let mut restore_mismatches = Vec::with_capacity(4);
1025        if left.restore_sha256() != right.restore_sha256() {
1026            restore_mismatches.push("logical_restore");
1027        }
1028        if left_initialization.input_sha256() != right_initialization.input_sha256() {
1029            restore_mismatches.push("external_input");
1030        }
1031        if left_initialization.rng_sha256() != right_initialization.rng_sha256() {
1032            restore_mismatches.push("rng");
1033        }
1034        if left_initialization.initial_state_sha256() != right_initialization.initial_state_sha256()
1035        {
1036            restore_mismatches.push("initial_state");
1037        }
1038        if !restore_mismatches.is_empty() {
1039            return Err(FerrumError::backend(format!(
1040                "{comparison_kind} restored different input/RNG/initial-state bytes between {} and {}: mismatch_fields={} left_restore_sha256={} right_restore_sha256={} left_input_sha256={} right_input_sha256={} left_rng_sha256={} right_rng_sha256={} left_initial_state_sha256={} right_initial_state_sha256={}",
1041                left.execution_id(),
1042                right.execution_id(),
1043                restore_mismatches.join(","),
1044                left.restore_sha256(),
1045                right.restore_sha256(),
1046                left_initialization.input_sha256(),
1047                right_initialization.input_sha256(),
1048                left_initialization.rng_sha256(),
1049                right_initialization.rng_sha256(),
1050                left_initialization.initial_state_sha256(),
1051                right_initialization.initial_state_sha256(),
1052            )));
1053        }
1054        if left.witnesses().len() != right.witnesses().len() {
1055            return Err(FerrumError::backend(format!(
1056                "{comparison_kind} witness cardinality differs between {} ({}) and {} ({})",
1057                left.execution_id(),
1058                left.witnesses().len(),
1059                right.execution_id(),
1060                right.witnesses().len()
1061            )));
1062        }
1063        for (left_witness, right_witness) in left.witnesses().iter().zip(right.witnesses()) {
1064            if left_witness != right_witness {
1065                return Err(witness_mismatch(
1066                    comparison_kind,
1067                    left.execution_id(),
1068                    right.execution_id(),
1069                    left_witness,
1070                    right_witness,
1071                ));
1072            }
1073        }
1074        Ok(())
1075    }
1076
1077    fn witness_mismatch(
1078        comparison_kind: &str,
1079        left_execution_id: &str,
1080        right_execution_id: &str,
1081        left: &SubmissionWaveDeterminismArtifactWitness,
1082        right: &SubmissionWaveDeterminismArtifactWitness,
1083    ) -> FerrumError {
1084        FerrumError::backend(format!(
1085            "{comparison_kind} first witness mismatch between {left_execution_id} and {right_execution_id}: left=({},{},{},{},{},{},{},{},{},{}) right=({},{},{},{},{},{},{},{},{},{})",
1086            left.kind(),
1087            left.semantic_id(),
1088            left.node_id(),
1089            left.resource_id(),
1090            left.access(),
1091            left.participant_index(),
1092            left.logical_offset_bytes(),
1093            left.length_bytes(),
1094            left.element_type(),
1095            left.raw_sha256(),
1096            right.kind(),
1097            right.semantic_id(),
1098            right.node_id(),
1099            right.resource_id(),
1100            right.access(),
1101            right.participant_index(),
1102            right.logical_offset_bytes(),
1103            right.length_bytes(),
1104            right.element_type(),
1105            right.raw_sha256(),
1106        ))
1107    }
1108
1109    fn ensure_poison_equivalence(zero: &PendingCase, a5: &PendingCase) -> Result<()> {
1110        let zero_execution = zero.executions.first().ok_or_else(|| {
1111            FerrumError::internal("zero-poison determinism case contains no execution")
1112        })?;
1113        let a5_execution = a5.executions.first().ok_or_else(|| {
1114            FerrumError::internal("a5-poison determinism case contains no execution")
1115        })?;
1116        ensure_execution_equivalence(zero_execution, a5_execution, "workspace_poison").map_err(
1117            |error| {
1118                FerrumError::backend(format!(
1119                    "workspace poison changed {} versus {}: {error}",
1120                    zero.case_id, a5.case_id
1121                ))
1122            },
1123        )
1124    }
1125
1126    #[allow(clippy::too_many_arguments)]
1127    fn stage_collection(
1128        stage: &Path,
1129        collected_models: Vec<CollectedModel>,
1130        denominator: &ExecutionDeterminismEvidenceDenominator,
1131        denominator_bytes: &[u8],
1132        denominator_fingerprint: &str,
1133        device_runtime_fingerprint: &str,
1134        device_fingerprint: &str,
1135        scope: VNextDeterminismScope,
1136        expected_case_count: usize,
1137        models_lock: FileReference,
1138        hardware_probe: FileReference,
1139        binary: FileReference,
1140    ) -> Result<CollectorManifest> {
1141        let denominator_path = stage.join("denominator.json");
1142        write_bytes_exclusive(&denominator_path, denominator_bytes)?;
1143        let denominator_ref = DenominatorReference {
1144            path: "denominator.json".to_owned(),
1145            sha256: file_sha256(&denominator_path)?,
1146            size_bytes: file_size(&denominator_path)?,
1147            fingerprint: denominator_fingerprint.to_owned(),
1148        };
1149        if denominator_ref.sha256 != denominator_ref.fingerprint {
1150            return Err(FerrumError::internal(
1151                "typed denominator fingerprint differs from exact serialized bytes",
1152            ));
1153        }
1154
1155        let binary_sha256 = binary.sha256.clone();
1156        let mut case_refs = Vec::with_capacity(expected_case_count);
1157        let mut model_summaries = Vec::with_capacity(collected_models.len());
1158        let mut execution_count = 0;
1159        let mut comparison_count = 0;
1160        for model in collected_models {
1161            let identity = denominator
1162                .coverage()
1163                .models()
1164                .iter()
1165                .find(|identity| identity.model_key() == model.key)
1166                .ok_or_else(|| {
1167                    FerrumError::internal(format!(
1168                        "typed denominator lacks model identity {}",
1169                        model.key
1170                    ))
1171                })?;
1172            let targets = coverage_targets(denominator, &model.key)?;
1173            let case_count = model.cases.len();
1174            model_summaries.push(CollectorModelSummary {
1175                model_key: model.key.clone(),
1176                model_dir: model.directory.display().to_string(),
1177                resolved_plan_fingerprint: identity.resolved_plan_fingerprint().to_owned(),
1178                plan_hash: identity.plan_hash().as_str().to_owned(),
1179                dtype: model.dtype,
1180                quantization: model.quantization,
1181                case_count,
1182            });
1183            for pending in model.cases {
1184                let first = pending.executions.first().ok_or_else(|| {
1185                    FerrumError::internal(format!(
1186                        "determinism case {} contains no executions",
1187                        pending.case_id
1188                    ))
1189                })?;
1190                let initialization_input_sha256 =
1191                    first.initialization_identity().input_sha256().to_owned();
1192                let initialization_rng_sha256 =
1193                    first.initialization_identity().rng_sha256().to_owned();
1194                let initialization_state_sha256 = first
1195                    .initialization_identity()
1196                    .initial_state_sha256()
1197                    .to_owned();
1198                let case = CaseArtifact {
1199                    schema_version: 2,
1200                    case_id: pending.case_id.clone(),
1201                    denominator_fingerprint: denominator_fingerprint.to_owned(),
1202                    binary_sha256: binary_sha256.clone(),
1203                    device_runtime_implementation_fingerprint: device_runtime_fingerprint
1204                        .to_owned(),
1205                    device_fingerprint: device_fingerprint.to_owned(),
1206                    model_key: pending.model_key,
1207                    resolved_plan_fingerprint: identity.resolved_plan_fingerprint().to_owned(),
1208                    plan_hash: identity.plan_hash().as_str().to_owned(),
1209                    phase: pending.phase,
1210                    token_shape: pending.token_shape,
1211                    dtype: pending.dtype,
1212                    quantization: pending.quantization,
1213                    initialization: InitializationArtifact {
1214                        input_sha256: initialization_input_sha256,
1215                        rng_sha256: initialization_rng_sha256,
1216                        initial_state_kind: pending.initial_state_kind,
1217                        initial_state_sha256: initialization_state_sha256,
1218                        workspace_poison: pending.workspace_poison,
1219                    },
1220                    coverage_targets: targets.clone(),
1221                    executions: pending.executions,
1222                    comparisons: pending.comparisons,
1223                    first_mismatch: None,
1224                };
1225                execution_count += case.executions.len();
1226                comparison_count += case.comparisons.len();
1227                let relative = format!("cases/{}.json", case.case_id);
1228                let staged_path = stage.join(&relative);
1229                write_json_exclusive(&staged_path, &case)?;
1230                case_refs.push(FileReference {
1231                    path: relative,
1232                    sha256: file_sha256(&staged_path)?,
1233                    size_bytes: file_size(&staged_path)?,
1234                });
1235            }
1236        }
1237        case_refs.sort_by(|left, right| left.path.cmp(&right.path));
1238        if case_refs.len() != expected_case_count {
1239            return Err(FerrumError::internal(format!(
1240                "determinism collector produced {} cases, expected {expected_case_count}",
1241                case_refs.len(),
1242            )));
1243        }
1244        let pass_prefix = match scope {
1245            VNextDeterminismScope::ReleaseFull => "FERRUM VNEXT DETERMINISM COLLECTOR PASS",
1246            VNextDeterminismScope::M1S2Focused => {
1247                "FERRUM VNEXT M1 S2 FOCUSED DETERMINISM COLLECTOR PASS"
1248            }
1249        };
1250        let pass_line = format!(
1251            "{pass_prefix}: {}",
1252            stage
1253                .parent()
1254                .expect("stage is inside artifact root")
1255                .display()
1256        );
1257        let manifest = CollectorManifest {
1258            schema_version: 1,
1259            artifact_type: ARTIFACT_TYPE,
1260            status: "pass",
1261            backend: "cuda",
1262            scope: scope.as_str(),
1263            models_lock,
1264            hardware_probe,
1265            device_fingerprint: device_fingerprint.to_owned(),
1266            binary,
1267            denominator: denominator_ref,
1268            models: model_summaries,
1269            cases: case_refs,
1270            case_count: expected_case_count,
1271            execution_count,
1272            comparison_count,
1273            pass_line,
1274        };
1275        write_json_exclusive(&stage.join("collector.json"), &manifest)?;
1276        Ok(manifest)
1277    }
1278
1279    fn coverage_targets(
1280        denominator: &ExecutionDeterminismEvidenceDenominator,
1281        model_key: &str,
1282    ) -> Result<Vec<CoverageTargetArtifact>> {
1283        let mut targets = Vec::new();
1284        let mut replay_equivalence = None;
1285        for requirement in denominator.coverage().provider_requirements() {
1286            let Some(selection) = requirement
1287                .model_selections()
1288                .iter()
1289                .find(|selection| selection.model_key() == model_key)
1290            else {
1291                continue;
1292            };
1293            let evidence = denominator
1294                .provider_evidence()
1295                .iter()
1296                .find(|evidence| {
1297                    evidence.model_key() == model_key
1298                        && evidence.operation_id() == requirement.operation_id()
1299                        && evidence.provider_id() == requirement.provider_id()
1300                })
1301                .ok_or_else(|| {
1302                    FerrumError::internal(format!(
1303                        "typed denominator lacks provider evidence for {model_key}/{}/{}",
1304                        requirement.operation_id(),
1305                        requirement.provider_id()
1306                    ))
1307                })?;
1308            match replay_equivalence {
1309                None => replay_equivalence = Some(requirement.replay_equivalence()),
1310                Some(expected) if expected != requirement.replay_equivalence() => {
1311                    return Err(FerrumError::unsupported(format!(
1312                        "model {model_key} mixes replay equivalence contracts inside one full-program determinism wave"
1313                    )));
1314                }
1315                Some(_) => {}
1316            }
1317            targets.push(CoverageTargetArtifact {
1318                operation_id: requirement.operation_id().to_string(),
1319                operation_version: requirement.operation_version(),
1320                operation_fingerprint: requirement.operation_fingerprint().to_owned(),
1321                provider_id: requirement.provider_id().to_string(),
1322                provider_version: requirement.provider_version(),
1323                provider_implementation_fingerprint: requirement
1324                    .provider_implementation_fingerprint()
1325                    .to_owned(),
1326                provider_execution_contract_fingerprint: requirement
1327                    .provider_execution_contract_fingerprint()
1328                    .to_string(),
1329                replay_equivalence: requirement.replay_equivalence().as_str().to_owned(),
1330                witness_plan_fingerprint: evidence.witness_plan_fingerprint().to_owned(),
1331                node_ids: selection
1332                    .node_ids()
1333                    .iter()
1334                    .map(ToString::to_string)
1335                    .collect(),
1336            });
1337        }
1338        if targets.is_empty() {
1339            return Err(FerrumError::internal(format!(
1340                "typed denominator selected no provider targets for model {model_key}"
1341            )));
1342        }
1343        if replay_equivalence != Some(ProviderReplayEquivalence::BitwiseEagerEquivalent) {
1344            return Err(FerrumError::unsupported(format!(
1345                "model {model_key} does not authorize bitwise eager/replay comparison for every selected provider"
1346            )));
1347        }
1348        targets.sort_by(|left, right| {
1349            (&left.operation_id, &left.provider_id).cmp(&(&right.operation_id, &right.provider_id))
1350        });
1351        Ok(targets)
1352    }
1353
1354    fn reject_existing_outputs(root: &Path) -> Result<()> {
1355        for relative in [
1356            "denominator.json",
1357            "cases",
1358            "collector.json",
1359            "collector.reject.json",
1360            "collector-progress",
1361        ] {
1362            let path = root.join(relative);
1363            if path.exists() {
1364                return Err(FerrumError::invalid_parameter(format!(
1365                    "determinism output already exists and will not be overwritten: {}",
1366                    path.display()
1367                )));
1368            }
1369        }
1370        Ok(())
1371    }
1372
1373    fn read_hardware_probe(path: &Path) -> Result<HardwareProbeIdentity> {
1374        let bytes = fs::read(path).map_err(|error| {
1375            FerrumError::io(format!(
1376                "cannot read CUDA hardware probe {}: {error}",
1377                path.display()
1378            ))
1379        })?;
1380        let probe: HardwareProbeIdentity = serde_json::from_slice(&bytes).map_err(|error| {
1381            FerrumError::serialization(format!(
1382                "cannot decode CUDA hardware probe {}: {error}",
1383                path.display()
1384            ))
1385        })?;
1386        if probe.schema_version != 1 || !is_sha256(&probe.fingerprint) {
1387            return Err(FerrumError::invalid_parameter(
1388                "CUDA hardware probe identity is not schema v1 with a lowercase SHA256",
1389            ));
1390        }
1391        Ok(probe)
1392    }
1393
1394    fn publish_stage(stage: &Path, root: &Path) -> Result<()> {
1395        for relative in ["denominator.json", "cases", "collector.json"] {
1396            let source = stage.join(relative);
1397            let destination = root.join(relative);
1398            fs::rename(&source, &destination).map_err(|error| {
1399                FerrumError::io(format!(
1400                    "cannot publish determinism artifact {} to {}: {error}",
1401                    source.display(),
1402                    destination.display()
1403                ))
1404            })?;
1405        }
1406        fs::remove_dir(stage).map_err(|error| {
1407            FerrumError::io(format!(
1408                "cannot remove empty determinism staging directory {}: {error}",
1409                stage.display()
1410            ))
1411        })?;
1412        Ok(())
1413    }
1414
1415    fn file_reference(root: &Path, path: &Path, expected_relative: &str) -> Result<FileReference> {
1416        let canonical_root = root.canonicalize().map_err(|error| {
1417            FerrumError::io(format!(
1418                "cannot canonicalize artifact root {}: {error}",
1419                root.display()
1420            ))
1421        })?;
1422        let canonical_path = path.canonicalize().map_err(|error| {
1423            FerrumError::io(format!("cannot canonicalize {}: {error}", path.display()))
1424        })?;
1425        let relative = canonical_path
1426            .strip_prefix(&canonical_root)
1427            .map_err(|_| {
1428                FerrumError::invalid_parameter(format!(
1429                    "artifact input must be inside artifact root: {}",
1430                    path.display()
1431                ))
1432            })?
1433            .to_string_lossy()
1434            .replace('\\', "/");
1435        if relative != expected_relative {
1436            return Err(FerrumError::invalid_parameter(format!(
1437                "artifact input path must be {expected_relative}, got {relative}"
1438            )));
1439        }
1440        Ok(FileReference {
1441            path: relative,
1442            sha256: file_sha256(path)?,
1443            size_bytes: file_size(path)?,
1444        })
1445    }
1446
1447    fn absolute_file_reference(path: &Path) -> Result<FileReference> {
1448        Ok(FileReference {
1449            path: path.display().to_string(),
1450            sha256: file_sha256(path)?,
1451            size_bytes: file_size(path)?,
1452        })
1453    }
1454
1455    fn file_size(path: &Path) -> Result<u64> {
1456        path.metadata()
1457            .map(|metadata| metadata.len())
1458            .map_err(|error| FerrumError::io(format!("cannot stat {}: {error}", path.display())))
1459    }
1460
1461    fn file_sha256(path: &Path) -> Result<String> {
1462        let mut file = File::open(path)
1463            .map_err(|error| FerrumError::io(format!("cannot open {}: {error}", path.display())))?;
1464        let mut digest = Sha256::new();
1465        let mut buffer = [0_u8; 1024 * 1024];
1466        loop {
1467            let read = file.read(&mut buffer).map_err(|error| {
1468                FerrumError::io(format!("cannot hash {}: {error}", path.display()))
1469            })?;
1470            if read == 0 {
1471                break;
1472            }
1473            digest.update(&buffer[..read]);
1474        }
1475        Ok(format!("{:x}", digest.finalize()))
1476    }
1477
1478    fn write_json_exclusive(path: &Path, value: &impl Serialize) -> Result<()> {
1479        let mut bytes = serde_json::to_vec_pretty(value)
1480            .map_err(|error| FerrumError::serialization(error.to_string()))?;
1481        bytes.push(b'\n');
1482        write_bytes_exclusive(path, &bytes)
1483    }
1484
1485    fn write_bytes_exclusive(path: &Path, bytes: &[u8]) -> Result<()> {
1486        let mut file = OpenOptions::new()
1487            .write(true)
1488            .create_new(true)
1489            .open(path)
1490            .map_err(|error| {
1491                FerrumError::io(format!("cannot create {}: {error}", path.display()))
1492            })?;
1493        file.write_all(bytes).map_err(|error| {
1494            FerrumError::io(format!("cannot write {}: {error}", path.display()))
1495        })?;
1496        file.sync_all()
1497            .map_err(|error| FerrumError::io(format!("cannot sync {}: {error}", path.display())))
1498    }
1499
1500    fn is_sha256(value: &str) -> bool {
1501        value.len() == 64
1502            && value
1503                .bytes()
1504                .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
1505    }
1506
1507    fn vnext_backend_error(error: VNextError) -> FerrumError {
1508        FerrumError::backend(error.to_string())
1509    }
1510}
1511
1512#[cfg(test)]
1513mod tests {
1514    use super::*;
1515
1516    #[test]
1517    fn model_bindings_are_exact_and_canonical() {
1518        let values = vec![
1519            "m3-qwen3-30b-a3b=/models/m3".to_owned(),
1520            "m1-qwen35-4b=/models/m1".to_owned(),
1521            "m2-qwen35-35b-a3b=/models/m2".to_owned(),
1522        ];
1523        let bindings =
1524            parse_model_bindings(&values, VNextDeterminismScope::ReleaseFull, false).unwrap();
1525        assert_eq!(
1526            bindings
1527                .iter()
1528                .map(|binding| binding.key.as_str())
1529                .collect::<Vec<_>>(),
1530            PRIMARY_MODEL_KEYS
1531        );
1532    }
1533
1534    #[test]
1535    fn model_bindings_reject_missing_duplicate_and_unknown_models() {
1536        let missing = vec![
1537            "m1-qwen35-4b=/models/m1".to_owned(),
1538            "m2-qwen35-35b-a3b=/models/m2".to_owned(),
1539        ];
1540        assert!(parse_model_bindings(&missing, VNextDeterminismScope::ReleaseFull, false).is_err());
1541
1542        let duplicate = vec![
1543            "m1-qwen35-4b=/models/m1".to_owned(),
1544            "m1-qwen35-4b=/models/other".to_owned(),
1545            "m2-qwen35-35b-a3b=/models/m2".to_owned(),
1546            "m3-qwen3-30b-a3b=/models/m3".to_owned(),
1547        ];
1548        assert!(
1549            parse_model_bindings(&duplicate, VNextDeterminismScope::ReleaseFull, false).is_err()
1550        );
1551
1552        let unknown = vec![
1553            "m1-qwen35-4b=/models/m1".to_owned(),
1554            "m2-qwen35-35b-a3b=/models/m2".to_owned(),
1555            "llama=/models/llama".to_owned(),
1556        ];
1557        assert!(parse_model_bindings(&unknown, VNextDeterminismScope::ReleaseFull, false).is_err());
1558    }
1559
1560    #[test]
1561    fn focused_scope_accepts_only_m1_and_cannot_bind_release_models() {
1562        assert_eq!(
1563            VNextDeterminismScope::from_str("m1-s2-focused", false).unwrap(),
1564            VNextDeterminismScope::M1S2Focused
1565        );
1566        assert!(VNextDeterminismScope::from_str("m1s2-focused", false).is_err());
1567
1568        let m1 = vec!["m1-qwen35-4b=/models/m1".to_owned()];
1569        let bindings =
1570            parse_model_bindings(&m1, VNextDeterminismScope::M1S2Focused, false).unwrap();
1571        assert_eq!(bindings.len(), 1);
1572        assert_eq!(bindings[0].key, "m1-qwen35-4b");
1573
1574        let release = vec![
1575            "m1-qwen35-4b=/models/m1".to_owned(),
1576            "m2-qwen35-35b-a3b=/models/m2".to_owned(),
1577            "m3-qwen3-30b-a3b=/models/m3".to_owned(),
1578        ];
1579        assert!(parse_model_bindings(&release, VNextDeterminismScope::M1S2Focused, false).is_err());
1580    }
1581
1582    #[test]
1583    fn release_shapes_cover_the_exact_bounded_partition_matrix() {
1584        let fixtures = shape_fixtures(VNextDeterminismScope::ReleaseFull);
1585        assert_eq!(fixtures.len(), 6);
1586        assert_eq!(
1587            fixtures
1588                .iter()
1589                .map(|fixture| (fixture.phase.as_str(), fixture.partition))
1590                .collect::<BTreeSet<_>>(),
1591            BTreeSet::from([
1592                ("prefill", "single_token"),
1593                ("prefill", "multi_token"),
1594                ("prefill", "chunk_boundary"),
1595                ("decode", "c1"),
1596                ("decode", "multi_participant"),
1597                ("decode", "c32"),
1598            ])
1599        );
1600        assert_eq!(
1601            fixtures
1602                .iter()
1603                .find(|fixture| fixture.partition == "c32")
1604                .unwrap()
1605                .participants
1606                .len(),
1607            MAX_VNEXT_DETERMINISM_PARTICIPANTS
1608        );
1609        assert!(fixtures.iter().all(|fixture| fixture
1610            .participants
1611            .iter()
1612            .all(|participant| participant.to_spec().is_ok())));
1613    }
1614
1615    #[test]
1616    fn case_denominator_is_three_models_times_exact_fixture_cross_product() {
1617        let states = [
1618            VNextDeterminismInitialState::Zero,
1619            VNextDeterminismInitialState::Nonzero,
1620        ];
1621        let poisons = [
1622            VNextDeterminismWorkspacePoison::Zero,
1623            VNextDeterminismWorkspacePoison::A5,
1624        ];
1625        assert_eq!(
1626            PRIMARY_MODEL_KEYS.len()
1627                * shape_fixtures(VNextDeterminismScope::ReleaseFull).len()
1628                * states.len()
1629                * poisons.len(),
1630            RELEASE_EXPECTED_CASES
1631        );
1632    }
1633
1634    #[test]
1635    fn focused_denominator_is_m1_without_c32() {
1636        let fixtures = shape_fixtures(VNextDeterminismScope::M1S2Focused);
1637        assert_eq!(fixtures.len(), 5);
1638        assert!(fixtures.iter().all(|fixture| fixture.partition != "c32"));
1639        assert_eq!(
1640            M1_MODEL_KEYS.len() * fixtures.len() * 2 * 2,
1641            M1_S2_FOCUSED_EXPECTED_CASES
1642        );
1643        assert_eq!(
1644            VNextDeterminismScope::M1S2Focused.expected_case_count(),
1645            M1_S2_FOCUSED_EXPECTED_CASES
1646        );
1647    }
1648}