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