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    Testing,
71}
72
73impl CapabilityId {
74    /// Rust enum variant spelling for generated contract expressions.
75    pub const fn variant_name(self) -> &'static str {
76        match self {
77            Self::Stdio => "Stdio",
78            Self::Term => "Term",
79            Self::Clock => "Clock",
80            Self::Fs => "Fs",
81            Self::Env => "Env",
82            Self::Random => "Random",
83            Self::Net => "Net",
84            Self::Process => "Process",
85            Self::Channels => "Channels",
86            Self::System => "System",
87            Self::Secrets => "Secrets",
88            Self::Llm => "Llm",
89            Self::Agent => "Agent",
90            Self::Tenant => "Tenant",
91            Self::Auth => "Auth",
92            Self::Observability => "Observability",
93            Self::Verdict => "Verdict",
94            Self::Tools => "Tools",
95            Self::Ast => "Ast",
96            Self::CodeIndex => "CodeIndex",
97            Self::Computer => "Computer",
98            Self::Embed => "Embed",
99            Self::Memory => "Memory",
100            Self::Sqlite => "Sqlite",
101            Self::Postgres => "Postgres",
102            Self::FsWatch => "FsWatch",
103            Self::HostLease => "HostLease",
104            Self::Scanner => "Scanner",
105            Self::SecretStore => "SecretStore",
106            Self::TerminalSession => "TerminalSession",
107            Self::Rules => "Rules",
108            Self::Lint => "Lint",
109            Self::Runtime => "Runtime",
110            Self::Interaction => "Interaction",
111            Self::Project => "Project",
112            Self::Testing => "Testing",
113        }
114    }
115
116    /// Every capability in canonical root-field order.
117    pub const ALL: &'static [Self] = &[
118        Self::Stdio,
119        Self::Term,
120        Self::Clock,
121        Self::Fs,
122        Self::Env,
123        Self::Random,
124        Self::Net,
125        Self::Process,
126        Self::Channels,
127        Self::System,
128        Self::Secrets,
129        Self::Llm,
130        Self::Agent,
131        Self::Tenant,
132        Self::Auth,
133        Self::Observability,
134        Self::Verdict,
135        Self::Tools,
136        Self::Ast,
137        Self::CodeIndex,
138        Self::Computer,
139        Self::Embed,
140        Self::Memory,
141        Self::Sqlite,
142        Self::Postgres,
143        Self::FsWatch,
144        Self::HostLease,
145        Self::Scanner,
146        Self::SecretStore,
147        Self::TerminalSession,
148        Self::Rules,
149        Self::Lint,
150        Self::Runtime,
151        Self::Interaction,
152        Self::Project,
153        Self::Testing,
154    ];
155
156    /// Canonical source-level `harness.<field>` name.
157    pub const fn field_name(self) -> &'static str {
158        match self {
159            Self::Stdio => "stdio",
160            Self::Term => "term",
161            Self::Clock => "clock",
162            Self::Fs => "fs",
163            Self::Env => "env",
164            Self::Random => "random",
165            Self::Net => "net",
166            Self::Process => "process",
167            Self::Channels => "channels",
168            Self::System => "system",
169            Self::Secrets => "secrets",
170            Self::Llm => "llm",
171            Self::Agent => "agent",
172            Self::Tenant => "tenant",
173            Self::Auth => "auth",
174            Self::Observability => "obs",
175            Self::Verdict => "verdict",
176            Self::Tools => "tools",
177            Self::Ast => "ast",
178            Self::CodeIndex => "code_index",
179            Self::Computer => "computer",
180            Self::Embed => "embed",
181            Self::Memory => "memory",
182            Self::Sqlite => "sqlite",
183            Self::Postgres => "postgres",
184            Self::FsWatch => "fs_watch",
185            Self::HostLease => "host_lease",
186            Self::Scanner => "scanner",
187            Self::SecretStore => "secret_store",
188            Self::TerminalSession => "terminal",
189            Self::Rules => "rules",
190            Self::Lint => "lint",
191            Self::Runtime => "runtime",
192            Self::Interaction => "interaction",
193            Self::Project => "project",
194            Self::Testing => "testing",
195        }
196    }
197
198    /// Nominal source type carried by this capability handle.
199    pub const fn type_name(self) -> &'static str {
200        match self {
201            Self::Stdio => "HarnessStdio",
202            Self::Term => "HarnessTerm",
203            Self::Clock => "HarnessClock",
204            Self::Fs => "HarnessFs",
205            Self::Env => "HarnessEnv",
206            Self::Random => "HarnessRandom",
207            Self::Net => "HarnessNet",
208            Self::Process => "HarnessProcess",
209            Self::Channels => "HarnessChannels",
210            Self::System => "HarnessSystem",
211            Self::Secrets => "HarnessSecrets",
212            Self::Llm => "HarnessLlm",
213            Self::Agent => "HarnessAgent",
214            Self::Tenant => "HarnessTenant",
215            Self::Auth => "HarnessAuth",
216            Self::Observability => "HarnessObs",
217            Self::Verdict => "HarnessVerdict",
218            Self::Tools => "HarnessTools",
219            Self::Ast => "HarnessAst",
220            Self::CodeIndex => "HarnessCodeIndex",
221            Self::Computer => "HarnessComputer",
222            Self::Embed => "HarnessEmbed",
223            Self::Memory => "HarnessMemory",
224            Self::Sqlite => "HarnessSqlite",
225            Self::Postgres => "HarnessPostgres",
226            Self::FsWatch => "HarnessFsWatch",
227            Self::HostLease => "HarnessHostLease",
228            Self::Scanner => "HarnessScanner",
229            Self::SecretStore => "HarnessSecretStore",
230            Self::TerminalSession => "HarnessTerminalSession",
231            Self::Rules => "HarnessRules",
232            Self::Lint => "HarnessLint",
233            Self::Runtime => "HarnessRuntime",
234            Self::Interaction => "HarnessInteraction",
235            Self::Project => "HarnessProject",
236            Self::Testing => "HarnessTesting",
237        }
238    }
239
240    /// Parse the closed source vocabulary used by the builtin macro.
241    pub const fn from_field_name(name: &str) -> Option<Self> {
242        match name.as_bytes() {
243            b"stdio" => Some(Self::Stdio),
244            b"term" => Some(Self::Term),
245            b"clock" => Some(Self::Clock),
246            b"fs" => Some(Self::Fs),
247            b"env" => Some(Self::Env),
248            b"random" => Some(Self::Random),
249            b"net" => Some(Self::Net),
250            b"process" => Some(Self::Process),
251            b"channels" => Some(Self::Channels),
252            b"system" => Some(Self::System),
253            b"secrets" => Some(Self::Secrets),
254            b"llm" => Some(Self::Llm),
255            b"agent" => Some(Self::Agent),
256            b"tenant" => Some(Self::Tenant),
257            b"auth" => Some(Self::Auth),
258            b"obs" => Some(Self::Observability),
259            b"verdict" => Some(Self::Verdict),
260            b"tools" => Some(Self::Tools),
261            b"ast" => Some(Self::Ast),
262            b"code_index" => Some(Self::CodeIndex),
263            b"computer" => Some(Self::Computer),
264            b"embed" => Some(Self::Embed),
265            b"memory" => Some(Self::Memory),
266            b"sqlite" => Some(Self::Sqlite),
267            b"postgres" => Some(Self::Postgres),
268            b"fs_watch" => Some(Self::FsWatch),
269            b"host_lease" => Some(Self::HostLease),
270            b"scanner" => Some(Self::Scanner),
271            b"secret_store" => Some(Self::SecretStore),
272            b"terminal" => Some(Self::TerminalSession),
273            b"rules" => Some(Self::Rules),
274            b"lint" => Some(Self::Lint),
275            b"runtime" => Some(Self::Runtime),
276            b"interaction" => Some(Self::Interaction),
277            b"project" => Some(Self::Project),
278            b"testing" => Some(Self::Testing),
279            _ => None,
280        }
281    }
282
283    pub fn from_type_name(name: &str) -> Option<Self> {
284        Self::ALL
285            .iter()
286            .copied()
287            .find(|capability| capability.type_name() == name)
288    }
289}
290
291/// Closed effect family used for static ceilings and runtime receipts.
292#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
293pub enum EffectKind {
294    Stdio,
295    Fs,
296    Env,
297    Clock,
298    Random,
299    Network,
300    Process,
301    Llm,
302    Tool,
303    Mcp,
304    Host,
305    Worker,
306    Secret,
307    Observability,
308    Channel,
309    State,
310}
311
312/// How an operation interacts with its effect resource.
313#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
314pub enum EffectAccess {
315    Read,
316    Write,
317    Mutate,
318    Observe,
319}
320
321/// Declarative extraction of resource identities from nominal call arguments.
322///
323/// A contract may carry several selectors, which covers moves/renames, staged
324/// batches, and option-dependent scopes without another name-based classifier.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub enum ResourceSelector {
327    /// Whole positional argument at the declared index.
328    Argument(u16),
329    /// A nested field inside one positional argument.
330    Field {
331        argument: u16,
332        path: &'static [&'static str],
333    },
334    /// Every element in a positional list argument.
335    EachArgument(u16),
336    /// A registry-owned fixed resource identity.
337    Constant(&'static str),
338    /// The operation is effectful but the resource cannot be resolved
339    /// statically. Runtime receipt resolution may still supply it.
340    Dynamic,
341}
342
343/// One conservative effect entry for a builtin.
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub struct EffectSpec {
346    pub kind: EffectKind,
347    pub access: EffectAccess,
348    pub resources: &'static [ResourceSelector],
349}
350
351impl EffectSpec {
352    pub const fn new(
353        kind: EffectKind,
354        access: EffectAccess,
355        resources: &'static [ResourceSelector],
356    ) -> Self {
357        Self {
358            kind,
359            access,
360            resources,
361        }
362    }
363}
364
365/// Complete source exposure and effect contract paired with one builtin
366/// implementation.
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368pub struct BuiltinContract {
369    pub exposure: BuiltinExposure,
370    pub effects: &'static [EffectSpec],
371}
372
373impl BuiltinContract {
374    pub const UNDECLARED: Self = Self {
375        exposure: BuiltinExposure::Undeclared,
376        effects: &[],
377    };
378
379    pub const PURE: Self = Self {
380        exposure: BuiltinExposure::PureGlobal,
381        effects: &[],
382    };
383
384    pub const RUNTIME_INTERNAL: Self = Self {
385        exposure: BuiltinExposure::RuntimeInternal,
386        effects: &[],
387    };
388
389    pub const fn harness(
390        capability: CapabilityId,
391        method: &'static str,
392        effects: &'static [EffectSpec],
393    ) -> Self {
394        Self {
395            exposure: BuiltinExposure::HarnessMethod { capability, method },
396            effects,
397        }
398    }
399
400    pub const fn capability_function(
401        authority_argument: u16,
402        effects: &'static [EffectSpec],
403    ) -> Self {
404        Self {
405            exposure: BuiltinExposure::CapabilityFunction { authority_argument },
406            effects,
407        }
408    }
409
410    pub const fn privileged_wire(effects: &'static [EffectSpec]) -> Self {
411        Self {
412            exposure: BuiltinExposure::PrivilegedWire,
413            effects,
414        }
415    }
416
417    pub const fn is_declared(self) -> bool {
418        !matches!(self.exposure, BuiltinExposure::Undeclared)
419    }
420
421    pub const fn is_pure(self) -> bool {
422        self.effects.is_empty()
423    }
424}