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 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
143pub fn initialize_runtime_assets() {
151 secret_patterns::initialize_default_secret_patterns();
152}
153
154pub fn initialize_runtime() -> Result<(), environment_registry::EnvironmentValidationError> {
157 environment_registry::validate_startup_environment()?;
158 initialize_runtime_assets();
159 Ok(())
160}
161
162pub 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 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};
344pub 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};
354pub 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
459pub 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
468pub 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
486pub struct SchemaAliasResolver {
494 compiler: compiler::Compiler,
495}
496
497impl SchemaAliasResolver {
498 pub fn empty() -> Self {
501 Self {
502 compiler: compiler::Compiler::new(),
503 }
504 }
505
506 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 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 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
563pub 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 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 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
675pub fn reset_thread_local_state() {
677 #[cfg(test)]
678 {
679 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 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 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 #[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 #[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 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}