1#![recursion_limit = "256"]
2#![allow(clippy::result_large_err, clippy::cloned_ref_to_slice_refs)]
3pub use harn_clock as clock;
23
24mod runtime_stack;
25pub use runtime_stack::{on_vm_stack, RUNTIME_STACK_SIZE};
26
27pub mod a2a;
28pub mod actor_chain;
29pub mod agent_events;
30mod agent_lifecycle_cleanup;
31pub(crate) mod agent_session_journal;
32pub mod agent_session_restore;
33pub mod agent_sessions;
34pub mod agent_transcript_budget;
35pub mod atomic_io;
36pub mod autonomy;
37#[cfg(feature = "cloud-aws")]
38pub(crate) mod aws_sigv4;
39#[cfg(not(feature = "cloud-aws"))]
40#[path = "aws_sigv4_disabled.rs"]
41pub(crate) mod aws_sigv4;
42pub mod boundary;
43pub mod bridge;
44pub use bridge::{
45 inject_leading_authorities, inject_leading_authority, leading_authority_param_count,
46};
47mod bounded_files;
48pub mod builtin_profile;
49pub mod bytecode_cache;
50pub mod call_budget;
51pub mod canonical_json;
52pub mod channel_guardrails;
53pub mod channels;
54pub mod checkpoint;
55mod chunk;
56mod compiler;
57pub mod composition;
58pub mod conditional_replace;
59pub mod config;
60pub mod connectors;
61pub mod context_manifest;
62pub mod corrections;
63pub mod coverage;
64pub(crate) mod durable_rate_limit;
65pub mod duration_parse;
66pub mod egress;
67pub mod environment_registry;
68pub mod event_log;
69pub mod events;
70pub mod external_agent;
71pub mod flight_recorder;
72pub mod flow;
73pub mod harness;
74pub mod harness_auth;
75pub(crate) mod harness_crypto;
76pub mod harness_net;
77pub mod harness_system;
78pub mod harness_tenant;
79pub mod host_attachments;
80
81pub mod subtask {
87 pub use crate::vm::subtask::{
88 placement, scope_placement, SubtaskPlacement, SubtaskPlacementParseError, PLACEMENT_ENV,
89 PLACEMENT_VALUES,
90 };
91}
92mod http;
93pub mod jsonrpc;
94pub(crate) mod limits;
95pub mod linked_program;
96pub mod llm;
97pub mod llm_config;
98pub mod local_selection;
99pub mod mcp;
100pub mod mcp_allowlist;
101pub mod mcp_auth;
102pub mod mcp_bulk_auth;
103pub mod mcp_card;
104pub mod mcp_client_roots;
105pub mod mcp_elicit;
106pub mod mcp_host;
107pub mod mcp_identity;
108pub mod mcp_input;
109pub mod mcp_json_discovery;
110pub mod mcp_oauth;
111pub mod mcp_presets;
112pub mod mcp_progress;
113pub mod mcp_protocol;
114pub mod mcp_registry;
115pub mod mcp_sampling;
116pub mod mcp_server;
117pub mod mcp_tasks;
118pub mod metadata;
119pub mod module_artifact;
120pub mod module_source;
121pub mod observability;
122pub mod op_interrupt;
123pub mod orchestration;
124pub mod runtime_content;
125pub use runtime_content::{
126 runtime_content_fingerprint, RuntimeBuildFeatures, RuntimeCompatibilityFingerprint,
127 RuntimeContentFingerprint,
128};
129mod persistent_state;
130pub mod personas;
131pub mod portable;
132mod prepared_module;
133pub mod prepared_run;
134pub mod process_sandbox;
135pub mod profile;
136pub mod provenance;
137pub mod provider_catalog;
138pub mod receipts;
139pub mod record_filter;
140pub mod redact;
141pub mod run_events;
142pub mod runtime_context;
143pub(crate) mod runtime_guards;
144pub mod runtime_limits;
145pub mod runtime_paths;
146pub(crate) mod runtime_sqlite;
147pub mod schema;
148pub(crate) mod secret_patterns;
149pub mod secrets;
150pub mod security;
151pub mod session_bundle;
152pub mod session_recap;
153pub mod session_timeline;
154pub mod sessions;
155pub(crate) mod shared_state;
156pub mod shells;
157pub mod skills;
158pub mod stdlib;
159pub use stdlib::session_change::{
161 subscribe as subscribe_session_changes, SessionChangeSubscription,
162};
163pub use stdlib::session_store::{open_canonical_store, open_canonical_store_for_maintenance};
164pub mod stdlib_modules;
165pub mod step_runtime;
166pub mod store;
167pub(crate) mod synchronization;
168pub mod tenant;
169pub(crate) mod term;
170pub(crate) mod test_env;
171pub mod testbench;
172pub mod text;
173pub mod text_diff;
174pub mod tool_annotations;
175pub mod tool_call_cancellations;
176mod tool_handler_scope;
177pub mod tool_registry;
178pub mod tool_surface;
179pub mod tracing;
180pub mod triggers;
181pub mod trust_graph;
182pub(crate) mod url_encoding;
183pub mod user_dirs;
184
185pub fn initialize_runtime_assets() {
193 secret_patterns::initialize_default_secret_patterns();
194}
195
196#[derive(Debug)]
198pub enum RuntimeInitError {
199 Environment(environment_registry::EnvironmentValidationError),
201 CacheDir(bytecode_cache::CacheDirError),
203}
204
205impl std::fmt::Display for RuntimeInitError {
206 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Self::Environment(error) => error.fmt(formatter),
209 Self::CacheDir(error) => error.fmt(formatter),
210 }
211 }
212}
213
214impl std::error::Error for RuntimeInitError {}
215
216pub fn initialize_runtime() -> Result<Option<&'static str>, RuntimeInitError> {
225 environment_registry::validate_startup_environment().map_err(RuntimeInitError::Environment)?;
226 let warning = bytecode_cache::check_cache_config().map_err(RuntimeInitError::CacheDir)?;
227 initialize_runtime_assets();
228 Ok(warning)
229}
230
231pub mod clock_mock {
236 pub(crate) use crate::triggers::test_util::clock::scope_capability_clock;
237 pub use crate::triggers::test_util::clock::{
238 active_clock, active_mock_clock, advance, clear_overrides, install_override, instant_now,
239 is_mocked, now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
240 };
241
242 pub mod leak_audit {
246 pub use crate::triggers::test_util::clock_leak::{
247 drain, enter_scope, install_scope, instant_now, reset, snapshot, wall_now, ClockLeak,
248 ClockLeakScope, ClockLeakScopeGuard,
249 };
250 }
251}
252
253#[cfg(test)]
254pub(crate) mod test_panic_silence;
255pub(crate) mod text_index;
256pub mod typecheck;
257pub mod value;
258pub mod verification;
259pub mod visible_text;
260mod vm;
261pub(crate) mod wait_for_graph;
262pub mod waitpoints;
263pub mod windows_path;
264pub mod workspace_anchor;
265pub mod workspace_path;
266
267pub use persistent_state::{
268 register_persistent_state_builtins_at_root, scope_persistent_state_root, PersistentStateRoot,
269 ScopedPersistentStateRoot,
270};
271pub use prepared_module::{
272 PreparedModuleCache, PreparedModuleCacheStats, PreparedModuleGenerationStats,
273};
274
275pub use actor_chain::{
276 ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
277 ScopeAttenuationPolicy, ScopeAttenuationViolation,
278};
279pub use call_budget::{
280 charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
281 mcp_calls_spent, pg_queries_spent, McpCallBudgetGuard, PgQueryBudgetGuard,
282};
283pub use checkpoint::register_checkpoint_builtins;
284pub use chunk::*;
285pub use compiler::*;
286pub use connectors::{
287 active_connector_client, active_metrics_registry, clear_active_connector_clients,
288 clear_active_metrics_registry, connector_export_denied_builtin_reason,
289 connector_export_denied_harness_method_reason, connector_export_effect_class,
290 cron::{CatchupMode, CronConnector},
291 declared_secret_ids, default_connector_export_policy,
292 harn_module::{
293 load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
294 },
295 hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
296 install_active_connector_clients, install_active_metrics_registry,
297 postprocess_normalized_event, scope_active_connector_clients, ActivationHandle,
298 ActiveConnectorClientsGuard, ClientError, Connector, ConnectorClient, ConnectorClientResolver,
299 ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
300 ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
301 HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
302 RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
303 TriggerRegistry, VmConnectorClients, WebhookSignatureVariant,
304};
305pub use corrections::{
306 append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
307 correction_record_from_json, policy_with_corrections, query_correction_records,
308 CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
309 CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
310};
311pub use harn_kernel::BuiltinId;
312pub use harness::{
313 DenyEvent, Harness, HarnessAgent, HarnessCall, HarnessChannels, HarnessClock, HarnessEnv,
314 HarnessFs, HarnessKind, HarnessLlm, HarnessMemory, HarnessNet, HarnessObs, HarnessPostgres,
315 HarnessProcess, HarnessRandom, HarnessSecrets, HarnessSqlite, HarnessStdio, HarnessSystem,
316 HarnessTenant, HarnessTerm, HarnessTesting, MockHarnessBuilder, VmHarness,
317};
318pub use harness_auth::{
319 current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
320 MISSING_PRINCIPAL_MESSAGE,
321};
322pub use harness_net::{
323 bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
324 NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
325 NET_POLICY_AUDIT_TOPIC,
326};
327pub use harness_tenant::{
328 current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
329};
330pub use http::{register_http_builtins, reset_http_state};
331pub use llm::register_llm_builtins;
332pub use llm::trigger_predicate::TriggerPredicateBudget;
333pub use llm::{
334 current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
335 peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
336 set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard, SessionEndHookRegistration,
337};
338pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
339pub use mcp_allowlist::{
340 build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
341 AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
342 McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
343 MCP_ALLOWLIST_SCHEMA_VERSION,
344};
345pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
346pub use mcp_host::{
347 cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
348 AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
349 BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
350 SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
351};
352pub use mcp_registry::{
353 active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
354 get_registration as mcp_get_registration, install_active as mcp_install_active,
355 is_registered as mcp_is_registered, register_servers as mcp_register_servers,
356 release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
357 sweep_expired as mcp_sweep_expired, McpServerPreparation, RegisteredMcpServer, RegistryStatus,
358};
359pub use mcp_server::{
360 take_mcp_serve_metadata, take_mcp_serve_prompts, take_mcp_serve_registry,
361 take_mcp_serve_resource_templates, take_mcp_serve_resources, tool_registry_to_mcp_tools,
362 McpPromptDef, McpResourceDef, McpResourceTemplateDef, McpServer, McpServerMetadata,
363 McpServerReload, McpToolSet,
364};
365pub use metadata::register_metadata_builtins;
366pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
367pub use observability::execution_scope::{
368 current_execution_scope, enter_execution_scope, mint_execution_scope, ExecutionId,
369 ExecutionScopeGuard, InvalidExecutionId,
370};
371pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
372pub use orchestration::{
373 benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
374 OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
375 ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
376 ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
377 ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
378 ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
379 REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
380};
381pub use orchestration::{
382 canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
383 ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
384 ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
385};
386pub use orchestration::{
387 install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
388 HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
389};
390pub use personas::{
391 disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
392 format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
393 pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
394 register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
395 resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
396 PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
397 PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
398 PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
399 PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
400 PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
401 PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
402 PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
403 PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
404};
405pub use provenance::{
406 build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
407 ReceiptBuildOptions, ReceiptVerificationReport,
408};
409pub use receipts::{
410 Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
411 RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
412};
413pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
414pub use runtime_limits::{
415 RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
416 RUNTIME_LIMIT_DESCRIPTIONS,
417};
418pub use schema::json_to_vm_value;
419pub use sessions::{
420 CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
421 TouchSession, SESSIONS_TOPIC,
422};
423pub use stdlib::fs::ignore_policy;
429#[doc(hidden)]
430pub use stdlib::fs::invalidate_cached_file_text;
431pub use stdlib::hitl::{
432 append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
433 HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
434};
435pub use stdlib::host::turn_cache as host_turn_cache;
437pub use stdlib::host::{
438 clear_host_call_bridge, dispatch_host_operation, host_call_ready, install_host_call_bridge,
439 set_host_call_bridge, HostCallBridge, HostCallBridgeGuard, HostCallDispatchFuture,
440};
441pub use stdlib::http_response::{
442 parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
443 HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
444};
445#[cfg(feature = "postgres")]
446pub use stdlib::install_shared_pool_registry;
447pub use stdlib::io::{
448 reserve_stdio_for_current_thread, set_stdout_passthrough, take_stderr_buffer,
449 StdioReservationGuard,
450};
451pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
452pub use stdlib::observability::install_default_backend as install_obs_default_backend;
453pub use stdlib::secret_scan::{
454 append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
455 SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
456};
457pub use stdlib::template::{
458 lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
459 PromptSourceSpan, PromptSpanKind,
460};
461pub use stdlib::waitpoint::{
462 process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
463};
464pub use stdlib::workflow_messages::{
465 workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
466 workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
467 workflow_update_for_base, WorkflowMailboxState,
468};
469pub use stdlib::{
470 register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
471};
472pub use store::register_store_builtins;
473pub use tenant::{
474 tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
475 TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
476 TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
477 TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
478 TENANT_SECRET_NAMESPACE_PREFIX,
479};
480pub use triggers::{
481 append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
482 binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
483 clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
484 dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
485 install_manifest_triggers, install_orchestrator_budget, micros_to_usd,
486 note_autonomous_decision, note_orchestrator_budget_cost, orchestrator_budget_would_exceed,
487 parse_flow_control_duration, pause, pin_trigger_binding, provider_metadata,
488 record_predicate_cost_sample, redact_headers, register_provider_schemas,
489 registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
490 reset_provider_catalog, resolve_live_or_as_of, resolve_live_trigger_binding,
491 resolve_trigger_binding_as_of, resume, run_trigger_harness_fixture, scheduler_in_flight_by_key,
492 scheduler_ready_stats_by_key, snapshot_dispatcher_stats, snapshot_orchestrator_budget,
493 snapshot_trigger_bindings, unpin_trigger_binding, usd_to_micros, worker_claims_topic_name,
494 worker_job_topic_name, worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest,
495 DispatchError, DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport,
496 DispatcherStatsSnapshot, ExtensionProviderPayload, FairnessKey, HeaderRedactionPolicy,
497 InboxIndex, OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog,
498 ProviderCatalogError, ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload,
499 ProviderRuntimeMetadata, ProviderSchema, ProviderSecretRequirement, ReadyKeyStats,
500 RecordedTriggerBinding, RetryPolicy, SchedulableJob, SchedulerKeyStat, SchedulerPolicy,
501 SchedulerSnapshot, SchedulerState, SchedulerStrategy, SignatureStatus,
502 SignatureVerificationMetadata, StreamEventPayload, TenantId, TraceId, TriggerBatchConfig,
503 TriggerBindingSnapshot, TriggerBindingSource, TriggerBindingSpec,
504 TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig, TriggerDebounceConfig,
505 TriggerDispatchOutcome, TriggerEvent, TriggerEventId, TriggerExpressionSpec,
506 TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult, TriggerId,
507 TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
508 TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
509 TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
510 WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
511 WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
512 DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
513 TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
514 TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
515 TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
516 TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
517};
518pub use trust_graph::{
519 append_active_scope_attenuation_alert, append_active_trust_record,
520 append_scope_attenuation_alert, append_trust_record, export_trust_chain,
521 group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
522 query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
523 summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
524 TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
525 TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
526 TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
527 METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
528 METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
529 OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
530 TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
531 TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
532};
533pub use value::*;
534pub use vm::*;
535
536#[cfg(feature = "vm-bench-internals")]
537#[doc(hidden)]
538pub mod bench_internals;
539
540pub fn compile_source(source: &str) -> Result<Chunk, String> {
545 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
546 Compiler::new().compile(&program).map_err(|e| e.to_string())
547}
548
549pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
554 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
555 let has_pipeline = program.iter().any(|sn| {
556 let (_, inner) = harn_parser::peel_attributes(sn);
557 matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
558 });
559 if !has_pipeline {
560 return Err(format!("no pipeline named `{pipeline_name}` in source"));
561 }
562 Compiler::new()
563 .compile_named(&program, pipeline_name)
564 .map_err(|e| e.to_string())
565}
566
567pub struct TypeSchemaResolver {
573 compiler: compiler::Compiler,
574 nominal_types: std::collections::BTreeMap<String, SchemaNominalType>,
575}
576
577#[derive(Clone)]
578enum SchemaNominalType {
579 Struct {
580 type_params: Vec<harn_parser::TypeParam>,
581 fields: Vec<harn_parser::StructField>,
582 },
583 Enum {
584 type_params: Vec<harn_parser::TypeParam>,
585 variants: Vec<harn_parser::EnumVariant>,
586 },
587}
588
589impl TypeSchemaResolver {
590 pub fn empty() -> Self {
592 Self {
593 compiler: compiler::Compiler::new(),
594 nominal_types: std::collections::BTreeMap::new(),
595 }
596 }
597
598 pub fn from_program(program: &[harn_parser::SNode]) -> Self {
600 let mut compiler = compiler::Compiler::new();
601 compiler.collect_type_aliases(program);
602 let mut nominal_types = std::collections::BTreeMap::new();
603 for node in program {
604 let (_, declaration) = harn_parser::peel_attributes(node);
605 match &declaration.node {
606 harn_parser::Node::StructDecl {
607 name,
608 type_params,
609 fields,
610 ..
611 } => {
612 nominal_types.insert(
613 name.clone(),
614 SchemaNominalType::Struct {
615 type_params: type_params.clone(),
616 fields: fields.clone(),
617 },
618 );
619 }
620 harn_parser::Node::EnumDecl {
621 name,
622 type_params,
623 variants,
624 ..
625 } => {
626 nominal_types.insert(
627 name.clone(),
628 SchemaNominalType::Enum {
629 type_params: type_params.clone(),
630 variants: variants.clone(),
631 },
632 );
633 }
634 _ => {}
635 }
636 }
637 Self {
638 compiler,
639 nominal_types,
640 }
641 }
642
643 pub fn json_schema_for_type_expr(
646 &self,
647 type_expr: &harn_parser::TypeExpr,
648 ) -> Option<serde_json::Value> {
649 self.json_schema_for_type_expr_inner(type_expr, &mut Vec::new())
650 }
651
652 pub fn json_schema_for_input_type_expr(
657 &self,
658 type_expr: &harn_parser::TypeExpr,
659 ) -> Option<serde_json::Value> {
660 let expanded = self.compiler.expand_alias(type_expr);
661 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
662 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
663 Some(llm::vm_value_to_json(&json_schema))
664 }
665
666 fn json_schema_for_type_expr_inner(
667 &self,
668 type_expr: &harn_parser::TypeExpr,
669 resolving: &mut Vec<harn_parser::TypeExpr>,
670 ) -> Option<serde_json::Value> {
671 const MAX_SCHEMA_TYPE_NEST: usize = 128;
672 let expanded = self.compiler.expand_alias(type_expr);
673 if resolving.len() >= MAX_SCHEMA_TYPE_NEST || resolving.contains(&expanded) {
674 return Some(serde_json::json!({}));
675 }
676
677 if let Some((name, args)) = nominal_reference(&expanded) {
678 if let Some(declaration) = self.nominal_types.get(name).cloned() {
679 resolving.push(expanded.clone());
680 let schema = self.json_schema_for_nominal(name, &declaration, args, resolving);
681 resolving.pop();
682 return schema;
683 }
684 }
685
686 if !contains_nominal_reference(&expanded, &self.nominal_types) {
687 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
688 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
689 return Some(llm::vm_value_to_json(&json_schema));
690 }
691
692 use harn_parser::TypeExpr;
693 match expanded {
694 TypeExpr::Shape(fields) => {
695 let mut properties = serde_json::Map::new();
696 let mut required = Vec::new();
697 for field in fields {
698 let mut field_schema =
699 self.json_schema_for_type_expr_inner(&field.type_expr, resolving)?;
700 if field.optional {
701 field_schema = serde_json::json!({
702 "anyOf": [field_schema, {"type": "null"}],
703 });
704 } else {
705 required.push(serde_json::Value::String(field.name.clone()));
706 }
707 properties.insert(field.name, field_schema);
708 }
709 Some(serde_json::json!({
710 "type": "object",
711 "properties": properties,
712 "required": required,
713 }))
714 }
715 TypeExpr::List(inner) => Some(serde_json::json!({
716 "type": "array",
717 "items": self.json_schema_for_type_expr_inner(&inner, resolving)?,
718 })),
719 TypeExpr::Tuple(elements) => {
720 let prefix_items = elements
721 .iter()
722 .map(|element| self.json_schema_for_type_expr_inner(element, resolving))
723 .collect::<Option<Vec<_>>>()?;
724 Some(serde_json::json!({
725 "type": "array",
726 "prefixItems": prefix_items,
727 "items": false,
728 "minItems": elements.len(),
729 "maxItems": elements.len(),
730 }))
731 }
732 TypeExpr::DictType(key, value) if matches!(key.as_ref(), TypeExpr::Named(name) if name == "string") => {
733 Some(serde_json::json!({
734 "type": "object",
735 "additionalProperties": self
736 .json_schema_for_type_expr_inner(&value, resolving)?,
737 }))
738 }
739 TypeExpr::Union(members) => Some(serde_json::json!({
740 "anyOf": members
741 .iter()
742 .map(|member| self.json_schema_for_type_expr_inner(member, resolving))
743 .collect::<Option<Vec<_>>>()?,
744 })),
745 TypeExpr::Intersection(members) => Some(serde_json::json!({
746 "allOf": members
747 .iter()
748 .map(|member| self.json_schema_for_type_expr_inner(member, resolving))
749 .collect::<Option<Vec<_>>>()?,
750 })),
751 TypeExpr::Owned(inner) => self.json_schema_for_type_expr_inner(&inner, resolving),
752 _ => None,
753 }
754 }
755
756 fn json_schema_for_nominal(
757 &self,
758 name: &str,
759 declaration: &SchemaNominalType,
760 args: &[harn_parser::TypeExpr],
761 resolving: &mut Vec<harn_parser::TypeExpr>,
762 ) -> Option<serde_json::Value> {
763 let type_params = match declaration {
764 SchemaNominalType::Struct { type_params, .. }
765 | SchemaNominalType::Enum { type_params, .. } => type_params,
766 };
767 if type_params.len() != args.len() {
768 return None;
769 }
770 let bindings = type_params
771 .iter()
772 .zip(args.iter().cloned())
773 .map(|(param, arg)| (param.name.clone(), arg))
774 .collect::<std::collections::BTreeMap<_, _>>();
775
776 match declaration {
777 SchemaNominalType::Struct { fields, .. } => {
778 let fields = fields
779 .iter()
780 .map(|field| harn_parser::ShapeField {
781 name: field.name.clone(),
782 type_expr: field
783 .type_expr
784 .as_ref()
785 .map(|ty| harn_parser::substitute_type_expr(ty, &bindings))
786 .unwrap_or_else(|| harn_parser::TypeExpr::Named("unknown".into())),
787 optional: field.optional,
788 span: field.span,
789 })
790 .collect();
791 self.json_schema_for_type_expr_inner(
792 &harn_parser::TypeExpr::Shape(fields),
793 resolving,
794 )
795 }
796 SchemaNominalType::Enum { variants, .. } => {
797 let branches = variants
798 .iter()
799 .map(|variant| {
800 let prefix_items = variant
801 .fields
802 .iter()
803 .map(|field| {
804 let type_expr = field.type_expr.as_ref()?;
805 let instantiated =
806 harn_parser::substitute_type_expr(type_expr, &bindings);
807 let mut schema =
808 self.json_schema_for_type_expr_inner(&instantiated, resolving)?;
809 if let serde_json::Value::Object(object) = &mut schema {
810 object.insert(
811 "title".to_string(),
812 serde_json::Value::String(field.name.clone()),
813 );
814 }
815 Some(schema)
816 })
817 .collect::<Option<Vec<_>>>()?;
818 Some(serde_json::json!({
819 "type": "object",
820 "properties": {
821 "enum": {"const": name},
822 "variant": {"const": variant.name},
823 "fields": {
824 "type": "array",
825 "prefixItems": prefix_items,
826 "items": false,
827 "minItems": variant.fields.len(),
828 "maxItems": variant.fields.len(),
829 },
830 },
831 "required": ["enum", "variant", "fields"],
832 "additionalProperties": false,
833 }))
834 })
835 .collect::<Option<Vec<_>>>()?;
836 Some(serde_json::json!({"oneOf": branches}))
837 }
838 }
839 }
840
841 pub fn json_schema_for_typed_params(
844 &self,
845 params: &[harn_parser::TypedParam],
846 ) -> serde_json::Value {
847 let mut properties = serde_json::Map::new();
848 let mut required = Vec::new();
849
850 for param in params {
851 let item_schema = param
852 .type_expr
853 .as_ref()
854 .and_then(|type_expr| self.json_schema_for_input_type_expr(type_expr))
855 .unwrap_or_else(|| serde_json::json!({}));
856 let param_schema = if param.rest {
857 serde_json::json!({"type": "array", "items": item_schema})
858 } else {
859 item_schema
860 };
861 if param.default_value.is_none() && !param.rest {
862 required.push(serde_json::Value::String(param.name.clone()));
863 }
864 properties.insert(param.name.clone(), param_schema);
865 }
866
867 let mut schema = serde_json::Map::new();
868 schema.insert(
869 "type".to_string(),
870 serde_json::Value::String("object".to_string()),
871 );
872 schema.insert(
873 "properties".to_string(),
874 serde_json::Value::Object(properties),
875 );
876 if !required.is_empty() {
877 schema.insert("required".to_string(), serde_json::Value::Array(required));
878 }
879 schema.insert(
885 "additionalProperties".to_string(),
886 serde_json::Value::Bool(false),
887 );
888 serde_json::Value::Object(schema)
889 }
890}
891
892fn nominal_reference(
893 type_expr: &harn_parser::TypeExpr,
894) -> Option<(&str, &[harn_parser::TypeExpr])> {
895 match type_expr {
896 harn_parser::TypeExpr::Named(name) => Some((name, &[])),
897 harn_parser::TypeExpr::Applied { name, args } => Some((name, args)),
898 _ => None,
899 }
900}
901
902fn contains_nominal_reference(
903 type_expr: &harn_parser::TypeExpr,
904 nominal_types: &std::collections::BTreeMap<String, SchemaNominalType>,
905) -> bool {
906 use harn_parser::TypeExpr;
907 match type_expr {
908 TypeExpr::Named(name) => nominal_types.contains_key(name),
909 TypeExpr::Applied { name, args } => {
910 nominal_types.contains_key(name)
911 || args
912 .iter()
913 .any(|arg| contains_nominal_reference(arg, nominal_types))
914 }
915 TypeExpr::Union(types) | TypeExpr::Intersection(types) | TypeExpr::Tuple(types) => types
916 .iter()
917 .any(|ty| contains_nominal_reference(ty, nominal_types)),
918 TypeExpr::Shape(fields) => fields
919 .iter()
920 .any(|field| contains_nominal_reference(&field.type_expr, nominal_types)),
921 TypeExpr::OpenShape { fields, rests } => {
922 fields
923 .iter()
924 .any(|field| contains_nominal_reference(&field.type_expr, nominal_types))
925 || rests
926 .iter()
927 .any(|rest| contains_nominal_reference(rest, nominal_types))
928 }
929 TypeExpr::List(inner)
930 | TypeExpr::Iter(inner)
931 | TypeExpr::Generator(inner)
932 | TypeExpr::Stream(inner)
933 | TypeExpr::Owned(inner) => contains_nominal_reference(inner, nominal_types),
934 TypeExpr::DictType(key, value) => {
935 contains_nominal_reference(key, nominal_types)
936 || contains_nominal_reference(value, nominal_types)
937 }
938 TypeExpr::FnType {
939 params,
940 return_type,
941 } => {
942 params
943 .iter()
944 .any(|param| contains_nominal_reference(param, nominal_types))
945 || contains_nominal_reference(return_type, nominal_types)
946 }
947 TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => false,
948 }
949}
950
951pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
955 TypeSchemaResolver::empty().json_schema_for_type_expr(type_expr)
956}
957
958pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
959 TypeSchemaResolver::empty().json_schema_for_typed_params(params)
960}
961
962#[cfg(test)]
963mod schema_alias_resolver_tests {
964 use super::*;
965
966 fn fn_params_schema(src: &str) -> serde_json::Value {
967 let program = harn_parser::parse_source(src).expect("parse test source");
968 let resolver = TypeSchemaResolver::from_program(&program);
969 for node in &program {
970 let (_, inner) = harn_parser::peel_attributes(node);
971 if let harn_parser::Node::FnDecl { params, .. } = &inner.node {
972 return resolver.json_schema_for_typed_params(params);
973 }
974 }
975 panic!("no fn decl in test source");
976 }
977
978 #[test]
979 fn named_shape_alias_projects_like_inline_shape() {
980 let inline = fn_params_schema("pub fn f(p: {kind: string, path: string}) {}");
981 let aliased =
982 fn_params_schema("type Src = {kind: string, path: string}\npub fn f(p: Src) {}");
983 assert_eq!(
984 aliased, inline,
985 "a named shape alias must project the same inputSchema as its inline shape",
986 );
987 assert_ne!(
988 aliased["properties"]["p"],
989 serde_json::json!({}),
990 "the alias parameter must not erase to an empty schema",
991 );
992 }
993
994 #[test]
995 fn rest_parameters_project_as_optional_arrays_of_the_declared_item_type() {
996 let schema = fn_params_schema("pub fn collect(prefix: string, ...values: int) {}");
997 assert_eq!(schema["properties"]["values"]["type"], "array");
998 assert_eq!(schema["properties"]["values"]["items"]["type"], "integer");
999 assert_eq!(schema["required"], serde_json::json!(["prefix"]));
1000 }
1001
1002 #[test]
1003 fn literal_union_alias_projects_json_enum() {
1004 let schema = fn_params_schema("type Kind = \"local\" | \"ssh\"\npub fn f(p: Kind) {}");
1005 let p = &schema["properties"]["p"];
1006 assert_eq!(p["type"], "string");
1007 assert_eq!(p["enum"], serde_json::json!(["local", "ssh"]));
1008 }
1009
1010 #[test]
1011 fn unknown_named_type_still_erases_to_empty() {
1012 let schema = fn_params_schema("pub fn f(p: Unknown) {}");
1014 assert_eq!(schema["properties"]["p"], serde_json::json!({}));
1015 }
1016}
1017
1018fn reset_llm_state_for_thread_reset() {
1019 llm::reset_llm_state();
1020 #[cfg(test)]
1021 reset_thread_local_state_test_hooks::before_llm_global_reset();
1022 llm::reset_rate_limit_registry();
1025 llm_config::clear_user_overrides();
1026 llm_config::clear_runtime_provider_endpoint_overrides();
1027}
1028
1029#[cfg(test)]
1030mod reset_thread_local_state_test_hooks {
1031 use std::sync::{Arc, Mutex, OnceLock};
1032
1033 type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
1034
1035 static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
1036
1037 fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
1038 BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
1039 }
1040
1041 pub(crate) struct HookGuard;
1042
1043 impl Drop for HookGuard {
1044 fn drop(&mut self) {
1045 let mut hook = before_llm_global_reset_hook()
1046 .lock()
1047 .unwrap_or_else(std::sync::PoisonError::into_inner);
1048 *hook = None;
1049 }
1050 }
1051
1052 pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
1053 let mut slot = before_llm_global_reset_hook()
1054 .lock()
1055 .unwrap_or_else(std::sync::PoisonError::into_inner);
1056 *slot = Some(hook);
1057 HookGuard
1058 }
1059
1060 pub(crate) fn before_llm_global_reset() {
1061 let hook = before_llm_global_reset_hook()
1062 .lock()
1063 .unwrap_or_else(std::sync::PoisonError::into_inner)
1064 .clone();
1065 if let Some(hook) = hook {
1066 hook();
1067 }
1068 }
1069}
1070
1071pub fn reset_thread_local_state() {
1073 #[cfg(test)]
1074 {
1075 let _guard = llm::env_guard();
1080 reset_llm_state_for_thread_reset();
1081 }
1082 #[cfg(not(test))]
1083 reset_llm_state_for_thread_reset();
1084
1085 http::reset_http_state();
1086 channels::reset_channel_state();
1087 event_log::reset_active_event_log();
1088 egress::clear_explicit_egress_policy_requirement_for_host();
1089 egress::clear_ssrf_guard_requirement_for_host();
1090 stdlib::reset_stdlib_state();
1091 connectors::clear_active_connector_clients();
1092 orchestration::clear_runtime_hooks();
1093 orchestration::clear_file_edit_queue();
1094 orchestration::clear_execution_policy_stacks();
1095 orchestration::clear_command_policies();
1096 orchestration::clear_pipeline_on_finish();
1097 orchestration::reset_lifecycle_receipt_registry();
1098 orchestration::agent_inbox::reset();
1099 tool_call_cancellations::reset_registry();
1100 redact::clear_policy_stack();
1101 security::reset_thread_state();
1102 triggers::clear_dispatcher_state();
1103 triggers::clear_trigger_registry();
1104 events::reset_event_sinks();
1105 tracing::set_tracing_enabled(false);
1106 tracing::reset_tracing();
1107 agent_events::reset_all_sinks();
1119 agent_sessions::reset_session_store();
1120 mcp_registry::reset();
1121 mcp_host::reset_for_tests();
1122 call_budget::reset_call_budget_state();
1123 clock_mock::leak_audit::reset();
1124}
1125
1126#[cfg(test)]
1127mod reset_leak_tests {
1128 use super::*;
1134 use crate::value::VmValue;
1135
1136 #[test]
1137 fn reset_drains_pending_file_edit_notifications() {
1138 orchestration::queue_file_edited("stale.harn", serde_json::json!({"operation": "write"}));
1139
1140 reset_thread_local_state();
1141
1142 assert!(
1143 orchestration::drain_file_edits().is_empty(),
1144 "a later VM run must not receive file edits queued by the previous run"
1145 );
1146 }
1147
1148 #[test]
1157 fn builtin_profile_recording_ends_with_its_run_not_with_a_global_reset() {
1158 let _lock = builtin_profile::test_lock()
1159 .lock()
1160 .unwrap_or_else(std::sync::PoisonError::into_inner);
1161 let recording = builtin_profile::enable();
1162 builtin_profile::record("run_shell", std::time::Duration::from_millis(5));
1163 assert!(builtin_profile::is_enabled());
1164 assert!(!builtin_profile::snapshot().is_empty());
1165
1166 reset_thread_local_state();
1167
1168 assert!(
1169 builtin_profile::is_enabled(),
1170 "an unrelated global reset must not disarm an in-flight profiled run"
1171 );
1172 assert!(
1173 !builtin_profile::snapshot().is_empty(),
1174 "an unrelated global reset must not drop totals the run still owns"
1175 );
1176
1177 drop(recording);
1178
1179 assert!(
1180 !builtin_profile::is_enabled(),
1181 "a profiled run must not leave the recorder on for the next one"
1182 );
1183 assert!(
1184 builtin_profile::snapshot().is_empty(),
1185 "builtin totals must be empty once the run's guard drops"
1186 );
1187 }
1188
1189 #[test]
1194 fn reset_drains_session_changed_paths() {
1195 let session = "sess-leak";
1196 agent_sessions::open_or_create(Some(session.to_string())).expect("open fixture session");
1197 agent_sessions::record_session_changed_path(session, "/tmp/written-by-a-dead-run.txt");
1198 assert!(!agent_sessions::session_changed_paths(session).is_empty());
1199 reset_thread_local_state();
1200 assert!(
1201 agent_sessions::session_changed_paths(session).is_empty(),
1202 "a receipt must not inherit an abandoned session's writes"
1203 );
1204 }
1205
1206 #[test]
1207 fn reset_drains_agent_inbox() {
1208 orchestration::agent_inbox::reset();
1209 orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
1210 assert!(orchestration::agent_inbox::session_count() > 0);
1211 reset_thread_local_state();
1212 assert_eq!(
1213 orchestration::agent_inbox::session_count(),
1214 0,
1215 "agent_inbox must be empty after reset"
1216 );
1217 }
1218
1219 #[test]
1220 fn reset_drains_tool_call_cancellation_registry() {
1221 tool_call_cancellations::reset_registry();
1222 let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
1225 if let Some((_handle, guard)) = registered {
1226 std::mem::forget(guard);
1227 }
1228 assert!(tool_call_cancellations::registry_len() > 0);
1229 reset_thread_local_state();
1230 assert_eq!(
1231 tool_call_cancellations::registry_len(),
1232 0,
1233 "tool-call cancellation registry must be empty after reset"
1234 );
1235 }
1236
1237 #[test]
1238 fn reset_drains_routing_policy_registry() {
1239 llm::routing::clear_policy_registry();
1240 let mut config: crate::value::DictMap = crate::value::DictMap::new();
1241 config.insert(
1242 crate::value::intern_key("chain"),
1243 VmValue::List(std::sync::Arc::new(vec![VmValue::String(
1244 arcstr::ArcStr::from("mock:mock"),
1245 )])),
1246 );
1247 llm::routing::build_routing_policy(&config).expect("intern a routing policy");
1248 assert!(llm::routing::policy_registry_len() > 0);
1249 reset_thread_local_state();
1250 assert_eq!(
1251 llm::routing::policy_registry_len(),
1252 0,
1253 "routing policy registry must be empty after reset"
1254 );
1255 }
1256
1257 #[test]
1258 fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
1259 let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1260 let observed_hook = std::sync::Arc::clone(&observed);
1261 let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
1262 std::sync::Arc::new(move || {
1263 assert!(
1264 matches!(
1265 llm::env_lock().try_lock(),
1266 Err(std::sync::TryLockError::WouldBlock)
1267 ),
1268 "reset_thread_local_state must hold env_guard before wiping LLM globals"
1269 );
1270 observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
1271 }),
1272 );
1273
1274 reset_thread_local_state();
1275 assert!(
1276 observed.load(std::sync::atomic::Ordering::SeqCst),
1277 "LLM global reset hook should have run"
1278 );
1279 }
1280}