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