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, VNextDeterminismInitialState,
15    VNextDeterminismPhase, VNextDeterminismWorkspacePoison,
16};
17#[cfg(any(feature = "cuda", test))]
18use ferrum_models::{VNextDeterminismParticipantSpec, MAX_VNEXT_DETERMINISM_PARTICIPANTS};
19use ferrum_types::{FerrumError, Result};
20
21const PRIMARY_MODEL_KEYS: [&str; 3] = ["m1-qwen35-4b", "m2-qwen35-35b-a3b", "m3-qwen3-30b-a3b"];
22const M1_MODEL_KEYS: [&str; 1] = ["m1-qwen35-4b"];
23#[cfg(feature = "cuda")]
24const EXECUTIONS_PER_MODE: usize = 6;
25#[cfg(feature = "cuda")]
26const RELEASE_EXPECTED_CASES: usize = 72;
27#[cfg(feature = "cuda")]
28const M1_S2_FOCUSED_EXPECTED_CASES: usize = 20;
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
31pub enum VNextDeterminismScope {
32    #[default]
33    #[value(name = "release-full")]
34    ReleaseFull,
35    #[value(name = "m1-s2-focused")]
36    M1S2Focused,
37}
38
39impl VNextDeterminismScope {
40    const fn as_str(self) -> &'static str {
41        match self {
42            Self::ReleaseFull => "release-full",
43            Self::M1S2Focused => "m1-s2-focused",
44        }
45    }
46
47    const fn model_keys(self) -> &'static [&'static str] {
48        match self {
49            Self::ReleaseFull => &PRIMARY_MODEL_KEYS,
50            Self::M1S2Focused => &M1_MODEL_KEYS,
51        }
52    }
53
54    #[cfg(feature = "cuda")]
55    const fn expected_case_count(self) -> usize {
56        match self {
57            Self::ReleaseFull => RELEASE_EXPECTED_CASES,
58            Self::M1S2Focused => M1_S2_FOCUSED_EXPECTED_CASES,
59        }
60    }
61}
62
63#[derive(Args, Clone, Debug)]
64pub struct VNextDeterminismCommand {
65    /// Immutable three-model lock generated from the checked-in release catalog.
66    #[arg(long, value_name = "PATH")]
67    pub models_lock: PathBuf,
68
69    /// Existing artifact root containing `hardware-probe/probe.json`.
70    #[arg(long, value_name = "DIR")]
71    pub artifact_root: PathBuf,
72
73    /// Evidence denominator: full three-model release matrix or the bounded M1 S2 witness.
74    #[arg(long, value_enum, default_value_t = VNextDeterminismScope::ReleaseFull)]
75    pub scope: VNextDeterminismScope,
76
77    /// Primary model binding in `MODEL_KEY=/absolute/model/directory` form.
78    #[arg(
79        long = "model",
80        value_name = "MODEL_KEY=DIR",
81        action = clap::ArgAction::Append
82    )]
83    pub models: Vec<String>,
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
87struct ModelBinding {
88    key: String,
89    directory: PathBuf,
90}
91
92#[cfg(any(feature = "cuda", test))]
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94enum ShapePhase {
95    Prefill,
96    Decode,
97}
98
99#[cfg(any(feature = "cuda", test))]
100impl ShapePhase {
101    #[cfg(feature = "cuda")]
102    const fn as_str(self) -> &'static str {
103        match self {
104            Self::Prefill => "prefill",
105            Self::Decode => "decode",
106        }
107    }
108
109    #[cfg(feature = "cuda")]
110    const fn execution_phase(self) -> VNextDeterminismPhase {
111        match self {
112            Self::Prefill => VNextDeterminismPhase::Prefill,
113            Self::Decode => VNextDeterminismPhase::Decode,
114        }
115    }
116}
117
118#[cfg(any(feature = "cuda", test))]
119#[derive(Clone, Debug, PartialEq, Eq)]
120struct ParticipantFixture {
121    token_ids: Vec<u32>,
122    immediate_start: usize,
123}
124
125#[cfg(any(feature = "cuda", test))]
126impl ParticipantFixture {
127    fn immediate_end(&self) -> usize {
128        self.token_ids.len()
129    }
130
131    fn to_spec(&self) -> Result<VNextDeterminismParticipantSpec> {
132        VNextDeterminismParticipantSpec::new(
133            self.token_ids.clone(),
134            self.immediate_start..self.immediate_end(),
135            self.immediate_end().saturating_add(8),
136        )
137    }
138}
139
140#[cfg(any(feature = "cuda", test))]
141#[derive(Clone, Debug, PartialEq, Eq)]
142struct ShapeFixture {
143    phase: ShapePhase,
144    partition: &'static str,
145    participants: Vec<ParticipantFixture>,
146}
147
148#[cfg(any(feature = "cuda", test))]
149impl ShapeFixture {
150    #[cfg(feature = "cuda")]
151    fn execution_spec(
152        &self,
153        initial_state: VNextDeterminismInitialState,
154        workspace_poison: VNextDeterminismWorkspacePoison,
155        mode: VNextDeterminismExecutionMode,
156    ) -> Result<VNextDeterminismExecutionSpec> {
157        VNextDeterminismExecutionSpec::new(
158            self.phase.execution_phase(),
159            self.participants
160                .iter()
161                .map(ParticipantFixture::to_spec)
162                .collect::<Result<Vec<_>>>()?,
163            initial_state,
164            workspace_poison,
165            mode,
166        )
167    }
168}
169
170pub async fn execute(command: VNextDeterminismCommand) -> Result<()> {
171    let bindings = validate_command(&command)?;
172    #[cfg(feature = "cuda")]
173    {
174        return cuda::collect(command, bindings).await;
175    }
176    #[cfg(not(feature = "cuda"))]
177    {
178        let _ = bindings;
179        Err(FerrumError::unsupported(
180            "ferrum vnext-determinism requires a binary built with the cuda feature",
181        ))
182    }
183}
184
185fn validate_command(command: &VNextDeterminismCommand) -> Result<Vec<ModelBinding>> {
186    require_regular_file(&command.models_lock, "--models-lock")?;
187    if !command.artifact_root.is_dir() {
188        return Err(FerrumError::invalid_parameter(format!(
189            "--artifact-root is not a directory: {}",
190            command.artifact_root.display()
191        )));
192    }
193    require_regular_file(
194        &command.artifact_root.join("hardware-probe/probe.json"),
195        "hardware-probe/probe.json",
196    )?;
197    parse_model_bindings(&command.models, command.scope, true)
198}
199
200fn require_regular_file(path: &Path, label: &str) -> Result<()> {
201    let metadata = path.symlink_metadata().map_err(|error| {
202        FerrumError::invalid_parameter(format!(
203            "{label} is not a readable regular file at {}: {error}",
204            path.display()
205        ))
206    })?;
207    if metadata.file_type().is_symlink() || !metadata.is_file() {
208        return Err(FerrumError::invalid_parameter(format!(
209            "{label} must be a real regular file: {}",
210            path.display()
211        )));
212    }
213    Ok(())
214}
215
216fn parse_model_bindings(
217    values: &[String],
218    scope: VNextDeterminismScope,
219    require_directories: bool,
220) -> Result<Vec<ModelBinding>> {
221    let model_keys = scope.model_keys();
222    let expected = model_keys.iter().copied().collect::<BTreeSet<_>>();
223    let mut indexed = BTreeMap::new();
224    for value in values {
225        let (key, raw_directory) = value.split_once('=').ok_or_else(|| {
226            FerrumError::invalid_parameter(format!(
227                "--model must use MODEL_KEY=DIR form, got {value:?}"
228            ))
229        })?;
230        if key.is_empty() || raw_directory.is_empty() || !expected.contains(key) {
231            return Err(FerrumError::invalid_parameter(format!(
232                "--model has an unknown key or empty directory: {value:?}"
233            )));
234        }
235        let directory = PathBuf::from(raw_directory);
236        if require_directories && (!directory.is_absolute() || !directory.is_dir()) {
237            return Err(FerrumError::invalid_parameter(format!(
238                "--model {key} must name an existing absolute directory: {}",
239                directory.display()
240            )));
241        }
242        if indexed.insert(key.to_owned(), directory).is_some() {
243            return Err(FerrumError::invalid_parameter(format!(
244                "--model duplicates primary model key {key}"
245            )));
246        }
247    }
248    let actual = indexed.keys().map(String::as_str).collect::<BTreeSet<_>>();
249    if actual != expected {
250        let missing = expected.difference(&actual).copied().collect::<Vec<_>>();
251        return Err(FerrumError::invalid_parameter(format!(
252            "--model must bind exactly the {} model set for scope {}; missing {missing:?}",
253            model_keys.len(),
254            scope.as_str(),
255        )));
256    }
257    Ok(model_keys
258        .iter()
259        .map(|key| ModelBinding {
260            key: (*key).to_owned(),
261            directory: indexed
262                .remove(*key)
263                .expect("exact primary model set was checked"),
264        })
265        .collect())
266}
267
268#[cfg(any(feature = "cuda", test))]
269fn shape_fixtures(scope: VNextDeterminismScope) -> Vec<ShapeFixture> {
270    let prefill = |partition, token_count, immediate_start| ShapeFixture {
271        phase: ShapePhase::Prefill,
272        partition,
273        participants: vec![ParticipantFixture {
274            token_ids: deterministic_tokens(0, token_count),
275            immediate_start,
276        }],
277    };
278    let decode = |partition, participant_count| ShapeFixture {
279        phase: ShapePhase::Decode,
280        partition,
281        participants: (0..participant_count)
282            .map(|participant| ParticipantFixture {
283                token_ids: deterministic_tokens(participant, 9),
284                immediate_start: 8,
285            })
286            .collect(),
287    };
288    let mut fixtures = vec![
289        prefill("single_token", 1, 0),
290        prefill("multi_token", 4, 0),
291        prefill("chunk_boundary", 8, 4),
292        decode("c1", 1),
293        decode("multi_participant", 4),
294    ];
295    if scope == VNextDeterminismScope::ReleaseFull {
296        fixtures.push(decode("c32", MAX_VNEXT_DETERMINISM_PARTICIPANTS));
297    }
298    fixtures
299}
300
301#[cfg(any(feature = "cuda", test))]
302fn deterministic_tokens(participant: usize, count: usize) -> Vec<u32> {
303    let base = 100_u32.saturating_add(
304        u32::try_from(participant)
305            .unwrap_or(u32::MAX)
306            .saturating_mul(16),
307    );
308    (0..count)
309        .map(|offset| base.saturating_add(u32::try_from(offset).unwrap_or(u32::MAX)))
310        .collect()
311}
312
313#[cfg(feature = "cuda")]
314mod cuda {
315    use std::fs::{self, File, OpenOptions};
316    use std::io::{Read, Write};
317    use std::sync::Arc;
318
319    use ferrum_engine::vnext_determinism::{
320        create_cuda_vnext_determinism_collector, CudaVNextDeterminismCollector,
321    };
322    use ferrum_interfaces::vnext::{
323        CapabilityCatalog, ContractVersion, ExecutionDeterminismEvidenceDenominator,
324        ExecutionDeterminismProviderCoverage, ProviderReplayEquivalence, ResolvedModelPlan,
325        SubmissionWaveDeterminismArtifactExecution, SubmissionWaveDeterminismArtifactWitness,
326        VNextError,
327    };
328    use ferrum_models::vnext::{
329        open_registered_colocated_safetensors, resolve_registered_model_from_sources,
330        DefinedProductionModel,
331    };
332    use ferrum_types::{Device, EngineConfig, ModelId};
333    use serde::{Deserialize, Serialize};
334    use sha2::{Digest, Sha256};
335    use uuid::Uuid;
336
337    use super::*;
338
339    const ARTIFACT_TYPE: &str = "runtime_vnext_cuda_determinism_collector";
340
341    #[derive(Debug, Serialize)]
342    struct TokenShapeArtifact {
343        partition: String,
344        participant_count: usize,
345        immediate_tokens: Vec<usize>,
346        source_start_tokens: Vec<usize>,
347        source_end_tokens: Vec<usize>,
348    }
349
350    impl TokenShapeArtifact {
351        fn from_fixture(fixture: &ShapeFixture) -> Self {
352            Self {
353                partition: fixture.partition.to_owned(),
354                participant_count: fixture.participants.len(),
355                immediate_tokens: fixture
356                    .participants
357                    .iter()
358                    .map(|participant| {
359                        participant
360                            .immediate_end()
361                            .saturating_sub(participant.immediate_start)
362                    })
363                    .collect(),
364                source_start_tokens: fixture
365                    .participants
366                    .iter()
367                    .map(|participant| participant.immediate_start)
368                    .collect(),
369                source_end_tokens: fixture
370                    .participants
371                    .iter()
372                    .map(ParticipantFixture::immediate_end)
373                    .collect(),
374            }
375        }
376    }
377
378    #[derive(Debug, Serialize)]
379    struct InitializationArtifact {
380        input_sha256: String,
381        rng_sha256: String,
382        initial_state_kind: String,
383        initial_state_sha256: String,
384        workspace_poison: String,
385    }
386
387    #[derive(Clone, Debug, Serialize)]
388    struct CoverageTargetArtifact {
389        operation_id: String,
390        operation_version: ContractVersion,
391        operation_fingerprint: String,
392        provider_id: String,
393        provider_version: ContractVersion,
394        provider_implementation_fingerprint: String,
395        provider_execution_contract_fingerprint: String,
396        replay_equivalence: String,
397        witness_plan_fingerprint: String,
398        node_ids: Vec<String>,
399    }
400
401    #[derive(Debug, Serialize)]
402    struct ComparisonArtifact {
403        kind: String,
404        ordinal: usize,
405        left_execution_id: String,
406        right_execution_id: String,
407        relation: &'static str,
408        first_mismatch: Option<String>,
409    }
410
411    #[derive(Debug, Serialize)]
412    struct CaseArtifact {
413        schema_version: u32,
414        case_id: String,
415        denominator_fingerprint: String,
416        binary_sha256: String,
417        device_runtime_implementation_fingerprint: String,
418        device_fingerprint: String,
419        model_key: String,
420        resolved_plan_fingerprint: String,
421        plan_hash: String,
422        phase: String,
423        token_shape: TokenShapeArtifact,
424        dtype: String,
425        quantization: String,
426        initialization: InitializationArtifact,
427        coverage_targets: Vec<CoverageTargetArtifact>,
428        executions: Vec<SubmissionWaveDeterminismArtifactExecution>,
429        comparisons: Vec<ComparisonArtifact>,
430        first_mismatch: Option<String>,
431    }
432
433    struct PendingCase {
434        case_id: String,
435        model_key: String,
436        phase: String,
437        token_shape: TokenShapeArtifact,
438        dtype: String,
439        quantization: String,
440        initial_state_kind: String,
441        workspace_poison: String,
442        executions: Vec<SubmissionWaveDeterminismArtifactExecution>,
443        comparisons: Vec<ComparisonArtifact>,
444    }
445
446    #[derive(Debug, Serialize)]
447    struct CaseProgressArtifact<'a> {
448        schema_version: u32,
449        artifact_type: &'static str,
450        status: &'static str,
451        case_id: &'a str,
452        model_key: &'a str,
453        phase: &'a str,
454        token_shape: &'a TokenShapeArtifact,
455        dtype: &'a str,
456        quantization: &'a str,
457        initial_state_kind: &'a str,
458        workspace_poison: &'a str,
459        initialization_identity:
460            &'a ferrum_interfaces::vnext::SubmissionWaveDeterminismArtifactInitializationIdentity,
461        execution_count: usize,
462        comparison_count: usize,
463        canonical_witness_count: usize,
464        canonical_witnesses_sha256: String,
465        replayed_segment_count: usize,
466    }
467
468    struct CollectedModel {
469        key: String,
470        directory: PathBuf,
471        plan: ResolvedModelPlan,
472        dtype: String,
473        quantization: String,
474        cases: Vec<PendingCase>,
475    }
476
477    #[derive(Debug, Deserialize)]
478    struct HardwareProbeIdentity {
479        schema_version: u32,
480        fingerprint: String,
481    }
482
483    #[derive(Debug, Serialize)]
484    struct FileReference {
485        path: String,
486        sha256: String,
487        size_bytes: u64,
488    }
489
490    #[derive(Debug, Serialize)]
491    struct DenominatorReference {
492        path: String,
493        sha256: String,
494        size_bytes: u64,
495        fingerprint: String,
496    }
497
498    #[derive(Debug, Serialize)]
499    struct CollectorModelSummary {
500        model_key: String,
501        model_dir: String,
502        resolved_plan_fingerprint: String,
503        plan_hash: String,
504        dtype: String,
505        quantization: String,
506        case_count: usize,
507    }
508
509    #[derive(Debug, Serialize)]
510    struct CollectorManifest {
511        schema_version: u32,
512        artifact_type: &'static str,
513        status: &'static str,
514        backend: &'static str,
515        scope: &'static str,
516        models_lock: FileReference,
517        hardware_probe: FileReference,
518        device_fingerprint: String,
519        binary: FileReference,
520        denominator: DenominatorReference,
521        models: Vec<CollectorModelSummary>,
522        cases: Vec<FileReference>,
523        case_count: usize,
524        execution_count: usize,
525        comparison_count: usize,
526        pass_line: String,
527    }
528
529    #[derive(Debug, Serialize)]
530    struct RejectionArtifact {
531        schema_version: u32,
532        artifact_type: &'static str,
533        status: &'static str,
534        failure_class: &'static str,
535        message: String,
536    }
537
538    pub(super) async fn collect(
539        command: VNextDeterminismCommand,
540        bindings: Vec<ModelBinding>,
541    ) -> Result<()> {
542        match collect_inner(&command, &bindings).await {
543            Ok(pass_line) => {
544                println!("{pass_line}");
545                Ok(())
546            }
547            Err(error) => {
548                let rejection = RejectionArtifact {
549                    schema_version: 1,
550                    artifact_type: ARTIFACT_TYPE,
551                    status: "reject",
552                    failure_class: "collector_failure",
553                    message: error.to_string(),
554                };
555                let rejection_path = command.artifact_root.join("collector.reject.json");
556                let _ = write_json_exclusive(&rejection_path, &rejection);
557                Err(error)
558            }
559        }
560    }
561
562    async fn collect_inner(
563        command: &VNextDeterminismCommand,
564        bindings: &[ModelBinding],
565    ) -> Result<String> {
566        let fixtures = shape_fixtures(command.scope);
567        let expected_case_count = command.scope.expected_case_count();
568        let cases_per_model = fixtures.len() * 4;
569        reject_existing_outputs(&command.artifact_root)?;
570        let models_lock = file_reference(
571            &command.artifact_root,
572            &command.models_lock,
573            "models.lock.json",
574        )?;
575        let probe_path = command.artifact_root.join("hardware-probe/probe.json");
576        let hardware_probe = read_hardware_probe(&probe_path)?;
577        let hardware_probe_ref = file_reference(
578            &command.artifact_root,
579            &probe_path,
580            "hardware-probe/probe.json",
581        )?;
582        let current_exe = std::env::current_exe().map_err(|error| {
583            FerrumError::io(format!("cannot resolve current ferrum binary: {error}"))
584        })?;
585        let binary = absolute_file_reference(&current_exe)?;
586        let progress_root = command.artifact_root.join("collector-progress");
587        let progress_cases = progress_root.join("cases");
588        fs::create_dir(&progress_root).map_err(|error| {
589            FerrumError::io(format!(
590                "cannot create determinism progress directory {}: {error}",
591                progress_root.display()
592            ))
593        })?;
594        fs::create_dir(&progress_cases).map_err(|error| {
595            FerrumError::io(format!(
596                "cannot create determinism progress case directory {}: {error}",
597                progress_cases.display()
598            ))
599        })?;
600
601        let mut canonical_catalog: Option<CapabilityCatalog> = None;
602        let mut canonical_catalog_fingerprint: Option<String> = None;
603        let mut collected_models = Vec::with_capacity(bindings.len());
604        for (model_ordinal, binding) in bindings.iter().enumerate() {
605            println!(
606                "FERRUM VNEXT DETERMINISM PROGRESS model={} stage=load ordinal={}/{}",
607                binding.key,
608                model_ordinal + 1,
609                bindings.len()
610            );
611            let sources = Arc::new(
612                open_registered_colocated_safetensors(&binding.directory).map_err(|error| {
613                    FerrumError::model(format!(
614                        "cannot open registered source for {} at {}: {error}",
615                        binding.key,
616                        binding.directory.display()
617                    ))
618                })?,
619            );
620            let registration = resolve_registered_model_from_sources(sources.as_ref())
621                .and_then(|registration| registration.into_required())
622                .map_err(|error| {
623                    FerrumError::model(format!(
624                        "cannot require vNext registration for {}: {error}",
625                        binding.key
626                    ))
627                })?;
628            let prepared = registration.define_from_sources(sources).map_err(|error| {
629                FerrumError::model(format!(
630                    "cannot prepare registered vNext model {}: {error}",
631                    binding.key
632                ))
633            })?;
634            let capabilities = prepared.model_capabilities(
635                &ferrum_types::NumericalExecutionPolicy::Auto,
636                ferrum_types::KvStorageFormat::F16,
637            )?;
638            let quantization = capabilities
639                .quantization
640                .unwrap_or_else(|| "none".to_owned());
641            let mut engine = determinism_engine_config(binding, &prepared);
642            engine.backend.dtype = prepared.descriptor().execution_dtype();
643            let collector = create_cuda_vnext_determinism_collector(&engine, &prepared, 0)
644                .map_err(|error| {
645                    FerrumError::backend(format!(
646                        "cannot create CUDA determinism collector for {}: {error}",
647                        binding.key
648                    ))
649                })?;
650            let catalog_fingerprint = collector
651                .capability_catalog()
652                .fingerprint()
653                .map_err(vnext_backend_error)?;
654            match canonical_catalog_fingerprint.as_deref() {
655                Some(expected) if expected != catalog_fingerprint => {
656                    return Err(FerrumError::backend(format!(
657                        "CUDA capability catalog drifted between primary models: expected {expected}, got {catalog_fingerprint} for {}",
658                        binding.key
659                    )));
660                }
661                None => {
662                    canonical_catalog = Some(collector.capability_catalog().clone());
663                    canonical_catalog_fingerprint = Some(catalog_fingerprint);
664                }
665                Some(_) => {}
666            }
667            let plan = collector.resolved_model_plan().clone();
668            let dtype = collector.model_info().dtype.to_string();
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: &DefinedProductionModel,
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 determinism_fixtures_are_valid_product_inputs() {
1584        for scope in [
1585            VNextDeterminismScope::ReleaseFull,
1586            VNextDeterminismScope::M1S2Focused,
1587        ] {
1588            let fixtures = shape_fixtures(scope);
1589            assert!(!fixtures.is_empty());
1590            assert!(fixtures.iter().all(|fixture| fixture
1591                .participants
1592                .iter()
1593                .all(|participant| participant.to_spec().is_ok())));
1594        }
1595    }
1596}