Skip to main content

harn_vm/
stdlib.rs

1//! Standard library builtins for the Harn VM.
2//!
3//! Every builtin is declared with the `#[harn_builtin]` proc-macro
4//! (`crate::stdlib::macros::harn_builtin`). Each annotation emits a sibling
5//! `static <FN>_DEF: VmBuiltinDef` carrying the signature, aliases, handler,
6//! and metadata, and registers it into the workspace-global
7//! [`macros::ALL_BUILTIN_DEFS`] distributed slice at link time. The CLI / LSP /
8//! lint / serve / dap binaries call [`force_link`] to defeat rlib dead-code
9//! stripping (linkme issue #36) so every static lands in the slice. Modules
10//! still expose a `register_<module>_builtins(vm)` helper for ordered eager
11//! registration (e.g. so `clock::timestamp` can override `process::timestamp`).
12//! `register_vm_stdlib` calls those helpers in order and then installs the
13//! aggregated signatures into the parser registry.
14//!
15//! See `CONTRIBUTING.md` ("Adding a stdlib builtin") for the full template.
16
17pub mod macros;
18
19mod agent_sessions;
20pub mod agent_state;
21pub(crate) mod agents;
22mod agents_daemon;
23mod artifact_emit;
24pub(crate) mod assemble;
25pub mod asset_paths;
26mod bytes;
27mod calendar;
28mod channel_guardrails;
29mod channels;
30pub(crate) mod clock;
31pub(crate) mod collections;
32mod command_policy;
33pub(crate) mod compaction;
34#[cfg(feature = "compression")]
35mod compression;
36mod concurrency;
37pub(crate) use concurrency::cancelled_vm_error;
38mod connectors;
39mod cookies;
40mod cron;
41mod crypto;
42mod csv;
43mod datetime;
44mod diff;
45pub(crate) use datetime::date_dict_from_millis;
46#[cfg(feature = "content")]
47mod document;
48mod durable_step;
49mod event_log;
50pub use event_log::mint_hypothesis_native_attestation;
51mod external_agent;
52pub(crate) mod files;
53mod flow;
54pub(crate) mod fs;
55mod git;
56pub(crate) mod git_topology;
57mod grounding;
58pub(crate) mod harn_entry;
59pub(crate) mod hitl;
60mod hitl_read;
61pub mod host;
62pub mod http_response;
63pub(crate) mod io;
64mod iter;
65pub(crate) mod json;
66mod json_query;
67pub(crate) mod json_stream;
68mod jsonrpc;
69mod junit;
70mod lifecycle_receipts;
71mod logging;
72pub mod long_running;
73mod math;
74pub(crate) use math::call_seeded_random_method;
75pub(crate) mod memory;
76mod monitors;
77mod multipart;
78mod net;
79mod net_policy;
80mod oauth_dynreg;
81mod oauth_storage;
82pub(crate) mod observability;
83pub(crate) mod options;
84mod package_snapshot;
85pub(crate) use package_snapshot::PackageSnapshotRegistry;
86mod path;
87pub(crate) mod path_scope_guard;
88pub(crate) mod pool;
89mod portable;
90#[cfg(feature = "postgres")]
91mod postgres;
92#[cfg(feature = "postgres")]
93pub use postgres::install_shared_pool_registry;
94pub mod process;
95pub(crate) mod process_spawn;
96mod project;
97mod project_catalog;
98mod project_enrich;
99mod regex;
100mod review;
101mod runtime_scope;
102pub(crate) mod sandbox;
103pub mod secret_scan;
104pub(crate) mod session_store;
105mod sets;
106pub(crate) mod shapes;
107mod skills;
108#[cfg(feature = "sqlite")]
109mod sqlite;
110pub(crate) mod strings;
111pub(crate) mod supervisor;
112pub mod template;
113mod testbench;
114mod testing;
115mod timing;
116pub mod token_redaction;
117pub(crate) mod tool_hooks;
118pub(crate) mod tools;
119pub mod tracing;
120mod transcript_compact;
121pub(crate) mod transcript_project;
122mod triggers_stdlib;
123mod tui;
124mod types;
125mod url_parse;
126mod vision;
127pub(crate) mod waitpoint;
128#[cfg(feature = "content")]
129mod web;
130pub mod workflow_messages;
131pub(crate) mod xml;
132
133use crate::http::register_http_builtins;
134use crate::llm::register_llm_builtins;
135use crate::mcp::register_mcp_builtins;
136use crate::mcp_server::register_mcp_server_builtins;
137use crate::vm::Vm;
138
139pub(crate) use crate::schema::{json_to_vm_value, schema_result_value};
140pub(crate) fn set_thread_source_dir(dir: &std::path::Path) {
141    process::set_thread_source_dir(dir);
142}
143
144/// Register core builtins: pure/deterministic, no I/O.
145pub fn register_core_stdlib(vm: &mut Vm) {
146    types::register_type_builtins(vm);
147    math::register_math_builtins(vm);
148    strings::register_string_builtins(vm);
149    json::register_json_builtins(vm);
150    json_stream::register_json_stream_builtins(vm);
151    xml::register_xml_builtins(vm);
152    datetime::register_datetime_builtins(vm);
153    diff::register_diff_builtins(vm);
154    #[cfg(feature = "content")]
155    document::register_document_builtins(vm);
156    calendar::register_calendar_builtins(vm);
157    cron::register_cron_builtins(vm);
158    regex::register_regex_builtins(vm);
159    bytes::register_bytes_builtins(vm);
160    #[cfg(feature = "compression")]
161    compression::register_compression_builtins(vm);
162    command_policy::register_command_policy_builtins(vm);
163    runtime_scope::register_runtime_scope_builtins(vm);
164    crypto::register_crypto_builtins(vm);
165    csv::register_csv_builtins(vm);
166    junit::register_junit_builtins(vm);
167    multipart::register_multipart_builtins(vm);
168    url_parse::register_url_builtins(vm);
169    #[cfg(feature = "content")]
170    web::register_web_builtins(vm);
171    cookies::register_cookie_builtins(vm);
172    path::register_path_helper_builtins(vm);
173    sets::register_set_builtins(vm);
174    collections::register_collection_builtins(vm);
175    iter::register_iter_builtins(vm);
176    event_log::register_event_log_builtins(vm);
177    durable_step::register_durable_step_builtins(vm);
178    channels::register_channel_builtins(vm);
179    channel_guardrails::register_channel_guardrail_builtins(vm);
180    shapes::register_shape_builtins(vm);
181    testing::register_testing_builtins(vm);
182    flow::register_flow_builtins(vm);
183    lifecycle_receipts::register_lifecycle_receipt_builtins(vm);
184    net_policy::register_net_policy_builtins(vm);
185    http_response::register_http_response_builtins(vm);
186    portable::register_portable_builtins(vm);
187}
188
189/// Register I/O builtins (requires OS access).
190pub fn register_io_stdlib(vm: &mut Vm) {
191    io::register_io_builtins(vm);
192    host::register_host_builtins(vm);
193    fs::register_fs_builtins(vm);
194    package_snapshot::register_package_snapshot_builtins(vm);
195    files::register_file_builtins(vm);
196    git::register_git_builtins(vm);
197    vision::register_vision_builtins(vm);
198    agent_state::register_agent_state_builtins(vm);
199    memory::register_memory_builtins(vm);
200    session_store::register_session_store_builtins(vm);
201    net::register_net_builtins(vm);
202    process::register_process_builtins(vm);
203    process::register_path_builtins(vm);
204    sandbox::register_sandbox_builtins(vm);
205    // Clock builtins overlay process::timestamp/elapsed so they honor
206    // mock_time / advance_time. Register AFTER process to take precedence.
207    clock::register_clock_builtins(vm);
208    crate::durable_rate_limit::register_durable_rate_limit_builtins(vm);
209    testbench::register_testbench_builtins(vm);
210    project::register_project_builtins(vm);
211    grounding::register_grounding_builtins(vm);
212    tracing::register_tracing_builtins(vm);
213    observability::register_observability_builtins(vm);
214    timing::register_timing_builtins(vm);
215    tui::register_tui_builtins(vm);
216}
217
218fn register_agent_stdlib_before_llm(vm: &mut Vm) {
219    concurrency::register_concurrency_builtins(vm);
220    connectors::register_connector_builtins(vm);
221    review::register_review_builtins(vm);
222    secret_scan::register_secret_scan_builtins(vm);
223    tools::register_tool_builtins(vm);
224    tool_hooks::register_tool_hooks_builtins(vm);
225    crate::composition::register_composition_builtins(vm);
226    skills::register_skill_builtins(vm);
227    agents_daemon::register_daemon_builtins(vm);
228    triggers_stdlib::register_trigger_builtins(vm);
229    #[cfg(feature = "postgres")]
230    postgres::register_postgres_builtins(vm);
231    #[cfg(feature = "sqlite")]
232    sqlite::register_sqlite_builtins(vm);
233    monitors::register_monitor_builtins(vm);
234    hitl::register_hitl_builtins(vm);
235    hitl_read::register_hitl_read_builtins(vm);
236    waitpoint::register_waitpoint_builtins(vm);
237    supervisor::register_supervisor_builtins(vm);
238    agents::register_agent_builtins(vm);
239    pool::register_pool_builtins(vm);
240    oauth_storage::register_oauth_storage_builtins(vm);
241    oauth_dynreg::register_oauth_dynreg_builtins(vm);
242    token_redaction::register_token_redaction_builtins(vm);
243    agent_sessions::register_agent_session_builtins(vm);
244    artifact_emit::register_artifact_emit_builtins(vm);
245    external_agent::register_external_agent_builtins(vm);
246    path_scope_guard::register_path_scope_guard_builtins(vm);
247    workflow_messages::register_workflow_message_builtins(vm);
248    transcript_compact::register_transcript_compaction_builtins(vm);
249    compaction::register_compaction_builtins(vm);
250    transcript_project::register_transcript_projection_builtins(vm);
251    assemble::register_assemble_context_builtin(vm);
252    crate::egress::register_egress_builtins(vm);
253    crate::security::register_security_builtins(vm);
254    register_http_builtins(vm);
255    jsonrpc::register_jsonrpc_builtins(vm);
256}
257
258fn register_agent_stdlib_after_llm(vm: &mut Vm) {
259    register_mcp_builtins(vm);
260    register_mcp_server_builtins(vm);
261    crate::step_runtime::register_step_builtins(vm);
262}
263
264/// Register agent builtins (requires network access and async runtime).
265pub fn register_agent_stdlib(vm: &mut Vm) {
266    register_agent_stdlib_before_llm(vm);
267    register_llm_builtins(vm);
268    register_agent_stdlib_after_llm(vm);
269}
270
271/// Register all standard builtins on a VM (core + io + agent). Also
272/// installs the macro-emitted signature slice into the parser registry
273/// (idempotent under repeat calls with the same slice pointer).
274pub fn register_vm_stdlib(vm: &mut Vm) {
275    register_core_stdlib(vm);
276    register_io_stdlib(vm);
277    register_agent_stdlib(vm);
278    vm.project_declared_capability_methods();
279    if vm.harness().is_none() {
280        vm.set_harness(crate::harness::Harness::real());
281    }
282    if harn_parser::legacy_ambient_capabilities_enabled() && vm.global("harness").is_none() {
283        let harness = vm
284            .root_harness_value()
285            .expect("register_vm_stdlib installs a root Harness");
286        vm.set_global("harness", harness);
287    }
288    vm.project_legacy_capability_globals();
289    harn_builtin_registry::install_builtin_manifest(all_builtin_manifest());
290}
291
292pub(crate) fn rebind_execution_state_builtins(vm: &mut Vm) {
293    concurrency::register_concurrency_builtins(vm);
294}
295
296fn stdlib_probe_vm() -> Vm {
297    let mut vm = Vm::new();
298    register_vm_stdlib(&mut vm);
299    // Name-only/metadata introspection never accesses this path, but passing
300    // a real per-platform temp dir keeps registration logic honest if a
301    // callee someday validates its parent.
302    let tmp = std::env::temp_dir();
303    crate::store::register_store_builtins(&mut vm, &tmp);
304    crate::checkpoint::register_checkpoint_builtins(&mut vm, &tmp, "default");
305    crate::metadata::register_metadata_builtins(&mut vm, &tmp);
306    // Install the macro-emitted signatures into the parser registry so any
307    // probe-driven name/metadata query (e.g. the alignment test) sees the
308    // post-migration sig set. Idempotent under repeat install with the same
309    // pointer (which `all_builtin_manifest()` guarantees).
310    harn_builtin_registry::install_builtin_manifest(all_builtin_manifest());
311    vm
312}
313
314/// Aggregate of every `#[harn_builtin]`-emitted `VmBuiltinDef` in the stdlib.
315///
316/// Backed by the `linkme::distributed_slice` declared on
317/// [`crate::stdlib::macros::ALL_BUILTIN_DEFS`] — every annotated fn
318/// contributes one entry automatically at link time. Keep builtin registration
319/// on this distributed slice instead of per-module arrays plus a central
320/// hand-maintained aggregator.
321///
322/// **Force-link warning** (linkme issue #36): rlib dead-code stripping
323/// can drop these statics when `harn-vm` is linked transitively. Every
324/// binary that exercises builtins (`harn-cli`, `harn-lsp`, `harn-lint`,
325/// `harn-serve`, `harn-dap`) calls [`force_link`] near `main()` to defeat
326/// the stripping. The alignment test
327/// `linkme_distributed_slice_populates_with_all_builtins` catches a silent
328/// regression by asserting the slice is non-empty.
329pub fn all_builtin_defs() -> &'static [&'static macros::VmBuiltinDef] {
330    let defs = &macros::ALL_BUILTIN_DEFS;
331    validate_builtin_contracts(defs);
332    defs
333}
334
335fn validate_builtin_contracts(defs: &[&macros::VmBuiltinDef]) {
336    use harn_builtin_meta::BuiltinExposure;
337    let mut source_names = std::collections::BTreeSet::new();
338    let mut capability_methods = std::collections::BTreeSet::new();
339    for def in defs {
340        assert!(
341            def.contract.is_declared(),
342            "builtin `{}` has no typed exposure/effect contract",
343            def.sig.name
344        );
345        assert!(
346            !matches!(def.contract.exposure, BuiltinExposure::PureGlobal)
347                || def.contract.effects.is_empty(),
348            "ambient global builtin `{}` declares effects; effects must flow through Harness",
349            def.sig.name
350        );
351        if let BuiltinExposure::CapabilityFunction { authority_argument } = def.contract.exposure {
352            assert!(
353                !def.contract.effects.is_empty(),
354                "capability function `{}` must declare effects",
355                def.sig.name
356            );
357            assert!(
358                usize::from(authority_argument) < def.sig.params.len(),
359                "capability function `{}` authority argument is out of range",
360                def.sig.name
361            );
362        }
363        if def.runtime_only {
364            assert!(
365                matches!(def.contract.exposure, BuiltinExposure::RuntimeInternal),
366                "runtime_only builtin `{}` must use runtime_internal exposure",
367                def.sig.name
368            );
369        }
370        if let BuiltinExposure::HarnessMethod { method, .. } = def.contract.exposure {
371            assert!(
372                !method.is_empty(),
373                "harness method for `{}` cannot be empty",
374                def.sig.name
375            );
376            let BuiltinExposure::HarnessMethod { capability, method } = def.contract.exposure
377            else {
378                unreachable!()
379            };
380            assert!(
381                capability_methods.insert((capability, method)),
382                "duplicate contract for harness.{}.{}",
383                capability.field_name(),
384                method
385            );
386        }
387        if matches!(
388            def.contract.exposure,
389            BuiltinExposure::PureGlobal
390                | BuiltinExposure::CapabilityFunction { .. }
391                | BuiltinExposure::PrivilegedWire
392                | BuiltinExposure::HarnessMethod { .. }
393        ) {
394            assert!(
395                source_names.insert(def.sig.name),
396                "duplicate source contract name `{}`",
397                def.sig.name
398            );
399        }
400    }
401}
402
403/// Force-link entry point: a `pub fn` that touches `ALL_BUILTIN_DEFS` so
404/// the linker keeps every `#[harn_builtin]`-emitted static. Drivers
405/// (`harn-cli`, `harn-lsp`, etc.) call this once at startup. Doing nothing
406/// at runtime is fine — the side effect is purely a link-time signal.
407///
408/// See [`linkme issue #36`](https://github.com/dtolnay/linkme/issues/36)
409/// for why the explicit touch is necessary on every supported target.
410pub fn force_link() {
411    // `black_box` prevents LLVM from constant-folding the length read away.
412    // The `>= 1` guard never trips at runtime but is a load-bearing safety
413    // net: it converts a silent slice-empty regression into a panic that
414    // surfaces at the first builtin call instead of a confusing
415    // `HARN-NAM-002` somewhere down the line.
416    let len = std::hint::black_box(macros::ALL_BUILTIN_DEFS.len());
417    assert!(
418        len >= 1,
419        "linkme distributed_slice ALL_BUILTIN_DEFS is empty — \
420         the binary is missing `harn_vm::stdlib::force_link()` at startup, \
421         or the linker stripped the harn-vm rlib statics (see linkme issue #36)"
422    );
423}
424
425/// Driver-facing immutable manifest for parser, IR, policy, and docs.
426pub fn all_builtin_manifest() -> &'static [&'static harn_builtin_registry::BuiltinManifestEntry] {
427    use std::sync::OnceLock;
428    static AGG: OnceLock<Vec<&'static harn_builtin_registry::BuiltinManifestEntry>> =
429        OnceLock::new();
430    AGG.get_or_init(|| {
431        let mut out = Vec::new();
432        let mut capability_methods = std::collections::BTreeSet::new();
433        for def in all_builtin_defs() {
434            if def.runtime_only {
435                continue;
436            }
437            out.push(
438                Box::leak(Box::new(harn_builtin_registry::BuiltinManifestEntry {
439                    name: def.sig.name,
440                    canonical_name: def.sig.name,
441                    signature: &def.sig,
442                    contract: def.contract,
443                })) as &'static harn_builtin_registry::BuiltinManifestEntry,
444            );
445            if let harn_builtin_meta::BuiltinExposure::HarnessMethod { capability, method } =
446                def.contract.exposure
447            {
448                capability_methods.insert((capability, method));
449            }
450            for alias in def.aliases {
451                let signature = Box::leak(Box::new(harn_builtin_meta::BuiltinSignature {
452                    name: alias,
453                    ..def.sig
454                }));
455                out.push(
456                    Box::leak(Box::new(harn_builtin_registry::BuiltinManifestEntry {
457                        name: alias,
458                        canonical_name: def.sig.name,
459                        signature,
460                        contract: def.contract,
461                    })) as &'static harn_builtin_registry::BuiltinManifestEntry,
462                );
463            }
464        }
465        for entry in harn_capability_contracts::manifest() {
466            let harn_builtin_meta::BuiltinExposure::HarnessMethod { capability, method } =
467                entry.contract.exposure
468            else {
469                unreachable!("leaf capability manifest contains a non-method contract")
470            };
471            if !capability_methods.insert((capability, method)) {
472                let runtime_entry = out
473                    .iter()
474                    .find(|candidate| candidate.contract.exposure == entry.contract.exposure)
475                    .expect("duplicate capability key must have a runtime manifest entry");
476                assert_eq!(
477                    runtime_entry.contract,
478                    entry.contract,
479                    "runtime effect contract drift for harness.{}.{}",
480                    capability.field_name(),
481                    method
482                );
483                assert_eq!(
484                    runtime_entry.signature,
485                    entry.signature,
486                    "runtime signature drift for harness.{}.{}",
487                    capability.field_name(),
488                    method
489                );
490                continue;
491            }
492            out.push(*entry);
493        }
494        for group in harn_builtin_meta::host_capabilities::all_host_capability_groups() {
495            for method in group.methods {
496                if !capability_methods.insert((group.capability, *method)) {
497                    continue;
498                }
499                let internal_name: &'static str = Box::leak(
500                    format!("__cap_{}_{}", group.capability.field_name(), method).into_boxed_str(),
501                );
502                let params: &'static [harn_builtin_meta::Param] =
503                    Box::leak(Box::new([harn_builtin_meta::Param::new(
504                        "request",
505                        harn_builtin_meta::Ty::Named("dict"),
506                    )]));
507                let signature = Box::leak(Box::new(harn_builtin_meta::BuiltinSignature::simple(
508                    internal_name,
509                    params,
510                    harn_builtin_meta::Ty::Named("dict"),
511                )));
512                out.push(Box::leak(Box::new(
513                    harn_builtin_registry::BuiltinManifestEntry {
514                        name: internal_name,
515                        canonical_name: internal_name,
516                        signature,
517                        contract: harn_builtin_meta::BuiltinContract::harness(
518                            group.capability,
519                            method,
520                            group.effects,
521                        ),
522                    },
523                )));
524            }
525        }
526        out
527    })
528    .as_slice()
529}
530
531/// Indexed view of the authoritative builtin manifest.
532///
533/// Runtime dispatch reaches this boundary for every typed Harness call. Keep
534/// lookup policy here with the manifest owner instead of making each consumer
535/// linearly rescan the full registry. The nested capability map also accepts a
536/// borrowed `&str`, so a method call does not allocate an owned lookup key.
537struct BuiltinManifestIndex {
538    by_name: std::collections::HashMap<
539        &'static str,
540        &'static harn_builtin_registry::BuiltinManifestEntry,
541    >,
542    by_capability: std::collections::HashMap<
543        harn_builtin_meta::CapabilityId,
544        std::collections::HashMap<
545            &'static str,
546            &'static harn_builtin_registry::BuiltinManifestEntry,
547        >,
548    >,
549    recorded_effects_by_name: std::collections::HashMap<
550        &'static str,
551        &'static harn_builtin_registry::BuiltinManifestEntry,
552    >,
553}
554
555fn builtin_manifest_index() -> &'static BuiltinManifestIndex {
556    use harn_builtin_meta::BuiltinExposure;
557    use std::sync::OnceLock;
558
559    static INDEX: OnceLock<BuiltinManifestIndex> = OnceLock::new();
560    INDEX.get_or_init(|| {
561        let mut by_name = std::collections::HashMap::new();
562        let mut by_capability: std::collections::HashMap<
563            harn_builtin_meta::CapabilityId,
564            std::collections::HashMap<
565                &'static str,
566                &'static harn_builtin_registry::BuiltinManifestEntry,
567            >,
568        > = std::collections::HashMap::new();
569        let mut recorded_effects_by_name = std::collections::HashMap::new();
570        for entry in all_builtin_manifest() {
571            assert!(
572                by_name.insert(entry.name, *entry).is_none(),
573                "duplicate builtin manifest name `{}`",
574                entry.name
575            );
576            // An alias repeats its primary's contract under a second name, so
577            // only the canonical entry may claim the capability method.
578            if let BuiltinExposure::HarnessMethod { capability, method } = entry.contract.exposure {
579                if entry.is_canonical() {
580                    assert!(
581                        by_capability
582                            .entry(capability)
583                            .or_default()
584                            .insert(method, *entry)
585                            .is_none(),
586                        "duplicate Harness method manifest entry `harness.{}.{method}`",
587                        capability.field_name()
588                    );
589                }
590            }
591            if matches!(
592                entry.contract.exposure,
593                BuiltinExposure::CapabilityFunction { .. }
594                    | BuiltinExposure::HarnessMethod { .. }
595                    | BuiltinExposure::PrivilegedWire
596            ) && !entry.contract.effects.is_empty()
597            {
598                recorded_effects_by_name.insert(entry.name, *entry);
599            }
600        }
601        BuiltinManifestIndex {
602            by_name,
603            by_capability,
604            recorded_effects_by_name,
605        }
606    })
607}
608
609/// Resolve the registry name behind a public `harness.<capability>.<method>`
610/// path.
611///
612/// Observation, diagnostics, and tooling classify a capability call by the
613/// same registry entry the removed ambient global resolved to, so a call
614/// through the typed handle profiles and audits identically.
615pub fn builtin_for_harness_path(path: &str) -> Option<&'static str> {
616    let (field, method) = path.strip_prefix("harness.")?.split_once('.')?;
617    let capability = harn_builtin_meta::CapabilityId::from_field_name(field)?;
618    capability_method_manifest_entry(capability, method).map(|entry| entry.name)
619}
620
621/// Resolve the typed Harness method that replaced an ambient builtin name.
622pub fn harness_method_for_builtin(
623    name: &str,
624) -> Option<(harn_builtin_meta::CapabilityId, &'static str)> {
625    match builtin_manifest_entry(name)?.contract.exposure {
626        harn_builtin_meta::BuiltinExposure::HarnessMethod { capability, method } => {
627            Some((capability, method))
628        }
629        _ => None,
630    }
631}
632
633/// Resolve a source/runtime builtin contract without rescanning the manifest.
634pub fn builtin_manifest_entry(
635    name: &str,
636) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
637    builtin_manifest_index().by_name.get(name).copied()
638}
639
640/// Resolve the contract for one typed Harness method without allocation.
641pub fn capability_method_manifest_entry(
642    capability: harn_builtin_meta::CapabilityId,
643    method: &str,
644) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
645    builtin_manifest_index()
646        .by_capability
647        .get(&capability)
648        .and_then(|methods| methods.get(method))
649        .copied()
650}
651
652/// Resolve only builtin contracts that can emit runtime effect receipts.
653///
654/// Pure builtins dominate VM call volume. Keeping them out of this projection
655/// makes their receipt check one negative hash lookup instead of a full
656/// manifest lookup plus exposure classification on every call.
657pub fn recorded_effect_builtin_manifest_entry(
658    name: &str,
659) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
660    builtin_manifest_index()
661        .recorded_effects_by_name
662        .get(name)
663        .copied()
664}
665
666mod harness_migration;
667
668pub use harness_migration::{
669    harness_migration_for_builtin, HarnessBuiltinArgumentMigration, HarnessBuiltinMigration,
670};
671
672/// Register every `#[harn_builtin]`-emitted def on the given VM. Drivers
673/// that build the full stdlib via `register_vm_stdlib` get this for free —
674/// each module's `register_*_builtins` walks its `MODULE_BUILTINS` slice.
675/// This helper is exposed for embedders / tests that want a one-call entry.
676pub fn register_all_macro_builtins(vm: &mut Vm) {
677    for def in all_builtin_defs() {
678        vm.register_builtin_def(def);
679    }
680}
681
682/// Return the canonical list of all stdlib builtin names. Used by
683/// harn-lint and harn-lsp to avoid hardcoded duplicate lists.
684pub fn stdlib_builtin_names() -> Vec<String> {
685    let vm = stdlib_probe_vm();
686    let mut names = vm.builtin_names();
687    // Special opcodes/keywords, not registered builtins, but linter
688    // should recognize them as valid function calls.
689    for extra in harn_parser::builtin_signatures::LANGUAGE_INTRINSICS {
690        names.push(extra.to_string());
691    }
692    names
693}
694
695/// Return discoverable metadata for registered stdlib builtins.
696pub fn stdlib_builtin_metadata() -> Vec<crate::vm::VmBuiltinMetadata> {
697    stdlib_probe_vm().builtin_metadata()
698}
699
700/// Declared exposure of a registered builtin, or `None` when the VM registers
701/// no builtin by that name.
702///
703/// `harn_builtin_meta` calls itself "the semantic owner for which script
704/// surface may reach a builtin", and its vocabulary is explicit:
705/// `PrivilegedWire` is documented as "User modules cannot name or re-export
706/// it" and `RuntimeInternal` as "never source-visible". The typechecker
707/// enforces that. Nothing else read it — [`stdlib_builtin_names`] answers the
708/// different question of what the VM has *registered*, so a consumer that
709/// treats that set as the callable surface goes quiet on exactly the calls the
710/// typechecker will reject. `host_call` is the case that surfaced it: declared
711/// `privileged_wire`, rejected by `harn check`, and silent under `harn lint`
712/// across 114 call sites in one downstream repo (harn#6126).
713pub fn builtin_exposure(name: &str) -> Option<harn_builtin_meta::BuiltinExposure> {
714    use std::sync::OnceLock;
715    static BY_NAME: OnceLock<
716        std::collections::HashMap<String, harn_builtin_meta::BuiltinExposure>,
717    > = OnceLock::new();
718    BY_NAME
719        .get_or_init(|| {
720            stdlib_probe_vm()
721                .builtin_metadata()
722                .into_iter()
723                .map(|entry| (entry.name().to_string(), entry.contract().exposure))
724                .collect()
725        })
726        .get(name)
727        .copied()
728}
729
730/// The declared harness method that owns a host-wire operation name.
731#[derive(Debug, Clone, Copy, PartialEq, Eq)]
732pub struct HarnessMethodTarget {
733    pub capability: harn_builtin_meta::CapabilityId,
734    pub method: &'static str,
735}
736
737impl HarnessMethodTarget {
738    /// Source spelling of the call target, without the harness root.
739    pub fn path(&self) -> String {
740        format!("{}.{}", self.capability.field_name(), self.method)
741    }
742}
743
744/// Resolve a host-wire operation name such as `"prmonitor.run_commands"` to
745/// the `harness.<capability>.<method>` that declares it, when one exists.
746///
747/// Host wires carry their destination as a *string*, so a call to one is
748/// opaque to every name-keyed check in the toolchain: the operation name is
749/// data, not a symbol. Reading it back through the declared contract is what
750/// turns "you cannot call this" into "call this instead", which is the whole
751/// difference for a downstream repo holding hundreds of such call sites
752/// (harn#6126).
753///
754/// Resolution composes two owners rather than adding a third table.
755/// `capability_binding_for_schema` goes first because it owns the deliberate
756/// remappings — a `"session.open"` wire is `harness.agent.session_open`, not
757/// the `harness.session` handle a namespace match alone would reach. Its
758/// `HOST_CAPABILITY_GROUPS` domain is only part of the declared surface
759/// (5 of 86 real targets), so the manifest answers the rest.
760///
761/// Returns `None` when the namespace names no capability, when the capability
762/// declares no such method, or when a normalized match would be ambiguous. A
763/// wrong destination is worse than none: it would aim a migration at the wrong
764/// method.
765pub fn harness_method_for_host_operation(operation: &str) -> Option<HarnessMethodTarget> {
766    use harn_builtin_meta::{wire_identifier_key, BuiltinExposure, CapabilityId};
767    use std::collections::HashMap;
768    use std::sync::OnceLock;
769
770    let (namespace, operation_method) = operation.split_once('.')?;
771    if let Some((capability, method)) =
772        harn_builtin_meta::host_capabilities::capability_binding_for_schema(
773            namespace,
774            operation_method,
775        )
776    {
777        return Some(HarnessMethodTarget { capability, method });
778    }
779
780    static BY_CAPABILITY: OnceLock<HashMap<CapabilityId, Vec<&'static str>>> = OnceLock::new();
781    let declared = BY_CAPABILITY.get_or_init(|| {
782        let mut index: HashMap<CapabilityId, Vec<&'static str>> = HashMap::new();
783        // The manifest, not a probe VM: `stdlib_probe_vm().builtin_metadata()`
784        // sees only what that VM registers — 334 harness methods against the
785        // manifest's 978 — and the ones it misses are the host-implemented
786        // capabilities a wire actually targets. Alias entries repeat a
787        // primary's contract under a second name, so a capability-method
788        // projection must take only canonical entries.
789        for entry in all_builtin_manifest() {
790            if !entry.is_canonical() {
791                continue;
792            }
793            if let BuiltinExposure::HarnessMethod { capability, method } = entry.contract.exposure {
794                index.entry(capability).or_default().push(method);
795            }
796        }
797        index
798    });
799
800    // Wires predate the typed vocabulary and spell the namespace without a
801    // separator, so `prmonitor` names the `pr_monitor` capability.
802    let capability = CapabilityId::from_host_namespace(namespace)?;
803    let methods = declared.get(&capability)?;
804    if let Some(exact) = methods.iter().find(|method| **method == operation_method) {
805        return Some(HarnessMethodTarget {
806            capability,
807            method: exact,
808        });
809    }
810    let wanted = wire_identifier_key(operation_method);
811    let mut lenient = methods
812        .iter()
813        .filter(|method| wire_identifier_key(method) == wanted);
814    let only = lenient.next()?;
815    lenient.next().is_none().then_some(HarnessMethodTarget {
816        capability,
817        method: only,
818    })
819}
820
821/// Whether Harn source may write this builtin's bare name in a call.
822///
823/// A harness method is reached as `harness.<capability>.<method>` rather than
824/// as a global, so it is not bare-nameable either. `Undeclared` is a migration
825/// state rather than a promise, and answering `true` there keeps this
826/// predicate from inventing a restriction the contract has not made yet.
827pub fn exposure_is_source_nameable(exposure: harn_builtin_meta::BuiltinExposure) -> bool {
828    use harn_builtin_meta::BuiltinExposure;
829    match exposure {
830        BuiltinExposure::PureGlobal
831        | BuiltinExposure::CapabilityFunction { .. }
832        | BuiltinExposure::Undeclared => true,
833        BuiltinExposure::HarnessMethod { .. }
834        | BuiltinExposure::PrivilegedWire
835        | BuiltinExposure::RuntimeInternal => false,
836    }
837}
838
839/// Reset thread-local stdlib state. Call between test runs.
840///
841/// Note: `long_running::reset_state()` is intentionally NOT called here
842/// because that store is process-global, not thread-local. Wiping it
843/// from a per-test reset hook lets one test cancel another test's
844/// in-flight worker thread (and lose its `agent_inbox::push`
845/// notification), which surfaces as `walk_dir_long_running` /
846/// `glob_long_running` timing out under parallel test load. The two
847/// call sites that genuinely need a clean handle store —
848/// `stdlib::fs::tests::{walk_dir_long_running,glob_long_running}` — call
849/// `long_running::reset_state()` explicitly while holding
850/// `LONG_RUNNING_TEST_LOCK`.
851pub fn reset_stdlib_state() {
852    logging::reset_logging_state();
853    process::reset_process_state();
854    clock::reset_clock_state();
855    io::reset_io_state();
856    sandbox::reset_sandbox_state();
857    git::reset_git_state();
858    fs::reset_fs_state();
859    json::reset_json_state();
860    json_stream::reset_json_stream_state();
861    host::reset_host_state();
862    host::reset_scoped_host_state();
863    observability::reset_observability_state();
864    timing::reset_timing_state();
865    durable_step::reset_durable_step_state();
866    crate::egress::reset_egress_policy_for_host();
867    hitl::reset_hitl_state();
868    crate::http::reset_http_state();
869    crate::external_agent::reset_external_agent_state();
870    monitors::reset_monitor_state();
871    waitpoint::reset_waitpoint_state();
872    triggers_stdlib::reset_auto_resume_timeouts();
873    compaction::reset_compaction_state();
874    agents::reset_agent_worker_state();
875    agents::workflow::reset_workflow_run_states();
876    pool::reset_pool_state();
877    #[cfg(feature = "postgres")]
878    postgres::reset_postgres_state();
879    #[cfg(feature = "sqlite")]
880    sqlite::reset_sqlite_state();
881    supervisor::reset_supervisor_state();
882    agents::records::reset_eval_metrics();
883    agents::records::reset_friction_events();
884    tools::clear_current_tool_registry();
885    tools::clear_tool_synthesis_cache();
886    vision::reset_vision_state();
887    crate::skills::clear_current_skill_registry();
888    template::reset_prompt_registry();
889    crate::triggers::clear_webhook_intake_state();
890    crate::llm::cache::reset_in_process_cache_state();
891}
892
893#[cfg(test)]
894mod tests {
895    use super::*;
896
897    #[test]
898    fn host_operation_resolves_to_its_declared_harness_method() {
899        let target = harness_method_for_host_operation("ast.outline")
900            .expect("`ast.outline` is declared by harn-hostlib");
901        assert_eq!(target.path(), "ast.outline");
902    }
903
904    #[test]
905    fn host_operation_namespace_matching_ignores_underscores() {
906        // Host wires spell the namespace without a separator. The capability
907        // field name has one, and both must reach the same method.
908        let squashed = harness_method_for_host_operation("prmonitor.run_commands")
909            .expect("`prmonitor` names the `pr_monitor` capability");
910        let spelled = harness_method_for_host_operation("pr_monitor.run_commands")
911            .expect("the declared spelling resolves too");
912        assert_eq!(squashed, spelled);
913        assert_eq!(squashed.path(), "pr_monitor.run_commands");
914    }
915
916    #[test]
917    fn host_operation_without_a_declared_owner_resolves_to_nothing() {
918        // A wildcard, an unknown capability, and a real capability with no
919        // such method. Each must decline rather than guess: naming the wrong
920        // destination would point a migration at the wrong method.
921        for operation in [
922            "ast.*",
923            "capability.operation",
924            "runtime.set_result",
925            "ast",
926            "",
927        ] {
928            assert_eq!(
929                harness_method_for_host_operation(operation),
930                None,
931                "`{operation}` must not resolve"
932            );
933        }
934    }
935
936    #[test]
937    fn host_operation_honors_the_schema_tables_deliberate_remapping() {
938        // Session persistence is owned by `HarnessAgent`, so the `session.*`
939        // hostlib schema is exposed as `harness.agent.session_*`. A resolver
940        // that matched the namespace against the capability vocabulary would
941        // answer `harness.session.open` — a real handle, and the wrong one.
942        // This is why the resolver defers to `capability_binding_for_schema`
943        // rather than keeping a second table.
944        let target = harness_method_for_host_operation("session.open")
945            .expect("`session.open` is a declared hostlib schema operation");
946        assert_eq!(target.path(), "agent.session_open");
947    }
948
949    #[tokio::test(flavor = "current_thread")]
950    async fn register_vm_stdlib_passes_default_harness_only_to_main() {
951        let chunk = crate::compile_source(
952            r"
953fn __probe_harness_clock(clock: HarnessClock) {
954  const now = clock.now_ms()
955  return now >= 0
956}
957
958fn main(harness: Harness) {
959  return __probe_harness_clock(harness.clock)
960}
961",
962        )
963        .expect("compile harness clock probe");
964        let mut vm = Vm::new();
965        register_vm_stdlib(&mut vm);
966
967        assert!(vm.root_harness_value().is_some());
968        assert!(vm.global("harness").is_none());
969        let result = vm
970            .execute(&chunk)
971            .await
972            .expect("execute harness clock probe");
973        assert!(matches!(result, crate::value::VmValue::Bool(true)));
974    }
975
976    /// `harn_stdlib::builtin_reexports` names builtins from a crate that
977    /// cannot see the builtin registry, so nothing there can catch a typo or a
978    /// rename. This is that check: every re-exported name must resolve to a
979    /// real builtin, or `import { … } from "std/…"` binds a reference to
980    /// nothing and fails at the call site instead of the import.
981    #[test]
982    fn every_stdlib_builtin_reexport_names_a_registered_builtin() {
983        let registered: std::collections::HashSet<&str> = all_builtin_defs()
984            .iter()
985            .flat_map(|def| std::iter::once(def.sig.name).chain(def.aliases.iter().copied()))
986            .collect();
987
988        let mut checked = 0;
989        for entry in harn_stdlib::STDLIB_SOURCES {
990            for name in harn_stdlib::builtin_reexports(entry.module) {
991                assert!(
992                    registered.contains(name),
993                    "std/{} re-exports '{name}', which is not a registered builtin",
994                    entry.module
995                );
996                checked += 1;
997            }
998        }
999        assert!(
1000            checked > 0,
1001            "no re-exports were checked — the table or the module list is not being read"
1002        );
1003    }
1004
1005    #[test]
1006    fn ambient_effect_builtin_allowlist_is_empty() {
1007        let offenders = all_builtin_defs()
1008            .iter()
1009            .filter(|def| {
1010                matches!(
1011                    def.contract.exposure,
1012                    harn_builtin_meta::BuiltinExposure::PureGlobal
1013                ) && !def.contract.effects.is_empty()
1014            })
1015            .map(|def| def.sig.name)
1016            .collect::<Vec<_>>();
1017
1018        assert!(
1019            offenders.is_empty(),
1020            "ambient effect builtins are forbidden; route these through Harness: {offenders:?}"
1021        );
1022    }
1023
1024    #[test]
1025    fn harness_builtin_migrations_preserve_canonical_call_shapes() {
1026        use harn_builtin_meta::CapabilityId;
1027
1028        assert_eq!(
1029            harness_migration_for_builtin("provider_capabilities"),
1030            Some(HarnessBuiltinMigration {
1031                capability: CapabilityId::Llm,
1032                method: "provider_capabilities",
1033                arguments: HarnessBuiltinArgumentMigration::Forward,
1034            })
1035        );
1036        assert_eq!(
1037            harness_migration_for_builtin("log_info"),
1038            Some(HarnessBuiltinMigration {
1039                capability: CapabilityId::Observability,
1040                method: "log_info",
1041                arguments: HarnessBuiltinArgumentMigration::Forward,
1042            })
1043        );
1044        assert_eq!(
1045            harness_migration_for_builtin("runtime_introspection"),
1046            Some(HarnessBuiltinMigration {
1047                capability: CapabilityId::Runtime,
1048                method: "introspection",
1049                arguments: HarnessBuiltinArgumentMigration::Forward,
1050            })
1051        );
1052        assert_eq!(
1053            harness_migration_for_builtin("project_fingerprint"),
1054            Some(HarnessBuiltinMigration {
1055                capability: CapabilityId::Project,
1056                method: "fingerprint",
1057                arguments: HarnessBuiltinArgumentMigration::Forward,
1058            })
1059        );
1060        assert_eq!(
1061            harness_migration_for_builtin("project_scan_tree_native"),
1062            Some(HarnessBuiltinMigration {
1063                capability: CapabilityId::Project,
1064                method: "scan_tree",
1065                arguments: HarnessBuiltinArgumentMigration::Forward,
1066            })
1067        );
1068        assert_eq!(
1069            harness_migration_for_builtin("llm_call"),
1070            Some(HarnessBuiltinMigration {
1071                capability: CapabilityId::Llm,
1072                method: "call",
1073                arguments: HarnessBuiltinArgumentMigration::Forward,
1074            })
1075        );
1076        assert_eq!(
1077            harness_migration_for_builtin("llm_call_structured"),
1078            Some(HarnessBuiltinMigration {
1079                capability: CapabilityId::Llm,
1080                method: "call_structured",
1081                arguments: HarnessBuiltinArgumentMigration::Forward,
1082            })
1083        );
1084        assert_eq!(
1085            harness_migration_for_builtin("security_policy"),
1086            Some(HarnessBuiltinMigration {
1087                capability: CapabilityId::System,
1088                method: "security_policy",
1089                arguments: HarnessBuiltinArgumentMigration::Forward,
1090            })
1091        );
1092        assert_eq!(
1093            harness_migration_for_builtin("llm_provider_status"),
1094            Some(HarnessBuiltinMigration {
1095                capability: CapabilityId::Llm,
1096                method: "providers",
1097                arguments: HarnessBuiltinArgumentMigration::Forward,
1098            })
1099        );
1100        assert_eq!(
1101            harness_migration_for_builtin("agent_session_current_id"),
1102            Some(HarnessBuiltinMigration {
1103                capability: CapabilityId::Agent,
1104                method: "current_id",
1105                arguments: HarnessBuiltinArgumentMigration::Forward,
1106            })
1107        );
1108        assert_eq!(
1109            harness_migration_for_builtin("metadata_set"),
1110            Some(HarnessBuiltinMigration {
1111                capability: CapabilityId::Project,
1112                method: "metadata_set",
1113                arguments: HarnessBuiltinArgumentMigration::RequestRecord(&[
1114                    "dir",
1115                    "namespace",
1116                    "data",
1117                ]),
1118            })
1119        );
1120        assert_eq!(
1121            harness_migration_for_builtin("metadata_save"),
1122            Some(HarnessBuiltinMigration {
1123                capability: CapabilityId::Project,
1124                method: "metadata_save",
1125                arguments: HarnessBuiltinArgumentMigration::RequestRecord(&[]),
1126            })
1127        );
1128        assert_eq!(
1129            harness_migration_for_builtin("platform"),
1130            Some(HarnessBuiltinMigration {
1131                capability: CapabilityId::System,
1132                method: "platform",
1133                arguments: HarnessBuiltinArgumentMigration::CallThenProperty("os"),
1134            })
1135        );
1136        assert_eq!(
1137            harness_migration_for_builtin("arch"),
1138            Some(HarnessBuiltinMigration {
1139                capability: CapabilityId::System,
1140                method: "platform",
1141                arguments: HarnessBuiltinArgumentMigration::CallThenProperty("arch"),
1142            })
1143        );
1144        assert_eq!(
1145            harness_migration_for_builtin("home_dir"),
1146            Some(HarnessBuiltinMigration {
1147                capability: CapabilityId::Fs,
1148                method: "home_dir",
1149                arguments: HarnessBuiltinArgumentMigration::Forward,
1150            })
1151        );
1152        assert_eq!(harness_migration_for_builtin("json_parse"), None);
1153    }
1154
1155    #[test]
1156    fn every_source_named_runtime_callable_has_a_typed_contract() {
1157        let manifest_names = all_builtin_manifest()
1158            .iter()
1159            .map(|entry| entry.name)
1160            .collect::<std::collections::HashSet<_>>();
1161        let offenders = stdlib_probe_vm()
1162            .builtin_names()
1163            .into_iter()
1164            .filter(|name| {
1165                !name.starts_with("__")
1166                    && !manifest_names.contains(name.as_str())
1167                    && !harn_parser::builtin_signatures::is_language_intrinsic(name)
1168            })
1169            .collect::<Vec<_>>();
1170
1171        assert!(
1172            offenders.is_empty(),
1173            "runtime callables without typed source contracts bypass the Harness gate: {offenders:?}"
1174        );
1175    }
1176
1177    #[test]
1178    fn builtin_manifest_indexes_preserve_the_authoritative_contracts() {
1179        use harn_builtin_meta::BuiltinExposure;
1180
1181        let mut capability_entries = 0;
1182        for entry in all_builtin_manifest() {
1183            assert!(
1184                std::ptr::eq(
1185                    builtin_manifest_entry(entry.name).expect("indexed builtin entry"),
1186                    *entry,
1187                ),
1188                "name index projected a different contract for `{}`",
1189                entry.name
1190            );
1191            let should_record = matches!(
1192                entry.contract.exposure,
1193                BuiltinExposure::CapabilityFunction { .. }
1194                    | BuiltinExposure::HarnessMethod { .. }
1195                    | BuiltinExposure::PrivilegedWire
1196            ) && !entry.contract.effects.is_empty();
1197            assert_eq!(
1198                recorded_effect_builtin_manifest_entry(entry.name).is_some(),
1199                should_record,
1200                "recorded-effect index drifted for `{}`",
1201                entry.name
1202            );
1203            if let BuiltinExposure::HarnessMethod { capability, method } = entry.contract.exposure {
1204                capability_entries += 1;
1205                let indexed = capability_method_manifest_entry(capability, method)
1206                    .expect("indexed Harness method entry");
1207                // An alias shares its primary's contract under a second name.
1208                // The capability index answers with the primary either way.
1209                assert_eq!(
1210                    indexed.name,
1211                    entry.canonical_name,
1212                    "capability index projected a different builtin for `harness.{}.{method}`",
1213                    capability.field_name()
1214                );
1215                assert_eq!(
1216                    indexed.contract,
1217                    entry.contract,
1218                    "capability index projected a different contract for `harness.{}.{method}`",
1219                    capability.field_name()
1220                );
1221            }
1222        }
1223        assert!(capability_entries > 0, "no Harness contracts were indexed");
1224        assert!(builtin_manifest_entry("__definitely_missing_builtin").is_none());
1225        assert!(capability_method_manifest_entry(
1226            harn_builtin_meta::CapabilityId::Fs,
1227            "__definitely_missing_method",
1228        )
1229        .is_none());
1230    }
1231}
1232
1233#[cfg(test)]
1234mod ambient_host_internal_projection_tests {
1235    use super::*;
1236
1237    #[test]
1238    fn ambient_bridge_projects_host_internal_emit_event_alias() {
1239        let previous = std::env::var_os(harn_parser::HARN_LEGACY_AMBIENT_CAPABILITIES_ENV);
1240        unsafe {
1241            std::env::set_var(harn_parser::HARN_LEGACY_AMBIENT_CAPABILITIES_ENV, "1");
1242        }
1243        let mut vm = Vm::new();
1244        register_vm_stdlib(&mut vm);
1245        assert!(
1246            vm.builtin_metadata_for("agent_emit_event").is_some(),
1247            "ambient bridge must project __host_agent_emit_event as agent_emit_event"
1248        );
1249        assert!(
1250            vm.builtin_metadata_for("__host_agent_emit_event").is_some(),
1251            "canonical host internal must remain registered"
1252        );
1253        unsafe {
1254            match previous {
1255                Some(value) => {
1256                    std::env::set_var(harn_parser::HARN_LEGACY_AMBIENT_CAPABILITIES_ENV, value);
1257                }
1258                None => std::env::remove_var(harn_parser::HARN_LEGACY_AMBIENT_CAPABILITIES_ENV),
1259            }
1260        }
1261    }
1262}