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}
489
490impl BuiltinContract {
491 pub const UNDECLARED: Self = Self {
492 exposure: BuiltinExposure::Undeclared,
493 effects: &[],
494 effects_authorized_by: None,
495 };
496
497 pub const PURE: Self = Self {
498 exposure: BuiltinExposure::PureGlobal,
499 effects: &[],
500 effects_authorized_by: None,
501 };
502
503 pub const RUNTIME_INTERNAL: Self = Self {
504 exposure: BuiltinExposure::RuntimeInternal,
505 effects: &[],
506 effects_authorized_by: None,
507 };
508
509 pub const STDLIB_INTERNAL: Self = Self {
510 exposure: BuiltinExposure::StdlibInternal,
511 effects: &[],
512 effects_authorized_by: None,
513 };
514
515 pub const fn harness(
516 capability: CapabilityId,
517 method: &'static str,
518 effects: &'static [EffectSpec],
519 ) -> Self {
520 Self {
521 exposure: BuiltinExposure::HarnessMethod { capability, method },
522 effects,
523 effects_authorized_by: None,
524 }
525 }
526
527 pub const fn harness_with_effect_authorization(
528 capability: CapabilityId,
529 method: &'static str,
530 effects: &'static [EffectSpec],
531 effects_authorized_by: EffectAuthorization,
532 ) -> Self {
533 assert!(!effects.is_empty(), "effect authorization requires effects");
534 let mut index = 0;
535 while index < effects.len() {
536 assert!(
537 matches!(
538 effects[index].access,
539 EffectAccess::Read | EffectAccess::Observe
540 ),
541 "effect authorization is limited to read-only effects"
542 );
543 index += 1;
544 }
545 Self {
546 exposure: BuiltinExposure::HarnessMethod { capability, method },
547 effects,
548 effects_authorized_by: Some(effects_authorized_by),
549 }
550 }
551
552 pub const fn capability_function(
553 authority_argument: u16,
554 effects: &'static [EffectSpec],
555 ) -> Self {
556 Self {
557 exposure: BuiltinExposure::CapabilityFunction { authority_argument },
558 effects,
559 effects_authorized_by: None,
560 }
561 }
562
563 pub const fn privileged_wire(effects: &'static [EffectSpec]) -> Self {
564 Self {
565 exposure: BuiltinExposure::PrivilegedWire,
566 effects,
567 effects_authorized_by: None,
568 }
569 }
570
571 pub const fn is_declared(self) -> bool {
572 !matches!(self.exposure, BuiltinExposure::Undeclared)
573 }
574
575 pub const fn is_pure(self) -> bool {
576 self.effects.is_empty()
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 static WRITE_EFFECTS: &[EffectSpec] = &[EffectSpec::new(
585 EffectKind::State,
586 EffectAccess::Write,
587 &[ResourceSelector::Dynamic],
588 )];
589
590 #[test]
591 #[should_panic(expected = "effect authorization is limited to read-only effects")]
592 fn effect_authorization_rejects_write_effects() {
593 let _ = BuiltinContract::harness_with_effect_authorization(
594 CapabilityId::Runtime,
595 "test_write",
596 WRITE_EFFECTS,
597 EffectAuthorization::new(CapabilityId::Llm, "call"),
598 );
599 }
600}