tenferro_runtime/runtime/policy.rs
1use std::sync::Arc;
2
3use super::identity::validate_identifier;
4use super::{EngineId, HardwareClassId, IdentityError, IdentityKind, PlacementConstraintError};
5
6/// Selects the planning and execution reproducibility policy.
7///
8/// # Examples
9///
10/// ```
11/// use tenferro_runtime::Determinism;
12///
13/// assert_eq!(Determinism::Fast, Determinism::Fast);
14/// ```
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
16pub enum Determinism {
17 /// Prefer fast backend choices when multiple valid plans exist.
18 Fast,
19 /// Prefer reproducible backend choices when multiple valid plans exist.
20 Reproducible,
21}
22
23/// Validated namespaced storage-class identity.
24///
25/// # Examples
26///
27/// ```
28/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
29/// use tenferro_runtime::StorageClass;
30///
31/// assert_eq!(
32/// StorageClass::new("tenferro.storage.host")?.as_str(),
33/// "tenferro.storage.host"
34/// );
35/// # Ok(())
36/// # }
37/// ```
38#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
39pub struct StorageClass(Arc<str>);
40
41impl StorageClass {
42 /// Validate a lowercase ASCII namespaced storage-class identifier.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use tenferro_runtime::StorageClass;
48 ///
49 /// assert!(StorageClass::new("tenferro.storage.host").is_ok());
50 /// ```
51 ///
52 /// # Errors
53 ///
54 /// Returns [`IdentityError`] with [`IdentityKind::StorageClass`] when
55 /// `value` does not match the lowercase ASCII namespaced identifier grammar.
56 pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
57 validate_identifier(value.into(), IdentityKind::StorageClass).map(Self)
58 }
59
60 /// Borrow the validated identifier text.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
66 /// use tenferro_runtime::StorageClass;
67 ///
68 /// assert_eq!(StorageClass::new("tenferro.storage.host")?.as_str(), "tenferro.storage.host");
69 /// # Ok(())
70 /// # }
71 /// ```
72 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75
76 pub(crate) fn runtime_created(value: impl Into<Arc<str>>) -> Self {
77 Self(value.into())
78 }
79}
80
81/// Immutable execution endpoint used to identify a transfer route.
82///
83/// An endpoint combines the logical runtime engine with the storage class that
84/// the engine exposes. Equality, ordering, and hashing are based on both
85/// values, so two engines that expose the same storage class remain distinct
86/// route endpoints.
87///
88/// # Examples
89///
90/// ```
91/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
92/// use tenferro_runtime::{EngineId, StorageClass, TransferEndpoint};
93///
94/// let endpoint = TransferEndpoint::new(
95/// EngineId::new("tenferro.cpu")?,
96/// StorageClass::new("tenferro.storage.host")?,
97/// );
98/// assert_eq!(endpoint.engine_id().as_str(), "tenferro.cpu");
99/// assert_eq!(endpoint.storage_class().as_str(), "tenferro.storage.host");
100/// # Ok(())
101/// # }
102/// ```
103#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
104pub struct TransferEndpoint {
105 engine_id: EngineId,
106 storage_class: StorageClass,
107}
108
109impl TransferEndpoint {
110 /// Construct an endpoint from an engine and one of its storage classes.
111 ///
112 /// The runtime validates that the engine is registered and supports the
113 /// storage class when a transfer-provider candidate is frozen.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
119 /// use tenferro_runtime::{EngineId, StorageClass, TransferEndpoint};
120 ///
121 /// let endpoint = TransferEndpoint::new(
122 /// EngineId::new("tenferro.cpu")?,
123 /// StorageClass::new("tenferro.storage.host")?,
124 /// );
125 /// assert_eq!(endpoint.engine_id().as_str(), "tenferro.cpu");
126 /// # Ok(())
127 /// # }
128 /// ```
129 pub fn new(engine_id: EngineId, storage_class: StorageClass) -> Self {
130 Self {
131 engine_id,
132 storage_class,
133 }
134 }
135
136 /// Return the logical engine that owns this endpoint.
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
142 /// use tenferro_runtime::{EngineId, StorageClass, TransferEndpoint};
143 ///
144 /// let engine = EngineId::new("tenferro.cpu")?;
145 /// let endpoint = TransferEndpoint::new(
146 /// engine.clone(),
147 /// StorageClass::new("tenferro.storage.host")?,
148 /// );
149 /// assert_eq!(endpoint.engine_id(), &engine);
150 /// # Ok(())
151 /// # }
152 /// ```
153 pub fn engine_id(&self) -> &EngineId {
154 &self.engine_id
155 }
156
157 /// Return the storage class exposed at this endpoint.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
163 /// use tenferro_runtime::{EngineId, StorageClass, TransferEndpoint};
164 ///
165 /// let storage = StorageClass::new("tenferro.storage.host")?;
166 /// let endpoint = TransferEndpoint::new(EngineId::new("tenferro.cpu")?, storage.clone());
167 /// assert_eq!(endpoint.storage_class(), &storage);
168 /// # Ok(())
169 /// # }
170 /// ```
171 pub fn storage_class(&self) -> &StorageClass {
172 &self.storage_class
173 }
174}
175
176/// Validated namespaced layout-class identity.
177///
178/// # Examples
179///
180/// ```
181/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
182/// use tenferro_runtime::LayoutClass;
183///
184/// assert_eq!(
185/// LayoutClass::new("tenferro.layout.col-major")?.as_str(),
186/// "tenferro.layout.col-major"
187/// );
188/// # Ok(())
189/// # }
190/// ```
191#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
192pub struct LayoutClass(Arc<str>);
193
194impl LayoutClass {
195 /// Validate a lowercase ASCII namespaced layout-class identifier.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use tenferro_runtime::LayoutClass;
201 ///
202 /// assert!(LayoutClass::new("tenferro.layout.col-major").is_ok());
203 /// ```
204 ///
205 /// # Errors
206 ///
207 /// Returns [`IdentityError`] with [`IdentityKind::LayoutClass`] when
208 /// `value` does not match the lowercase ASCII namespaced identifier grammar.
209 pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
210 validate_identifier(value.into(), IdentityKind::LayoutClass).map(Self)
211 }
212
213 /// Borrow the validated identifier text.
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
219 /// use tenferro_runtime::LayoutClass;
220 ///
221 /// assert_eq!(LayoutClass::new("tenferro.layout.col-major")?.as_str(), "tenferro.layout.col-major");
222 /// # Ok(())
223 /// # }
224 /// ```
225 pub fn as_str(&self) -> &str {
226 &self.0
227 }
228
229 pub(crate) fn runtime_created(value: impl Into<Arc<str>>) -> Self {
230 Self(value.into())
231 }
232}
233
234/// Requested program placement constraints.
235///
236/// An empty engine list means any engine is allowed.
237///
238/// # Examples
239///
240/// ```
241/// use tenferro_runtime::ProgramPlacementConstraint;
242///
243/// assert!(ProgramPlacementConstraint::any().allowed_engines().is_empty());
244/// ```
245#[derive(Clone, Debug, Eq, Hash, PartialEq)]
246pub struct ProgramPlacementConstraint {
247 allowed_engines: Arc<[EngineId]>,
248 storage_class: Option<StorageClass>,
249}
250
251impl ProgramPlacementConstraint {
252 /// Return an unconstrained placement request.
253 ///
254 /// # Examples
255 ///
256 /// ```
257 /// use tenferro_runtime::ProgramPlacementConstraint;
258 ///
259 /// let any = ProgramPlacementConstraint::any();
260 /// assert!(any.allowed_engines().is_empty());
261 /// assert!(any.storage_class().is_none());
262 /// ```
263 pub fn any() -> Self {
264 Self {
265 allowed_engines: Arc::from(Vec::<EngineId>::new()),
266 storage_class: None,
267 }
268 }
269
270 /// Build a placement constraint from engine preferences and storage class.
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
276 /// use tenferro_runtime::{EngineId, ProgramPlacementConstraint, StorageClass};
277 ///
278 /// let engine = EngineId::new("tenferro.cpu")?;
279 /// let storage = StorageClass::new("tenferro.storage.host")?;
280 /// let constraint = ProgramPlacementConstraint::new(vec![engine.clone()], Some(storage.clone()))?;
281 /// assert_eq!(constraint.allowed_engines(), &[engine]);
282 /// assert_eq!(constraint.storage_class(), Some(&storage));
283 /// # Ok(())
284 /// # }
285 /// ```
286 ///
287 /// # Errors
288 ///
289 /// Returns [`PlacementConstraintError::DuplicateEngine`] when the same
290 /// engine appears more than once.
291 pub fn new(
292 allowed_engines: impl Into<Arc<[EngineId]>>,
293 storage_class: Option<StorageClass>,
294 ) -> Result<Self, PlacementConstraintError> {
295 let allowed_engines = allowed_engines.into();
296 for duplicate_index in 0..allowed_engines.len() {
297 if let Some(first_index) = (0..duplicate_index).find(|&first_index| {
298 allowed_engines[first_index] == allowed_engines[duplicate_index]
299 }) {
300 return Err(PlacementConstraintError::DuplicateEngine {
301 engine_id: allowed_engines[duplicate_index].clone(),
302 first_index,
303 duplicate_index,
304 });
305 }
306 }
307 Ok(Self {
308 allowed_engines,
309 storage_class,
310 })
311 }
312
313 /// Return the allowed engines in preference order.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
319 /// use tenferro_runtime::{EngineId, ProgramPlacementConstraint};
320 ///
321 /// let engine = EngineId::new("tenferro.cpu")?;
322 /// let constraint = ProgramPlacementConstraint::new(vec![engine.clone()], None)?;
323 /// assert_eq!(constraint.allowed_engines(), &[engine]);
324 /// # Ok(())
325 /// # }
326 /// ```
327 pub fn allowed_engines(&self) -> &[EngineId] {
328 &self.allowed_engines
329 }
330
331 /// Return the requested storage class, if one was supplied.
332 ///
333 /// # Examples
334 ///
335 /// ```
336 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
337 /// use tenferro_runtime::{ProgramPlacementConstraint, StorageClass};
338 ///
339 /// let storage = StorageClass::new("tenferro.storage.host")?;
340 /// let constraint = ProgramPlacementConstraint::new(Vec::new(), Some(storage.clone()))?;
341 /// assert_eq!(constraint.storage_class(), Some(&storage));
342 /// # Ok(())
343 /// # }
344 /// ```
345 pub fn storage_class(&self) -> Option<&StorageClass> {
346 self.storage_class.as_ref()
347 }
348}
349
350/// Runtime-resolved program placement.
351///
352/// A0 exposes no public constructor for this runtime-created value; B0 supplies
353/// its runtime creation path.
354///
355/// # Examples
356///
357/// ```
358/// use std::fmt::Debug;
359/// use tenferro_runtime::ResolvedProgramPlacement;
360///
361/// fn requires_debug<T: Debug>() {}
362/// requires_debug::<ResolvedProgramPlacement>();
363/// ```
364#[derive(Clone, Debug, Eq, Hash, PartialEq)]
365pub struct ResolvedProgramPlacement {
366 engine_id: EngineId,
367 storage_class: StorageClass,
368}
369
370impl ResolvedProgramPlacement {
371 // INVARIANT: A0 module-local tests create resolved placements until B0 owns runtime resolution.
372 #[allow(dead_code)]
373 pub(crate) fn new(engine_id: EngineId, storage_class: StorageClass) -> Self {
374 Self {
375 engine_id,
376 storage_class,
377 }
378 }
379
380 /// Return the resolved execution engine.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// use std::fmt::Debug;
386 /// use tenferro_runtime::ResolvedProgramPlacement;
387 ///
388 /// fn requires_debug<T: Debug>() {}
389 /// requires_debug::<ResolvedProgramPlacement>();
390 /// ```
391 pub fn engine_id(&self) -> &EngineId {
392 &self.engine_id
393 }
394
395 /// Return the resolved storage class.
396 ///
397 /// # Examples
398 ///
399 /// ```
400 /// use std::fmt::Debug;
401 /// use tenferro_runtime::ResolvedProgramPlacement;
402 ///
403 /// fn requires_debug<T: Debug>() {}
404 /// requires_debug::<ResolvedProgramPlacement>();
405 /// ```
406 pub fn storage_class(&self) -> &StorageClass {
407 &self.storage_class
408 }
409}
410
411/// Controls how a caller reacts to a preparation already in flight.
412///
413/// The default is [`CacheInFlightBehavior::Wait`].
414///
415/// # Examples
416///
417/// ```
418/// use tenferro_runtime::CacheInFlightBehavior;
419///
420/// assert_eq!(CacheInFlightBehavior::default(), CacheInFlightBehavior::Wait);
421/// ```
422#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
423pub enum CacheInFlightBehavior {
424 /// Wait for the in-flight preparation.
425 #[default]
426 Wait,
427 /// Refuse to join the in-flight preparation.
428 Refuse,
429}
430
431/// Process-wide execution planning policy.
432///
433/// # Examples
434///
435/// ```
436/// use tenferro_runtime::{Determinism, ExecutionPolicy};
437///
438/// let policy = ExecutionPolicy::new(Determinism::Fast, Some(1024), 7);
439/// assert_eq!(policy.hard_workspace_limit_bytes(), Some(1024));
440/// ```
441#[derive(Clone, Debug, Eq, Hash, PartialEq)]
442pub struct ExecutionPolicy {
443 determinism: Determinism,
444 hard_workspace_limit_bytes: Option<usize>,
445 planning_seed: u64,
446}
447
448impl ExecutionPolicy {
449 /// Build an execution policy.
450 ///
451 /// # Examples
452 ///
453 /// ```
454 /// use tenferro_runtime::{Determinism, ExecutionPolicy};
455 ///
456 /// let policy = ExecutionPolicy::new(Determinism::Reproducible, None, 0);
457 /// assert_eq!(policy.determinism(), Determinism::Reproducible);
458 /// ```
459 pub fn new(
460 determinism: Determinism,
461 hard_workspace_limit_bytes: Option<usize>,
462 planning_seed: u64,
463 ) -> Self {
464 Self {
465 determinism,
466 hard_workspace_limit_bytes,
467 planning_seed,
468 }
469 }
470
471 /// Return the determinism mode.
472 ///
473 /// # Examples
474 ///
475 /// ```
476 /// use tenferro_runtime::{Determinism, ExecutionPolicy};
477 ///
478 /// assert_eq!(
479 /// ExecutionPolicy::new(Determinism::Fast, None, 0).determinism(),
480 /// Determinism::Fast
481 /// );
482 /// ```
483 pub fn determinism(&self) -> Determinism {
484 self.determinism
485 }
486
487 /// Return the hard workspace limit in bytes, if one is configured.
488 ///
489 /// # Examples
490 ///
491 /// ```
492 /// use tenferro_runtime::{Determinism, ExecutionPolicy};
493 ///
494 /// assert_eq!(
495 /// ExecutionPolicy::new(Determinism::Fast, Some(8), 0).hard_workspace_limit_bytes(),
496 /// Some(8)
497 /// );
498 /// ```
499 pub fn hard_workspace_limit_bytes(&self) -> Option<usize> {
500 self.hard_workspace_limit_bytes
501 }
502
503 /// Return the planning seed.
504 ///
505 /// # Examples
506 ///
507 /// ```
508 /// use tenferro_runtime::{Determinism, ExecutionPolicy};
509 ///
510 /// assert_eq!(ExecutionPolicy::new(Determinism::Fast, None, 42).planning_seed(), 42);
511 /// ```
512 pub fn planning_seed(&self) -> u64 {
513 self.planning_seed
514 }
515}
516
517/// Per-prepare request options before runtime normalization.
518///
519/// Raw options intentionally do not implement `Hash`; normalized keys are
520/// created only after placement and planning resolution.
521///
522/// # Examples
523///
524/// ```
525/// use tenferro_runtime::PrepareOptions;
526///
527/// assert!(PrepareOptions::new().planning_seed().is_none());
528/// ```
529#[derive(Clone, Debug, Default, Eq, PartialEq)]
530pub struct PrepareOptions {
531 placement: Option<ProgramPlacementConstraint>,
532 hard_workspace_limit_bytes: Option<usize>,
533 planning_seed: Option<u64>,
534 cache_in_flight: CacheInFlightBehavior,
535}
536
537impl PrepareOptions {
538 /// Return default prepare options.
539 ///
540 /// # Examples
541 ///
542 /// ```
543 /// use tenferro_runtime::{CacheInFlightBehavior, PrepareOptions};
544 ///
545 /// let options = PrepareOptions::new();
546 /// assert_eq!(options.cache_in_flight(), CacheInFlightBehavior::Wait);
547 /// ```
548 pub fn new() -> Self {
549 Self::default()
550 }
551
552 /// Set the placement constraint.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// use tenferro_runtime::{PrepareOptions, ProgramPlacementConstraint};
558 ///
559 /// let placement = ProgramPlacementConstraint::any();
560 /// let options = PrepareOptions::new().with_placement(placement.clone());
561 /// assert_eq!(options.placement(), Some(&placement));
562 /// ```
563 pub fn with_placement(mut self, placement: ProgramPlacementConstraint) -> Self {
564 self.placement = Some(placement);
565 self
566 }
567
568 /// Set or reset the per-call workspace override.
569 ///
570 /// Passing `None` resets the field to inherit the policy value.
571 ///
572 /// # Examples
573 ///
574 /// ```
575 /// use tenferro_runtime::PrepareOptions;
576 ///
577 /// let options = PrepareOptions::new().with_hard_workspace_limit_bytes(Some(0));
578 /// assert_eq!(options.hard_workspace_limit_bytes(), Some(0));
579 /// assert_eq!(options.with_hard_workspace_limit_bytes(None).hard_workspace_limit_bytes(), None);
580 /// ```
581 pub fn with_hard_workspace_limit_bytes(mut self, limit: Option<usize>) -> Self {
582 self.hard_workspace_limit_bytes = limit;
583 self
584 }
585
586 /// Set the per-call planning seed override.
587 ///
588 /// # Examples
589 ///
590 /// ```
591 /// use tenferro_runtime::PrepareOptions;
592 ///
593 /// assert_eq!(PrepareOptions::new().with_planning_seed(3).planning_seed(), Some(3));
594 /// ```
595 pub fn with_planning_seed(mut self, seed: u64) -> Self {
596 self.planning_seed = Some(seed);
597 self
598 }
599
600 /// Set the in-flight cache behavior for this request.
601 ///
602 /// # Examples
603 ///
604 /// ```
605 /// use tenferro_runtime::{CacheInFlightBehavior, PrepareOptions};
606 ///
607 /// let options = PrepareOptions::new().with_cache_in_flight(CacheInFlightBehavior::Refuse);
608 /// assert_eq!(options.cache_in_flight(), CacheInFlightBehavior::Refuse);
609 /// ```
610 pub fn with_cache_in_flight(mut self, behavior: CacheInFlightBehavior) -> Self {
611 self.cache_in_flight = behavior;
612 self
613 }
614
615 /// Return the raw placement constraint, if supplied.
616 ///
617 /// # Examples
618 ///
619 /// ```
620 /// use tenferro_runtime::{PrepareOptions, ProgramPlacementConstraint};
621 ///
622 /// let placement = ProgramPlacementConstraint::any();
623 /// let options = PrepareOptions::new().with_placement(placement.clone());
624 /// assert_eq!(options.placement(), Some(&placement));
625 /// ```
626 pub fn placement(&self) -> Option<&ProgramPlacementConstraint> {
627 self.placement.as_ref()
628 }
629
630 /// Return the raw per-call workspace override.
631 ///
632 /// # Examples
633 ///
634 /// ```
635 /// use tenferro_runtime::PrepareOptions;
636 ///
637 /// assert_eq!(
638 /// PrepareOptions::new().with_hard_workspace_limit_bytes(Some(16)).hard_workspace_limit_bytes(),
639 /// Some(16)
640 /// );
641 /// ```
642 pub fn hard_workspace_limit_bytes(&self) -> Option<usize> {
643 self.hard_workspace_limit_bytes
644 }
645
646 /// Return the raw per-call planning seed override.
647 ///
648 /// # Examples
649 ///
650 /// ```
651 /// use tenferro_runtime::PrepareOptions;
652 ///
653 /// assert_eq!(PrepareOptions::new().with_planning_seed(9).planning_seed(), Some(9));
654 /// ```
655 pub fn planning_seed(&self) -> Option<u64> {
656 self.planning_seed
657 }
658
659 /// Return the request's in-flight cache behavior.
660 ///
661 /// # Examples
662 ///
663 /// ```
664 /// use tenferro_runtime::{CacheInFlightBehavior, PrepareOptions};
665 ///
666 /// assert_eq!(PrepareOptions::new().cache_in_flight(), CacheInFlightBehavior::Wait);
667 /// ```
668 pub fn cache_in_flight(&self) -> CacheInFlightBehavior {
669 self.cache_in_flight
670 }
671}
672
673/// Normalized prepare-options cache key.
674///
675/// A0 exposes no public constructor for this runtime-created value; B0 supplies
676/// its runtime creation path.
677///
678/// # Examples
679///
680/// ```
681/// use std::fmt::Debug;
682/// use tenferro_runtime::PrepareOptionsKey;
683///
684/// fn requires_debug<T: Debug>() {}
685/// requires_debug::<PrepareOptionsKey>();
686/// ```
687#[derive(Clone, Debug, Eq, Hash, PartialEq)]
688pub struct PrepareOptionsKey {
689 resolved_placement: ResolvedProgramPlacement,
690 hard_workspace_limit_bytes: Option<usize>,
691 planning_seed: u64,
692}
693
694impl PrepareOptionsKey {
695 // INVARIANT: A0 module-local tests create normalized keys until B0 owns prepare-key resolution.
696 #[allow(dead_code)]
697 pub(crate) fn from_resolved(
698 resolved_placement: ResolvedProgramPlacement,
699 hard_workspace_limit_bytes: Option<usize>,
700 planning_seed: u64,
701 ) -> Self {
702 Self {
703 resolved_placement,
704 hard_workspace_limit_bytes,
705 planning_seed,
706 }
707 }
708
709 /// Return the resolved placement carried by this key.
710 ///
711 /// # Examples
712 ///
713 /// ```
714 /// use std::fmt::Debug;
715 /// use tenferro_runtime::PrepareOptionsKey;
716 ///
717 /// fn requires_debug<T: Debug>() {}
718 /// requires_debug::<PrepareOptionsKey>();
719 /// ```
720 pub fn resolved_placement(&self) -> &ResolvedProgramPlacement {
721 &self.resolved_placement
722 }
723
724 /// Return the resolved hard workspace limit.
725 ///
726 /// # Examples
727 ///
728 /// ```
729 /// use std::fmt::Debug;
730 /// use tenferro_runtime::PrepareOptionsKey;
731 ///
732 /// fn requires_debug<T: Debug>() {}
733 /// requires_debug::<PrepareOptionsKey>();
734 /// ```
735 pub fn hard_workspace_limit_bytes(&self) -> Option<usize> {
736 self.hard_workspace_limit_bytes
737 }
738
739 /// Return the resolved planning seed.
740 ///
741 /// # Examples
742 ///
743 /// ```
744 /// use std::fmt::Debug;
745 /// use tenferro_runtime::PrepareOptionsKey;
746 ///
747 /// fn requires_debug<T: Debug>() {}
748 /// requires_debug::<PrepareOptionsKey>();
749 /// ```
750 pub fn planning_seed(&self) -> u64 {
751 self.planning_seed
752 }
753}
754
755/// Fully resolved planning configuration.
756///
757/// # Examples
758///
759/// ```
760/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
761/// use tenferro_runtime::{
762/// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
763/// };
764///
765/// let policy = ExecutionPolicy::new(Determinism::Fast, Some(32), 5);
766/// let config = ResolvedPlanningConfig::resolve(
767/// &policy,
768/// &PrepareOptions::new(),
769/// HardwareClassId::new("tenferro.cpu")?,
770/// );
771/// assert_eq!(config.hard_workspace_limit_bytes(), Some(32));
772/// # Ok(())
773/// # }
774/// ```
775#[derive(Clone, Debug, Eq, Hash, PartialEq)]
776pub struct ResolvedPlanningConfig {
777 determinism: Determinism,
778 hard_workspace_limit_bytes: Option<usize>,
779 planning_seed: u64,
780 hardware_class: HardwareClassId,
781}
782
783impl ResolvedPlanningConfig {
784 /// Resolve raw policy and per-call options into concrete planning config.
785 ///
786 /// # Examples
787 ///
788 /// ```
789 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
790 /// use tenferro_runtime::{
791 /// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
792 /// };
793 ///
794 /// let policy = ExecutionPolicy::new(Determinism::Fast, Some(32), 5);
795 /// let options = PrepareOptions::new().with_hard_workspace_limit_bytes(Some(0));
796 /// let config = ResolvedPlanningConfig::resolve(
797 /// &policy,
798 /// &options,
799 /// HardwareClassId::new("tenferro.cpu")?,
800 /// );
801 /// assert_eq!(config.hard_workspace_limit_bytes(), Some(0));
802 /// # Ok(())
803 /// # }
804 /// ```
805 pub fn resolve(
806 policy: &ExecutionPolicy,
807 options: &PrepareOptions,
808 hardware_class: HardwareClassId,
809 ) -> Self {
810 Self {
811 determinism: policy.determinism(),
812 hard_workspace_limit_bytes: options
813 .hard_workspace_limit_bytes()
814 .or(policy.hard_workspace_limit_bytes()),
815 planning_seed: options.planning_seed().unwrap_or(policy.planning_seed()),
816 hardware_class,
817 }
818 }
819
820 /// Return the resolved determinism mode.
821 ///
822 /// # Examples
823 ///
824 /// ```
825 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
826 /// use tenferro_runtime::{
827 /// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
828 /// };
829 ///
830 /// let config = ResolvedPlanningConfig::resolve(
831 /// &ExecutionPolicy::new(Determinism::Reproducible, None, 0),
832 /// &PrepareOptions::new(),
833 /// HardwareClassId::new("tenferro.cpu")?,
834 /// );
835 /// assert_eq!(config.determinism(), Determinism::Reproducible);
836 /// # Ok(())
837 /// # }
838 /// ```
839 pub fn determinism(&self) -> Determinism {
840 self.determinism
841 }
842
843 /// Return the resolved hard workspace limit.
844 ///
845 /// # Examples
846 ///
847 /// ```
848 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
849 /// use tenferro_runtime::{
850 /// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
851 /// };
852 ///
853 /// let config = ResolvedPlanningConfig::resolve(
854 /// &ExecutionPolicy::new(Determinism::Fast, Some(7), 0),
855 /// &PrepareOptions::new(),
856 /// HardwareClassId::new("tenferro.cpu")?,
857 /// );
858 /// assert_eq!(config.hard_workspace_limit_bytes(), Some(7));
859 /// # Ok(())
860 /// # }
861 /// ```
862 pub fn hard_workspace_limit_bytes(&self) -> Option<usize> {
863 self.hard_workspace_limit_bytes
864 }
865
866 /// Return the resolved planning seed.
867 ///
868 /// # Examples
869 ///
870 /// ```
871 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
872 /// use tenferro_runtime::{
873 /// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
874 /// };
875 ///
876 /// let config = ResolvedPlanningConfig::resolve(
877 /// &ExecutionPolicy::new(Determinism::Fast, None, 19),
878 /// &PrepareOptions::new(),
879 /// HardwareClassId::new("tenferro.cpu")?,
880 /// );
881 /// assert_eq!(config.planning_seed(), 19);
882 /// # Ok(())
883 /// # }
884 /// ```
885 pub fn planning_seed(&self) -> u64 {
886 self.planning_seed
887 }
888
889 /// Return the resolved hardware class.
890 ///
891 /// # Examples
892 ///
893 /// ```
894 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
895 /// use tenferro_runtime::{
896 /// Determinism, ExecutionPolicy, HardwareClassId, PrepareOptions, ResolvedPlanningConfig,
897 /// };
898 ///
899 /// let hardware = HardwareClassId::new("tenferro.cpu")?;
900 /// let config = ResolvedPlanningConfig::resolve(
901 /// &ExecutionPolicy::new(Determinism::Fast, None, 0),
902 /// &PrepareOptions::new(),
903 /// hardware.clone(),
904 /// );
905 /// assert_eq!(config.hardware_class(), &hardware);
906 /// # Ok(())
907 /// # }
908 /// ```
909 pub fn hardware_class(&self) -> &HardwareClassId {
910 &self.hardware_class
911 }
912}
913
914/// Normalized planning cache key.
915///
916/// A0 exposes no public constructor for this runtime-created value; B0 supplies
917/// its runtime creation path.
918///
919/// # Examples
920///
921/// ```
922/// use std::fmt::Debug;
923/// use tenferro_runtime::ResolvedPlanningKey;
924///
925/// fn requires_debug<T: Debug>() {}
926/// requires_debug::<ResolvedPlanningKey>();
927/// ```
928#[derive(Clone, Debug, Eq, Hash, PartialEq)]
929pub struct ResolvedPlanningKey {
930 determinism: Determinism,
931 hard_workspace_limit_bytes: Option<usize>,
932 planning_seed: u64,
933 hardware_class: HardwareClassId,
934}
935
936impl ResolvedPlanningKey {
937 // INVARIANT: A0 module-local tests create normalized keys until B0 owns planning-key resolution.
938 #[allow(dead_code)]
939 pub(crate) fn from_config(config: &ResolvedPlanningConfig) -> Self {
940 Self {
941 determinism: config.determinism(),
942 hard_workspace_limit_bytes: config.hard_workspace_limit_bytes(),
943 planning_seed: config.planning_seed(),
944 hardware_class: config.hardware_class().clone(),
945 }
946 }
947
948 /// Return the key's determinism component.
949 ///
950 /// # Examples
951 ///
952 /// ```
953 /// use std::fmt::Debug;
954 /// use tenferro_runtime::ResolvedPlanningKey;
955 ///
956 /// fn requires_debug<T: Debug>() {}
957 /// requires_debug::<ResolvedPlanningKey>();
958 /// ```
959 pub fn determinism(&self) -> Determinism {
960 self.determinism
961 }
962
963 /// Return the key's workspace component.
964 ///
965 /// # Examples
966 ///
967 /// ```
968 /// use std::fmt::Debug;
969 /// use tenferro_runtime::ResolvedPlanningKey;
970 ///
971 /// fn requires_debug<T: Debug>() {}
972 /// requires_debug::<ResolvedPlanningKey>();
973 /// ```
974 pub fn hard_workspace_limit_bytes(&self) -> Option<usize> {
975 self.hard_workspace_limit_bytes
976 }
977
978 /// Return the key's planning seed component.
979 ///
980 /// # Examples
981 ///
982 /// ```
983 /// use std::fmt::Debug;
984 /// use tenferro_runtime::ResolvedPlanningKey;
985 ///
986 /// fn requires_debug<T: Debug>() {}
987 /// requires_debug::<ResolvedPlanningKey>();
988 /// ```
989 pub fn planning_seed(&self) -> u64 {
990 self.planning_seed
991 }
992
993 /// Return the key's hardware-class component.
994 ///
995 /// # Examples
996 ///
997 /// ```
998 /// use std::fmt::Debug;
999 /// use tenferro_runtime::ResolvedPlanningKey;
1000 ///
1001 /// fn requires_debug<T: Debug>() {}
1002 /// requires_debug::<ResolvedPlanningKey>();
1003 /// ```
1004 pub fn hardware_class(&self) -> &HardwareClassId {
1005 &self.hardware_class
1006 }
1007}