Skip to main content

harn_capability_contracts/
lib.rs

1//! Canonical typed contracts for every `Harness` capability method.
2//!
3//! This dependency-leaf crate owns the method names, signatures, effects, and
4//! documentation shared by parser, IR, runtime policy, receipts, and tooling.
5//! Consumers read the immutable manifest directly; correctness never depends
6//! on a VM having initialized a process-global registry first.
7
8use std::sync::OnceLock;
9
10use harn_builtin_meta::{BuiltinContract, BuiltinSignature};
11use harn_builtin_registry::BuiltinManifestEntry;
12
13/// One method contract before its name-keyed manifest projection.
14#[derive(Debug, Clone, Copy)]
15pub struct CapabilityMethodDef {
16    pub signature: BuiltinSignature,
17    pub contract: BuiltinContract,
18    pub doc: &'static str,
19    pub signature_text: Option<&'static str>,
20}
21
22/// Build-generated projection of the source declarations. A plain static
23/// slice works on native and `wasm32-unknown-unknown`, unlike linker-section
24/// discovery, while the macro declarations remain the single contract owner.
25pub static ALL_CAPABILITY_METHOD_DEFS: &[&CapabilityMethodDef] =
26    include!(concat!(env!("OUT_DIR"), "/capability_method_defs.rs"));
27
28/// Proc-macro support paths kept in one deliberately boring module.
29#[doc(hidden)]
30pub mod support {
31    pub use crate::{CapabilityMethodDef, ALL_CAPABILITY_METHOD_DEFS};
32    pub use harn_builtin_meta::{
33        shapes, BuiltinContract, BuiltinExposure, BuiltinSignature, CapabilityId, EffectAccess,
34        EffectAuthorization, EffectKind, EffectSpec, Param, ResourceSelector, ShapeFieldDescriptor,
35        Ty, TY_ANY, TY_BOOL, TY_BYTES, TY_BYTES_OR_NIL, TY_CLOSURE, TY_DICT, TY_DICT_OR_NIL,
36        TY_DURATION, TY_FLOAT, TY_INT, TY_INT_OR_NIL, TY_LIST, TY_NEVER, TY_NIL, TY_NUMBER,
37        TY_RESOURCE, TY_STRING, TY_STRING_OR_NIL,
38    };
39}
40
41use harn_builtin_macros::harn_capability_contract as capability_method;
42
43mod vm_declared;
44
45include!("ai.rs");
46include!("data.rs");
47include!("host.rs");
48include!("io.rs");
49
50/// Deterministic manifest projection consumed directly by every compiler and
51/// runtime surface.
52pub fn manifest() -> &'static [&'static BuiltinManifestEntry] {
53    static MANIFEST: OnceLock<Vec<&'static BuiltinManifestEntry>> = OnceLock::new();
54    MANIFEST
55        .get_or_init(|| {
56            let mut defs = ALL_CAPABILITY_METHOD_DEFS.to_vec();
57            defs.sort_by_key(|def| def.signature.name);
58            defs.into_iter()
59                .map(|def| {
60                    Box::leak(Box::new(BuiltinManifestEntry {
61                        name: def.signature.name,
62                        canonical_name: def.signature.name,
63                        signature: &def.signature,
64                        contract: def.contract,
65                    })) as &'static BuiltinManifestEntry
66                })
67                .collect()
68        })
69        .as_slice()
70}
71
72/// Resolve one `harness.<field>.<method>` contract without mutable registry
73/// initialization.
74pub fn capability_method_entry(field: &str, method: &str) -> Option<&'static BuiltinManifestEntry> {
75    let capability = harn_builtin_meta::CapabilityId::from_field_name(field)?;
76    manifest().iter().copied().find(|entry| {
77        matches!(
78            entry.contract.exposure,
79            harn_builtin_meta::BuiltinExposure::HarnessMethod {
80                capability: candidate,
81                method: candidate_method,
82            } if candidate == capability && candidate_method == method
83        )
84    })
85}
86
87/// Is `harness.<field>.<method>` a declared capability method anywhere in the
88/// workspace?
89///
90/// Weaker than [`capability_method_entry`] on purpose: it answers existence
91/// without a contract, because the 280 methods `harn-vm` declares through
92/// `#[harn_builtin]` have no leaf-crate contract to return. Their bodies close
93/// over VM internals, so the declaration cannot move here — only its name can.
94///
95/// A consumer that needs the signature must still go through the installed
96/// manifest and accept that it is empty before the VM installs it. A consumer
97/// that only needs to reject a typo — `harn check` — can use this, and get the
98/// same answer whether or not a VM ever starts (#6101).
99#[must_use]
100pub fn is_declared_capability_method(field: &str, method: &str) -> bool {
101    if capability_method_entry(field, method).is_some() {
102        return true;
103    }
104    if vm_declared::VM_DECLARED_CAPABILITY_METHODS
105        .binary_search(&(field, method))
106        .is_ok()
107    {
108        return true;
109    }
110    // The third registry. A host-bridged method has no builtin at all — the VM
111    // routes it to `host_call`, and the implementation lives in the embedder.
112    // `harness.workspace.search` is real for a host that serves it and reaches
113    // no declaration in either table above.
114    harn_builtin_meta::CapabilityId::from_field_name(field).is_some_and(|capability| {
115        harn_builtin_meta::host_capabilities::is_host_capability_method(capability, method)
116    })
117}
118
119/// Every method name declared on one capability, contract-owned and
120/// `harn-vm`-owned together, sorted and de-duplicated.
121///
122/// Used to suggest a near miss when [`is_declared_capability_method`] rejects
123/// a name.
124#[must_use]
125pub fn declared_capability_method_names(field: &str) -> Vec<&'static str> {
126    let mut names: Vec<&'static str> = vm_declared::VM_DECLARED_CAPABILITY_METHODS
127        .iter()
128        .filter(|(capability, _)| *capability == field)
129        .map(|(_, method)| *method)
130        .collect();
131    if let Some(capability) = harn_builtin_meta::CapabilityId::from_field_name(field) {
132        names.extend(
133            manifest()
134                .iter()
135                .filter_map(|entry| match entry.contract.exposure {
136                    harn_builtin_meta::BuiltinExposure::HarnessMethod {
137                        capability: candidate,
138                        method,
139                    } if candidate == capability => Some(method),
140                    _ => None,
141                }),
142        );
143        names.extend(
144            harn_builtin_meta::host_capabilities::all_host_capability_groups()
145                .filter(|group| group.capability == capability)
146                .flat_map(|group| group.methods.iter().copied()),
147        );
148    }
149    names.sort_unstable();
150    names.dedup();
151    names
152}
153
154#[cfg(test)]
155mod tests {
156    /// The generated table is sorted, which `is_declared_capability_method`
157    /// binary-searches. A generator change that lost the ordering would make
158    /// lookups miss silently rather than fail.
159    #[test]
160    fn vm_declared_methods_are_sorted_and_unique() {
161        let table = super::vm_declared::VM_DECLARED_CAPABILITY_METHODS;
162        assert!(!table.is_empty());
163        assert!(
164            table.windows(2).all(|pair| pair[0] < pair[1]),
165            "the generated table must be sorted and free of duplicates"
166        );
167    }
168
169    /// The motivating pair from #6101: real, VM-owned, and invisible to the
170    /// static manifest.
171    #[test]
172    fn a_vm_only_method_is_declared_without_the_vm() {
173        assert!(super::capability_method_entry("runtime", "shared_cell").is_none());
174        assert!(super::is_declared_capability_method(
175            "runtime",
176            "shared_cell"
177        ));
178    }
179
180    #[test]
181    fn a_contract_owned_method_is_declared() {
182        assert!(super::is_declared_capability_method("fs", "read_text"));
183    }
184
185    /// The third registry. `harness.workspace.search` has no builtin at all:
186    /// the VM routes it to `host_call`, and a host serves it. Reading only the
187    /// contract manifest and the generated `harn-vm` projection reported it as
188    /// a typo, which broke the embedder-bridge tests.
189    #[test]
190    fn a_host_bridged_method_is_declared() {
191        assert!(super::capability_method_entry("workspace", "search").is_none());
192        assert!(super::is_declared_capability_method("workspace", "search"));
193        assert!(super::declared_capability_method_names("workspace").contains(&"search"));
194    }
195
196    #[test]
197    fn a_typo_is_not_declared() {
198        assert!(!super::is_declared_capability_method("fs", "bogus_method"));
199        assert!(!super::is_declared_capability_method(
200            "runtime",
201            "shared_cel"
202        ));
203        assert!(!super::is_declared_capability_method(
204            "workspace",
205            "searchh"
206        ));
207    }
208}