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