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
351/// Closed effect family used for static ceilings and runtime receipts.
352#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
353pub enum EffectKind {
354    Stdio,
355    Fs,
356    Env,
357    Clock,
358    Random,
359    Network,
360    Process,
361    Llm,
362    Tool,
363    Mcp,
364    Host,
365    Worker,
366    Secret,
367    Observability,
368    Channel,
369    State,
370}
371
372/// How an operation interacts with its effect resource.
373#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
374pub enum EffectAccess {
375    Read,
376    Write,
377    Mutate,
378    Observe,
379}
380
381/// Declarative extraction of resource identities from nominal call arguments.
382///
383/// A contract may carry several selectors, which covers moves/renames, staged
384/// batches, and option-dependent scopes without another name-based classifier.
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum ResourceSelector {
387    /// Whole positional argument at the declared index.
388    Argument(u16),
389    /// A nested field inside one positional argument.
390    Field {
391        argument: u16,
392        path: &'static [&'static str],
393    },
394    /// Every element in a positional list argument.
395    EachArgument(u16),
396    /// A registry-owned fixed resource identity.
397    Constant(&'static str),
398    /// The operation is effectful but the resource cannot be resolved
399    /// statically. Runtime receipt resolution may still supply it.
400    Dynamic,
401}
402
403/// One conservative effect entry for a builtin.
404#[derive(Debug, Clone, Copy, PartialEq, Eq)]
405pub struct EffectSpec {
406    pub kind: EffectKind,
407    pub access: EffectAccess,
408    pub resources: &'static [ResourceSelector],
409}
410
411impl EffectSpec {
412    pub const fn new(
413        kind: EffectKind,
414        access: EffectAccess,
415        resources: &'static [ResourceSelector],
416    ) -> Self {
417        Self {
418            kind,
419            access,
420            resources,
421        }
422    }
423}
424
425/// Complete source exposure and effect contract paired with one builtin
426/// implementation.
427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428pub struct BuiltinContract {
429    pub exposure: BuiltinExposure,
430    pub effects: &'static [EffectSpec],
431}
432
433impl BuiltinContract {
434    pub const UNDECLARED: Self = Self {
435        exposure: BuiltinExposure::Undeclared,
436        effects: &[],
437    };
438
439    pub const PURE: Self = Self {
440        exposure: BuiltinExposure::PureGlobal,
441        effects: &[],
442    };
443
444    pub const RUNTIME_INTERNAL: Self = Self {
445        exposure: BuiltinExposure::RuntimeInternal,
446        effects: &[],
447    };
448
449    pub const fn harness(
450        capability: CapabilityId,
451        method: &'static str,
452        effects: &'static [EffectSpec],
453    ) -> Self {
454        Self {
455            exposure: BuiltinExposure::HarnessMethod { capability, method },
456            effects,
457        }
458    }
459
460    pub const fn capability_function(
461        authority_argument: u16,
462        effects: &'static [EffectSpec],
463    ) -> Self {
464        Self {
465            exposure: BuiltinExposure::CapabilityFunction { authority_argument },
466            effects,
467        }
468    }
469
470    pub const fn privileged_wire(effects: &'static [EffectSpec]) -> Self {
471        Self {
472            exposure: BuiltinExposure::PrivilegedWire,
473            effects,
474        }
475    }
476
477    pub const fn is_declared(self) -> bool {
478        !matches!(self.exposure, BuiltinExposure::Undeclared)
479    }
480
481    pub const fn is_pure(self) -> bool {
482        self.effects.is_empty()
483    }
484}