1use std::collections::BTreeMap;
2use std::fmt;
3
4use super::{
5 path_starts_with, unwrap_secret_v1, ExplainResult, InspectMemory, InspectResult, IntoPath,
6 Kernel, KernelError, KernelEvent, Memory, P256PrivateKey, Path, RecomputeMode, Snapshot,
7 StoredWrappedKey, Value, WrappedSecretCleartext, WrappedSecretError, WrappedSecretOutput,
8};
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct MeTargetAst {
12 pub scheme: String,
13 pub namespace: String,
14 pub operation: String,
15 pub path: String,
16 pub raw: Option<String>,
17 pub context_raw: Option<String>,
18}
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum ExecuteValue {
22 None,
23 Value(Value),
24 Memories(Vec<Memory>),
25 Events(Vec<KernelEvent>),
26 Snapshot(Snapshot),
27 Inspect(InspectResult),
28 Explain(ExplainResult),
29 Mode(RecomputeMode),
30 KeySpaceManifest(BTreeMap<String, StoredWrappedKey>),
31 WrappedKey(Value),
32 WrappedKeyWrite {
33 envelope: Value,
34 recipient_key_id: Option<String>,
35 },
36 WrappedKeyOpenOptions {
37 recipient_key_id: Option<String>,
38 recipient_private_key: Option<P256PrivateKey>,
39 output: WrappedSecretOutput,
40 },
41 RecipientPrivateKey(Vec<u8>),
42 Bytes(Vec<u8>),
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub enum ExecuteError {
47 Kernel(KernelError),
48 EmptyTarget,
49 InvalidTarget(String),
50 MissingNamespace(String),
51 MissingNamespaceBeforeContext(String),
52 MissingOperation(String),
53 MalformedContext(String),
54 MissingBody(&'static str),
55 InvalidBody(&'static str),
56 UnsupportedNamespace(String),
57 UnsupportedSelfOperation(String),
58 UnsupportedKernelOperation(String),
59 UnsupportedKernelPath { operation: String, path: String },
60 UnsupportedKeysOperation(String),
61 EmptyRecipientKeyId,
62 EmptyKeyId(&'static str),
63 KeySpaceNotFound(String),
64 InvalidWrappedKeyEnvelope,
65 NoRecipientPrivateKey(String),
66 SelfWriteRequiresPath,
67 SelfExplainRequiresPath,
68}
69
70impl fmt::Display for ExecuteError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::Kernel(error) => write!(f, "{error}"),
74 Self::EmptyTarget => write!(f, "execute(...) received an empty me target"),
75 Self::InvalidTarget(target) => write!(
76 f,
77 "invalid me target \"{target}\": expected \":\" between namespace and operation"
78 ),
79 Self::MissingNamespace(target) => {
80 write!(f, "invalid me target \"{target}\": missing namespace")
81 }
82 Self::MissingNamespaceBeforeContext(target) => write!(
83 f,
84 "invalid me target \"{target}\": missing namespace before context"
85 ),
86 Self::MissingOperation(target) => {
87 write!(f, "invalid me target \"{target}\": missing operation")
88 }
89 Self::MalformedContext(target) => {
90 write!(f, "invalid me target \"{target}\": malformed context segment")
91 }
92 Self::MissingBody(message) => write!(f, "{message}"),
93 Self::InvalidBody(message) => write!(f, "{message}"),
94 Self::UnsupportedNamespace(namespace) => write!(
95 f,
96 "external me target \"{namespace}\" must be resolved by cleaker or monad.ai before reaching the local kernel"
97 ),
98 Self::UnsupportedSelfOperation(operation) => {
99 write!(f, "unsupported self operation: {operation}")
100 }
101 Self::UnsupportedKernelOperation(operation) => {
102 write!(f, "unsupported kernel operation: {operation}")
103 }
104 Self::UnsupportedKernelPath { operation, path } => write!(
105 f,
106 "unsupported kernel:{operation} path: {}",
107 if path.is_empty() { "<root>" } else { path }
108 ),
109 Self::UnsupportedKeysOperation(operation) => {
110 write!(f, "unsupported keys operation: {operation}")
111 }
112 Self::EmptyRecipientKeyId => {
113 write!(f, "install_recipient_key(...) requires a recipient key id")
114 }
115 Self::EmptyKeyId(message) => write!(f, "{message}"),
116 Self::KeySpaceNotFound(key_id) => write!(f, "key space \"{key_id}\" was not found"),
117 Self::InvalidWrappedKeyEnvelope => {
118 write!(f, "store_wrapped_key(...) requires a valid WrappedSecretV1 envelope")
119 }
120 Self::NoRecipientPrivateKey(key_id) => write!(
121 f,
122 "no recipient private key is available to open \"{key_id}\". Install one first or pass it inline"
123 ),
124 Self::SelfWriteRequiresPath => write!(f, "self:write requires a semantic path"),
125 Self::SelfExplainRequiresPath => write!(f, "self:explain requires a semantic path"),
126 }
127 }
128}
129
130impl std::error::Error for ExecuteError {}
131
132impl From<KernelError> for ExecuteError {
133 fn from(error: KernelError) -> Self {
134 Self::Kernel(error)
135 }
136}
137
138impl From<WrappedSecretError> for ExecuteError {
139 fn from(error: WrappedSecretError) -> Self {
140 match error {
141 WrappedSecretError::InvalidEnvelope => Self::InvalidWrappedKeyEnvelope,
142 _ => Self::InvalidBody("wrapped secret cryptographic operation failed"),
143 }
144 }
145}
146
147impl From<Value> for ExecuteValue {
148 fn from(value: Value) -> Self {
149 Self::Value(value)
150 }
151}
152
153impl From<&str> for ExecuteValue {
154 fn from(value: &str) -> Self {
155 Self::Value(Value::from(value))
156 }
157}
158
159impl From<String> for ExecuteValue {
160 fn from(value: String) -> Self {
161 Self::Value(Value::from(value))
162 }
163}
164
165impl From<bool> for ExecuteValue {
166 fn from(value: bool) -> Self {
167 Self::Value(Value::from(value))
168 }
169}
170
171impl From<u64> for ExecuteValue {
172 fn from(value: u64) -> Self {
173 Self::Value(Value::from(value))
174 }
175}
176
177impl From<i64> for ExecuteValue {
178 fn from(value: i64) -> Self {
179 Self::Value(Value::from(value))
180 }
181}
182
183impl From<f64> for ExecuteValue {
184 fn from(value: f64) -> Self {
185 Self::Value(Value::from(value))
186 }
187}
188
189impl Kernel {
190 pub fn execute(
191 &mut self,
192 raw_target: impl AsRef<str>,
193 body: Option<ExecuteValue>,
194 ) -> Result<ExecuteValue, ExecuteError> {
195 let target = parse_executable_target(raw_target.as_ref())?;
196
197 match target.namespace.as_str() {
198 "self" => self.handle_self_target(&target.operation, &target.path, body),
199 "kernel" => self.handle_kernel_target(&target.operation, &target.path, body),
200 namespace => Err(ExecuteError::UnsupportedNamespace(namespace.to_string())),
201 }
202 }
203
204 pub fn execute_ast(
205 &mut self,
206 target: MeTargetAst,
207 body: Option<ExecuteValue>,
208 ) -> Result<ExecuteValue, ExecuteError> {
209 if target.namespace.trim().is_empty() {
210 return Err(ExecuteError::MissingNamespace(
211 target.raw.unwrap_or_else(|| "<ast>".to_string()),
212 ));
213 }
214 if target.operation.trim().is_empty() {
215 return Err(ExecuteError::MissingOperation(
216 target.raw.unwrap_or_else(|| "<ast>".to_string()),
217 ));
218 }
219
220 let namespace = target.namespace.trim().to_string();
221 let operation = target.operation.trim().to_ascii_lowercase();
222 match namespace.as_str() {
223 "self" => self.handle_self_target(&operation, target.path.trim(), body),
224 "kernel" => self.handle_kernel_target(&operation, target.path.trim(), body),
225 namespace => Err(ExecuteError::UnsupportedNamespace(namespace.to_string())),
226 }
227 }
228
229 fn handle_self_target(
230 &mut self,
231 operation: &str,
232 raw_path: &str,
233 body: Option<ExecuteValue>,
234 ) -> Result<ExecuteValue, ExecuteError> {
235 if let Some(key_id) = parse_keyspace_path(raw_path) {
236 return self.handle_keyspace_target(operation, key_id.as_deref(), body);
237 }
238
239 let path = normalize_executable_path_inner(raw_path)?;
240
241 match operation {
242 "read" => Ok(self
243 .read(path.parts)
244 .cloned()
245 .map(ExecuteValue::Value)
246 .unwrap_or(ExecuteValue::None)),
247 "write" => {
248 if path.parts.is_empty() {
249 return Err(ExecuteError::SelfWriteRequiresPath);
250 }
251 let value = expect_value_body(body, "self:write requires a body payload")?;
252 self.postulate(path.parts, value.clone())?;
253 Ok(ExecuteValue::Value(value))
254 }
255 "inspect" => Ok(ExecuteValue::Inspect(self.inspect_at_path(&path.parts))),
256 "explain" => {
257 if path.parts.is_empty() {
258 return Err(ExecuteError::SelfExplainRequiresPath);
259 }
260 Ok(ExecuteValue::Explain(self.explain(path.parts)?))
261 }
262 operation => Err(ExecuteError::UnsupportedSelfOperation(
263 operation.to_string(),
264 )),
265 }
266 }
267
268 fn handle_kernel_target(
269 &mut self,
270 operation: &str,
271 raw_path: &str,
272 body: Option<ExecuteValue>,
273 ) -> Result<ExecuteValue, ExecuteError> {
274 let path = normalize_executable_path_inner(raw_path)?;
275 let key = path.key;
276
277 match operation {
278 "read" => self.handle_kernel_read(&key),
279 "drain" => self.handle_kernel_drain(&path.parts, &key),
280 "export" => self.handle_kernel_export(&key),
281 "import" => {
282 let snapshot = expect_snapshot_body(body, "kernel:import requires a payload")?;
283 *self = Kernel::hydrate(snapshot)?;
284 Ok(ExecuteValue::Snapshot(self.export_snapshot()))
285 }
286 "hydrate" => {
287 let snapshot = expect_snapshot_body(body, "kernel:hydrate requires a payload")?;
288 *self = Kernel::hydrate(snapshot)?;
289 Ok(ExecuteValue::Snapshot(self.export_snapshot()))
290 }
291 "replay" => {
292 let memories = expect_memories_body(body, "kernel:replay requires a payload")?;
293 self.replay_memories(memories)?;
294 Ok(ExecuteValue::Memories(self.memories().to_vec()))
295 }
296 "rehydrate" => {
297 let snapshot = expect_snapshot_body(body, "kernel:rehydrate requires a payload")?;
298 *self = Kernel::hydrate(snapshot)?;
299 Ok(ExecuteValue::Snapshot(self.export_snapshot()))
300 }
301 "get" => self.handle_kernel_get(&key),
302 "set" => self.handle_kernel_set(&key, body),
303 operation => Err(ExecuteError::UnsupportedKernelOperation(
304 operation.to_string(),
305 )),
306 }
307 }
308
309 fn handle_kernel_read(&self, key: &str) -> Result<ExecuteValue, ExecuteError> {
310 if let Some(path) = event_filter_path(key) {
311 return Ok(ExecuteValue::Events(self.events_matching(path)?));
312 }
313
314 match key {
315 "memory" | "memories" | "logs" => Ok(ExecuteValue::Memories(self.memories().to_vec())),
316 "events" => Ok(ExecuteValue::Events(self.events().to_vec())),
317 "snapshot" => Ok(ExecuteValue::Snapshot(self.export_snapshot())),
318 "mode" | "recompute.mode" => Ok(ExecuteValue::Mode(self.recompute_mode())),
319 _ => Err(ExecuteError::UnsupportedKernelPath {
320 operation: "read".to_string(),
321 path: key.to_string(),
322 }),
323 }
324 }
325
326 fn handle_kernel_drain(
327 &mut self,
328 parts: &[String],
329 key: &str,
330 ) -> Result<ExecuteValue, ExecuteError> {
331 if parts.first().map(String::as_str) == Some("events") && parts.len() > 1 {
332 return Ok(ExecuteValue::Events(
333 self.drain_events_matching(parts[1..].to_vec())?,
334 ));
335 }
336
337 match key {
338 "events" => Ok(ExecuteValue::Events(self.drain_events())),
339 _ => Err(ExecuteError::UnsupportedKernelPath {
340 operation: "drain".to_string(),
341 path: key.to_string(),
342 }),
343 }
344 }
345
346 fn handle_kernel_export(&self, key: &str) -> Result<ExecuteValue, ExecuteError> {
347 match key {
348 "memory" | "memories" | "logs" => Ok(ExecuteValue::Memories(self.memories().to_vec())),
349 "snapshot" => Ok(ExecuteValue::Snapshot(self.export_snapshot())),
350 _ => Err(ExecuteError::UnsupportedKernelPath {
351 operation: "export".to_string(),
352 path: key.to_string(),
353 }),
354 }
355 }
356
357 fn handle_kernel_get(&self, key: &str) -> Result<ExecuteValue, ExecuteError> {
358 match key {
359 "mode" | "recompute.mode" => Ok(ExecuteValue::Mode(self.recompute_mode())),
360 _ => Err(ExecuteError::UnsupportedKernelPath {
361 operation: "get".to_string(),
362 path: key.to_string(),
363 }),
364 }
365 }
366
367 fn handle_kernel_set(
368 &mut self,
369 key: &str,
370 body: Option<ExecuteValue>,
371 ) -> Result<ExecuteValue, ExecuteError> {
372 match key {
373 "mode" | "recompute.mode" => {
374 let mode = expect_recompute_mode_body(body)?;
375 self.set_recompute_mode(mode);
376 Ok(ExecuteValue::Mode(self.recompute_mode()))
377 }
378 _ => Err(ExecuteError::UnsupportedKernelPath {
379 operation: "set".to_string(),
380 path: key.to_string(),
381 }),
382 }
383 }
384
385 fn inspect_at_path(&self, scope: &[String]) -> InspectResult {
386 if scope.is_empty() {
387 return self.inspect();
388 }
389
390 let snapshot = self.inspect();
391 InspectResult {
392 memories: snapshot
393 .memories
394 .into_iter()
395 .filter(|memory| matches_scope(&memory.path, scope))
396 .collect::<Vec<InspectMemory>>(),
397 index: snapshot
398 .index
399 .into_iter()
400 .filter(|(path, _)| matches_scope(path, scope))
401 .collect::<BTreeMap<Path, Value>>(),
402 secret_scopes: snapshot
403 .secret_scopes
404 .into_iter()
405 .filter(|path| matches_scope(path, scope))
406 .collect(),
407 noise_scopes: snapshot
408 .noise_scopes
409 .into_iter()
410 .filter(|path| matches_scope(path, scope))
411 .collect(),
412 derivations: snapshot
413 .derivations
414 .into_iter()
415 .filter(|path| matches_scope(path, scope))
416 .collect(),
417 }
418 }
419
420 pub fn install_recipient_key(
421 &mut self,
422 recipient_key_id: &str,
423 private_key: impl Into<Vec<u8>>,
424 ) -> Result<&mut Self, ExecuteError> {
425 let key_id = recipient_key_id.trim();
426 if key_id.is_empty() {
427 return Err(ExecuteError::EmptyRecipientKeyId);
428 }
429 let private_key = P256PrivateKey::from_slice(&private_key.into()).map_err(|_| {
430 ExecuteError::InvalidBody("recipient private key must be a P-256 scalar")
431 })?;
432 self.recipient_keyring
433 .insert(key_id.to_string(), private_key);
434 Ok(self)
435 }
436
437 pub fn uninstall_recipient_key(&mut self, recipient_key_id: &str) -> &mut Self {
438 let key_id = recipient_key_id.trim();
439 if !key_id.is_empty() {
440 self.recipient_keyring.remove(key_id);
441 }
442 self
443 }
444
445 pub fn store_wrapped_key(
446 &mut self,
447 key_id: &str,
448 envelope: Value,
449 recipient_key_id: Option<String>,
450 ) -> Result<&mut Self, ExecuteError> {
451 let normalized_key_id = key_id.trim();
452 if normalized_key_id.is_empty() {
453 return Err(ExecuteError::EmptyKeyId(
454 "store_wrapped_key(...) requires a key id",
455 ));
456 }
457 ensure_wrapped_secret_v1_envelope(&envelope)?;
458 self.key_spaces.insert(
459 normalized_key_id.to_string(),
460 StoredWrappedKey {
461 envelope,
462 recipient_key_id,
463 },
464 );
465 Ok(self)
466 }
467
468 pub fn read_wrapped_key(&self, key_id: &str) -> Result<Value, ExecuteError> {
469 let Some(entry) = self.key_spaces.get(key_id) else {
470 return Err(ExecuteError::KeySpaceNotFound(key_id.to_string()));
471 };
472 Ok(entry.envelope.clone())
473 }
474
475 pub fn key_space_manifest(&self) -> BTreeMap<String, StoredWrappedKey> {
476 self.key_spaces.clone()
477 }
478
479 fn handle_keyspace_target(
480 &mut self,
481 operation: &str,
482 key_id: Option<&str>,
483 body: Option<ExecuteValue>,
484 ) -> Result<ExecuteValue, ExecuteError> {
485 match operation {
486 "read" => {
487 if let Some(key_id) = key_id {
488 Ok(ExecuteValue::WrappedKey(self.read_wrapped_key(key_id)?))
489 } else {
490 Ok(ExecuteValue::KeySpaceManifest(self.key_space_manifest()))
491 }
492 }
493 "write" => {
494 let Some(key_id) = key_id else {
495 return Err(ExecuteError::EmptyKeyId(
496 "self:write/keys requires a key id",
497 ));
498 };
499 let (envelope, recipient_key_id) = expect_wrapped_key_write_body(body)?;
500 self.store_wrapped_key(key_id, envelope.clone(), recipient_key_id)?;
501 Ok(ExecuteValue::WrappedKey(envelope))
502 }
503 "open" | "use" => {
504 let Some(key_id) = key_id else {
505 return Err(ExecuteError::EmptyKeyId("self:open/keys requires a key id"));
506 };
507 let entry = self
508 .key_spaces
509 .get(key_id)
510 .ok_or_else(|| ExecuteError::KeySpaceNotFound(key_id.to_string()))?;
511 let (inline_private_key, recipient_key_id, output) = match body {
512 Some(ExecuteValue::WrappedKeyOpenOptions {
513 recipient_key_id,
514 recipient_private_key,
515 output,
516 }) => (recipient_private_key, recipient_key_id, output),
517 Some(ExecuteValue::RecipientPrivateKey(private_key)) => (
518 Some(P256PrivateKey::from_slice(&private_key).map_err(|_| {
519 ExecuteError::InvalidBody(
520 "recipient private key must be a P-256 scalar",
521 )
522 })?),
523 None,
524 WrappedSecretOutput::Bytes,
525 ),
526 Some(_) => {
527 return Err(ExecuteError::InvalidBody(
528 "self:open/keys expects recipient private key material",
529 ))
530 }
531 None => (None, None, WrappedSecretOutput::Bytes),
532 };
533 let resolved_recipient_key_id = recipient_key_id
534 .as_ref()
535 .or(entry.recipient_key_id.as_ref());
536 let private_key = inline_private_key.or_else(|| {
537 resolved_recipient_key_id
538 .and_then(|key_id| self.recipient_keyring.get(key_id).cloned())
539 });
540 let Some(private_key) = private_key else {
541 return Err(ExecuteError::NoRecipientPrivateKey(key_id.to_string()));
542 };
543 match unwrap_secret_v1(&entry.envelope, &private_key, output)? {
544 WrappedSecretCleartext::Bytes(bytes) => Ok(ExecuteValue::Bytes(bytes)),
545 WrappedSecretCleartext::Utf8(text) => {
546 Ok(ExecuteValue::Value(Value::from(text)))
547 }
548 }
549 }
550 operation => Err(ExecuteError::UnsupportedKeysOperation(
551 operation.to_string(),
552 )),
553 }
554 }
555}
556
557#[derive(Debug, Clone, PartialEq, Eq)]
558struct ExecutablePath {
559 key: String,
560 parts: Path,
561}
562
563pub fn parse_executable_target(raw_target: &str) -> Result<MeTargetAst, ExecuteError> {
564 let raw = raw_target.trim();
565 if raw.is_empty() {
566 return Err(ExecuteError::EmptyTarget);
567 }
568
569 let without_scheme = raw.strip_prefix("me://").unwrap_or(raw);
570 let Some(colon_index) = find_top_level_colon(without_scheme) else {
571 return Err(ExecuteError::InvalidTarget(raw.to_string()));
572 };
573
574 let namespace_with_context = without_scheme[..colon_index].trim();
575 let rhs = without_scheme[colon_index + 1..].trim();
576 if namespace_with_context.is_empty() {
577 return Err(ExecuteError::MissingNamespace(raw.to_string()));
578 }
579 if rhs.is_empty() {
580 return Err(ExecuteError::MissingOperation(raw.to_string()));
581 }
582
583 let slash_index = rhs.find('/');
584 let operation = slash_index
585 .map(|index| &rhs[..index])
586 .unwrap_or(rhs)
587 .trim()
588 .to_ascii_lowercase();
589 let path = slash_index
590 .map(|index| &rhs[index + 1..])
591 .unwrap_or("")
592 .trim()
593 .to_string();
594 if operation.is_empty() {
595 return Err(ExecuteError::MissingOperation(raw.to_string()));
596 }
597
598 let (namespace, context_raw) = split_target_namespace(namespace_with_context, raw)?;
599 Ok(MeTargetAst {
600 scheme: "me".to_string(),
601 namespace,
602 operation,
603 path,
604 raw: Some(raw.to_string()),
605 context_raw,
606 })
607}
608
609pub fn normalize_executable_path(raw_path: &str) -> Result<(String, Path), ExecuteError> {
610 let path = normalize_executable_path_inner(raw_path)?;
611 Ok((path.key, path.parts))
612}
613
614pub fn parse_keyspace_path(raw_path: &str) -> Option<Option<String>> {
615 let trimmed = raw_path.trim().trim_matches('/');
616 if trimmed.is_empty() {
617 return None;
618 }
619 if trimmed == "keys" {
620 return Some(None);
621 }
622 if let Some(key_id) = trimmed.strip_prefix("keys/") {
623 return Some(non_empty_key_id(key_id));
624 }
625 trimmed.strip_prefix("keys.").map(non_empty_key_id)
626}
627
628fn normalize_executable_path_inner(raw_path: &str) -> Result<ExecutablePath, ExecuteError> {
629 let dotted = raw_path
630 .trim()
631 .trim_matches('/')
632 .replace('/', ".")
633 .trim()
634 .to_string();
635 if dotted.is_empty() {
636 return Ok(ExecutablePath {
637 key: String::new(),
638 parts: Vec::new(),
639 });
640 }
641 let parts = dotted.into_path().map_err(KernelError::InvalidPath)?;
642 Ok(ExecutablePath {
643 key: parts.join("."),
644 parts,
645 })
646}
647
648fn event_filter_path(key: &str) -> Option<String> {
649 key.strip_prefix("events.")
650 .map(str::to_string)
651 .or_else(|| key.strip_prefix("events/").map(str::to_string))
652}
653
654fn split_target_namespace(
655 namespace_with_context: &str,
656 raw_target: &str,
657) -> Result<(String, Option<String>), ExecuteError> {
658 let Some(open_index) = namespace_with_context.find('[') else {
659 return Ok((namespace_with_context.to_string(), None));
660 };
661
662 let close_index = namespace_with_context.rfind(']');
663 if close_index
664 .is_none_or(|index| index < open_index || index != namespace_with_context.len() - 1)
665 {
666 return Err(ExecuteError::MalformedContext(raw_target.to_string()));
667 }
668 let close_index = close_index.expect("checked above");
669 let namespace = namespace_with_context[..open_index].trim();
670 if namespace.is_empty() {
671 return Err(ExecuteError::MissingNamespaceBeforeContext(
672 raw_target.to_string(),
673 ));
674 }
675 let context_raw = namespace_with_context[open_index + 1..close_index].trim();
676 Ok((
677 namespace.to_string(),
678 (!context_raw.is_empty()).then(|| context_raw.to_string()),
679 ))
680}
681
682fn find_top_level_colon(value: &str) -> Option<usize> {
683 let mut bracket_depth = 0_u32;
684 let mut quote = None;
685 let mut escaped = false;
686
687 for (index, ch) in value.char_indices() {
688 if escaped {
689 escaped = false;
690 continue;
691 }
692 if let Some(expected_quote) = quote {
693 if ch == '\\' {
694 escaped = true;
695 } else if ch == expected_quote {
696 quote = None;
697 }
698 continue;
699 }
700
701 match ch {
702 '"' | '\'' => quote = Some(ch),
703 '[' => bracket_depth += 1,
704 ']' => bracket_depth = bracket_depth.saturating_sub(1),
705 ':' if bracket_depth == 0 => return Some(index),
706 _ => {}
707 }
708 }
709
710 None
711}
712
713fn expect_value_body(
714 body: Option<ExecuteValue>,
715 missing_message: &'static str,
716) -> Result<Value, ExecuteError> {
717 match body {
718 Some(ExecuteValue::Value(value)) => Ok(value),
719 None => Err(ExecuteError::MissingBody(missing_message)),
720 _ => Err(ExecuteError::InvalidBody(
721 "expected a semantic value payload",
722 )),
723 }
724}
725
726fn expect_snapshot_body(
727 body: Option<ExecuteValue>,
728 missing_message: &'static str,
729) -> Result<Snapshot, ExecuteError> {
730 match body {
731 Some(ExecuteValue::Snapshot(snapshot)) => Ok(snapshot),
732 None => Err(ExecuteError::MissingBody(missing_message)),
733 _ => Err(ExecuteError::InvalidBody("expected a snapshot payload")),
734 }
735}
736
737fn expect_memories_body(
738 body: Option<ExecuteValue>,
739 missing_message: &'static str,
740) -> Result<Vec<Memory>, ExecuteError> {
741 match body {
742 Some(ExecuteValue::Memories(memories)) => Ok(memories),
743 None => Err(ExecuteError::MissingBody(missing_message)),
744 _ => Err(ExecuteError::InvalidBody("expected a memory log payload")),
745 }
746}
747
748fn expect_recompute_mode_body(body: Option<ExecuteValue>) -> Result<RecomputeMode, ExecuteError> {
749 match body {
750 Some(ExecuteValue::Mode(mode)) => Ok(mode),
751 Some(ExecuteValue::Value(Value::String(mode))) if mode == "eager" => {
752 Ok(RecomputeMode::Eager)
753 }
754 Some(ExecuteValue::Value(Value::String(mode))) if mode == "lazy" => Ok(RecomputeMode::Lazy),
755 None => Err(ExecuteError::MissingBody("kernel:set requires a payload")),
756 _ => Err(ExecuteError::InvalidBody(
757 "kernel:set/recompute.mode only accepts \"eager\" or \"lazy\"",
758 )),
759 }
760}
761
762fn expect_wrapped_key_write_body(
763 body: Option<ExecuteValue>,
764) -> Result<(Value, Option<String>), ExecuteError> {
765 match body {
766 Some(ExecuteValue::WrappedKeyWrite {
767 envelope,
768 recipient_key_id,
769 }) => {
770 ensure_wrapped_secret_v1_envelope(&envelope)?;
771 Ok((envelope, recipient_key_id))
772 }
773 Some(ExecuteValue::WrappedKey(envelope)) | Some(ExecuteValue::Value(envelope)) => {
774 ensure_wrapped_secret_v1_envelope(&envelope)?;
775 Ok((envelope, None))
776 }
777 None => Err(ExecuteError::MissingBody(
778 "self:write/keys requires a payload",
779 )),
780 _ => Err(ExecuteError::InvalidBody(
781 "self:write/keys expects a wrapped key envelope payload",
782 )),
783 }
784}
785
786fn ensure_wrapped_secret_v1_envelope(envelope: &Value) -> Result<(), ExecuteError> {
787 let Value::Object(object) = envelope else {
788 return Err(ExecuteError::InvalidWrappedKeyEnvelope);
789 };
790 if object.get("version") != Some(&Value::from(1_u64)) {
791 return Err(ExecuteError::InvalidWrappedKeyEnvelope);
792 }
793 if !object.contains_key("kid") || !object.contains_key("encryption") {
794 return Err(ExecuteError::InvalidWrappedKeyEnvelope);
795 }
796 Ok(())
797}
798
799fn non_empty_key_id(key_id: &str) -> Option<String> {
800 let key_id = key_id.trim();
801 (!key_id.is_empty()).then(|| key_id.to_string())
802}
803
804fn matches_scope(candidate: &[String], scope: &[String]) -> bool {
805 candidate == scope || path_starts_with(candidate, scope)
806}