Skip to main content

ferrum_interfaces/vnext/resource/
plan_runtime.rs

1use super::{
2    core_resource_failure, deferred_device_cleanup_status, invalid_resource,
3    maintain_deferred_device_cleanups, new_deferred_device_cleanup_domain,
4    retire_deferred_device_cleanup_domain, watch, AdmissionDeferred, AdmissionDemand,
5    AdmissionFitPolicy, AdmissionPressureAction, AdmissionRejected, AllocationLifetime, Arc,
6    AtomicU8, BackingPrepareDecision, CapacityAvailabilityEpoch, CapacityEntry, CapacityEpochs,
7    CapacityUnits, CapacityVector, CapacityWaitCondition, CapacityWaitRecheck,
8    DeferredDeviceCleanupDomainId, DeferredDeviceCleanupMaintenanceReceipt,
9    DeferredDeviceCleanupStatus, DeviceCapacityClaim, DeviceCapacitySignal, DeviceId,
10    DeviceRuntime, DynamicBackingDeferred, DynamicDeferredMaintenanceOutcome,
11    DynamicPoolGrowthBatchReceipt, DynamicPoolMaintenanceController, DynamicPoolMaintenanceStatus,
12    DynamicPoolSet, DynamicResourceShape, EvaluatedBackingProjection, EvaluatedBackingRequest,
13    ExecutionLane, ExecutionLaneCreationError, FailureEnvelope, InvocationLivenessMode,
14    LaneBackingPrepareDecision, LogicalAdmissionCoordinator, LogicalAdmissionCoordinatorId, Mutex,
15    NoStatic, NodeId, Ordering, PhysicalBackingClaimIdentity, PlanHash, PlanId, PlanNode,
16    ResourceAbandonSignal, ResourceActionCursor, ResourceDriverFailure,
17    ResourceLedgerEntrySnapshot, ResourceOwnershipReason, ResourceOwnershipTransferFailure,
18    ResourcePoolIdentity, ResourcePoolOwnership, ResourceReservation, ResourceReservationBatch,
19    ResourceTransactionAction, ResourceTransactionContext, ResourceTransactionDriver,
20    ResourceTransactionIdentity, ResourceTransactionState, RwLock, RwLockReadGuard, Serialize,
21    StaticProvisioningBinding, StaticProvisioningLease, VNextError,
22};
23use crate::vnext::{
24    ResolvedReusableExecutionBucket, ReusableExecutionBucketId, ReusableExecutionBucketSpec,
25};
26
27pub(super) const PLAN_RUNTIME_OPEN: u8 = 0;
28const PLAN_RUNTIME_CLOSING: u8 = 1;
29
30pub(super) trait ErasedPlanStaticDriver<R>: Send
31where
32    R: DeviceRuntime,
33{
34    fn release_resource(
35        &mut self,
36        context: &ResourceTransactionContext<'_, R>,
37        reservation: &ResourceReservation,
38        buffer: &R::Buffer,
39    ) -> Result<(), ResourceDriverFailure>;
40
41    fn quarantine_transaction(
42        &mut self,
43        context: &ResourceTransactionContext<'_, R>,
44        ownership: ResourcePoolOwnership<R>,
45    ) -> Result<(), ResourceOwnershipTransferFailure<R>>;
46
47    fn abandon_transaction(&mut self, ownership: ResourcePoolOwnership<R>);
48}
49
50impl<D> ErasedPlanStaticDriver<D::Runtime> for D
51where
52    D: ResourceTransactionDriver,
53{
54    fn release_resource(
55        &mut self,
56        context: &ResourceTransactionContext<'_, D::Runtime>,
57        reservation: &ResourceReservation,
58        buffer: &D::Buffer,
59    ) -> Result<(), ResourceDriverFailure> {
60        ResourceTransactionDriver::release_resource(self, context, reservation, buffer)
61    }
62
63    fn quarantine_transaction(
64        &mut self,
65        context: &ResourceTransactionContext<'_, D::Runtime>,
66        ownership: ResourcePoolOwnership<D::Runtime>,
67    ) -> Result<(), ResourceOwnershipTransferFailure<D::Runtime>> {
68        ResourceTransactionDriver::quarantine_transaction(self, context, ownership)
69    }
70
71    fn abandon_transaction(&mut self, ownership: ResourcePoolOwnership<D::Runtime>) {
72        ResourceTransactionDriver::abandon_transaction(self, ownership);
73    }
74}
75
76pub(super) struct PlanStaticResources<R>
77where
78    R: DeviceRuntime,
79{
80    pub(super) driver: Mutex<Option<Box<dyn ErasedPlanStaticDriver<R>>>>,
81    pub(super) identity: ResourceTransactionIdentity,
82    pub(super) admission: StaticProvisioningBinding,
83    pub(super) reservations: ResourceReservationBatch,
84    pub(super) states: Vec<ResourceTransactionState>,
85    pub(super) capacity_claim: Option<DeviceCapacityClaim>,
86    pub(super) lease: Option<StaticProvisioningLease<R>>,
87    pub(super) finalized: bool,
88}
89
90impl<R> PlanStaticResources<R>
91where
92    R: DeviceRuntime,
93{
94    fn ledger_snapshot_entries(&self) -> Vec<ResourceLedgerEntrySnapshot> {
95        self.lease
96            .as_ref()
97            .expect("open plan runtime owns its static lease")
98            .slots
99            .iter()
100            .zip(&self.states)
101            .map(|(slot, &transaction_state)| ResourceLedgerEntrySnapshot {
102                entry: slot.entry.clone(),
103                transaction_state,
104                buffer_present: slot.buffer.is_some(),
105                actual_resource_id: slot.actual_resource_id.clone(),
106                actual_generation: slot.actual_generation,
107                actual_descriptor: slot.descriptor.clone(),
108            })
109            .collect()
110    }
111
112    fn release_all(&mut self) -> Result<usize, ResourceDriverFailure> {
113        let mut released = 0;
114        for order in 0..self.states.len() {
115            if self.states[order] == ResourceTransactionState::Released {
116                continue;
117            }
118            if self.states[order] != ResourceTransactionState::Committed {
119                return Err(ResourceDriverFailure::new(core_resource_failure(
120                    "plan_runtime_close_ledger_diverged",
121                    "plan runtime close found a non-committed static resource",
122                    false,
123                ))
124                .expect("core failure has resource domain"));
125            }
126            let lease = self
127                .lease
128                .as_ref()
129                .expect("open plan runtime owns its static lease");
130            let reservation = self.reservations.reservations[order].clone();
131            let buffer = lease
132                .buffer(order)
133                .expect("committed plan runtime owns its static buffer");
134            let context = ResourceTransactionContext {
135                runtime: &lease.runtime,
136                identity: &self.identity,
137                binding: &self.admission,
138                reservations: &self.reservations,
139                cursor: Some(ResourceActionCursor {
140                    order,
141                    action: ResourceTransactionAction::Release,
142                    before: self.states[order],
143                    allocation_authorized: false,
144                }),
145                allocation_authority: None,
146                pending_allocation: None,
147            };
148            let driver = match self.driver.get_mut() {
149                Ok(driver) => driver,
150                Err(poisoned) => poisoned.into_inner(),
151            };
152            driver
153                .as_mut()
154                .expect("open plan runtime owns its static driver")
155                .release_resource(&context, &reservation, buffer)?;
156            self.lease
157                .as_mut()
158                .expect("open plan runtime owns its static lease")
159                .clear(order);
160            self.states[order] = ResourceTransactionState::Released;
161            self.capacity_claim
162                .as_mut()
163                .expect("open plan runtime owns its static capacity claim")
164                .release_bytes(reservation.size_bytes());
165            released += 1;
166        }
167        if let Some(mut claim) = self.capacity_claim.take() {
168            claim.release();
169        }
170        self.finalized = true;
171        Ok(released)
172    }
173
174    fn quarantine_remaining(&mut self) -> Result<usize, ResourceDriverFailure> {
175        let quarantined = self
176            .states
177            .iter()
178            .filter(|state| **state == ResourceTransactionState::Committed)
179            .count();
180        if self.states.iter().any(|state| {
181            !matches!(
182                state,
183                ResourceTransactionState::Committed | ResourceTransactionState::Released
184            )
185        }) {
186            return Err(ResourceDriverFailure::new(core_resource_failure(
187                "plan_runtime_quarantine_ledger_diverged",
188                "plan runtime quarantine found an invalid static resource state",
189                false,
190            ))
191            .expect("core failure has resource domain"));
192        }
193        if quarantined == 0 {
194            self.finalized = true;
195            return Ok(0);
196        }
197        let lease = self
198            .lease
199            .as_mut()
200            .expect("failed plan runtime close owns its static lease");
201        let buffers = lease.take_owned_buffers(&self.reservations);
202        let ownership = ResourcePoolOwnership {
203            runtime: Arc::clone(&lease.runtime),
204            pool_identity: self.admission.pool_identity.clone(),
205            reason: ResourceOwnershipReason::Quarantine,
206            signal: None,
207            buffers,
208            capacity_claim: self.capacity_claim.take(),
209        };
210        let result = {
211            let context = ResourceTransactionContext {
212                runtime: &lease.runtime,
213                identity: &self.identity,
214                binding: &self.admission,
215                reservations: &self.reservations,
216                cursor: None,
217                allocation_authority: None,
218                pending_allocation: None,
219            };
220            let driver = match self.driver.get_mut() {
221                Ok(driver) => driver,
222                Err(poisoned) => poisoned.into_inner(),
223            };
224            driver
225                .as_mut()
226                .expect("failed plan runtime close owns its static driver")
227                .quarantine_transaction(&context, ownership)
228        };
229        if let Err(failure) = result {
230            let (failure, mut ownership) = failure.into_parts();
231            let expected_claimed_bytes = self
232                .states
233                .iter()
234                .zip(self.reservations.reservations())
235                .filter(|(state, _)| state.is_live())
236                .map(|(_, reservation)| reservation.size_bytes())
237                .sum::<u64>();
238            if ownership.pool_identity != self.admission.pool_identity
239                || ownership.claimed_bytes() != expected_claimed_bytes
240            {
241                std::mem::forget(ownership);
242                return Err(ResourceDriverFailure::new(core_resource_failure(
243                    "ownership_transfer_identity_mismatch",
244                    "plan runtime quarantine failure returned foreign ownership",
245                    false,
246                ))
247                .expect("core failure has resource domain"));
248            }
249            self.lease
250                .as_mut()
251                .expect("failed plan runtime close owns its static lease")
252                .restore_owned_buffers(std::mem::take(&mut ownership.buffers));
253            self.capacity_claim = ownership.capacity_claim.take();
254            return Err(failure);
255        }
256        for state in &mut self.states {
257            if *state == ResourceTransactionState::Committed {
258                *state = ResourceTransactionState::Quarantined;
259            }
260        }
261        self.finalized = true;
262        Ok(quarantined)
263    }
264}
265
266impl<R> Drop for PlanStaticResources<R>
267where
268    R: DeviceRuntime,
269{
270    fn drop(&mut self) {
271        if self.finalized {
272            return;
273        }
274        let signal = ResourceAbandonSignal {
275            identity: self.identity.clone(),
276            admission: self.admission.clone(),
277            state: if self
278                .states
279                .iter()
280                .all(|state| *state == ResourceTransactionState::Committed)
281            {
282                ResourceTransactionState::Committed
283            } else {
284                ResourceTransactionState::Released
285            },
286            pending_action: None,
287            ledger: self.ledger_snapshot_entries(),
288            active_sequence_slots: Vec::new(),
289            poisoned_sequence_slots: Vec::new(),
290            undrained_sequence_slots: Vec::new(),
291            failure: None,
292        };
293        let mut lease = self
294            .lease
295            .take()
296            .expect("open plan runtime owns its static lease");
297        let buffers = lease.take_owned_buffers(&self.reservations);
298        let StaticProvisioningLease {
299            slots: _,
300            identity: _,
301            admission: _,
302            runtime,
303        } = lease;
304        let ownership = ResourcePoolOwnership {
305            runtime,
306            pool_identity: self.admission.pool_identity.clone(),
307            reason: ResourceOwnershipReason::Abandon,
308            signal: Some(signal),
309            buffers,
310            capacity_claim: self.capacity_claim.take(),
311        };
312        let driver = match self.driver.get_mut() {
313            Ok(driver) => driver,
314            Err(poisoned) => poisoned.into_inner(),
315        };
316        if let Some(driver) = driver.as_mut() {
317            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
318                driver.abandon_transaction(ownership);
319            }));
320        } else {
321            std::mem::forget(ownership);
322        }
323        self.finalized = true;
324    }
325}
326
327pub(super) enum PlanRuntimeStatic<R>
328where
329    R: DeviceRuntime,
330{
331    NoStatic { binding: StaticProvisioningBinding },
332    Static(PlanStaticResources<R>),
333}
334
335/// Unique plan-lifetime owner of the runtime, dynamic pools, maintenance
336/// authority, static buffers, capacity claims, and cleanup authority.
337#[must_use = "plan runtime resources must be explicitly closed or safely abandoned"]
338pub struct PlanRuntimeResources<R>
339where
340    R: DeviceRuntime,
341{
342    pub(super) lifecycle: RwLock<()>,
343    pub(super) phase: AtomicU8,
344    pub(super) lifecycle_tx: watch::Sender<u8>,
345    pub(super) maintenance_controller: DynamicPoolMaintenanceController<R>,
346    pub(super) dynamic_pools: Arc<DynamicPoolSet<R>>,
347    pub(super) static_resources: PlanRuntimeStatic<R>,
348    pub(super) runtime: Arc<R>,
349    pub(super) deferred_cleanup_domain: DeferredDeviceCleanupDomainId,
350}
351
352/// Sealed owning proof that one exact plan, runtime instance, provisioning
353/// outcome, and admission coordinator belong together. Every durable child
354/// authority holds the same root `Arc`.
355#[must_use = "a trusted plan/runtime binding must be consumed by logical admission"]
356pub struct TrustedPlanRuntimeBinding<R>
357where
358    R: DeviceRuntime,
359{
360    pub(super) resources: Arc<PlanRuntimeResources<R>>,
361}
362
363/// Internal owning capability that binds non-authoritative backing evidence to
364/// the one plan runtime allowed to revalidate it. Public scope-specific handles
365/// embed this value and retain any additional request/session/step parent.
366pub(super) struct PlanBackingDeferral<R>
367where
368    R: DeviceRuntime,
369{
370    evidence: DynamicBackingDeferred,
371    resources: Arc<PlanRuntimeResources<R>>,
372}
373
374impl<R> PlanBackingDeferral<R>
375where
376    R: DeviceRuntime,
377{
378    pub(super) fn new(
379        resources: Arc<PlanRuntimeResources<R>>,
380        evidence: DynamicBackingDeferred,
381    ) -> Result<Self, VNextError> {
382        {
383            let _lifecycle = resources.read_lifecycle("bind deferred backing to its plan")?;
384            if evidence.wait_condition().coordinator_id()
385                != resources.dynamic_pools.logical_admission.id()
386            {
387                return Err(invalid_resource(
388                    "deferred backing belongs to another plan coordinator",
389                ));
390            }
391        }
392        Ok(Self {
393            evidence,
394            resources,
395        })
396    }
397
398    pub(super) fn evidence(&self) -> &DynamicBackingDeferred {
399        &self.evidence
400    }
401
402    pub(super) fn maintain(&self) -> Result<DynamicDeferredMaintenanceOutcome, VNextError> {
403        let _lifecycle = self
404            .resources
405            .read_lifecycle("maintain plan-owned deferred backing")?;
406        // Stable lane slots own real residency. Reclaim a provably idle slot
407        // before asking the pool to grow, otherwise a reclaimable cache entry
408        // can make growth look like a terminal resident-ceiling violation.
409        if self
410            .resources
411            .dynamic_pools
412            .try_reclaim_expired_lane_slots()?
413        {
414            return self.retry_admission();
415        }
416        let outcome = self
417            .resources
418            .maintenance_controller
419            .maintain_for_live_deferred(&self.evidence)?;
420        if matches!(
421            &outcome,
422            DynamicDeferredMaintenanceOutcome::WaitForRelease { .. }
423        ) && self
424            .resources
425            .dynamic_pools
426            .try_reclaim_one_idle_lane_slot()?
427        {
428            return self.retry_admission();
429        }
430        Ok(outcome)
431    }
432
433    pub(super) fn retry_admission(&self) -> Result<DynamicDeferredMaintenanceOutcome, VNextError> {
434        let mut availability = Vec::with_capacity(self.resources.dynamic_pools.domains.len() + 3);
435        let current_epochs = self
436            .resources
437            .dynamic_pools
438            .write_capacity_availability(&mut availability)?;
439        Ok(DynamicDeferredMaintenanceOutcome::RetryAdmission { current_epochs })
440    }
441
442    pub(super) fn register_waiter(&self) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
443        self.resources
444            .register_capacity_waiter(self.evidence.wait_condition())
445    }
446}
447
448/// A capacity wait registration that keeps its exact plan runtime alive until
449/// the waiter either observes a retry epoch or is cancelled by being dropped.
450#[must_use = "capacity wait registrations must be awaited, rechecked, or dropped"]
451pub struct PlanCapacityWaitRegistration<R>
452where
453    R: DeviceRuntime,
454{
455    observed: CapacityWaitCondition,
456    registered: CapacityWaitCondition,
457    logical_rx: watch::Receiver<CapacityEpochs>,
458    plan_capacity_rx: watch::Receiver<DeviceCapacitySignal>,
459    process_capacity_rx: watch::Receiver<DeviceCapacitySignal>,
460    lifecycle_rx: watch::Receiver<u8>,
461    resources: Arc<PlanRuntimeResources<R>>,
462}
463
464impl<R> PlanCapacityWaitRegistration<R>
465where
466    R: DeviceRuntime,
467{
468    pub fn recheck(&self) -> Result<CapacityWaitRecheck, VNextError> {
469        let _lifecycle = self.resources.read_lifecycle("recheck a capacity waiter")?;
470        let mut availability = Vec::with_capacity(self.resources.dynamic_pools.domains.len() + 3);
471        let current = self
472            .resources
473            .dynamic_pools
474            .write_capacity_availability(&mut availability)?;
475        Ok(CapacityWaitRecheck::new(
476            current,
477            self.observed.changed_since(&availability)?,
478            self.registered.changed_since(&availability)?,
479        ))
480    }
481
482    pub async fn wait_for_change(self) -> Result<CapacityEpochs, VNextError> {
483        let Self {
484            observed,
485            registered,
486            mut logical_rx,
487            mut plan_capacity_rx,
488            mut process_capacity_rx,
489            mut lifecycle_rx,
490            resources,
491        } = self;
492        loop {
493            let recheck = {
494                let _lifecycle = match resources.read_lifecycle("recheck a capacity waiter") {
495                    Ok(lifecycle) => lifecycle,
496                    Err(_) if resources.is_closing() => {
497                        return Err(invalid_resource(
498                            "closing plan runtime cancelled its capacity waiter",
499                        ));
500                    }
501                    Err(error) => return Err(error),
502                };
503                let mut availability =
504                    Vec::with_capacity(resources.dynamic_pools.domains.len() + 3);
505                let current = resources
506                    .dynamic_pools
507                    .write_capacity_availability(&mut availability)?;
508                CapacityWaitRecheck::new(
509                    current,
510                    observed.changed_since(&availability)?,
511                    registered.changed_since(&availability)?,
512                )
513            };
514            if recheck.should_retry() {
515                return Ok(recheck.current());
516            }
517            tokio::select! {
518                biased;
519                changed = lifecycle_rx.changed() => {
520                    changed.map_err(|_| invalid_resource(
521                        "plan runtime lifecycle signal closed while a capacity waiter was live",
522                    ))?;
523                    if *lifecycle_rx.borrow_and_update() == PLAN_RUNTIME_CLOSING {
524                        return Err(invalid_resource(
525                            "closing plan runtime cancelled its capacity waiter",
526                        ));
527                    }
528                    return Err(invalid_resource(
529                        "capacity waiter observed an invalid plan runtime lifecycle transition",
530                    ));
531                }
532                changed = logical_rx.changed() => {
533                    changed.map_err(|_| invalid_resource(
534                        "logical capacity signal closed while a plan waiter was live",
535                    ))?;
536                    logical_rx.borrow_and_update();
537                }
538                changed = plan_capacity_rx.changed() => {
539                    changed.map_err(|_| invalid_resource(
540                        "plan device-capacity signal closed while a waiter was live",
541                    ))?;
542                    plan_capacity_rx.borrow_and_update();
543                }
544                changed = process_capacity_rx.changed() => {
545                    changed.map_err(|_| invalid_resource(
546                        "process device-capacity signal closed while a waiter was live",
547                    ))?;
548                    process_capacity_rx.borrow_and_update();
549                }
550            }
551        }
552    }
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
556pub struct PlanRuntimeCloseReceipt {
557    evidence: TrustedPlanRuntimeEvidence,
558    released_static_resources: usize,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
562pub struct PlanRuntimeQuarantineReceipt {
563    evidence: TrustedPlanRuntimeEvidence,
564    released_static_resources: usize,
565    quarantined_static_resources: usize,
566}
567
568impl PlanRuntimeQuarantineReceipt {
569    pub fn evidence(&self) -> &TrustedPlanRuntimeEvidence {
570        &self.evidence
571    }
572
573    pub const fn released_static_resources(&self) -> usize {
574        self.released_static_resources
575    }
576
577    pub const fn quarantined_static_resources(&self) -> usize {
578        self.quarantined_static_resources
579    }
580}
581
582impl PlanRuntimeCloseReceipt {
583    pub fn evidence(&self) -> &TrustedPlanRuntimeEvidence {
584        &self.evidence
585    }
586
587    pub const fn released_static_resources(&self) -> usize {
588        self.released_static_resources
589    }
590}
591
592pub enum PlanRuntimeCloseOutcome<R>
593where
594    R: DeviceRuntime,
595{
596    Closed(PlanRuntimeCloseReceipt),
597    Referenced {
598        resources: Arc<PlanRuntimeResources<R>>,
599        strong_count: usize,
600        deferred_cleanup: DeferredDeviceCleanupStatus,
601    },
602}
603
604#[must_use = "failed plan runtime close retains static cleanup ownership"]
605pub struct PlanRuntimeCloseFailure<R>
606where
607    R: DeviceRuntime,
608{
609    failure: FailureEnvelope,
610    evidence: TrustedPlanRuntimeEvidence,
611    static_resources: Option<PlanStaticResources<R>>,
612}
613
614impl<R> PlanRuntimeCloseFailure<R>
615where
616    R: DeviceRuntime,
617{
618    pub fn failure(&self) -> &FailureEnvelope {
619        &self.failure
620    }
621
622    pub fn retry(mut self) -> Result<PlanRuntimeCloseReceipt, Self> {
623        let static_resources = self
624            .static_resources
625            .as_mut()
626            .expect("plan runtime close failure owns static cleanup authority");
627        let total_static_resources = static_resources.states.len();
628        match static_resources.release_all() {
629            Ok(_) => {
630                self.static_resources.take();
631                Ok(PlanRuntimeCloseReceipt {
632                    evidence: self.evidence,
633                    released_static_resources: total_static_resources,
634                })
635            }
636            Err(failure) => {
637                self.failure = failure.into_failure();
638                Err(self)
639            }
640        }
641    }
642
643    pub fn quarantine(mut self) -> Result<PlanRuntimeQuarantineReceipt, Self> {
644        let static_resources = self
645            .static_resources
646            .as_mut()
647            .expect("plan runtime close failure owns static cleanup authority");
648        let released_static_resources = static_resources
649            .states
650            .iter()
651            .filter(|state| **state == ResourceTransactionState::Released)
652            .count();
653        match static_resources.quarantine_remaining() {
654            Ok(quarantined_static_resources) => {
655                self.static_resources.take();
656                Ok(PlanRuntimeQuarantineReceipt {
657                    evidence: self.evidence,
658                    released_static_resources,
659                    quarantined_static_resources,
660                })
661            }
662            Err(failure) => {
663                self.failure = failure.into_failure();
664                Err(self)
665            }
666        }
667    }
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
671pub struct TrustedPlanRuntimeEvidence {
672    plan_id: PlanId,
673    plan_hash: PlanHash,
674    device_id: DeviceId,
675    runtime_implementation_fingerprint: String,
676    coordinator_id: LogicalAdmissionCoordinatorId,
677    static_provisioning_binding: Option<StaticProvisioningBinding>,
678    static_pool_identity: Option<ResourcePoolIdentity>,
679    static_provisioning_identity: Option<ResourceTransactionIdentity>,
680}
681
682impl TrustedPlanRuntimeEvidence {
683    pub fn plan_id(&self) -> &PlanId {
684        &self.plan_id
685    }
686
687    pub fn plan_hash(&self) -> &PlanHash {
688        &self.plan_hash
689    }
690
691    pub fn device_id(&self) -> &DeviceId {
692        &self.device_id
693    }
694
695    pub fn runtime_implementation_fingerprint(&self) -> &str {
696        &self.runtime_implementation_fingerprint
697    }
698
699    pub const fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
700        self.coordinator_id
701    }
702
703    pub fn static_pool_identity(&self) -> Option<&ResourcePoolIdentity> {
704        self.static_pool_identity.as_ref()
705    }
706
707    pub fn static_provisioning_binding(&self) -> Option<&StaticProvisioningBinding> {
708        self.static_provisioning_binding.as_ref()
709    }
710
711    pub fn static_provisioning_identity(&self) -> Option<&ResourceTransactionIdentity> {
712        self.static_provisioning_identity.as_ref()
713    }
714}
715
716impl<R> NoStatic<R>
717where
718    R: DeviceRuntime,
719{
720    pub fn into_plan_runtime(self) -> Arc<PlanRuntimeResources<R>> {
721        let Self {
722            maintenance_controller,
723            dynamic_pools,
724            binding,
725            runtime,
726        } = self;
727        let (lifecycle_tx, _) = watch::channel(PLAN_RUNTIME_OPEN);
728        Arc::new(PlanRuntimeResources {
729            lifecycle: RwLock::new(()),
730            phase: AtomicU8::new(PLAN_RUNTIME_OPEN),
731            lifecycle_tx,
732            maintenance_controller,
733            dynamic_pools,
734            static_resources: PlanRuntimeStatic::NoStatic { binding },
735            runtime,
736            deferred_cleanup_domain: new_deferred_device_cleanup_domain(),
737        })
738    }
739}
740
741impl<R> PlanRuntimeResources<R>
742where
743    R: DeviceRuntime,
744{
745    fn evidence(&self) -> TrustedPlanRuntimeEvidence {
746        let (binding, identity) = match &self.static_resources {
747            PlanRuntimeStatic::NoStatic { binding } => (binding, None),
748            PlanRuntimeStatic::Static(source) => (&source.admission, Some(&source.identity)),
749        };
750        let has_static = matches!(&self.static_resources, PlanRuntimeStatic::Static(_));
751        TrustedPlanRuntimeEvidence {
752            plan_id: binding.plan_id().clone(),
753            plan_hash: binding.plan_hash().clone(),
754            device_id: binding.device_id().clone(),
755            runtime_implementation_fingerprint: binding
756                .device_runtime_implementation_fingerprint()
757                .to_owned(),
758            coordinator_id: self.dynamic_pools.logical_admission.id(),
759            static_provisioning_binding: has_static.then(|| binding.clone()),
760            static_pool_identity: has_static.then(|| binding.pool_identity().clone()),
761            static_provisioning_identity: identity.cloned(),
762        }
763    }
764
765    pub(super) fn read_lifecycle(
766        &self,
767        action: &'static str,
768    ) -> Result<RwLockReadGuard<'_, ()>, VNextError> {
769        let lifecycle = self
770            .lifecycle
771            .read()
772            .map_err(|_| invalid_resource("plan runtime lifecycle gate is poisoned"))?;
773        if self.phase.load(Ordering::Acquire) != PLAN_RUNTIME_OPEN {
774            return Err(invalid_resource(format!(
775                "closing plan runtime cannot {action}"
776            )));
777        }
778        let cleanup = self.deferred_cleanup_status();
779        if cleanup.is_saturated() {
780            return Err(invalid_resource(format!(
781                "plan runtime cannot {action} while {} deferred device cleanup owners await recovery",
782                cleanup.pending()
783            )));
784        }
785        Ok(lifecycle)
786    }
787
788    pub fn deferred_cleanup_status(&self) -> DeferredDeviceCleanupStatus {
789        deferred_device_cleanup_status(self.deferred_cleanup_domain)
790    }
791
792    pub fn create_execution_lane(
793        &self,
794    ) -> Result<Arc<ExecutionLane<R>>, ExecutionLaneCreationError<R::Error>> {
795        let _lifecycle = self
796            .read_lifecycle("create an execution lane")
797            .map_err(ExecutionLaneCreationError::Contract)?;
798        ExecutionLane::create(Arc::clone(&self.runtime))
799    }
800
801    /// Attempts each selected cleanup owner at most once. This may block in a
802    /// backend recovery call and must therefore run on a scheduler recovery
803    /// thread, never on a request, admission, or destructor path.
804    pub fn maintain_deferred_cleanups(
805        &self,
806        maximum_tasks: usize,
807    ) -> Result<DeferredDeviceCleanupMaintenanceReceipt, VNextError> {
808        if maximum_tasks == 0
809            || maximum_tasks > super::MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS
810        {
811            return Err(invalid_resource(format!(
812                "deferred device cleanup maintenance size must be in 1..={}",
813                super::MAX_DEFERRED_DEVICE_CLEANUP_MAINTENANCE_TASKS
814            )));
815        }
816        Ok(maintain_deferred_device_cleanups(
817            self.deferred_cleanup_domain,
818            maximum_tasks,
819        ))
820    }
821
822    pub fn trusted_runtime_binding(
823        self: &Arc<Self>,
824    ) -> Result<TrustedPlanRuntimeBinding<R>, VNextError> {
825        let _lifecycle = self.read_lifecycle("mint a trusted runtime binding")?;
826        Ok(TrustedPlanRuntimeBinding {
827            resources: Arc::clone(self),
828        })
829    }
830
831    pub fn maintain_for_admission_deferred(
832        self: &Arc<Self>,
833        deferred: &AdmissionDeferred,
834    ) -> Result<DynamicDeferredMaintenanceOutcome, VNextError> {
835        let _lifecycle = self.read_lifecycle("maintain deferred logical backing growth")?;
836        self.maintenance_controller
837            .maintain_for_admission_deferred(deferred)
838    }
839
840    /// Attempts currently needed foreground pool growth without reclaiming
841    /// other pools or increasing execution slots. `None` means a slot or
842    /// non-pool blocker is inapplicable. Any receipt, including an empty one,
843    /// requires a fresh admission probe; it does not reserve the free capacity.
844    /// Device/pool capacity errors remain typed so the caller can next apply
845    /// its existing cache-eviction or waiting policy.
846    pub fn try_maintain_for_capacity_pressure(
847        self: &Arc<Self>,
848        deferred: &AdmissionDeferred,
849    ) -> Result<Option<DynamicPoolGrowthBatchReceipt>, VNextError> {
850        let _lifecycle = self.read_lifecycle("maintain foreground capacity pressure")?;
851        self.maintenance_controller
852            .try_maintain_for_capacity_pressure(deferred)
853    }
854
855    /// Returns a point-in-time view of the exact dynamic pools owned by this
856    /// plan. Product telemetry consumes this instead of maintaining a second
857    /// allocator ledger that can drift from admission decisions.
858    pub fn dynamic_pool_status(&self) -> Result<DynamicPoolMaintenanceStatus, VNextError> {
859        let _lifecycle = self.read_lifecycle("observe dynamic pool status")?;
860        self.maintenance_controller.status()
861    }
862
863    pub fn write_dynamic_capacity_availability(
864        &self,
865        out: &mut Vec<CapacityAvailabilityEpoch>,
866    ) -> Result<CapacityEpochs, VNextError> {
867        let _lifecycle = self.read_lifecycle("observe dynamic capacity availability")?;
868        self.dynamic_pools.write_capacity_availability(out)
869    }
870
871    pub fn register_capacity_waiter(
872        self: &Arc<Self>,
873        observed: &CapacityWaitCondition,
874    ) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
875        let _lifecycle = self.read_lifecycle("register a capacity waiter")?;
876        if observed.coordinator_id() != self.dynamic_pools.logical_admission.id() {
877            return Err(invalid_resource(
878                "capacity wait condition belongs to another plan coordinator",
879            ));
880        }
881        let logical_rx = self.dynamic_pools.logical_admission.subscribe_epochs();
882        let plan_capacity_rx = self.dynamic_pools.budget.subscribe_plan_availability();
883        let process_capacity_rx = self.dynamic_pools.budget.subscribe_process_availability();
884        let lifecycle_rx = self.lifecycle_tx.subscribe();
885        let mut availability = Vec::with_capacity(self.dynamic_pools.domains.len() + 3);
886        self.dynamic_pools
887            .write_capacity_availability(&mut availability)?;
888        let registered = observed.refreshed_from(&availability)?;
889        Ok(PlanCapacityWaitRegistration {
890            observed: observed.clone(),
891            registered,
892            logical_rx,
893            plan_capacity_rx,
894            process_capacity_rx,
895            lifecycle_rx,
896            resources: Arc::clone(self),
897        })
898    }
899
900    pub fn is_closing(&self) -> bool {
901        self.phase.load(Ordering::Acquire) == PLAN_RUNTIME_CLOSING
902    }
903
904    pub fn close(
905        resources: Arc<Self>,
906    ) -> Result<PlanRuntimeCloseOutcome<R>, PlanRuntimeCloseFailure<R>> {
907        {
908            let _lifecycle = resources
909                .lifecycle
910                .write()
911                .unwrap_or_else(std::sync::PoisonError::into_inner);
912            // The write gate excludes checkpoint prepare/claim/commit. A
913            // poisoned coordinator is already fail-closed; existing owners
914            // still keep the plan alive and may release their backing.
915            let _ = resources
916                .dynamic_pools
917                .logical_admission
918                .close_checkpoint_admission();
919            match resources.phase.compare_exchange(
920                PLAN_RUNTIME_OPEN,
921                PLAN_RUNTIME_CLOSING,
922                Ordering::AcqRel,
923                Ordering::Acquire,
924            ) {
925                Ok(_) => {
926                    resources.lifecycle_tx.send_replace(PLAN_RUNTIME_CLOSING);
927                }
928                Err(PLAN_RUNTIME_CLOSING) => {}
929                Err(_) => unreachable!("plan runtime phase is privately bounded"),
930            }
931        }
932        let resources = match Arc::try_unwrap(resources) {
933            Ok(resources) => resources,
934            Err(resources) => {
935                let strong_count = Arc::strong_count(&resources);
936                let deferred_cleanup = resources.deferred_cleanup_status();
937                return Ok(PlanRuntimeCloseOutcome::Referenced {
938                    resources,
939                    strong_count,
940                    deferred_cleanup,
941                });
942            }
943        };
944        if resources.deferred_cleanup_status().pending() != 0 {
945            let resources = Arc::new(resources);
946            let deferred_cleanup = resources.deferred_cleanup_status();
947            return Ok(PlanRuntimeCloseOutcome::Referenced {
948                resources,
949                strong_count: 1,
950                deferred_cleanup,
951            });
952        }
953        let evidence = resources.evidence();
954        let Self {
955            lifecycle: _,
956            phase: _,
957            lifecycle_tx,
958            maintenance_controller,
959            dynamic_pools,
960            static_resources,
961            runtime,
962            deferred_cleanup_domain,
963        } = resources;
964        debug_assert!(retire_deferred_device_cleanup_domain(
965            deferred_cleanup_domain
966        ));
967        drop(lifecycle_tx);
968        drop(maintenance_controller);
969        drop(dynamic_pools);
970        match static_resources {
971            PlanRuntimeStatic::NoStatic { .. } => {
972                drop(runtime);
973                Ok(PlanRuntimeCloseOutcome::Closed(PlanRuntimeCloseReceipt {
974                    evidence,
975                    released_static_resources: 0,
976                }))
977            }
978            PlanRuntimeStatic::Static(mut static_resources) => {
979                drop(runtime);
980                let total_static_resources = static_resources.states.len();
981                match static_resources.release_all() {
982                    Ok(_) => {
983                        drop(static_resources);
984                        Ok(PlanRuntimeCloseOutcome::Closed(PlanRuntimeCloseReceipt {
985                            evidence,
986                            released_static_resources: total_static_resources,
987                        }))
988                    }
989                    Err(failure) => Err(PlanRuntimeCloseFailure {
990                        failure: failure.into_failure(),
991                        evidence,
992                        static_resources: Some(static_resources),
993                    }),
994                }
995            }
996        }
997    }
998}
999
1000impl<R> TrustedPlanRuntimeBinding<R>
1001where
1002    R: DeviceRuntime,
1003{
1004    pub(super) fn runtime(&self) -> &Arc<R> {
1005        &self.resources.runtime
1006    }
1007
1008    pub(super) fn logical_admission(&self) -> &LogicalAdmissionCoordinator {
1009        &self.dynamic_pools().logical_admission
1010    }
1011
1012    pub(super) fn dynamic_pools(&self) -> &Arc<DynamicPoolSet<R>> {
1013        &self.resources.dynamic_pools
1014    }
1015
1016    pub(super) fn nodes(&self) -> &[PlanNode] {
1017        &self.dynamic_pools().nodes
1018    }
1019
1020    pub(super) fn reusable_execution_bucket(
1021        &self,
1022        bucket_id: &ReusableExecutionBucketId,
1023    ) -> Option<&ResolvedReusableExecutionBucket> {
1024        self.dynamic_pools()
1025            .reusable_execution
1026            .as_ref()
1027            .and_then(|plan| plan.bucket(bucket_id))
1028    }
1029
1030    pub fn plan_id(&self) -> &PlanId {
1031        match &self.resources.static_resources {
1032            PlanRuntimeStatic::NoStatic { binding } => binding.plan_id(),
1033            PlanRuntimeStatic::Static(source) => source.admission.plan_id(),
1034        }
1035    }
1036
1037    pub fn plan_hash(&self) -> &PlanHash {
1038        match &self.resources.static_resources {
1039            PlanRuntimeStatic::NoStatic { binding } => binding.plan_hash(),
1040            PlanRuntimeStatic::Static(source) => source.admission.plan_hash(),
1041        }
1042    }
1043
1044    pub fn device_id(&self) -> &DeviceId {
1045        match &self.resources.static_resources {
1046            PlanRuntimeStatic::NoStatic { binding } => binding.device_id(),
1047            PlanRuntimeStatic::Static(source) => source.admission.device_id(),
1048        }
1049    }
1050
1051    pub fn runtime_implementation_fingerprint(&self) -> &str {
1052        match &self.resources.static_resources {
1053            PlanRuntimeStatic::NoStatic { binding } => {
1054                binding.device_runtime_implementation_fingerprint()
1055            }
1056            PlanRuntimeStatic::Static(source) => {
1057                source.admission.device_runtime_implementation_fingerprint()
1058            }
1059        }
1060    }
1061
1062    pub fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
1063        self.logical_admission().id()
1064    }
1065
1066    pub fn static_provisioning(&self) -> Option<&StaticProvisioningLease<R>> {
1067        match &self.resources.static_resources {
1068            PlanRuntimeStatic::NoStatic { .. } => None,
1069            PlanRuntimeStatic::Static(source) => source.lease.as_ref(),
1070        }
1071    }
1072
1073    pub fn evidence(&self) -> TrustedPlanRuntimeEvidence {
1074        self.resources.evidence()
1075    }
1076
1077    /// Rejects only a plan-derived lower bound that cannot fit even after every
1078    /// other owner releases. Current occupancy and process-wide contention are
1079    /// deliberately absent: they can disappear and must remain recoverable.
1080    pub(super) fn reject_impossible_plan_fit(
1081        &self,
1082        immediate: &CapacityVector,
1083        fit: &CapacityVector,
1084    ) -> Result<Option<AdmissionRejected>, VNextError> {
1085        let pools = &self.resources.dynamic_pools;
1086        let snapshot = self.logical_admission().snapshot()?;
1087        for entry in fit.entries() {
1088            let domain = snapshot
1089                .domains()
1090                .iter()
1091                .find(|domain| domain.domain() == entry.domain())
1092                .ok_or_else(|| {
1093                    invalid_resource("plan fit references an unknown capacity domain")
1094                })?;
1095            // Preserve the existing, more specific per-domain rejection.
1096            if entry.units().get() > domain.maximum_total().get() {
1097                return Ok(None);
1098            }
1099        }
1100        let binding = match &self.resources.static_resources {
1101            PlanRuntimeStatic::NoStatic { binding } => binding,
1102            PlanRuntimeStatic::Static(source) => &source.admission,
1103        };
1104        let minimum_required =
1105            pools
1106                .domains
1107                .iter()
1108                .try_fold(binding.plan_static_bytes(), |total, domain| {
1109                    let requested = fit
1110                        .units_for(domain.domain_id)
1111                        .map_or(0, CapacityUnits::get);
1112                    let minimum = domain.pool.provisioning().minimum_resident_bytes();
1113                    total
1114                        .checked_add(minimum.max(requested))
1115                        .ok_or_else(|| invalid_resource("joint plan fit lower bound overflows u64"))
1116                })?;
1117        Ok(
1118            (minimum_required > binding.usable_capacity_bytes()).then(|| {
1119                AdmissionRejected::for_plan_budget(
1120                    immediate.clone(),
1121                    fit.clone(),
1122                    snapshot,
1123                    minimum_required,
1124                    binding.usable_capacity_bytes(),
1125                )
1126            }),
1127        )
1128    }
1129
1130    pub(super) fn scoped_demand(
1131        &self,
1132        lifetime: AllocationLifetime,
1133        node_id: Option<&NodeId>,
1134        immediate_shape: DynamicResourceShape,
1135        fit_shape: DynamicResourceShape,
1136        reusable_execution_bucket: Option<&ReusableExecutionBucketSpec>,
1137        fit_policy: AdmissionFitPolicy,
1138        pressure_action: AdmissionPressureAction,
1139    ) -> Result<(AdmissionDemand, Vec<EvaluatedBackingRequest<'_>>), VNextError> {
1140        if (lifetime == AllocationLifetime::Invocation) != node_id.is_some() {
1141            return Err(invalid_resource(
1142                "invocation resource demand requires one exact node identity",
1143            ));
1144        }
1145        let capacity_shape = match reusable_execution_bucket {
1146            Some(bucket)
1147                if matches!(
1148                    lifetime,
1149                    AllocationLifetime::Step | AllocationLifetime::Invocation
1150                ) && bucket.capacity().covers(
1151                    immediate_shape.sequences(),
1152                    immediate_shape.tokens(),
1153                    immediate_shape.pages(),
1154                ) && bucket.capacity().covers(
1155                    fit_shape.sequences(),
1156                    fit_shape.tokens(),
1157                    fit_shape.pages(),
1158                ) =>
1159            {
1160                DynamicResourceShape::from_validated(
1161                    bucket.capacity().maximum_sequences(),
1162                    bucket.capacity().maximum_tokens(),
1163                    bucket.capacity().maximum_pages(),
1164                )
1165            }
1166            Some(_) => {
1167                return Err(invalid_resource(
1168                    "reusable execution bucket does not cover this Step or Invocation demand",
1169                ));
1170            }
1171            None => immediate_shape,
1172        };
1173        let node_resources = node_id
1174            .map(|node_id| {
1175                self.nodes()
1176                    .iter()
1177                    .find(|node| node.id() == node_id)
1178                    .map(PlanNode::resources)
1179                    .ok_or_else(|| {
1180                        invalid_resource("resource admission references an unknown node")
1181                    })
1182            })
1183            .transpose()?;
1184        let mut immediate_entries = Vec::new();
1185        let mut fit_entries = Vec::new();
1186        let mut requested_slices = Vec::new();
1187        for domain in &self.dynamic_pools().domains {
1188            let mut immediate_pool_bytes = 0_u64;
1189            let mut fit_pool_bytes = 0_u64;
1190            let mut matched = false;
1191            if lifetime == AllocationLifetime::Step {
1192                for slot in domain.pool.step_resource_slots() {
1193                    let mut projections = Vec::with_capacity(slot.resource_ids().len());
1194                    let mut immediate_slot_bytes = 0_u64;
1195                    let mut fit_slot_bytes = 0_u64;
1196                    let mut capacity_slot_bytes = 0_u64;
1197                    for resource_id in slot.resource_ids() {
1198                        let descriptor = domain
1199                            .descriptors
1200                            .iter()
1201                            .find(|descriptor| descriptor.base_resource_id() == resource_id)
1202                            .ok_or_else(|| {
1203                                invalid_resource(
1204                                    "step physical slot references a descriptor outside its pool",
1205                                )
1206                            })?;
1207                        if descriptor.lifetime() != AllocationLifetime::Step {
1208                            return Err(invalid_resource(
1209                                "step physical slot references a non-Step descriptor",
1210                            ));
1211                        }
1212                        let logical_size_bytes =
1213                            descriptor.evaluate_request_bytes_for_shape(immediate_shape)?;
1214                        let fit_bytes = descriptor.evaluate_request_bytes_for_shape(fit_shape)?;
1215                        let capacity_size_bytes =
1216                            descriptor.evaluate_request_bytes_for_shape(capacity_shape)?;
1217                        immediate_slot_bytes = immediate_slot_bytes.max(logical_size_bytes);
1218                        fit_slot_bytes = fit_slot_bytes.max(fit_bytes);
1219                        capacity_slot_bytes = capacity_slot_bytes.max(capacity_size_bytes);
1220                        projections.push(EvaluatedBackingProjection {
1221                            descriptor,
1222                            physical_offset_bytes: 0,
1223                            logical_size_bytes,
1224                            capacity_size_bytes,
1225                        });
1226                    }
1227                    immediate_pool_bytes = immediate_pool_bytes
1228                        .checked_add(immediate_slot_bytes)
1229                        .ok_or_else(|| {
1230                        invalid_resource("dynamic pool immediate demand overflows u64")
1231                    })?;
1232                    fit_pool_bytes = fit_pool_bytes
1233                        .checked_add(fit_slot_bytes)
1234                        .ok_or_else(|| invalid_resource("dynamic pool fit demand overflows u64"))?;
1235                    requested_slices.push(EvaluatedBackingRequest {
1236                        domain,
1237                        claim_identity: PhysicalBackingClaimIdentity::new(
1238                            domain.pool_id().clone(),
1239                            slot.resource_ids().to_vec(),
1240                        )?,
1241                        capacity_size_bytes: capacity_slot_bytes,
1242                        reusable_execution_bucket_id: reusable_execution_bucket
1243                            .map(|bucket| bucket.bucket_id().clone()),
1244                        projections,
1245                    });
1246                    matched = true;
1247                }
1248            } else {
1249                for descriptor in &domain.descriptors {
1250                    if descriptor.lifetime() != lifetime
1251                        || node_resources.is_some_and(|resources| {
1252                            !resources.contains(descriptor.base_resource_id())
1253                        })
1254                    {
1255                        continue;
1256                    }
1257                    matched = true;
1258                    let logical_size_bytes =
1259                        descriptor.evaluate_request_bytes_for_shape(immediate_shape)?;
1260                    let fit_bytes = descriptor.evaluate_request_bytes_for_shape(fit_shape)?;
1261                    let capacity_size_bytes =
1262                        descriptor.evaluate_request_bytes_for_shape(capacity_shape)?;
1263                    immediate_pool_bytes = immediate_pool_bytes
1264                        .checked_add(logical_size_bytes)
1265                        .ok_or_else(|| {
1266                            invalid_resource("dynamic pool immediate demand overflows u64")
1267                        })?;
1268                    fit_pool_bytes = fit_pool_bytes
1269                        .checked_add(fit_bytes)
1270                        .ok_or_else(|| invalid_resource("dynamic pool fit demand overflows u64"))?;
1271                    requested_slices.push(EvaluatedBackingRequest {
1272                        domain,
1273                        claim_identity: PhysicalBackingClaimIdentity::new(
1274                            domain.pool_id().clone(),
1275                            vec![descriptor.base_resource_id().clone()],
1276                        )?,
1277                        capacity_size_bytes,
1278                        reusable_execution_bucket_id: reusable_execution_bucket
1279                            .map(|bucket| bucket.bucket_id().clone()),
1280                        projections: vec![EvaluatedBackingProjection {
1281                            descriptor,
1282                            physical_offset_bytes: 0,
1283                            logical_size_bytes,
1284                            capacity_size_bytes,
1285                        }],
1286                    });
1287                }
1288            }
1289            if matched {
1290                immediate_entries.push(CapacityEntry::new(
1291                    domain.domain_id(),
1292                    CapacityUnits::new(immediate_pool_bytes),
1293                )?);
1294                fit_entries.push(CapacityEntry::new(
1295                    domain.domain_id(),
1296                    CapacityUnits::new(fit_pool_bytes),
1297                )?);
1298            }
1299        }
1300        let immediate = if immediate_entries.is_empty() {
1301            CapacityVector::empty()
1302        } else {
1303            CapacityVector::new(immediate_entries)?
1304        };
1305        let fit = if fit_entries.is_empty() {
1306            CapacityVector::empty()
1307        } else {
1308            CapacityVector::new(fit_entries)?
1309        };
1310        Ok((
1311            AdmissionDemand::from_plan(immediate, fit, fit_policy, pressure_action)?,
1312            requested_slices,
1313        ))
1314    }
1315
1316    /// Derives the exact additional physical/logical claim needed to advance
1317    /// one sequence's committed frontier. Existing extents remain owned by the
1318    /// prior snapshot; only paged storage can append disjoint extents.
1319    pub(super) fn sequence_extension_demand(
1320        &self,
1321        committed: DynamicResourceShape,
1322        target: DynamicResourceShape,
1323        pressure_action: AdmissionPressureAction,
1324    ) -> Result<(AdmissionDemand, Vec<EvaluatedBackingRequest<'_>>), VNextError> {
1325        if committed.sequences() != 1
1326            || target.sequences() != 1
1327            || target.tokens() < committed.tokens()
1328            || target.pages() < committed.pages()
1329        {
1330            return Err(invalid_resource(
1331                "sequence extension target must monotonically advance one committed sequence",
1332            ));
1333        }
1334
1335        let mut entries = Vec::new();
1336        let mut requested_slices = Vec::new();
1337        for domain in &self.dynamic_pools().domains {
1338            let mut pool_delta = 0_u64;
1339            for descriptor in &domain.descriptors {
1340                if descriptor.lifetime() != AllocationLifetime::Sequence {
1341                    continue;
1342                }
1343                let committed_bytes = descriptor.evaluate_request_bytes_for_shape(committed)?;
1344                let target_bytes = descriptor.evaluate_request_bytes_for_shape(target)?;
1345                let delta_bytes = target_bytes.checked_sub(committed_bytes).ok_or_else(|| {
1346                    invalid_resource("sequence extension descriptor capacity regressed")
1347                })?;
1348                if delta_bytes == 0 {
1349                    continue;
1350                }
1351                if !matches!(
1352                    descriptor.storage().profile().view(),
1353                    super::DynamicStorageView::PagedRegions { .. }
1354                ) {
1355                    return Err(invalid_resource(
1356                        "sequence backing extension requires a paged storage profile",
1357                    ));
1358                }
1359                pool_delta = pool_delta.checked_add(delta_bytes).ok_or_else(|| {
1360                    invalid_resource("sequence extension pool demand overflows u64")
1361                })?;
1362                requested_slices.push(EvaluatedBackingRequest {
1363                    domain,
1364                    claim_identity: PhysicalBackingClaimIdentity::new(
1365                        domain.pool_id().clone(),
1366                        vec![descriptor.base_resource_id().clone()],
1367                    )?,
1368                    capacity_size_bytes: delta_bytes,
1369                    reusable_execution_bucket_id: None,
1370                    projections: vec![EvaluatedBackingProjection {
1371                        descriptor,
1372                        physical_offset_bytes: 0,
1373                        logical_size_bytes: delta_bytes,
1374                        capacity_size_bytes: delta_bytes,
1375                    }],
1376                });
1377            }
1378            if pool_delta != 0 {
1379                entries.push(CapacityEntry::new(
1380                    domain.domain_id(),
1381                    CapacityUnits::new(pool_delta),
1382                )?);
1383            }
1384        }
1385        let delta = if entries.is_empty() {
1386            CapacityVector::empty()
1387        } else {
1388            CapacityVector::new(entries)?
1389        };
1390        Ok((
1391            AdmissionDemand::from_plan(
1392                delta.clone(),
1393                delta,
1394                AdmissionFitPolicy::ImmediateOnly,
1395                pressure_action,
1396            )?,
1397            requested_slices,
1398        ))
1399    }
1400
1401    /// Evaluates all Invocation-scoped resources for one immutable-plan
1402    /// submission wave. A total-order pool reuses one physical extent across
1403    /// node rows, while a conservative pool retains disjoint row ranges.
1404    pub(super) fn submission_wave_demand(
1405        &self,
1406        immediate_shape: DynamicResourceShape,
1407        fit_shape: DynamicResourceShape,
1408        reusable_execution_bucket: Option<&ReusableExecutionBucketSpec>,
1409        fit_policy: AdmissionFitPolicy,
1410        pressure_action: AdmissionPressureAction,
1411    ) -> Result<(AdmissionDemand, Vec<EvaluatedBackingRequest<'_>>), VNextError> {
1412        match reusable_execution_bucket {
1413            Some(bucket)
1414                if bucket.capacity().covers(
1415                    immediate_shape.sequences(),
1416                    immediate_shape.tokens(),
1417                    immediate_shape.pages(),
1418                ) && bucket.capacity().covers(
1419                    fit_shape.sequences(),
1420                    fit_shape.tokens(),
1421                    fit_shape.pages(),
1422                ) =>
1423            {
1424                // Physical reusable capacity is compiled once with the immutable
1425                // plan. Per-wave logical and fit demand remain dynamic below.
1426            }
1427            Some(_) => {
1428                return Err(invalid_resource(
1429                    "reusable execution bucket does not cover the submission-wave work shape",
1430                ));
1431            }
1432            None => {}
1433        }
1434
1435        let mut immediate_entries = Vec::new();
1436        let mut fit_entries = Vec::new();
1437        let mut requested_slices = Vec::new();
1438        let pools = self.dynamic_pools();
1439        if pools.domains.len() != pools.submission_wave_layouts.len() {
1440            return Err(invalid_resource(
1441                "submission wave layout count differs from immutable plan domains",
1442            ));
1443        }
1444        let reusable_capacity_layouts = reusable_execution_bucket
1445            .map(|bucket| {
1446                pools
1447                    .submission_wave_reusable_capacity_layouts
1448                    .get(bucket.bucket_id())
1449                    .ok_or_else(|| {
1450                        invalid_resource(
1451                            "reusable execution bucket has no compiled submission-wave capacity layout",
1452                        )
1453                    })
1454            })
1455            .transpose()?;
1456        if reusable_capacity_layouts.is_some_and(|layouts| layouts.len() != pools.domains.len()) {
1457            return Err(invalid_resource(
1458                "compiled reusable submission-wave capacity layout count differs from immutable plan domains",
1459            ));
1460        }
1461        for (domain_index, (domain, layout)) in pools
1462            .domains
1463            .iter()
1464            .zip(&pools.submission_wave_layouts)
1465            .enumerate()
1466        {
1467            let reusable_capacity_layout = reusable_capacity_layouts
1468                .map(|layouts| {
1469                    layouts.get(domain_index).ok_or_else(|| {
1470                        invalid_resource(
1471                            "compiled reusable submission-wave capacity layout is incomplete",
1472                        )
1473                    })
1474                })
1475                .transpose()?
1476                .and_then(Option::as_ref);
1477            let Some(layout) = layout else {
1478                if reusable_capacity_layout.is_some() {
1479                    return Err(invalid_resource(
1480                        "compiled reusable submission-wave capacity exists without a domain layout",
1481                    ));
1482                }
1483                continue;
1484            };
1485            if reusable_execution_bucket.is_some() && reusable_capacity_layout.is_none() {
1486                return Err(invalid_resource(
1487                    "compiled reusable submission-wave capacity is missing for a domain layout",
1488                ));
1489            }
1490            if reusable_capacity_layout
1491                .is_some_and(|capacity| capacity.projections.len() != layout.projection_count)
1492            {
1493                return Err(invalid_resource(
1494                    "compiled reusable submission-wave projection count differs from its domain layout",
1495                ));
1496            }
1497            let mode = domain.pool.invocation_liveness_mode();
1498
1499            let mut projections = vec![None; layout.projection_count];
1500            let mut immediate_pool_bytes = 0_u64;
1501            let mut fit_pool_bytes = 0_u64;
1502            let mut capacity_pool_bytes = 0_u64;
1503            for row in &layout.rows {
1504                let row_base = match mode {
1505                    InvocationLivenessMode::TotalOrderReuse => 0,
1506                    InvocationLivenessMode::ConservativeConcurrent => capacity_pool_bytes,
1507                    InvocationLivenessMode::NoInvocationResources => unreachable!(),
1508                };
1509                let mut immediate_row_bytes = 0_u64;
1510                let mut fit_row_bytes = 0_u64;
1511                let mut capacity_row_bytes = 0_u64;
1512                for projection_layout in &row.projections {
1513                    let descriptor = domain
1514                        .descriptors
1515                        .get(projection_layout.descriptor_index)
1516                        .ok_or_else(|| {
1517                            invalid_resource(
1518                                "submission wave layout references a descriptor outside its pool",
1519                            )
1520                        })?;
1521                    if descriptor.lifetime() != AllocationLifetime::Invocation {
1522                        return Err(invalid_resource(
1523                            "invocation liveness row references a non-Invocation descriptor",
1524                        ));
1525                    }
1526                    let logical_size_bytes =
1527                        descriptor.evaluate_request_bytes_for_shape(immediate_shape)?;
1528                    let fit_bytes = if fit_shape == immediate_shape {
1529                        logical_size_bytes
1530                    } else {
1531                        descriptor.evaluate_request_bytes_for_shape(fit_shape)?
1532                    };
1533                    let (physical_offset_bytes, capacity_size_bytes) =
1534                        match reusable_capacity_layout {
1535                            Some(capacity_layout) => {
1536                                let capacity_projection = capacity_layout
1537                                    .projections
1538                                    .get(projection_layout.projection_index)
1539                                    .ok_or_else(|| {
1540                                        invalid_resource(
1541                                            "compiled reusable submission-wave projection is missing",
1542                                        )
1543                                    })?;
1544                                (
1545                                    capacity_projection.physical_offset_bytes,
1546                                    capacity_projection.capacity_size_bytes,
1547                                )
1548                            }
1549                            None => {
1550                                let physical_offset_bytes =
1551                                    row_base.checked_add(capacity_row_bytes).ok_or_else(|| {
1552                                        invalid_resource(
1553                                            "invocation wave projection offset overflows u64",
1554                                        )
1555                                    })?;
1556                                (physical_offset_bytes, logical_size_bytes)
1557                            }
1558                        };
1559                    immediate_row_bytes = immediate_row_bytes
1560                        .checked_add(logical_size_bytes)
1561                        .ok_or_else(|| {
1562                            invalid_resource("invocation wave row demand overflows u64")
1563                        })?;
1564                    fit_row_bytes = fit_row_bytes.checked_add(fit_bytes).ok_or_else(|| {
1565                        invalid_resource("invocation wave fit row demand overflows u64")
1566                    })?;
1567                    if reusable_capacity_layout.is_none() {
1568                        capacity_row_bytes = capacity_row_bytes
1569                            .checked_add(capacity_size_bytes)
1570                            .ok_or_else(|| {
1571                                invalid_resource("invocation wave capacity row overflows u64")
1572                            })?;
1573                    }
1574                    if projections[projection_layout.projection_index]
1575                        .replace(EvaluatedBackingProjection {
1576                            descriptor,
1577                            physical_offset_bytes,
1578                            logical_size_bytes,
1579                            capacity_size_bytes,
1580                        })
1581                        .is_some()
1582                    {
1583                        return Err(invalid_resource(
1584                            "submission wave layout repeated a canonical projection",
1585                        ));
1586                    }
1587                }
1588                match mode {
1589                    InvocationLivenessMode::TotalOrderReuse => {
1590                        immediate_pool_bytes = immediate_pool_bytes.max(immediate_row_bytes);
1591                        fit_pool_bytes = fit_pool_bytes.max(fit_row_bytes);
1592                        if reusable_capacity_layout.is_none() {
1593                            capacity_pool_bytes = capacity_pool_bytes.max(capacity_row_bytes);
1594                        }
1595                    }
1596                    InvocationLivenessMode::ConservativeConcurrent => {
1597                        immediate_pool_bytes = immediate_pool_bytes
1598                            .checked_add(immediate_row_bytes)
1599                            .ok_or_else(|| {
1600                                invalid_resource("invocation wave pool demand overflows u64")
1601                            })?;
1602                        fit_pool_bytes =
1603                            fit_pool_bytes.checked_add(fit_row_bytes).ok_or_else(|| {
1604                                invalid_resource("invocation wave pool fit demand overflows u64")
1605                            })?;
1606                        if reusable_capacity_layout.is_none() {
1607                            capacity_pool_bytes = capacity_pool_bytes
1608                                .checked_add(capacity_row_bytes)
1609                                .ok_or_else(|| {
1610                                    invalid_resource(
1611                                        "invocation wave capacity pool demand overflows u64",
1612                                    )
1613                                })?;
1614                        }
1615                    }
1616                    InvocationLivenessMode::NoInvocationResources => unreachable!(),
1617                }
1618            }
1619            if let Some(capacity_layout) = reusable_capacity_layout {
1620                capacity_pool_bytes = capacity_layout.physical_size_bytes;
1621            }
1622            let projections = projections
1623                .into_iter()
1624                .collect::<Option<Vec<_>>>()
1625                .ok_or_else(|| {
1626                    invalid_resource("submission wave layout left a projection unevaluated")
1627                })?;
1628            if projections.is_empty() || immediate_pool_bytes == 0 {
1629                return Err(invalid_resource(
1630                    "submission wave layout evaluated to empty invocation demand",
1631                ));
1632            }
1633
1634            requested_slices.push(EvaluatedBackingRequest {
1635                domain,
1636                claim_identity: layout.claim_identity.clone(),
1637                capacity_size_bytes: capacity_pool_bytes,
1638                reusable_execution_bucket_id: reusable_execution_bucket
1639                    .map(|bucket| bucket.bucket_id().clone()),
1640                projections,
1641            });
1642            immediate_entries.push(CapacityEntry::new(
1643                domain.domain_id(),
1644                CapacityUnits::new(immediate_pool_bytes),
1645            )?);
1646            fit_entries.push(CapacityEntry::new(
1647                domain.domain_id(),
1648                CapacityUnits::new(fit_pool_bytes),
1649            )?);
1650        }
1651
1652        let immediate = if immediate_entries.is_empty() {
1653            CapacityVector::empty()
1654        } else {
1655            CapacityVector::new(immediate_entries)?
1656        };
1657        let fit = if fit_entries.is_empty() {
1658            CapacityVector::empty()
1659        } else {
1660            CapacityVector::new(fit_entries)?
1661        };
1662        Ok((
1663            AdmissionDemand::from_plan(immediate, fit, fit_policy, pressure_action)?,
1664            requested_slices,
1665        ))
1666    }
1667
1668    pub(super) fn prepare_backing_slices(
1669        &self,
1670        requested_slices: Vec<EvaluatedBackingRequest<'_>>,
1671    ) -> Result<BackingPrepareDecision<R>, VNextError> {
1672        self.dynamic_pools().prepare_claim(&requested_slices)
1673    }
1674
1675    pub(super) fn prepare_lane_stable_backing_slices(
1676        &self,
1677        lane: &Arc<ExecutionLane<R>>,
1678        requested_slices: Vec<EvaluatedBackingRequest<'_>>,
1679    ) -> Result<LaneBackingPrepareDecision, VNextError> {
1680        self.dynamic_pools()
1681            .prepare_lane_stable_claim(lane, &requested_slices)
1682    }
1683
1684    pub(super) fn prepare_initial_sequence_backing_slices(
1685        &self,
1686        requested_slices: &[EvaluatedBackingRequest<'_>],
1687    ) -> Result<BackingPrepareDecision<R>, VNextError> {
1688        self.dynamic_pools()
1689            .prepare_initial_sequence_claim(requested_slices)
1690    }
1691
1692    pub(super) fn register_backing_waiter(
1693        &self,
1694        deferred: &DynamicBackingDeferred,
1695    ) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
1696        self.resources
1697            .register_capacity_waiter(deferred.wait_condition())
1698    }
1699
1700    pub fn register_admission_waiter(
1701        &self,
1702        deferred: &AdmissionDeferred,
1703    ) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
1704        self.resources
1705            .register_capacity_waiter(deferred.wait_condition())
1706    }
1707}