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