deepstrike_core/runtime/kernel/wire/
binding.rs1use super::checkpoint::{
15 CheckpointCandidate, KernelCheckpoint, LogicalKernelState, LogicalStateProjection,
16};
17use super::config::ConfigDefaults;
18use super::driver::{CanonicalOperationDriver, PlannedStep};
19use super::effect::{Digest, KernelEffect};
20use super::envelope::{
21 OperationLifecycle, WireEnvelope, WireRejection, WireRejectionKind, decode_envelope_json,
22};
23use super::fault::{
24 KernelFault, KernelFaultCode, KernelPreparation, PrepareToken, RejectedTransition,
25};
26use super::record::{KernelRecord, RecordPreparation};
27use super::restore::{RestoreCost, restore_operation};
28use super::terminal::KernelTerminal;
29use super::transaction::{
30 CheckpointBoundary, CommittedTransition, DurableHead, InMemoryRecordIndex, KernelTransaction,
31 TailUsage,
32};
33
34type CanonicalTransaction = KernelTransaction<PlannedStep, InMemoryRecordIndex>;
35
36enum DriverRestorePoint {
37 Fresh,
38 Logical(Box<LogicalKernelState>),
39}
40
41pub struct CanonicalKernel {
47 defaults: ConfigDefaults,
48 transaction: CanonicalTransaction,
49 driver: CanonicalOperationDriver,
50 before_candidate: Option<DriverRestorePoint>,
51}
52
53impl std::fmt::Debug for CanonicalKernel {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("CanonicalKernel")
56 .field("operation_id", &self.transaction.operation_id())
57 .field("head", &self.transaction.head())
58 .field("lifecycle", &self.transaction.lifecycle())
59 .field("has_candidate", &self.transaction.has_candidate())
60 .finish()
61 }
62}
63
64impl Default for CanonicalKernel {
65 fn default() -> Self {
66 Self::new(ConfigDefaults::default())
67 }
68}
69
70impl CanonicalKernel {
71 pub fn new(defaults: ConfigDefaults) -> Self {
73 Self {
74 transaction: KernelTransaction::new(defaults.clone(), InMemoryRecordIndex::new()),
75 driver: CanonicalOperationDriver::new(),
76 defaults,
77 before_candidate: None,
78 }
79 }
80
81 pub fn prepare_json(&mut self, input_json: &str) -> RecordPreparation<PlannedStep> {
86 match decode_envelope_json(input_json, &self.defaults.bootstrap_limits) {
87 Ok(envelope) => self.prepare(&envelope),
88 Err(rejection) => KernelPreparation::Rejected(RejectedTransition {
89 fault: rejection_fault(rejection),
90 }),
91 }
92 }
93
94 pub fn prepare(&mut self, envelope: &WireEnvelope) -> RecordPreparation<PlannedStep> {
96 if self.before_candidate.is_some() {
99 let Self {
100 transaction,
101 driver,
102 ..
103 } = self;
104 return transaction.prepare(envelope, |context| driver.plan(context));
105 }
106
107 let restore_point = match self.capture_driver_restore_point() {
108 Ok(restore_point) => restore_point,
109 Err(fault) => {
110 return KernelPreparation::Rejected(RejectedTransition { fault });
111 }
112 };
113 let preparation = {
114 let Self {
115 transaction,
116 driver,
117 ..
118 } = self;
119 transaction.prepare(envelope, |context| driver.plan(context))
120 };
121
122 if matches!(preparation, KernelPreparation::Prepared(_)) {
123 self.before_candidate = Some(restore_point);
124 return preparation;
125 }
126
127 if let Err(fault) = self.restore_driver(restore_point) {
131 return KernelPreparation::Rejected(RejectedTransition { fault });
132 }
133 preparation
134 }
135
136 pub fn commit(
138 &mut self,
139 token: &PrepareToken,
140 appended_head: &Digest,
141 ) -> Result<CommittedTransition<PlannedStep>, KernelFault> {
142 let committed = self.transaction.commit(token, appended_head)?;
143 let step_seq = committed.step_seq;
144 self.before_candidate = None;
145 self.driver.note_committed(step_seq)?;
146 Ok(committed)
147 }
148
149 pub fn abort(&mut self, token: &PrepareToken) -> Result<KernelRecord, KernelFault> {
151 let record = self.transaction.abort(token)?;
152 let restore_point = self.before_candidate.take().ok_or_else(|| {
153 KernelFault::new(
154 KernelFaultCode::TransactionConflict,
155 "the transaction aborted a candidate but the canonical driver has no restore point",
156 )
157 })?;
158 self.restore_driver(restore_point)?;
159 Ok(record)
160 }
161
162 pub fn restore(
167 &mut self,
168 checkpoint: Option<&KernelCheckpoint>,
169 records: &[KernelRecord],
170 ) -> Result<RestoreCost, KernelFault> {
171 let restored = restore_operation(
172 checkpoint,
173 records,
174 self.defaults.clone(),
175 InMemoryRecordIndex::from_records(records),
176 )?;
177 let cost = restored.cost;
178 self.transaction = restored.transaction;
179 self.driver = restored.driver;
180 self.before_candidate = None;
181 Ok(cost)
182 }
183
184 pub fn restore_bytes(
186 &mut self,
187 checkpoint_bytes: Option<&[u8]>,
188 record_bytes: &[Vec<u8>],
189 ) -> Result<RestoreCost, KernelFault> {
190 let checkpoint = checkpoint_bytes
191 .map(KernelCheckpoint::from_checkpoint_bytes)
192 .transpose()
193 .map_err(|error| error.fault())?;
194 let records = record_bytes
195 .iter()
196 .map(|bytes| {
197 KernelRecord::from_record_bytes(bytes)
198 .map_err(|error| KernelFault::new(error.code(), error.message().to_string()))
199 })
200 .collect::<Result<Vec<_>, _>>()?;
201 self.restore(checkpoint.as_ref(), &records)
202 }
203
204 pub fn checkpoint_candidate(&self) -> Result<CheckpointCandidate, KernelFault> {
206 self.transaction
207 .checkpoint_candidate(self.driver.project_logical_state())
208 }
209
210 pub fn checkpoint_rebase(
212 &self,
213 base: &KernelCheckpoint,
214 ) -> Result<CheckpointCandidate, KernelFault> {
215 self.transaction
216 .checkpoint_rebase(&base.boundary(), base.logical_state().clone())
217 }
218
219 pub fn note_checkpoint_acked(
221 &mut self,
222 boundary: &CheckpointBoundary,
223 ) -> Result<TailUsage, KernelFault> {
224 self.transaction.note_checkpoint_acked(boundary)
225 }
226
227 pub fn head(&self) -> Option<DurableHead> {
228 self.transaction.head()
229 }
230
231 pub fn lifecycle(&self) -> OperationLifecycle {
232 self.transaction.lifecycle()
233 }
234
235 pub fn pending_effects(&self) -> impl Iterator<Item = &KernelEffect> {
236 self.transaction.pending_effects()
237 }
238
239 pub fn terminal(&self) -> Option<&KernelTerminal> {
240 self.transaction.terminal()
241 }
242
243 pub fn attempt_id(&self, task_id: &str) -> Option<super::scalar::AttemptId> {
245 self.driver.attempt_id(task_id).cloned()
246 }
247
248 pub fn turn(&self) -> u32 {
250 self.driver.engine().map_or(0, |engine| engine.turn)
251 }
252
253 pub fn recovery_content_bytes(&self) -> Option<usize> {
255 self.driver.engine().map(|engine| {
256 let tokens = engine
257 .ctx
258 .config
259 .recovery_content_tokens(engine.ctx.max_tokens);
260 engine.ctx.engine.token_budget_to_bytes(tokens)
261 })
262 }
263
264 pub fn preserved_refs(&self) -> Vec<String> {
266 self.driver
267 .engine()
268 .map(|engine| engine.ctx.partitions.task_state.preserved_refs.clone())
269 .unwrap_or_default()
270 }
271
272 pub fn count_tokens(&self, text: &str) -> Option<u32> {
274 self.driver
275 .engine()
276 .map(|engine| engine.ctx.engine.count(text))
277 }
278
279 pub fn local_subagents_spawned(&self) -> u32 {
281 self.driver
282 .engine()
283 .map_or(0, |engine| engine.local_subagents_spawned())
284 }
285
286 pub fn new_messages(&self) -> Vec<crate::types::message::Message> {
288 self.driver
289 .engine()
290 .map(|engine| engine.drain_new_messages())
291 .unwrap_or_default()
292 }
293
294 pub fn poison(&self) -> Option<&KernelFault> {
295 self.transaction.poison().or_else(|| self.driver.poison())
296 }
297
298 fn capture_driver_restore_point(&self) -> Result<DriverRestorePoint, KernelFault> {
299 if self.transaction.config().is_none() {
300 return Ok(DriverRestorePoint::Fresh);
301 }
302 let LogicalStateProjection {
303 root_kind,
304 focus,
305 syscall,
306 scheduler,
307 context_vm,
308 } = self.driver.project_logical_state();
309 let transition = self
310 .transaction
311 .transition_state_for_restore(root_kind, focus)?;
312 Ok(DriverRestorePoint::Logical(Box::new(LogicalKernelState {
313 transition,
314 syscall,
315 scheduler,
316 context_vm,
317 })))
318 }
319
320 fn restore_driver(&mut self, restore_point: DriverRestorePoint) -> Result<(), KernelFault> {
321 self.driver = match restore_point {
322 DriverRestorePoint::Fresh => CanonicalOperationDriver::new(),
323 DriverRestorePoint::Logical(state) => CanonicalOperationDriver::restore_logical_state(
324 &state.transition.resolved_config,
325 &state,
326 )?,
327 };
328 Ok(())
329 }
330}
331
332fn rejection_fault(rejection: WireRejection) -> KernelFault {
333 let code = match rejection.kind {
334 WireRejectionKind::PolicyViolation => KernelFaultCode::InvalidConfig,
335 _ => KernelFaultCode::MalformedEnvelope,
336 };
337 KernelFault::new(code, rejection.message)
338}
339
340#[cfg(test)]
341mod tests {
342 use serde_json::Value;
343
344 use super::CanonicalKernel;
345 use crate::runtime::kernel::wire::{
346 KernelFaultCode, KernelPreparation, PrepareToken, WireEnvelope,
347 };
348
349 fn golden_agent_root() -> Value {
350 serde_json::from_str(include_str!(
351 "../../../../../../tests/fixtures/kernel-wire/golden_lifecycle_agent_root.json"
352 ))
353 .expect("golden fixture")
354 }
355
356 fn commit_input(kernel: &mut CanonicalKernel, input: &str) {
357 let prepared = kernel.prepare_json(input);
358 let KernelPreparation::Prepared(prepared) = prepared else {
359 panic!("expected prepared transition");
360 };
361 kernel
362 .commit(&prepared.token, prepared.record.record_digest())
363 .expect("commit");
364 }
365
366 #[test]
367 fn canonical_kernel_produces_the_shared_genesis_record() {
368 let fixture = golden_agent_root();
369 let mut kernel = CanonicalKernel::default();
370 let preparation = kernel.prepare_json(&fixture["links"][0]["envelope"].to_string());
371 let KernelPreparation::Prepared(prepared) = preparation else {
372 panic!("golden envelope must prepare");
373 };
374
375 assert_eq!(
376 prepared.record.record_digest().as_str(),
377 fixture["genesis_digest"].as_str().unwrap()
378 );
379 assert_eq!(
380 std::str::from_utf8(prepared.record.record_bytes().as_slice()).unwrap(),
381 serde_json::to_string(&fixture["links"][0]["record"]).unwrap()
382 );
383 }
384
385 #[test]
386 fn abort_restores_the_driver_before_the_next_prepare() {
387 let fixture = golden_agent_root();
388 let mut kernel = CanonicalKernel::default();
389 commit_input(&mut kernel, &fixture["links"][0]["envelope"].to_string());
390
391 let start = fixture["links"][1]["envelope"].clone();
392 let first = kernel.prepare_json(&start.to_string());
393 let KernelPreparation::Prepared(first) = first else {
394 panic!("start must prepare");
395 };
396 let first_digest = first.record.record_digest().clone();
397 kernel.abort(&first.token).expect("abort before append");
398
399 let second = kernel.prepare_json(&start.to_string());
400 let KernelPreparation::Prepared(second) = second else {
401 panic!("the same input must prepare after abort");
402 };
403 assert_eq!(second.record.record_digest(), &first_digest);
404 }
405
406 #[test]
407 fn malformed_and_unknown_envelopes_are_structured_rejections() {
408 let mut kernel = CanonicalKernel::default();
409 let malformed = kernel.prepare_json("{");
410 assert_eq!(
411 malformed.fault().map(|fault| fault.code),
412 Some(KernelFaultCode::MalformedEnvelope)
413 );
414
415 let fixture = golden_agent_root();
416 let mut unknown = fixture["links"][0]["envelope"].clone();
417 unknown
418 .as_object_mut()
419 .unwrap()
420 .insert("session_id".to_string(), Value::String("host-only".into()));
421 let rejected = kernel.prepare_json(&unknown.to_string());
422 assert_eq!(
423 rejected.fault().map(|fault| fault.code),
424 Some(KernelFaultCode::MalformedEnvelope)
425 );
426 }
427
428 #[test]
429 fn restore_replaces_the_same_typed_handle() {
430 let fixture = golden_agent_root();
431 let mut kernel = CanonicalKernel::default();
432 commit_input(&mut kernel, &fixture["links"][0]["envelope"].to_string());
433 let checkpoint = kernel
434 .checkpoint_candidate()
435 .expect("configured operation checkpoints")
436 .decode()
437 .expect("checkpoint verifies");
438
439 let start: WireEnvelope =
440 serde_json::from_value(fixture["links"][1]["envelope"].clone()).unwrap();
441 let KernelPreparation::Prepared(prepared) = kernel.prepare(&start) else {
442 panic!("start prepares");
443 };
444 let post_checkpoint_record = prepared.record.clone();
445 let expected_head = prepared.record.record_digest().clone();
446 kernel
447 .commit(&prepared.token, prepared.record.record_digest())
448 .unwrap();
449
450 let handle_address = std::ptr::addr_of!(kernel);
451 kernel
452 .restore(Some(&checkpoint), &[post_checkpoint_record])
453 .expect("restore");
454 assert_eq!(std::ptr::addr_of!(kernel), handle_address);
455 assert_eq!(kernel.head().unwrap().digest, expected_head);
456
457 let no_candidate = PrepareToken::new("no-candidate").unwrap();
458 assert!(kernel.abort(&no_candidate).is_err());
459 }
460}