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