deepstrike_core/runtime/kernel/wire/driver/
planning.rs1use super::*;
2
3impl CanonicalOperationDriver {
4 pub fn plan(&mut self, context: &PlanContext<'_>) -> Result<PlannedStep, KernelFault> {
12 if let Some(fault) = &self.poison {
13 return Err(fault.clone());
14 }
15 if let Some(staged) = &self.staged {
16 let staged_seq = staged.step_seq;
17 return Err(self.poison_with(KernelFault::new(
18 KernelFaultCode::TransactionConflict,
19 format!(
20 "the driver still holds the plan of step {staged_seq}; its transition never \
21 committed while the semantic kernel already advanced under it, so this \
22 runtime no longer describes the journal — rebuild from the records"
23 ),
24 )));
25 }
26 let mut step = self.plan_inner(context)?;
27 step.observations = self
28 .engine
29 .as_mut()
30 .map(LoopStateMachine::take_observations)
31 .unwrap_or_default();
32 self.staged = Some(StagedFocus {
33 step_seq: context.step_seq,
34 root_kind: step.root_kind,
35 focus: step.focus.clone(),
36 });
37 Ok(step)
38 }
39
40 pub fn note_committed(&mut self, step_seq: WireU64) -> Result<(), KernelFault> {
43 if let Some(fault) = &self.poison {
44 return Err(fault.clone());
45 }
46 let Some(staged) = self.staged.take() else {
47 return Err(self.poison_with(KernelFault::new(
48 KernelFaultCode::TransactionConflict,
49 format!("step {step_seq} committed, but the driver planned no such step"),
50 )));
51 };
52 if staged.step_seq != step_seq {
53 let planned = staged.step_seq;
54 return Err(self.poison_with(KernelFault::new(
55 KernelFaultCode::TransactionConflict,
56 format!("step {step_seq} committed, but the driver planned step {planned}"),
57 )));
58 }
59 if let Some(kind) = staged.root_kind {
60 self.root_kind = Some(kind);
61 }
62 self.focus = staged.focus;
63 Ok(())
64 }
65
66 pub fn fold(&mut self, context: &PlanContext<'_>) -> Result<PlannedStep, KernelFault> {
70 let step = self.plan(context)?;
71 self.note_committed(context.step_seq)?;
72 Ok(step)
73 }
74
75 pub fn begin_nested_workflow(
88 &mut self,
89 context: &PlanContext<'_>,
90 spec: &WireSpec,
91 ) -> Result<PlannedStep, KernelFault> {
92 if let Some(fault) = &self.poison {
93 return Err(fault.clone());
94 }
95 let mut index = 0;
96 let outcome = self
97 .enter_nested_workflow(context, spec, &mut index)
98 .map_err(|refusal| match refusal {
99 SyscallRefusal::Fault(fault) => fault,
100 SyscallRefusal::Rejected(rejected) => {
103 KernelFault::new(KernelFaultCode::ResourceLimitExceeded, rejected.reason)
104 }
105 })?;
106 let step = PlannedStep {
107 root_kind: Some(RootKind::Agent),
108 focus: outcome.focus,
109 observations: self
110 .engine
111 .as_mut()
112 .map(LoopStateMachine::take_observations)
113 .unwrap_or_default(),
114 disposition: StepDisposition::Effects(EffectsDisposition {
115 effects: outcome.effects,
116 }),
117 };
118 self.staged = Some(StagedFocus {
119 step_seq: context.step_seq,
120 root_kind: step.root_kind,
121 focus: step.focus.clone(),
122 });
123 Ok(step)
124 }
125
126 pub(super) fn enter_nested_workflow(
129 &mut self,
130 context: &PlanContext<'_>,
131 spec: &WireSpec,
132 effect_index: &mut u32,
133 ) -> Result<SyscallOutcome, SyscallRefusal> {
134 let staged = self.staged.as_ref().map(|staged| staged.focus.clone());
135 let focus = staged.as_ref().unwrap_or(&self.focus);
136 let root_kind = self.root_kind;
137
138 let parent_task_id = match (root_kind, focus) {
139 (Some(RootKind::Agent), Some(ExecutionFocus::AgentTurn(turn))) => turn.task_id.clone(),
140 (Some(RootKind::Agent), Some(ExecutionFocus::WorkflowController(_))) => {
141 return Err(authority(
142 "a workflow is already the execution focus; workflows do not stack, so a \
143 second start request is refused with no spawn effect (§7.4 focus depth ≤ 1)",
144 ));
145 }
146 (Some(RootKind::Workflow), _) => {
147 return Err(authority(
148 "this operation's root is a workflow; its focus never moves, and a nested \
149 workflow start is not a transition it admits (§7.4)",
150 ));
151 }
152 _ => {
153 return Err(SyscallRefusal::Fault(KernelFault::new(
154 KernelFaultCode::InvalidLifecycle,
155 "no root has started, so there is no agent turn to suspend".to_string(),
156 )));
157 }
158 };
159
160 for node in &spec.nodes {
161 self.require_known_contract(context.config, node.run_spec.as_ref())
162 .map_err(|fault| {
163 SyscallRefusal::Rejected(SyscallRejection::new("start_workflow", fault.message))
164 })?;
165 }
166 let core_spec = build_core_spec(spec).map_err(SyscallRefusal::Fault)?;
167 let node_ids = wire_node_ids(spec);
168 let workflow_id = mint_workflow_id(&context.input.operation_id, context.step_seq);
169 self.require_effect_support(context.config, EffectKindTag::SpawnTasks)
170 .map_err(SyscallRefusal::Fault)?;
171
172 let engine = self.engine_mut().map_err(SyscallRefusal::Fault)?;
175 let disposition = engine.gate_syscall(&CoreSyscall::LoadWorkflow {
176 node_count: spec.nodes.len(),
177 });
178 if !disposition.is_allowed() {
179 return Err(SyscallRefusal::Rejected(SyscallRejection::new(
180 "start_workflow",
181 denial_reason(&disposition, "workflow authoring denied"),
182 )));
183 }
184
185 engine.set_root_workflow(false);
187 let action = engine.load_workflow_as(core_spec, parent_task_id.as_str());
188 self.node_ids = node_ids;
189 self.workflow_nodes = spec.nodes.clone();
190 self.workflow_id = Some(workflow_id.clone());
191 let disposition = self
192 .disposition_for_at(context, action, RootKind::Agent, effect_index)
193 .map_err(SyscallRefusal::Fault)?;
194 let StepDisposition::Effects(effects) = disposition else {
195 return Err(SyscallRefusal::Fault(KernelFault::new(
196 KernelFaultCode::InvalidLifecycle,
197 "entering a nested workflow cannot terminate the operation".to_string(),
198 )));
199 };
200 Ok(SyscallOutcome {
201 effects: effects.effects,
202 focus: Some(ExecutionFocus::workflow_controller(
203 workflow_id,
204 Some(parent_task_id),
205 )),
206 needs_workflow_round: false,
207 ack: None,
208 })
209 }
210
211 pub(super) fn mint_effect(
212 &self,
213 context: &PlanContext<'_>,
214 effect: EffectKind,
215 effect_index: &mut u32,
216 ) -> KernelEffect {
217 let effect_id =
218 mint_effect_id(&context.input.operation_id, context.step_seq, *effect_index);
219 *effect_index += 1;
220 KernelEffect {
221 effect_id,
222 causation_input_id: context.input.input_id.clone(),
223 effect,
224 }
225 }
226
227 pub(super) fn extend_with_action(
229 &mut self,
230 context: &PlanContext<'_>,
231 action: LoopAction,
232 root_kind: RootKind,
233 effect_index: &mut u32,
234 effects: &mut Vec<KernelEffect>,
235 ) -> Result<(), KernelFault> {
236 match self.disposition_for_at(context, action, root_kind, effect_index)? {
237 StepDisposition::Effects(published) => {
238 effects.extend(published.effects);
239 Ok(())
240 }
241 StepDisposition::Terminal(_) => Err(KernelFault::new(
242 KernelFaultCode::InvalidLifecycle,
243 "a syscall batch cannot terminate the operation; §7.12 admits effects or a \
244 terminal, never both in one step"
245 .to_string(),
246 )),
247 }
248 }
249
250 pub(super) fn plan_inner(
253 &mut self,
254 context: &PlanContext<'_>,
255 ) -> Result<PlannedStep, KernelFault> {
256 if let Some(engine) = self.engine.as_mut() {
261 engine.take_observations();
262 engine.observe_accepted_time(context.input.observed_at_ms.get());
267 let woken = engine
268 .task_table_mut()
269 .wake_expired_timers(context.input.observed_at_ms.get());
270 if !woken.is_empty() {
271 engine.observe_local_runnable_tasks();
272 }
273 }
274 match &context.input.input {
275 NormalizedPayload::ConfigureOperation(configure) => {
276 self.plan_configure(&configure.config)
277 }
278 NormalizedPayload::StartOperation(start) => {
279 self.plan_start(context, &start.entry, &start.initial_context)
280 }
281 NormalizedPayload::ResolveEffect(resolve) => self.plan_resolve_effect(context, resolve),
282 NormalizedPayload::DeliverExternalEvent(event) => {
283 self.plan_external_event(context, &event.event)
284 }
285 NormalizedPayload::HostControl(control) => {
286 self.plan_host_control(context, &control.command)
287 }
288 }
289 }
290
291 pub(super) fn plan_configure(
294 &mut self,
295 config: &ResolvedOperationConfig,
296 ) -> Result<PlannedStep, KernelFault> {
297 self.engine = Some(build_engine(config));
298 self.policy = Some(LivePolicyState::new(config.clone()));
301 Ok(PlannedStep::quiet(None, None))
302 }
303
304 pub(super) fn plan_start(
311 &mut self,
312 context: &PlanContext<'_>,
313 entry: &RootEntry,
314 initial: &InitialContext,
315 ) -> Result<PlannedStep, KernelFault> {
316 if self.root_kind.is_some() || self.staged.is_some() {
317 return Err(KernelFault::new(
318 KernelFaultCode::InvalidLifecycle,
319 "this operation already has a root; a root entry is chosen once and is immutable \
320 (§6.1.3–6.1.5)"
321 .to_string(),
322 ));
323 }
324
325 match entry {
326 RootEntry::Agent(agent) => {
327 self.require_effect_support(context.config, EffectKindTag::CallProvider)?;
328 self.require_known_contract(context.config, agent.run_spec.as_ref())?;
329 let task = runtime_task(&agent.task);
330 let run_spec = agent.run_spec.as_ref().map(agent_run_spec);
331
332 self.load_verification_contract(context.config, agent.run_spec.as_ref())?;
336 let engine = self.engine_mut()?;
337 seed_initial_context(engine, initial);
338 engine.run_spec = run_spec;
339 let action = engine.start(task);
340 let disposition = self.disposition_for(context, action, RootKind::Agent)?;
341 if !publishes(&disposition, EffectKindTag::CallProvider) {
342 return Err(KernelFault::new(
343 KernelFaultCode::InvalidLifecycle,
344 "an agent root's first committed step must publish a provider call (§7.4)"
345 .to_string(),
346 ));
347 }
348 Ok(PlannedStep {
349 root_kind: Some(RootKind::Agent),
350 focus: Some(ExecutionFocus::agent_turn(root_task_id())),
351 observations: Vec::new(),
352 disposition,
353 })
354 }
355 RootEntry::Workflow(workflow) => {
356 self.require_effect_support(context.config, EffectKindTag::SpawnTasks)?;
357 for node in &workflow.spec.nodes {
358 self.require_known_contract(context.config, node.run_spec.as_ref())?;
359 }
360 if workflow.spec.nodes.is_empty() {
361 return Err(KernelFault::new(
362 KernelFaultCode::InvalidConfig,
363 "a workflow root with no nodes has no first task to spawn; a root entry \
364 must be able to publish its first effect (§10.1)"
365 .to_string(),
366 ));
367 }
368 let core_spec = build_core_spec(&workflow.spec)?;
369 let node_ids = wire_node_ids(&workflow.spec);
370 let workflow_id = mint_workflow_id(&context.input.operation_id, context.step_seq);
371
372 let engine = self.engine_mut()?;
374 seed_initial_context(engine, initial);
375 engine.set_root_workflow(true);
378 let action = engine.load_workflow_as(core_spec, ROOT_TASK_ID);
379 self.node_ids = node_ids;
380 self.workflow_nodes = workflow.spec.nodes.clone();
381 self.workflow_id = Some(workflow_id.clone());
382 let disposition = self.disposition_for(context, action, RootKind::Workflow)?;
383 if !publishes(&disposition, EffectKindTag::SpawnTasks) {
384 return Err(KernelFault::new(
385 KernelFaultCode::InvalidLifecycle,
386 "a workflow root's first committed step must publish a task spawn, never a \
387 provider call (§10.1)"
388 .to_string(),
389 ));
390 }
391 Ok(PlannedStep {
392 root_kind: Some(RootKind::Workflow),
393 focus: Some(ExecutionFocus::workflow_controller(workflow_id, None)),
394 observations: Vec::new(),
395 disposition,
396 })
397 }
398 }
399 }
400}