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