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;
35pub use bridge::inject_leading_authority;
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;
99pub mod portable;
100mod prepared_module;
101pub mod process_sandbox;
102pub mod profile;
103pub mod provenance;
104pub mod provider_catalog;
105pub mod receipts;
106pub mod record_filter;
107pub mod redact;
108pub mod run_events;
109pub mod runtime_context;
110pub(crate) mod runtime_guards;
111pub mod runtime_limits;
112pub mod runtime_paths;
113pub(crate) mod runtime_sqlite;
114pub mod schema;
115pub(crate) mod secret_patterns;
116pub mod secrets;
117pub mod security;
118pub mod session_bundle;
119pub mod session_timeline;
120pub mod sessions;
121pub(crate) mod shared_state;
122pub mod shells;
123pub mod skills;
124pub mod stdlib;
125pub mod stdlib_modules;
126pub mod step_runtime;
127pub mod store;
128pub(crate) mod synchronization;
129pub mod tenant;
130pub(crate) mod term;
131pub(crate) mod test_env;
132pub mod testbench;
133pub mod text;
134pub mod text_diff;
135pub mod tool_annotations;
136pub mod tool_call_cancellations;
137pub mod tool_surface;
138pub mod tracing;
139pub mod triggers;
140pub mod trust_graph;
141pub(crate) mod url_encoding;
142pub mod user_dirs;
143
144pub fn initialize_runtime_assets() {
152 secret_patterns::initialize_default_secret_patterns();
153}
154
155pub fn initialize_runtime() -> Result<(), environment_registry::EnvironmentValidationError> {
158 environment_registry::validate_startup_environment()?;
159 initialize_runtime_assets();
160 Ok(())
161}
162
163pub mod clock_mock {
168 pub(crate) use crate::triggers::test_util::clock::scope_capability_clock;
169 pub use crate::triggers::test_util::clock::{
170 active_clock, active_mock_clock, advance, clear_overrides, install_override, instant_now,
171 is_mocked, now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
172 };
173
174 pub mod leak_audit {
178 pub use crate::triggers::test_util::clock_leak::{
179 drain, enter_scope, install_scope, instant_now, reset, snapshot, wall_now, ClockLeak,
180 ClockLeakScope, ClockLeakScopeGuard,
181 };
182 }
183}
184
185pub(crate) mod text_index;
186pub mod typecheck;
187pub mod value;
188pub mod verification;
189pub mod visible_text;
190mod vm;
191pub(crate) mod wait_for_graph;
192pub mod waitpoints;
193pub mod windows_path;
194pub mod workspace_anchor;
195pub mod workspace_path;
196
197pub use persistent_state::{register_persistent_state_builtins_at_root, PersistentStateRoot};
198pub use prepared_module::{PreparedModuleCache, PreparedModuleCacheStats};
199
200pub use actor_chain::{
201 ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
202 ScopeAttenuationPolicy, ScopeAttenuationViolation,
203};
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 harn_kernel::BuiltinId;
236pub use harness::{
237 DenyEvent, Harness, HarnessAgent, HarnessCall, HarnessChannels, HarnessClock, HarnessEnv,
238 HarnessFs, HarnessKind, HarnessLlm, HarnessMemory, HarnessNet, HarnessObs, HarnessPostgres,
239 HarnessProcess, HarnessRandom, HarnessSecrets, HarnessSqlite, HarnessStdio, HarnessSystem,
240 HarnessTenant, HarnessTerm, HarnessTesting, MockHarnessBuilder, VmHarness,
241};
242pub use harness_auth::{
243 current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
244 MISSING_PRINCIPAL_MESSAGE,
245};
246pub use harness_net::{
247 bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
248 NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
249 NET_POLICY_AUDIT_TOPIC,
250};
251pub use harness_tenant::{
252 current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
253};
254pub use http::{register_http_builtins, reset_http_state};
255pub use llm::register_llm_builtins;
256pub use llm::trigger_predicate::TriggerPredicateBudget;
257pub use llm::{
258 current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
259 peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
260 set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard, SessionEndHookRegistration,
261};
262pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
263pub use mcp_allowlist::{
264 build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
265 AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
266 McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
267 MCP_ALLOWLIST_SCHEMA_VERSION,
268};
269pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
270pub use mcp_host::{
271 cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
272 AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
273 BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
274 SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
275};
276pub use mcp_registry::{
277 active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
278 get_registration as mcp_get_registration, install_active as mcp_install_active,
279 is_registered as mcp_is_registered, register_servers as mcp_register_servers,
280 release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
281 sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
282};
283pub use mcp_server::{
284 take_mcp_serve_metadata, take_mcp_serve_prompts, take_mcp_serve_registry,
285 take_mcp_serve_resource_templates, take_mcp_serve_resources, tool_registry_to_mcp_tools,
286 McpServer, McpServerMetadata,
287};
288pub use metadata::register_metadata_builtins;
289pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
290pub use observability::execution_scope::{
291 current_execution_scope, enter_execution_scope, mint_execution_scope, ExecutionScopeGuard,
292};
293pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
294pub use orchestration::{
295 benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
296 OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
297 ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
298 ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
299 ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
300 ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
301 REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
302};
303pub use orchestration::{
304 canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
305 ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
306 ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
307};
308pub use orchestration::{
309 install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
310 HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
311};
312pub use personas::{
313 disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
314 format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
315 pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
316 register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
317 resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
318 PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
319 PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
320 PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
321 PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
322 PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
323 PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
324 PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
325 PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
326};
327pub use provenance::{
328 build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
329 ReceiptBuildOptions, ReceiptVerificationReport,
330};
331pub use receipts::{
332 Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
333 RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
334};
335pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
336pub use runtime_limits::{
337 RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
338 RUNTIME_LIMIT_DESCRIPTIONS,
339};
340pub use schema::json_to_vm_value;
341pub use sessions::{
342 CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
343 TouchSession, SESSIONS_TOPIC,
344};
345pub use stdlib::fs::ignore_policy;
351pub use stdlib::hitl::{
352 append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
353 HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
354};
355pub use stdlib::host::turn_cache as host_turn_cache;
357pub use stdlib::host::{
358 clear_host_call_bridge, dispatch_host_operation, host_call_ready, set_host_call_bridge,
359 HostCallBridge, HostCallDispatchFuture,
360};
361pub use stdlib::http_response::{
362 parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
363 HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
364};
365#[cfg(feature = "postgres")]
366pub use stdlib::install_shared_pool_registry;
367pub use stdlib::io::{
368 reserve_stdio_for_current_thread, set_stdout_passthrough, take_stderr_buffer,
369 StdioReservationGuard,
370};
371pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
372pub use stdlib::observability::install_default_backend as install_obs_default_backend;
373pub use stdlib::secret_scan::{
374 append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
375 SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
376};
377pub use stdlib::template::{
378 lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
379 PromptSourceSpan, PromptSpanKind,
380};
381pub use stdlib::waitpoint::{
382 process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
383};
384pub use stdlib::workflow_messages::{
385 workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
386 workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
387 workflow_update_for_base, WorkflowMailboxState,
388};
389pub use stdlib::{
390 register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
391};
392pub use store::register_store_builtins;
393pub use tenant::{
394 tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
395 TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
396 TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
397 TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
398 TENANT_SECRET_NAMESPACE_PREFIX,
399};
400pub use triggers::{
401 append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
402 binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
403 clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
404 dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
405 install_manifest_triggers, install_orchestrator_budget, micros_to_usd,
406 note_autonomous_decision, note_orchestrator_budget_cost, orchestrator_budget_would_exceed,
407 parse_flow_control_duration, pause, pin_trigger_binding, provider_metadata,
408 record_predicate_cost_sample, redact_headers, register_provider_schemas,
409 registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
410 reset_provider_catalog, resolve_live_or_as_of, resolve_live_trigger_binding,
411 resolve_trigger_binding_as_of, resume, run_trigger_harness_fixture, scheduler_in_flight_by_key,
412 scheduler_ready_stats_by_key, snapshot_dispatcher_stats, snapshot_orchestrator_budget,
413 snapshot_trigger_bindings, unpin_trigger_binding, usd_to_micros, worker_claims_topic_name,
414 worker_job_topic_name, worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest,
415 DispatchError, DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport,
416 DispatcherStatsSnapshot, ExtensionProviderPayload, FairnessKey, HeaderRedactionPolicy,
417 InboxIndex, OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog,
418 ProviderCatalogError, ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload,
419 ProviderRuntimeMetadata, ProviderSchema, ProviderSecretRequirement, ReadyKeyStats,
420 RecordedTriggerBinding, RetryPolicy, SchedulableJob, SchedulerKeyStat, SchedulerPolicy,
421 SchedulerSnapshot, SchedulerState, SchedulerStrategy, SignatureStatus,
422 SignatureVerificationMetadata, StreamEventPayload, TenantId, TraceId, TriggerBatchConfig,
423 TriggerBindingSnapshot, TriggerBindingSource, TriggerBindingSpec,
424 TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig, TriggerDebounceConfig,
425 TriggerDispatchOutcome, TriggerEvent, TriggerEventId, TriggerExpressionSpec,
426 TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult, TriggerId,
427 TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
428 TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
429 TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
430 WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
431 WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
432 DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
433 TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
434 TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
435 TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
436 TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
437};
438pub use trust_graph::{
439 append_active_scope_attenuation_alert, append_active_trust_record,
440 append_scope_attenuation_alert, append_trust_record, export_trust_chain,
441 group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
442 query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
443 summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
444 TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
445 TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
446 TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
447 METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
448 METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
449 OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
450 TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
451 TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
452};
453pub use value::*;
454pub use vm::*;
455
456#[cfg(feature = "vm-bench-internals")]
457#[doc(hidden)]
458pub mod bench_internals;
459
460pub fn compile_source(source: &str) -> Result<Chunk, String> {
465 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
466 Compiler::new().compile(&program).map_err(|e| e.to_string())
467}
468
469pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
474 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
475 let has_pipeline = program.iter().any(|sn| {
476 let (_, inner) = harn_parser::peel_attributes(sn);
477 matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
478 });
479 if !has_pipeline {
480 return Err(format!("no pipeline named `{pipeline_name}` in source"));
481 }
482 Compiler::new()
483 .compile_named(&program, pipeline_name)
484 .map_err(|e| e.to_string())
485}
486
487pub struct SchemaAliasResolver {
495 compiler: compiler::Compiler,
496}
497
498impl SchemaAliasResolver {
499 pub fn empty() -> Self {
502 Self {
503 compiler: compiler::Compiler::new(),
504 }
505 }
506
507 pub fn from_program(program: &[harn_parser::SNode]) -> Self {
510 let mut compiler = compiler::Compiler::new();
511 compiler.collect_type_aliases(program);
512 Self { compiler }
513 }
514
515 pub fn json_schema_for_type_expr(
518 &self,
519 type_expr: &harn_parser::TypeExpr,
520 ) -> Option<serde_json::Value> {
521 let expanded = self.compiler.expand_alias(type_expr);
522 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
523 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
524 Some(llm::vm_value_to_json(&json_schema))
525 }
526
527 pub fn json_schema_for_typed_params(
530 &self,
531 params: &[harn_parser::TypedParam],
532 ) -> serde_json::Value {
533 let mut properties = serde_json::Map::new();
534 let mut required = Vec::new();
535
536 for param in params {
537 let param_schema = param
538 .type_expr
539 .as_ref()
540 .and_then(|type_expr| self.json_schema_for_type_expr(type_expr))
541 .unwrap_or_else(|| serde_json::json!({}));
542 if param.default_value.is_none() {
543 required.push(serde_json::Value::String(param.name.clone()));
544 }
545 properties.insert(param.name.clone(), param_schema);
546 }
547
548 let mut schema = serde_json::Map::new();
549 schema.insert(
550 "type".to_string(),
551 serde_json::Value::String("object".to_string()),
552 );
553 schema.insert(
554 "properties".to_string(),
555 serde_json::Value::Object(properties),
556 );
557 if !required.is_empty() {
558 schema.insert("required".to_string(), serde_json::Value::Array(required));
559 }
560 serde_json::Value::Object(schema)
561 }
562}
563
564pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
568 SchemaAliasResolver::empty().json_schema_for_type_expr(type_expr)
569}
570
571pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
572 SchemaAliasResolver::empty().json_schema_for_typed_params(params)
573}
574
575#[cfg(test)]
576mod schema_alias_resolver_tests {
577 use super::*;
578
579 fn fn_params_schema(src: &str) -> serde_json::Value {
580 let program = harn_parser::parse_source(src).expect("parse test source");
581 let resolver = SchemaAliasResolver::from_program(&program);
582 for node in &program {
583 let (_, inner) = harn_parser::peel_attributes(node);
584 if let harn_parser::Node::FnDecl { params, .. } = &inner.node {
585 return resolver.json_schema_for_typed_params(params);
586 }
587 }
588 panic!("no fn decl in test source");
589 }
590
591 #[test]
592 fn named_shape_alias_projects_like_inline_shape() {
593 let inline = fn_params_schema("pub fn f(p: {kind: string, path: string}) {}");
594 let aliased =
595 fn_params_schema("type Src = {kind: string, path: string}\npub fn f(p: Src) {}");
596 assert_eq!(
597 aliased, inline,
598 "a named shape alias must project the same inputSchema as its inline shape",
599 );
600 assert_ne!(
601 aliased["properties"]["p"],
602 serde_json::json!({}),
603 "the alias parameter must not erase to an empty schema",
604 );
605 }
606
607 #[test]
608 fn literal_union_alias_projects_json_enum() {
609 let schema = fn_params_schema("type Kind = \"local\" | \"ssh\"\npub fn f(p: Kind) {}");
610 let p = &schema["properties"]["p"];
611 assert_eq!(p["type"], "string");
612 assert_eq!(p["enum"], serde_json::json!(["local", "ssh"]));
613 }
614
615 #[test]
616 fn unknown_named_type_still_erases_to_empty() {
617 let schema = fn_params_schema("pub fn f(p: Unknown) {}");
619 assert_eq!(schema["properties"]["p"], serde_json::json!({}));
620 }
621}
622
623fn reset_llm_state_for_thread_reset() {
624 llm::reset_llm_state();
625 #[cfg(test)]
626 reset_thread_local_state_test_hooks::before_llm_global_reset();
627 llm::reset_rate_limit_registry();
630 llm_config::clear_user_overrides();
631 llm_config::clear_runtime_provider_endpoint_overrides();
632}
633
634#[cfg(test)]
635mod reset_thread_local_state_test_hooks {
636 use std::sync::{Arc, Mutex, OnceLock};
637
638 type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
639
640 static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
641
642 fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
643 BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
644 }
645
646 pub(crate) struct HookGuard;
647
648 impl Drop for HookGuard {
649 fn drop(&mut self) {
650 let mut hook = before_llm_global_reset_hook()
651 .lock()
652 .unwrap_or_else(std::sync::PoisonError::into_inner);
653 *hook = None;
654 }
655 }
656
657 pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
658 let mut slot = before_llm_global_reset_hook()
659 .lock()
660 .unwrap_or_else(std::sync::PoisonError::into_inner);
661 *slot = Some(hook);
662 HookGuard
663 }
664
665 pub(crate) fn before_llm_global_reset() {
666 let hook = before_llm_global_reset_hook()
667 .lock()
668 .unwrap_or_else(std::sync::PoisonError::into_inner)
669 .clone();
670 if let Some(hook) = hook {
671 hook();
672 }
673 }
674}
675
676pub fn reset_thread_local_state() {
678 #[cfg(test)]
679 {
680 let _guard = llm::env_guard();
685 reset_llm_state_for_thread_reset();
686 }
687 #[cfg(not(test))]
688 reset_llm_state_for_thread_reset();
689
690 http::reset_http_state();
691 channels::reset_channel_state();
692 event_log::reset_active_event_log();
693 egress::clear_explicit_egress_policy_requirement_for_host();
694 egress::clear_ssrf_guard_requirement_for_host();
695 stdlib::reset_stdlib_state();
696 connectors::clear_active_connector_clients();
697 orchestration::clear_runtime_hooks();
698 orchestration::clear_file_edit_queue();
699 orchestration::clear_execution_policy_stacks();
700 orchestration::clear_command_policies();
701 orchestration::clear_pipeline_on_finish();
702 orchestration::reset_lifecycle_receipt_registry();
703 orchestration::agent_inbox::reset();
704 tool_call_cancellations::reset_registry();
705 redact::clear_policy_stack();
706 security::reset_thread_state();
707 triggers::clear_dispatcher_state();
708 triggers::clear_trigger_registry();
709 events::reset_event_sinks();
710 tracing::set_tracing_enabled(false);
711 tracing::reset_tracing();
712 agent_events::reset_all_sinks();
724 agent_sessions::reset_session_store();
725 mcp_registry::reset();
726 mcp_host::reset_for_tests();
727 call_budget::reset_call_budget_state();
728 clock_mock::leak_audit::reset();
729}
730
731#[cfg(test)]
732mod reset_leak_tests {
733 use super::*;
739 use crate::value::VmValue;
740
741 #[test]
742 fn reset_drains_pending_file_edit_notifications() {
743 orchestration::queue_file_edited("stale.harn", serde_json::json!({"operation": "write"}));
744
745 reset_thread_local_state();
746
747 assert!(
748 orchestration::drain_file_edits().is_empty(),
749 "a later VM run must not receive file edits queued by the previous run"
750 );
751 }
752
753 #[test]
762 fn builtin_profile_recording_ends_with_its_run_not_with_a_global_reset() {
763 let _lock = builtin_profile::test_lock()
764 .lock()
765 .unwrap_or_else(std::sync::PoisonError::into_inner);
766 let recording = builtin_profile::enable();
767 builtin_profile::record("run_shell", std::time::Duration::from_millis(5));
768 assert!(builtin_profile::is_enabled());
769 assert!(!builtin_profile::snapshot().is_empty());
770
771 reset_thread_local_state();
772
773 assert!(
774 builtin_profile::is_enabled(),
775 "an unrelated global reset must not disarm an in-flight profiled run"
776 );
777 assert!(
778 !builtin_profile::snapshot().is_empty(),
779 "an unrelated global reset must not drop totals the run still owns"
780 );
781
782 drop(recording);
783
784 assert!(
785 !builtin_profile::is_enabled(),
786 "a profiled run must not leave the recorder on for the next one"
787 );
788 assert!(
789 builtin_profile::snapshot().is_empty(),
790 "builtin totals must be empty once the run's guard drops"
791 );
792 }
793
794 #[test]
799 fn reset_drains_session_changed_paths() {
800 let session = "sess-leak";
801 agent_sessions::open_or_create(Some(session.to_string()));
802 agent_sessions::record_session_changed_path(session, "/tmp/written-by-a-dead-run.txt");
803 assert!(!agent_sessions::session_changed_paths(session).is_empty());
804 reset_thread_local_state();
805 assert!(
806 agent_sessions::session_changed_paths(session).is_empty(),
807 "a receipt must not inherit an abandoned session's writes"
808 );
809 }
810
811 #[test]
812 fn reset_drains_agent_inbox() {
813 orchestration::agent_inbox::reset();
814 orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
815 assert!(orchestration::agent_inbox::session_count() > 0);
816 reset_thread_local_state();
817 assert_eq!(
818 orchestration::agent_inbox::session_count(),
819 0,
820 "agent_inbox must be empty after reset"
821 );
822 }
823
824 #[test]
825 fn reset_drains_tool_call_cancellation_registry() {
826 tool_call_cancellations::reset_registry();
827 let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
830 if let Some((_handle, guard)) = registered {
831 std::mem::forget(guard);
832 }
833 assert!(tool_call_cancellations::registry_len() > 0);
834 reset_thread_local_state();
835 assert_eq!(
836 tool_call_cancellations::registry_len(),
837 0,
838 "tool-call cancellation registry must be empty after reset"
839 );
840 }
841
842 #[test]
843 fn reset_drains_routing_policy_registry() {
844 llm::routing::clear_policy_registry();
845 let mut config: crate::value::DictMap = crate::value::DictMap::new();
846 config.insert(
847 crate::value::intern_key("chain"),
848 VmValue::List(std::sync::Arc::new(vec![VmValue::String(
849 arcstr::ArcStr::from("mock:mock"),
850 )])),
851 );
852 llm::routing::build_routing_policy(&config).expect("intern a routing policy");
853 assert!(llm::routing::policy_registry_len() > 0);
854 reset_thread_local_state();
855 assert_eq!(
856 llm::routing::policy_registry_len(),
857 0,
858 "routing policy registry must be empty after reset"
859 );
860 }
861
862 #[test]
863 fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
864 let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
865 let observed_hook = std::sync::Arc::clone(&observed);
866 let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
867 std::sync::Arc::new(move || {
868 assert!(
869 matches!(
870 llm::env_lock().try_lock(),
871 Err(std::sync::TryLockError::WouldBlock)
872 ),
873 "reset_thread_local_state must hold env_guard before wiping LLM globals"
874 );
875 observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
876 }),
877 );
878
879 reset_thread_local_state();
880 assert!(
881 observed.load(std::sync::atomic::Ordering::SeqCst),
882 "LLM global reset hook should have run"
883 );
884 }
885}