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