1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum BuiltinExposure {
11 Undeclared,
14 PureGlobal,
16 CapabilityFunction { authority_argument: u16 },
20 HarnessMethod {
22 capability: CapabilityId,
23 method: &'static str,
24 },
25 PrivilegedWire,
28 StdlibInternal,
32 RuntimeInternal,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub enum CapabilityId {
39 Stdio,
40 Term,
41 Clock,
42 Fs,
43 Env,
44 Random,
45 Net,
46 Process,
47 Channels,
48 System,
49 Secrets,
50 Llm,
51 Agent,
52 Tenant,
53 Auth,
54 Observability,
55 Verdict,
56 Tools,
57 Ast,
58 CodeIndex,
59 Computer,
60 Embed,
61 Memory,
62 Sqlite,
63 Postgres,
64 FsWatch,
65 HostLease,
66 Scanner,
67 SecretStore,
68 TerminalSession,
69 Rules,
70 Lint,
71 Runtime,
72 Interaction,
73 Project,
74 Dashboard,
75 Workspace,
76 MergeCaptain,
77 Session,
78 Permission,
79 Text,
80 Lsp,
81 Credentials,
82 PrMonitor,
83 Workflow,
84 Testing,
85}
86
87impl CapabilityId {
88 pub const fn variant_name(self) -> &'static str {
90 match self {
91 Self::Stdio => "Stdio",
92 Self::Term => "Term",
93 Self::Clock => "Clock",
94 Self::Fs => "Fs",
95 Self::Env => "Env",
96 Self::Random => "Random",
97 Self::Net => "Net",
98 Self::Process => "Process",
99 Self::Channels => "Channels",
100 Self::System => "System",
101 Self::Secrets => "Secrets",
102 Self::Llm => "Llm",
103 Self::Agent => "Agent",
104 Self::Tenant => "Tenant",
105 Self::Auth => "Auth",
106 Self::Observability => "Observability",
107 Self::Verdict => "Verdict",
108 Self::Tools => "Tools",
109 Self::Ast => "Ast",
110 Self::CodeIndex => "CodeIndex",
111 Self::Computer => "Computer",
112 Self::Embed => "Embed",
113 Self::Memory => "Memory",
114 Self::Sqlite => "Sqlite",
115 Self::Postgres => "Postgres",
116 Self::FsWatch => "FsWatch",
117 Self::HostLease => "HostLease",
118 Self::Scanner => "Scanner",
119 Self::SecretStore => "SecretStore",
120 Self::TerminalSession => "TerminalSession",
121 Self::Rules => "Rules",
122 Self::Lint => "Lint",
123 Self::Runtime => "Runtime",
124 Self::Interaction => "Interaction",
125 Self::Project => "Project",
126 Self::Dashboard => "Dashboard",
127 Self::Workspace => "Workspace",
128 Self::MergeCaptain => "MergeCaptain",
129 Self::Session => "Session",
130 Self::Permission => "Permission",
131 Self::Text => "Text",
132 Self::Lsp => "Lsp",
133 Self::Credentials => "Credentials",
134 Self::PrMonitor => "PrMonitor",
135 Self::Workflow => "Workflow",
136 Self::Testing => "Testing",
137 }
138 }
139
140 pub const ALL: &'static [Self] = &[
142 Self::Stdio,
143 Self::Term,
144 Self::Clock,
145 Self::Fs,
146 Self::Env,
147 Self::Random,
148 Self::Net,
149 Self::Process,
150 Self::Channels,
151 Self::System,
152 Self::Secrets,
153 Self::Llm,
154 Self::Agent,
155 Self::Tenant,
156 Self::Auth,
157 Self::Observability,
158 Self::Verdict,
159 Self::Tools,
160 Self::Ast,
161 Self::CodeIndex,
162 Self::Computer,
163 Self::Embed,
164 Self::Memory,
165 Self::Sqlite,
166 Self::Postgres,
167 Self::FsWatch,
168 Self::HostLease,
169 Self::Scanner,
170 Self::SecretStore,
171 Self::TerminalSession,
172 Self::Rules,
173 Self::Lint,
174 Self::Runtime,
175 Self::Interaction,
176 Self::Project,
177 Self::Dashboard,
178 Self::Workspace,
179 Self::MergeCaptain,
180 Self::Session,
181 Self::Permission,
182 Self::Text,
183 Self::Lsp,
184 Self::Credentials,
185 Self::PrMonitor,
186 Self::Workflow,
187 Self::Testing,
188 ];
189
190 pub const fn field_name(self) -> &'static str {
192 match self {
193 Self::Stdio => "stdio",
194 Self::Term => "term",
195 Self::Clock => "clock",
196 Self::Fs => "fs",
197 Self::Env => "env",
198 Self::Random => "random",
199 Self::Net => "net",
200 Self::Process => "process",
201 Self::Channels => "channels",
202 Self::System => "system",
203 Self::Secrets => "secrets",
204 Self::Llm => "llm",
205 Self::Agent => "agent",
206 Self::Tenant => "tenant",
207 Self::Auth => "auth",
208 Self::Observability => "obs",
209 Self::Verdict => "verdict",
210 Self::Tools => "tools",
211 Self::Ast => "ast",
212 Self::CodeIndex => "code_index",
213 Self::Computer => "computer",
214 Self::Embed => "embed",
215 Self::Memory => "memory",
216 Self::Sqlite => "sqlite",
217 Self::Postgres => "postgres",
218 Self::FsWatch => "fs_watch",
219 Self::HostLease => "host_lease",
220 Self::Scanner => "scanner",
221 Self::SecretStore => "secret_store",
222 Self::TerminalSession => "terminal",
223 Self::Rules => "rules",
224 Self::Lint => "lint",
225 Self::Runtime => "runtime",
226 Self::Interaction => "interaction",
227 Self::Project => "project",
228 Self::Dashboard => "dashboard",
229 Self::Workspace => "workspace",
230 Self::MergeCaptain => "merge_captain",
231 Self::Session => "session",
232 Self::Permission => "permission",
233 Self::Text => "text",
234 Self::Lsp => "lsp",
235 Self::Credentials => "credentials",
236 Self::PrMonitor => "pr_monitor",
237 Self::Workflow => "workflow",
238 Self::Testing => "testing",
239 }
240 }
241
242 pub const fn type_name(self) -> &'static str {
244 match self {
245 Self::Stdio => "HarnessStdio",
246 Self::Term => "HarnessTerm",
247 Self::Clock => "HarnessClock",
248 Self::Fs => "HarnessFs",
249 Self::Env => "HarnessEnv",
250 Self::Random => "HarnessRandom",
251 Self::Net => "HarnessNet",
252 Self::Process => "HarnessProcess",
253 Self::Channels => "HarnessChannels",
254 Self::System => "HarnessSystem",
255 Self::Secrets => "HarnessSecrets",
256 Self::Llm => "HarnessLlm",
257 Self::Agent => "HarnessAgent",
258 Self::Tenant => "HarnessTenant",
259 Self::Auth => "HarnessAuth",
260 Self::Observability => "HarnessObs",
261 Self::Verdict => "HarnessVerdict",
262 Self::Tools => "HarnessTools",
263 Self::Ast => "HarnessAst",
264 Self::CodeIndex => "HarnessCodeIndex",
265 Self::Computer => "HarnessComputer",
266 Self::Embed => "HarnessEmbed",
267 Self::Memory => "HarnessMemory",
268 Self::Sqlite => "HarnessSqlite",
269 Self::Postgres => "HarnessPostgres",
270 Self::FsWatch => "HarnessFsWatch",
271 Self::HostLease => "HarnessHostLease",
272 Self::Scanner => "HarnessScanner",
273 Self::SecretStore => "HarnessSecretStore",
274 Self::TerminalSession => "HarnessTerminalSession",
275 Self::Rules => "HarnessRules",
276 Self::Lint => "HarnessLint",
277 Self::Runtime => "HarnessRuntime",
278 Self::Interaction => "HarnessInteraction",
279 Self::Project => "HarnessProject",
280 Self::Dashboard => "HarnessDashboard",
281 Self::Workspace => "HarnessWorkspace",
282 Self::MergeCaptain => "HarnessMergeCaptain",
283 Self::Session => "HarnessSession",
284 Self::Permission => "HarnessPermission",
285 Self::Text => "HarnessText",
286 Self::Lsp => "HarnessLsp",
287 Self::Credentials => "HarnessCredentials",
288 Self::PrMonitor => "HarnessPrMonitor",
289 Self::Workflow => "HarnessWorkflow",
290 Self::Testing => "HarnessTesting",
291 }
292 }
293
294 pub const fn from_field_name(name: &str) -> Option<Self> {
296 match name.as_bytes() {
297 b"stdio" => Some(Self::Stdio),
298 b"term" => Some(Self::Term),
299 b"clock" => Some(Self::Clock),
300 b"fs" => Some(Self::Fs),
301 b"env" => Some(Self::Env),
302 b"random" => Some(Self::Random),
303 b"net" => Some(Self::Net),
304 b"process" => Some(Self::Process),
305 b"channels" => Some(Self::Channels),
306 b"system" => Some(Self::System),
307 b"secrets" => Some(Self::Secrets),
308 b"llm" => Some(Self::Llm),
309 b"agent" => Some(Self::Agent),
310 b"tenant" => Some(Self::Tenant),
311 b"auth" => Some(Self::Auth),
312 b"obs" => Some(Self::Observability),
313 b"verdict" => Some(Self::Verdict),
314 b"tools" => Some(Self::Tools),
315 b"ast" => Some(Self::Ast),
316 b"code_index" => Some(Self::CodeIndex),
317 b"computer" => Some(Self::Computer),
318 b"embed" => Some(Self::Embed),
319 b"memory" => Some(Self::Memory),
320 b"sqlite" => Some(Self::Sqlite),
321 b"postgres" => Some(Self::Postgres),
322 b"fs_watch" => Some(Self::FsWatch),
323 b"host_lease" => Some(Self::HostLease),
324 b"scanner" => Some(Self::Scanner),
325 b"secret_store" => Some(Self::SecretStore),
326 b"terminal" => Some(Self::TerminalSession),
327 b"rules" => Some(Self::Rules),
328 b"lint" => Some(Self::Lint),
329 b"runtime" => Some(Self::Runtime),
330 b"interaction" => Some(Self::Interaction),
331 b"project" => Some(Self::Project),
332 b"dashboard" => Some(Self::Dashboard),
333 b"workspace" => Some(Self::Workspace),
334 b"merge_captain" => Some(Self::MergeCaptain),
335 b"session" => Some(Self::Session),
336 b"permission" => Some(Self::Permission),
337 b"text" => Some(Self::Text),
338 b"lsp" => Some(Self::Lsp),
339 b"credentials" => Some(Self::Credentials),
340 b"pr_monitor" => Some(Self::PrMonitor),
341 b"workflow" => Some(Self::Workflow),
342 b"testing" => Some(Self::Testing),
343 _ => None,
344 }
345 }
346
347 pub fn from_type_name(name: &str) -> Option<Self> {
348 Self::ALL
349 .iter()
350 .copied()
351 .find(|capability| capability.type_name() == name)
352 }
353
354 pub fn from_host_namespace(namespace: &str) -> Option<Self> {
363 let wanted = wire_identifier_key(namespace);
364 Self::ALL
365 .iter()
366 .copied()
367 .find(|capability| wire_identifier_key(capability.field_name()) == wanted)
368 }
369}
370
371pub fn wire_identifier_key(value: &str) -> String {
377 value
378 .chars()
379 .filter(|character| *character != '_')
380 .map(|character| character.to_ascii_lowercase())
381 .collect()
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
386pub enum EffectKind {
387 Stdio,
388 Fs,
389 Env,
390 Clock,
391 Random,
392 Network,
393 Process,
394 Llm,
395 Tool,
396 Mcp,
397 Host,
398 Authority,
399 Worker,
400 Secret,
401 Observability,
402 Channel,
403 State,
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
408pub enum EffectAccess {
409 Read,
410 Write,
411 Mutate,
412 Observe,
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum ResourceSelector {
421 Argument(u16),
423 Field {
425 argument: u16,
426 path: &'static [&'static str],
427 },
428 EachArgument(u16),
430 Constant(&'static str),
432 Dynamic,
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439pub struct EffectSpec {
440 pub kind: EffectKind,
441 pub access: EffectAccess,
442 pub resources: &'static [ResourceSelector],
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
453pub struct EffectAuthorization {
454 pub capability: CapabilityId,
455 pub operation: &'static str,
456}
457
458impl EffectAuthorization {
459 pub const fn new(capability: CapabilityId, operation: &'static str) -> Self {
460 Self {
461 capability,
462 operation,
463 }
464 }
465}
466
467impl EffectSpec {
468 pub const fn new(
469 kind: EffectKind,
470 access: EffectAccess,
471 resources: &'static [ResourceSelector],
472 ) -> Self {
473 Self {
474 kind,
475 access,
476 resources,
477 }
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484pub struct BuiltinContract {
485 pub exposure: BuiltinExposure,
486 pub effects: &'static [EffectSpec],
487 pub effects_authorized_by: Option<EffectAuthorization>,
488 runtime_control_plane: bool,
508}
509
510impl BuiltinContract {
511 pub const UNDECLARED: Self = Self {
512 exposure: BuiltinExposure::Undeclared,
513 effects: &[],
514 effects_authorized_by: None,
515 runtime_control_plane: false,
516 };
517
518 pub const PURE: Self = Self {
519 exposure: BuiltinExposure::PureGlobal,
520 effects: &[],
521 effects_authorized_by: None,
522 runtime_control_plane: false,
523 };
524
525 pub const RUNTIME_INTERNAL: Self = Self {
526 exposure: BuiltinExposure::RuntimeInternal,
527 effects: &[],
528 effects_authorized_by: None,
529 runtime_control_plane: false,
530 };
531
532 pub const STDLIB_INTERNAL: Self = Self {
533 exposure: BuiltinExposure::StdlibInternal,
534 effects: &[],
535 effects_authorized_by: None,
536 runtime_control_plane: false,
537 };
538
539 pub const fn harness(
540 capability: CapabilityId,
541 method: &'static str,
542 effects: &'static [EffectSpec],
543 ) -> Self {
544 Self {
545 exposure: BuiltinExposure::HarnessMethod { capability, method },
546 effects,
547 effects_authorized_by: None,
548 runtime_control_plane: false,
549 }
550 }
551
552 pub const fn harness_with_effect_authorization(
553 capability: CapabilityId,
554 method: &'static str,
555 effects: &'static [EffectSpec],
556 effects_authorized_by: EffectAuthorization,
557 ) -> Self {
558 assert!(!effects.is_empty(), "effect authorization requires effects");
559 let mut index = 0;
560 while index < effects.len() {
561 assert!(
562 matches!(
563 effects[index].access,
564 EffectAccess::Read | EffectAccess::Observe
565 ),
566 "effect authorization is limited to read-only effects"
567 );
568 index += 1;
569 }
570 Self {
571 exposure: BuiltinExposure::HarnessMethod { capability, method },
572 effects,
573 effects_authorized_by: Some(effects_authorized_by),
574 runtime_control_plane: false,
575 }
576 }
577
578 pub const fn harness_runtime_control_plane(
582 capability: CapabilityId,
583 method: &'static str,
584 effects: &'static [EffectSpec],
585 ) -> Self {
586 assert!(
587 !effects.is_empty(),
588 "runtime control plane requires declared effects: the marker classifies \
589 effects outside the user-world side-effect ladder, so a contract with \
590 none is decorative and would read as audited while asserting nothing"
591 );
592 let mut index = 0;
593 let mut mutates_state = false;
594 while index < effects.len() {
595 assert!(
596 matches!(effects[index].kind, EffectKind::State),
597 "runtime control plane effects must target state"
598 );
599 if matches!(
600 effects[index].access,
601 EffectAccess::Write | EffectAccess::Mutate
602 ) {
603 mutates_state = true;
604 }
605 index += 1;
606 }
607 assert!(
608 mutates_state,
609 "runtime control plane requires at least one state write or mutation"
610 );
611 Self {
612 exposure: BuiltinExposure::HarnessMethod { capability, method },
613 effects,
614 effects_authorized_by: None,
615 runtime_control_plane: true,
616 }
617 }
618
619 pub const fn is_runtime_control_plane(self) -> bool {
624 self.runtime_control_plane
625 }
626
627 pub const fn capability_function(
628 authority_argument: u16,
629 effects: &'static [EffectSpec],
630 ) -> Self {
631 Self {
632 exposure: BuiltinExposure::CapabilityFunction { authority_argument },
633 effects,
634 effects_authorized_by: None,
635 runtime_control_plane: false,
636 }
637 }
638
639 pub const fn privileged_wire(effects: &'static [EffectSpec]) -> Self {
640 Self {
641 exposure: BuiltinExposure::PrivilegedWire,
642 effects,
643 effects_authorized_by: None,
644 runtime_control_plane: false,
645 }
646 }
647
648 pub const fn is_declared(self) -> bool {
649 !matches!(self.exposure, BuiltinExposure::Undeclared)
650 }
651
652 pub const fn is_pure(self) -> bool {
653 self.effects.is_empty()
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 static WRITE_EFFECTS: &[EffectSpec] = &[EffectSpec::new(
662 EffectKind::State,
663 EffectAccess::Write,
664 &[ResourceSelector::Dynamic],
665 )];
666
667 #[test]
668 #[should_panic(expected = "effect authorization is limited to read-only effects")]
669 fn effect_authorization_rejects_write_effects() {
670 let _ = BuiltinContract::harness_with_effect_authorization(
671 CapabilityId::Runtime,
672 "test_write",
673 WRITE_EFFECTS,
674 EffectAuthorization::new(CapabilityId::Llm, "call"),
675 );
676 }
677
678 #[test]
679 #[should_panic(expected = "runtime control plane effects must target state")]
680 fn runtime_control_plane_rejects_non_state_effects() {
681 static EFFECTS: &[EffectSpec] = &[EffectSpec::new(
682 EffectKind::Fs,
683 EffectAccess::Write,
684 &[ResourceSelector::Dynamic],
685 )];
686 let _ = BuiltinContract::harness_runtime_control_plane(
687 CapabilityId::Agent,
688 "unsafe_write",
689 EFFECTS,
690 );
691 }
692
693 #[test]
694 #[should_panic(expected = "requires at least one state write or mutation")]
695 fn runtime_control_plane_rejects_read_only_state_effects() {
696 static EFFECTS: &[EffectSpec] = &[EffectSpec::new(
697 EffectKind::State,
698 EffectAccess::Read,
699 &[ResourceSelector::Dynamic],
700 )];
701 let _ = BuiltinContract::harness_runtime_control_plane(
702 CapabilityId::Agent,
703 "state_read",
704 EFFECTS,
705 );
706 }
707}