Skip to main content

harn_vm/
lib.rs

1#![recursion_limit = "256"]
2#![allow(clippy::result_large_err, clippy::cloned_ref_to_slice_refs)]
3//! # harn-vm
4//!
5//! The Harn compiler, virtual machine, standard library, provider/LLM layer,
6//! orchestration runtime, and host bridge.
7//!
8//! ## Stability
9//!
10//! This crate is consumed both by the in-tree surfaces (`harn-cli`,
11//! `harn-serve`, the LSP and DAP) and by external embedders. The intended
12//! embedding entry points are `Vm`, `Harness`, `compile_source`, and the
13//! `llm`, `orchestration`, `agent_events`, `agent_sessions`, `config`, and
14//! `security` modules. Other public items exist primarily for in-workspace use
15//! and may change between minor releases; anything marked `#[doc(hidden)]` is
16//! an implementation detail with no stability guarantee. The crate follows the
17//! workspace version and is pre-1.0, so the public surface may still evolve.
18
19/// Re-export of the unified clock substrate so downstream crates (CLI,
20/// orchestrator, and cloud runtimes) can depend on a single canonical `Clock`
21/// trait without each adding `harn-clock` as a direct dependency.
22pub use harn_clock as clock;
23
24mod runtime_stack;
25pub use runtime_stack::RUNTIME_STACK_SIZE;
26
27pub mod a2a;
28pub mod actor_chain;
29pub mod agent_events;
30pub(crate) mod agent_session_journal;
31pub mod agent_sessions;
32pub mod agent_transcript_budget;
33pub mod atomic_io;
34pub mod autonomy;
35pub(crate) mod aws_sigv4;
36pub mod boundary;
37pub mod bridge;
38pub use bridge::inject_leading_authority;
39pub mod builtin_profile;
40pub mod bytecode_cache;
41pub mod call_budget;
42pub mod canonical_json;
43pub mod channel_guardrails;
44pub mod channels;
45pub mod checkpoint;
46mod chunk;
47mod compiler;
48pub mod composition;
49pub mod conditional_replace;
50pub mod config;
51pub mod connectors;
52pub mod context_manifest;
53pub mod corrections;
54pub mod coverage;
55pub(crate) mod durable_rate_limit;
56pub mod duration_parse;
57pub mod egress;
58pub mod environment_registry;
59pub mod event_log;
60pub mod events;
61pub mod external_agent;
62pub mod flow;
63pub mod harness;
64pub mod harness_auth;
65pub(crate) mod harness_crypto;
66pub mod harness_net;
67pub mod harness_system;
68pub mod harness_tenant;
69pub mod host_attachments;
70mod http;
71pub mod jsonrpc;
72pub(crate) mod limits;
73pub mod linked_program;
74pub mod llm;
75pub mod llm_config;
76pub mod mcp;
77pub mod mcp_allowlist;
78pub mod mcp_auth;
79pub mod mcp_bulk_auth;
80pub mod mcp_card;
81pub mod mcp_client_roots;
82pub mod mcp_elicit;
83pub mod mcp_host;
84pub mod mcp_identity;
85pub mod mcp_input;
86pub mod mcp_json_discovery;
87pub mod mcp_oauth;
88pub mod mcp_presets;
89pub mod mcp_progress;
90pub mod mcp_protocol;
91pub mod mcp_registry;
92pub mod mcp_sampling;
93pub mod mcp_server;
94pub mod metadata;
95pub mod module_artifact;
96pub mod module_source;
97pub mod observability;
98pub mod op_interrupt;
99pub mod orchestration;
100mod persistent_state;
101pub mod personas;
102pub mod portable;
103mod prepared_module;
104pub mod process_sandbox;
105pub mod profile;
106pub mod provenance;
107pub mod provider_catalog;
108pub mod receipts;
109pub mod record_filter;
110pub mod redact;
111pub mod run_events;
112pub mod runtime_context;
113pub(crate) mod runtime_guards;
114pub mod runtime_limits;
115pub mod runtime_paths;
116pub(crate) mod runtime_sqlite;
117pub mod schema;
118pub(crate) mod secret_patterns;
119pub mod secrets;
120pub mod security;
121pub mod session_bundle;
122pub mod session_timeline;
123pub mod sessions;
124pub(crate) mod shared_state;
125pub mod shells;
126pub mod skills;
127pub mod stdlib;
128pub mod stdlib_modules;
129pub mod step_runtime;
130pub mod store;
131pub(crate) mod synchronization;
132pub mod tenant;
133pub(crate) mod term;
134pub(crate) mod test_env;
135pub mod testbench;
136pub mod text;
137pub mod text_diff;
138pub mod tool_annotations;
139pub mod tool_call_cancellations;
140pub mod tool_surface;
141pub mod tracing;
142pub mod triggers;
143pub mod trust_graph;
144pub(crate) mod url_encoding;
145pub mod user_dirs;
146
147/// Initialize process-wide assets whose construction should happen before an
148/// embedding host enters an async request or VM execution stack.
149///
150/// New embedding hosts should call [`initialize_runtime`] instead so startup
151/// also validates the Harn-owned environment namespace. This asset-only
152/// operation remains for compatibility, and VM construction retains it as a
153/// fallback for embedders without an explicit bootstrap phase.
154pub fn initialize_runtime_assets() {
155    secret_patterns::initialize_default_secret_patterns();
156}
157
158/// Validate the Harn-owned environment namespace and initialize process-wide
159/// runtime assets through the same bootstrap boundary used by the CLI.
160pub fn initialize_runtime() -> Result<(), environment_registry::EnvironmentValidationError> {
161    environment_registry::validate_startup_environment()?;
162    initialize_runtime_assets();
163    Ok(())
164}
165
166/// Crate-wide deterministic clock mock used by stdlib time builtins, the
167/// trigger dispatcher, the cron scheduler, and Rust-side tests. Re-exports
168/// the long-lived implementation under `triggers::test_util::clock` so all
169/// callers go through one source of truth.
170pub mod clock_mock {
171    pub(crate) use crate::triggers::test_util::clock::scope_capability_clock;
172    pub use crate::triggers::test_util::clock::{
173        active_clock, active_mock_clock, advance, clear_overrides, install_override, instant_now,
174        is_mocked, now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
175    };
176
177    /// Runtime audit for capabilities that observe real wall-clock or
178    /// monotonic time while a testbench mock is installed. See the module
179    /// docs for the full design.
180    pub mod leak_audit {
181        pub use crate::triggers::test_util::clock_leak::{
182            drain, enter_scope, install_scope, instant_now, reset, snapshot, wall_now, ClockLeak,
183            ClockLeakScope, ClockLeakScopeGuard,
184        };
185    }
186}
187
188pub(crate) mod text_index;
189pub mod typecheck;
190pub mod value;
191pub mod verification;
192pub mod visible_text;
193mod vm;
194pub(crate) mod wait_for_graph;
195pub mod waitpoints;
196pub mod windows_path;
197pub mod workspace_anchor;
198pub mod workspace_path;
199
200pub use persistent_state::{register_persistent_state_builtins_at_root, PersistentStateRoot};
201pub use prepared_module::{PreparedModuleCache, PreparedModuleCacheStats};
202
203pub use actor_chain::{
204    ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
205    ScopeAttenuationPolicy, ScopeAttenuationViolation,
206};
207pub use call_budget::{
208    charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
209    McpCallBudgetGuard, PgQueryBudgetGuard,
210};
211pub use checkpoint::register_checkpoint_builtins;
212pub use chunk::*;
213pub use compiler::*;
214pub use connectors::{
215    active_connector_client, active_metrics_registry, clear_active_connector_clients,
216    clear_active_metrics_registry, connector_export_denied_builtin_reason,
217    connector_export_denied_harness_method_reason, connector_export_effect_class,
218    cron::{CatchupMode, CronConnector},
219    default_connector_export_policy,
220    harn_module::{
221        load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
222    },
223    hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
224    install_active_connector_clients, install_active_metrics_registry,
225    postprocess_normalized_event, ActivationHandle, ClientError, Connector, ConnectorClient,
226    ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
227    ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
228    HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
229    RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
230    TriggerRegistry, WebhookSignatureVariant,
231};
232pub use corrections::{
233    append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
234    correction_record_from_json, policy_with_corrections, query_correction_records,
235    CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
236    CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
237};
238pub use harn_kernel::BuiltinId;
239pub use harness::{
240    DenyEvent, Harness, HarnessAgent, HarnessCall, HarnessChannels, HarnessClock, HarnessEnv,
241    HarnessFs, HarnessKind, HarnessLlm, HarnessMemory, HarnessNet, HarnessObs, HarnessPostgres,
242    HarnessProcess, HarnessRandom, HarnessSecrets, HarnessSqlite, HarnessStdio, HarnessSystem,
243    HarnessTenant, HarnessTerm, HarnessTesting, MockHarnessBuilder, VmHarness,
244};
245pub use harness_auth::{
246    current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
247    MISSING_PRINCIPAL_MESSAGE,
248};
249pub use harness_net::{
250    bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
251    NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
252    NET_POLICY_AUDIT_TOPIC,
253};
254pub use harness_tenant::{
255    current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
256};
257pub use http::{register_http_builtins, reset_http_state};
258pub use llm::register_llm_builtins;
259pub use llm::trigger_predicate::TriggerPredicateBudget;
260pub use llm::{
261    current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
262    peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
263    set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard, SessionEndHookRegistration,
264};
265pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
266pub use mcp_allowlist::{
267    build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
268    AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
269    McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
270    MCP_ALLOWLIST_SCHEMA_VERSION,
271};
272pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
273pub use mcp_host::{
274    cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
275    AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
276    BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
277    SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
278};
279pub use mcp_registry::{
280    active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
281    get_registration as mcp_get_registration, install_active as mcp_install_active,
282    is_registered as mcp_is_registered, register_servers as mcp_register_servers,
283    release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
284    sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
285};
286pub use mcp_server::{
287    take_mcp_serve_metadata, take_mcp_serve_prompts, take_mcp_serve_registry,
288    take_mcp_serve_resource_templates, take_mcp_serve_resources, tool_registry_to_mcp_tools,
289    McpServer, McpServerMetadata,
290};
291pub use metadata::register_metadata_builtins;
292pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
293pub use observability::execution_scope::{
294    current_execution_scope, enter_execution_scope, mint_execution_scope, ExecutionScopeGuard,
295};
296pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
297pub use orchestration::{
298    benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
299    OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
300    ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
301    ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
302    ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
303    ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
304    REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
305};
306pub use orchestration::{
307    canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
308    ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
309    ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
310};
311pub use orchestration::{
312    install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
313    HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
314};
315pub use personas::{
316    disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
317    format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
318    pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
319    register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
320    resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
321    PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
322    PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
323    PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
324    PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
325    PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
326    PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
327    PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
328    PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
329};
330pub use provenance::{
331    build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
332    ReceiptBuildOptions, ReceiptVerificationReport,
333};
334pub use receipts::{
335    Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
336    RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
337};
338pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
339pub use runtime_limits::{
340    RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
341    RUNTIME_LIMIT_DESCRIPTIONS,
342};
343pub use schema::json_to_vm_value;
344pub use sessions::{
345    CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
346    TouchSession, SESSIONS_TOPIC,
347};
348/// The single owner of ignore policy for every Harn filesystem walk.
349///
350/// Re-exported so embedders that enumerate files on behalf of Harn scripts
351/// (today: the `harn-hostlib` deterministic-tool builtins) skip exactly the
352/// same paths the in-VM builtins do.
353pub use stdlib::fs::ignore_policy;
354pub use stdlib::hitl::{
355    append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
356    HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
357};
358/// Per-turn memo for turn-stable host reads. See [`stdlib::host::turn_cache`].
359pub use stdlib::host::turn_cache as host_turn_cache;
360pub use stdlib::host::{
361    clear_host_call_bridge, dispatch_host_operation, host_call_ready, set_host_call_bridge,
362    HostCallBridge, HostCallDispatchFuture,
363};
364pub use stdlib::http_response::{
365    parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
366    HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
367};
368#[cfg(feature = "postgres")]
369pub use stdlib::install_shared_pool_registry;
370pub use stdlib::io::{
371    reserve_stdio_for_current_thread, set_stdout_passthrough, take_stderr_buffer,
372    StdioReservationGuard,
373};
374pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
375pub use stdlib::observability::install_default_backend as install_obs_default_backend;
376pub use stdlib::secret_scan::{
377    append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
378    SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
379};
380pub use stdlib::template::{
381    lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
382    PromptSourceSpan, PromptSpanKind,
383};
384pub use stdlib::waitpoint::{
385    process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
386};
387pub use stdlib::workflow_messages::{
388    workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
389    workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
390    workflow_update_for_base, WorkflowMailboxState,
391};
392pub use stdlib::{
393    register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
394};
395pub use store::register_store_builtins;
396pub use tenant::{
397    tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
398    TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
399    TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
400    TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
401    TENANT_SECRET_NAMESPACE_PREFIX,
402};
403pub use triggers::{
404    append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
405    binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
406    clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
407    dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
408    install_manifest_triggers, install_orchestrator_budget, micros_to_usd,
409    note_autonomous_decision, note_orchestrator_budget_cost, orchestrator_budget_would_exceed,
410    parse_flow_control_duration, pause, pin_trigger_binding, provider_metadata,
411    record_predicate_cost_sample, redact_headers, register_provider_schemas,
412    registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
413    reset_provider_catalog, resolve_live_or_as_of, resolve_live_trigger_binding,
414    resolve_trigger_binding_as_of, resume, run_trigger_harness_fixture, scheduler_in_flight_by_key,
415    scheduler_ready_stats_by_key, snapshot_dispatcher_stats, snapshot_orchestrator_budget,
416    snapshot_trigger_bindings, unpin_trigger_binding, usd_to_micros, worker_claims_topic_name,
417    worker_job_topic_name, worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest,
418    DispatchError, DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport,
419    DispatcherStatsSnapshot, ExtensionProviderPayload, FairnessKey, HeaderRedactionPolicy,
420    InboxIndex, OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog,
421    ProviderCatalogError, ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload,
422    ProviderRuntimeMetadata, ProviderSchema, ProviderSecretRequirement, ReadyKeyStats,
423    RecordedTriggerBinding, RetryPolicy, SchedulableJob, SchedulerKeyStat, SchedulerPolicy,
424    SchedulerSnapshot, SchedulerState, SchedulerStrategy, SignatureStatus,
425    SignatureVerificationMetadata, StreamEventPayload, TenantId, TraceId, TriggerBatchConfig,
426    TriggerBindingSnapshot, TriggerBindingSource, TriggerBindingSpec,
427    TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig, TriggerDebounceConfig,
428    TriggerDispatchOutcome, TriggerEvent, TriggerEventId, TriggerExpressionSpec,
429    TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult, TriggerId,
430    TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
431    TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
432    TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
433    WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
434    WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
435    DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
436    TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
437    TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
438    TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
439    TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
440};
441pub use trust_graph::{
442    append_active_scope_attenuation_alert, append_active_trust_record,
443    append_scope_attenuation_alert, append_trust_record, export_trust_chain,
444    group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
445    query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
446    summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
447    TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
448    TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
449    TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
450    METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
451    METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
452    OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
453    TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
454    TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
455};
456pub use value::*;
457pub use vm::*;
458
459#[cfg(feature = "vm-bench-internals")]
460#[doc(hidden)]
461pub mod bench_internals;
462
463/// Lex, parse, type-check, and compile source to bytecode in one call.
464/// Bails on the first type error. For callers that need diagnostics
465/// rather than early exit, use `harn_parser::check_source` directly
466/// and then call `Compiler::new().compile(&program)`.
467pub fn compile_source(source: &str) -> Result<Chunk, String> {
468    let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
469    Compiler::new().compile(&program).map_err(|e| e.to_string())
470}
471
472/// Same as [`compile_source`] but compiles a specific named pipeline as
473/// the program entry point instead of the default-pipeline-or-first
474/// selection rule. Returns a runtime error when no pipeline with
475/// `pipeline_name` exists in the source.
476pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
477    let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
478    let has_pipeline = program.iter().any(|sn| {
479        let (_, inner) = harn_parser::peel_attributes(sn);
480        matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
481    });
482    if !has_pipeline {
483        return Err(format!("no pipeline named `{pipeline_name}` in source"));
484    }
485    Compiler::new()
486        .compile_named(&program, pipeline_name)
487        .map_err(|e| e.to_string())
488}
489
490/// Lowers Harn `TypeExpr`s to JSON Schema with `type`-alias EXPANSION, built once
491/// from a parsed program's alias declarations. Without expansion, a tool parameter
492/// typed as a user alias (`p: EvalSource`, `p: FunnelStage`) erases to an empty
493/// `{}` schema because the low-level lowering only recognizes built-in type names —
494/// the exporter must first resolve the alias to its underlying shape/union (a
495/// literal-union alias then round-trips as a JSON `enum`). Cycle-safe via the same
496/// `expand_alias` guard the compiler and typechecker share.
497pub struct SchemaAliasResolver {
498    compiler: compiler::Compiler,
499}
500
501impl SchemaAliasResolver {
502    /// A resolver with no aliases in scope — lowering is identical to the raw
503    /// (unexpanded) form, so `Named(alias)` still lowers to `{}` when unknown.
504    pub fn empty() -> Self {
505        Self {
506            compiler: compiler::Compiler::new(),
507        }
508    }
509
510    /// Collect every `type` alias declared in `program`, so named references in
511    /// tool signatures resolve to their bodies.
512    pub fn from_program(program: &[harn_parser::SNode]) -> Self {
513        let mut compiler = compiler::Compiler::new();
514        compiler.collect_type_aliases(program);
515        Self { compiler }
516    }
517
518    /// JSON Schema for one `TypeExpr`, expanding any named alias first. `None`
519    /// when the (expanded) type has no JSON-Schema form (function types, ...).
520    pub fn json_schema_for_type_expr(
521        &self,
522        type_expr: &harn_parser::TypeExpr,
523    ) -> Option<serde_json::Value> {
524        let expanded = self.compiler.expand_alias(type_expr);
525        let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
526        let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
527        Some(llm::vm_value_to_json(&json_schema))
528    }
529
530    /// JSON Schema `object` for a parameter list (a served tool's `inputSchema`),
531    /// expanding aliases per parameter.
532    pub fn json_schema_for_typed_params(
533        &self,
534        params: &[harn_parser::TypedParam],
535    ) -> serde_json::Value {
536        let mut properties = serde_json::Map::new();
537        let mut required = Vec::new();
538
539        for param in params {
540            let param_schema = param
541                .type_expr
542                .as_ref()
543                .and_then(|type_expr| self.json_schema_for_type_expr(type_expr))
544                .unwrap_or_else(|| serde_json::json!({}));
545            if param.default_value.is_none() {
546                required.push(serde_json::Value::String(param.name.clone()));
547            }
548            properties.insert(param.name.clone(), param_schema);
549        }
550
551        let mut schema = serde_json::Map::new();
552        schema.insert(
553            "type".to_string(),
554            serde_json::Value::String("object".to_string()),
555        );
556        schema.insert(
557            "properties".to_string(),
558            serde_json::Value::Object(properties),
559        );
560        if !required.is_empty() {
561            schema.insert("required".to_string(), serde_json::Value::Array(required));
562        }
563        serde_json::Value::Object(schema)
564    }
565}
566
567/// Raw lowering with no program aliases in scope. Prefer
568/// [`SchemaAliasResolver::from_program`] when serving a module so named-alias
569/// parameters resolve instead of erasing to `{}`.
570pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
571    SchemaAliasResolver::empty().json_schema_for_type_expr(type_expr)
572}
573
574pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
575    SchemaAliasResolver::empty().json_schema_for_typed_params(params)
576}
577
578#[cfg(test)]
579mod schema_alias_resolver_tests {
580    use super::*;
581
582    fn fn_params_schema(src: &str) -> serde_json::Value {
583        let program = harn_parser::parse_source(src).expect("parse test source");
584        let resolver = SchemaAliasResolver::from_program(&program);
585        for node in &program {
586            let (_, inner) = harn_parser::peel_attributes(node);
587            if let harn_parser::Node::FnDecl { params, .. } = &inner.node {
588                return resolver.json_schema_for_typed_params(params);
589            }
590        }
591        panic!("no fn decl in test source");
592    }
593
594    #[test]
595    fn named_shape_alias_projects_like_inline_shape() {
596        let inline = fn_params_schema("pub fn f(p: {kind: string, path: string}) {}");
597        let aliased =
598            fn_params_schema("type Src = {kind: string, path: string}\npub fn f(p: Src) {}");
599        assert_eq!(
600            aliased, inline,
601            "a named shape alias must project the same inputSchema as its inline shape",
602        );
603        assert_ne!(
604            aliased["properties"]["p"],
605            serde_json::json!({}),
606            "the alias parameter must not erase to an empty schema",
607        );
608    }
609
610    #[test]
611    fn literal_union_alias_projects_json_enum() {
612        let schema = fn_params_schema("type Kind = \"local\" | \"ssh\"\npub fn f(p: Kind) {}");
613        let p = &schema["properties"]["p"];
614        assert_eq!(p["type"], "string");
615        assert_eq!(p["enum"], serde_json::json!(["local", "ssh"]));
616    }
617
618    #[test]
619    fn unknown_named_type_still_erases_to_empty() {
620        // No alias declared: unchanged behavior — an unknown named type lowers to {}.
621        let schema = fn_params_schema("pub fn f(p: Unknown) {}");
622        assert_eq!(schema["properties"]["p"], serde_json::json!({}));
623    }
624}
625
626fn reset_llm_state_for_thread_reset() {
627    llm::reset_llm_state();
628    #[cfg(test)]
629    reset_thread_local_state_test_hooks::before_llm_global_reset();
630    // This full wipe is necessary between Harn programs to clear durable
631    // cooldowns that would otherwise stall a later run under a paused clock.
632    llm::reset_rate_limit_registry();
633    llm_config::clear_user_overrides();
634    llm_config::clear_runtime_provider_endpoint_overrides();
635}
636
637#[cfg(test)]
638mod reset_thread_local_state_test_hooks {
639    use std::sync::{Arc, Mutex, OnceLock};
640
641    type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
642
643    static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
644
645    fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
646        BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
647    }
648
649    pub(crate) struct HookGuard;
650
651    impl Drop for HookGuard {
652        fn drop(&mut self) {
653            let mut hook = before_llm_global_reset_hook()
654                .lock()
655                .unwrap_or_else(std::sync::PoisonError::into_inner);
656            *hook = None;
657        }
658    }
659
660    pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
661        let mut slot = before_llm_global_reset_hook()
662            .lock()
663            .unwrap_or_else(std::sync::PoisonError::into_inner);
664        *slot = Some(hook);
665        HookGuard
666    }
667
668    pub(crate) fn before_llm_global_reset() {
669        let hook = before_llm_global_reset_hook()
670            .lock()
671            .unwrap_or_else(std::sync::PoisonError::into_inner)
672            .clone();
673        if let Some(hook) = hook {
674            hook();
675        }
676    }
677}
678
679/// Reset all thread-local state that can leak between test runs.
680pub fn reset_thread_local_state() {
681    #[cfg(test)]
682    {
683        // `reset_thread_local_state` is also used by in-process unit tests. It
684        // clears process-global LLM config/rate-limit state, so share the same
685        // lock used by LLM env tests; otherwise a sibling reset can erase a
686        // parked rate-limit test's registry while the test still owns a permit.
687        let _guard = llm::env_guard();
688        reset_llm_state_for_thread_reset();
689    }
690    #[cfg(not(test))]
691    reset_llm_state_for_thread_reset();
692
693    http::reset_http_state();
694    channels::reset_channel_state();
695    event_log::reset_active_event_log();
696    egress::clear_explicit_egress_policy_requirement_for_host();
697    egress::clear_ssrf_guard_requirement_for_host();
698    stdlib::reset_stdlib_state();
699    connectors::clear_active_connector_clients();
700    orchestration::clear_runtime_hooks();
701    orchestration::clear_file_edit_queue();
702    orchestration::clear_execution_policy_stacks();
703    orchestration::clear_command_policies();
704    orchestration::clear_pipeline_on_finish();
705    orchestration::reset_lifecycle_receipt_registry();
706    orchestration::agent_inbox::reset();
707    tool_call_cancellations::reset_registry();
708    redact::clear_policy_stack();
709    security::reset_thread_state();
710    triggers::clear_dispatcher_state();
711    triggers::clear_trigger_registry();
712    events::reset_event_sinks();
713    tracing::set_tracing_enabled(false);
714    tracing::reset_tracing();
715    // `builtin_profile` is deliberately NOT reset here. Its recorder is
716    // process-global (`static ENABLED` / `static TOTALS`), and this function
717    // runs from ~150 test setups and from production entry points like
718    // `execute_conformance_source` and the orchestrator lifecycle. Every one
719    // of those calls disarmed the recorder that a concurrently running
720    // profiled run had just enabled, so `harn run --profile` reported
721    // `vm/residual 100%` and named nothing. `builtin_profile::enable()`
722    // already discards the previous run's totals, so the profiling entry
723    // point owns the lifecycle without help from here. Same reasoning as
724    // `llm::rate_limit::reset_runtime_rate_limit_overrides` and the
725    // `long_running::reset_state` exclusion in `stdlib::reset_stdlib_state`.
726    agent_events::reset_all_sinks();
727    agent_sessions::reset_session_store();
728    mcp_registry::reset();
729    mcp_host::reset_for_tests();
730    call_budget::reset_call_budget_state();
731    clock_mock::leak_audit::reset();
732}
733
734#[cfg(test)]
735mod reset_leak_tests {
736    //! Regression coverage for harn#2660: process-/thread-global
737    //! registries that accumulated one entry per test because they were
738    //! never drained by `reset_thread_local_state`. Each case populates a
739    //! registry through its real entry point, runs the reset, and asserts
740    //! the registry is empty again.
741    use super::*;
742    use crate::value::VmValue;
743
744    #[test]
745    fn reset_drains_pending_file_edit_notifications() {
746        orchestration::queue_file_edited("stale.harn", serde_json::json!({"operation": "write"}));
747
748        reset_thread_local_state();
749
750        assert!(
751            orchestration::drain_file_edits().is_empty(),
752            "a later VM run must not receive file edits queued by the previous run"
753        );
754    }
755
756    /// The recorder is enabled per RUN but lives for the PROCESS, so an
757    /// embedder that runs one script with `--profile` and the next without it
758    /// would keep paying for bookkeeping nobody reads and fold the second
759    /// run's builtins into the first run's totals. Enablement therefore ends
760    /// with the run that asked for it — the guard `enable()` returns — and NOT
761    /// in `reset_thread_local_state`, which fires from ~150 test setups and
762    /// from production entry points that know nothing about an in-flight
763    /// profiled run.
764    #[test]
765    fn builtin_profile_recording_ends_with_its_run_not_with_a_global_reset() {
766        let _lock = builtin_profile::test_lock()
767            .lock()
768            .unwrap_or_else(std::sync::PoisonError::into_inner);
769        let recording = builtin_profile::enable();
770        builtin_profile::record("run_shell", std::time::Duration::from_millis(5));
771        assert!(builtin_profile::is_enabled());
772        assert!(!builtin_profile::snapshot().is_empty());
773
774        reset_thread_local_state();
775
776        assert!(
777            builtin_profile::is_enabled(),
778            "an unrelated global reset must not disarm an in-flight profiled run"
779        );
780        assert!(
781            !builtin_profile::snapshot().is_empty(),
782            "an unrelated global reset must not drop totals the run still owns"
783        );
784
785        drop(recording);
786
787        assert!(
788            !builtin_profile::is_enabled(),
789            "a profiled run must not leave the recorder on for the next one"
790        );
791        assert!(
792            builtin_profile::snapshot().is_empty(),
793            "builtin totals must be empty once the run's guard drops"
794        );
795    }
796
797    /// The changed-path map is the authoritative source for a sub-agent's
798    /// `files_written` receipt, is process-global, and is drained only at
799    /// teardown — which a session that errors never reaches. A later session
800    /// reusing the id would report writes it never made.
801    #[test]
802    fn reset_drains_session_changed_paths() {
803        let session = "sess-leak";
804        agent_sessions::open_or_create(Some(session.to_string()));
805        agent_sessions::record_session_changed_path(session, "/tmp/written-by-a-dead-run.txt");
806        assert!(!agent_sessions::session_changed_paths(session).is_empty());
807        reset_thread_local_state();
808        assert!(
809            agent_sessions::session_changed_paths(session).is_empty(),
810            "a receipt must not inherit an abandoned session's writes"
811        );
812    }
813
814    #[test]
815    fn reset_drains_agent_inbox() {
816        orchestration::agent_inbox::reset();
817        orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
818        assert!(orchestration::agent_inbox::session_count() > 0);
819        reset_thread_local_state();
820        assert_eq!(
821            orchestration::agent_inbox::session_count(),
822            0,
823            "agent_inbox must be empty after reset"
824        );
825    }
826
827    #[test]
828    fn reset_drains_tool_call_cancellation_registry() {
829        tool_call_cancellations::reset_registry();
830        // Leak the guard so the entry survives until the reset runs —
831        // this mirrors a dispatch abandoned mid-flight.
832        let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
833        if let Some((_handle, guard)) = registered {
834            std::mem::forget(guard);
835        }
836        assert!(tool_call_cancellations::registry_len() > 0);
837        reset_thread_local_state();
838        assert_eq!(
839            tool_call_cancellations::registry_len(),
840            0,
841            "tool-call cancellation registry must be empty after reset"
842        );
843    }
844
845    #[test]
846    fn reset_drains_routing_policy_registry() {
847        llm::routing::clear_policy_registry();
848        let mut config: crate::value::DictMap = crate::value::DictMap::new();
849        config.insert(
850            crate::value::intern_key("chain"),
851            VmValue::List(std::sync::Arc::new(vec![VmValue::String(
852                arcstr::ArcStr::from("mock:mock"),
853            )])),
854        );
855        llm::routing::build_routing_policy(&config).expect("intern a routing policy");
856        assert!(llm::routing::policy_registry_len() > 0);
857        reset_thread_local_state();
858        assert_eq!(
859            llm::routing::policy_registry_len(),
860            0,
861            "routing policy registry must be empty after reset"
862        );
863    }
864
865    #[test]
866    fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
867        let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
868        let observed_hook = std::sync::Arc::clone(&observed);
869        let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
870            std::sync::Arc::new(move || {
871                assert!(
872                    matches!(
873                        llm::env_lock().try_lock(),
874                        Err(std::sync::TryLockError::WouldBlock)
875                    ),
876                    "reset_thread_local_state must hold env_guard before wiping LLM globals"
877                );
878                observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
879            }),
880        );
881
882        reset_thread_local_state();
883        assert!(
884            observed.load(std::sync::atomic::Ordering::SeqCst),
885            "LLM global reset hook should have run"
886        );
887    }
888}