Skip to main content

harn_builtin_meta/
contracts.rs

1//! Typed authority and effect contracts for Harn builtins.
2//!
3//! This dependency-leaf model is the semantic owner for which script surface
4//! may reach a builtin and what that call can do. Runtime handler pointers stay
5//! in `harn-vm`; parser, IR, policy, hostlib, and documentation consumers can
6//! all depend on this crate without reversing the workspace dependency graph.
7
8/// Where a builtin is visible to Harn source.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum BuiltinExposure {
11    /// Contract has not been declared yet. Production registries must reject
12    /// this value; it exists only to make migration failures precise.
13    Undeclared,
14    /// Pure computation available as an ordinary global function.
15    PureGlobal,
16    /// Imported operation whose authority is carried by one explicit,
17    /// unforgeable argument derived from `Harness`. Importing the symbol
18    /// itself grants no authority.
19    CapabilityFunction { authority_argument: u16 },
20    /// Effectful operation available only through a typed harness handle.
21    HarnessMethod {
22        capability: CapabilityId,
23        method: &'static str,
24    },
25    /// Trusted embedder wire primitive. User modules cannot name or re-export
26    /// it; only artifacts stamped with privileged provenance may call it.
27    PrivilegedWire,
28    /// Compiler/runtime implementation detail that is never source-visible.
29    RuntimeInternal,
30}
31
32/// Closed vocabulary of capability handles exposed by `Harness`.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum CapabilityId {
35    Stdio,
36    Term,
37    Clock,
38    Fs,
39    Env,
40    Random,
41    Net,
42    Process,
43    Channels,
44    System,
45    Secrets,
46    Llm,
47    Agent,
48    Tenant,
49    Auth,
50    Observability,
51    Verdict,
52    Tools,
53    Ast,
54    CodeIndex,
55    Computer,
56    Embed,
57    Memory,
58    Sqlite,
59    Postgres,
60    FsWatch,
61    HostLease,
62    Scanner,
63    SecretStore,
64    TerminalSession,
65    Rules,
66    Lint,
67    Runtime,
68    Interaction,
69    Project,
70    Dashboard,
71    Workspace,
72    MergeCaptain,
73    Session,
74    Permission,
75    Text,
76    Lsp,
77    Credentials,
78    PrMonitor,
79    Workflow,
80    Testing,
81}
82
83impl CapabilityId {
84    /// Rust enum variant spelling for generated contract expressions.
85    pub const fn variant_name(self) -> &'static str {
86        match self {
87            Self::Stdio => "Stdio",
88            Self::Term => "Term",
89            Self::Clock => "Clock",
90            Self::Fs => "Fs",
91            Self::Env => "Env",
92            Self::Random => "Random",
93            Self::Net => "Net",
94            Self::Process => "Process",
95            Self::Channels => "Channels",
96            Self::System => "System",
97            Self::Secrets => "Secrets",
98            Self::Llm => "Llm",
99            Self::Agent => "Agent",
100            Self::Tenant => "Tenant",
101            Self::Auth => "Auth",
102            Self::Observability => "Observability",
103            Self::Verdict => "Verdict",
104            Self::Tools => "Tools",
105            Self::Ast => "Ast",
106            Self::CodeIndex => "CodeIndex",
107            Self::Computer => "Computer",
108            Self::Embed => "Embed",
109            Self::Memory => "Memory",
110            Self::Sqlite => "Sqlite",
111            Self::Postgres => "Postgres",
112            Self::FsWatch => "FsWatch",
113            Self::HostLease => "HostLease",
114            Self::Scanner => "Scanner",
115            Self::SecretStore => "SecretStore",
116            Self::TerminalSession => "TerminalSession",
117            Self::Rules => "Rules",
118            Self::Lint => "Lint",
119            Self::Runtime => "Runtime",
120            Self::Interaction => "Interaction",
121            Self::Project => "Project",
122            Self::Dashboard => "Dashboard",
123            Self::Workspace => "Workspace",
124            Self::MergeCaptain => "MergeCaptain",
125            Self::Session => "Session",
126            Self::Permission => "Permission",
127            Self::Text => "Text",
128            Self::Lsp => "Lsp",
129            Self::Credentials => "Credentials",
130            Self::PrMonitor => "PrMonitor",
131            Self::Workflow => "Workflow",
132            Self::Testing => "Testing",
133        }
134    }
135
136    /// Every capability in canonical root-field order.
137    pub const ALL: &'static [Self] = &[
138        Self::Stdio,
139        Self::Term,
140        Self::Clock,
141        Self::Fs,
142        Self::Env,
143        Self::Random,
144        Self::Net,
145        Self::Process,
146        Self::Channels,
147        Self::System,
148        Self::Secrets,
149        Self::Llm,
150        Self::Agent,
151        Self::Tenant,
152        Self::Auth,
153        Self::Observability,
154        Self::Verdict,
155        Self::Tools,
156        Self::Ast,
157        Self::CodeIndex,
158        Self::Computer,
159        Self::Embed,
160        Self::Memory,
161        Self::Sqlite,
162        Self::Postgres,
163        Self::FsWatch,
164        Self::HostLease,
165        Self::Scanner,
166        Self::SecretStore,
167        Self::TerminalSession,
168        Self::Rules,
169        Self::Lint,
170        Self::Runtime,
171        Self::Interaction,
172        Self::Project,
173        Self::Dashboard,
174        Self::Workspace,
175        Self::MergeCaptain,
176        Self::Session,
177        Self::Permission,
178        Self::Text,
179        Self::Lsp,
180        Self::Credentials,
181        Self::PrMonitor,
182        Self::Workflow,
183        Self::Testing,
184    ];
185
186    /// Canonical source-level `harness.<field>` name.
187    pub const fn field_name(self) -> &'static str {
188        match self {
189            Self::Stdio => "stdio",
190            Self::Term => "term",
191            Self::Clock => "clock",
192            Self::Fs => "fs",
193            Self::Env => "env",
194            Self::Random => "random",
195            Self::Net => "net",
196            Self::Process => "process",
197            Self::Channels => "channels",
198            Self::System => "system",
199            Self::Secrets => "secrets",
200            Self::Llm => "llm",
201            Self::Agent => "agent",
202            Self::Tenant => "tenant",
203            Self::Auth => "auth",
204            Self::Observability => "obs",
205            Self::Verdict => "verdict",
206            Self::Tools => "tools",
207            Self::Ast => "ast",
208            Self::CodeIndex => "code_index",
209            Self::Computer => "computer",
210            Self::Embed => "embed",
211            Self::Memory => "memory",
212            Self::Sqlite => "sqlite",
213            Self::Postgres => "postgres",
214            Self::FsWatch => "fs_watch",
215            Self::HostLease => "host_lease",
216            Self::Scanner => "scanner",
217            Self::SecretStore => "secret_store",
218            Self::TerminalSession => "terminal",
219            Self::Rules => "rules",
220            Self::Lint => "lint",
221            Self::Runtime => "runtime",
222            Self::Interaction => "interaction",
223            Self::Project => "project",
224            Self::Dashboard => "dashboard",
225            Self::Workspace => "workspace",
226            Self::MergeCaptain => "merge_captain",
227            Self::Session => "session",
228            Self::Permission => "permission",
229            Self::Text => "text",
230            Self::Lsp => "lsp",
231            Self::Credentials => "credentials",
232            Self::PrMonitor => "pr_monitor",
233            Self::Workflow => "workflow",
234            Self::Testing => "testing",
235        }
236    }
237
238    /// Nominal source type carried by this capability handle.
239    pub const fn type_name(self) -> &'static str {
240        match self {
241            Self::Stdio => "HarnessStdio",
242            Self::Term => "HarnessTerm",
243            Self::Clock => "HarnessClock",
244            Self::Fs => "HarnessFs",
245            Self::Env => "HarnessEnv",
246            Self::Random => "HarnessRandom",
247            Self::Net => "HarnessNet",
248            Self::Process => "HarnessProcess",
249            Self::Channels => "HarnessChannels",
250            Self::System => "HarnessSystem",
251            Self::Secrets => "HarnessSecrets",
252            Self::Llm => "HarnessLlm",
253            Self::Agent => "HarnessAgent",
254            Self::Tenant => "HarnessTenant",
255            Self::Auth => "HarnessAuth",
256            Self::Observability => "HarnessObs",
257            Self::Verdict => "HarnessVerdict",
258            Self::Tools => "HarnessTools",
259            Self::Ast => "HarnessAst",
260            Self::CodeIndex => "HarnessCodeIndex",
261            Self::Computer => "HarnessComputer",
262            Self::Embed => "HarnessEmbed",
263            Self::Memory => "HarnessMemory",
264            Self::Sqlite => "HarnessSqlite",
265            Self::Postgres => "HarnessPostgres",
266            Self::FsWatch => "HarnessFsWatch",
267            Self::HostLease => "HarnessHostLease",
268            Self::Scanner => "HarnessScanner",
269            Self::SecretStore => "HarnessSecretStore",
270            Self::TerminalSession => "HarnessTerminalSession",
271            Self::Rules => "HarnessRules",
272            Self::Lint => "HarnessLint",
273            Self::Runtime => "HarnessRuntime",
274            Self::Interaction => "HarnessInteraction",
275            Self::Project => "HarnessProject",
276            Self::Dashboard => "HarnessDashboard",
277            Self::Workspace => "HarnessWorkspace",
278            Self::MergeCaptain => "HarnessMergeCaptain",
279            Self::Session => "HarnessSession",
280            Self::Permission => "HarnessPermission",
281            Self::Text => "HarnessText",
282            Self::Lsp => "HarnessLsp",
283            Self::Credentials => "HarnessCredentials",
284            Self::PrMonitor => "HarnessPrMonitor",
285            Self::Workflow => "HarnessWorkflow",
286            Self::Testing => "HarnessTesting",
287        }
288    }
289
290    /// Parse the closed source vocabulary used by the builtin macro.
291    pub const fn from_field_name(name: &str) -> Option<Self> {
292        match name.as_bytes() {
293            b"stdio" => Some(Self::Stdio),
294            b"term" => Some(Self::Term),
295            b"clock" => Some(Self::Clock),
296            b"fs" => Some(Self::Fs),
297            b"env" => Some(Self::Env),
298            b"random" => Some(Self::Random),
299            b"net" => Some(Self::Net),
300            b"process" => Some(Self::Process),
301            b"channels" => Some(Self::Channels),
302            b"system" => Some(Self::System),
303            b"secrets" => Some(Self::Secrets),
304            b"llm" => Some(Self::Llm),
305            b"agent" => Some(Self::Agent),
306            b"tenant" => Some(Self::Tenant),
307            b"auth" => Some(Self::Auth),
308            b"obs" => Some(Self::Observability),
309            b"verdict" => Some(Self::Verdict),
310            b"tools" => Some(Self::Tools),
311            b"ast" => Some(Self::Ast),
312            b"code_index" => Some(Self::CodeIndex),
313            b"computer" => Some(Self::Computer),
314            b"embed" => Some(Self::Embed),
315            b"memory" => Some(Self::Memory),
316            b"sqlite" => Some(Self::Sqlite),
317            b"postgres" => Some(Self::Postgres),
318            b"fs_watch" => Some(Self::FsWatch),
319            b"host_lease" => Some(Self::HostLease),
320            b"scanner" => Some(Self::Scanner),
321            b"secret_store" => Some(Self::SecretStore),
322            b"terminal" => Some(Self::TerminalSession),
323            b"rules" => Some(Self::Rules),
324            b"lint" => Some(Self::Lint),
325            b"runtime" => Some(Self::Runtime),
326            b"interaction" => Some(Self::Interaction),
327            b"project" => Some(Self::Project),
328            b"dashboard" => Some(Self::Dashboard),
329            b"workspace" => Some(Self::Workspace),
330            b"merge_captain" => Some(Self::MergeCaptain),
331            b"session" => Some(Self::Session),
332            b"permission" => Some(Self::Permission),
333            b"text" => Some(Self::Text),
334            b"lsp" => Some(Self::Lsp),
335            b"credentials" => Some(Self::Credentials),
336            b"pr_monitor" => Some(Self::PrMonitor),
337            b"workflow" => Some(Self::Workflow),
338            b"testing" => Some(Self::Testing),
339            _ => None,
340        }
341    }
342
343    pub fn from_type_name(name: &str) -> Option<Self> {
344        Self::ALL
345            .iter()
346            .copied()
347            .find(|capability| capability.type_name() == name)
348    }
349
350    /// Resolve the namespace half of a host-wire operation name such as
351    /// `"prmonitor.run_commands"` or `"code_index.search"`.
352    ///
353    /// Host wires predate the typed capability vocabulary and spell namespaces
354    /// without separators, so `"prmonitor"` and `"pr_monitor"` name the same
355    /// capability. [`Self::from_field_name`] stays exact because it parses the
356    /// closed source vocabulary, where a spelling either is or is not the
357    /// declared field name.
358    pub fn from_host_namespace(namespace: &str) -> Option<Self> {
359        let wanted = wire_identifier_key(namespace);
360        Self::ALL
361            .iter()
362            .copied()
363            .find(|capability| wire_identifier_key(capability.field_name()) == wanted)
364    }
365}
366
367/// Normalized spelling used to match a host-wire name against a declared
368/// identifier: `_` removed and ASCII case folded.
369///
370/// One definition so the namespace half and the operation half of a wire name
371/// are matched by the same rule.
372pub fn wire_identifier_key(value: &str) -> String {
373    value
374        .chars()
375        .filter(|character| *character != '_')
376        .map(|character| character.to_ascii_lowercase())
377        .collect()
378}
379
380/// Closed effect family used for static ceilings and runtime receipts.
381#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
382pub enum EffectKind {
383    Stdio,
384    Fs,
385    Env,
386    Clock,
387    Random,
388    Network,
389    Process,
390    Llm,
391    Tool,
392    Mcp,
393    Host,
394    Worker,
395    Secret,
396    Observability,
397    Channel,
398    State,
399}
400
401/// How an operation interacts with its effect resource.
402#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
403pub enum EffectAccess {
404    Read,
405    Write,
406    Mutate,
407    Observe,
408}
409
410/// Declarative extraction of resource identities from nominal call arguments.
411///
412/// A contract may carry several selectors, which covers moves/renames, staged
413/// batches, and option-dependent scopes without another name-based classifier.
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
415pub enum ResourceSelector {
416    /// Whole positional argument at the declared index.
417    Argument(u16),
418    /// A nested field inside one positional argument.
419    Field {
420        argument: u16,
421        path: &'static [&'static str],
422    },
423    /// Every element in a positional list argument.
424    EachArgument(u16),
425    /// A registry-owned fixed resource identity.
426    Constant(&'static str),
427    /// The operation is effectful but the resource cannot be resolved
428    /// statically. Runtime receipt resolution may still supply it.
429    Dynamic,
430}
431
432/// One conservative effect entry for a builtin.
433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
434pub struct EffectSpec {
435    pub kind: EffectKind,
436    pub access: EffectAccess,
437    pub resources: &'static [ResourceSelector],
438}
439
440impl EffectSpec {
441    pub const fn new(
442        kind: EffectKind,
443        access: EffectAccess,
444        resources: &'static [ResourceSelector],
445    ) -> Self {
446        Self {
447            kind,
448            access,
449            resources,
450        }
451    }
452}
453
454/// Complete source exposure and effect contract paired with one builtin
455/// implementation.
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457pub struct BuiltinContract {
458    pub exposure: BuiltinExposure,
459    pub effects: &'static [EffectSpec],
460}
461
462impl BuiltinContract {
463    pub const UNDECLARED: Self = Self {
464        exposure: BuiltinExposure::Undeclared,
465        effects: &[],
466    };
467
468    pub const PURE: Self = Self {
469        exposure: BuiltinExposure::PureGlobal,
470        effects: &[],
471    };
472
473    pub const RUNTIME_INTERNAL: Self = Self {
474        exposure: BuiltinExposure::RuntimeInternal,
475        effects: &[],
476    };
477
478    pub const fn harness(
479        capability: CapabilityId,
480        method: &'static str,
481        effects: &'static [EffectSpec],
482    ) -> Self {
483        Self {
484            exposure: BuiltinExposure::HarnessMethod { capability, method },
485            effects,
486        }
487    }
488
489    pub const fn capability_function(
490        authority_argument: u16,
491        effects: &'static [EffectSpec],
492    ) -> Self {
493        Self {
494            exposure: BuiltinExposure::CapabilityFunction { authority_argument },
495            effects,
496        }
497    }
498
499    pub const fn privileged_wire(effects: &'static [EffectSpec]) -> Self {
500        Self {
501            exposure: BuiltinExposure::PrivilegedWire,
502            effects,
503        }
504    }
505
506    pub const fn is_declared(self) -> bool {
507        !matches!(self.exposure, BuiltinExposure::Undeclared)
508    }
509
510    pub const fn is_pure(self) -> bool {
511        self.effects.is_empty()
512    }
513}