Skip to main content

ferrum_interfaces/vnext/execution/
policy.rs

1use super::{
2    canonical_fingerprint, invalid_plan, CapabilityCatalog, CompletionRetentionSpec,
3    ContractVersion, DynamicStorageProfile, ExecutionWeightPlan, PlanNodeResolution,
4    PreparedModelFamily, ReusableExecutionPolicy, Serialize, TrustedExecutionWeightPlan,
5    VNextError,
6};
7use crate::vnext::{CheckpointCapacityPolicy, ExecutionDeterminismRequirement};
8use ferrum_types::AttentionExecutionPolicy;
9
10/// Typed policy selected before planning. Memory capacity is part of the
11/// public policy contract so a plan cannot depend on an undocumented env var.
12pub trait RuntimePolicy:
13    Clone + Send + Sync + Serialize + serde::de::DeserializeOwned + std::fmt::Debug + 'static
14{
15    fn version(&self) -> ContractVersion;
16
17    /// Policy memory ceiling before the explicit reserve is subtracted. It may
18    /// be lower than, but never exceed, the raw device capacity.
19    fn memory_capacity_bytes(&self) -> u64;
20
21    fn memory_reserve_bytes(&self) -> u64;
22
23    /// A non-zero protocol ceiling only. Planning must not reserve, claim,
24    /// iterate, or materialize resources up to this value.
25    fn maximum_active_sequences(&self) -> u32;
26
27    /// A non-zero ceiling for tokens selected into one scheduler step. This is
28    /// not installed capacity: admission still evaluates the exact step shape
29    /// against then-live backing before any provider work is encoded.
30    fn maximum_scheduled_tokens(&self) -> u64;
31
32    /// Provider-family policy sealed into the plan fingerprint. Physical
33    /// kernel variants remain an invocation-time provider decision.
34    fn attention_execution_policy(&self) -> AttentionExecutionPolicy;
35
36    /// Minimum repeatability and reusable-execution equivalence that every
37    /// selected operation provider must prove.
38    fn execution_determinism_requirement(&self) -> ExecutionDeterminismRequirement;
39
40    /// Ordered, non-empty allowlist used by planning after intersecting every
41    /// selected provider requirement with the concrete runtime offers.
42    fn dynamic_storage_profile_order(&self) -> &[DynamicStorageProfile];
43
44    /// Optional reusable-execution capacity policy selected before planning.
45    /// Core treats class identities as opaque and owns their memory derivation.
46    fn reusable_execution_policy(&self) -> Option<&ReusableExecutionPolicy>;
47
48    /// Optional physical State-pool growth permission. Runtime aggregate
49    /// charging and foreground protection remain separate requirements.
50    fn checkpoint_capacity_policy(&self) -> Option<&CheckpointCapacityPolicy> {
51        None
52    }
53
54    fn validate(&self) -> Result<(), VNextError>;
55}
56
57pub fn canonical_runtime_policy_fingerprint<P: RuntimePolicy>(
58    policy: &P,
59) -> Result<String, VNextError> {
60    policy.validate()?;
61    canonical_fingerprint(policy, "fingerprint validated runtime policy")
62}
63
64pub struct PlanBuildRequest<'a, P: RuntimePolicy> {
65    pub(super) family: &'a PreparedModelFamily,
66    pub(super) capabilities: &'a CapabilityCatalog,
67    pub(super) policy: &'a P,
68    pub(super) node_resolutions: Vec<PlanNodeResolution>,
69    pub(super) completion_retention: CompletionRetentionSpec,
70    pub(super) execution_weights: TrustedExecutionWeightPlan,
71}
72
73impl<'a, P: RuntimePolicy> PlanBuildRequest<'a, P> {
74    pub fn new(
75        family: &'a PreparedModelFamily,
76        capabilities: &'a CapabilityCatalog,
77        policy: &'a P,
78        node_resolutions: Vec<PlanNodeResolution>,
79    ) -> Result<Self, VNextError> {
80        if node_resolutions.is_empty() {
81            return Err(invalid_plan("plan build request has no node resolutions"));
82        }
83        policy.validate()?;
84        Ok(Self {
85            family,
86            capabilities,
87            policy,
88            node_resolutions,
89            completion_retention: CompletionRetentionSpec::default(),
90            execution_weights: TrustedExecutionWeightPlan::identity(family)?,
91        })
92    }
93
94    pub fn with_completion_retention(
95        mut self,
96        completion_retention: CompletionRetentionSpec,
97    ) -> Result<Self, VNextError> {
98        let produced_values = self
99            .family
100            .program()
101            .blocks()
102            .iter()
103            .flat_map(|block| block.nodes.iter())
104            .flat_map(|node| node.outputs.iter())
105            .collect::<std::collections::BTreeSet<_>>();
106        if completion_retention
107            .values()
108            .iter()
109            .any(|value_id| !produced_values.contains(value_id))
110        {
111            return Err(invalid_plan(
112                "completion retention must reference a semantic node output",
113            ));
114        }
115        self.completion_retention = completion_retention;
116        Ok(self)
117    }
118
119    pub fn with_execution_weights(
120        mut self,
121        execution_weights: TrustedExecutionWeightPlan,
122    ) -> Result<Self, VNextError> {
123        execution_weights.validate_against_catalog(self.family, self.capabilities)?;
124        self.execution_weights = execution_weights;
125        Ok(self)
126    }
127
128    pub fn family(&self) -> &PreparedModelFamily {
129        self.family
130    }
131
132    pub fn capabilities(&self) -> &CapabilityCatalog {
133        self.capabilities
134    }
135
136    pub fn policy(&self) -> &P {
137        self.policy
138    }
139
140    pub fn execution_weights(&self) -> &ExecutionWeightPlan {
141        self.execution_weights.plan()
142    }
143}