arkhe_kernel/state/op.rs
1//! `Op` — kernel-level effect payload (ABI sub-enums).
2//!
3//! Each variant is the "what to do" intent; `Effect<S, 'i>` wraps it with
4//! authorization state, instance brand, and originating principal.
5//! Ships a flat enum; sub-categorization is reserved (deferred).
6
7use bytes::Bytes;
8use serde::{Deserialize, Serialize};
9
10use crate::abi::{EntityId, InstanceId, Principal, RouteId, Tick, TypeCode};
11
12/// Kernel-level effect intent. Each variant is "what to do"; the
13/// kernel wraps it in [`Effect`](crate::state::Effect) for
14/// authorization, then dispatches into the per-step `StepStage`.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub enum Op {
18 /// Register a new entity with the given id and owner.
19 SpawnEntity {
20 /// Entity id to register.
21 id: EntityId,
22 /// Owning principal — recorded in `EntityMeta`.
23 owner: Principal,
24 },
25 /// Remove an entity from the instance.
26 DespawnEntity {
27 /// Entity to remove.
28 id: EntityId,
29 },
30 /// Attach (or replace) a component on `entity` under `type_code`.
31 SetComponent {
32 /// Entity that owns the component.
33 entity: EntityId,
34 /// Component type discriminant.
35 type_code: TypeCode,
36 /// Canonical-postcard bytes of the component value.
37 bytes: Bytes,
38 /// Approximate size in bytes — used by the resource ledger
39 /// (memory budget enforcement).
40 size: u64,
41 },
42 /// Detach a component from `entity`.
43 RemoveComponent {
44 /// Entity to detach from.
45 entity: EntityId,
46 /// Component type discriminant.
47 type_code: TypeCode,
48 /// Approximate size in bytes — must match the original
49 /// `SetComponent` size for the ledger to balance.
50 size: u64,
51 },
52 /// Emit a domain event. Surfaces as `KernelEvent::DomainEventEmitted`
53 /// to observers post-commit.
54 EmitEvent {
55 /// Optional originating entity.
56 actor: Option<EntityId>,
57 /// Event type discriminant.
58 event_type_code: TypeCode,
59 /// Canonical-postcard bytes of the event payload.
60 event_bytes: Bytes,
61 },
62 /// Enqueue another action for a future tick. The scheduled action
63 /// inherits the *scheduling* action's principal (captured at dispatch)
64 /// and a `caps_ceiling` equal to the parent's effective capabilities —
65 /// privilege can only narrow across a schedule, never widen, so a domain
66 /// action cannot mint a more-privileged future action (no time-shifted
67 /// escalation). Kernel-internal System-origin scheduling goes through a
68 /// kernel API, not this domain Op.
69 ScheduleAction {
70 /// Tick at which the scheduled action becomes due.
71 at: Tick,
72 /// Optional originating entity.
73 actor: Option<EntityId>,
74 /// Type code of the scheduled action.
75 action_type_code: TypeCode,
76 /// Canonical-postcard bytes of the scheduled action.
77 action_bytes: Bytes,
78 },
79 /// Cross-instance signal. Routed by the kernel to the target
80 /// instance's IPC queue post-commit.
81 SendSignal {
82 /// Receiving instance.
83 target: InstanceId,
84 /// Route discriminant — the receiving instance dispatches by
85 /// this id.
86 route: RouteId,
87 /// Canonical-postcard bytes of the signal payload.
88 payload: Bytes,
89 },
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn op_variants_clone() {
98 let id = EntityId::new(1).unwrap();
99 let _ = Op::SpawnEntity {
100 id,
101 owner: Principal::System,
102 }
103 .clone();
104 let _ = Op::DespawnEntity { id }.clone();
105 let _ = Op::SetComponent {
106 entity: id,
107 type_code: TypeCode(1),
108 bytes: Bytes::from_static(b"x"),
109 size: 1,
110 }
111 .clone();
112 let _ = Op::RemoveComponent {
113 entity: id,
114 type_code: TypeCode(1),
115 size: 1,
116 }
117 .clone();
118 let _ = Op::EmitEvent {
119 actor: Some(id),
120 event_type_code: TypeCode(2),
121 event_bytes: Bytes::new(),
122 }
123 .clone();
124 let _ = Op::ScheduleAction {
125 at: Tick(0),
126 actor: None,
127 action_type_code: TypeCode(3),
128 action_bytes: Bytes::new(),
129 }
130 .clone();
131 let _ = Op::SendSignal {
132 target: InstanceId::new(1).unwrap(),
133 route: RouteId(1),
134 payload: Bytes::new(),
135 }
136 .clone();
137 }
138}