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