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