Skip to main content

harn_vm/
harness.rs

1//! Capability handle threaded into every Harn script as the `harness`
2//! parameter of `main`.
3//!
4//! `Harness` is the Harn-language analog of an explicit-capability handle: a
5//! single value the runtime hands to a script's `main` so that stdio,
6//! terminal, clock, filesystem, environment, randomness, network, process,
7//! channels, system, secrets, and LLM catalog access become surface in the type system
8//! instead of ambient globals. Each sub-handle (`stdio`, `term`, `clock`, `fs`,
9//! `env`, `random`, `net`, `process`, `channels`, `system`, `secrets`, `llm`,
10//! `tenant`, `auth`, `obs`) is a distinct named type that anchors the surface
11//! for its capability slice.
12//!
13//! This module defines:
14//!   * The runtime [`Harness`] value and its sub-handle wrappers.
15//!   * [`Harness::real`], the production constructor that installs the backing
16//!     state used by concrete sub-handle methods.
17//!   * [`VmHarness`], the compact `VmValue` payload that carries the same
18//!     state through the bytecode VM and distinguishes the root handle from
19//!     its sub-handles via [`HarnessKind`].
20
21use std::collections::{BTreeMap, VecDeque};
22use std::fmt;
23use std::sync::{Arc, Mutex};
24use std::time::Duration;
25
26use async_trait::async_trait;
27use harn_clock::{Clock, PausedClock, RealClock};
28use time::OffsetDateTime;
29
30/// Runtime discriminator for the root grant or one typed capability handle.
31///
32/// [`harn_builtin_meta::CapabilityId`] owns the closed capability vocabulary;
33/// this wrapper adds the root state without duplicating its field/type maps.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct HarnessKind(Option<harn_builtin_meta::CapabilityId>);
36
37#[allow(non_upper_case_globals)]
38impl HarnessKind {
39    pub const Root: Self = Self(None);
40    pub const Stdio: Self = Self(Some(harn_builtin_meta::CapabilityId::Stdio));
41    pub const Term: Self = Self(Some(harn_builtin_meta::CapabilityId::Term));
42    pub const Clock: Self = Self(Some(harn_builtin_meta::CapabilityId::Clock));
43    pub const Fs: Self = Self(Some(harn_builtin_meta::CapabilityId::Fs));
44    pub const Env: Self = Self(Some(harn_builtin_meta::CapabilityId::Env));
45    pub const Random: Self = Self(Some(harn_builtin_meta::CapabilityId::Random));
46    pub const Net: Self = Self(Some(harn_builtin_meta::CapabilityId::Net));
47    pub const Process: Self = Self(Some(harn_builtin_meta::CapabilityId::Process));
48    pub const Channels: Self = Self(Some(harn_builtin_meta::CapabilityId::Channels));
49    pub const System: Self = Self(Some(harn_builtin_meta::CapabilityId::System));
50    pub const Secrets: Self = Self(Some(harn_builtin_meta::CapabilityId::Secrets));
51    pub const Llm: Self = Self(Some(harn_builtin_meta::CapabilityId::Llm));
52    pub const Agent: Self = Self(Some(harn_builtin_meta::CapabilityId::Agent));
53    pub const Tenant: Self = Self(Some(harn_builtin_meta::CapabilityId::Tenant));
54    pub const Auth: Self = Self(Some(harn_builtin_meta::CapabilityId::Auth));
55    pub const Obs: Self = Self(Some(harn_builtin_meta::CapabilityId::Observability));
56    pub const Verdict: Self = Self(Some(harn_builtin_meta::CapabilityId::Verdict));
57    pub const Tools: Self = Self(Some(harn_builtin_meta::CapabilityId::Tools));
58    pub const Ast: Self = Self(Some(harn_builtin_meta::CapabilityId::Ast));
59    pub const CodeIndex: Self = Self(Some(harn_builtin_meta::CapabilityId::CodeIndex));
60    pub const Computer: Self = Self(Some(harn_builtin_meta::CapabilityId::Computer));
61    pub const Embed: Self = Self(Some(harn_builtin_meta::CapabilityId::Embed));
62    pub const Memory: Self = Self(Some(harn_builtin_meta::CapabilityId::Memory));
63    pub const Sqlite: Self = Self(Some(harn_builtin_meta::CapabilityId::Sqlite));
64    pub const Postgres: Self = Self(Some(harn_builtin_meta::CapabilityId::Postgres));
65    pub const FsWatch: Self = Self(Some(harn_builtin_meta::CapabilityId::FsWatch));
66    pub const HostLease: Self = Self(Some(harn_builtin_meta::CapabilityId::HostLease));
67    pub const Scanner: Self = Self(Some(harn_builtin_meta::CapabilityId::Scanner));
68    pub const SecretStore: Self = Self(Some(harn_builtin_meta::CapabilityId::SecretStore));
69    pub const TerminalSession: Self = Self(Some(harn_builtin_meta::CapabilityId::TerminalSession));
70    pub const Rules: Self = Self(Some(harn_builtin_meta::CapabilityId::Rules));
71    pub const Lint: Self = Self(Some(harn_builtin_meta::CapabilityId::Lint));
72    pub const Runtime: Self = Self(Some(harn_builtin_meta::CapabilityId::Runtime));
73    pub const Interaction: Self = Self(Some(harn_builtin_meta::CapabilityId::Interaction));
74    pub const Project: Self = Self(Some(harn_builtin_meta::CapabilityId::Project));
75    pub const Dashboard: Self = Self(Some(harn_builtin_meta::CapabilityId::Dashboard));
76    pub const Workspace: Self = Self(Some(harn_builtin_meta::CapabilityId::Workspace));
77    pub const MergeCaptain: Self = Self(Some(harn_builtin_meta::CapabilityId::MergeCaptain));
78    pub const Session: Self = Self(Some(harn_builtin_meta::CapabilityId::Session));
79    pub const Permission: Self = Self(Some(harn_builtin_meta::CapabilityId::Permission));
80    pub const Text: Self = Self(Some(harn_builtin_meta::CapabilityId::Text));
81    pub const Lsp: Self = Self(Some(harn_builtin_meta::CapabilityId::Lsp));
82    pub const Credentials: Self = Self(Some(harn_builtin_meta::CapabilityId::Credentials));
83    pub const PrMonitor: Self = Self(Some(harn_builtin_meta::CapabilityId::PrMonitor));
84    pub const Workflow: Self = Self(Some(harn_builtin_meta::CapabilityId::Workflow));
85    pub const Testing: Self = Self(Some(harn_builtin_meta::CapabilityId::Testing));
86
87    pub const fn capability_id(self) -> Option<harn_builtin_meta::CapabilityId> {
88        self.0
89    }
90
91    /// The Harn-language type name for this kind (`Harness`, `HarnessStdio`,
92    /// etc.). Used by the typechecker primitive registration and by
93    /// `VmValue::type_name`.
94    pub const fn type_name(self) -> &'static str {
95        match self.0 {
96            None => "Harness",
97            Some(capability) => capability.type_name(),
98        }
99    }
100
101    /// Field name a parent `Harness` exposes for this sub-handle (e.g. the
102    /// `stdio` in `harness.stdio`). Returns `None` for the root.
103    pub const fn field_name(self) -> Option<&'static str> {
104        match self.0 {
105            None => None,
106            Some(capability) => Some(capability.field_name()),
107        }
108    }
109
110    /// Parse the field name a script uses to reach a sub-handle.
111    pub fn from_field_name(name: &str) -> Option<Self> {
112        harn_builtin_meta::CapabilityId::from_field_name(name)
113            .map(|capability| Self(Some(capability)))
114    }
115
116    /// All sub-handle kinds, in the canonical field order.
117    pub const SUB_HANDLES: &'static [HarnessKind] = &[
118        HarnessKind::Stdio,
119        HarnessKind::Term,
120        HarnessKind::Clock,
121        HarnessKind::Fs,
122        HarnessKind::Env,
123        HarnessKind::Random,
124        HarnessKind::Net,
125        HarnessKind::Process,
126        HarnessKind::Channels,
127        HarnessKind::System,
128        HarnessKind::Secrets,
129        HarnessKind::Llm,
130        HarnessKind::Agent,
131        HarnessKind::Tenant,
132        HarnessKind::Auth,
133        HarnessKind::Obs,
134        HarnessKind::Verdict,
135        HarnessKind::Tools,
136        HarnessKind::Ast,
137        HarnessKind::CodeIndex,
138        HarnessKind::Computer,
139        HarnessKind::Embed,
140        HarnessKind::Memory,
141        HarnessKind::Sqlite,
142        HarnessKind::Postgres,
143        HarnessKind::FsWatch,
144        HarnessKind::HostLease,
145        HarnessKind::Scanner,
146        HarnessKind::SecretStore,
147        HarnessKind::TerminalSession,
148        HarnessKind::Rules,
149        HarnessKind::Lint,
150        HarnessKind::Runtime,
151        HarnessKind::Interaction,
152        HarnessKind::Project,
153        HarnessKind::Dashboard,
154        HarnessKind::Workspace,
155        HarnessKind::MergeCaptain,
156        HarnessKind::Session,
157        HarnessKind::Permission,
158        HarnessKind::Text,
159        HarnessKind::Lsp,
160        HarnessKind::Credentials,
161        HarnessKind::PrMonitor,
162        HarnessKind::Workflow,
163        HarnessKind::Testing,
164    ];
165
166    /// Every kind a Harn-script type annotation may reference.
167    pub const ALL: &'static [HarnessKind] = &[
168        HarnessKind::Root,
169        HarnessKind::Stdio,
170        HarnessKind::Term,
171        HarnessKind::Clock,
172        HarnessKind::Fs,
173        HarnessKind::Env,
174        HarnessKind::Random,
175        HarnessKind::Net,
176        HarnessKind::Process,
177        HarnessKind::Channels,
178        HarnessKind::System,
179        HarnessKind::Secrets,
180        HarnessKind::Llm,
181        HarnessKind::Agent,
182        HarnessKind::Tenant,
183        HarnessKind::Auth,
184        HarnessKind::Obs,
185        HarnessKind::Verdict,
186        HarnessKind::Tools,
187        HarnessKind::Ast,
188        HarnessKind::CodeIndex,
189        HarnessKind::Computer,
190        HarnessKind::Embed,
191        HarnessKind::Memory,
192        HarnessKind::Sqlite,
193        HarnessKind::Postgres,
194        HarnessKind::FsWatch,
195        HarnessKind::HostLease,
196        HarnessKind::Scanner,
197        HarnessKind::SecretStore,
198        HarnessKind::TerminalSession,
199        HarnessKind::Rules,
200        HarnessKind::Lint,
201        HarnessKind::Runtime,
202        HarnessKind::Interaction,
203        HarnessKind::Project,
204        HarnessKind::Dashboard,
205        HarnessKind::Workspace,
206        HarnessKind::MergeCaptain,
207        HarnessKind::Session,
208        HarnessKind::Permission,
209        HarnessKind::Text,
210        HarnessKind::Lsp,
211        HarnessKind::Credentials,
212        HarnessKind::PrMonitor,
213        HarnessKind::Workflow,
214        HarnessKind::Testing,
215    ];
216}
217
218/// Per-harness clock router used by explicit test controls.
219///
220/// The override belongs to one `HarnessInner`; tests can pin and advance time
221/// without installing thread-local state that unrelated VMs could observe.
222#[derive(Debug)]
223struct HarnessClockRouter {
224    base: Arc<dyn Clock>,
225    override_clock: Mutex<Option<Arc<PausedClock>>>,
226}
227
228impl HarnessClockRouter {
229    fn new(base: Arc<dyn Clock>) -> Self {
230        Self {
231            base,
232            override_clock: Mutex::new(None),
233        }
234    }
235
236    fn active(&self) -> Arc<dyn Clock> {
237        self.override_clock
238            .lock()
239            .expect("harness clock override poisoned")
240            .as_ref()
241            .map(|clock| Arc::clone(clock) as Arc<dyn Clock>)
242            .unwrap_or_else(|| Arc::clone(&self.base))
243    }
244
245    fn set_unix_ms(&self, unix_ms: i64) -> Result<(), crate::VmError> {
246        let nanos = i128::from(unix_ms).checked_mul(1_000_000).ok_or_else(|| {
247            crate::VmError::TypeError("HarnessTesting.clock_set timestamp overflow".to_string())
248        })?;
249        let wall = OffsetDateTime::from_unix_timestamp_nanos(nanos).map_err(|error| {
250            crate::VmError::TypeError(format!(
251                "HarnessTesting.clock_set timestamp is out of range: {error}"
252            ))
253        })?;
254        *self
255            .override_clock
256            .lock()
257            .expect("harness clock override poisoned") = Some(PausedClock::new(wall));
258        Ok(())
259    }
260
261    fn advance_ms(&self, milliseconds: i64) -> Result<i64, crate::VmError> {
262        let milliseconds = u64::try_from(milliseconds).map_err(|_| {
263            crate::VmError::TypeError(
264                "HarnessTesting.clock_advance expects non-negative milliseconds".to_string(),
265            )
266        })?;
267        let clock = self
268            .override_clock
269            .lock()
270            .expect("harness clock override poisoned")
271            .clone()
272            .ok_or_else(|| {
273                crate::VmError::Runtime(
274                    "HarnessTesting.clock_advance requires clock_set first".to_string(),
275                )
276            })?;
277        clock.advance(Duration::from_millis(milliseconds));
278        Ok(harn_clock::now_wall_ms(clock.as_ref()))
279    }
280
281    fn clear_override(&self) {
282        *self
283            .override_clock
284            .lock()
285            .expect("harness clock override poisoned") = None;
286    }
287
288    async fn wait_for_advance(&self, duration: Duration) {
289        self.active().sleep(duration).await;
290    }
291}
292
293#[async_trait]
294impl Clock for HarnessClockRouter {
295    fn now_utc(&self) -> OffsetDateTime {
296        self.active().now_utc()
297    }
298
299    fn monotonic_ms(&self) -> i64 {
300        self.active().monotonic_ms()
301    }
302
303    async fn sleep(&self, duration: Duration) {
304        let clock = self.active();
305        if self
306            .override_clock
307            .lock()
308            .expect("harness clock override poisoned")
309            .is_some()
310        {
311            if let Some(paused) = self
312                .override_clock
313                .lock()
314                .expect("harness clock override poisoned")
315                .clone()
316            {
317                paused.advance(duration);
318                return;
319            }
320        }
321        clock.sleep(duration).await;
322    }
323
324    async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
325        let clock = self.active();
326        if let Some(paused) = self
327            .override_clock
328            .lock()
329            .expect("harness clock override poisoned")
330            .clone()
331        {
332            let now = paused.now_utc();
333            if deadline > now {
334                paused.advance_time(deadline - now);
335            }
336            return;
337        }
338        clock.sleep_until_utc(deadline).await;
339    }
340}
341
342/// Shared, refcounted state backing every sub-handle of a single `Harness`.
343///
344/// Method implementations (in `crate::vm::methods::harness`) borrow this to
345/// reach the concrete OS-backed primitives. Wrapped in `Arc` so handles are
346/// `Send + Sync` for VM contexts that move work onto other tasks.
347pub struct HarnessInner {
348    clock: Arc<dyn Clock>,
349    clock_control: Arc<HarnessClockRouter>,
350    mode: HarnessMode,
351    /// Per-harness `harness.net.*` access policy. `None` means the
352    /// handle inherits the legacy unrestricted behaviour (subject to
353    /// the process-wide `crate::egress` allowlist, if configured).
354    /// See `Harness::with_net_policy` and `crate::harness_net`.
355    net_policy: Option<crate::harness_net::NetPolicy>,
356    /// Optional provider backing `harness.secrets.*`. Runtime embedders install
357    /// the managed provider that owns custody, audit, leases, and rotation.
358    secret_provider: Option<Arc<dyn crate::secrets::SecretProvider>>,
359    /// `true` once a request denied under `OnViolation::Quarantine`
360    /// has fired. Sticky for the lifetime of the underlying
361    /// `Arc<HarnessInner>` so downstream consumers can pin on the
362    /// signal even after the originating call has returned. The flag
363    /// is per-`Arc` (i.e. per-`Harness` build) so unrelated harnesses
364    /// stay independent.
365    quarantined: Mutex<bool>,
366    fixtures: Arc<CapabilityFixtureState>,
367}
368
369impl fmt::Debug for HarnessInner {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        f.debug_struct("HarnessInner")
372            .field("clock", &"<dyn Clock>")
373            .field("mode", &self.mode)
374            .field("net_policy", &self.net_policy)
375            .field(
376                "secret_provider",
377                &self
378                    .secret_provider
379                    .as_ref()
380                    .map(|provider| provider.namespace().to_string()),
381            )
382            .field("quarantined", &self.is_quarantined())
383            .field("fixtures", &self.fixtures)
384            .finish()
385    }
386}
387
388impl HarnessInner {
389    pub fn clock(&self) -> &Arc<dyn Clock> {
390        &self.clock
391    }
392
393    pub(crate) fn set_test_clock(&self, unix_ms: i64) -> Result<(), crate::VmError> {
394        self.clock_control.set_unix_ms(unix_ms)
395    }
396
397    pub(crate) fn advance_test_clock(&self, milliseconds: i64) -> Result<i64, crate::VmError> {
398        self.clock_control.advance_ms(milliseconds)
399    }
400
401    pub(crate) fn clear_test_clock(&self) {
402        self.clock_control.clear_override();
403    }
404
405    pub(crate) async fn wait_for_clock_advance(&self, duration: Duration) {
406        self.clock_control.wait_for_advance(duration).await;
407    }
408
409    pub(crate) fn mode(&self) -> &HarnessMode {
410        &self.mode
411    }
412
413    pub fn net_policy(&self) -> Option<&crate::harness_net::NetPolicy> {
414        self.net_policy.as_ref()
415    }
416
417    pub fn secret_provider(&self) -> Option<&Arc<dyn crate::secrets::SecretProvider>> {
418        self.secret_provider.as_ref()
419    }
420
421    pub(crate) fn mark_quarantined(&self) {
422        if let Ok(mut guard) = self.quarantined.lock() {
423            *guard = true;
424        }
425    }
426
427    pub fn is_quarantined(&self) -> bool {
428        self.quarantined.lock().map(|guard| *guard).unwrap_or(false)
429    }
430
431    pub(crate) fn fixtures(&self) -> &CapabilityFixtureState {
432        &self.fixtures
433    }
434
435    pub(crate) fn fixtures_arc(&self) -> Arc<CapabilityFixtureState> {
436        Arc::clone(&self.fixtures)
437    }
438}
439
440#[derive(Debug)]
441pub(crate) enum HarnessMode {
442    Real,
443    Null(NullHarnessState),
444    Mock(Arc<MockHarnessState>),
445}
446
447#[derive(Debug, Default)]
448pub(crate) struct NullHarnessState {
449    deny_events: Mutex<Vec<DenyEvent>>,
450}
451
452impl NullHarnessState {
453    pub(crate) fn record_deny(
454        &self,
455        sub_handle: HarnessKind,
456        method: &str,
457        args: &[crate::VmValue],
458    ) {
459        self.deny_events
460            .lock()
461            .expect("deny events poisoned")
462            .push(DenyEvent::new(
463                sub_handle,
464                method,
465                args.iter().map(crate::VmValue::display).collect(),
466            ));
467    }
468
469    pub(crate) fn deny_events(&self) -> Vec<DenyEvent> {
470        self.deny_events
471            .lock()
472            .expect("deny events poisoned")
473            .clone()
474    }
475}
476
477#[derive(Debug, Clone, PartialEq, Eq)]
478pub struct DenyEvent {
479    pub sub_handle: HarnessKind,
480    pub method: String,
481    pub args: Vec<String>,
482}
483
484impl DenyEvent {
485    fn new(sub_handle: HarnessKind, method: &str, args: Vec<String>) -> Self {
486        Self {
487            sub_handle,
488            method: method.to_string(),
489            args,
490        }
491    }
492}
493
494#[derive(Debug)]
495pub(crate) struct MockHarnessState {
496    calls: Mutex<Vec<HarnessCall>>,
497    clock: Arc<PausedClock>,
498    env: BTreeMap<String, String>,
499    fs_reads: BTreeMap<String, Vec<u8>>,
500    net_gets: BTreeMap<String, String>,
501    random_u64: Mutex<VecDeque<u64>>,
502    capability_responses:
503        Mutex<BTreeMap<(harn_builtin_meta::CapabilityId, String), VecDeque<crate::VmValue>>>,
504    stdin_lines: Mutex<VecDeque<String>>,
505    stdio: Mutex<String>,
506    stderr: Mutex<String>,
507}
508
509impl MockHarnessState {
510    pub(crate) fn record_call(
511        &self,
512        sub_handle: HarnessKind,
513        method: &str,
514        args: &[crate::VmValue],
515    ) {
516        self.calls
517            .lock()
518            .expect("calls poisoned")
519            .push(HarnessCall::new(
520                sub_handle,
521                method,
522                args.iter().map(crate::VmValue::display).collect(),
523            ));
524    }
525
526    pub(crate) fn calls(&self) -> Vec<HarnessCall> {
527        self.calls.lock().expect("calls poisoned").clone()
528    }
529
530    pub(crate) fn env_get(&self, key: &str) -> Option<&str> {
531        self.env.get(key).map(String::as_str)
532    }
533
534    pub(crate) fn fs_read(&self, path: &str) -> Option<&[u8]> {
535        self.fs_reads.get(path).map(Vec::as_slice)
536    }
537
538    pub(crate) fn net_get(&self, url: &str) -> Option<&str> {
539        self.net_gets.get(url).map(String::as_str)
540    }
541
542    pub(crate) fn next_random_u64(&self) -> Option<u64> {
543        let mut values = self.random_u64.lock().expect("random values poisoned");
544        values.pop_front()
545    }
546
547    pub(crate) fn capability_response(
548        &self,
549        capability: harn_builtin_meta::CapabilityId,
550        method: &str,
551    ) -> Option<crate::VmValue> {
552        self.capability_responses
553            .lock()
554            .expect("capability responses poisoned")
555            .get_mut(&(capability, method.to_string()))
556            .and_then(VecDeque::pop_front)
557    }
558
559    /// Whether a canned response is still queued for this capability method.
560    ///
561    /// `capability_response` consumes one response per call, so dispatch needs
562    /// this to decide whether the mock owns a method without spending the
563    /// answer it is deciding about.
564    pub(crate) fn has_capability_response(
565        &self,
566        capability: harn_builtin_meta::CapabilityId,
567        method: &str,
568    ) -> bool {
569        self.capability_responses
570            .lock()
571            .expect("capability responses poisoned")
572            .get(&(capability, method.to_string()))
573            .is_some_and(|queued| !queued.is_empty())
574    }
575
576    pub(crate) fn advance_clock(&self, duration: std::time::Duration) {
577        self.clock.advance(duration);
578    }
579
580    pub(crate) fn push_stdio(&self, text: &str) {
581        self.stdio
582            .lock()
583            .expect("stdio buffer poisoned")
584            .push_str(text);
585    }
586
587    pub(crate) fn stdio(&self) -> String {
588        self.stdio.lock().expect("stdio buffer poisoned").clone()
589    }
590
591    pub(crate) fn push_stderr(&self, text: &str) {
592        self.stderr
593            .lock()
594            .expect("stderr buffer poisoned")
595            .push_str(text);
596    }
597
598    pub(crate) fn stderr(&self) -> String {
599        self.stderr.lock().expect("stderr buffer poisoned").clone()
600    }
601
602    pub(crate) fn pop_stdin_line(&self) -> Option<String> {
603        self.stdin_lines
604            .lock()
605            .expect("stdin queue poisoned")
606            .pop_front()
607    }
608}
609
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct HarnessCall {
612    pub sub_handle: HarnessKind,
613    pub method: String,
614    pub args: Vec<String>,
615}
616
617impl HarnessCall {
618    fn new(sub_handle: HarnessKind, method: &str, args: Vec<String>) -> Self {
619        Self {
620            sub_handle,
621            method: method.to_string(),
622            args,
623        }
624    }
625}
626
627#[derive(Debug)]
628pub struct MockHarnessBuilder {
629    clock: Arc<PausedClock>,
630    env: BTreeMap<String, String>,
631    fs_reads: BTreeMap<String, Vec<u8>>,
632    net_gets: BTreeMap<String, String>,
633    random_u64: Vec<u64>,
634    capability_responses: BTreeMap<(harn_builtin_meta::CapabilityId, String), Vec<crate::VmValue>>,
635    stdin_lines: Vec<String>,
636}
637
638impl MockHarnessBuilder {
639    fn new() -> Self {
640        Self {
641            clock: paused_clock_at_unix_ms(0),
642            env: BTreeMap::new(),
643            fs_reads: BTreeMap::new(),
644            net_gets: BTreeMap::new(),
645            random_u64: Vec::new(),
646            capability_responses: BTreeMap::new(),
647            stdin_lines: Vec::new(),
648        }
649    }
650
651    pub fn clock_at_unix_ms(mut self, unix_ms: i64) -> Self {
652        self.clock = paused_clock_at_unix_ms(unix_ms);
653        self
654    }
655
656    pub fn clock_at(mut self, origin: OffsetDateTime) -> Self {
657        self.clock = PausedClock::new(origin);
658        self
659    }
660
661    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
662        self.env.insert(key.into(), value.into());
663        self
664    }
665
666    pub fn fs_read(mut self, path: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
667        self.fs_reads.insert(path.into(), data.into());
668        self
669    }
670
671    pub fn net_get(mut self, url: impl Into<String>, body: impl Into<String>) -> Self {
672        self.net_gets.insert(url.into(), body.into());
673        self
674    }
675
676    pub fn random_u64(mut self, value: u64) -> Self {
677        self.random_u64.push(value);
678        self
679    }
680
681    /// Queue an exact return value for a capability method that does not have
682    /// a purpose-built fixture helper. Responses are consumed FIFO and belong
683    /// to this harness instance; no process registry or thread-local mock
684    /// state participates.
685    pub fn capability_response(
686        mut self,
687        capability: harn_builtin_meta::CapabilityId,
688        method: impl Into<String>,
689        value: crate::VmValue,
690    ) -> Self {
691        self.capability_responses
692            .entry((capability, method.into()))
693            .or_default()
694            .push(value);
695        self
696    }
697
698    /// Queue a line that `harness.stdio.read_line()` or
699    /// `harness.stdio.prompt(...)` will return next. Lines are dequeued
700    /// FIFO; once the queue is empty subsequent reads surface EOF
701    /// (`nil` for the unstructured form, `{ok: false, status: "eof"}`
702    /// for the structured form).
703    pub fn stdin_line(mut self, line: impl Into<String>) -> Self {
704        self.stdin_lines.push(line.into());
705        self
706    }
707
708    pub fn build(self) -> Harness {
709        let clock = self.clock;
710        Harness::with_mode(
711            clock.clone() as Arc<dyn Clock>,
712            HarnessMode::Mock(Arc::new(MockHarnessState {
713                calls: Mutex::new(Vec::new()),
714                clock,
715                env: self.env,
716                fs_reads: self.fs_reads,
717                net_gets: self.net_gets,
718                random_u64: Mutex::new(self.random_u64.into()),
719                capability_responses: Mutex::new(
720                    self.capability_responses
721                        .into_iter()
722                        .map(|(key, values)| (key, values.into()))
723                        .collect(),
724                ),
725                stdin_lines: Mutex::new(self.stdin_lines.into()),
726                stdio: Mutex::new(String::new()),
727                stderr: Mutex::new(String::new()),
728            })),
729        )
730    }
731}
732
733/// The runtime handle threaded into `main(harness: Harness)`.
734///
735/// Cheap to clone; sub-handles share the same `Arc` inner state.
736#[derive(Debug, Clone)]
737pub struct Harness {
738    inner: Arc<HarnessInner>,
739}
740
741impl Harness {
742    /// Build the production handle wired to wall-clock time.
743    ///
744    /// Test-time overrides are scoped to this handle and are reachable only
745    /// through `harness.testing`; production construction never consults
746    /// thread-local clock state.
747    pub fn real() -> Self {
748        Self::with_mode(Arc::new(RealClock::new()), HarnessMode::Real)
749    }
750
751    /// Build a deny-by-default test handle. Every sub-handle method records a
752    /// [`DenyEvent`] and fails with a categorized VM error.
753    pub fn null() -> Self {
754        Self::with_mode(
755            paused_clock_at_unix_ms(0) as Arc<dyn Clock>,
756            HarnessMode::Null(NullHarnessState::default()),
757        )
758    }
759
760    /// Build a record/replay test handle backed by a paused clock.
761    pub fn mock() -> MockHarnessBuilder {
762        MockHarnessBuilder::new()
763    }
764
765    /// Build a handle wired to a caller-supplied clock. Most callers want
766    /// [`Self::test`] (which constructs the `PausedClock` for you);
767    /// reach for this when an existing `Arc<dyn Clock>` is already in
768    /// hand — e.g. a `RecordedClock` wrapper.
769    pub fn with_clock(clock: Arc<dyn Clock>) -> Self {
770        Self::with_mode(clock, HarnessMode::Real)
771    }
772
773    /// Construct a `Harness` from a pre-built `Arc<HarnessInner>`.
774    /// Used by VM method dispatch when it needs to re-wrap a sub-handle's
775    /// inner state into a root `Harness` (e.g. to invoke
776    /// [`Self::with_net_policy`] from inside the method dispatcher).
777    pub fn from_inner(inner: Arc<HarnessInner>) -> Self {
778        Self { inner }
779    }
780
781    fn with_mode(clock: Arc<dyn Clock>, mode: HarnessMode) -> Self {
782        let clock_control = Arc::new(HarnessClockRouter::new(clock));
783        let clock: Arc<dyn Clock> = clock_control.clone();
784        let inner = Arc::new(HarnessInner {
785            clock,
786            clock_control,
787            mode,
788            net_policy: None,
789            secret_provider: None,
790            quarantined: Mutex::new(false),
791            fixtures: Arc::new(CapabilityFixtureState::default()),
792        });
793        Self { inner }
794    }
795
796    /// Attach a per-harness `harness.net.*` access policy.
797    ///
798    /// Returns a new `Harness` value whose sub-handles share a fresh
799    /// `Arc<HarnessInner>`. Existing handles built off the prior inner
800    /// keep operating without the policy — so calling
801    /// `harness.with_net_policy(...)` does NOT retroactively gate
802    /// references to `harness` held elsewhere. Per issue #1913.
803    ///
804    /// The clock and mode are propagated verbatim. Mock canned
805    /// responses (`net_gets`, `random_u64`, etc.) live behind the
806    /// shared `HarnessMode::Mock` payload, so the new handle observes
807    /// the same recorded calls and the same canned responses as the
808    /// source handle.
809    pub fn with_net_policy(&self, policy: crate::harness_net::NetPolicy) -> Self {
810        let clock = Arc::clone(&self.inner.clock);
811        let clock_control = Arc::clone(&self.inner.clock_control);
812        let mode = self.clone_mode_for_child();
813        // See `with_mode` for the rationale on this suppression.
814        #[allow(clippy::arc_with_non_send_sync)]
815        let inner = Arc::new(HarnessInner {
816            clock,
817            clock_control,
818            mode,
819            net_policy: Some(policy),
820            secret_provider: self.inner.secret_provider.clone(),
821            quarantined: Mutex::new(self.is_quarantined()),
822            fixtures: Arc::clone(&self.inner.fixtures),
823        });
824        Self { inner }
825    }
826
827    /// Return the provider attached to `harness.secrets.*`, when present.
828    pub fn secret_provider(&self) -> Option<&Arc<dyn crate::secrets::SecretProvider>> {
829        self.inner.secret_provider()
830    }
831
832    /// Attach a provider for `harness.secrets.*`.
833    ///
834    /// The provider is intentionally embedder-supplied. Harn owns the typed
835    /// method contract; the host owns custody details such as KMS wrapping,
836    /// lease storage, audit sinks, and scope policy.
837    pub fn with_secret_provider(&self, provider: Arc<dyn crate::secrets::SecretProvider>) -> Self {
838        let clock = Arc::clone(&self.inner.clock);
839        let clock_control = Arc::clone(&self.inner.clock_control);
840        let mode = self.clone_mode_for_child();
841        #[allow(clippy::arc_with_non_send_sync)]
842        let inner = Arc::new(HarnessInner {
843            clock,
844            clock_control,
845            mode,
846            net_policy: self.inner.net_policy.clone(),
847            secret_provider: Some(provider),
848            quarantined: Mutex::new(self.is_quarantined()),
849            fixtures: Arc::clone(&self.inner.fixtures),
850        });
851        Self { inner }
852    }
853
854    fn clone_mode_for_child(&self) -> HarnessMode {
855        match &self.inner.mode {
856            HarnessMode::Real => HarnessMode::Real,
857            HarnessMode::Null(_) => HarnessMode::Null(NullHarnessState::default()),
858            HarnessMode::Mock(state) => HarnessMode::Mock(Arc::clone(state)),
859        }
860    }
861
862    /// `true` if the harness has been marked quarantined by an
863    /// `OnViolation::Quarantine` deny event.
864    pub fn is_quarantined(&self) -> bool {
865        self.inner.is_quarantined()
866    }
867
868    pub fn deny_events(&self) -> Vec<DenyEvent> {
869        match self.inner.mode() {
870            HarnessMode::Null(state) => state.deny_events(),
871            HarnessMode::Real | HarnessMode::Mock(_) => Vec::new(),
872        }
873    }
874
875    pub fn calls(&self) -> Vec<HarnessCall> {
876        match self.inner.mode() {
877            HarnessMode::Mock(state) => state.calls(),
878            HarnessMode::Real | HarnessMode::Null(_) => Vec::new(),
879        }
880    }
881
882    pub fn captured_stdio(&self) -> String {
883        match self.inner.mode() {
884            HarnessMode::Mock(state) => state.stdio(),
885            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
886        }
887    }
888
889    pub fn captured_stderr(&self) -> String {
890        match self.inner.mode() {
891            HarnessMode::Mock(state) => state.stderr(),
892            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
893        }
894    }
895
896    /// Build a deterministic test handle wired to a fresh
897    /// [`PausedClock`] pinned at the Unix epoch.
898    ///
899    /// Returns the harness paired with the underlying `PausedClock` so
900    /// tests can drive virtual time through `PausedClock::advance`
901    /// while passing the same `Harness` value into the VM. The two
902    /// share the underlying `Arc<dyn Clock>`, so the harness reflects
903    /// every advance immediately.
904    ///
905    /// Pairs with [`PausedClock::advance`] / [`PausedClock::set`] — see
906    /// [`Self::with_paused_clock`] for picking a non-epoch origin.
907    pub fn test() -> (Self, Arc<PausedClock>) {
908        Self::with_paused_clock(OffsetDateTime::UNIX_EPOCH)
909    }
910
911    /// Like [`Self::test`], but pins the paused clock's wall origin to
912    /// `origin`. Lets tests anchor virtual time to a meaningful date
913    /// without manually advancing past the epoch first.
914    pub fn with_paused_clock(origin: OffsetDateTime) -> (Self, Arc<PausedClock>) {
915        let paused = PausedClock::new(origin);
916        let as_dyn: Arc<dyn Clock> = paused.clone();
917        (Self::with_clock(as_dyn), paused)
918    }
919
920    /// Field access for `harness.stdio`.
921    pub fn stdio(&self) -> HarnessStdio {
922        HarnessStdio {
923            inner: Arc::clone(&self.inner),
924        }
925    }
926
927    /// Field access for `harness.term`.
928    pub fn term(&self) -> HarnessTerm {
929        HarnessTerm {
930            inner: Arc::clone(&self.inner),
931        }
932    }
933
934    /// Field access for `harness.clock`.
935    pub fn clock(&self) -> HarnessClock {
936        HarnessClock {
937            inner: Arc::clone(&self.inner),
938        }
939    }
940
941    /// Field access for `harness.fs`.
942    pub fn fs(&self) -> HarnessFs {
943        HarnessFs {
944            inner: Arc::clone(&self.inner),
945        }
946    }
947
948    /// Field access for `harness.env`.
949    pub fn env(&self) -> HarnessEnv {
950        HarnessEnv {
951            inner: Arc::clone(&self.inner),
952        }
953    }
954
955    /// Field access for `harness.random`.
956    pub fn random(&self) -> HarnessRandom {
957        HarnessRandom {
958            inner: Arc::clone(&self.inner),
959        }
960    }
961
962    /// Field access for `harness.net`.
963    pub fn net(&self) -> HarnessNet {
964        HarnessNet {
965            inner: Arc::clone(&self.inner),
966        }
967    }
968
969    /// Field access for `harness.process`.
970    pub fn process(&self) -> HarnessProcess {
971        HarnessProcess {
972            inner: Arc::clone(&self.inner),
973        }
974    }
975
976    /// Field access for `harness.channels`.
977    pub fn channels(&self) -> HarnessChannels {
978        HarnessChannels {
979            inner: Arc::clone(&self.inner),
980        }
981    }
982
983    /// Field access for `harness.system`.
984    pub fn system(&self) -> HarnessSystem {
985        HarnessSystem {
986            inner: Arc::clone(&self.inner),
987        }
988    }
989
990    /// Field access for `harness.secrets`.
991    pub fn secrets(&self) -> HarnessSecrets {
992        HarnessSecrets {
993            inner: Arc::clone(&self.inner),
994        }
995    }
996
997    /// Field access for `harness.llm`.
998    pub fn llm(&self) -> HarnessLlm {
999        HarnessLlm {
1000            inner: Arc::clone(&self.inner),
1001        }
1002    }
1003
1004    /// Field access for `harness.tenant`.
1005    pub fn tenant(&self) -> HarnessTenant {
1006        HarnessTenant {
1007            inner: Arc::clone(&self.inner),
1008        }
1009    }
1010
1011    /// Field access for `harness.auth`.
1012    pub fn auth(&self) -> HarnessAuth {
1013        HarnessAuth {
1014            inner: Arc::clone(&self.inner),
1015        }
1016    }
1017
1018    /// Field access for `harness.obs`.
1019    pub fn obs(&self) -> HarnessObs {
1020        HarnessObs {
1021            inner: Arc::clone(&self.inner),
1022        }
1023    }
1024
1025    /// Field access for `harness.testing`.
1026    pub fn testing(&self) -> HarnessTesting {
1027        HarnessTesting {
1028            inner: Arc::clone(&self.inner),
1029        }
1030    }
1031
1032    /// Field access for `harness.memory`.
1033    pub fn memory(&self) -> HarnessMemory {
1034        HarnessMemory {
1035            inner: Arc::clone(&self.inner),
1036        }
1037    }
1038
1039    pub fn sqlite(&self) -> HarnessSqlite {
1040        HarnessSqlite {
1041            inner: Arc::clone(&self.inner),
1042        }
1043    }
1044
1045    pub fn postgres(&self) -> HarnessPostgres {
1046        HarnessPostgres {
1047            inner: Arc::clone(&self.inner),
1048        }
1049    }
1050
1051    pub fn agent(&self) -> HarnessAgent {
1052        HarnessAgent {
1053            inner: Arc::clone(&self.inner),
1054        }
1055    }
1056
1057    /// Lower this handle into the `VmValue::Harness` payload.
1058    pub fn into_vm_value(self) -> crate::value::VmValue {
1059        crate::value::VmValue::harness(VmHarness {
1060            inner: self.inner,
1061            kind: HarnessKind::Root,
1062        })
1063    }
1064}
1065
1066fn paused_clock_at_unix_ms(unix_ms: i64) -> Arc<PausedClock> {
1067    let nanos = (unix_ms as i128).saturating_mul(1_000_000);
1068    let origin =
1069        OffsetDateTime::from_unix_timestamp_nanos(nanos).unwrap_or(OffsetDateTime::UNIX_EPOCH);
1070    PausedClock::new(origin)
1071}
1072
1073pub(crate) fn vm_string(value: impl Into<String>) -> crate::VmValue {
1074    crate::VmValue::String(arcstr::ArcStr::from(value.into()))
1075}
1076
1077impl Default for Harness {
1078    fn default() -> Self {
1079        Self::real()
1080    }
1081}
1082
1083/// stdio sub-handle: `print`, `println`, `eprint`, `eprintln`, `prompt`,
1084/// `read_line`.
1085#[derive(Debug, Clone)]
1086pub struct HarnessStdio {
1087    inner: Arc<HarnessInner>,
1088}
1089
1090/// term sub-handle: `width`, `height`, `read_password`.
1091#[derive(Debug, Clone)]
1092pub struct HarnessTerm {
1093    inner: Arc<HarnessInner>,
1094}
1095
1096/// clock sub-handle: `now`, `monotonic_now`, `sleep`.
1097#[derive(Debug, Clone)]
1098pub struct HarnessClock {
1099    inner: Arc<HarnessInner>,
1100}
1101
1102impl HarnessClock {
1103    pub fn clock(&self) -> &Arc<dyn Clock> {
1104        self.inner.clock()
1105    }
1106}
1107
1108include!("harness/fixtures.rs");
1109include!("harness/value.rs");
1110include!("harness/tests.rs");
1111/// fs sub-handle: `read_file`, `write_file`, `exists`, `list_dir`,
1112/// `delete_file`, ...
1113#[derive(Debug, Clone)]
1114pub struct HarnessFs {
1115    inner: Arc<HarnessInner>,
1116}
1117
1118/// env sub-handle: `get`, `set`, `vars`.
1119#[derive(Debug, Clone)]
1120pub struct HarnessEnv {
1121    inner: Arc<HarnessInner>,
1122}
1123
1124/// random sub-handle: `u64`, `range`, `f64`, ...
1125#[derive(Debug, Clone)]
1126pub struct HarnessRandom {
1127    inner: Arc<HarnessInner>,
1128}
1129
1130/// net sub-handle: `http_get`, `http_post`, ...
1131#[derive(Debug, Clone)]
1132pub struct HarnessNet {
1133    inner: Arc<HarnessInner>,
1134}
1135
1136/// process sub-handle: structured process execution and shell discovery.
1137#[derive(Debug, Clone)]
1138pub struct HarnessProcess {
1139    inner: Arc<HarnessInner>,
1140}
1141
1142/// Durable transcript channel sub-handle.
1143#[derive(Debug, Clone)]
1144pub struct HarnessChannels {
1145    inner: Arc<HarnessInner>,
1146}
1147
1148/// system sub-handle: `cpu`, `memory`, `gpus`, `temperature`, `platform`,
1149/// `processes`. Read-only host introspection — no side effects on the host
1150/// system. Gated by the harness handle so scripts running under
1151/// `Harness::null()` or restricted policies cannot fingerprint the runner
1152/// without an explicit grant (issue #1912 / epic #1765).
1153#[derive(Debug, Clone)]
1154pub struct HarnessSystem {
1155    inner: Arc<HarnessInner>,
1156}
1157
1158/// secrets sub-handle: `read`, `write`, `rotate`, `lease`.
1159#[derive(Debug, Clone)]
1160pub struct HarnessSecrets {
1161    inner: Arc<HarnessInner>,
1162}
1163
1164/// llm sub-handle: `catalog`, `providers`.
1165#[derive(Debug, Clone)]
1166pub struct HarnessLlm {
1167    inner: Arc<HarnessInner>,
1168}
1169
1170/// tenant sub-handle: `id`, `try_id`. Surfaces the ambient `TenantId`
1171/// bound by the dispatching host (see [`crate::harness_tenant`]). No
1172/// host state — the methods consult a thread-local stack — but the
1173/// handle still rides the shared `Arc<HarnessInner>` so null/mock-mode
1174/// gating in [`crate::vm::methods::harness`] applies uniformly.
1175#[derive(Debug, Clone)]
1176pub struct HarnessTenant {
1177    inner: Arc<HarnessInner>,
1178}
1179
1180/// auth sub-handle: `is_authenticated`, `subject` / `try_subject`,
1181/// `scheme` / `try_scheme`, `kind`, `scopes`, `has_scope`. Surfaces the
1182/// ambient authenticated principal bound by the dispatching host (see
1183/// [`crate::harness_auth`]). Like [`HarnessTenant`] it holds no host
1184/// state — the methods consult a thread-local stack — but rides the
1185/// shared `Arc<HarnessInner>` so null/mock-mode gating in
1186/// [`crate::vm::methods::harness`] applies uniformly.
1187#[derive(Debug, Clone)]
1188pub struct HarnessAuth {
1189    inner: Arc<HarnessInner>,
1190}
1191
1192/// obs sub-handle: `span` / `start_span` / `end_span` / `counter` /
1193/// `histogram` / `gauge` / `log` / `request_id`. Wraps the existing
1194/// `__obs_*` builtins and the request_id ambient pushed by the
1195/// dispatching host (see [`crate::observability::request_id`]) behind a
1196/// typed surface so handlers don't reach into the lower-level builtins
1197/// directly. Backend selection / exporter wiring still lives in
1198/// [`crate::events`] (OTel sink) and `std/observability` (`configure`,
1199/// backend factories) — the sub-handle is the *emit-side* surface that
1200/// every harn-serve primitive shares.
1201#[derive(Debug, Clone)]
1202pub struct HarnessObs {
1203    inner: Arc<HarnessInner>,
1204}
1205
1206/// Per-harness deterministic fixture control. Responses and call records are
1207/// owned by this Harness instance rather than a process registry or
1208/// thread-local scope.
1209#[derive(Debug, Clone)]
1210pub struct HarnessTesting {
1211    inner: Arc<HarnessInner>,
1212}
1213
1214/// Durable memory sub-handle. Keeping this distinct from [`HarnessEmbed`]
1215/// prevents a caller that can compute vectors from implicitly gaining
1216/// persistent read/write authority.
1217#[derive(Debug, Clone)]
1218pub struct HarnessMemory {
1219    inner: Arc<HarnessInner>,
1220}
1221
1222#[derive(Debug, Clone)]
1223pub struct HarnessSqlite {
1224    inner: Arc<HarnessInner>,
1225}
1226
1227#[derive(Debug, Clone)]
1228pub struct HarnessPostgres {
1229    inner: Arc<HarnessInner>,
1230}
1231
1232#[derive(Debug, Clone)]
1233pub struct HarnessAgent {
1234    inner: Arc<HarnessInner>,
1235}
1236
1237macro_rules! sub_handle_inner {
1238    ($($ty:ty),* $(,)?) => {
1239        $(
1240            impl $ty {
1241                #[allow(dead_code)]
1242                pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1243                    &self.inner
1244                }
1245            }
1246        )*
1247    };
1248}
1249sub_handle_inner!(
1250    HarnessStdio,
1251    HarnessTerm,
1252    HarnessFs,
1253    HarnessEnv,
1254    HarnessRandom,
1255    HarnessNet,
1256    HarnessProcess,
1257    HarnessChannels,
1258    HarnessSystem,
1259    HarnessSecrets,
1260    HarnessLlm,
1261    HarnessTenant,
1262    HarnessAuth,
1263    HarnessObs,
1264    HarnessMemory,
1265    HarnessSqlite,
1266    HarnessPostgres,
1267    HarnessAgent,
1268    HarnessTesting,
1269);
1270
1271impl HarnessClock {
1272    #[allow(dead_code)]
1273    pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1274        &self.inner
1275    }
1276}