Skip to main content

harn_vm/
stdlib.rs

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