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
4/// Re-export of the unified clock substrate so downstream crates (CLI,
5/// orchestrator, harn-cloud) can depend on a single canonical `Clock`
6/// trait without each adding `harn-clock` as a direct dependency.
7pub use harn_clock as clock;
8
9pub mod a2a;
10pub mod actor_chain;
11pub mod agent_events;
12pub mod agent_sessions;
13pub mod atomic_io;
14pub mod autonomy;
15pub(crate) mod aws_sigv4;
16pub mod bridge;
17mod builtin_id;
18pub mod bytecode_cache;
19pub mod call_budget;
20pub mod channel_guardrails;
21pub mod channels;
22pub mod checkpoint;
23mod chunk;
24mod compiler;
25pub mod composition;
26pub mod config;
27pub mod connectors;
28pub mod corrections;
29pub mod coverage;
30pub(crate) mod durable_rate_limit;
31pub(crate) mod duration_parse;
32pub mod egress;
33pub mod event_log;
34pub mod events;
35pub mod external_agent;
36pub mod flow;
37pub mod harness;
38pub mod harness_auth;
39pub(crate) mod harness_crypto;
40pub mod harness_net;
41pub mod harness_system;
42pub mod harness_tenant;
43mod http;
44pub mod jsonrpc;
45pub(crate) mod limits;
46pub mod llm;
47pub mod llm_config;
48pub mod mcp;
49pub mod mcp_allowlist;
50pub mod mcp_auth;
51pub mod mcp_bulk_auth;
52pub mod mcp_card;
53pub mod mcp_elicit;
54pub mod mcp_file_upload;
55pub mod mcp_host;
56pub mod mcp_identity;
57pub mod mcp_oauth;
58pub mod mcp_presets;
59pub mod mcp_progress;
60pub mod mcp_protocol;
61pub mod mcp_registry;
62pub mod mcp_sampling;
63pub mod mcp_server;
64pub mod metadata;
65pub mod module_artifact;
66pub mod observability;
67pub mod orchestration;
68pub mod personas;
69pub mod process_sandbox;
70pub mod profile;
71pub mod provenance;
72pub mod provider_catalog;
73pub mod receipts;
74pub mod record_filter;
75pub mod redact;
76pub mod run_events;
77pub mod runtime_context;
78pub(crate) mod runtime_guards;
79pub mod runtime_limits;
80pub mod runtime_paths;
81pub(crate) mod runtime_sqlite;
82pub mod schema;
83pub(crate) mod secret_patterns;
84pub mod secrets;
85pub mod security;
86pub mod session_bundle;
87pub mod session_timeline;
88pub mod sessions;
89pub(crate) mod shared_state;
90pub mod shells;
91pub mod skills;
92pub mod stdlib;
93pub mod stdlib_modules;
94pub mod step_runtime;
95pub mod store;
96pub(crate) mod synchronization;
97pub mod tenant;
98pub(crate) mod term;
99pub mod testbench;
100pub mod tool_annotations;
101pub mod tool_call_cancellations;
102pub mod tool_surface;
103pub mod tracing;
104pub mod triggers;
105pub mod trust_graph;
106pub(crate) mod url_encoding;
107pub mod user_dirs;
108
109/// Crate-wide deterministic clock mock used by stdlib time builtins, the
110/// trigger dispatcher, the cron scheduler, and Rust-side tests. Re-exports
111/// the long-lived implementation under `triggers::test_util::clock` so all
112/// callers go through one source of truth.
113pub mod clock_mock {
114    pub use crate::triggers::test_util::clock::{
115        active_mock_clock, advance, clear_overrides, install_override, instant_now, is_mocked,
116        now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
117    };
118
119    /// Runtime audit for capabilities that observe real wall-clock or
120    /// monotonic time while a testbench mock is installed. See the module
121    /// docs for the full design.
122    pub mod leak_audit {
123        #[cfg(test)]
124        pub use crate::triggers::test_util::clock_leak::TEST_LOCK;
125        pub use crate::triggers::test_util::clock_leak::{
126            drain, instant_now, reset, snapshot, wall_now, ClockLeak,
127        };
128    }
129}
130
131pub mod typecheck;
132pub mod value;
133pub mod visible_text;
134mod vm;
135pub(crate) mod wait_for_graph;
136pub mod waitpoints;
137pub mod workspace_anchor;
138pub mod workspace_path;
139
140pub use actor_chain::{
141    ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
142    ScopeAttenuationPolicy, ScopeAttenuationViolation,
143};
144pub use builtin_id::BuiltinId;
145pub use call_budget::{
146    charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
147    McpCallBudgetGuard, PgQueryBudgetGuard,
148};
149pub use checkpoint::register_checkpoint_builtins;
150pub use chunk::*;
151pub use compiler::*;
152pub use connectors::{
153    active_connector_client, active_metrics_registry, clear_active_connector_clients,
154    clear_active_metrics_registry, connector_export_denied_builtin_reason,
155    connector_export_effect_class,
156    cron::{CatchupMode, CronConnector},
157    default_connector_export_policy,
158    harn_module::{
159        load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
160    },
161    hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
162    install_active_connector_clients, install_active_metrics_registry,
163    postprocess_normalized_event, ActivationHandle, ClientError, Connector, ConnectorClient,
164    ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
165    ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
166    HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
167    RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
168    TriggerRegistry, WebhookSignatureVariant,
169};
170pub use corrections::{
171    append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
172    correction_record_from_json, policy_with_corrections, query_correction_records,
173    CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
174    CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
175};
176pub use harness::{
177    DenyEvent, Harness, HarnessCall, HarnessClock, HarnessCrypto, HarnessEnv, HarnessFs,
178    HarnessKind, HarnessLlm, HarnessNet, HarnessObs, HarnessProcess, HarnessRandom, HarnessSecrets,
179    HarnessStdio, HarnessSystem, HarnessTenant, HarnessTerm, MockAwareClock, MockHarnessBuilder,
180    VmHarness,
181};
182pub use harness_auth::{
183    current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
184    MISSING_PRINCIPAL_MESSAGE,
185};
186pub use harness_net::{
187    bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
188    NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
189    NET_POLICY_AUDIT_TOPIC,
190};
191pub use harness_tenant::{
192    current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
193};
194pub use http::{register_http_builtins, reset_http_state};
195pub use llm::register_llm_builtins;
196pub use llm::trigger_predicate::TriggerPredicateBudget;
197pub use llm::{
198    current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
199    peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
200    set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard,
201};
202pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
203pub use mcp_allowlist::{
204    build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
205    AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
206    McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
207    MCP_ALLOWLIST_SCHEMA_VERSION,
208};
209pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
210pub use mcp_host::{
211    cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
212    AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
213    BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
214    SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
215};
216pub use mcp_registry::{
217    active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
218    get_registration as mcp_get_registration, install_active as mcp_install_active,
219    is_registered as mcp_is_registered, register_servers as mcp_register_servers,
220    release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
221    sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
222};
223pub use mcp_server::{
224    take_mcp_serve_prompts, take_mcp_serve_registry, take_mcp_serve_resource_templates,
225    take_mcp_serve_resources, tool_registry_to_mcp_tools, McpServer,
226};
227pub use metadata::register_metadata_builtins;
228pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
229pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
230pub use orchestration::{
231    benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
232    OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
233    ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
234    ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
235    ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
236    ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
237    REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
238};
239pub use orchestration::{
240    canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
241    ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
242    ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
243};
244pub use orchestration::{
245    install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
246    HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
247};
248pub use personas::{
249    disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
250    format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
251    pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
252    register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
253    resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
254    PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
255    PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
256    PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
257    PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
258    PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
259    PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
260    PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
261    PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
262};
263pub use provenance::{
264    build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
265    ReceiptBuildOptions, ReceiptVerificationReport,
266};
267pub use receipts::{
268    Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
269    RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
270};
271pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
272pub use runtime_limits::{
273    RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
274    RUNTIME_LIMIT_DESCRIPTIONS,
275};
276pub use schema::json_to_vm_value;
277pub use sessions::{
278    CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
279    TouchSession, SESSIONS_TOPIC,
280};
281pub use stdlib::hitl::{
282    append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
283    HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
284};
285pub use stdlib::host::{clear_host_call_bridge, set_host_call_bridge, HostCallBridge};
286pub use stdlib::http_response::{
287    parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
288    HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
289};
290#[cfg(feature = "postgres")]
291pub use stdlib::install_shared_pool_registry;
292pub use stdlib::io::{set_stdout_passthrough, take_stderr_buffer};
293pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
294pub use stdlib::observability::install_default_backend as install_obs_default_backend;
295pub use stdlib::secret_scan::{
296    append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
297    SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
298};
299pub use stdlib::template::{
300    lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
301    PromptSourceSpan, PromptSpanKind,
302};
303pub use stdlib::waitpoint::{
304    process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
305};
306pub use stdlib::workflow_messages::{
307    workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
308    workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
309    workflow_update_for_base, WorkflowMailboxState,
310};
311pub use stdlib::{
312    register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
313};
314pub use store::register_store_builtins;
315pub use tenant::{
316    tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
317    TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
318    TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
319    TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
320    TENANT_SECRET_NAMESPACE_PREFIX,
321};
322pub use triggers::{
323    append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
324    binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
325    clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
326    dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
327    install_manifest_triggers, install_orchestrator_budget, install_provider_catalog,
328    micros_to_usd, note_autonomous_decision, note_orchestrator_budget_cost,
329    orchestrator_budget_would_exceed, parse_flow_control_duration, pause, pin_trigger_binding,
330    provider_metadata, record_predicate_cost_sample, redact_headers, register_provider_schema,
331    registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
332    reset_provider_catalog, reset_provider_catalog_with, resolve_live_or_as_of,
333    resolve_live_trigger_binding, resolve_trigger_binding_as_of, resume,
334    run_trigger_harness_fixture, scheduler_in_flight_by_key, scheduler_ready_stats_by_key,
335    snapshot_dispatcher_stats, snapshot_orchestrator_budget, snapshot_trigger_bindings,
336    unpin_trigger_binding, usd_to_micros, worker_claims_topic_name, worker_job_topic_name,
337    worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest, DispatchError,
338    DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport, DispatcherStatsSnapshot,
339    FairnessKey, HeaderRedactionPolicy, InboxIndex, NotionPolledChangeEvent,
340    OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog, ProviderCatalogError,
341    ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload, ProviderRuntimeMetadata,
342    ProviderSchema, ProviderSecretRequirement, ReadyKeyStats, RecordedTriggerBinding, RetryPolicy,
343    SchedulableJob, SchedulerKeyStat, SchedulerPolicy, SchedulerSnapshot, SchedulerState,
344    SchedulerStrategy, SignatureStatus, SignatureVerificationMetadata, StreamEventPayload,
345    TenantId, TraceId, TriggerBatchConfig, TriggerBindingSnapshot, TriggerBindingSource,
346    TriggerBindingSpec, TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig,
347    TriggerDebounceConfig, TriggerDispatchOutcome, TriggerEvent, TriggerEventId,
348    TriggerExpressionSpec, TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult,
349    TriggerId, TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
350    TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
351    TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
352    WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
353    WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
354    DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
355    TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
356    TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
357    TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
358    TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
359};
360pub use trust_graph::{
361    append_active_scope_attenuation_alert, append_active_trust_record,
362    append_scope_attenuation_alert, append_trust_record, export_trust_chain,
363    group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
364    query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
365    summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
366    TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
367    TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
368    TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
369    METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
370    METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
371    OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
372    TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
373    TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
374};
375pub use value::*;
376pub use vm::*;
377
378#[cfg(feature = "vm-bench-internals")]
379#[doc(hidden)]
380pub mod bench_internals;
381
382/// Lex, parse, type-check, and compile source to bytecode in one call.
383/// Bails on the first type error. For callers that need diagnostics
384/// rather than early exit, use `harn_parser::check_source` directly
385/// and then call `Compiler::new().compile(&program)`.
386pub fn compile_source(source: &str) -> Result<Chunk, String> {
387    let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
388    Compiler::new().compile(&program).map_err(|e| e.to_string())
389}
390
391/// Same as [`compile_source`] but compiles a specific named pipeline as
392/// the program entry point instead of the default-pipeline-or-first
393/// selection rule. Returns a runtime error when no pipeline with
394/// `pipeline_name` exists in the source.
395pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
396    let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
397    let has_pipeline = program.iter().any(|sn| {
398        let (_, inner) = harn_parser::peel_attributes(sn);
399        matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
400    });
401    if !has_pipeline {
402        return Err(format!("no pipeline named `{pipeline_name}` in source"));
403    }
404    Compiler::new()
405        .compile_named(&program, pipeline_name)
406        .map_err(|e| e.to_string())
407}
408
409pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
410    let schema = compiler::Compiler::type_expr_to_schema_value(type_expr)?;
411    let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
412    Some(llm::vm_value_to_json(&json_schema))
413}
414
415pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
416    let mut properties = serde_json::Map::new();
417    let mut required = Vec::new();
418
419    for param in params {
420        let param_schema = param
421            .type_expr
422            .as_ref()
423            .and_then(json_schema_for_type_expr)
424            .unwrap_or_else(|| serde_json::json!({}));
425        if param.default_value.is_none() {
426            required.push(serde_json::Value::String(param.name.clone()));
427        }
428        properties.insert(param.name.clone(), param_schema);
429    }
430
431    let mut schema = serde_json::Map::new();
432    schema.insert(
433        "type".to_string(),
434        serde_json::Value::String("object".to_string()),
435    );
436    schema.insert(
437        "properties".to_string(),
438        serde_json::Value::Object(properties),
439    );
440    if !required.is_empty() {
441        schema.insert("required".to_string(), serde_json::Value::Array(required));
442    }
443    serde_json::Value::Object(schema)
444}
445
446/// Reset all thread-local state that can leak between test runs.
447pub fn reset_thread_local_state() {
448    llm::reset_llm_state();
449    // `reset_thread_local_state` only runs in sequential / separate-process
450    // contexts (the CLI test runner between cases, integration-test binaries,
451    // per-job worker resets) — never from the parallel in-process unit tests
452    // that share the rate-limiter registry. So unlike `reset_llm_state`, it is
453    // safe (and necessary for test isolation) to fully wipe the registry here,
454    // clearing retry-after cooldowns that would otherwise stall a later test's
455    // LLM call under a paused clock.
456    llm::reset_rate_limit_registry();
457    llm_config::clear_user_overrides();
458    http::reset_http_state();
459    channels::reset_channel_state();
460    event_log::reset_active_event_log();
461    egress::clear_explicit_egress_policy_requirement_for_host();
462    egress::clear_ssrf_guard_requirement_for_host();
463    stdlib::reset_stdlib_state();
464    connectors::clear_active_connector_clients();
465    orchestration::clear_runtime_hooks();
466    orchestration::clear_execution_policy_stacks();
467    orchestration::clear_command_policies();
468    orchestration::clear_pipeline_on_finish();
469    orchestration::reset_lifecycle_receipt_registry();
470    orchestration::agent_inbox::reset();
471    tool_call_cancellations::reset_registry();
472    redact::clear_policy_stack();
473    security::reset_thread_state();
474    triggers::clear_dispatcher_state();
475    triggers::clear_trigger_registry();
476    events::reset_event_sinks();
477    tracing::set_tracing_enabled(false);
478    tracing::reset_tracing();
479    agent_events::reset_all_sinks();
480    agent_sessions::reset_session_store();
481    mcp_registry::reset();
482    mcp_host::reset_for_tests();
483    call_budget::reset_call_budget_state();
484    clock_mock::leak_audit::reset();
485}
486
487#[cfg(test)]
488mod reset_leak_tests {
489    //! Regression coverage for harn#2660: process-/thread-global
490    //! registries that accumulated one entry per test because they were
491    //! never drained by `reset_thread_local_state`. Each case populates a
492    //! registry through its real entry point, runs the reset, and asserts
493    //! the registry is empty again.
494    use super::*;
495    use crate::value::VmValue;
496
497    #[test]
498    fn reset_drains_agent_inbox() {
499        orchestration::agent_inbox::reset();
500        orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
501        assert!(orchestration::agent_inbox::session_count() > 0);
502        reset_thread_local_state();
503        assert_eq!(
504            orchestration::agent_inbox::session_count(),
505            0,
506            "agent_inbox must be empty after reset"
507        );
508    }
509
510    #[test]
511    fn reset_drains_tool_call_cancellation_registry() {
512        tool_call_cancellations::reset_registry();
513        // Leak the guard so the entry survives until the reset runs —
514        // this mirrors a dispatch abandoned mid-flight.
515        let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
516        if let Some((_handle, guard)) = registered {
517            std::mem::forget(guard);
518        }
519        assert!(tool_call_cancellations::registry_len() > 0);
520        reset_thread_local_state();
521        assert_eq!(
522            tool_call_cancellations::registry_len(),
523            0,
524            "tool-call cancellation registry must be empty after reset"
525        );
526    }
527
528    #[test]
529    fn reset_drains_routing_policy_registry() {
530        llm::routing::clear_policy_registry();
531        let mut config: crate::value::DictMap = crate::value::DictMap::new();
532        config.insert(
533            crate::value::intern_key("chain"),
534            VmValue::List(std::sync::Arc::new(vec![VmValue::String(
535                arcstr::ArcStr::from("mock:mock"),
536            )])),
537        );
538        llm::routing::build_routing_policy(&config).expect("intern a routing policy");
539        assert!(llm::routing::policy_registry_len() > 0);
540        reset_thread_local_state();
541        assert_eq!(
542            llm::routing::policy_registry_len(),
543            0,
544            "routing policy registry must be empty after reset"
545        );
546    }
547}