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    /// Attach a provider for `harness.secrets.*`.
828    ///
829    /// The provider is intentionally embedder-supplied. Harn owns the typed
830    /// method contract; the host owns custody details such as KMS wrapping,
831    /// lease storage, audit sinks, and scope policy.
832    pub fn with_secret_provider(&self, provider: Arc<dyn crate::secrets::SecretProvider>) -> Self {
833        let clock = Arc::clone(&self.inner.clock);
834        let clock_control = Arc::clone(&self.inner.clock_control);
835        let mode = self.clone_mode_for_child();
836        #[allow(clippy::arc_with_non_send_sync)]
837        let inner = Arc::new(HarnessInner {
838            clock,
839            clock_control,
840            mode,
841            net_policy: self.inner.net_policy.clone(),
842            secret_provider: Some(provider),
843            quarantined: Mutex::new(self.is_quarantined()),
844            fixtures: Arc::clone(&self.inner.fixtures),
845        });
846        Self { inner }
847    }
848
849    fn clone_mode_for_child(&self) -> HarnessMode {
850        match &self.inner.mode {
851            HarnessMode::Real => HarnessMode::Real,
852            HarnessMode::Null(_) => HarnessMode::Null(NullHarnessState::default()),
853            HarnessMode::Mock(state) => HarnessMode::Mock(Arc::clone(state)),
854        }
855    }
856
857    /// `true` if the harness has been marked quarantined by an
858    /// `OnViolation::Quarantine` deny event.
859    pub fn is_quarantined(&self) -> bool {
860        self.inner.is_quarantined()
861    }
862
863    pub fn deny_events(&self) -> Vec<DenyEvent> {
864        match self.inner.mode() {
865            HarnessMode::Null(state) => state.deny_events(),
866            HarnessMode::Real | HarnessMode::Mock(_) => Vec::new(),
867        }
868    }
869
870    pub fn calls(&self) -> Vec<HarnessCall> {
871        match self.inner.mode() {
872            HarnessMode::Mock(state) => state.calls(),
873            HarnessMode::Real | HarnessMode::Null(_) => Vec::new(),
874        }
875    }
876
877    pub fn captured_stdio(&self) -> String {
878        match self.inner.mode() {
879            HarnessMode::Mock(state) => state.stdio(),
880            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
881        }
882    }
883
884    pub fn captured_stderr(&self) -> String {
885        match self.inner.mode() {
886            HarnessMode::Mock(state) => state.stderr(),
887            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
888        }
889    }
890
891    /// Build a deterministic test handle wired to a fresh
892    /// [`PausedClock`] pinned at the Unix epoch.
893    ///
894    /// Returns the harness paired with the underlying `PausedClock` so
895    /// tests can drive virtual time through `PausedClock::advance`
896    /// while passing the same `Harness` value into the VM. The two
897    /// share the underlying `Arc<dyn Clock>`, so the harness reflects
898    /// every advance immediately.
899    ///
900    /// Pairs with [`PausedClock::advance`] / [`PausedClock::set`] — see
901    /// [`Self::with_paused_clock`] for picking a non-epoch origin.
902    pub fn test() -> (Self, Arc<PausedClock>) {
903        Self::with_paused_clock(OffsetDateTime::UNIX_EPOCH)
904    }
905
906    /// Like [`Self::test`], but pins the paused clock's wall origin to
907    /// `origin`. Lets tests anchor virtual time to a meaningful date
908    /// without manually advancing past the epoch first.
909    pub fn with_paused_clock(origin: OffsetDateTime) -> (Self, Arc<PausedClock>) {
910        let paused = PausedClock::new(origin);
911        let as_dyn: Arc<dyn Clock> = paused.clone();
912        (Self::with_clock(as_dyn), paused)
913    }
914
915    /// Field access for `harness.stdio`.
916    pub fn stdio(&self) -> HarnessStdio {
917        HarnessStdio {
918            inner: Arc::clone(&self.inner),
919        }
920    }
921
922    /// Field access for `harness.term`.
923    pub fn term(&self) -> HarnessTerm {
924        HarnessTerm {
925            inner: Arc::clone(&self.inner),
926        }
927    }
928
929    /// Field access for `harness.clock`.
930    pub fn clock(&self) -> HarnessClock {
931        HarnessClock {
932            inner: Arc::clone(&self.inner),
933        }
934    }
935
936    /// Field access for `harness.fs`.
937    pub fn fs(&self) -> HarnessFs {
938        HarnessFs {
939            inner: Arc::clone(&self.inner),
940        }
941    }
942
943    /// Field access for `harness.env`.
944    pub fn env(&self) -> HarnessEnv {
945        HarnessEnv {
946            inner: Arc::clone(&self.inner),
947        }
948    }
949
950    /// Field access for `harness.random`.
951    pub fn random(&self) -> HarnessRandom {
952        HarnessRandom {
953            inner: Arc::clone(&self.inner),
954        }
955    }
956
957    /// Field access for `harness.net`.
958    pub fn net(&self) -> HarnessNet {
959        HarnessNet {
960            inner: Arc::clone(&self.inner),
961        }
962    }
963
964    /// Field access for `harness.process`.
965    pub fn process(&self) -> HarnessProcess {
966        HarnessProcess {
967            inner: Arc::clone(&self.inner),
968        }
969    }
970
971    /// Field access for `harness.channels`.
972    pub fn channels(&self) -> HarnessChannels {
973        HarnessChannels {
974            inner: Arc::clone(&self.inner),
975        }
976    }
977
978    /// Field access for `harness.system`.
979    pub fn system(&self) -> HarnessSystem {
980        HarnessSystem {
981            inner: Arc::clone(&self.inner),
982        }
983    }
984
985    /// Field access for `harness.secrets`.
986    pub fn secrets(&self) -> HarnessSecrets {
987        HarnessSecrets {
988            inner: Arc::clone(&self.inner),
989        }
990    }
991
992    /// Field access for `harness.llm`.
993    pub fn llm(&self) -> HarnessLlm {
994        HarnessLlm {
995            inner: Arc::clone(&self.inner),
996        }
997    }
998
999    /// Field access for `harness.tenant`.
1000    pub fn tenant(&self) -> HarnessTenant {
1001        HarnessTenant {
1002            inner: Arc::clone(&self.inner),
1003        }
1004    }
1005
1006    /// Field access for `harness.auth`.
1007    pub fn auth(&self) -> HarnessAuth {
1008        HarnessAuth {
1009            inner: Arc::clone(&self.inner),
1010        }
1011    }
1012
1013    /// Field access for `harness.obs`.
1014    pub fn obs(&self) -> HarnessObs {
1015        HarnessObs {
1016            inner: Arc::clone(&self.inner),
1017        }
1018    }
1019
1020    /// Field access for `harness.testing`.
1021    pub fn testing(&self) -> HarnessTesting {
1022        HarnessTesting {
1023            inner: Arc::clone(&self.inner),
1024        }
1025    }
1026
1027    /// Field access for `harness.memory`.
1028    pub fn memory(&self) -> HarnessMemory {
1029        HarnessMemory {
1030            inner: Arc::clone(&self.inner),
1031        }
1032    }
1033
1034    pub fn sqlite(&self) -> HarnessSqlite {
1035        HarnessSqlite {
1036            inner: Arc::clone(&self.inner),
1037        }
1038    }
1039
1040    pub fn postgres(&self) -> HarnessPostgres {
1041        HarnessPostgres {
1042            inner: Arc::clone(&self.inner),
1043        }
1044    }
1045
1046    pub fn agent(&self) -> HarnessAgent {
1047        HarnessAgent {
1048            inner: Arc::clone(&self.inner),
1049        }
1050    }
1051
1052    /// Lower this handle into the `VmValue::Harness` payload.
1053    pub fn into_vm_value(self) -> crate::value::VmValue {
1054        crate::value::VmValue::harness(VmHarness {
1055            inner: self.inner,
1056            kind: HarnessKind::Root,
1057        })
1058    }
1059}
1060
1061fn paused_clock_at_unix_ms(unix_ms: i64) -> Arc<PausedClock> {
1062    let nanos = (unix_ms as i128).saturating_mul(1_000_000);
1063    let origin =
1064        OffsetDateTime::from_unix_timestamp_nanos(nanos).unwrap_or(OffsetDateTime::UNIX_EPOCH);
1065    PausedClock::new(origin)
1066}
1067
1068pub(crate) fn vm_string(value: impl Into<String>) -> crate::VmValue {
1069    crate::VmValue::String(arcstr::ArcStr::from(value.into()))
1070}
1071
1072impl Default for Harness {
1073    fn default() -> Self {
1074        Self::real()
1075    }
1076}
1077
1078/// stdio sub-handle: `print`, `println`, `eprint`, `eprintln`, `prompt`,
1079/// `read_line`.
1080#[derive(Debug, Clone)]
1081pub struct HarnessStdio {
1082    inner: Arc<HarnessInner>,
1083}
1084
1085/// term sub-handle: `width`, `height`, `read_password`.
1086#[derive(Debug, Clone)]
1087pub struct HarnessTerm {
1088    inner: Arc<HarnessInner>,
1089}
1090
1091/// clock sub-handle: `now`, `monotonic_now`, `sleep`.
1092#[derive(Debug, Clone)]
1093pub struct HarnessClock {
1094    inner: Arc<HarnessInner>,
1095}
1096
1097impl HarnessClock {
1098    pub fn clock(&self) -> &Arc<dyn Clock> {
1099        self.inner.clock()
1100    }
1101}
1102
1103include!("harness/fixtures.rs");
1104include!("harness/value.rs");
1105include!("harness/tests.rs");
1106/// fs sub-handle: `read_file`, `write_file`, `exists`, `list_dir`,
1107/// `delete_file`, ...
1108#[derive(Debug, Clone)]
1109pub struct HarnessFs {
1110    inner: Arc<HarnessInner>,
1111}
1112
1113/// env sub-handle: `get`, `set`, `vars`.
1114#[derive(Debug, Clone)]
1115pub struct HarnessEnv {
1116    inner: Arc<HarnessInner>,
1117}
1118
1119/// random sub-handle: `u64`, `range`, `f64`, ...
1120#[derive(Debug, Clone)]
1121pub struct HarnessRandom {
1122    inner: Arc<HarnessInner>,
1123}
1124
1125/// net sub-handle: `http_get`, `http_post`, ...
1126#[derive(Debug, Clone)]
1127pub struct HarnessNet {
1128    inner: Arc<HarnessInner>,
1129}
1130
1131/// process sub-handle: structured process execution and shell discovery.
1132#[derive(Debug, Clone)]
1133pub struct HarnessProcess {
1134    inner: Arc<HarnessInner>,
1135}
1136
1137/// Durable transcript channel sub-handle.
1138#[derive(Debug, Clone)]
1139pub struct HarnessChannels {
1140    inner: Arc<HarnessInner>,
1141}
1142
1143/// system sub-handle: `cpu`, `memory`, `gpus`, `temperature`, `platform`,
1144/// `processes`. Read-only host introspection — no side effects on the host
1145/// system. Gated by the harness handle so scripts running under
1146/// `Harness::null()` or restricted policies cannot fingerprint the runner
1147/// without an explicit grant (issue #1912 / epic #1765).
1148#[derive(Debug, Clone)]
1149pub struct HarnessSystem {
1150    inner: Arc<HarnessInner>,
1151}
1152
1153/// secrets sub-handle: `read`, `write`, `rotate`, `lease`.
1154#[derive(Debug, Clone)]
1155pub struct HarnessSecrets {
1156    inner: Arc<HarnessInner>,
1157}
1158
1159/// llm sub-handle: `catalog`, `providers`.
1160#[derive(Debug, Clone)]
1161pub struct HarnessLlm {
1162    inner: Arc<HarnessInner>,
1163}
1164
1165/// tenant sub-handle: `id`, `try_id`. Surfaces the ambient `TenantId`
1166/// bound by the dispatching host (see [`crate::harness_tenant`]). No
1167/// host state — the methods consult a thread-local stack — but the
1168/// handle still rides the shared `Arc<HarnessInner>` so null/mock-mode
1169/// gating in [`crate::vm::methods::harness`] applies uniformly.
1170#[derive(Debug, Clone)]
1171pub struct HarnessTenant {
1172    inner: Arc<HarnessInner>,
1173}
1174
1175/// auth sub-handle: `is_authenticated`, `subject` / `try_subject`,
1176/// `scheme` / `try_scheme`, `kind`, `scopes`, `has_scope`. Surfaces the
1177/// ambient authenticated principal bound by the dispatching host (see
1178/// [`crate::harness_auth`]). Like [`HarnessTenant`] it holds no host
1179/// state — the methods consult a thread-local stack — but rides the
1180/// shared `Arc<HarnessInner>` so null/mock-mode gating in
1181/// [`crate::vm::methods::harness`] applies uniformly.
1182#[derive(Debug, Clone)]
1183pub struct HarnessAuth {
1184    inner: Arc<HarnessInner>,
1185}
1186
1187/// obs sub-handle: `span` / `start_span` / `end_span` / `counter` /
1188/// `histogram` / `gauge` / `log` / `request_id`. Wraps the existing
1189/// `__obs_*` builtins and the request_id ambient pushed by the
1190/// dispatching host (see [`crate::observability::request_id`]) behind a
1191/// typed surface so handlers don't reach into the lower-level builtins
1192/// directly. Backend selection / exporter wiring still lives in
1193/// [`crate::events`] (OTel sink) and `std/observability` (`configure`,
1194/// backend factories) — the sub-handle is the *emit-side* surface that
1195/// every harn-serve primitive shares.
1196#[derive(Debug, Clone)]
1197pub struct HarnessObs {
1198    inner: Arc<HarnessInner>,
1199}
1200
1201/// Per-harness deterministic fixture control. Responses and call records are
1202/// owned by this Harness instance rather than a process registry or
1203/// thread-local scope.
1204#[derive(Debug, Clone)]
1205pub struct HarnessTesting {
1206    inner: Arc<HarnessInner>,
1207}
1208
1209/// Durable memory sub-handle. Keeping this distinct from [`HarnessEmbed`]
1210/// prevents a caller that can compute vectors from implicitly gaining
1211/// persistent read/write authority.
1212#[derive(Debug, Clone)]
1213pub struct HarnessMemory {
1214    inner: Arc<HarnessInner>,
1215}
1216
1217#[derive(Debug, Clone)]
1218pub struct HarnessSqlite {
1219    inner: Arc<HarnessInner>,
1220}
1221
1222#[derive(Debug, Clone)]
1223pub struct HarnessPostgres {
1224    inner: Arc<HarnessInner>,
1225}
1226
1227#[derive(Debug, Clone)]
1228pub struct HarnessAgent {
1229    inner: Arc<HarnessInner>,
1230}
1231
1232macro_rules! sub_handle_inner {
1233    ($($ty:ty),* $(,)?) => {
1234        $(
1235            impl $ty {
1236                #[allow(dead_code)]
1237                pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1238                    &self.inner
1239                }
1240            }
1241        )*
1242    };
1243}
1244sub_handle_inner!(
1245    HarnessStdio,
1246    HarnessTerm,
1247    HarnessFs,
1248    HarnessEnv,
1249    HarnessRandom,
1250    HarnessNet,
1251    HarnessProcess,
1252    HarnessChannels,
1253    HarnessSystem,
1254    HarnessSecrets,
1255    HarnessLlm,
1256    HarnessTenant,
1257    HarnessAuth,
1258    HarnessObs,
1259    HarnessMemory,
1260    HarnessSqlite,
1261    HarnessPostgres,
1262    HarnessAgent,
1263    HarnessTesting,
1264);
1265
1266impl HarnessClock {
1267    #[allow(dead_code)]
1268    pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1269        &self.inner
1270    }
1271}