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