Skip to main content

ferrum_interfaces/vnext/
device.rs

1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3use std::collections::{BTreeMap, BTreeSet, VecDeque};
4use std::error::Error;
5use std::num::NonZeroU64;
6use std::panic::{catch_unwind, AssertUnwindSafe};
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
9use std::time::Duration;
10
11use ferrum_types::AttentionExecutionPolicy;
12
13use super::{
14    CapabilityId, DeviceAllocationPermit, DeviceId, DynamicStorageProfile, ElementType,
15    ExecutionIdentityEnvelope, FailureDomain, FailureEnvelope, IdentifiedFailure, PlanHash,
16    ReusableExecutionBucketId, StaticWeightTransformPlan, VNextError, WeightComponentPayload,
17    WeightComponentSegments, WeightComponentSpec,
18};
19
20/// Backend-neutral device capability for an explicit cold-path reusable
21/// executable preparation lifecycle.
22pub const DEVICE_REUSABLE_EXECUTION_CAPABILITY_ID: &str = "capability.device.reusable_execution.v1";
23/// Backend-neutral declaration that a composition can compile a native
24/// invocation-adaptive attention provider.
25pub const DEVICE_NATIVE_ADAPTIVE_ATTENTION_CAPABILITY_ID: &str =
26    "capability.device.native_adaptive_attention.v1";
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
29#[serde(transparent)]
30pub struct ExecutionLaneId(NonZeroU64);
31
32impl ExecutionLaneId {
33    pub(crate) fn mint() -> Result<Self, VNextError> {
34        static NEXT_LANE_ID: AtomicU64 = AtomicU64::new(1);
35        let raw = NEXT_LANE_ID
36            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
37                current.checked_add(1)
38            })
39            .map_err(|_| VNextError::InvalidExecutionPlan {
40                reason: "execution lane identity space is exhausted".to_owned(),
41            })?;
42        NonZeroU64::new(raw)
43            .map(Self)
44            .ok_or_else(|| VNextError::InvalidExecutionPlan {
45                reason: "execution lane identity must be non-zero".to_owned(),
46            })
47    }
48
49    pub const fn get(self) -> u64 {
50        self.0.get()
51    }
52}
53
54/// Cleanup pressure is independent from model size and normal request
55/// concurrency. Once one plan accumulates this many non-quiescent owners, new
56/// execution authority is rejected until an explicit recovery worker drains
57/// the backlog.
58pub const MAX_DEFERRED_DEVICE_CLEANUP_TASKS: usize = 64;
59pub const MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS: usize = 64;
60
61const _: () = assert!(
62    MAX_DEFERRED_DEVICE_CLEANUP_TASKS > 0
63        && MAX_DEFERRED_DEVICE_CLEANUP_TASKS <= 64
64        && MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS > 0
65        && MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS <= 64
66);
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
69pub(crate) struct DeferredDeviceCleanupDomainId(NonZeroU64);
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub(crate) enum DeferredDeviceCleanupDisposition {
73    Completed,
74    Retryable,
75    Quarantined,
76}
77
78pub(crate) trait DeferredDeviceCleanupTask: Send + 'static {
79    fn try_cleanup(&mut self) -> DeferredDeviceCleanupDisposition;
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83enum DeferredDeviceCleanupTaskState {
84    Pending,
85    Retryable,
86    Quarantined,
87    Panicked,
88}
89
90struct DeferredDeviceCleanupEntry {
91    task_id: NonZeroU64,
92    task: Box<dyn DeferredDeviceCleanupTask>,
93    state: DeferredDeviceCleanupTaskState,
94}
95
96#[derive(Default)]
97struct DeferredDeviceCleanupDomain {
98    queued: VecDeque<DeferredDeviceCleanupEntry>,
99    in_progress: usize,
100    submitted_total: u64,
101    attempted_total: u64,
102    completed_total: u64,
103    panicked_total: u64,
104}
105
106#[derive(Default)]
107struct DeferredDeviceCleanupRegistry {
108    domains: BTreeMap<DeferredDeviceCleanupDomainId, DeferredDeviceCleanupDomain>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
112pub struct DeferredDeviceCleanupStatus {
113    queued: usize,
114    in_progress: usize,
115    retryable: usize,
116    quarantined: usize,
117    panicked: usize,
118    submitted_total: u64,
119    attempted_total: u64,
120    completed_total: u64,
121    panicked_total: u64,
122}
123
124impl DeferredDeviceCleanupStatus {
125    pub const fn queued(&self) -> usize {
126        self.queued
127    }
128
129    pub const fn in_progress(&self) -> usize {
130        self.in_progress
131    }
132
133    pub const fn pending(&self) -> usize {
134        self.queued + self.in_progress
135    }
136
137    pub const fn retryable(&self) -> usize {
138        self.retryable
139    }
140
141    pub const fn quarantined(&self) -> usize {
142        self.quarantined
143    }
144
145    pub const fn panicked(&self) -> usize {
146        self.panicked
147    }
148
149    pub const fn submitted_total(&self) -> u64 {
150        self.submitted_total
151    }
152
153    pub const fn attempted_total(&self) -> u64 {
154        self.attempted_total
155    }
156
157    pub const fn completed_total(&self) -> u64 {
158        self.completed_total
159    }
160
161    pub const fn panicked_total(&self) -> u64 {
162        self.panicked_total
163    }
164
165    pub const fn is_saturated(&self) -> bool {
166        self.pending() >= MAX_DEFERRED_DEVICE_CLEANUP_TASKS
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
171pub struct DeferredDeviceCleanupMaintenanceReceipt {
172    attempted: usize,
173    completed: usize,
174    retryable: usize,
175    quarantined: usize,
176    panicked: usize,
177    status_after: DeferredDeviceCleanupStatus,
178}
179
180impl DeferredDeviceCleanupMaintenanceReceipt {
181    pub const fn attempted(&self) -> usize {
182        self.attempted
183    }
184
185    pub const fn completed(&self) -> usize {
186        self.completed
187    }
188
189    pub const fn retryable(&self) -> usize {
190        self.retryable
191    }
192
193    pub const fn quarantined(&self) -> usize {
194        self.quarantined
195    }
196
197    pub const fn panicked(&self) -> usize {
198        self.panicked
199    }
200
201    pub const fn status_after(&self) -> &DeferredDeviceCleanupStatus {
202        &self.status_after
203    }
204}
205
206static NEXT_DEFERRED_DEVICE_CLEANUP_DOMAIN_ID: AtomicU64 = AtomicU64::new(1);
207static NEXT_DEFERRED_DEVICE_CLEANUP_TASK_ID: AtomicU64 = AtomicU64::new(1);
208static DEFERRED_DEVICE_CLEANUP_REGISTRY: OnceLock<Mutex<DeferredDeviceCleanupRegistry>> =
209    OnceLock::new();
210
211fn deferred_device_cleanup_registry() -> &'static Mutex<DeferredDeviceCleanupRegistry> {
212    DEFERRED_DEVICE_CLEANUP_REGISTRY
213        .get_or_init(|| Mutex::new(DeferredDeviceCleanupRegistry::default()))
214}
215
216fn lock_deferred_device_cleanup_registry() -> MutexGuard<'static, DeferredDeviceCleanupRegistry> {
217    deferred_device_cleanup_registry()
218        .lock()
219        .unwrap_or_else(std::sync::PoisonError::into_inner)
220}
221
222pub(crate) fn new_deferred_device_cleanup_domain() -> DeferredDeviceCleanupDomainId {
223    let raw = NEXT_DEFERRED_DEVICE_CLEANUP_DOMAIN_ID
224        .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
225            current.checked_add(1)
226        })
227        .expect("deferred device cleanup domain identity space is exhausted");
228    DeferredDeviceCleanupDomainId(
229        NonZeroU64::new(raw).expect("deferred device cleanup domain ids start at one"),
230    )
231}
232
233/// Transfers one aggregate owner into a process-reachable registry. This path
234/// performs no backend call and never drops or forgets a non-quiescent task.
235/// Recovery is driven explicitly through bounded maintenance on a scheduler
236/// recovery thread.
237pub(crate) fn defer_device_cleanup<T>(domain_id: DeferredDeviceCleanupDomainId, task: T)
238where
239    T: DeferredDeviceCleanupTask,
240{
241    let task_id = NEXT_DEFERRED_DEVICE_CLEANUP_TASK_ID
242        .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
243            current.checked_add(1)
244        })
245        .expect("deferred device cleanup task identity space is exhausted");
246    let mut registry = lock_deferred_device_cleanup_registry();
247    let domain = registry.domains.entry(domain_id).or_default();
248    domain.queued.push_back(DeferredDeviceCleanupEntry {
249        task_id: NonZeroU64::new(task_id).expect("deferred device cleanup task ids start at one"),
250        task: Box::new(task),
251        state: DeferredDeviceCleanupTaskState::Pending,
252    });
253    domain.submitted_total = domain.submitted_total.saturating_add(1);
254}
255
256pub(crate) fn deferred_device_cleanup_status(
257    domain_id: DeferredDeviceCleanupDomainId,
258) -> DeferredDeviceCleanupStatus {
259    let registry = lock_deferred_device_cleanup_registry();
260    registry
261        .domains
262        .get(&domain_id)
263        .map(deferred_device_cleanup_domain_status)
264        .unwrap_or_else(empty_deferred_device_cleanup_status)
265}
266
267pub(crate) fn maintain_deferred_device_cleanups(
268    domain_id: DeferredDeviceCleanupDomainId,
269    maximum_tasks: usize,
270) -> DeferredDeviceCleanupMaintenanceReceipt {
271    debug_assert!(
272        maximum_tasks > 0 && maximum_tasks <= MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS
273    );
274    let selected = {
275        let registry = lock_deferred_device_cleanup_registry();
276        registry
277            .domains
278            .get(&domain_id)
279            .map(|domain| {
280                domain
281                    .queued
282                    .iter()
283                    .take(maximum_tasks)
284                    .map(|entry| entry.task_id)
285                    .collect::<Vec<_>>()
286            })
287            .unwrap_or_default()
288    };
289
290    let mut attempted = 0;
291    let mut completed = 0;
292    let mut retryable = 0;
293    let mut quarantined = 0;
294    let mut panicked = 0;
295    for task_id in selected {
296        let Some(mut entry) = ({
297            let mut registry = lock_deferred_device_cleanup_registry();
298            let domain = registry.domains.entry(domain_id).or_default();
299            let entry = domain
300                .queued
301                .iter()
302                .position(|entry| entry.task_id == task_id)
303                .and_then(|position| domain.queued.remove(position));
304            if entry.is_some() {
305                domain.in_progress = domain.in_progress.saturating_add(1);
306            }
307            entry
308        }) else {
309            continue;
310        };
311        attempted += 1;
312        let outcome = catch_unwind(AssertUnwindSafe(|| entry.task.try_cleanup()));
313        let mut registry = lock_deferred_device_cleanup_registry();
314        let domain = registry.domains.entry(domain_id).or_default();
315        domain.in_progress = domain.in_progress.saturating_sub(1);
316        domain.attempted_total = domain.attempted_total.saturating_add(1);
317        match outcome {
318            Ok(DeferredDeviceCleanupDisposition::Completed) => {
319                domain.completed_total = domain.completed_total.saturating_add(1);
320                completed += 1;
321            }
322            Ok(DeferredDeviceCleanupDisposition::Retryable) => {
323                entry.state = DeferredDeviceCleanupTaskState::Retryable;
324                domain.queued.push_back(entry);
325                retryable += 1;
326            }
327            Ok(DeferredDeviceCleanupDisposition::Quarantined) => {
328                entry.state = DeferredDeviceCleanupTaskState::Quarantined;
329                domain.queued.push_back(entry);
330                quarantined += 1;
331            }
332            Err(_) => {
333                entry.state = DeferredDeviceCleanupTaskState::Panicked;
334                domain.queued.push_back(entry);
335                domain.panicked_total = domain.panicked_total.saturating_add(1);
336                panicked += 1;
337            }
338        }
339    }
340
341    let status_after = deferred_device_cleanup_status(domain_id);
342    DeferredDeviceCleanupMaintenanceReceipt {
343        attempted,
344        completed,
345        retryable,
346        quarantined,
347        panicked,
348        status_after,
349    }
350}
351
352pub(crate) fn retire_deferred_device_cleanup_domain(
353    domain_id: DeferredDeviceCleanupDomainId,
354) -> bool {
355    let mut registry = lock_deferred_device_cleanup_registry();
356    if registry
357        .domains
358        .get(&domain_id)
359        .is_some_and(|domain| !domain.queued.is_empty() || domain.in_progress != 0)
360    {
361        return false;
362    }
363    registry.domains.remove(&domain_id);
364    true
365}
366
367fn deferred_device_cleanup_domain_status(
368    domain: &DeferredDeviceCleanupDomain,
369) -> DeferredDeviceCleanupStatus {
370    DeferredDeviceCleanupStatus {
371        queued: domain.queued.len(),
372        in_progress: domain.in_progress,
373        retryable: domain
374            .queued
375            .iter()
376            .filter(|entry| entry.state == DeferredDeviceCleanupTaskState::Retryable)
377            .count(),
378        quarantined: domain
379            .queued
380            .iter()
381            .filter(|entry| entry.state == DeferredDeviceCleanupTaskState::Quarantined)
382            .count(),
383        panicked: domain
384            .queued
385            .iter()
386            .filter(|entry| entry.state == DeferredDeviceCleanupTaskState::Panicked)
387            .count(),
388        submitted_total: domain.submitted_total,
389        attempted_total: domain.attempted_total,
390        completed_total: domain.completed_total,
391        panicked_total: domain.panicked_total,
392    }
393}
394
395const fn empty_deferred_device_cleanup_status() -> DeferredDeviceCleanupStatus {
396    DeferredDeviceCleanupStatus {
397        queued: 0,
398        in_progress: 0,
399        retryable: 0,
400        quarantined: 0,
401        panicked: 0,
402        submitted_total: 0,
403        attempted_total: 0,
404        completed_total: 0,
405        panicked_total: 0,
406    }
407}
408
409/// Backend-neutral device classes. Concrete backend names do not belong here.
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
411#[serde(rename_all = "snake_case")]
412pub enum DeviceClass {
413    Host,
414    Accelerator,
415    Reference,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
419pub struct DeviceDescriptor {
420    pub id: DeviceId,
421    pub class: DeviceClass,
422    pub ordinal: u32,
423    pub total_memory_bytes: u64,
424    pub runtime_implementation_fingerprint: String,
425    pub capabilities: BTreeSet<CapabilityId>,
426    pub dynamic_storage_profiles: BTreeSet<DynamicStorageProfile>,
427}
428
429impl DeviceDescriptor {
430    pub fn validate(&self) -> Result<(), VNextError> {
431        if self.total_memory_bytes == 0
432            || self.dynamic_storage_profiles.is_empty()
433            || self.runtime_implementation_fingerprint.len() != 64
434            || !self
435                .runtime_implementation_fingerprint
436                .bytes()
437                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
438        {
439            return Err(VNextError::InvalidExecutionPlan {
440                reason: format!(
441                    "device `{}` has invalid capacity or runtime implementation fingerprint",
442                    self.id
443                ),
444            });
445        }
446        Ok(())
447    }
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
451pub struct BufferRequest {
452    resource_id: super::ResourceId,
453    size_bytes: u64,
454    alignment_bytes: u64,
455    usage: BufferUsage,
456    element_type: ElementType,
457}
458
459impl BufferRequest {
460    pub fn new(
461        resource_id: super::ResourceId,
462        size_bytes: u64,
463        alignment_bytes: u64,
464        usage: BufferUsage,
465        element_type: ElementType,
466    ) -> Result<Self, super::VNextError> {
467        if size_bytes == 0 || alignment_bytes == 0 || !alignment_bytes.is_power_of_two() {
468            return Err(super::VNextError::InvalidExecutionPlan {
469                reason: "buffer request has invalid size or alignment".to_owned(),
470            });
471        }
472        Ok(Self {
473            resource_id,
474            size_bytes,
475            alignment_bytes,
476            usage,
477            element_type,
478        })
479    }
480
481    pub fn resource_id(&self) -> &super::ResourceId {
482        &self.resource_id
483    }
484
485    pub fn size_bytes(&self) -> u64 {
486        self.size_bytes
487    }
488
489    pub fn alignment_bytes(&self) -> u64 {
490        self.alignment_bytes
491    }
492
493    pub fn usage(&self) -> BufferUsage {
494        self.usage
495    }
496
497    pub fn element_type(&self) -> ElementType {
498        self.element_type
499    }
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
503pub struct BufferDescriptor {
504    pub resource_id: super::ResourceId,
505    pub size_bytes: u64,
506    pub alignment_bytes: u64,
507    pub usage: BufferUsage,
508    pub element_type: ElementType,
509}
510
511/// Opaque core ownership retained by backend commands that outlive the
512/// borrowed buffer view used to encode them. Backends may clone and store this
513/// value, but cannot inspect or manufacture resource ownership.
514#[derive(Clone)]
515pub struct DeviceBufferRetention {
516    _primary_owner: Arc<dyn Send + Sync + 'static>,
517    _secondary_owner: Option<Arc<dyn Send + Sync + 'static>>,
518    reusable_address_scope: Option<DeviceReusableAddressScope>,
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
522pub enum DeviceReusableAddressScope {
523    Plan,
524    ExecutionLane(ExecutionLaneId),
525}
526
527impl DeviceBufferRetention {
528    pub(crate) fn plan<T>(owner: Arc<T>) -> Self
529    where
530        T: Send + Sync + 'static,
531    {
532        Self {
533            _primary_owner: owner,
534            _secondary_owner: None,
535            reusable_address_scope: Some(DeviceReusableAddressScope::Plan),
536        }
537    }
538
539    pub(crate) fn pair<T, U>(primary_owner: Arc<T>, secondary_owner: Arc<U>) -> Self
540    where
541        T: Send + Sync + 'static,
542        U: Send + Sync + 'static,
543    {
544        Self {
545            _primary_owner: primary_owner,
546            _secondary_owner: Some(secondary_owner),
547            reusable_address_scope: None,
548        }
549    }
550
551    pub(crate) fn lane_pair<T, U>(
552        lane_id: ExecutionLaneId,
553        primary_owner: Arc<T>,
554        secondary_owner: Arc<U>,
555    ) -> Self
556    where
557        T: Send + Sync + 'static,
558        U: Send + Sync + 'static,
559    {
560        Self {
561            _primary_owner: primary_owner,
562            _secondary_owner: Some(secondary_owner),
563            reusable_address_scope: Some(DeviceReusableAddressScope::ExecutionLane(lane_id)),
564        }
565    }
566
567    pub const fn reusable_address_scope(&self) -> Option<DeviceReusableAddressScope> {
568        self.reusable_address_scope
569    }
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
573#[serde(rename_all = "snake_case")]
574pub enum BufferUsage {
575    Weights,
576    Activations,
577    State,
578    /// Provider/runtime workspace whose lifetime spans operations but is not
579    /// model semantic state (for example packed metadata or persistent scratch).
580    Persistent,
581    /// Request-shaped provider control data written before reusable compute.
582    Binding,
583    Scratch,
584    Transfer,
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
588pub struct CopyRegion {
589    source_offset_bytes: u64,
590    destination_offset_bytes: u64,
591    length_bytes: u64,
592}
593
594impl CopyRegion {
595    pub fn new(
596        source_offset_bytes: u64,
597        destination_offset_bytes: u64,
598        length_bytes: u64,
599    ) -> Result<Self, super::VNextError> {
600        if length_bytes == 0
601            || source_offset_bytes.checked_add(length_bytes).is_none()
602            || destination_offset_bytes.checked_add(length_bytes).is_none()
603        {
604            return Err(super::VNextError::InvalidExecutionPlan {
605                reason: "copy region is empty or overflows u64".to_owned(),
606            });
607        }
608        Ok(Self {
609            source_offset_bytes,
610            destination_offset_bytes,
611            length_bytes,
612        })
613    }
614
615    pub fn validate_bounds(
616        &self,
617        source: &BufferDescriptor,
618        destination: &BufferDescriptor,
619    ) -> Result<(), super::VNextError> {
620        let source_end = self
621            .source_offset_bytes
622            .checked_add(self.length_bytes)
623            .ok_or_else(|| super::VNextError::InvalidExecutionPlan {
624                reason: "source copy range overflows u64".to_owned(),
625            })?;
626        let destination_end = self
627            .destination_offset_bytes
628            .checked_add(self.length_bytes)
629            .ok_or_else(|| super::VNextError::InvalidExecutionPlan {
630                reason: "destination copy range overflows u64".to_owned(),
631            })?;
632        if source_end > source.size_bytes || destination_end > destination.size_bytes {
633            return Err(super::VNextError::InvalidExecutionPlan {
634                reason: "copy region exceeds a buffer boundary".to_owned(),
635            });
636        }
637        Ok(())
638    }
639
640    pub fn source_offset_bytes(self) -> u64 {
641        self.source_offset_bytes
642    }
643
644    pub fn destination_offset_bytes(self) -> u64 {
645        self.destination_offset_bytes
646    }
647
648    pub fn length_bytes(self) -> u64 {
649        self.length_bytes
650    }
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
654#[serde(rename_all = "snake_case")]
655pub enum StreamState {
656    Ready,
657    Recording,
658    Submitted,
659    Failed,
660}
661
662/// Backend timing is enabled monotonically before product requests start.
663/// `Off` must not allocate backend events or add host clock reads to the hot
664/// path; `Completion` measures only the existing submission terminal and
665/// readback boundaries; `Replay` measures physical executable/eager spans;
666/// `Kernel`
667/// additionally attributes backend-observed physical work to immutable-plan
668/// node indices. `Verification` retains full logical/kernel attribution.
669/// Execution-path selection and scratch initialization are independent typed
670/// submission policy; timing cannot silently change either one.
671#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
672#[repr(u8)]
673#[serde(rename_all = "snake_case")]
674pub enum DeviceTimingMode {
675    #[default]
676    Off = 0,
677    Completion = 1,
678    Replay = 2,
679    Kernel = 3,
680    Verification = 4,
681}
682
683impl DeviceTimingMode {
684    pub const fn completion_enabled(self) -> bool {
685        !matches!(self, Self::Off)
686    }
687
688    pub const fn physical_span_attribution_enabled(self) -> bool {
689        matches!(self, Self::Replay | Self::Kernel | Self::Verification)
690    }
691
692    pub const fn kernel_attribution_enabled(self) -> bool {
693        matches!(self, Self::Kernel | Self::Verification)
694    }
695
696    pub const fn direct_reusable_execution_allowed(self) -> bool {
697        !matches!(self, Self::Kernel | Self::Verification)
698    }
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
702#[serde(rename_all = "snake_case")]
703pub enum DeviceExecutionPath {
704    Eager,
705    Replayed,
706}
707
708impl DeviceExecutionPath {
709    pub const fn as_str(self) -> &'static str {
710        match self {
711            Self::Eager => "eager",
712            Self::Replayed => "replayed",
713        }
714    }
715}
716
717/// Required compute implementation for one physical submission.
718///
719/// Initialization and dynamic/result binding commands remain eager boundaries;
720/// this requirement applies only to provider compute. Backends must reject a
721/// batch before submission when they cannot honor the requested path.
722#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
723#[serde(rename_all = "snake_case")]
724pub enum DeviceComputePathRequirement {
725    #[default]
726    Adaptive,
727    EagerOnly,
728    ReplayedOnly,
729    /// Every replay-eligible node must execute from a sealed resident segment,
730    /// while only core-declared topology boundaries may remain eager.
731    ReplayedWithDeclaredEagerBoundaries,
732}
733
734/// Backend evidence required for one physical submission.
735///
736/// This is independent from timing: deterministic correctness needs actual
737/// eager/replay path attribution without enabling per-kernel profiling or
738/// changing the selected compute path.
739#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
740#[serde(rename_all = "snake_case")]
741pub enum DeviceSubmissionAttributionRequirement {
742    #[default]
743    None,
744    LogicalExecutionPath,
745}
746
747impl DeviceSubmissionAttributionRequirement {
748    pub const fn logical_execution_path_required(self) -> bool {
749        matches!(self, Self::LogicalExecutionPath)
750    }
751}
752
753#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
754#[serde(rename_all = "snake_case")]
755pub enum DeviceBatchingForm {
756    Scalar,
757    Packed,
758    ParticipantLoop,
759}
760
761impl DeviceBatchingForm {
762    pub const fn as_str(self) -> &'static str {
763        match self {
764            Self::Scalar => "scalar",
765            Self::Packed => "packed",
766            Self::ParticipantLoop => "participant_loop",
767        }
768    }
769}
770
771/// Core-owned logical work bound to a node-scoped device command.
772///
773/// Providers describe their own command work directly. Core-created commands,
774/// such as scratch initialization, use this value so backend-native
775/// attribution remains bound to the exact batch node instead of inventing a
776/// participant or token count.
777#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
778pub struct DeviceCommandLogicalWork {
779    batching_form: DeviceBatchingForm,
780    participant_start: u32,
781    participant_count: u32,
782    token_count: u64,
783}
784
785impl DeviceCommandLogicalWork {
786    pub fn new(
787        batching_form: DeviceBatchingForm,
788        participant_count: u32,
789        token_count: u64,
790    ) -> Result<Self, super::VNextError> {
791        Self::for_participant_range(batching_form, 0, participant_count, token_count)
792    }
793
794    pub fn for_participant_range(
795        batching_form: DeviceBatchingForm,
796        participant_start: u32,
797        participant_count: u32,
798        token_count: u64,
799    ) -> Result<Self, super::VNextError> {
800        if participant_count == 0 || participant_start.checked_add(participant_count).is_none() {
801            return Err(super::VNextError::InvalidExecutionPlan {
802                reason: "node-scoped device command has an empty or overflowing logical participant range"
803                    .to_owned(),
804            });
805        }
806        Ok(Self {
807            batching_form,
808            participant_start,
809            participant_count,
810            token_count,
811        })
812    }
813
814    pub const fn batching_form(self) -> DeviceBatchingForm {
815        self.batching_form
816    }
817
818    pub const fn participant_start(self) -> u32 {
819        self.participant_start
820    }
821
822    pub const fn participant_count(self) -> u32 {
823        self.participant_count
824    }
825
826    pub const fn participant_end(self) -> u32 {
827        self.participant_start + self.participant_count
828    }
829
830    pub const fn token_count(self) -> u64 {
831        self.token_count
832    }
833}
834
835/// Typed host boundaries inside one backend submission. These intervals use
836/// the host monotonic clock and must not be combined with device-event time.
837#[derive(Debug, Clone, Copy, PartialEq, Eq)]
838pub enum DeviceSubmissionStage {
839    ValidateAndPrepare,
840    BeginTiming,
841    EnqueueCommands,
842    RecordFenceAndAccount,
843}
844
845/// Aggregate reusable-execution work observed inside one backend submission.
846///
847/// The observation carries no execution authority and is recorded only by an
848/// enabled diagnostic sink. Backends update one stack value while submitting;
849/// the product sink aggregates it without allocating on the hot path.
850#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
851pub struct DeviceReusableExecutionObservation {
852    candidate_segments: u64,
853    captured_segments: u64,
854    uploaded_segments: u64,
855    cache_hit_segments: u64,
856    cached_rejected_segments: u64,
857    capture_rejected_segments: u64,
858    quiescence_deferred_segments: u64,
859    capacity_deferred_segments: u64,
860    outside_preparation_segments: u64,
861    evicted_segments: u64,
862    replayed_segments: u64,
863    replayed_commands: u64,
864    eager_commands: u64,
865}
866
867impl DeviceReusableExecutionObservation {
868    pub fn observe_candidate_segment(&mut self) {
869        self.candidate_segments = self.candidate_segments.saturating_add(1);
870    }
871
872    pub fn observe_captured_segment(&mut self) {
873        self.captured_segments = self.captured_segments.saturating_add(1);
874    }
875
876    pub fn observe_uploaded_segment(&mut self) {
877        self.uploaded_segments = self.uploaded_segments.saturating_add(1);
878    }
879
880    pub fn observe_cache_hit_segment(&mut self) {
881        self.cache_hit_segments = self.cache_hit_segments.saturating_add(1);
882    }
883
884    pub fn observe_cached_rejected_segment(&mut self) {
885        self.cached_rejected_segments = self.cached_rejected_segments.saturating_add(1);
886    }
887
888    pub fn observe_capture_rejection(&mut self) {
889        self.capture_rejected_segments = self.capture_rejected_segments.saturating_add(1);
890    }
891
892    pub fn observe_quiescence_deferred_segment(&mut self) {
893        self.quiescence_deferred_segments = self.quiescence_deferred_segments.saturating_add(1);
894    }
895
896    pub fn observe_capacity_deferred_segment(&mut self) {
897        self.capacity_deferred_segments = self.capacity_deferred_segments.saturating_add(1);
898    }
899
900    pub fn observe_outside_preparation_segment(&mut self) {
901        self.outside_preparation_segments = self.outside_preparation_segments.saturating_add(1);
902    }
903
904    pub fn observe_evicted_segment(&mut self) {
905        self.evicted_segments = self.evicted_segments.saturating_add(1);
906    }
907
908    pub fn observe_replayed_segment(&mut self, command_count: usize) {
909        self.replayed_segments = self.replayed_segments.saturating_add(1);
910        self.replayed_commands = self
911            .replayed_commands
912            .saturating_add(u64::try_from(command_count).unwrap_or(u64::MAX));
913    }
914
915    pub fn observe_eager_command(&mut self) {
916        self.eager_commands = self.eager_commands.saturating_add(1);
917    }
918
919    pub const fn candidate_segments(self) -> u64 {
920        self.candidate_segments
921    }
922
923    pub const fn captured_segments(self) -> u64 {
924        self.captured_segments
925    }
926
927    pub const fn uploaded_segments(self) -> u64 {
928        self.uploaded_segments
929    }
930
931    pub const fn cache_hit_segments(self) -> u64 {
932        self.cache_hit_segments
933    }
934
935    pub const fn cached_rejected_segments(self) -> u64 {
936        self.cached_rejected_segments
937    }
938
939    pub const fn capture_rejected_segments(self) -> u64 {
940        self.capture_rejected_segments
941    }
942
943    pub const fn quiescence_deferred_segments(self) -> u64 {
944        self.quiescence_deferred_segments
945    }
946
947    pub const fn capacity_deferred_segments(self) -> u64 {
948        self.capacity_deferred_segments
949    }
950
951    pub const fn outside_preparation_segments(self) -> u64 {
952        self.outside_preparation_segments
953    }
954
955    pub const fn evicted_segments(self) -> u64 {
956        self.evicted_segments
957    }
958
959    pub const fn replayed_segments(self) -> u64 {
960        self.replayed_segments
961    }
962
963    pub const fn replayed_commands(self) -> u64 {
964        self.replayed_commands
965    }
966
967    pub const fn eager_commands(self) -> u64 {
968        self.eager_commands
969    }
970}
971
972/// Cold-path receipt for releasing backend reusable executables after an
973/// execution lane has reached proven quiescence.
974#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
975pub struct DeviceReusableExecutionTrim {
976    released_executables: u64,
977    released_rejections: u64,
978}
979
980/// Cold-path capacity selected by the model execution plan before reusable
981/// device executables are prepared.
982///
983/// The value is an upper bound on resident executable descriptors, not a
984/// hardware- or model-name heuristic. Product composition derives it from the
985/// immutable execution plan and the startup shape matrix.
986#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
987pub struct DeviceReusableExecutionPlan {
988    maximum_executables: usize,
989}
990
991impl DeviceReusableExecutionPlan {
992    pub fn new(maximum_executables: usize) -> Result<Self, super::VNextError> {
993        if maximum_executables == 0 {
994            return Err(super::VNextError::InvalidExecutionPlan {
995                reason: "reusable execution plan requires non-zero capacity".to_owned(),
996            });
997        }
998        Ok(Self {
999            maximum_executables,
1000        })
1001    }
1002
1003    pub const fn maximum_executables(self) -> usize {
1004        self.maximum_executables
1005    }
1006}
1007
1008/// Opaque provider-owned topology identity for one reusable compute program.
1009///
1010/// The core aggregates these fixed-size values with node and provider identity.
1011/// Backends keep kernel-selection details private while the program catalog can
1012/// still reject a stale executable before any command is encoded or submitted.
1013#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
1014#[serde(transparent)]
1015pub struct DeviceReusableExecutionTopologyFingerprint([u8; 32]);
1016
1017impl DeviceReusableExecutionTopologyFingerprint {
1018    pub const fn from_sha256(bytes: [u8; 32]) -> Self {
1019        Self(bytes)
1020    }
1021
1022    pub const fn static_program() -> Self {
1023        Self([0; 32])
1024    }
1025
1026    pub const fn as_bytes(&self) -> &[u8; 32] {
1027        &self.0
1028    }
1029}
1030
1031/// Runtime-local identity for one immutable reusable program.
1032///
1033/// The bucket binds execution topology and capacity, while the plan and lane
1034/// bind provider semantics and every lane-stable physical address. Request and
1035/// sequence identities are deliberately excluded because their live state is
1036/// supplied through explicit per-wave bindings.
1037#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
1038pub struct DeviceReusableExecutionProgramId {
1039    plan_hash: PlanHash,
1040    runtime_implementation_fingerprint: String,
1041    lane_id: ExecutionLaneId,
1042    bucket_id: ReusableExecutionBucketId,
1043    program_binding_layout_fingerprint: String,
1044    lane_stable_layout_fingerprint: String,
1045    lane_slot_id: u64,
1046    immediate_sequences: u32,
1047    immediate_tokens: u64,
1048    immediate_pages: u64,
1049    topology_fingerprint: DeviceReusableExecutionTopologyFingerprint,
1050}
1051
1052impl DeviceReusableExecutionProgramId {
1053    pub fn new(
1054        plan_hash: PlanHash,
1055        runtime_implementation_fingerprint: String,
1056        lane_id: ExecutionLaneId,
1057        bucket_id: ReusableExecutionBucketId,
1058        program_binding_layout_fingerprint: String,
1059        lane_stable_layout_fingerprint: String,
1060        lane_slot_id: u64,
1061        immediate_sequences: u32,
1062        immediate_tokens: u64,
1063        immediate_pages: u64,
1064    ) -> Result<Self, VNextError> {
1065        let is_sha256 = |value: &str| {
1066            let digest = value.strip_prefix("sha256/").unwrap_or(value);
1067            digest.len() == 64
1068                && digest
1069                    .bytes()
1070                    .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
1071        };
1072        if !is_sha256(&runtime_implementation_fingerprint)
1073            || !is_sha256(&program_binding_layout_fingerprint)
1074            || !is_sha256(&lane_stable_layout_fingerprint)
1075            || immediate_sequences == 0
1076            || immediate_tokens == 0
1077        {
1078            return Err(VNextError::InvalidExecutionPlan {
1079                reason: "reusable execution program identity is incomplete or non-canonical"
1080                    .to_owned(),
1081            });
1082        }
1083        Ok(Self {
1084            plan_hash,
1085            runtime_implementation_fingerprint,
1086            lane_id,
1087            bucket_id,
1088            program_binding_layout_fingerprint,
1089            lane_stable_layout_fingerprint,
1090            lane_slot_id,
1091            immediate_sequences,
1092            immediate_tokens,
1093            immediate_pages,
1094            topology_fingerprint: DeviceReusableExecutionTopologyFingerprint::static_program(),
1095        })
1096    }
1097
1098    pub fn with_topology_fingerprint(
1099        mut self,
1100        topology_fingerprint: DeviceReusableExecutionTopologyFingerprint,
1101    ) -> Self {
1102        self.topology_fingerprint = topology_fingerprint;
1103        self
1104    }
1105
1106    pub fn plan_hash(&self) -> &PlanHash {
1107        &self.plan_hash
1108    }
1109
1110    pub fn runtime_implementation_fingerprint(&self) -> &str {
1111        &self.runtime_implementation_fingerprint
1112    }
1113
1114    pub const fn lane_id(&self) -> ExecutionLaneId {
1115        self.lane_id
1116    }
1117
1118    pub fn bucket_id(&self) -> &ReusableExecutionBucketId {
1119        &self.bucket_id
1120    }
1121
1122    pub fn program_binding_layout_fingerprint(&self) -> &str {
1123        &self.program_binding_layout_fingerprint
1124    }
1125
1126    pub fn lane_stable_layout_fingerprint(&self) -> &str {
1127        &self.lane_stable_layout_fingerprint
1128    }
1129
1130    pub const fn lane_slot_id(&self) -> u64 {
1131        self.lane_slot_id
1132    }
1133
1134    pub const fn immediate_sequences(&self) -> u32 {
1135        self.immediate_sequences
1136    }
1137
1138    pub const fn immediate_tokens(&self) -> u64 {
1139        self.immediate_tokens
1140    }
1141
1142    pub const fn immediate_pages(&self) -> u64 {
1143        self.immediate_pages
1144    }
1145
1146    pub const fn topology_fingerprint(&self) -> DeviceReusableExecutionTopologyFingerprint {
1147        self.topology_fingerprint
1148    }
1149
1150    /// Stable identity used by detached execution evidence. This hashes every
1151    /// field that selects one exact resident program without serializing
1152    /// backend-private executable contents.
1153    pub fn fingerprint(&self) -> String {
1154        const DOMAIN: &[u8] = b"ferrum.runtime-vnext.reusable-program-id.v1\0";
1155        let mut digest = Sha256::new();
1156        digest.update(DOMAIN);
1157        for value in [
1158            self.plan_hash.as_str(),
1159            self.runtime_implementation_fingerprint.as_str(),
1160            self.bucket_id.as_str(),
1161            self.program_binding_layout_fingerprint.as_str(),
1162            self.lane_stable_layout_fingerprint.as_str(),
1163        ] {
1164            digest.update(
1165                u64::try_from(value.len())
1166                    .expect("validated reusable program identity strings fit u64")
1167                    .to_le_bytes(),
1168            );
1169            digest.update(value.as_bytes());
1170        }
1171        digest.update(self.lane_id.get().to_le_bytes());
1172        digest.update(self.lane_slot_id.to_le_bytes());
1173        digest.update(self.immediate_sequences.to_le_bytes());
1174        digest.update(self.immediate_tokens.to_le_bytes());
1175        digest.update(self.immediate_pages.to_le_bytes());
1176        digest.update(self.topology_fingerprint.as_bytes());
1177        format!("{:x}", digest.finalize())
1178    }
1179}
1180
1181/// Core metadata attached to a full eager encoding while a backend prepares
1182/// reusable programs. The backend may publish a catalog entry only when every
1183/// referenced segment is resident and the observed node topology is stable.
1184#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1185pub struct DeviceReusableExecutionCapture {
1186    program_id: DeviceReusableExecutionProgramId,
1187    node_count: u32,
1188    eager_boundary_node_indices: Box<[u32]>,
1189    per_wave_binding_node_indices: Box<[u32]>,
1190}
1191
1192impl DeviceReusableExecutionCapture {
1193    pub fn new(
1194        program_id: DeviceReusableExecutionProgramId,
1195        node_count: u32,
1196        eager_boundary_node_indices: Vec<u32>,
1197        mut per_wave_binding_node_indices: Vec<u32>,
1198    ) -> Result<Self, VNextError> {
1199        if node_count == 0
1200            || eager_boundary_node_indices
1201                .windows(2)
1202                .any(|pair| pair[0] >= pair[1])
1203            || eager_boundary_node_indices
1204                .iter()
1205                .any(|node_index| *node_index >= node_count)
1206        {
1207            return Err(VNextError::InvalidExecutionPlan {
1208                reason: "reusable execution capture topology is empty or non-canonical".to_owned(),
1209            });
1210        }
1211        per_wave_binding_node_indices.sort_unstable();
1212        per_wave_binding_node_indices.dedup();
1213        if per_wave_binding_node_indices
1214            .iter()
1215            .any(|node_index| *node_index >= node_count)
1216        {
1217            return Err(VNextError::InvalidExecutionPlan {
1218                reason: "reusable execution binding node is outside the captured topology"
1219                    .to_owned(),
1220            });
1221        }
1222        Ok(Self {
1223            program_id,
1224            node_count,
1225            eager_boundary_node_indices: eager_boundary_node_indices.into_boxed_slice(),
1226            per_wave_binding_node_indices: per_wave_binding_node_indices.into_boxed_slice(),
1227        })
1228    }
1229
1230    pub fn program_id(&self) -> &DeviceReusableExecutionProgramId {
1231        &self.program_id
1232    }
1233
1234    pub const fn node_count(&self) -> u32 {
1235        self.node_count
1236    }
1237
1238    pub fn eager_boundary_node_indices(&self) -> &[u32] {
1239        &self.eager_boundary_node_indices
1240    }
1241
1242    pub fn per_wave_binding_node_indices(&self) -> &[u32] {
1243        &self.per_wave_binding_node_indices
1244    }
1245}
1246
1247/// Exact reason why one replay-eligible plan node is absent from a resident
1248/// reusable executable. These rows preserve the cold-path failure class after
1249/// the backend publishes a partial product catalog.
1250#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1251#[serde(rename_all = "snake_case")]
1252pub enum DeviceReusableExecutionProgramGapReason {
1253    MissingComputeCommand,
1254    ProviderReplayKeyMissing,
1255    ReusableAddressScopeMissing,
1256    ReusableAddressScopeConflict,
1257    CaptureRejected,
1258    CachedCaptureRejected,
1259    QuiescenceDeferred,
1260    CapacityDeferred,
1261    Evicted,
1262    OutsidePreparation,
1263}
1264
1265impl DeviceReusableExecutionProgramGapReason {
1266    pub const fn as_str(self) -> &'static str {
1267        match self {
1268            Self::MissingComputeCommand => "missing_compute_command",
1269            Self::ProviderReplayKeyMissing => "provider_replay_key_missing",
1270            Self::ReusableAddressScopeMissing => "reusable_address_scope_missing",
1271            Self::ReusableAddressScopeConflict => "reusable_address_scope_conflict",
1272            Self::CaptureRejected => "capture_rejected",
1273            Self::CachedCaptureRejected => "cached_capture_rejected",
1274            Self::QuiescenceDeferred => "quiescence_deferred",
1275            Self::CapacityDeferred => "capacity_deferred",
1276            Self::Evicted => "evicted",
1277            Self::OutsidePreparation => "outside_preparation",
1278        }
1279    }
1280}
1281
1282#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1283#[serde(rename_all = "snake_case")]
1284pub enum DeviceReusableExecutionProgramState {
1285    Partial,
1286    DeterminismReady,
1287}
1288
1289/// One classified replay-eligible gap in a partial reusable program.
1290#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1291pub struct DeviceReusableExecutionProgramGap {
1292    node_index: u32,
1293    reason: DeviceReusableExecutionProgramGapReason,
1294}
1295
1296impl DeviceReusableExecutionProgramGap {
1297    pub const fn new(node_index: u32, reason: DeviceReusableExecutionProgramGapReason) -> Self {
1298        Self { node_index, reason }
1299    }
1300
1301    pub const fn node_index(self) -> u32 {
1302        self.node_index
1303    }
1304
1305    pub const fn reason(self) -> DeviceReusableExecutionProgramGapReason {
1306        self.reason
1307    }
1308}
1309
1310/// One contiguous node range owned by a resident backend executable.
1311#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1312pub struct DeviceReusableExecutionSegment {
1313    ordinal: u32,
1314    start_node_index: u32,
1315    end_node_index: u32,
1316    logical_command_count: u32,
1317}
1318
1319impl DeviceReusableExecutionSegment {
1320    pub fn new(
1321        ordinal: u32,
1322        start_node_index: u32,
1323        end_node_index: u32,
1324        logical_command_count: u32,
1325    ) -> Result<Self, VNextError> {
1326        if end_node_index <= start_node_index || logical_command_count == 0 {
1327            return Err(VNextError::InvalidExecutionPlan {
1328                reason: "reusable execution segment is empty".to_owned(),
1329            });
1330        }
1331        Ok(Self {
1332            ordinal,
1333            start_node_index,
1334            end_node_index,
1335            logical_command_count,
1336        })
1337    }
1338
1339    pub const fn ordinal(&self) -> u32 {
1340        self.ordinal
1341    }
1342
1343    pub const fn start_node_index(&self) -> u32 {
1344        self.start_node_index
1345    }
1346
1347    pub const fn end_node_index(&self) -> u32 {
1348        self.end_node_index
1349    }
1350
1351    pub const fn logical_command_count(&self) -> u32 {
1352        self.logical_command_count
1353    }
1354
1355    pub const fn contains_node(&self, node_index: u32) -> bool {
1356        node_index >= self.start_node_index && node_index < self.end_node_index
1357    }
1358}
1359
1360/// Catalog row assembled during backend preparation and published only after
1361/// that preparation window is sealed. A partial row may contain only typed
1362/// gaps; product requests can reference resident segments, while determinism
1363/// requires [`Self::is_determinism_ready`].
1364#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1365pub struct DeviceReusableExecutionProgram {
1366    program_id: DeviceReusableExecutionProgramId,
1367    node_count: u32,
1368    eager_boundary_node_indices: Box<[u32]>,
1369    segments: Box<[DeviceReusableExecutionSegment]>,
1370    per_wave_binding_node_indices: Box<[u32]>,
1371    gaps: Box<[DeviceReusableExecutionProgramGap]>,
1372}
1373
1374impl DeviceReusableExecutionProgram {
1375    pub fn new(
1376        capture: &DeviceReusableExecutionCapture,
1377        segments: Vec<DeviceReusableExecutionSegment>,
1378        mut per_wave_binding_node_indices: Vec<u32>,
1379        gaps: Vec<DeviceReusableExecutionProgramGap>,
1380    ) -> Result<Self, VNextError> {
1381        if (segments.is_empty() && gaps.is_empty())
1382            || segments
1383                .iter()
1384                .enumerate()
1385                .any(|(ordinal, segment)| segment.ordinal() as usize != ordinal)
1386            || segments
1387                .windows(2)
1388                .any(|pair| pair[0].end_node_index() > pair[1].start_node_index())
1389        {
1390            return Err(VNextError::InvalidExecutionPlan {
1391                reason: "reusable execution program segments are empty, unordered, or overlap"
1392                    .to_owned(),
1393            });
1394        }
1395        let node_count = capture.node_count() as usize;
1396        let mut coverage = vec![0_u8; node_count];
1397        for node_index in capture.eager_boundary_node_indices() {
1398            coverage[*node_index as usize] = 1;
1399        }
1400        for segment in &segments {
1401            let start = segment.start_node_index() as usize;
1402            let end = segment.end_node_index() as usize;
1403            let Some(range) = coverage.get_mut(start..end) else {
1404                return Err(VNextError::InvalidExecutionPlan {
1405                    reason: "reusable execution segment is outside the captured topology"
1406                        .to_owned(),
1407                });
1408            };
1409            if range.iter().any(|marker| *marker != 0) {
1410                return Err(VNextError::InvalidExecutionPlan {
1411                    reason: "reusable execution segment overlaps an eager boundary or another classification"
1412                        .to_owned(),
1413                });
1414            }
1415            range.fill(2);
1416        }
1417        if gaps
1418            .windows(2)
1419            .any(|pair| pair[0].node_index() >= pair[1].node_index())
1420        {
1421            return Err(VNextError::InvalidExecutionPlan {
1422                reason: "reusable execution program gaps are unordered or duplicated".to_owned(),
1423            });
1424        }
1425        for gap in &gaps {
1426            let Some(marker) = coverage.get_mut(gap.node_index() as usize) else {
1427                return Err(VNextError::InvalidExecutionPlan {
1428                    reason: "reusable execution program gap is outside the captured topology"
1429                        .to_owned(),
1430                });
1431            };
1432            if *marker != 0 {
1433                return Err(VNextError::InvalidExecutionPlan {
1434                    reason: "reusable execution program gap overlaps a resident segment or eager boundary"
1435                        .to_owned(),
1436                });
1437            }
1438            *marker = 3;
1439        }
1440        if coverage.iter().any(|marker| *marker == 0) {
1441            return Err(VNextError::InvalidExecutionPlan {
1442                reason: "reusable execution program does not classify every captured topology node"
1443                    .to_owned(),
1444            });
1445        }
1446        per_wave_binding_node_indices.sort_unstable();
1447        per_wave_binding_node_indices.dedup();
1448        if per_wave_binding_node_indices.iter().any(|node_index| {
1449            !segments
1450                .iter()
1451                .any(|segment| segment.contains_node(*node_index))
1452        }) {
1453            return Err(VNextError::InvalidExecutionPlan {
1454                reason: "reusable execution binding node is outside every resident segment"
1455                    .to_owned(),
1456            });
1457        }
1458        Ok(Self {
1459            program_id: capture.program_id().clone(),
1460            node_count: capture.node_count(),
1461            eager_boundary_node_indices: capture
1462                .eager_boundary_node_indices()
1463                .to_vec()
1464                .into_boxed_slice(),
1465            segments: segments.into_boxed_slice(),
1466            per_wave_binding_node_indices: per_wave_binding_node_indices.into_boxed_slice(),
1467            gaps: gaps.into_boxed_slice(),
1468        })
1469    }
1470
1471    pub fn program_id(&self) -> &DeviceReusableExecutionProgramId {
1472        &self.program_id
1473    }
1474
1475    pub const fn node_count(&self) -> u32 {
1476        self.node_count
1477    }
1478
1479    pub fn eager_boundary_node_indices(&self) -> &[u32] {
1480        &self.eager_boundary_node_indices
1481    }
1482
1483    pub fn segments(&self) -> &[DeviceReusableExecutionSegment] {
1484        &self.segments
1485    }
1486
1487    pub fn per_wave_binding_node_indices(&self) -> &[u32] {
1488        &self.per_wave_binding_node_indices
1489    }
1490
1491    pub fn gaps(&self) -> &[DeviceReusableExecutionProgramGap] {
1492        &self.gaps
1493    }
1494
1495    pub const fn state(&self) -> DeviceReusableExecutionProgramState {
1496        if self.gaps.is_empty() {
1497            DeviceReusableExecutionProgramState::DeterminismReady
1498        } else {
1499            DeviceReusableExecutionProgramState::Partial
1500        }
1501    }
1502
1503    pub fn has_resident_segments(&self) -> bool {
1504        !self.segments.is_empty()
1505    }
1506
1507    pub fn is_determinism_ready(&self) -> bool {
1508        self.state() == DeviceReusableExecutionProgramState::DeterminismReady
1509    }
1510}
1511
1512/// One exact invocation of a segment from the sealed reusable program catalog.
1513#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1514pub struct DeviceReusableExecutionInvocation {
1515    program_id: DeviceReusableExecutionProgramId,
1516    segment: DeviceReusableExecutionSegment,
1517    participant_count: u32,
1518    token_count: u64,
1519}
1520
1521impl DeviceReusableExecutionInvocation {
1522    pub fn new(
1523        program_id: DeviceReusableExecutionProgramId,
1524        segment: DeviceReusableExecutionSegment,
1525        participant_count: u32,
1526        token_count: u64,
1527    ) -> Result<Self, VNextError> {
1528        if participant_count == 0
1529            || token_count == 0
1530            || program_id.immediate_sequences() != participant_count
1531            || program_id.immediate_tokens() != token_count
1532        {
1533            return Err(VNextError::InvalidExecutionPlan {
1534                reason: "reusable execution invocation differs from its program work shape"
1535                    .to_owned(),
1536            });
1537        }
1538        Ok(Self {
1539            program_id,
1540            segment,
1541            participant_count,
1542            token_count,
1543        })
1544    }
1545
1546    pub fn program_id(&self) -> &DeviceReusableExecutionProgramId {
1547        &self.program_id
1548    }
1549
1550    pub const fn segment(&self) -> &DeviceReusableExecutionSegment {
1551        &self.segment
1552    }
1553
1554    pub const fn participant_count(&self) -> u32 {
1555        self.participant_count
1556    }
1557
1558    pub const fn token_count(&self) -> u64 {
1559        self.token_count
1560    }
1561}
1562
1563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1564#[serde(rename_all = "snake_case")]
1565pub enum DeviceReusableExecutionPreparationState {
1566    Unsupported,
1567    Preparing,
1568    Ready,
1569}
1570
1571/// Backend receipt for the explicit configure -> prepare -> seal lifecycle.
1572///
1573/// Captures happen only between `Preparing` and `Ready`. Once sealed, a
1574/// backend must replay a resident executable or use eager execution; it must
1575/// not compile new work on a product request.
1576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1577pub struct DeviceReusableExecutionPreparation {
1578    state: DeviceReusableExecutionPreparationState,
1579    maximum_executables: u64,
1580    resident_executables: u64,
1581    rejected_executables: u64,
1582    captured_executables: u64,
1583    uploaded_executables: u64,
1584    capacity_deferred_executables: u64,
1585}
1586
1587impl DeviceReusableExecutionPreparation {
1588    pub const fn unsupported() -> Self {
1589        Self {
1590            state: DeviceReusableExecutionPreparationState::Unsupported,
1591            maximum_executables: 0,
1592            resident_executables: 0,
1593            rejected_executables: 0,
1594            captured_executables: 0,
1595            uploaded_executables: 0,
1596            capacity_deferred_executables: 0,
1597        }
1598    }
1599
1600    pub fn preparing(plan: DeviceReusableExecutionPlan) -> Self {
1601        Self {
1602            state: DeviceReusableExecutionPreparationState::Preparing,
1603            maximum_executables: u64::try_from(plan.maximum_executables()).unwrap_or(u64::MAX),
1604            ..Self::unsupported()
1605        }
1606    }
1607
1608    pub fn preparing_with_progress(
1609        plan: DeviceReusableExecutionPlan,
1610        resident_executables: usize,
1611        rejected_executables: usize,
1612        captured_executables: u64,
1613        uploaded_executables: u64,
1614        capacity_deferred_executables: u64,
1615    ) -> Result<Self, super::VNextError> {
1616        Self::with_progress(
1617            DeviceReusableExecutionPreparationState::Preparing,
1618            plan,
1619            resident_executables,
1620            rejected_executables,
1621            captured_executables,
1622            uploaded_executables,
1623            capacity_deferred_executables,
1624        )
1625    }
1626
1627    pub fn ready(
1628        plan: DeviceReusableExecutionPlan,
1629        resident_executables: usize,
1630        rejected_executables: usize,
1631        captured_executables: u64,
1632        uploaded_executables: u64,
1633        capacity_deferred_executables: u64,
1634    ) -> Result<Self, super::VNextError> {
1635        Self::with_progress(
1636            DeviceReusableExecutionPreparationState::Ready,
1637            plan,
1638            resident_executables,
1639            rejected_executables,
1640            captured_executables,
1641            uploaded_executables,
1642            capacity_deferred_executables,
1643        )
1644    }
1645
1646    fn with_progress(
1647        state: DeviceReusableExecutionPreparationState,
1648        plan: DeviceReusableExecutionPlan,
1649        resident_executables: usize,
1650        rejected_executables: usize,
1651        captured_executables: u64,
1652        uploaded_executables: u64,
1653        capacity_deferred_executables: u64,
1654    ) -> Result<Self, super::VNextError> {
1655        if resident_executables > plan.maximum_executables()
1656            || uploaded_executables < u64::try_from(resident_executables).unwrap_or(u64::MAX)
1657            || captured_executables < uploaded_executables
1658        {
1659            return Err(super::VNextError::InvalidExecutionPlan {
1660                reason: "reusable execution preparation receipt is internally inconsistent"
1661                    .to_owned(),
1662            });
1663        }
1664        Ok(Self {
1665            state,
1666            maximum_executables: u64::try_from(plan.maximum_executables()).unwrap_or(u64::MAX),
1667            resident_executables: u64::try_from(resident_executables).unwrap_or(u64::MAX),
1668            rejected_executables: u64::try_from(rejected_executables).unwrap_or(u64::MAX),
1669            captured_executables,
1670            uploaded_executables,
1671            capacity_deferred_executables,
1672        })
1673    }
1674
1675    pub const fn state(self) -> DeviceReusableExecutionPreparationState {
1676        self.state
1677    }
1678
1679    pub const fn maximum_executables(self) -> u64 {
1680        self.maximum_executables
1681    }
1682
1683    pub const fn resident_executables(self) -> u64 {
1684        self.resident_executables
1685    }
1686
1687    pub const fn rejected_executables(self) -> u64 {
1688        self.rejected_executables
1689    }
1690
1691    pub const fn captured_executables(self) -> u64 {
1692        self.captured_executables
1693    }
1694
1695    pub const fn uploaded_executables(self) -> u64 {
1696        self.uploaded_executables
1697    }
1698
1699    pub const fn capacity_deferred_executables(self) -> u64 {
1700        self.capacity_deferred_executables
1701    }
1702}
1703
1704impl DeviceReusableExecutionTrim {
1705    pub fn new(released_executables: usize, released_rejections: usize) -> Self {
1706        Self {
1707            released_executables: u64::try_from(released_executables).unwrap_or(u64::MAX),
1708            released_rejections: u64::try_from(released_rejections).unwrap_or(u64::MAX),
1709        }
1710    }
1711
1712    pub const fn released_executables(self) -> u64 {
1713        self.released_executables
1714    }
1715
1716    pub const fn released_rejections(self) -> u64 {
1717        self.released_rejections
1718    }
1719}
1720
1721/// Diagnostic-only sink for backend submission attribution.
1722///
1723/// `ENABLED = false` is the compile-time off path: a backend must not read a
1724/// clock or call `record_device_submission` in that specialization. Enabled
1725/// implementations run on the submission thread and must not block, allocate,
1726/// or panic.
1727pub trait DeviceSubmissionTimingSink: Send + Sync {
1728    const ENABLED: bool;
1729
1730    fn record_device_submission(&self, stage: DeviceSubmissionStage, elapsed: Duration);
1731
1732    fn record_reusable_execution(&self, _observation: DeviceReusableExecutionObservation) {}
1733}
1734
1735pub struct DisabledDeviceSubmissionTimingSink;
1736
1737impl DeviceSubmissionTimingSink for DisabledDeviceSubmissionTimingSink {
1738    const ENABLED: bool = false;
1739
1740    fn record_device_submission(&self, _stage: DeviceSubmissionStage, _elapsed: Duration) {
1741        unreachable!("disabled device submission timing cannot record")
1742    }
1743}
1744
1745#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1746#[serde(rename_all = "snake_case")]
1747pub enum DeviceTimingUnavailableReason {
1748    BackendUnsupported,
1749    BackendMeasurementFailed,
1750    DurationOverflow,
1751}
1752
1753#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1754#[serde(rename_all = "snake_case", tag = "status", content = "detail")]
1755pub enum DeviceTimingMeasurement<T> {
1756    NotRequested,
1757    Measured(T),
1758    Unavailable(DeviceTimingUnavailableReason),
1759}
1760
1761impl<T> DeviceTimingMeasurement<T> {
1762    pub const fn measured(&self) -> Option<&T> {
1763        match self {
1764            Self::Measured(measured) => Some(measured),
1765            Self::NotRequested | Self::Unavailable(_) => None,
1766        }
1767    }
1768}
1769
1770#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1771#[serde(rename_all = "snake_case")]
1772pub enum DeviceTimingClock {
1773    DeviceEventElapsed,
1774}
1775
1776#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1777pub struct DeviceExecutionTiming {
1778    elapsed_ns: u64,
1779    clock: DeviceTimingClock,
1780}
1781
1782impl DeviceExecutionTiming {
1783    pub const fn device_event_elapsed(elapsed_ns: u64) -> Self {
1784        Self {
1785            elapsed_ns,
1786            clock: DeviceTimingClock::DeviceEventElapsed,
1787        }
1788    }
1789
1790    pub const fn elapsed_ns(self) -> u64 {
1791        self.elapsed_ns
1792    }
1793
1794    pub const fn clock(self) -> DeviceTimingClock {
1795        self.clock
1796    }
1797}
1798
1799/// One backend-counter interval relative to the first sampled command in an
1800/// exact submission. Intervals remain in a device elapsed-time domain; they
1801/// must not be subtracted from host timestamps.
1802#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1803#[serde(rename_all = "snake_case")]
1804pub enum DeviceExecutionIntervalKind {
1805    Compute,
1806    Transfer,
1807}
1808
1809impl DeviceExecutionIntervalKind {
1810    pub const fn as_str(self) -> &'static str {
1811        match self {
1812            Self::Compute => "compute",
1813            Self::Transfer => "transfer",
1814        }
1815    }
1816}
1817
1818#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1819pub struct DeviceExecutionInterval {
1820    kind: DeviceExecutionIntervalKind,
1821    start_offset_ns: u64,
1822    end_offset_ns: u64,
1823    subwork_id: Option<&'static str>,
1824}
1825
1826impl DeviceExecutionInterval {
1827    pub fn new(
1828        kind: DeviceExecutionIntervalKind,
1829        start_offset_ns: u64,
1830        end_offset_ns: u64,
1831    ) -> Option<Self> {
1832        (end_offset_ns > start_offset_ns).then_some(Self {
1833            kind,
1834            start_offset_ns,
1835            end_offset_ns,
1836            subwork_id: None,
1837        })
1838    }
1839
1840    pub fn new_labeled(
1841        kind: DeviceExecutionIntervalKind,
1842        start_offset_ns: u64,
1843        end_offset_ns: u64,
1844        subwork_id: &'static str,
1845    ) -> Option<Self> {
1846        (!subwork_id.is_empty() && end_offset_ns > start_offset_ns).then_some(Self {
1847            kind,
1848            start_offset_ns,
1849            end_offset_ns,
1850            subwork_id: Some(subwork_id),
1851        })
1852    }
1853
1854    pub const fn kind(self) -> DeviceExecutionIntervalKind {
1855        self.kind
1856    }
1857
1858    pub const fn start_offset_ns(self) -> u64 {
1859        self.start_offset_ns
1860    }
1861
1862    pub const fn end_offset_ns(self) -> u64 {
1863        self.end_offset_ns
1864    }
1865
1866    pub const fn subwork_id(self) -> Option<&'static str> {
1867        self.subwork_id
1868    }
1869
1870    pub const fn elapsed_ns(self) -> u64 {
1871        self.end_offset_ns - self.start_offset_ns
1872    }
1873}
1874
1875/// Backend-counter timing for one command entry in a core-owned submission.
1876/// A command may own multiple physical encoder intervals, for example a
1877/// gather-compute-scatter implementation.
1878#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1879pub struct DeviceCommandExecutionTiming {
1880    command_index: u32,
1881    intervals: Box<[DeviceExecutionInterval]>,
1882    elapsed_ns: u64,
1883}
1884
1885impl DeviceCommandExecutionTiming {
1886    pub fn new(command_index: u32, intervals: Vec<DeviceExecutionInterval>) -> Option<Self> {
1887        if intervals.is_empty()
1888            || intervals
1889                .windows(2)
1890                .any(|pair| pair[0].end_offset_ns() > pair[1].start_offset_ns())
1891        {
1892            return None;
1893        }
1894        let elapsed_ns = intervals.iter().try_fold(0_u64, |total, interval| {
1895            total.checked_add(interval.elapsed_ns())
1896        })?;
1897        Some(Self {
1898            command_index,
1899            intervals: intervals.into_boxed_slice(),
1900            elapsed_ns,
1901        })
1902    }
1903
1904    pub const fn command_index(&self) -> u32 {
1905        self.command_index
1906    }
1907
1908    pub fn intervals(&self) -> &[DeviceExecutionInterval] {
1909        &self.intervals
1910    }
1911
1912    pub fn elapsed_ns(&self) -> u64 {
1913        self.elapsed_ns
1914    }
1915}
1916
1917#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1918#[serde(rename_all = "snake_case")]
1919pub enum DeviceExecutionSpanKind {
1920    EagerCommand,
1921    ReusableExecutable,
1922}
1923
1924impl DeviceExecutionSpanKind {
1925    pub const fn as_str(self) -> &'static str {
1926        match self {
1927            Self::EagerCommand => "eager_command",
1928            Self::ReusableExecutable => "reusable_executable",
1929        }
1930    }
1931}
1932
1933#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1934#[serde(rename_all = "snake_case", tag = "status", content = "detail")]
1935pub enum DeviceExecutionSpanMeasurement {
1936    Measured {
1937        intervals: Box<[DeviceExecutionInterval]>,
1938        elapsed_ns: u64,
1939    },
1940    Unavailable(DeviceTimingUnavailableReason),
1941}
1942
1943impl DeviceExecutionSpanMeasurement {
1944    pub fn measured(intervals: Vec<DeviceExecutionInterval>) -> Option<Self> {
1945        if intervals.is_empty()
1946            || intervals
1947                .windows(2)
1948                .any(|pair| pair[0].end_offset_ns() > pair[1].start_offset_ns())
1949        {
1950            return None;
1951        }
1952        let elapsed_ns = intervals.iter().try_fold(0_u64, |total, interval| {
1953            total.checked_add(interval.elapsed_ns())
1954        })?;
1955        Some(Self::Measured {
1956            intervals: intervals.into_boxed_slice(),
1957            elapsed_ns,
1958        })
1959    }
1960
1961    pub const fn unavailable(reason: DeviceTimingUnavailableReason) -> Self {
1962        Self::Unavailable(reason)
1963    }
1964
1965    pub fn intervals(&self) -> Option<&[DeviceExecutionInterval]> {
1966        match self {
1967            Self::Measured { intervals, .. } => Some(intervals),
1968            Self::Unavailable(_) => None,
1969        }
1970    }
1971
1972    pub const fn elapsed_ns(&self) -> Option<u64> {
1973        match self {
1974            Self::Measured { elapsed_ns, .. } => Some(*elapsed_ns),
1975            Self::Unavailable(_) => None,
1976        }
1977    }
1978
1979    pub const fn unavailable_reason(&self) -> Option<DeviceTimingUnavailableReason> {
1980        match self {
1981            Self::Measured { .. } => None,
1982            Self::Unavailable(reason) => Some(*reason),
1983        }
1984    }
1985}
1986
1987/// One physical device interval owner inside an exact submission.
1988///
1989/// Eager spans own one core command. Reusable executable spans own one
1990/// contiguous command range, because a single CUDA graph launch cannot be
1991/// truthfully duplicated across each logical command it contains.
1992#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1993pub struct DeviceSubmissionExecutionSpan {
1994    start_command_index: u32,
1995    end_command_index: u32,
1996    kind: DeviceExecutionSpanKind,
1997    measurement: DeviceExecutionSpanMeasurement,
1998    #[serde(skip_serializing_if = "Option::is_none")]
1999    reusable_executable_fingerprint: Option<Box<str>>,
2000}
2001
2002impl DeviceSubmissionExecutionSpan {
2003    pub fn measured(
2004        start_command_index: u32,
2005        end_command_index: u32,
2006        kind: DeviceExecutionSpanKind,
2007        intervals: Vec<DeviceExecutionInterval>,
2008    ) -> Option<Self> {
2009        let measurement = DeviceExecutionSpanMeasurement::measured(intervals)?;
2010        Self::new(start_command_index, end_command_index, kind, measurement)
2011    }
2012
2013    pub fn unavailable(
2014        start_command_index: u32,
2015        end_command_index: u32,
2016        kind: DeviceExecutionSpanKind,
2017        reason: DeviceTimingUnavailableReason,
2018    ) -> Option<Self> {
2019        Self::new(
2020            start_command_index,
2021            end_command_index,
2022            kind,
2023            DeviceExecutionSpanMeasurement::unavailable(reason),
2024        )
2025    }
2026
2027    fn new(
2028        start_command_index: u32,
2029        end_command_index: u32,
2030        kind: DeviceExecutionSpanKind,
2031        measurement: DeviceExecutionSpanMeasurement,
2032    ) -> Option<Self> {
2033        if end_command_index <= start_command_index
2034            || (kind == DeviceExecutionSpanKind::EagerCommand
2035                && end_command_index != start_command_index.checked_add(1)?)
2036        {
2037            return None;
2038        }
2039        Some(Self {
2040            start_command_index,
2041            end_command_index,
2042            kind,
2043            measurement,
2044            reusable_executable_fingerprint: None,
2045        })
2046    }
2047
2048    pub fn with_reusable_executable_fingerprint(mut self, fingerprint: String) -> Option<Self> {
2049        if self.kind != DeviceExecutionSpanKind::ReusableExecutable
2050            || fingerprint.len() != 64
2051            || !fingerprint
2052                .bytes()
2053                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2054        {
2055            return None;
2056        }
2057        self.reusable_executable_fingerprint = Some(fingerprint.into_boxed_str());
2058        Some(self)
2059    }
2060
2061    fn from_command(command: DeviceCommandExecutionTiming) -> Option<Self> {
2062        let end_command_index = command.command_index.checked_add(1)?;
2063        Self::measured(
2064            command.command_index,
2065            end_command_index,
2066            DeviceExecutionSpanKind::EagerCommand,
2067            command.intervals.into_vec(),
2068        )
2069    }
2070
2071    pub const fn start_command_index(&self) -> u32 {
2072        self.start_command_index
2073    }
2074
2075    pub const fn end_command_index(&self) -> u32 {
2076        self.end_command_index
2077    }
2078
2079    pub const fn command_count(&self) -> u32 {
2080        self.end_command_index - self.start_command_index
2081    }
2082
2083    pub const fn kind(&self) -> DeviceExecutionSpanKind {
2084        self.kind
2085    }
2086
2087    pub const fn measurement(&self) -> &DeviceExecutionSpanMeasurement {
2088        &self.measurement
2089    }
2090
2091    pub fn reusable_executable_fingerprint(&self) -> Option<&str> {
2092        self.reusable_executable_fingerprint.as_deref()
2093    }
2094
2095    pub const fn contains_command(&self, command_index: u32) -> bool {
2096        command_index >= self.start_command_index && command_index < self.end_command_index
2097    }
2098}
2099
2100/// Terminal backend-counter evidence for one exact submission. Physical spans
2101/// cover every core command exactly once, remain ordered, and may explicitly
2102/// mark a range unavailable without discarding measured sibling spans.
2103#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2104pub struct DeviceSubmissionExecutionTiming {
2105    command_count: u32,
2106    spans: Box<[DeviceSubmissionExecutionSpan]>,
2107}
2108
2109impl DeviceSubmissionExecutionTiming {
2110    pub fn new(commands: Vec<DeviceCommandExecutionTiming>) -> Option<Self> {
2111        let command_count = commands.last()?.command_index().checked_add(1)?;
2112        let spans = commands
2113            .into_iter()
2114            .map(DeviceSubmissionExecutionSpan::from_command)
2115            .collect::<Option<Vec<_>>>()?;
2116        Self::from_spans(command_count, spans)
2117    }
2118
2119    pub fn from_spans(
2120        command_count: u32,
2121        spans: Vec<DeviceSubmissionExecutionSpan>,
2122    ) -> Option<Self> {
2123        if command_count == 0 || spans.is_empty() {
2124            return None;
2125        }
2126        let mut expected_start = 0_u32;
2127        for span in &spans {
2128            if span.start_command_index() != expected_start
2129                || span.end_command_index() > command_count
2130            {
2131                return None;
2132            }
2133            expected_start = span.end_command_index();
2134        }
2135        if expected_start != command_count {
2136            return None;
2137        }
2138        Some(Self {
2139            command_count,
2140            spans: spans.into_boxed_slice(),
2141        })
2142    }
2143
2144    pub const fn command_count(&self) -> u32 {
2145        self.command_count
2146    }
2147
2148    pub fn spans(&self) -> &[DeviceSubmissionExecutionSpan] {
2149        &self.spans
2150    }
2151
2152    pub fn span_for_command(&self, command_index: u32) -> Option<&DeviceSubmissionExecutionSpan> {
2153        let index = self
2154            .spans
2155            .partition_point(|span| span.end_command_index() <= command_index);
2156        self.spans
2157            .get(index)
2158            .filter(|span| span.contains_command(command_index))
2159    }
2160}
2161
2162/// A terminal and its optional backend clock evidence are inseparable. This
2163/// prevents timing from being queried before the exact fence proves quiescence.
2164#[derive(Debug, Serialize)]
2165#[must_use = "a device terminal receipt owns exact fence timing evidence"]
2166pub struct DeviceTerminalReceipt<E> {
2167    terminal: DeviceTerminal<E>,
2168    execution_timing: DeviceTimingMeasurement<DeviceExecutionTiming>,
2169    submission_timing: DeviceTimingMeasurement<DeviceSubmissionExecutionTiming>,
2170}
2171
2172impl<E> DeviceTerminalReceipt<E> {
2173    pub fn unprofiled(terminal: DeviceTerminal<E>) -> Self {
2174        Self {
2175            terminal,
2176            execution_timing: DeviceTimingMeasurement::NotRequested,
2177            submission_timing: DeviceTimingMeasurement::NotRequested,
2178        }
2179    }
2180
2181    pub fn profiled(
2182        terminal: DeviceTerminal<E>,
2183        execution_timing: DeviceTimingMeasurement<DeviceExecutionTiming>,
2184    ) -> Self {
2185        Self {
2186            terminal,
2187            execution_timing,
2188            submission_timing: DeviceTimingMeasurement::NotRequested,
2189        }
2190    }
2191
2192    pub fn profiled_with_submission_timing(
2193        terminal: DeviceTerminal<E>,
2194        execution_timing: DeviceTimingMeasurement<DeviceExecutionTiming>,
2195        submission_timing: DeviceTimingMeasurement<DeviceSubmissionExecutionTiming>,
2196    ) -> Self {
2197        Self {
2198            terminal,
2199            execution_timing,
2200            submission_timing,
2201        }
2202    }
2203
2204    pub const fn terminal(&self) -> &DeviceTerminal<E> {
2205        &self.terminal
2206    }
2207
2208    pub const fn execution_timing(&self) -> &DeviceTimingMeasurement<DeviceExecutionTiming> {
2209        &self.execution_timing
2210    }
2211
2212    pub const fn submission_timing(
2213        &self,
2214    ) -> &DeviceTimingMeasurement<DeviceSubmissionExecutionTiming> {
2215        &self.submission_timing
2216    }
2217
2218    pub fn into_parts(
2219        self,
2220    ) -> (
2221        DeviceTerminal<E>,
2222        DeviceTimingMeasurement<DeviceExecutionTiming>,
2223        DeviceTimingMeasurement<DeviceSubmissionExecutionTiming>,
2224    ) {
2225        (self.terminal, self.execution_timing, self.submission_timing)
2226    }
2227}
2228
2229/// A submit failure that guarantees no device-visible work was enqueued.
2230///
2231/// This wrapper is deliberately distinct from an arbitrary runtime error:
2232/// only this outcome may release prepared resources or authorize an exact
2233/// retry without first reaching a fence terminal state.
2234#[derive(Debug, Serialize)]
2235#[must_use = "a definitely-not-submitted failure owns the only safe retry classification"]
2236pub struct DefinitelyNotSubmitted<E> {
2237    error: E,
2238}
2239
2240impl<E> DefinitelyNotSubmitted<E> {
2241    pub fn new(error: E) -> Self {
2242        Self { error }
2243    }
2244
2245    pub fn error(&self) -> &E {
2246        &self.error
2247    }
2248
2249    pub fn into_error(self) -> E {
2250        self.error
2251    }
2252}
2253
2254/// A quiescent device terminal. Both variants prove that command-owned
2255/// buffers are no longer accessed by the device.
2256#[derive(Debug, Serialize)]
2257#[serde(rename_all = "snake_case", tag = "status", content = "error")]
2258#[must_use = "a device terminal determines whether in-flight resources are release-safe"]
2259pub enum DeviceTerminal<E> {
2260    Succeeded,
2261    FailedButQuiescent(E),
2262}
2263
2264impl<E> DeviceTerminal<E> {
2265    pub const fn is_succeeded(&self) -> bool {
2266        matches!(self, Self::Succeeded)
2267    }
2268}
2269
2270/// Non-blocking fence observation. An indeterminate query retains the fence
2271/// and routes ownership to blocking recovery; it is not a terminal failure.
2272#[derive(Debug, Serialize)]
2273#[serde(rename_all = "snake_case", tag = "status", content = "detail")]
2274#[must_use = "a fence query must preserve pending or indeterminate ownership"]
2275pub enum FenceQuery<E> {
2276    Pending,
2277    Terminal(DeviceTerminalReceipt<E>),
2278    Indeterminate(E),
2279}
2280
2281impl<E> FenceQuery<E> {
2282    pub const fn is_pending(&self) -> bool {
2283        matches!(self, Self::Pending)
2284    }
2285}
2286
2287/// Blocking wait could not prove fence quiescence. The fence and all
2288/// in-flight ownership must remain retained for lane recovery or quarantine.
2289#[derive(Debug, Serialize)]
2290#[must_use = "an indeterminate fence retains recovery and quarantine ownership"]
2291pub struct FenceIndeterminate<E> {
2292    error: E,
2293}
2294
2295impl<E> FenceIndeterminate<E> {
2296    pub fn new(error: E) -> Self {
2297        Self { error }
2298    }
2299
2300    pub fn error(&self) -> &E {
2301        &self.error
2302    }
2303
2304    pub fn into_error(self) -> E {
2305        self.error
2306    }
2307}
2308
2309/// Backend-provided description of one device error. The backend cannot pick
2310/// a failure domain or execution identity; core attaches both after checking
2311/// the concrete runtime device.
2312#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2313pub struct DeviceErrorReport {
2314    failure: FailureEnvelope,
2315}
2316
2317impl DeviceErrorReport {
2318    pub fn new(
2319        code: impl Into<String>,
2320        message: impl Into<String>,
2321        retryable: bool,
2322    ) -> Result<Self, VNextError> {
2323        Ok(Self {
2324            failure: FailureEnvelope::new(FailureDomain::Device, code, message, retryable)?,
2325        })
2326    }
2327
2328    pub fn code(&self) -> &str {
2329        self.failure.code()
2330    }
2331
2332    pub fn message(&self) -> &str {
2333        self.failure.message()
2334    }
2335
2336    pub const fn retryable(&self) -> bool {
2337        self.failure.retryable()
2338    }
2339
2340    fn into_failure(self) -> FailureEnvelope {
2341        self.failure
2342    }
2343}
2344
2345#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2346pub struct HostTransferLayout {
2347    element_type: ElementType,
2348    element_count: u64,
2349}
2350
2351impl HostTransferLayout {
2352    pub fn new(element_type: ElementType, element_count: u64) -> Result<Self, super::VNextError> {
2353        if element_count == 0
2354            || element_count
2355                .checked_mul(element_type.size_bytes())
2356                .is_none()
2357        {
2358            return Err(super::VNextError::InvalidExecutionPlan {
2359                reason: "host transfer layout is empty or overflows u64".to_owned(),
2360            });
2361        }
2362        Ok(Self {
2363            element_type,
2364            element_count,
2365        })
2366    }
2367
2368    pub fn byte_len(self) -> Result<u64, super::VNextError> {
2369        self.element_count
2370            .checked_mul(self.element_type.size_bytes())
2371            .ok_or_else(|| super::VNextError::InvalidExecutionPlan {
2372                reason: "host transfer byte count overflows u64".to_owned(),
2373            })
2374    }
2375
2376    pub fn validate_bytes(self, bytes: usize) -> Result<(), super::VNextError> {
2377        if self.byte_len()? != bytes as u64 {
2378            return Err(super::VNextError::InvalidExecutionPlan {
2379                reason: "host transfer byte count does not match its element layout".to_owned(),
2380            });
2381        }
2382        Ok(())
2383    }
2384
2385    pub fn element_type(self) -> ElementType {
2386        self.element_type
2387    }
2388
2389    pub fn element_count(self) -> u64 {
2390        self.element_count
2391    }
2392}
2393
2394/// Semantic phase of one command inside a core-owned submission batch.
2395///
2396/// Backends may use this phase to compile reusable device executables, but
2397/// they must preserve the original ordering and may only reuse `Compute`
2398/// commands whose backend provider supplied an exact replay contract.
2399/// Initialization, dynamic binding, and result binding commands are explicit
2400/// eager barriers: replaying them can reset live state, reuse stale request
2401/// data, or write results into an earlier request's backing.
2402#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2403#[serde(rename_all = "snake_case")]
2404pub enum DeviceCommandPhase {
2405    Initialization,
2406    DynamicBinding,
2407    Compute,
2408    ResultBinding,
2409}
2410
2411/// Stable machine identity for backend-native work attribution.
2412///
2413/// This identity is emitted into replay, determinism, and profile artifacts,
2414/// so it must remain portable across filesystems and analysis tools. Human
2415/// display labels belong in backend errors rather than this field.
2416#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
2417#[serde(transparent)]
2418pub struct DeviceNativeOperationId(&'static str);
2419
2420impl DeviceNativeOperationId {
2421    pub const MAX_BYTES: usize = 256;
2422
2423    pub const fn new(value: &'static str) -> Option<Self> {
2424        let bytes = value.as_bytes();
2425        if bytes.is_empty() || bytes.len() > Self::MAX_BYTES {
2426            return None;
2427        }
2428        let mut index = 0;
2429        while index < bytes.len() {
2430            let byte = bytes[index];
2431            if !matches!(
2432                byte,
2433                b'a'..=b'z'
2434                    | b'A'..=b'Z'
2435                    | b'0'..=b'9'
2436                    | b'.'
2437                    | b'_'
2438                    | b':'
2439                    | b'/'
2440                    | b'-'
2441            ) {
2442                return None;
2443            }
2444            index += 1;
2445        }
2446        Some(Self(value))
2447    }
2448
2449    pub const fn as_str(self) -> &'static str {
2450        self.0
2451    }
2452
2453    const fn built_in(value: &'static str) -> Self {
2454        match Self::new(value) {
2455            Some(identity) => identity,
2456            None => panic!("built-in native operation identity must be portable"),
2457        }
2458    }
2459}
2460
2461pub const DEVICE_COPY_NATIVE_OPERATION_ID: DeviceNativeOperationId =
2462    DeviceNativeOperationId::built_in("device.copy");
2463pub const HOST_UPLOAD_NATIVE_OPERATION_ID: DeviceNativeOperationId =
2464    DeviceNativeOperationId::built_in("host.upload");
2465pub const DEVICE_ZERO_NATIVE_OPERATION_ID: DeviceNativeOperationId =
2466    DeviceNativeOperationId::built_in("device.zero");
2467
2468/// Backend-observed physical work for one core-owned command entry.
2469///
2470/// Rows are created only when core explicitly requests attribution. The node
2471/// index is issued by core and binds backend work back to the immutable plan;
2472/// backend labels and counters carry observation only and grant no execution
2473/// authority.
2474#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2475pub struct DeviceNativeWorkAttribution {
2476    command_index: u32,
2477    node_index: Option<u32>,
2478    command_phase: DeviceCommandPhase,
2479    native_op_id: DeviceNativeOperationId,
2480    execution_path: DeviceExecutionPath,
2481    batching_form: DeviceBatchingForm,
2482    participant_start: u32,
2483    participant_count: u32,
2484    token_count: u64,
2485    compute_dispatch_count: u64,
2486    transfer_command_count: u64,
2487    reusable_graph_node_count: Option<u64>,
2488}
2489
2490impl DeviceNativeWorkAttribution {
2491    #[allow(clippy::too_many_arguments)]
2492    pub fn new(
2493        command_index: u32,
2494        node_index: Option<u32>,
2495        command_phase: DeviceCommandPhase,
2496        native_op_id: DeviceNativeOperationId,
2497        execution_path: DeviceExecutionPath,
2498        batching_form: DeviceBatchingForm,
2499        participant_count: u32,
2500        token_count: u64,
2501        compute_dispatch_count: u64,
2502        transfer_command_count: u64,
2503        reusable_graph_node_count: Option<u64>,
2504    ) -> Option<Self> {
2505        Self::with_participant_range(
2506            command_index,
2507            node_index,
2508            command_phase,
2509            native_op_id,
2510            execution_path,
2511            batching_form,
2512            0,
2513            participant_count,
2514            token_count,
2515            compute_dispatch_count,
2516            transfer_command_count,
2517            reusable_graph_node_count,
2518        )
2519    }
2520
2521    #[allow(clippy::too_many_arguments)]
2522    pub fn with_participant_range(
2523        command_index: u32,
2524        node_index: Option<u32>,
2525        command_phase: DeviceCommandPhase,
2526        native_op_id: DeviceNativeOperationId,
2527        execution_path: DeviceExecutionPath,
2528        batching_form: DeviceBatchingForm,
2529        participant_start: u32,
2530        participant_count: u32,
2531        token_count: u64,
2532        compute_dispatch_count: u64,
2533        transfer_command_count: u64,
2534        reusable_graph_node_count: Option<u64>,
2535    ) -> Option<Self> {
2536        if (compute_dispatch_count == 0 && transfer_command_count == 0)
2537            || (node_index.is_some() && participant_count == 0)
2538            || participant_start.checked_add(participant_count).is_none()
2539            || (node_index.is_none() && participant_start != 0)
2540            || (reusable_graph_node_count.is_some()
2541                && execution_path != DeviceExecutionPath::Replayed)
2542        {
2543            return None;
2544        }
2545        Some(Self {
2546            command_index,
2547            node_index,
2548            command_phase,
2549            native_op_id,
2550            execution_path,
2551            batching_form,
2552            participant_start,
2553            participant_count,
2554            token_count,
2555            compute_dispatch_count,
2556            transfer_command_count,
2557            reusable_graph_node_count,
2558        })
2559    }
2560
2561    pub const fn command_index(&self) -> u32 {
2562        self.command_index
2563    }
2564
2565    pub const fn node_index(&self) -> Option<u32> {
2566        self.node_index
2567    }
2568
2569    pub const fn command_phase(&self) -> DeviceCommandPhase {
2570        self.command_phase
2571    }
2572
2573    pub const fn native_op_id(&self) -> &'static str {
2574        self.native_op_id.as_str()
2575    }
2576
2577    pub const fn execution_path(&self) -> DeviceExecutionPath {
2578        self.execution_path
2579    }
2580
2581    pub const fn batching_form(&self) -> DeviceBatchingForm {
2582        self.batching_form
2583    }
2584
2585    pub const fn participant_start(&self) -> u32 {
2586        self.participant_start
2587    }
2588
2589    pub const fn participant_count(&self) -> u32 {
2590        self.participant_count
2591    }
2592
2593    pub const fn participant_end(&self) -> u32 {
2594        self.participant_start + self.participant_count
2595    }
2596
2597    pub const fn token_count(&self) -> u64 {
2598        self.token_count
2599    }
2600
2601    pub const fn compute_dispatch_count(&self) -> u64 {
2602        self.compute_dispatch_count
2603    }
2604
2605    pub const fn transfer_command_count(&self) -> u64 {
2606        self.transfer_command_count
2607    }
2608
2609    /// Actual native graph nodes captured for this replayed command.
2610    ///
2611    /// This observation may include kernels, copies, memsets, and dependency
2612    /// nodes selected internally by a native library.
2613    pub const fn reusable_graph_node_count(&self) -> Option<u64> {
2614        self.reusable_graph_node_count
2615    }
2616}
2617
2618/// One logical plan-node command sealed inside a physical reusable executable.
2619///
2620/// A CUDA graph segment launches as one physical command, but release
2621/// determinism must still prove which immutable-plan nodes were replayed and
2622/// how much native graph work each logical command contributed.
2623#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2624pub struct DeviceReplayedLogicalCommandAttribution {
2625    logical_command_ordinal: u32,
2626    node_index: u32,
2627    native_op_id: DeviceNativeOperationId,
2628    batching_form: DeviceBatchingForm,
2629    participant_count: u32,
2630    token_count: u64,
2631    compute_dispatch_count: u64,
2632    transfer_command_count: u64,
2633    reusable_graph_node_count: u64,
2634}
2635
2636impl DeviceReplayedLogicalCommandAttribution {
2637    #[allow(clippy::too_many_arguments)]
2638    pub fn new(
2639        logical_command_ordinal: u32,
2640        node_index: u32,
2641        native_op_id: DeviceNativeOperationId,
2642        batching_form: DeviceBatchingForm,
2643        participant_count: u32,
2644        token_count: u64,
2645        compute_dispatch_count: u64,
2646        transfer_command_count: u64,
2647        reusable_graph_node_count: u64,
2648    ) -> Option<Self> {
2649        if participant_count == 0
2650            || (compute_dispatch_count == 0 && transfer_command_count == 0)
2651            || reusable_graph_node_count == 0
2652        {
2653            return None;
2654        }
2655        Some(Self {
2656            logical_command_ordinal,
2657            node_index,
2658            native_op_id,
2659            batching_form,
2660            participant_count,
2661            token_count,
2662            compute_dispatch_count,
2663            transfer_command_count,
2664            reusable_graph_node_count,
2665        })
2666    }
2667
2668    pub const fn logical_command_ordinal(&self) -> u32 {
2669        self.logical_command_ordinal
2670    }
2671
2672    pub const fn node_index(&self) -> u32 {
2673        self.node_index
2674    }
2675
2676    pub const fn native_op_id(&self) -> &'static str {
2677        self.native_op_id.as_str()
2678    }
2679
2680    pub const fn batching_form(&self) -> DeviceBatchingForm {
2681        self.batching_form
2682    }
2683
2684    pub const fn participant_count(&self) -> u32 {
2685        self.participant_count
2686    }
2687
2688    pub const fn token_count(&self) -> u64 {
2689        self.token_count
2690    }
2691
2692    pub const fn compute_dispatch_count(&self) -> u64 {
2693        self.compute_dispatch_count
2694    }
2695
2696    pub const fn transfer_command_count(&self) -> u64 {
2697        self.transfer_command_count
2698    }
2699
2700    pub const fn reusable_graph_node_count(&self) -> u64 {
2701        self.reusable_graph_node_count
2702    }
2703}
2704
2705/// Logical-node attribution for one physical reusable executable launch.
2706///
2707/// Physical timing remains indexed by `physical_command_index`; the ordered
2708/// logical rows bind that launch back to every plan node captured in the
2709/// sealed program segment.
2710#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2711pub struct DeviceReplayedSegmentAttribution {
2712    physical_command_index: u32,
2713    program_id: DeviceReusableExecutionProgramId,
2714    segment: DeviceReusableExecutionSegment,
2715    reusable_executable_fingerprint: String,
2716    logical_commands: Box<[DeviceReplayedLogicalCommandAttribution]>,
2717}
2718
2719impl DeviceReplayedSegmentAttribution {
2720    pub fn new(
2721        physical_command_index: u32,
2722        program_id: DeviceReusableExecutionProgramId,
2723        segment: DeviceReusableExecutionSegment,
2724        reusable_executable_fingerprint: String,
2725        logical_commands: Vec<DeviceReplayedLogicalCommandAttribution>,
2726    ) -> Option<Self> {
2727        let canonical_sha256 = reusable_executable_fingerprint.len() == 64
2728            && reusable_executable_fingerprint
2729                .bytes()
2730                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte));
2731        if !canonical_sha256
2732            || segment
2733                .start_node_index()
2734                .checked_add(segment.logical_command_count())
2735                != Some(segment.end_node_index())
2736            || logical_commands.len() != segment.logical_command_count() as usize
2737            || logical_commands
2738                .iter()
2739                .enumerate()
2740                .any(|(ordinal, command)| {
2741                    u32::try_from(ordinal).ok() != Some(command.logical_command_ordinal())
2742                        || segment
2743                            .start_node_index()
2744                            .checked_add(command.logical_command_ordinal())
2745                            != Some(command.node_index())
2746                })
2747        {
2748            return None;
2749        }
2750        Some(Self {
2751            physical_command_index,
2752            program_id,
2753            segment,
2754            reusable_executable_fingerprint,
2755            logical_commands: logical_commands.into_boxed_slice(),
2756        })
2757    }
2758
2759    pub const fn physical_command_index(&self) -> u32 {
2760        self.physical_command_index
2761    }
2762
2763    pub fn program_id(&self) -> &DeviceReusableExecutionProgramId {
2764        &self.program_id
2765    }
2766
2767    pub const fn segment(&self) -> &DeviceReusableExecutionSegment {
2768        &self.segment
2769    }
2770
2771    pub fn reusable_executable_fingerprint(&self) -> &str {
2772        &self.reusable_executable_fingerprint
2773    }
2774
2775    pub fn logical_commands(&self) -> &[DeviceReplayedLogicalCommandAttribution] {
2776        &self.logical_commands
2777    }
2778}
2779
2780#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
2781pub struct DeviceSubmissionAttribution {
2782    commands: Box<[DeviceNativeWorkAttribution]>,
2783    replayed_segments: Box<[DeviceReplayedSegmentAttribution]>,
2784}
2785
2786impl DeviceSubmissionAttribution {
2787    pub fn new(commands: Vec<DeviceNativeWorkAttribution>) -> Option<Self> {
2788        Self::with_replayed_segments(commands, Vec::new())
2789    }
2790
2791    pub fn with_replayed_segments(
2792        commands: Vec<DeviceNativeWorkAttribution>,
2793        replayed_segments: Vec<DeviceReplayedSegmentAttribution>,
2794    ) -> Option<Self> {
2795        if commands.is_empty()
2796            || commands
2797                .windows(2)
2798                .any(|pair| pair[0].command_index() >= pair[1].command_index())
2799            || replayed_segments.windows(2).any(|pair| {
2800                pair[0].physical_command_index() >= pair[1].physical_command_index()
2801                    || pair[0].segment().end_node_index() > pair[1].segment().start_node_index()
2802            })
2803        {
2804            return None;
2805        }
2806        for segment in &replayed_segments {
2807            let physical_index = commands
2808                .binary_search_by_key(&segment.physical_command_index(), |command| {
2809                    command.command_index()
2810                })
2811                .ok()?;
2812            let physical = commands.get(physical_index)?;
2813            let logical_graph_node_count = segment
2814                .logical_commands()
2815                .iter()
2816                .try_fold(0_u64, |total, logical| {
2817                    total.checked_add(logical.reusable_graph_node_count())
2818                })?;
2819            if physical.command_index() != segment.physical_command_index()
2820                || physical.command_phase() != DeviceCommandPhase::Compute
2821                || physical.execution_path() != DeviceExecutionPath::Replayed
2822                || physical.reusable_graph_node_count() != Some(logical_graph_node_count)
2823                || physical.node_index() != Some(segment.segment().start_node_index())
2824                || physical.participant_start() != 0
2825                || physical.participant_count() != segment.program_id().immediate_sequences()
2826                || physical.token_count() != segment.program_id().immediate_tokens()
2827                || segment
2828                    .logical_commands()
2829                    .iter()
2830                    .any(|logical| logical.participant_count() != physical.participant_count())
2831            {
2832                return None;
2833            }
2834        }
2835        Some(Self {
2836            commands: commands.into_boxed_slice(),
2837            replayed_segments: replayed_segments.into_boxed_slice(),
2838        })
2839    }
2840
2841    pub fn commands(&self) -> &[DeviceNativeWorkAttribution] {
2842        &self.commands
2843    }
2844
2845    pub fn replayed_segments(&self) -> &[DeviceReplayedSegmentAttribution] {
2846        &self.replayed_segments
2847    }
2848}
2849
2850/// Provider-encoded work for one logical operation.
2851///
2852/// Core owns the phase boundaries: request-specific inputs are written before
2853/// compute, and request-specific outputs are materialized afterwards. A
2854/// backend may optimize the compute command, but cannot accidentally capture
2855/// either dynamic boundary into a reusable executable.
2856#[must_use = "encoded device operations must be appended to a submission batch"]
2857pub struct EncodedDeviceOperation<C> {
2858    program_bindings: Vec<C>,
2859    dynamic_bindings: Vec<C>,
2860    compute: C,
2861    result_bindings: Vec<C>,
2862}
2863
2864impl<C> EncodedDeviceOperation<C> {
2865    pub fn compute(command: C) -> Self {
2866        Self {
2867            program_bindings: Vec::new(),
2868            dynamic_bindings: Vec::new(),
2869            compute: command,
2870            result_bindings: Vec::new(),
2871        }
2872    }
2873
2874    /// Adds a binding that may execute in the wave-level program prelude.
2875    ///
2876    /// Providers may use this only for writes into their own non-aliasing
2877    /// binding workspace. The command must not read provider outputs or depend
2878    /// on earlier compute in the same wave.
2879    pub fn with_program_binding(mut self, command: C) -> Self {
2880        self.program_bindings.push(command);
2881        self
2882    }
2883
2884    pub fn with_dynamic_binding(mut self, command: C) -> Self {
2885        self.dynamic_bindings.push(command);
2886        self
2887    }
2888
2889    pub fn with_result_binding(mut self, command: C) -> Self {
2890        self.result_bindings.push(command);
2891        self
2892    }
2893
2894    pub fn dynamic_binding_count(&self) -> usize {
2895        self.dynamic_bindings.len()
2896    }
2897
2898    pub fn program_binding_count(&self) -> usize {
2899        self.program_bindings.len()
2900    }
2901
2902    pub fn result_binding_count(&self) -> usize {
2903        self.result_bindings.len()
2904    }
2905
2906    pub(crate) fn into_parts(self) -> (Vec<C>, Vec<C>, C, Vec<C>) {
2907        (
2908            self.program_bindings,
2909            self.dynamic_bindings,
2910            self.compute,
2911            self.result_bindings,
2912        )
2913    }
2914}
2915
2916/// Per-wave commands that remain outside one resident reusable compute
2917/// segment. Program bindings may be coalesced into the wave prelude; dynamic
2918/// and result bindings preserve their position around the segment launch.
2919#[must_use = "reusable execution bindings must accompany their segment launch"]
2920pub struct EncodedReusableExecutionBindings<C> {
2921    program_bindings: Vec<C>,
2922    dynamic_bindings: Vec<C>,
2923    result_bindings: Vec<C>,
2924}
2925
2926impl<C> EncodedReusableExecutionBindings<C> {
2927    pub fn empty() -> Self {
2928        Self {
2929            program_bindings: Vec::new(),
2930            dynamic_bindings: Vec::new(),
2931            result_bindings: Vec::new(),
2932        }
2933    }
2934
2935    pub fn with_program_binding(mut self, command: C) -> Self {
2936        self.program_bindings.push(command);
2937        self
2938    }
2939
2940    pub fn with_dynamic_binding(mut self, command: C) -> Self {
2941        self.dynamic_bindings.push(command);
2942        self
2943    }
2944
2945    pub fn with_result_binding(mut self, command: C) -> Self {
2946        self.result_bindings.push(command);
2947        self
2948    }
2949
2950    pub fn from_operation(operation: EncodedDeviceOperation<C>) -> Self {
2951        let (program_bindings, dynamic_bindings, _compute, result_bindings) =
2952            operation.into_parts();
2953        Self {
2954            program_bindings,
2955            dynamic_bindings,
2956            result_bindings,
2957        }
2958    }
2959
2960    pub fn program_binding_count(&self) -> usize {
2961        self.program_bindings.len()
2962    }
2963
2964    pub fn dynamic_binding_count(&self) -> usize {
2965        self.dynamic_bindings.len()
2966    }
2967
2968    pub fn result_binding_count(&self) -> usize {
2969        self.result_bindings.len()
2970    }
2971
2972    pub(crate) fn into_parts(self) -> (Vec<C>, Vec<C>, Vec<C>) {
2973        (
2974            self.program_bindings,
2975            self.dynamic_bindings,
2976            self.result_bindings,
2977        )
2978    }
2979}
2980
2981/// One command plus the core-issued semantic phase that constrains backend
2982/// execution optimizations.
2983pub struct DeviceCommandEntry<C> {
2984    phase: DeviceCommandPhase,
2985    node_index: Option<u32>,
2986    logical_work: Option<DeviceCommandLogicalWork>,
2987    command: C,
2988}
2989
2990impl<C> DeviceCommandEntry<C> {
2991    pub const fn phase(&self) -> DeviceCommandPhase {
2992        self.phase
2993    }
2994
2995    pub const fn node_index(&self) -> Option<u32> {
2996        self.node_index
2997    }
2998
2999    pub const fn logical_work(&self) -> Option<DeviceCommandLogicalWork> {
3000        self.logical_work
3001    }
3002
3003    pub const fn command(&self) -> &C {
3004        &self.command
3005    }
3006
3007    pub fn into_parts(
3008        self,
3009    ) -> (
3010        DeviceCommandPhase,
3011        Option<u32>,
3012        Option<DeviceCommandLogicalWork>,
3013        C,
3014    ) {
3015        (self.phase, self.node_index, self.logical_work, self.command)
3016    }
3017}
3018
3019/// Core-owned physical submission unit.
3020///
3021/// Operation providers produce individual commands, while the execution
3022/// runtime decides which commands share one ordered device submission and
3023/// completion fence. Construction stays private to core so a backend cannot
3024/// silently split one admitted lane segment into unrelated submissions.
3025#[must_use = "encoded device command batches must be submitted"]
3026pub struct DeviceCommandBatch<C> {
3027    commands: Vec<DeviceCommandEntry<C>>,
3028    timing_mode: DeviceTimingMode,
3029    compute_path_requirement: DeviceComputePathRequirement,
3030    declared_eager_compute_node_indices: Vec<u32>,
3031    attribution_requirement: DeviceSubmissionAttributionRequirement,
3032    reusable_execution_capture: Option<DeviceReusableExecutionCapture>,
3033}
3034
3035impl<C> DeviceCommandBatch<C> {
3036    pub(crate) fn singleton(command: C) -> Self {
3037        Self {
3038            commands: vec![DeviceCommandEntry {
3039                phase: DeviceCommandPhase::Compute,
3040                node_index: None,
3041                logical_work: None,
3042                command,
3043            }],
3044            timing_mode: DeviceTimingMode::Off,
3045            compute_path_requirement: DeviceComputePathRequirement::Adaptive,
3046            declared_eager_compute_node_indices: Vec::new(),
3047            attribution_requirement: DeviceSubmissionAttributionRequirement::None,
3048            reusable_execution_capture: None,
3049        }
3050    }
3051
3052    pub(crate) fn with_capacity(capacity: usize) -> Self {
3053        Self {
3054            commands: Vec::with_capacity(capacity),
3055            timing_mode: DeviceTimingMode::Off,
3056            compute_path_requirement: DeviceComputePathRequirement::Adaptive,
3057            declared_eager_compute_node_indices: Vec::new(),
3058            attribution_requirement: DeviceSubmissionAttributionRequirement::None,
3059            reusable_execution_capture: None,
3060        }
3061    }
3062
3063    pub(crate) fn with_capacity_and_timing(capacity: usize, timing_mode: DeviceTimingMode) -> Self {
3064        Self {
3065            commands: Vec::with_capacity(capacity),
3066            timing_mode,
3067            compute_path_requirement: DeviceComputePathRequirement::Adaptive,
3068            declared_eager_compute_node_indices: Vec::new(),
3069            attribution_requirement: DeviceSubmissionAttributionRequirement::None,
3070            reusable_execution_capture: None,
3071        }
3072    }
3073
3074    pub(crate) fn with_capacity_timing_and_compute_path(
3075        capacity: usize,
3076        timing_mode: DeviceTimingMode,
3077        compute_path_requirement: DeviceComputePathRequirement,
3078    ) -> Self {
3079        Self {
3080            commands: Vec::with_capacity(capacity),
3081            timing_mode,
3082            compute_path_requirement,
3083            declared_eager_compute_node_indices: Vec::new(),
3084            attribution_requirement: DeviceSubmissionAttributionRequirement::None,
3085            reusable_execution_capture: None,
3086        }
3087    }
3088
3089    pub(crate) fn set_declared_eager_compute_node_indices(
3090        &mut self,
3091        node_indices: Vec<u32>,
3092    ) -> Result<(), VNextError> {
3093        if self.compute_path_requirement
3094            != DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries
3095            || node_indices.is_empty()
3096            || node_indices.windows(2).any(|pair| pair[0] >= pair[1])
3097            || !self.declared_eager_compute_node_indices.is_empty()
3098        {
3099            return Err(VNextError::InvalidExecutionPlan {
3100                reason: "declared eager compute boundaries are absent, unordered, duplicated, or attached to the wrong path requirement"
3101                    .to_owned(),
3102            });
3103        }
3104        self.declared_eager_compute_node_indices = node_indices;
3105        Ok(())
3106    }
3107
3108    pub(crate) fn require_logical_execution_path_attribution(&mut self) {
3109        self.attribution_requirement = DeviceSubmissionAttributionRequirement::LogicalExecutionPath;
3110    }
3111
3112    pub(crate) fn set_reusable_execution_capture(
3113        &mut self,
3114        capture: DeviceReusableExecutionCapture,
3115    ) -> Result<(), VNextError> {
3116        if self.reusable_execution_capture.is_some() {
3117            return Err(VNextError::InvalidExecutionPlan {
3118                reason: "device command batch already owns reusable execution capture metadata"
3119                    .to_owned(),
3120            });
3121        }
3122        self.reusable_execution_capture = Some(capture);
3123        Ok(())
3124    }
3125
3126    pub fn reusable_execution_capture(&self) -> Option<&DeviceReusableExecutionCapture> {
3127        self.reusable_execution_capture.as_ref()
3128    }
3129
3130    pub(crate) fn push_initialization(&mut self, command: C) {
3131        self.commands.push(DeviceCommandEntry {
3132            phase: DeviceCommandPhase::Initialization,
3133            node_index: None,
3134            logical_work: None,
3135            command,
3136        });
3137    }
3138
3139    pub(crate) fn push_node_initialization(
3140        &mut self,
3141        node_index: u32,
3142        logical_work: DeviceCommandLogicalWork,
3143        command: C,
3144    ) {
3145        self.commands.push(DeviceCommandEntry {
3146            phase: DeviceCommandPhase::Initialization,
3147            node_index: Some(node_index),
3148            logical_work: Some(logical_work),
3149            command,
3150        });
3151    }
3152
3153    pub(crate) fn push_dynamic_binding(&mut self, command: C) {
3154        self.commands.push(DeviceCommandEntry {
3155            phase: DeviceCommandPhase::DynamicBinding,
3156            node_index: None,
3157            logical_work: None,
3158            command,
3159        });
3160    }
3161
3162    pub(crate) fn push_compute(&mut self, command: C) {
3163        self.commands.push(DeviceCommandEntry {
3164            phase: DeviceCommandPhase::Compute,
3165            node_index: None,
3166            logical_work: None,
3167            command,
3168        });
3169    }
3170
3171    pub(crate) fn push_result_binding(&mut self, command: C) {
3172        self.commands.push(DeviceCommandEntry {
3173            phase: DeviceCommandPhase::ResultBinding,
3174            node_index: None,
3175            logical_work: None,
3176            command,
3177        });
3178    }
3179
3180    pub(crate) fn push_operation(&mut self, node_index: u32, operation: EncodedDeviceOperation<C>) {
3181        let (program_bindings, dynamic_bindings, compute, result_bindings) = operation.into_parts();
3182        for command in program_bindings {
3183            self.commands.push(DeviceCommandEntry {
3184                phase: DeviceCommandPhase::DynamicBinding,
3185                node_index: Some(node_index),
3186                logical_work: None,
3187                command,
3188            });
3189        }
3190        self.push_operation_parts(node_index, dynamic_bindings, compute, result_bindings);
3191    }
3192
3193    pub(crate) fn push_operation_parts(
3194        &mut self,
3195        node_index: u32,
3196        dynamic_bindings: Vec<C>,
3197        compute: C,
3198        result_bindings: Vec<C>,
3199    ) {
3200        for command in dynamic_bindings {
3201            self.commands.push(DeviceCommandEntry {
3202                phase: DeviceCommandPhase::DynamicBinding,
3203                node_index: Some(node_index),
3204                logical_work: None,
3205                command,
3206            });
3207        }
3208        self.commands.push(DeviceCommandEntry {
3209            phase: DeviceCommandPhase::Compute,
3210            node_index: Some(node_index),
3211            logical_work: None,
3212            command: compute,
3213        });
3214        for command in result_bindings {
3215            self.commands.push(DeviceCommandEntry {
3216                phase: DeviceCommandPhase::ResultBinding,
3217                node_index: Some(node_index),
3218                logical_work: None,
3219                command,
3220            });
3221        }
3222    }
3223
3224    pub(crate) fn push(&mut self, command: C) {
3225        self.push_compute(command);
3226    }
3227
3228    pub fn len(&self) -> usize {
3229        self.commands.len()
3230    }
3231
3232    pub fn is_empty(&self) -> bool {
3233        self.commands.is_empty()
3234    }
3235
3236    pub const fn timing_mode(&self) -> DeviceTimingMode {
3237        self.timing_mode
3238    }
3239
3240    pub const fn compute_path_requirement(&self) -> DeviceComputePathRequirement {
3241        self.compute_path_requirement
3242    }
3243
3244    pub fn declared_eager_compute_node_indices(&self) -> &[u32] {
3245        &self.declared_eager_compute_node_indices
3246    }
3247
3248    pub const fn attribution_requirement(&self) -> DeviceSubmissionAttributionRequirement {
3249        self.attribution_requirement
3250    }
3251
3252    pub fn into_commands(self) -> Vec<C> {
3253        self.commands
3254            .into_iter()
3255            .map(|entry| entry.command)
3256            .collect()
3257    }
3258
3259    pub fn into_entries(self) -> Vec<DeviceCommandEntry<C>> {
3260        self.commands
3261    }
3262}
3263
3264/// Cold-path transaction for backends that can bind immutable weight
3265/// components directly instead of allocating and uploading one contiguous
3266/// physical arena.
3267///
3268/// The session must not publish a partially imported arena. [`Self::seal`]
3269/// consumes the complete transaction and is the only point at which imported
3270/// regions may become visible to device execution.
3271pub trait StaticWeightImportSession<B, E> {
3272    fn import_component(
3273        &mut self,
3274        payload: &WeightComponentPayload<'_>,
3275        destination: &B,
3276        destination_offset_bytes: u64,
3277    ) -> Result<(), E>;
3278
3279    fn seal(self: Box<Self>) -> Result<(), E>;
3280}
3281
3282/// One final execution-component destination for a required static transform.
3283pub struct StaticWeightTransformDestination<'request, B> {
3284    component: &'request WeightComponentSpec,
3285    buffer: &'request B,
3286    destination_offset_bytes: u64,
3287}
3288
3289impl<'request, B> StaticWeightTransformDestination<'request, B> {
3290    pub(crate) const fn new(
3291        component: &'request WeightComponentSpec,
3292        buffer: &'request B,
3293        destination_offset_bytes: u64,
3294    ) -> Self {
3295        Self {
3296            component,
3297            buffer,
3298            destination_offset_bytes,
3299        }
3300    }
3301
3302    pub fn component(&self) -> &WeightComponentSpec {
3303        self.component
3304    }
3305
3306    pub const fn buffer(&self) -> &B {
3307        self.buffer
3308    }
3309
3310    pub const fn destination_offset_bytes(&self) -> u64 {
3311        self.destination_offset_bytes
3312    }
3313}
3314
3315/// Fully validated input for one required cold-path device transform.
3316///
3317/// Source segment order and output order are part of the trusted plan. The
3318/// scratch buffer is a single plan-admitted allocation sized from the largest
3319/// matrix transform and reused serially across the model.
3320pub struct StaticWeightTransformRequest<'request, 'source, B> {
3321    plan: &'request StaticWeightTransformPlan,
3322    sources: &'request [WeightComponentSegments<'source>],
3323    destinations: &'request [StaticWeightTransformDestination<'request, B>],
3324    scratch: &'request B,
3325}
3326
3327impl<'request, 'source, B> StaticWeightTransformRequest<'request, 'source, B> {
3328    pub(crate) const fn new(
3329        plan: &'request StaticWeightTransformPlan,
3330        sources: &'request [WeightComponentSegments<'source>],
3331        destinations: &'request [StaticWeightTransformDestination<'request, B>],
3332        scratch: &'request B,
3333    ) -> Self {
3334        Self {
3335            plan,
3336            sources,
3337            destinations,
3338            scratch,
3339        }
3340    }
3341
3342    pub const fn plan(&self) -> &StaticWeightTransformPlan {
3343        self.plan
3344    }
3345
3346    pub fn sources(&self) -> &[WeightComponentSegments<'source>] {
3347        self.sources
3348    }
3349
3350    pub fn destinations(&self) -> &[StaticWeightTransformDestination<'request, B>] {
3351        self.destinations
3352    }
3353
3354    pub const fn scratch(&self) -> &B {
3355        self.scratch
3356    }
3357}
3358
3359/// Stable primitive boundary implemented by a concrete device runtime.
3360///
3361/// Associated buffer, stream, command, and error types preserve compile-time
3362/// type safety. Every operation is required; unsupported work cannot inherit a
3363/// success-returning default implementation.
3364pub trait DeviceRuntime: Send + Sync + 'static {
3365    type Buffer: Send + Sync + 'static;
3366    type Stream: Send + 'static;
3367    type Command: Send + 'static;
3368    type Fence: Send + 'static;
3369    type Error: Error + Send + Sync + 'static;
3370
3371    fn descriptor(&self) -> &DeviceDescriptor;
3372
3373    /// Resolved attention provider-family policy installed by this runtime
3374    /// composition. `Auto` is never valid after composition.
3375    fn attention_execution_policy(&self) -> AttentionExecutionPolicy;
3376
3377    /// Allocates only after the resource transaction has authorized the exact
3378    /// request. `DeviceAllocationPermit` has no public constructor and borrows
3379    /// the live transaction context, so raw device allocation cannot bypass
3380    /// admission or outlive the transaction action that authorized it.
3381    fn allocate(&self, permit: DeviceAllocationPermit<'_>) -> Result<Self::Buffer, Self::Error>;
3382
3383    fn buffer_descriptor(&self, buffer: &Self::Buffer) -> BufferDescriptor;
3384
3385    /// Begins an optional all-or-nothing static-weight import transaction.
3386    /// Returning `None` selects the portable zero-and-upload path. The default
3387    /// preserves existing CUDA, CPU, and test runtime behavior.
3388    fn begin_static_weight_import(
3389        &self,
3390    ) -> Option<
3391        Result<Box<dyn StaticWeightImportSession<Self::Buffer, Self::Error> + '_>, Self::Error>,
3392    > {
3393        None
3394    }
3395
3396    /// Encode a required source-to-execution static weight transform.
3397    ///
3398    /// `None` means this runtime does not implement the exact transform. Core
3399    /// treats that as a contract failure; it never uploads source bytes into a
3400    /// final execution layout and never falls back to a host transform hidden
3401    /// from the plan.
3402    fn encode_static_weight_transform(
3403        &self,
3404        _request: StaticWeightTransformRequest<'_, '_, Self::Buffer>,
3405    ) -> Option<Result<Self::Command, Self::Error>> {
3406        None
3407    }
3408
3409    fn create_stream(&self) -> Result<Self::Stream, Self::Error>;
3410
3411    fn stream_state(&self, stream: &Self::Stream) -> StreamState;
3412
3413    /// Opens the bounded cold-path preparation window for one stream.
3414    /// Backends without reusable executable support retain the no-op receipt.
3415    fn configure_reusable_executables(
3416        &self,
3417        _stream: &mut Self::Stream,
3418        _plan: DeviceReusableExecutionPlan,
3419    ) -> Result<DeviceReusableExecutionPreparation, Self::Error> {
3420        Ok(DeviceReusableExecutionPreparation::unsupported())
3421    }
3422
3423    /// Permanently closes the preparation window for this stream. A sealed
3424    /// stream may replay or fall back to eager execution but cannot capture on
3425    /// a later product request.
3426    fn seal_reusable_executables(
3427        &self,
3428        _stream: &mut Self::Stream,
3429    ) -> Result<DeviceReusableExecutionPreparation, Self::Error> {
3430        Ok(DeviceReusableExecutionPreparation::unsupported())
3431    }
3432
3433    /// Returns the current preparation receipt without changing lifecycle
3434    /// state. Product startup uses two snapshots to prove that its validation
3435    /// pass replayed stable executables instead of compiling more work.
3436    fn reusable_executable_preparation(
3437        &self,
3438        _stream: &Self::Stream,
3439    ) -> Result<DeviceReusableExecutionPreparation, Self::Error> {
3440        Ok(DeviceReusableExecutionPreparation::unsupported())
3441    }
3442
3443    /// Returns the immutable direct-submit catalog after preparation is sealed.
3444    ///
3445    /// Backends without direct reusable execution return an empty catalog.
3446    fn reusable_execution_catalog(
3447        &self,
3448        _stream: &Self::Stream,
3449    ) -> Result<Vec<DeviceReusableExecutionProgram>, Self::Error> {
3450        Ok(Vec::new())
3451    }
3452
3453    /// Encodes one lightweight reference to a resident reusable segment.
3454    ///
3455    /// Returning `None` selects the normal provider encoding path. The
3456    /// reference itself owns no request resources; every dynamic target must be
3457    /// retained by explicit per-wave binding commands and the completion fence.
3458    fn encode_reusable_execution(
3459        &self,
3460        _invocation: DeviceReusableExecutionInvocation,
3461    ) -> Result<Option<Self::Command>, Self::Error> {
3462        Ok(None)
3463    }
3464
3465    /// Releases reusable executable cache entries on a proven-quiescent
3466    /// stream. Backends without such a cache retain the no-op default.
3467    fn trim_reusable_executables(
3468        &self,
3469        _stream: &mut Self::Stream,
3470    ) -> Result<DeviceReusableExecutionTrim, Self::Error> {
3471        Ok(DeviceReusableExecutionTrim::default())
3472    }
3473
3474    fn encode_copy(
3475        &self,
3476        source: &Self::Buffer,
3477        destination: &Self::Buffer,
3478        region: CopyRegion,
3479    ) -> Result<Self::Command, Self::Error>;
3480
3481    fn encode_upload(
3482        &self,
3483        source: &[u8],
3484        source_layout: HostTransferLayout,
3485        destination: &Self::Buffer,
3486        destination_offset_bytes: u64,
3487    ) -> Result<Self::Command, Self::Error>;
3488
3489    fn encode_zero(
3490        &self,
3491        destination: &Self::Buffer,
3492        destination_offset_bytes: u64,
3493        length_bytes: u64,
3494    ) -> Result<Self::Command, Self::Error>;
3495
3496    /// Coalesces independent provider binding writes into a wave prelude.
3497    ///
3498    /// The default preserves one command per provider. Backends may return a
3499    /// smaller ordered set, but must retain every command-owned resource and
3500    /// preserve the exact enqueue order and failure semantics.
3501    fn coalesce_program_bindings(
3502        &self,
3503        commands: Vec<Self::Command>,
3504    ) -> Result<Vec<Self::Command>, Self::Error> {
3505        Ok(commands)
3506    }
3507
3508    /// Submits one non-empty ordered command batch and returns its exact
3509    /// completion fence. A backend must preserve command order and must not
3510    /// manufacture intermediate host-visible completion boundaries.
3511    ///
3512    /// The error type is intentionally closed over `DefinitelyNotSubmitted`:
3513    /// an ordinary backend error is not sufficient evidence that invocation
3514    /// resources may be released or retried. Backends that cannot prove that
3515    /// no work was enqueued must panic or retain/return a fence through their
3516    /// implementation boundary; core treats an unwind as possibly submitted.
3517    fn submit(
3518        &self,
3519        stream: &mut Self::Stream,
3520        commands: DeviceCommandBatch<Self::Command>,
3521    ) -> Result<Self::Fence, DefinitelyNotSubmitted<Self::Error>>;
3522
3523    /// Profile-attached submission entrypoint. Backends override this only
3524    /// when they can expose typed internal boundaries without changing
3525    /// submission ownership or error semantics.
3526    fn submit_with_timing<S>(
3527        &self,
3528        stream: &mut Self::Stream,
3529        commands: DeviceCommandBatch<Self::Command>,
3530        timing_sink: &S,
3531    ) -> Result<Self::Fence, DefinitelyNotSubmitted<Self::Error>>
3532    where
3533        Self: Sized,
3534        S: DeviceSubmissionTimingSink,
3535    {
3536        let _ = timing_sink;
3537        self.submit(stream, commands)
3538    }
3539
3540    /// Returns backend-observed native work for an already submitted fence.
3541    /// Attribution may be requested explicitly for correctness evidence or by
3542    /// a diagnostic timing mode. The returned rows never grant completion or
3543    /// resource-release authority.
3544    fn submission_attribution(&self, _fence: &Self::Fence) -> Option<DeviceSubmissionAttribution> {
3545        None
3546    }
3547
3548    /// Observes a fence without blocking. `Indeterminate` is not terminal and
3549    /// therefore cannot release command-owned resources.
3550    fn query_fence(&self, fence: &Self::Fence) -> FenceQuery<Self::Error>;
3551
3552    /// Waits for a quiescent terminal. Failure to prove quiescence retains the
3553    /// fence and every resource reachable from the submitted invocation.
3554    fn wait_fence(
3555        &self,
3556        fence: &Self::Fence,
3557    ) -> Result<DeviceTerminalReceipt<Self::Error>, FenceIndeterminate<Self::Error>>;
3558
3559    fn synchronize(&self, stream: &mut Self::Stream) -> Result<(), Self::Error>;
3560
3561    fn readback(
3562        &self,
3563        stream: &mut Self::Stream,
3564        source: &Self::Buffer,
3565        region: CopyRegion,
3566        output_layout: HostTransferLayout,
3567    ) -> Result<Vec<u8>, Self::Error>;
3568
3569    fn describe_error(&self, error: &Self::Error) -> Result<DeviceErrorReport, VNextError>;
3570}
3571
3572/// Closes a backend error over the exact runtime instance and a core-owned
3573/// device failure domain.
3574pub fn classify_device_error<R: DeviceRuntime + ?Sized>(
3575    runtime: &R,
3576    identity: ExecutionIdentityEnvelope,
3577    error: &R::Error,
3578) -> Result<IdentifiedFailure, VNextError> {
3579    runtime.descriptor().validate()?;
3580    if identity.parts().device_id.as_ref() != Some(&runtime.descriptor().id)
3581        || identity
3582            .parts()
3583            .runtime_implementation_fingerprint
3584            .as_deref()
3585            != Some(
3586                runtime
3587                    .descriptor()
3588                    .runtime_implementation_fingerprint
3589                    .as_str(),
3590            )
3591    {
3592        return Err(VNextError::InvalidExecutionPlan {
3593            reason: "device error identity differs from the concrete runtime device implementation"
3594                .to_owned(),
3595        });
3596    }
3597    IdentifiedFailure::new(identity, runtime.describe_error(error)?.into_failure())
3598}
3599
3600#[cfg(test)]
3601mod execution_timing_tests {
3602    use super::*;
3603    use crate::vnext::{
3604        ReusableExecutionBucketSpec, ReusableExecutionCapacity, ReusableExecutionClassId,
3605    };
3606
3607    #[test]
3608    fn native_operation_identity_is_portable_and_bounded() {
3609        let identity = DeviceNativeOperationId::new("cuda.op_1:variant/path-name").unwrap();
3610        assert_eq!(identity.as_str(), "cuda.op_1:variant/path-name");
3611        assert!(DeviceNativeOperationId::new("").is_none());
3612        assert!(DeviceNativeOperationId::new("device zero").is_none());
3613        assert!(DeviceNativeOperationId::new("native.操作").is_none());
3614        let oversized = Box::leak(
3615            "x".repeat(DeviceNativeOperationId::MAX_BYTES + 1)
3616                .into_boxed_str(),
3617        );
3618        assert!(DeviceNativeOperationId::new(oversized).is_none());
3619        assert_eq!(DEVICE_COPY_NATIVE_OPERATION_ID.as_str(), "device.copy");
3620        assert_eq!(HOST_UPLOAD_NATIVE_OPERATION_ID.as_str(), "host.upload");
3621        assert_eq!(DEVICE_ZERO_NATIVE_OPERATION_ID.as_str(), "device.zero");
3622    }
3623
3624    #[test]
3625    fn timing_capabilities_are_independent_from_compute_path_requirement() {
3626        assert!(DeviceTimingMode::Replay.completion_enabled());
3627        assert!(DeviceTimingMode::Replay.physical_span_attribution_enabled());
3628        assert!(!DeviceTimingMode::Replay.kernel_attribution_enabled());
3629        assert!(DeviceTimingMode::Kernel.physical_span_attribution_enabled());
3630        assert!(DeviceTimingMode::Kernel.kernel_attribution_enabled());
3631        assert!(!DeviceTimingMode::Kernel.direct_reusable_execution_allowed());
3632        assert!(DeviceTimingMode::Verification.completion_enabled());
3633        assert!(DeviceTimingMode::Verification.physical_span_attribution_enabled());
3634        assert!(DeviceTimingMode::Verification.kernel_attribution_enabled());
3635        assert!(!DeviceTimingMode::Verification.direct_reusable_execution_allowed());
3636        assert!(!DeviceTimingMode::Completion.physical_span_attribution_enabled());
3637        assert!(!DeviceTimingMode::Off.completion_enabled());
3638
3639        let mut batch = DeviceCommandBatch::<()>::with_capacity_timing_and_compute_path(
3640            1,
3641            DeviceTimingMode::Replay,
3642            DeviceComputePathRequirement::EagerOnly,
3643        );
3644        assert_eq!(
3645            batch.compute_path_requirement(),
3646            DeviceComputePathRequirement::EagerOnly
3647        );
3648        assert_eq!(
3649            batch.attribution_requirement(),
3650            DeviceSubmissionAttributionRequirement::None
3651        );
3652        batch.require_logical_execution_path_attribution();
3653        assert_eq!(
3654            batch.attribution_requirement(),
3655            DeviceSubmissionAttributionRequirement::LogicalExecutionPath
3656        );
3657        assert_eq!(batch.timing_mode(), DeviceTimingMode::Replay);
3658    }
3659
3660    #[test]
3661    fn mixed_replay_boundaries_are_explicit_canonical_batch_metadata() {
3662        let mut batch = DeviceCommandBatch::<()>::with_capacity_timing_and_compute_path(
3663            2,
3664            DeviceTimingMode::Replay,
3665            DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries,
3666        );
3667        batch
3668            .set_declared_eager_compute_node_indices(vec![0, 3])
3669            .unwrap();
3670        assert_eq!(batch.declared_eager_compute_node_indices(), &[0, 3]);
3671        assert_eq!(
3672            serde_json::to_value(batch.compute_path_requirement()).unwrap(),
3673            "replayed_with_declared_eager_boundaries"
3674        );
3675
3676        let mut duplicate = DeviceCommandBatch::<()>::with_capacity_timing_and_compute_path(
3677            2,
3678            DeviceTimingMode::Replay,
3679            DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries,
3680        );
3681        assert!(duplicate
3682            .set_declared_eager_compute_node_indices(vec![1, 1])
3683            .is_err());
3684        let mut wrong_mode = DeviceCommandBatch::<()>::with_capacity(1);
3685        assert!(wrong_mode
3686            .set_declared_eager_compute_node_indices(vec![0])
3687            .is_err());
3688    }
3689
3690    #[test]
3691    fn command_timing_requires_positive_ordered_nonoverlapping_intervals() {
3692        assert!(
3693            DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Compute, 10, 10).is_none()
3694        );
3695        assert!(
3696            DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Compute, 11, 10).is_none()
3697        );
3698
3699        let first =
3700            DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Compute, 10, 20).unwrap();
3701        let adjacent =
3702            DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Transfer, 20, 30).unwrap();
3703        let overlapping =
3704            DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Transfer, 19, 30).unwrap();
3705        assert!(DeviceCommandExecutionTiming::new(0, vec![first, adjacent]).is_some());
3706        assert!(DeviceCommandExecutionTiming::new(0, vec![first, overlapping]).is_none());
3707        let labeled = DeviceExecutionInterval::new_labeled(
3708            DeviceExecutionIntervalKind::Compute,
3709            30,
3710            40,
3711            "projection.qkv",
3712        )
3713        .unwrap();
3714        assert_eq!(labeled.subwork_id(), Some("projection.qkv"));
3715        assert!(DeviceExecutionInterval::new_labeled(
3716            DeviceExecutionIntervalKind::Compute,
3717            30,
3718            40,
3719            "",
3720        )
3721        .is_none());
3722    }
3723
3724    #[test]
3725    fn submission_timing_requires_complete_nonoverlapping_command_coverage() {
3726        let command = |command_index| {
3727            DeviceCommandExecutionTiming::new(
3728                command_index,
3729                vec![
3730                    DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Compute, 0, 1)
3731                        .unwrap(),
3732                ],
3733            )
3734            .unwrap()
3735        };
3736        let commands = DeviceSubmissionExecutionTiming::new(vec![command(0), command(1)]).unwrap();
3737        assert_eq!(commands.command_count(), 2);
3738        assert_eq!(commands.spans().len(), 2);
3739        assert!(commands
3740            .spans()
3741            .iter()
3742            .all(|span| span.kind() == DeviceExecutionSpanKind::EagerCommand));
3743        assert!(DeviceSubmissionExecutionTiming::new(vec![command(0), command(2)]).is_none());
3744        assert!(DeviceSubmissionExecutionTiming::new(vec![command(1), command(1)]).is_none());
3745        assert!(DeviceSubmissionExecutionTiming::new(vec![command(2), command(1)]).is_none());
3746    }
3747
3748    #[test]
3749    fn submission_timing_preserves_measured_and_unavailable_physical_spans() {
3750        let eager = DeviceSubmissionExecutionSpan::measured(
3751            0,
3752            1,
3753            DeviceExecutionSpanKind::EagerCommand,
3754            vec![
3755                DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Transfer, 0, 10).unwrap(),
3756            ],
3757        )
3758        .unwrap();
3759        let replay = DeviceSubmissionExecutionSpan::measured(
3760            1,
3761            4,
3762            DeviceExecutionSpanKind::ReusableExecutable,
3763            vec![DeviceExecutionInterval::new_labeled(
3764                DeviceExecutionIntervalKind::Compute,
3765                10,
3766                40,
3767                "cuda reusable executable",
3768            )
3769            .unwrap()],
3770        )
3771        .unwrap()
3772        .with_reusable_executable_fingerprint("a".repeat(64))
3773        .unwrap();
3774        let unavailable = DeviceSubmissionExecutionSpan::unavailable(
3775            4,
3776            5,
3777            DeviceExecutionSpanKind::EagerCommand,
3778            DeviceTimingUnavailableReason::BackendMeasurementFailed,
3779        )
3780        .unwrap();
3781
3782        let timing =
3783            DeviceSubmissionExecutionTiming::from_spans(5, vec![eager, replay, unavailable])
3784                .unwrap();
3785        assert_eq!(timing.command_count(), 5);
3786        assert_eq!(
3787            timing.span_for_command(2).unwrap().kind(),
3788            DeviceExecutionSpanKind::ReusableExecutable
3789        );
3790        assert_eq!(
3791            timing
3792                .span_for_command(2)
3793                .unwrap()
3794                .reusable_executable_fingerprint(),
3795            Some("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
3796        );
3797        assert_eq!(
3798            serde_json::to_value(timing.span_for_command(2).unwrap())
3799                .unwrap()
3800                .get("reusable_executable_fingerprint"),
3801            Some(&serde_json::json!(
3802                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
3803            ))
3804        );
3805        assert_eq!(
3806            timing
3807                .span_for_command(4)
3808                .unwrap()
3809                .measurement()
3810                .unavailable_reason(),
3811            Some(DeviceTimingUnavailableReason::BackendMeasurementFailed)
3812        );
3813        assert!(timing.span_for_command(5).is_none());
3814    }
3815
3816    #[test]
3817    fn physical_spans_reject_gaps_overlaps_and_invalid_eager_ranges() {
3818        let interval = || {
3819            vec![DeviceExecutionInterval::new(DeviceExecutionIntervalKind::Compute, 0, 1).unwrap()]
3820        };
3821        assert!(DeviceSubmissionExecutionSpan::measured(
3822            0,
3823            2,
3824            DeviceExecutionSpanKind::EagerCommand,
3825            interval(),
3826        )
3827        .is_none());
3828        assert!(DeviceSubmissionExecutionSpan::measured(
3829            0,
3830            1,
3831            DeviceExecutionSpanKind::EagerCommand,
3832            interval(),
3833        )
3834        .unwrap()
3835        .with_reusable_executable_fingerprint("a".repeat(64))
3836        .is_none());
3837        assert!(DeviceSubmissionExecutionSpan::measured(
3838            0,
3839            2,
3840            DeviceExecutionSpanKind::ReusableExecutable,
3841            interval(),
3842        )
3843        .unwrap()
3844        .with_reusable_executable_fingerprint("A".repeat(64))
3845        .is_none());
3846        let first = DeviceSubmissionExecutionSpan::measured(
3847            0,
3848            1,
3849            DeviceExecutionSpanKind::EagerCommand,
3850            interval(),
3851        )
3852        .unwrap();
3853        assert!(serde_json::to_value(&first)
3854            .unwrap()
3855            .get("reusable_executable_fingerprint")
3856            .is_none());
3857        let gap = DeviceSubmissionExecutionSpan::measured(
3858            2,
3859            3,
3860            DeviceExecutionSpanKind::EagerCommand,
3861            interval(),
3862        )
3863        .unwrap();
3864        assert!(DeviceSubmissionExecutionTiming::from_spans(3, vec![first.clone(), gap]).is_none());
3865        let overlap = DeviceSubmissionExecutionSpan::measured(
3866            0,
3867            2,
3868            DeviceExecutionSpanKind::ReusableExecutable,
3869            interval(),
3870        )
3871        .unwrap();
3872        assert!(DeviceSubmissionExecutionTiming::from_spans(2, vec![first, overlap]).is_none());
3873    }
3874
3875    #[test]
3876    fn native_graph_node_observation_belongs_only_to_replayed_work() {
3877        let row = |execution_path, graph_nodes| {
3878            DeviceNativeWorkAttribution::new(
3879                0,
3880                Some(0),
3881                DeviceCommandPhase::Compute,
3882                DeviceNativeOperationId::new("test.compute").unwrap(),
3883                execution_path,
3884                DeviceBatchingForm::Scalar,
3885                1,
3886                1,
3887                1,
3888                0,
3889                graph_nodes,
3890            )
3891        };
3892
3893        assert!(row(DeviceExecutionPath::Eager, Some(2)).is_none());
3894        let replayed = row(DeviceExecutionPath::Replayed, Some(2)).unwrap();
3895        assert_eq!(replayed.reusable_graph_node_count(), Some(2));
3896        assert_eq!(
3897            serde_json::to_value(replayed).unwrap()["reusable_graph_node_count"],
3898            serde_json::json!(2)
3899        );
3900    }
3901
3902    #[test]
3903    fn native_work_attribution_preserves_bounded_participant_range() {
3904        let row = DeviceNativeWorkAttribution::with_participant_range(
3905            3,
3906            Some(7),
3907            DeviceCommandPhase::Initialization,
3908            DeviceNativeOperationId::new("test.restore").unwrap(),
3909            DeviceExecutionPath::Eager,
3910            DeviceBatchingForm::Scalar,
3911            2,
3912            1,
3913            4,
3914            0,
3915            1,
3916            None,
3917        )
3918        .unwrap();
3919        assert_eq!(row.participant_start(), 2);
3920        assert_eq!(row.participant_count(), 1);
3921        assert_eq!(row.participant_end(), 3);
3922        assert!(DeviceNativeWorkAttribution::with_participant_range(
3923            3,
3924            Some(7),
3925            DeviceCommandPhase::Initialization,
3926            DeviceNativeOperationId::new("test.restore").unwrap(),
3927            DeviceExecutionPath::Eager,
3928            DeviceBatchingForm::Scalar,
3929            u32::MAX,
3930            2,
3931            4,
3932            0,
3933            1,
3934            None,
3935        )
3936        .is_none());
3937    }
3938
3939    fn replay_test_program_id() -> DeviceReusableExecutionProgramId {
3940        let plan_hash: PlanHash =
3941            serde_json::from_value(serde_json::json!("a".repeat(64))).unwrap();
3942        let bucket = ReusableExecutionBucketSpec::new(
3943            ReusableExecutionClassId::new("device-attribution-test").unwrap(),
3944            ReusableExecutionCapacity::new(2, 3, 1).unwrap(),
3945        )
3946        .unwrap();
3947        DeviceReusableExecutionProgramId::new(
3948            plan_hash,
3949            "b".repeat(64),
3950            ExecutionLaneId::mint().unwrap(),
3951            bucket.bucket_id().clone(),
3952            "c".repeat(64),
3953            "d".repeat(64),
3954            7,
3955            2,
3956            3,
3957            1,
3958        )
3959        .unwrap()
3960    }
3961
3962    fn replay_test_physical(
3963        execution_path: DeviceExecutionPath,
3964        node_index: u32,
3965        graph_node_count: Option<u64>,
3966    ) -> DeviceNativeWorkAttribution {
3967        DeviceNativeWorkAttribution::new(
3968            0,
3969            Some(node_index),
3970            DeviceCommandPhase::Compute,
3971            DeviceNativeOperationId::new("vnext_reusable_execution").unwrap(),
3972            execution_path,
3973            DeviceBatchingForm::ParticipantLoop,
3974            2,
3975            3,
3976            1,
3977            0,
3978            graph_node_count,
3979        )
3980        .unwrap()
3981    }
3982
3983    fn replay_test_logical(
3984        ordinal: u32,
3985        node_index: u32,
3986        graph_node_count: u64,
3987    ) -> DeviceReplayedLogicalCommandAttribution {
3988        replay_test_logical_with_tokens(ordinal, node_index, 3, graph_node_count)
3989    }
3990
3991    fn replay_test_logical_with_tokens(
3992        ordinal: u32,
3993        node_index: u32,
3994        token_count: u64,
3995        graph_node_count: u64,
3996    ) -> DeviceReplayedLogicalCommandAttribution {
3997        DeviceReplayedLogicalCommandAttribution::new(
3998            ordinal,
3999            node_index,
4000            DeviceNativeOperationId::new("test.logical.compute").unwrap(),
4001            DeviceBatchingForm::ParticipantLoop,
4002            2,
4003            token_count,
4004            1,
4005            0,
4006            graph_node_count,
4007        )
4008        .unwrap()
4009    }
4010
4011    #[test]
4012    fn replayed_segment_separates_one_physical_launch_from_logical_plan_nodes() {
4013        let attribution = DeviceSubmissionAttribution::with_replayed_segments(
4014            vec![replay_test_physical(
4015                DeviceExecutionPath::Replayed,
4016                4,
4017                Some(5),
4018            )],
4019            vec![DeviceReplayedSegmentAttribution::new(
4020                0,
4021                replay_test_program_id(),
4022                DeviceReusableExecutionSegment::new(0, 4, 6, 2).unwrap(),
4023                "e".repeat(64),
4024                vec![
4025                    replay_test_logical_with_tokens(0, 4, 3, 2),
4026                    replay_test_logical_with_tokens(1, 5, 1, 3),
4027                ],
4028            )
4029            .unwrap()],
4030        )
4031        .unwrap();
4032
4033        assert_eq!(attribution.commands().len(), 1);
4034        assert_eq!(attribution.replayed_segments().len(), 1);
4035        assert_eq!(
4036            attribution.replayed_segments()[0].logical_commands().len(),
4037            2
4038        );
4039    }
4040
4041    #[test]
4042    fn replayed_segment_rejects_incomplete_or_mismatched_logical_attribution() {
4043        assert!(DeviceReplayedLogicalCommandAttribution::new(
4044            0,
4045            4,
4046            DeviceNativeOperationId::new("test.logical.compute").unwrap(),
4047            DeviceBatchingForm::ParticipantLoop,
4048            2,
4049            3,
4050            1,
4051            0,
4052            0,
4053        )
4054        .is_none());
4055
4056        let segment = DeviceReusableExecutionSegment::new(0, 4, 6, 2).unwrap();
4057        assert!(DeviceReplayedSegmentAttribution::new(
4058            0,
4059            replay_test_program_id(),
4060            segment.clone(),
4061            "e".repeat(64),
4062            vec![replay_test_logical(0, 4, 2), replay_test_logical(1, 6, 3)],
4063        )
4064        .is_none());
4065
4066        let replayed = || {
4067            DeviceReplayedSegmentAttribution::new(
4068                0,
4069                replay_test_program_id(),
4070                segment.clone(),
4071                "e".repeat(64),
4072                vec![replay_test_logical(0, 4, 2), replay_test_logical(1, 5, 3)],
4073            )
4074            .unwrap()
4075        };
4076        assert!(DeviceSubmissionAttribution::with_replayed_segments(
4077            vec![replay_test_physical(
4078                DeviceExecutionPath::Replayed,
4079                4,
4080                Some(4),
4081            )],
4082            vec![replayed()],
4083        )
4084        .is_none());
4085        assert!(DeviceSubmissionAttribution::with_replayed_segments(
4086            vec![replay_test_physical(DeviceExecutionPath::Eager, 4, None)],
4087            vec![replayed()],
4088        )
4089        .is_none());
4090        assert!(DeviceSubmissionAttribution::with_replayed_segments(
4091            vec![replay_test_physical(
4092                DeviceExecutionPath::Replayed,
4093                5,
4094                Some(5),
4095            )],
4096            vec![replayed()],
4097        )
4098        .is_none());
4099    }
4100}
4101
4102#[cfg(test)]
4103mod deferred_cleanup_tests {
4104    use super::*;
4105    use std::collections::VecDeque;
4106    use std::sync::atomic::{AtomicBool, AtomicUsize};
4107    use std::sync::{Arc, Barrier};
4108
4109    struct ScriptedCleanup {
4110        outcomes: VecDeque<DeferredDeviceCleanupDisposition>,
4111        attempts: Arc<AtomicUsize>,
4112        dropped: Arc<AtomicBool>,
4113    }
4114
4115    impl DeferredDeviceCleanupTask for ScriptedCleanup {
4116        fn try_cleanup(&mut self) -> DeferredDeviceCleanupDisposition {
4117            self.attempts.fetch_add(1, Ordering::AcqRel);
4118            self.outcomes
4119                .pop_front()
4120                .unwrap_or(DeferredDeviceCleanupDisposition::Completed)
4121        }
4122    }
4123
4124    impl Drop for ScriptedCleanup {
4125        fn drop(&mut self) {
4126            self.dropped.store(true, Ordering::Release);
4127        }
4128    }
4129
4130    struct PanicOnceCleanup {
4131        first: bool,
4132        attempts: Arc<AtomicUsize>,
4133    }
4134
4135    impl DeferredDeviceCleanupTask for PanicOnceCleanup {
4136        fn try_cleanup(&mut self) -> DeferredDeviceCleanupDisposition {
4137            self.attempts.fetch_add(1, Ordering::AcqRel);
4138            if self.first {
4139                self.first = false;
4140                panic!("injected deferred cleanup panic");
4141            }
4142            DeferredDeviceCleanupDisposition::Completed
4143        }
4144    }
4145
4146    struct BlockingCleanup {
4147        entered: Arc<Barrier>,
4148        release: Arc<Barrier>,
4149    }
4150
4151    impl DeferredDeviceCleanupTask for BlockingCleanup {
4152        fn try_cleanup(&mut self) -> DeferredDeviceCleanupDisposition {
4153            self.entered.wait();
4154            self.release.wait();
4155            DeferredDeviceCleanupDisposition::Completed
4156        }
4157    }
4158
4159    fn scripted(
4160        outcomes: impl IntoIterator<Item = DeferredDeviceCleanupDisposition>,
4161    ) -> (ScriptedCleanup, Arc<AtomicUsize>, Arc<AtomicBool>) {
4162        let attempts = Arc::new(AtomicUsize::new(0));
4163        let dropped = Arc::new(AtomicBool::new(false));
4164        (
4165            ScriptedCleanup {
4166                outcomes: outcomes.into_iter().collect(),
4167                attempts: Arc::clone(&attempts),
4168                dropped: Arc::clone(&dropped),
4169            },
4170            attempts,
4171            dropped,
4172        )
4173    }
4174
4175    #[test]
4176    fn encoded_operation_preserves_program_dynamic_compute_and_result_boundaries() {
4177        let operation = EncodedDeviceOperation::compute("compute")
4178            .with_program_binding("program-bind")
4179            .with_dynamic_binding("bind-a")
4180            .with_dynamic_binding("bind-b")
4181            .with_result_binding("writeback");
4182        let mut batch = DeviceCommandBatch::with_capacity(5);
4183        batch.push_operation(0, operation);
4184
4185        let entries = batch.into_entries();
4186        assert_eq!(
4187            entries
4188                .iter()
4189                .map(DeviceCommandEntry::phase)
4190                .collect::<Vec<_>>(),
4191            vec![
4192                DeviceCommandPhase::DynamicBinding,
4193                DeviceCommandPhase::DynamicBinding,
4194                DeviceCommandPhase::DynamicBinding,
4195                DeviceCommandPhase::Compute,
4196                DeviceCommandPhase::ResultBinding,
4197            ]
4198        );
4199        assert_eq!(
4200            entries
4201                .into_iter()
4202                .map(DeviceCommandEntry::into_parts)
4203                .map(|(_, _, _, command)| command)
4204                .collect::<Vec<_>>(),
4205            vec!["program-bind", "bind-a", "bind-b", "compute", "writeback",]
4206        );
4207    }
4208
4209    #[test]
4210    fn node_initialization_carries_core_owned_logical_work() {
4211        let logical_work =
4212            DeviceCommandLogicalWork::new(DeviceBatchingForm::Packed, 4, 17).unwrap();
4213        let mut batch = DeviceCommandBatch::with_capacity(1);
4214        batch.push_node_initialization(7, logical_work, "workspace-zero");
4215
4216        let mut entries = batch.into_entries();
4217        assert_eq!(entries.len(), 1);
4218        let entry = entries.pop().unwrap();
4219        assert_eq!(entry.phase(), DeviceCommandPhase::Initialization);
4220        assert_eq!(entry.node_index(), Some(7));
4221        assert_eq!(entry.logical_work(), Some(logical_work));
4222        assert_eq!(entry.command(), &"workspace-zero");
4223        assert_eq!(logical_work.participant_start(), 0);
4224        assert_eq!(logical_work.participant_end(), 4);
4225        let scoped =
4226            DeviceCommandLogicalWork::for_participant_range(DeviceBatchingForm::Scalar, 3, 1, 5)
4227                .unwrap();
4228        assert_eq!(scoped.participant_start(), 3);
4229        assert_eq!(scoped.participant_count(), 1);
4230        assert_eq!(scoped.participant_end(), 4);
4231        assert!(DeviceCommandLogicalWork::new(DeviceBatchingForm::Packed, 0, 17).is_err());
4232        assert!(DeviceCommandLogicalWork::for_participant_range(
4233            DeviceBatchingForm::Packed,
4234            u32::MAX,
4235            2,
4236            17,
4237        )
4238        .is_err());
4239    }
4240
4241    #[test]
4242    fn reusable_execution_observation_preserves_each_fallback_and_replay_counter() {
4243        let mut observation = DeviceReusableExecutionObservation::default();
4244        observation.observe_candidate_segment();
4245        observation.observe_captured_segment();
4246        observation.observe_uploaded_segment();
4247        observation.observe_cache_hit_segment();
4248        observation.observe_cached_rejected_segment();
4249        observation.observe_capture_rejection();
4250        observation.observe_quiescence_deferred_segment();
4251        observation.observe_capacity_deferred_segment();
4252        observation.observe_outside_preparation_segment();
4253        observation.observe_evicted_segment();
4254        observation.observe_replayed_segment(3);
4255        observation.observe_eager_command();
4256
4257        assert_eq!(observation.candidate_segments(), 1);
4258        assert_eq!(observation.captured_segments(), 1);
4259        assert_eq!(observation.uploaded_segments(), 1);
4260        assert_eq!(observation.cache_hit_segments(), 1);
4261        assert_eq!(observation.cached_rejected_segments(), 1);
4262        assert_eq!(observation.capture_rejected_segments(), 1);
4263        assert_eq!(observation.quiescence_deferred_segments(), 1);
4264        assert_eq!(observation.capacity_deferred_segments(), 1);
4265        assert_eq!(observation.outside_preparation_segments(), 1);
4266        assert_eq!(observation.evicted_segments(), 1);
4267        assert_eq!(observation.replayed_segments(), 1);
4268        assert_eq!(observation.replayed_commands(), 3);
4269        assert_eq!(observation.eager_commands(), 1);
4270
4271        let value = serde_json::to_value(observation).expect("observation serializes");
4272        for field in [
4273            "candidate_segments",
4274            "captured_segments",
4275            "uploaded_segments",
4276            "cache_hit_segments",
4277            "cached_rejected_segments",
4278            "capture_rejected_segments",
4279            "quiescence_deferred_segments",
4280            "capacity_deferred_segments",
4281            "outside_preparation_segments",
4282            "evicted_segments",
4283            "replayed_segments",
4284            "eager_commands",
4285        ] {
4286            assert_eq!(value[field], 1, "counter {field} must remain typed");
4287        }
4288        assert_eq!(value["replayed_commands"], 3);
4289    }
4290
4291    #[test]
4292    fn retryable_and_quarantined_cleanup_owners_remain_reachable() {
4293        for first in [
4294            DeferredDeviceCleanupDisposition::Retryable,
4295            DeferredDeviceCleanupDisposition::Quarantined,
4296        ] {
4297            let domain = new_deferred_device_cleanup_domain();
4298            let (task, attempts, dropped) =
4299                scripted([first, DeferredDeviceCleanupDisposition::Completed]);
4300            defer_device_cleanup(domain, task);
4301
4302            let first_receipt = maintain_deferred_device_cleanups(domain, 1);
4303            assert_eq!(first_receipt.attempted(), 1);
4304            assert_eq!(first_receipt.completed(), 0);
4305            assert_eq!(first_receipt.status_after().pending(), 1);
4306            assert!(!dropped.load(Ordering::Acquire));
4307
4308            let second_receipt = maintain_deferred_device_cleanups(domain, 1);
4309            assert_eq!(second_receipt.completed(), 1);
4310            assert_eq!(second_receipt.status_after().pending(), 0);
4311            assert_eq!(attempts.load(Ordering::Acquire), 2);
4312            assert!(dropped.load(Ordering::Acquire));
4313            assert!(retire_deferred_device_cleanup_domain(domain));
4314        }
4315    }
4316
4317    #[test]
4318    fn panicking_cleanup_owner_is_retried_in_place() {
4319        let domain = new_deferred_device_cleanup_domain();
4320        let attempts = Arc::new(AtomicUsize::new(0));
4321        defer_device_cleanup(
4322            domain,
4323            PanicOnceCleanup {
4324                first: true,
4325                attempts: Arc::clone(&attempts),
4326            },
4327        );
4328
4329        let first = maintain_deferred_device_cleanups(domain, 1);
4330        assert_eq!(first.panicked(), 1);
4331        assert_eq!(first.status_after().panicked(), 1);
4332        assert_eq!(first.status_after().pending(), 1);
4333        let second = maintain_deferred_device_cleanups(domain, 1);
4334        assert_eq!(second.completed(), 1);
4335        assert_eq!(second.status_after().pending(), 0);
4336        assert_eq!(attempts.load(Ordering::Acquire), 2);
4337        assert!(retire_deferred_device_cleanup_domain(domain));
4338    }
4339
4340    #[test]
4341    fn saturation_keeps_every_owner_and_bounds_each_maintenance_pass() {
4342        let domain = new_deferred_device_cleanup_domain();
4343        let task_count = MAX_DEFERRED_DEVICE_CLEANUP_TASKS + 1;
4344        for _ in 0..task_count {
4345            let (task, _, _) = scripted([DeferredDeviceCleanupDisposition::Completed]);
4346            defer_device_cleanup(domain, task);
4347        }
4348        let saturated = deferred_device_cleanup_status(domain);
4349        assert_eq!(saturated.pending(), task_count);
4350        assert!(saturated.is_saturated());
4351
4352        let first = maintain_deferred_device_cleanups(
4353            domain,
4354            MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS,
4355        );
4356        assert_eq!(first.attempted(), MAX_DEFERRED_DEVICE_CLEANUP_TASKS);
4357        assert_eq!(first.completed(), MAX_DEFERRED_DEVICE_CLEANUP_TASKS);
4358        assert_eq!(first.status_after().pending(), 1);
4359        let second = maintain_deferred_device_cleanups(domain, 1);
4360        assert_eq!(second.completed(), 1);
4361        assert_eq!(second.status_after().pending(), 0);
4362        assert!(retire_deferred_device_cleanup_domain(domain));
4363    }
4364
4365    #[test]
4366    fn blocked_cleanup_does_not_withhold_sibling_task_or_registry() {
4367        let domain = new_deferred_device_cleanup_domain();
4368        let entered = Arc::new(Barrier::new(2));
4369        let release = Arc::new(Barrier::new(2));
4370        defer_device_cleanup(
4371            domain,
4372            BlockingCleanup {
4373                entered: Arc::clone(&entered),
4374                release: Arc::clone(&release),
4375            },
4376        );
4377        let (ready, _, _) = scripted([DeferredDeviceCleanupDisposition::Completed]);
4378        defer_device_cleanup(domain, ready);
4379
4380        let (blocked_receipt, ready_receipt) = std::thread::scope(|scope| {
4381            let worker = std::thread::Builder::new()
4382                .name("vnext-cleanup-domain-isolation".to_owned())
4383                .spawn_scoped(scope, move || maintain_deferred_device_cleanups(domain, 2))
4384                .expect("the single bounded cleanup isolation worker starts");
4385            entered.wait();
4386            let ready_receipt = maintain_deferred_device_cleanups(domain, 1);
4387            release.wait();
4388            let blocked_receipt = worker
4389                .join()
4390                .expect("the bounded cleanup isolation worker does not panic");
4391            (blocked_receipt, ready_receipt)
4392        });
4393
4394        assert_eq!(ready_receipt.completed(), 1);
4395        assert_eq!(blocked_receipt.completed(), 1);
4396        assert_eq!(deferred_device_cleanup_status(domain).pending(), 0);
4397        assert!(retire_deferred_device_cleanup_domain(domain));
4398    }
4399}