1#![recursion_limit = "256"]
2#![allow(clippy::result_large_err, clippy::cloned_ref_to_slice_refs)]
3pub 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 bridge;
34mod builtin_id;
35pub mod builtin_profile;
36pub mod bytecode_cache;
37pub mod call_budget;
38pub mod canonical_json;
39pub mod channel_guardrails;
40pub mod channels;
41pub mod checkpoint;
42mod chunk;
43mod compiler;
44pub mod composition;
45pub mod conditional_replace;
46pub mod config;
47pub mod connectors;
48pub mod corrections;
49pub mod coverage;
50pub(crate) mod durable_rate_limit;
51pub mod duration_parse;
52pub mod egress;
53pub mod event_log;
54pub mod events;
55pub mod external_agent;
56pub mod flow;
57pub mod harness;
58pub mod harness_auth;
59pub(crate) mod harness_crypto;
60pub mod harness_net;
61pub mod harness_system;
62pub mod harness_tenant;
63pub mod host_attachments;
64mod http;
65pub mod jsonrpc;
66pub(crate) mod limits;
67pub mod llm;
68pub mod llm_config;
69pub mod mcp;
70pub mod mcp_allowlist;
71pub mod mcp_auth;
72pub mod mcp_bulk_auth;
73pub mod mcp_card;
74pub mod mcp_client_request;
75pub mod mcp_client_roots;
76pub mod mcp_elicit;
77pub mod mcp_file_upload;
78pub mod mcp_host;
79pub mod mcp_identity;
80pub mod mcp_json_discovery;
81pub mod mcp_oauth;
82pub mod mcp_presets;
83pub mod mcp_progress;
84pub mod mcp_protocol;
85pub mod mcp_registry;
86pub mod mcp_sampling;
87pub mod mcp_server;
88pub mod metadata;
89pub mod module_artifact;
90pub mod observability;
91pub mod op_interrupt;
92pub mod orchestration;
93mod persistent_state;
94pub mod personas;
95mod prepared_module;
96pub mod process_sandbox;
97pub mod profile;
98pub mod provenance;
99pub mod provider_catalog;
100pub mod receipts;
101pub mod record_filter;
102pub mod redact;
103pub mod run_events;
104pub mod runtime_context;
105pub(crate) mod runtime_guards;
106pub mod runtime_limits;
107pub mod runtime_paths;
108pub(crate) mod runtime_sqlite;
109pub mod schema;
110pub(crate) mod secret_patterns;
111pub mod secrets;
112pub mod security;
113pub mod session_bundle;
114pub mod session_timeline;
115pub mod sessions;
116pub(crate) mod shared_state;
117pub mod shells;
118pub mod skills;
119pub mod stdlib;
120pub mod stdlib_modules;
121pub mod step_runtime;
122pub mod store;
123pub(crate) mod synchronization;
124pub mod tenant;
125pub(crate) mod term;
126pub(crate) mod test_env;
127pub mod testbench;
128pub mod text;
129pub mod text_diff;
130pub mod tool_annotations;
131pub mod tool_call_cancellations;
132pub mod tool_surface;
133pub mod tracing;
134pub mod triggers;
135pub mod trust_graph;
136pub(crate) mod url_encoding;
137pub mod user_dirs;
138
139pub fn initialize_runtime_assets() {
146 secret_patterns::initialize_default_secret_patterns();
147}
148
149pub mod clock_mock {
154 pub use crate::triggers::test_util::clock::{
155 active_mock_clock, advance, clear_overrides, install_override, instant_now, is_mocked,
156 now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
157 };
158
159 pub mod leak_audit {
163 pub use crate::triggers::test_util::clock_leak::{
164 drain, enter_scope, install_scope, instant_now, reset, snapshot, wall_now, ClockLeak,
165 ClockLeakScope, ClockLeakScopeGuard,
166 };
167 }
168}
169
170pub mod typecheck;
171pub mod value;
172pub mod verification;
173pub mod visible_text;
174mod vm;
175pub(crate) mod wait_for_graph;
176pub mod waitpoints;
177pub mod windows_path;
178pub mod workspace_anchor;
179pub mod workspace_path;
180
181pub use persistent_state::{register_persistent_state_builtins_at_root, PersistentStateRoot};
182pub use prepared_module::{PreparedModuleCache, PreparedModuleCacheStats};
183
184pub use actor_chain::{
185 ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
186 ScopeAttenuationPolicy, ScopeAttenuationViolation,
187};
188pub use builtin_id::BuiltinId;
189pub use call_budget::{
190 charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
191 McpCallBudgetGuard, PgQueryBudgetGuard,
192};
193pub use checkpoint::register_checkpoint_builtins;
194pub use chunk::*;
195pub use compiler::*;
196pub use connectors::{
197 active_connector_client, active_metrics_registry, clear_active_connector_clients,
198 clear_active_metrics_registry, connector_export_denied_builtin_reason,
199 connector_export_effect_class,
200 cron::{CatchupMode, CronConnector},
201 default_connector_export_policy,
202 harn_module::{
203 load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
204 },
205 hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
206 install_active_connector_clients, install_active_metrics_registry,
207 postprocess_normalized_event, ActivationHandle, ClientError, Connector, ConnectorClient,
208 ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
209 ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
210 HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
211 RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
212 TriggerRegistry, WebhookSignatureVariant,
213};
214pub use corrections::{
215 append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
216 correction_record_from_json, policy_with_corrections, query_correction_records,
217 CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
218 CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
219};
220pub use harness::{
221 DenyEvent, Harness, HarnessCall, HarnessClock, HarnessCrypto, HarnessEnv, HarnessFs,
222 HarnessKind, HarnessLlm, HarnessNet, HarnessObs, HarnessProcess, HarnessRandom, HarnessSecrets,
223 HarnessStdio, HarnessSystem, HarnessTenant, HarnessTerm, MockAwareClock, MockHarnessBuilder,
224 VmHarness,
225};
226pub use harness_auth::{
227 current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
228 MISSING_PRINCIPAL_MESSAGE,
229};
230pub use harness_net::{
231 bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
232 NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
233 NET_POLICY_AUDIT_TOPIC,
234};
235pub use harness_tenant::{
236 current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
237};
238pub use http::{register_http_builtins, reset_http_state};
239pub use llm::register_llm_builtins;
240pub use llm::trigger_predicate::TriggerPredicateBudget;
241pub use llm::{
242 current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
243 peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
244 set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard, SessionEndHookRegistration,
245};
246pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
247pub use mcp_allowlist::{
248 build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
249 AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
250 McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
251 MCP_ALLOWLIST_SCHEMA_VERSION,
252};
253pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
254pub use mcp_host::{
255 cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
256 AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
257 BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
258 SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
259};
260pub use mcp_registry::{
261 active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
262 get_registration as mcp_get_registration, install_active as mcp_install_active,
263 is_registered as mcp_is_registered, register_servers as mcp_register_servers,
264 release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
265 sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
266};
267pub use mcp_server::{
268 take_mcp_serve_metadata, take_mcp_serve_prompts, take_mcp_serve_registry,
269 take_mcp_serve_resource_templates, take_mcp_serve_resources, tool_registry_to_mcp_tools,
270 McpServer, McpServerMetadata,
271};
272pub use metadata::register_metadata_builtins;
273pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
274pub use observability::execution_scope::{
275 current_execution_scope, enter_execution_scope, mint_execution_scope, ExecutionScopeGuard,
276};
277pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
278pub use orchestration::{
279 benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
280 OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
281 ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
282 ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
283 ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
284 ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
285 REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
286};
287pub use orchestration::{
288 canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
289 ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
290 ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
291};
292pub use orchestration::{
293 install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
294 HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
295};
296pub use personas::{
297 disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
298 format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
299 pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
300 register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
301 resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
302 PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
303 PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
304 PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
305 PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
306 PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
307 PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
308 PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
309 PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
310};
311pub use provenance::{
312 build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
313 ReceiptBuildOptions, ReceiptVerificationReport,
314};
315pub use receipts::{
316 Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
317 RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
318};
319pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
320pub use runtime_limits::{
321 RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
322 RUNTIME_LIMIT_DESCRIPTIONS,
323};
324pub use schema::json_to_vm_value;
325pub use sessions::{
326 CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
327 TouchSession, SESSIONS_TOPIC,
328};
329pub use stdlib::hitl::{
330 append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
331 HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
332};
333pub use stdlib::host::{clear_host_call_bridge, set_host_call_bridge, HostCallBridge};
334pub use stdlib::http_response::{
335 parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
336 HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
337};
338#[cfg(feature = "postgres")]
339pub use stdlib::install_shared_pool_registry;
340pub use stdlib::io::{
341 reserve_stdio_for_current_thread, set_stdout_passthrough, take_stderr_buffer,
342 StdioReservationGuard,
343};
344pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
345pub use stdlib::observability::install_default_backend as install_obs_default_backend;
346pub use stdlib::secret_scan::{
347 append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
348 SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
349};
350pub use stdlib::template::{
351 lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
352 PromptSourceSpan, PromptSpanKind,
353};
354pub use stdlib::waitpoint::{
355 process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
356};
357pub use stdlib::workflow_messages::{
358 workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
359 workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
360 workflow_update_for_base, WorkflowMailboxState,
361};
362pub use stdlib::{
363 register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
364};
365pub use store::register_store_builtins;
366pub use tenant::{
367 tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
368 TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
369 TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
370 TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
371 TENANT_SECRET_NAMESPACE_PREFIX,
372};
373pub use triggers::{
374 append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
375 binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
376 clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
377 dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
378 install_manifest_triggers, install_orchestrator_budget, install_provider_catalog,
379 micros_to_usd, note_autonomous_decision, note_orchestrator_budget_cost,
380 orchestrator_budget_would_exceed, parse_flow_control_duration, pause, pin_trigger_binding,
381 provider_metadata, record_predicate_cost_sample, redact_headers, register_provider_schema,
382 registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
383 reset_provider_catalog, reset_provider_catalog_with, resolve_live_or_as_of,
384 resolve_live_trigger_binding, resolve_trigger_binding_as_of, resume,
385 run_trigger_harness_fixture, scheduler_in_flight_by_key, scheduler_ready_stats_by_key,
386 snapshot_dispatcher_stats, snapshot_orchestrator_budget, snapshot_trigger_bindings,
387 unpin_trigger_binding, usd_to_micros, worker_claims_topic_name, worker_job_topic_name,
388 worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest, DispatchError,
389 DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport, DispatcherStatsSnapshot,
390 FairnessKey, HeaderRedactionPolicy, InboxIndex, NotionPolledChangeEvent,
391 OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog, ProviderCatalogError,
392 ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload, ProviderRuntimeMetadata,
393 ProviderSchema, ProviderSecretRequirement, ReadyKeyStats, RecordedTriggerBinding, RetryPolicy,
394 SchedulableJob, SchedulerKeyStat, SchedulerPolicy, SchedulerSnapshot, SchedulerState,
395 SchedulerStrategy, SignatureStatus, SignatureVerificationMetadata, StreamEventPayload,
396 TenantId, TraceId, TriggerBatchConfig, TriggerBindingSnapshot, TriggerBindingSource,
397 TriggerBindingSpec, TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig,
398 TriggerDebounceConfig, TriggerDispatchOutcome, TriggerEvent, TriggerEventId,
399 TriggerExpressionSpec, TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult,
400 TriggerId, TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
401 TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
402 TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
403 WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
404 WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
405 DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
406 TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
407 TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
408 TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
409 TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
410};
411pub use trust_graph::{
412 append_active_scope_attenuation_alert, append_active_trust_record,
413 append_scope_attenuation_alert, append_trust_record, export_trust_chain,
414 group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
415 query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
416 summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
417 TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
418 TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
419 TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
420 METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
421 METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
422 OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
423 TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
424 TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
425};
426pub use value::*;
427pub use vm::*;
428
429#[cfg(feature = "vm-bench-internals")]
430#[doc(hidden)]
431pub mod bench_internals;
432
433pub fn compile_source(source: &str) -> Result<Chunk, String> {
438 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
439 Compiler::new().compile(&program).map_err(|e| e.to_string())
440}
441
442pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
447 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
448 let has_pipeline = program.iter().any(|sn| {
449 let (_, inner) = harn_parser::peel_attributes(sn);
450 matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
451 });
452 if !has_pipeline {
453 return Err(format!("no pipeline named `{pipeline_name}` in source"));
454 }
455 Compiler::new()
456 .compile_named(&program, pipeline_name)
457 .map_err(|e| e.to_string())
458}
459
460pub struct SchemaAliasResolver {
468 compiler: compiler::Compiler,
469}
470
471impl SchemaAliasResolver {
472 pub fn empty() -> Self {
475 Self {
476 compiler: compiler::Compiler::new(),
477 }
478 }
479
480 pub fn from_program(program: &[harn_parser::SNode]) -> Self {
483 let mut compiler = compiler::Compiler::new();
484 compiler.collect_type_aliases(program);
485 Self { compiler }
486 }
487
488 pub fn json_schema_for_type_expr(
491 &self,
492 type_expr: &harn_parser::TypeExpr,
493 ) -> Option<serde_json::Value> {
494 let expanded = self.compiler.expand_alias(type_expr);
495 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
496 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
497 Some(llm::vm_value_to_json(&json_schema))
498 }
499
500 pub fn json_schema_for_typed_params(
503 &self,
504 params: &[harn_parser::TypedParam],
505 ) -> serde_json::Value {
506 let mut properties = serde_json::Map::new();
507 let mut required = Vec::new();
508
509 for param in params {
510 let param_schema = param
511 .type_expr
512 .as_ref()
513 .and_then(|type_expr| self.json_schema_for_type_expr(type_expr))
514 .unwrap_or_else(|| serde_json::json!({}));
515 if param.default_value.is_none() {
516 required.push(serde_json::Value::String(param.name.clone()));
517 }
518 properties.insert(param.name.clone(), param_schema);
519 }
520
521 let mut schema = serde_json::Map::new();
522 schema.insert(
523 "type".to_string(),
524 serde_json::Value::String("object".to_string()),
525 );
526 schema.insert(
527 "properties".to_string(),
528 serde_json::Value::Object(properties),
529 );
530 if !required.is_empty() {
531 schema.insert("required".to_string(), serde_json::Value::Array(required));
532 }
533 serde_json::Value::Object(schema)
534 }
535}
536
537pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
541 SchemaAliasResolver::empty().json_schema_for_type_expr(type_expr)
542}
543
544pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
545 SchemaAliasResolver::empty().json_schema_for_typed_params(params)
546}
547
548#[cfg(test)]
549mod schema_alias_resolver_tests {
550 use super::*;
551
552 fn fn_params_schema(src: &str) -> serde_json::Value {
553 let program = harn_parser::parse_source(src).expect("parse test source");
554 let resolver = SchemaAliasResolver::from_program(&program);
555 for node in &program {
556 let (_, inner) = harn_parser::peel_attributes(node);
557 if let harn_parser::Node::FnDecl { params, .. } = &inner.node {
558 return resolver.json_schema_for_typed_params(params);
559 }
560 }
561 panic!("no fn decl in test source");
562 }
563
564 #[test]
565 fn named_shape_alias_projects_like_inline_shape() {
566 let inline = fn_params_schema("pub fn f(p: {kind: string, path: string}) {}");
567 let aliased =
568 fn_params_schema("type Src = {kind: string, path: string}\npub fn f(p: Src) {}");
569 assert_eq!(
570 aliased, inline,
571 "a named shape alias must project the same inputSchema as its inline shape",
572 );
573 assert_ne!(
574 aliased["properties"]["p"],
575 serde_json::json!({}),
576 "the alias parameter must not erase to an empty schema",
577 );
578 }
579
580 #[test]
581 fn literal_union_alias_projects_json_enum() {
582 let schema = fn_params_schema("type Kind = \"local\" | \"ssh\"\npub fn f(p: Kind) {}");
583 let p = &schema["properties"]["p"];
584 assert_eq!(p["type"], "string");
585 assert_eq!(p["enum"], serde_json::json!(["local", "ssh"]));
586 }
587
588 #[test]
589 fn unknown_named_type_still_erases_to_empty() {
590 let schema = fn_params_schema("pub fn f(p: Unknown) {}");
592 assert_eq!(schema["properties"]["p"], serde_json::json!({}));
593 }
594}
595
596fn reset_llm_state_for_thread_reset() {
597 llm::reset_llm_state();
598 #[cfg(test)]
599 reset_thread_local_state_test_hooks::before_llm_global_reset();
600 llm::reset_rate_limit_registry();
603 llm_config::clear_user_overrides();
604 llm_config::clear_runtime_provider_endpoint_overrides();
605}
606
607#[cfg(test)]
608mod reset_thread_local_state_test_hooks {
609 use std::sync::{Arc, Mutex, OnceLock};
610
611 type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
612
613 static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
614
615 fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
616 BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
617 }
618
619 pub(crate) struct HookGuard;
620
621 impl Drop for HookGuard {
622 fn drop(&mut self) {
623 let mut hook = before_llm_global_reset_hook()
624 .lock()
625 .unwrap_or_else(std::sync::PoisonError::into_inner);
626 *hook = None;
627 }
628 }
629
630 pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
631 let mut slot = before_llm_global_reset_hook()
632 .lock()
633 .unwrap_or_else(std::sync::PoisonError::into_inner);
634 *slot = Some(hook);
635 HookGuard
636 }
637
638 pub(crate) fn before_llm_global_reset() {
639 let hook = before_llm_global_reset_hook()
640 .lock()
641 .unwrap_or_else(std::sync::PoisonError::into_inner)
642 .clone();
643 if let Some(hook) = hook {
644 hook();
645 }
646 }
647}
648
649pub fn reset_thread_local_state() {
651 #[cfg(test)]
652 {
653 let _guard = llm::env_guard();
658 reset_llm_state_for_thread_reset();
659 }
660 #[cfg(not(test))]
661 reset_llm_state_for_thread_reset();
662
663 http::reset_http_state();
664 channels::reset_channel_state();
665 event_log::reset_active_event_log();
666 egress::clear_explicit_egress_policy_requirement_for_host();
667 egress::clear_ssrf_guard_requirement_for_host();
668 stdlib::reset_stdlib_state();
669 connectors::clear_active_connector_clients();
670 orchestration::clear_runtime_hooks();
671 orchestration::clear_file_edit_queue();
672 orchestration::clear_execution_policy_stacks();
673 orchestration::clear_command_policies();
674 orchestration::clear_pipeline_on_finish();
675 orchestration::reset_lifecycle_receipt_registry();
676 orchestration::agent_inbox::reset();
677 tool_call_cancellations::reset_registry();
678 redact::clear_policy_stack();
679 security::reset_thread_state();
680 triggers::clear_dispatcher_state();
681 triggers::clear_trigger_registry();
682 events::reset_event_sinks();
683 tracing::set_tracing_enabled(false);
684 tracing::reset_tracing();
685 builtin_profile::reset();
686 agent_events::reset_all_sinks();
687 agent_sessions::reset_session_store();
688 mcp_registry::reset();
689 mcp_host::reset_for_tests();
690 call_budget::reset_call_budget_state();
691 clock_mock::leak_audit::reset();
692}
693
694#[cfg(test)]
695mod reset_leak_tests {
696 use super::*;
702 use crate::value::VmValue;
703
704 #[test]
705 fn reset_drains_pending_file_edit_notifications() {
706 orchestration::queue_file_edited("stale.harn", serde_json::json!({"operation": "write"}));
707
708 reset_thread_local_state();
709
710 assert!(
711 orchestration::drain_file_edits().is_empty(),
712 "a later VM run must not receive file edits queued by the previous run"
713 );
714 }
715
716 #[test]
722 fn reset_disables_and_drains_builtin_profile() {
723 let _guard = builtin_profile::test_lock()
724 .lock()
725 .unwrap_or_else(std::sync::PoisonError::into_inner);
726 builtin_profile::enable();
727 builtin_profile::record("run_shell", std::time::Duration::from_millis(5));
728 assert!(builtin_profile::is_enabled());
729 assert!(!builtin_profile::snapshot().is_empty());
730
731 reset_thread_local_state();
732
733 assert!(
734 !builtin_profile::is_enabled(),
735 "a profiled run must not leave the recorder on for the next one"
736 );
737 assert!(
738 builtin_profile::snapshot().is_empty(),
739 "builtin totals must be empty after reset"
740 );
741 }
742
743 #[test]
748 fn reset_drains_session_changed_paths() {
749 let session = "sess-leak";
750 agent_sessions::open_or_create(Some(session.to_string()));
751 agent_sessions::record_session_changed_path(session, "/tmp/written-by-a-dead-run.txt");
752 assert!(!agent_sessions::session_changed_paths(session).is_empty());
753 reset_thread_local_state();
754 assert!(
755 agent_sessions::session_changed_paths(session).is_empty(),
756 "a receipt must not inherit an abandoned session's writes"
757 );
758 }
759
760 #[test]
761 fn reset_drains_agent_inbox() {
762 orchestration::agent_inbox::reset();
763 orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
764 assert!(orchestration::agent_inbox::session_count() > 0);
765 reset_thread_local_state();
766 assert_eq!(
767 orchestration::agent_inbox::session_count(),
768 0,
769 "agent_inbox must be empty after reset"
770 );
771 }
772
773 #[test]
774 fn reset_drains_tool_call_cancellation_registry() {
775 tool_call_cancellations::reset_registry();
776 let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
779 if let Some((_handle, guard)) = registered {
780 std::mem::forget(guard);
781 }
782 assert!(tool_call_cancellations::registry_len() > 0);
783 reset_thread_local_state();
784 assert_eq!(
785 tool_call_cancellations::registry_len(),
786 0,
787 "tool-call cancellation registry must be empty after reset"
788 );
789 }
790
791 #[test]
792 fn reset_drains_routing_policy_registry() {
793 llm::routing::clear_policy_registry();
794 let mut config: crate::value::DictMap = crate::value::DictMap::new();
795 config.insert(
796 crate::value::intern_key("chain"),
797 VmValue::List(std::sync::Arc::new(vec![VmValue::String(
798 arcstr::ArcStr::from("mock:mock"),
799 )])),
800 );
801 llm::routing::build_routing_policy(&config).expect("intern a routing policy");
802 assert!(llm::routing::policy_registry_len() > 0);
803 reset_thread_local_state();
804 assert_eq!(
805 llm::routing::policy_registry_len(),
806 0,
807 "routing policy registry must be empty after reset"
808 );
809 }
810
811 #[test]
812 fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
813 let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
814 let observed_hook = std::sync::Arc::clone(&observed);
815 let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
816 std::sync::Arc::new(move || {
817 assert!(
818 matches!(
819 llm::env_lock().try_lock(),
820 Err(std::sync::TryLockError::WouldBlock)
821 ),
822 "reset_thread_local_state must hold env_guard before wiping LLM globals"
823 );
824 observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
825 }),
826 );
827
828 reset_thread_local_state();
829 assert!(
830 observed.load(std::sync::atomic::Ordering::SeqCst),
831 "LLM global reset hook should have run"
832 );
833 }
834}