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