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