Skip to main content

ferrum_interfaces/vnext/execution/
reusable.rs

1use super::{
2    canonical_fingerprint, invalid_plan, BTreeMap, BTreeSet, Deserialize, Deserializer,
3    DynamicBackingPoolId, Serialize, VNextError,
4};
5
6pub const MAX_REUSABLE_EXECUTION_BUCKETS: usize = 64;
7pub const MAX_REUSABLE_EXECUTION_PROGRAM_SHAPES: usize = 256;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
10#[serde(transparent)]
11pub struct ReusableExecutionClassId(String);
12
13impl ReusableExecutionClassId {
14    pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
15        let value = value.into();
16        if value.is_empty()
17            || value.len() > 160
18            || !value.bytes().all(|byte| {
19                byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
20            })
21        {
22            return Err(invalid_plan(
23                "reusable execution class id is empty, too long, or non-portable",
24            ));
25        }
26        Ok(Self(value))
27    }
28
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34impl<'de> Deserialize<'de> for ReusableExecutionClassId {
35    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36    where
37        D: Deserializer<'de>,
38    {
39        Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
40    }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
44#[serde(transparent)]
45pub struct ReusableExecutionBucketId(String);
46
47impl ReusableExecutionBucketId {
48    fn derive(
49        class_id: &ReusableExecutionClassId,
50        capacity: &ReusableExecutionCapacity,
51    ) -> Result<Self, VNextError> {
52        #[derive(Serialize)]
53        struct BucketIdentity<'a> {
54            domain: &'static str,
55            class_id: &'a ReusableExecutionClassId,
56            capacity: &'a ReusableExecutionCapacity,
57        }
58
59        Ok(Self(format!(
60            "reusable-bucket/sha256/{}",
61            canonical_fingerprint(
62                &BucketIdentity {
63                    domain: "ferrum.runtime-vnext.reusable-execution-bucket.v1",
64                    class_id,
65                    capacity,
66                },
67                "fingerprint reusable execution bucket",
68            )?
69        )))
70    }
71
72    fn validate_for(
73        &self,
74        class_id: &ReusableExecutionClassId,
75        capacity: &ReusableExecutionCapacity,
76    ) -> Result<(), VNextError> {
77        if self != &Self::derive(class_id, capacity)? {
78            return Err(invalid_plan(
79                "reusable execution bucket id is not derived from its class and capacity",
80            ));
81        }
82        Ok(())
83    }
84
85    pub fn as_str(&self) -> &str {
86        &self.0
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
91#[serde(deny_unknown_fields)]
92pub struct ReusableExecutionCapacity {
93    maximum_sequences: u32,
94    maximum_tokens: u64,
95    maximum_pages: u64,
96}
97
98#[derive(Deserialize)]
99#[serde(deny_unknown_fields)]
100struct ReusableExecutionCapacityWire {
101    maximum_sequences: u32,
102    maximum_tokens: u64,
103    maximum_pages: u64,
104}
105
106impl ReusableExecutionCapacity {
107    pub fn new(
108        maximum_sequences: u32,
109        maximum_tokens: u64,
110        maximum_pages: u64,
111    ) -> Result<Self, VNextError> {
112        if maximum_sequences == 0 || maximum_tokens == 0 || maximum_pages == 0 {
113            return Err(invalid_plan(
114                "reusable execution capacity dimensions must be non-zero",
115            ));
116        }
117        Ok(Self {
118            maximum_sequences,
119            maximum_tokens,
120            maximum_pages,
121        })
122    }
123
124    pub const fn maximum_sequences(self) -> u32 {
125        self.maximum_sequences
126    }
127
128    pub const fn maximum_tokens(self) -> u64 {
129        self.maximum_tokens
130    }
131
132    pub const fn maximum_pages(self) -> u64 {
133        self.maximum_pages
134    }
135
136    pub const fn covers(self, sequences: u32, tokens: u64, pages: u64) -> bool {
137        sequences > 0
138            && tokens > 0
139            && sequences <= self.maximum_sequences
140            && tokens <= self.maximum_tokens
141            && pages <= self.maximum_pages
142    }
143
144    fn strictly_extends(self, previous: Self) -> bool {
145        self.maximum_sequences >= previous.maximum_sequences
146            && self.maximum_tokens >= previous.maximum_tokens
147            && self.maximum_pages >= previous.maximum_pages
148            && self != previous
149    }
150}
151
152impl<'de> Deserialize<'de> for ReusableExecutionCapacity {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        let wire = ReusableExecutionCapacityWire::deserialize(deserializer)?;
158        Self::new(
159            wire.maximum_sequences,
160            wire.maximum_tokens,
161            wire.maximum_pages,
162        )
163        .map_err(serde::de::Error::custom)
164    }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
168#[serde(deny_unknown_fields)]
169pub struct ReusableExecutionBucketSpec {
170    bucket_id: ReusableExecutionBucketId,
171    class_id: ReusableExecutionClassId,
172    capacity: ReusableExecutionCapacity,
173}
174
175#[derive(Deserialize)]
176#[serde(deny_unknown_fields)]
177struct ReusableExecutionBucketSpecWire {
178    bucket_id: String,
179    class_id: ReusableExecutionClassId,
180    capacity: ReusableExecutionCapacity,
181}
182
183impl ReusableExecutionBucketSpec {
184    pub fn new(
185        class_id: ReusableExecutionClassId,
186        capacity: ReusableExecutionCapacity,
187    ) -> Result<Self, VNextError> {
188        let bucket_id = ReusableExecutionBucketId::derive(&class_id, &capacity)?;
189        Ok(Self {
190            bucket_id,
191            class_id,
192            capacity,
193        })
194    }
195
196    fn validate(&self) -> Result<(), VNextError> {
197        self.bucket_id.validate_for(&self.class_id, &self.capacity)
198    }
199
200    pub fn bucket_id(&self) -> &ReusableExecutionBucketId {
201        &self.bucket_id
202    }
203
204    pub fn class_id(&self) -> &ReusableExecutionClassId {
205        &self.class_id
206    }
207
208    pub const fn capacity(&self) -> ReusableExecutionCapacity {
209        self.capacity
210    }
211}
212
213impl<'de> Deserialize<'de> for ReusableExecutionBucketSpec {
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: Deserializer<'de>,
217    {
218        let wire = ReusableExecutionBucketSpecWire::deserialize(deserializer)?;
219        let spec = Self {
220            bucket_id: ReusableExecutionBucketId(wire.bucket_id),
221            class_id: wire.class_id,
222            capacity: wire.capacity,
223        };
224        spec.validate().map_err(serde::de::Error::custom)?;
225        Ok(spec)
226    }
227}
228
229/// Exact logical startup capture case for a reusable device-program catalog.
230///
231/// Workspace buckets are capacity envelopes and may cover smaller work. These
232/// shapes are different: each row requests one exact logical startup case.
233/// The resulting physical program identity additionally includes pages,
234/// provider topology, lane layout, and runtime fingerprints. Consequently one
235/// logical width does not claim replay coverage for every context/topology
236/// variant. v0.8.0 deliberately does not imply padded replay.
237#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
238#[serde(tag = "topology", rename_all = "snake_case", deny_unknown_fields)]
239pub enum ReusableExecutionProgramShape {
240    UniformDecode {
241        request_capacity: u32,
242        token_capacity: u64,
243        query_tokens_per_sequence: u32,
244    },
245    Prefill {
246        tokens_processed: u64,
247        token_capacity: u64,
248        total_prompt_tokens: u64,
249    },
250}
251
252impl ReusableExecutionProgramShape {
253    pub fn uniform_decode(
254        request_capacity: u32,
255        query_tokens_per_sequence: u32,
256    ) -> Result<Self, VNextError> {
257        let token_capacity = u64::from(request_capacity)
258            .checked_mul(u64::from(query_tokens_per_sequence))
259            .ok_or_else(|| invalid_plan("reusable decode program token capacity overflows u64"))?;
260        let shape = Self::UniformDecode {
261            request_capacity,
262            token_capacity,
263            query_tokens_per_sequence,
264        };
265        shape.validate()?;
266        Ok(shape)
267    }
268
269    pub fn prefill(
270        tokens_processed: u64,
271        token_capacity: u64,
272        total_prompt_tokens: u64,
273    ) -> Result<Self, VNextError> {
274        let shape = Self::Prefill {
275            tokens_processed,
276            token_capacity,
277            total_prompt_tokens,
278        };
279        shape.validate()?;
280        Ok(shape)
281    }
282
283    fn validate(self) -> Result<(), VNextError> {
284        match self {
285            Self::UniformDecode {
286                request_capacity,
287                token_capacity,
288                query_tokens_per_sequence,
289            } => {
290                let expected_tokens =
291                    u64::from(request_capacity).checked_mul(u64::from(query_tokens_per_sequence));
292                if request_capacity == 0
293                    || query_tokens_per_sequence == 0
294                    || expected_tokens != Some(token_capacity)
295                {
296                    return Err(invalid_plan(
297                        "reusable decode program shape is empty or internally inconsistent",
298                    ));
299                }
300            }
301            Self::Prefill {
302                tokens_processed,
303                token_capacity,
304                total_prompt_tokens,
305            } => {
306                if token_capacity == 0
307                    || total_prompt_tokens == 0
308                    || tokens_processed
309                        .checked_add(token_capacity)
310                        .is_none_or(|end| end > total_prompt_tokens)
311                {
312                    return Err(invalid_plan(
313                        "reusable prefill program shape is empty or exceeds its prompt frontier",
314                    ));
315                }
316            }
317        }
318        Ok(())
319    }
320
321    pub const fn request_capacity(self) -> u32 {
322        match self {
323            Self::UniformDecode {
324                request_capacity, ..
325            } => request_capacity,
326            Self::Prefill { .. } => 1,
327        }
328    }
329
330    pub const fn token_capacity(self) -> u64 {
331        match self {
332            Self::UniformDecode { token_capacity, .. } | Self::Prefill { token_capacity, .. } => {
333                token_capacity
334            }
335        }
336    }
337}
338
339impl<'de> Deserialize<'de> for ReusableExecutionProgramShape {
340    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341    where
342        D: Deserializer<'de>,
343    {
344        #[derive(Deserialize)]
345        #[serde(tag = "topology", rename_all = "snake_case", deny_unknown_fields)]
346        enum Wire {
347            UniformDecode {
348                request_capacity: u32,
349                token_capacity: u64,
350                query_tokens_per_sequence: u32,
351            },
352            Prefill {
353                tokens_processed: u64,
354                token_capacity: u64,
355                total_prompt_tokens: u64,
356            },
357        }
358
359        let shape = match Wire::deserialize(deserializer)? {
360            Wire::UniformDecode {
361                request_capacity,
362                token_capacity,
363                query_tokens_per_sequence,
364            } => Self::UniformDecode {
365                request_capacity,
366                token_capacity,
367                query_tokens_per_sequence,
368            },
369            Wire::Prefill {
370                tokens_processed,
371                token_capacity,
372                total_prompt_tokens,
373            } => Self::Prefill {
374                tokens_processed,
375                token_capacity,
376                total_prompt_tokens,
377            },
378        };
379        shape.validate().map_err(serde::de::Error::custom)?;
380        Ok(shape)
381    }
382}
383
384/// One logical startup capture case bound to its covering workspace class.
385///
386/// A spec budgets one observed physical variant. If startup observes more than
387/// one physical identity for the same case, backend capacity fails closed; it
388/// is never interpreted as universal coverage of that logical shape. Multiple
389/// logical cases may independently observe the same physical identity when
390/// their complete device-level identity is equal; that is physical reuse, not
391/// inferred coverage from another case.
392#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
393#[serde(deny_unknown_fields)]
394pub struct ReusableExecutionProgramSpec {
395    class_id: ReusableExecutionClassId,
396    shape: ReusableExecutionProgramShape,
397}
398
399impl ReusableExecutionProgramSpec {
400    pub fn new(
401        class_id: ReusableExecutionClassId,
402        shape: ReusableExecutionProgramShape,
403    ) -> Result<Self, VNextError> {
404        shape.validate()?;
405        Ok(Self { class_id, shape })
406    }
407
408    pub fn class_id(&self) -> &ReusableExecutionClassId {
409        &self.class_id
410    }
411
412    pub const fn shape(&self) -> ReusableExecutionProgramShape {
413        self.shape
414    }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum ReusableExecutionProgramShapeSemantics {
420    Exact,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
424#[serde(rename_all = "snake_case")]
425pub enum ReusableExecutionCatalogMissPolicy {
426    EagerFallback,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
430#[serde(rename_all = "snake_case")]
431pub enum ReusableExecutionCatalogLifetime {
432    StartupSealed,
433    /// Publish only programs encountered by real work, within the resolved
434    /// executable capacity. A miss remains eligible for ordinary execution.
435    OnDemandBounded,
436}
437
438impl ReusableExecutionCatalogLifetime {
439    pub const fn is_startup_sealed(&self) -> bool {
440        matches!(self, Self::StartupSealed)
441    }
442}
443
444/// Fully resolved, fingerprinted logical capture contract.
445///
446/// Physical catalog receipts remain authoritative for what was actually made
447/// resident. Startup-sealed mode executes the declared cases before readiness.
448/// On-demand mode uses that inventory to bound executable capacity and admits
449/// exact recurring shapes within the immutable workspace plan. Runtime contexts
450/// producing another pages/topology identity use the explicit catalog-miss policy.
451#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
452#[serde(deny_unknown_fields)]
453pub struct ReusableExecutionProgramPolicy {
454    shape_semantics: ReusableExecutionProgramShapeSemantics,
455    catalog_miss_policy: ReusableExecutionCatalogMissPolicy,
456    catalog_lifetime: ReusableExecutionCatalogLifetime,
457    warmup_passes: u32,
458    capture_passes: u32,
459    replay_validation_passes: u32,
460    programs: Vec<ReusableExecutionProgramSpec>,
461}
462
463#[derive(Deserialize)]
464#[serde(deny_unknown_fields)]
465struct ReusableExecutionProgramPolicyWire {
466    shape_semantics: ReusableExecutionProgramShapeSemantics,
467    catalog_miss_policy: ReusableExecutionCatalogMissPolicy,
468    catalog_lifetime: ReusableExecutionCatalogLifetime,
469    warmup_passes: u32,
470    capture_passes: u32,
471    replay_validation_passes: u32,
472    programs: Vec<ReusableExecutionProgramSpec>,
473}
474
475impl ReusableExecutionProgramPolicy {
476    /// Keep the same exact shape and workspace contract without executing its
477    /// cases before readiness. Warmup and capture consume successive real
478    /// submissions; validation must never replay a user's state transition.
479    pub fn exact_on_demand(
480        programs: Vec<ReusableExecutionProgramSpec>,
481    ) -> Result<Self, VNextError> {
482        let mut policy = Self::exact_startup_sealed(1, 1, 1, programs)?;
483        policy.catalog_lifetime = ReusableExecutionCatalogLifetime::OnDemandBounded;
484        policy.replay_validation_passes = 0;
485        policy.validate()?;
486        Ok(policy)
487    }
488
489    pub fn exact_startup_sealed(
490        warmup_passes: u32,
491        capture_passes: u32,
492        replay_validation_passes: u32,
493        mut programs: Vec<ReusableExecutionProgramSpec>,
494    ) -> Result<Self, VNextError> {
495        let prerequisite_prefill_programs = programs
496            .iter()
497            .filter_map(|program| match program.shape() {
498                ReusableExecutionProgramShape::Prefill {
499                    tokens_processed,
500                    total_prompt_tokens,
501                    ..
502                } if tokens_processed > 0 => Some(ReusableExecutionProgramSpec {
503                    class_id: program.class_id().clone(),
504                    shape: ReusableExecutionProgramShape::Prefill {
505                        tokens_processed: 0,
506                        token_capacity: tokens_processed,
507                        total_prompt_tokens,
508                    },
509                }),
510                _ => None,
511            })
512            .collect::<Vec<_>>();
513        programs.extend(prerequisite_prefill_programs);
514        programs.sort_unstable();
515        programs.dedup();
516        let policy = Self {
517            shape_semantics: ReusableExecutionProgramShapeSemantics::Exact,
518            catalog_miss_policy: ReusableExecutionCatalogMissPolicy::EagerFallback,
519            catalog_lifetime: ReusableExecutionCatalogLifetime::StartupSealed,
520            warmup_passes,
521            capture_passes,
522            replay_validation_passes,
523            programs,
524        };
525        policy.validate()?;
526        Ok(policy)
527    }
528
529    fn validate(&self) -> Result<(), VNextError> {
530        if self.shape_semantics != ReusableExecutionProgramShapeSemantics::Exact
531            || self.catalog_miss_policy != ReusableExecutionCatalogMissPolicy::EagerFallback
532            || self.warmup_passes == 0
533            || self.capture_passes == 0
534            || match self.catalog_lifetime {
535                ReusableExecutionCatalogLifetime::StartupSealed => {
536                    self.replay_validation_passes == 0
537                }
538                ReusableExecutionCatalogLifetime::OnDemandBounded => {
539                    self.warmup_passes != 1
540                        || self.capture_passes != 1
541                        || self.replay_validation_passes != 0
542                }
543            }
544            || self.programs.is_empty()
545            || self.programs.len() > MAX_REUSABLE_EXECUTION_PROGRAM_SHAPES
546        {
547            return Err(invalid_plan(
548                "reusable execution program policy has invalid semantics, passes, or shape count",
549            ));
550        }
551        if self.programs.windows(2).any(|pair| pair[0] >= pair[1]) {
552            return Err(invalid_plan(
553                "reusable execution program shapes are duplicate or non-canonical",
554            ));
555        }
556        for program in &self.programs {
557            program.shape().validate()?;
558        }
559        let mut shape_classes = BTreeMap::new();
560        for program in &self.programs {
561            if shape_classes
562                .insert(program.shape(), program.class_id())
563                .is_some()
564            {
565                return Err(invalid_plan(
566                    "reusable execution logical shape is assigned to multiple workspace classes",
567                ));
568            }
569        }
570        if self.programs.iter().any(|program| match program.shape() {
571            ReusableExecutionProgramShape::Prefill {
572                tokens_processed,
573                total_prompt_tokens,
574                ..
575            } if tokens_processed > 0 => !self.programs.contains(&ReusableExecutionProgramSpec {
576                class_id: program.class_id().clone(),
577                shape: ReusableExecutionProgramShape::Prefill {
578                    tokens_processed: 0,
579                    token_capacity: tokens_processed,
580                    total_prompt_tokens,
581                },
582            }),
583            _ => false,
584        }) {
585            return Err(invalid_plan(
586                "reusable prefill program policy omits a prerequisite prefix shape",
587            ));
588        }
589        Ok(())
590    }
591
592    pub const fn shape_semantics(&self) -> ReusableExecutionProgramShapeSemantics {
593        self.shape_semantics
594    }
595
596    pub const fn catalog_miss_policy(&self) -> ReusableExecutionCatalogMissPolicy {
597        self.catalog_miss_policy
598    }
599
600    pub const fn catalog_lifetime(&self) -> ReusableExecutionCatalogLifetime {
601        self.catalog_lifetime
602    }
603
604    pub const fn warmup_passes(&self) -> u32 {
605        self.warmup_passes
606    }
607
608    pub const fn capture_passes(&self) -> u32 {
609        self.capture_passes
610    }
611
612    pub const fn replay_validation_passes(&self) -> u32 {
613        self.replay_validation_passes
614    }
615
616    pub fn programs(&self) -> &[ReusableExecutionProgramSpec] {
617        &self.programs
618    }
619}
620
621impl<'de> Deserialize<'de> for ReusableExecutionProgramPolicy {
622    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
623    where
624        D: Deserializer<'de>,
625    {
626        let wire = ReusableExecutionProgramPolicyWire::deserialize(deserializer)?;
627        let policy = Self {
628            shape_semantics: wire.shape_semantics,
629            catalog_miss_policy: wire.catalog_miss_policy,
630            catalog_lifetime: wire.catalog_lifetime,
631            warmup_passes: wire.warmup_passes,
632            capture_passes: wire.capture_passes,
633            replay_validation_passes: wire.replay_validation_passes,
634            programs: wire.programs,
635        };
636        policy.validate().map_err(serde::de::Error::custom)?;
637        Ok(policy)
638    }
639}
640
641#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
642#[serde(deny_unknown_fields)]
643pub struct ReusableExecutionPolicy {
644    maximum_reusable_lanes: u32,
645    buckets: Vec<ReusableExecutionBucketSpec>,
646    #[serde(default, skip_serializing_if = "Option::is_none")]
647    program_policy: Option<ReusableExecutionProgramPolicy>,
648}
649
650#[derive(Deserialize)]
651#[serde(deny_unknown_fields)]
652struct ReusableExecutionPolicyWire {
653    maximum_reusable_lanes: u32,
654    buckets: Vec<ReusableExecutionBucketSpec>,
655    #[serde(default)]
656    program_policy: Option<ReusableExecutionProgramPolicy>,
657}
658
659impl ReusableExecutionPolicy {
660    pub fn new(
661        maximum_reusable_lanes: u32,
662        mut buckets: Vec<ReusableExecutionBucketSpec>,
663    ) -> Result<Self, VNextError> {
664        buckets.sort_by(|left, right| {
665            (left.class_id(), left.capacity()).cmp(&(right.class_id(), right.capacity()))
666        });
667        let policy = Self {
668            maximum_reusable_lanes,
669            buckets,
670            program_policy: None,
671        };
672        policy.validate()?;
673        Ok(policy)
674    }
675
676    pub fn with_program_policy(
677        mut self,
678        program_policy: ReusableExecutionProgramPolicy,
679    ) -> Result<Self, VNextError> {
680        self.program_policy = Some(program_policy);
681        self.validate()?;
682        Ok(self)
683    }
684
685    pub(crate) fn validate(&self) -> Result<(), VNextError> {
686        if self.maximum_reusable_lanes == 0
687            || self.buckets.is_empty()
688            || self.buckets.len() > MAX_REUSABLE_EXECUTION_BUCKETS
689        {
690            return Err(invalid_plan(
691                "reusable execution policy has an invalid lane or bucket count",
692            ));
693        }
694        for bucket in &self.buckets {
695            bucket.validate()?;
696        }
697        if let Some(program_policy) = &self.program_policy {
698            program_policy.validate()?;
699            if program_policy.programs().iter().any(|program| {
700                let shape = program.shape();
701                self.smallest_covering_bucket(
702                    program.class_id(),
703                    shape.request_capacity(),
704                    shape.token_capacity(),
705                    0,
706                )
707                .is_none()
708            }) {
709                return Err(invalid_plan(
710                    "reusable execution program shape has no covering workspace bucket in its class",
711                ));
712            }
713        }
714        if self.buckets.windows(2).any(|pair| {
715            let left = &pair[0];
716            let right = &pair[1];
717            left.class_id() > right.class_id()
718                || (left.class_id() == right.class_id()
719                    && !right.capacity().strictly_extends(left.capacity()))
720        }) {
721            return Err(invalid_plan(
722                "reusable execution buckets are not canonical monotonic class chains",
723            ));
724        }
725        Ok(())
726    }
727
728    pub const fn maximum_reusable_lanes(&self) -> u32 {
729        self.maximum_reusable_lanes
730    }
731
732    pub fn buckets(&self) -> &[ReusableExecutionBucketSpec] {
733        &self.buckets
734    }
735
736    pub fn program_policy(&self) -> Option<&ReusableExecutionProgramPolicy> {
737        self.program_policy.as_ref()
738    }
739
740    pub fn startup_capture_case_count(&self) -> usize {
741        self.program_policy
742            .as_ref()
743            .map_or(self.buckets.len(), |policy| policy.programs().len())
744    }
745
746    pub fn bucket(
747        &self,
748        bucket_id: &ReusableExecutionBucketId,
749    ) -> Option<&ReusableExecutionBucketSpec> {
750        self.buckets
751            .iter()
752            .find(|bucket| bucket.bucket_id() == bucket_id)
753    }
754
755    pub fn smallest_covering_bucket(
756        &self,
757        class_id: &ReusableExecutionClassId,
758        sequences: u32,
759        tokens: u64,
760        pages: u64,
761    ) -> Option<&ReusableExecutionBucketSpec> {
762        self.buckets.iter().find(|bucket| {
763            bucket.class_id() == class_id && bucket.capacity().covers(sequences, tokens, pages)
764        })
765    }
766}
767
768impl<'de> Deserialize<'de> for ReusableExecutionPolicy {
769    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
770    where
771        D: Deserializer<'de>,
772    {
773        let wire = ReusableExecutionPolicyWire::deserialize(deserializer)?;
774        let policy = Self {
775            maximum_reusable_lanes: wire.maximum_reusable_lanes,
776            buckets: wire.buckets,
777            program_policy: wire.program_policy,
778        };
779        policy.validate().map_err(serde::de::Error::custom)?;
780        Ok(policy)
781    }
782}
783
784#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
785#[serde(deny_unknown_fields)]
786pub struct ReusablePoolWorkspaceBudget {
787    pool_id: DynamicBackingPoolId,
788    step_bytes: u64,
789    invocation_bytes: u64,
790}
791
792#[derive(Deserialize)]
793#[serde(deny_unknown_fields)]
794struct ReusablePoolWorkspaceBudgetWire {
795    pool_id: DynamicBackingPoolId,
796    step_bytes: u64,
797    invocation_bytes: u64,
798}
799
800impl ReusablePoolWorkspaceBudget {
801    pub(crate) fn new(
802        pool_id: DynamicBackingPoolId,
803        step_bytes: u64,
804        invocation_bytes: u64,
805    ) -> Result<Self, VNextError> {
806        let budget = Self {
807            pool_id,
808            step_bytes,
809            invocation_bytes,
810        };
811        budget.validate()?;
812        Ok(budget)
813    }
814
815    fn validate(&self) -> Result<(), VNextError> {
816        self.step_bytes
817            .checked_add(self.invocation_bytes)
818            .filter(|total| *total > 0)
819            .ok_or_else(|| {
820                invalid_plan("reusable pool workspace budget is empty or overflows u64")
821            })?;
822        Ok(())
823    }
824
825    pub fn pool_id(&self) -> &DynamicBackingPoolId {
826        &self.pool_id
827    }
828
829    pub const fn step_bytes(&self) -> u64 {
830        self.step_bytes
831    }
832
833    pub const fn invocation_bytes(&self) -> u64 {
834        self.invocation_bytes
835    }
836
837    pub fn total_bytes(&self) -> Result<u64, VNextError> {
838        self.step_bytes
839            .checked_add(self.invocation_bytes)
840            .ok_or_else(|| invalid_plan("reusable pool workspace budget overflows u64"))
841    }
842}
843
844impl<'de> Deserialize<'de> for ReusablePoolWorkspaceBudget {
845    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
846    where
847        D: Deserializer<'de>,
848    {
849        let wire = ReusablePoolWorkspaceBudgetWire::deserialize(deserializer)?;
850        Self::new(wire.pool_id, wire.step_bytes, wire.invocation_bytes)
851            .map_err(serde::de::Error::custom)
852    }
853}
854
855#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
856#[serde(deny_unknown_fields)]
857pub struct ResolvedReusableExecutionBucket {
858    bucket: ReusableExecutionBucketSpec,
859    pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
860}
861
862#[derive(Deserialize)]
863#[serde(deny_unknown_fields)]
864struct ResolvedReusableExecutionBucketWire {
865    bucket: ReusableExecutionBucketSpec,
866    pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
867}
868
869impl ResolvedReusableExecutionBucket {
870    pub(crate) fn new(
871        bucket: ReusableExecutionBucketSpec,
872        pool_budgets: Vec<ReusablePoolWorkspaceBudget>,
873    ) -> Result<Self, VNextError> {
874        let resolved = Self {
875            bucket,
876            pool_budgets,
877        };
878        resolved.validate()?;
879        Ok(resolved)
880    }
881
882    fn validate(&self) -> Result<(), VNextError> {
883        self.bucket.validate()?;
884        if self
885            .pool_budgets
886            .windows(2)
887            .any(|pair| pair[0].pool_id() >= pair[1].pool_id())
888        {
889            return Err(invalid_plan(
890                "resolved reusable bucket pool budgets are duplicate or non-canonical",
891            ));
892        }
893        for budget in &self.pool_budgets {
894            budget.validate()?;
895        }
896        Ok(())
897    }
898
899    pub fn bucket(&self) -> &ReusableExecutionBucketSpec {
900        &self.bucket
901    }
902
903    pub fn pool_budgets(&self) -> &[ReusablePoolWorkspaceBudget] {
904        &self.pool_budgets
905    }
906}
907
908impl<'de> Deserialize<'de> for ResolvedReusableExecutionBucket {
909    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
910    where
911        D: Deserializer<'de>,
912    {
913        let wire = ResolvedReusableExecutionBucketWire::deserialize(deserializer)?;
914        Self::new(wire.bucket, wire.pool_budgets).map_err(serde::de::Error::custom)
915    }
916}
917
918#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
919#[serde(deny_unknown_fields)]
920pub struct ReusableExecutionMemoryPlan {
921    maximum_reusable_lanes: u32,
922    maximum_device_executables: u64,
923    buckets: Vec<ResolvedReusableExecutionBucket>,
924    #[serde(default, skip_serializing_if = "Option::is_none")]
925    program_policy: Option<ReusableExecutionProgramPolicy>,
926}
927
928#[derive(Deserialize)]
929#[serde(deny_unknown_fields)]
930struct ReusableExecutionMemoryPlanWire {
931    maximum_reusable_lanes: u32,
932    maximum_device_executables: u64,
933    buckets: Vec<ResolvedReusableExecutionBucket>,
934    #[serde(default)]
935    program_policy: Option<ReusableExecutionProgramPolicy>,
936}
937
938impl ReusableExecutionMemoryPlan {
939    pub(crate) fn new(
940        maximum_reusable_lanes: u32,
941        maximum_device_executables: u64,
942        buckets: Vec<ResolvedReusableExecutionBucket>,
943    ) -> Result<Self, VNextError> {
944        Self::new_with_program_policy(
945            maximum_reusable_lanes,
946            maximum_device_executables,
947            buckets,
948            None,
949        )
950    }
951
952    pub(crate) fn new_with_program_policy(
953        maximum_reusable_lanes: u32,
954        maximum_device_executables: u64,
955        buckets: Vec<ResolvedReusableExecutionBucket>,
956        program_policy: Option<ReusableExecutionProgramPolicy>,
957    ) -> Result<Self, VNextError> {
958        let plan = Self {
959            maximum_reusable_lanes,
960            maximum_device_executables,
961            buckets,
962            program_policy,
963        };
964        plan.validate_local()?;
965        Ok(plan)
966    }
967
968    pub(crate) fn validate_local(&self) -> Result<(), VNextError> {
969        if self.maximum_reusable_lanes == 0
970            || self.maximum_device_executables == 0
971            || self.buckets.is_empty()
972            || self.buckets.len() > MAX_REUSABLE_EXECUTION_BUCKETS
973        {
974            return Err(invalid_plan(
975                "reusable execution memory plan has an invalid lane, executable, or bucket count",
976            ));
977        }
978        for bucket in &self.buckets {
979            bucket.validate()?;
980        }
981        let mut canonical = ReusableExecutionPolicy::new(
982            self.maximum_reusable_lanes,
983            self.buckets
984                .iter()
985                .map(|bucket| bucket.bucket().clone())
986                .collect(),
987        )?;
988        if let Some(program_policy) = self.program_policy.clone() {
989            canonical = canonical.with_program_policy(program_policy)?;
990        }
991        if canonical.buckets().iter().ne(self
992            .buckets
993            .iter()
994            .map(ResolvedReusableExecutionBucket::bucket))
995        {
996            return Err(invalid_plan(
997                "reusable execution memory buckets are non-canonical",
998            ));
999        }
1000        Ok(())
1001    }
1002
1003    pub const fn maximum_reusable_lanes(&self) -> u32 {
1004        self.maximum_reusable_lanes
1005    }
1006
1007    pub const fn maximum_device_executables(&self) -> u64 {
1008        self.maximum_device_executables
1009    }
1010
1011    pub fn buckets(&self) -> &[ResolvedReusableExecutionBucket] {
1012        &self.buckets
1013    }
1014
1015    pub fn program_policy(&self) -> Option<&ReusableExecutionProgramPolicy> {
1016        self.program_policy.as_ref()
1017    }
1018
1019    pub fn bucket(
1020        &self,
1021        bucket_id: &ReusableExecutionBucketId,
1022    ) -> Option<&ResolvedReusableExecutionBucket> {
1023        self.buckets
1024            .iter()
1025            .find(|bucket| bucket.bucket().bucket_id() == bucket_id)
1026    }
1027
1028    pub fn smallest_covering_bucket(
1029        &self,
1030        class_id: &ReusableExecutionClassId,
1031        sequences: u32,
1032        tokens: u64,
1033        pages: u64,
1034    ) -> Option<&ResolvedReusableExecutionBucket> {
1035        self.buckets.iter().find(|bucket| {
1036            bucket.bucket().class_id() == class_id
1037                && bucket.bucket().capacity().covers(sequences, tokens, pages)
1038        })
1039    }
1040
1041    pub(crate) fn policy(&self) -> Result<ReusableExecutionPolicy, VNextError> {
1042        let mut policy = ReusableExecutionPolicy::new(
1043            self.maximum_reusable_lanes,
1044            self.buckets
1045                .iter()
1046                .map(|bucket| bucket.bucket().clone())
1047                .collect(),
1048        )?;
1049        if let Some(program_policy) = self.program_policy.clone() {
1050            policy = policy.with_program_policy(program_policy)?;
1051        }
1052        Ok(policy)
1053    }
1054
1055    pub(crate) fn pool_workspace_ceilings(
1056        &self,
1057    ) -> Result<BTreeMap<DynamicBackingPoolId, u64>, VNextError> {
1058        let lanes = u64::from(self.maximum_reusable_lanes);
1059        let mut totals = BTreeMap::new();
1060        for bucket in &self.buckets {
1061            for budget in bucket.pool_budgets() {
1062                let bytes = budget
1063                    .total_bytes()?
1064                    .checked_mul(lanes)
1065                    .ok_or_else(|| invalid_plan("reusable lane workspace budget overflows u64"))?;
1066                let total = totals.entry(budget.pool_id().clone()).or_insert(0_u64);
1067                *total = total
1068                    .checked_add(bytes)
1069                    .ok_or_else(|| invalid_plan("reusable pool workspace ceiling overflows u64"))?;
1070            }
1071        }
1072        Ok(totals)
1073    }
1074
1075    /// Arenas retained by the declared startup capture inventory. Multiple
1076    /// programs using one bucket share its lane arena; unreferenced buckets
1077    /// remain permissions rather than resident startup allocations.
1078    pub(crate) fn startup_sealed_pool_workspace_bytes(
1079        &self,
1080    ) -> Result<BTreeMap<DynamicBackingPoolId, u64>, VNextError> {
1081        let Some(policy) = self
1082            .program_policy()
1083            .filter(|policy| policy.catalog_lifetime().is_startup_sealed())
1084        else {
1085            return Ok(BTreeMap::new());
1086        };
1087        let mut captured = BTreeSet::new();
1088        let mut totals = BTreeMap::new();
1089        for program in policy.programs() {
1090            let shape = program.shape();
1091            let bucket = self
1092                .smallest_covering_bucket(
1093                    program.class_id(),
1094                    shape.request_capacity(),
1095                    shape.token_capacity(),
1096                    0,
1097                )
1098                .ok_or_else(|| invalid_plan("startup capture program has no workspace bucket"))?;
1099            if !captured.insert(bucket.bucket().bucket_id()) {
1100                continue;
1101            }
1102            for budget in bucket.pool_budgets() {
1103                let bytes = budget
1104                    .total_bytes()?
1105                    .checked_mul(u64::from(self.maximum_reusable_lanes))
1106                    .ok_or_else(|| invalid_plan("sealed reusable lane workspace overflows u64"))?;
1107                let total = totals.entry(budget.pool_id().clone()).or_insert(0_u64);
1108                *total = total
1109                    .checked_add(bytes)
1110                    .ok_or_else(|| invalid_plan("sealed reusable pool workspace overflows u64"))?;
1111            }
1112        }
1113        Ok(totals)
1114    }
1115}
1116
1117impl<'de> Deserialize<'de> for ReusableExecutionMemoryPlan {
1118    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1119    where
1120        D: Deserializer<'de>,
1121    {
1122        let wire = ReusableExecutionMemoryPlanWire::deserialize(deserializer)?;
1123        Self::new_with_program_policy(
1124            wire.maximum_reusable_lanes,
1125            wire.maximum_device_executables,
1126            wire.buckets,
1127            wire.program_policy,
1128        )
1129        .map_err(serde::de::Error::custom)
1130    }
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135    use super::{
1136        ReusableExecutionBucketSpec, ReusableExecutionCapacity, ReusableExecutionClassId,
1137        ReusableExecutionPolicy, ReusableExecutionProgramPolicy, ReusableExecutionProgramShape,
1138        ReusableExecutionProgramSpec,
1139    };
1140
1141    fn bucket(class: &str, sequences: u32, tokens: u64) -> ReusableExecutionBucketSpec {
1142        ReusableExecutionBucketSpec::new(
1143            ReusableExecutionClassId::new(class).unwrap(),
1144            ReusableExecutionCapacity::new(sequences, tokens, 1).unwrap(),
1145        )
1146        .unwrap()
1147    }
1148
1149    fn program(class: &str, shape: ReusableExecutionProgramShape) -> ReusableExecutionProgramSpec {
1150        ReusableExecutionProgramSpec::new(ReusableExecutionClassId::new(class).unwrap(), shape)
1151            .unwrap()
1152    }
1153
1154    #[test]
1155    fn reusable_policy_canonicalizes_and_selects_within_one_class() {
1156        let policy = ReusableExecutionPolicy::new(
1157            1,
1158            vec![
1159                bucket("uniform-query", 4, 4),
1160                bucket("packed-prefill", 1, 64),
1161                bucket("uniform-query", 1, 1),
1162                bucket("uniform-query", 2, 2),
1163            ],
1164        )
1165        .unwrap();
1166
1167        let class = ReusableExecutionClassId::new("uniform-query").unwrap();
1168        assert_eq!(
1169            policy
1170                .smallest_covering_bucket(&class, 3, 3, 0)
1171                .unwrap()
1172                .capacity(),
1173            ReusableExecutionCapacity::new(4, 4, 1).unwrap()
1174        );
1175        assert!(policy.smallest_covering_bucket(&class, 5, 5, 0).is_none());
1176    }
1177
1178    #[test]
1179    fn reusable_policy_wire_rejects_derived_id_tampering() {
1180        let policy =
1181            ReusableExecutionPolicy::new(1, vec![bucket("packed-prefill", 1, 64)]).unwrap();
1182        let mut value = serde_json::to_value(&policy).unwrap();
1183        value["buckets"][0]["bucket_id"] = serde_json::Value::String("forged".to_owned());
1184        assert!(serde_json::from_value::<ReusableExecutionPolicy>(value).is_err());
1185    }
1186
1187    #[test]
1188    fn reusable_policy_rejects_incomparable_capacities_within_class() {
1189        let first = bucket("mixed", 1, 64);
1190        let second = bucket("mixed", 4, 4);
1191        assert!(ReusableExecutionPolicy::new(1, vec![first, second]).is_err());
1192    }
1193
1194    #[test]
1195    fn exact_program_policy_is_canonical_and_adds_prefill_prerequisite() {
1196        let decode_4 = program(
1197            "uniform-query",
1198            ReusableExecutionProgramShape::uniform_decode(4, 1).unwrap(),
1199        );
1200        let decode_1 = program(
1201            "uniform-query",
1202            ReusableExecutionProgramShape::uniform_decode(1, 1).unwrap(),
1203        );
1204        let final_prefill = program(
1205            "packed-prefill",
1206            ReusableExecutionProgramShape::prefill(4, 4, 8).unwrap(),
1207        );
1208        let prerequisite = program(
1209            "packed-prefill",
1210            ReusableExecutionProgramShape::prefill(0, 4, 8).unwrap(),
1211        );
1212
1213        let program_policy = ReusableExecutionProgramPolicy::exact_startup_sealed(
1214            1,
1215            1,
1216            1,
1217            vec![
1218                decode_4.clone(),
1219                final_prefill.clone(),
1220                decode_1.clone(),
1221                decode_4.clone(),
1222            ],
1223        )
1224        .unwrap();
1225
1226        assert_eq!(
1227            program_policy.programs(),
1228            &[prerequisite, final_prefill, decode_1, decode_4]
1229        );
1230    }
1231
1232    #[test]
1233    fn on_demand_policy_preserves_exact_shapes_without_replaying_user_transitions() {
1234        let shapes = vec![program(
1235            "uniform-query",
1236            ReusableExecutionProgramShape::uniform_decode(3, 1).unwrap(),
1237        )];
1238        let startup =
1239            ReusableExecutionProgramPolicy::exact_startup_sealed(1, 1, 1, shapes.clone()).unwrap();
1240        let demand = ReusableExecutionProgramPolicy::exact_on_demand(shapes).unwrap();
1241        assert_eq!(startup.programs(), demand.programs());
1242        let value = serde_json::to_value(&demand).unwrap();
1243        assert_ne!(serde_json::to_value(&startup).unwrap(), value);
1244        assert_eq!(
1245            serde_json::from_value::<ReusableExecutionProgramPolicy>(value.clone()).unwrap(),
1246            demand
1247        );
1248        for field in [
1249            "warmup_passes",
1250            "capture_passes",
1251            "replay_validation_passes",
1252        ] {
1253            let mut invalid = value.clone();
1254            invalid[field] = serde_json::json!(2);
1255            assert!(serde_json::from_value::<ReusableExecutionProgramPolicy>(invalid).is_err());
1256        }
1257    }
1258
1259    #[test]
1260    fn program_policy_wire_rejects_missing_prefill_prerequisite() {
1261        let program_policy = ReusableExecutionProgramPolicy::exact_startup_sealed(
1262            1,
1263            1,
1264            1,
1265            vec![program(
1266                "packed-prefill",
1267                ReusableExecutionProgramShape::prefill(4, 4, 8).unwrap(),
1268            )],
1269        )
1270        .unwrap();
1271        let mut value = serde_json::to_value(program_policy).unwrap();
1272        value["programs"].as_array_mut().unwrap().remove(0);
1273
1274        assert!(serde_json::from_value::<ReusableExecutionProgramPolicy>(value).is_err());
1275    }
1276
1277    #[test]
1278    fn legacy_reusable_policy_wire_omits_program_policy() {
1279        let policy =
1280            ReusableExecutionPolicy::new(1, vec![bucket("packed-prefill", 1, 64)]).unwrap();
1281        let value = serde_json::to_value(&policy).unwrap();
1282
1283        assert!(value.get("program_policy").is_none());
1284        assert_eq!(
1285            serde_json::from_value::<ReusableExecutionPolicy>(value).unwrap(),
1286            policy
1287        );
1288    }
1289
1290    #[test]
1291    fn exact_program_policy_rejects_inconsistent_decode_shape_wire() {
1292        let shape = ReusableExecutionProgramShape::uniform_decode(4, 1).unwrap();
1293        let mut value = serde_json::to_value(shape).unwrap();
1294        value["token_capacity"] = serde_json::json!(3);
1295
1296        assert!(serde_json::from_value::<ReusableExecutionProgramShape>(value).is_err());
1297    }
1298}