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            let paused = self
312                .override_clock
313                .lock()
314                .expect("harness clock override poisoned")
315                .clone();
316            if let Some(paused) = paused {
317                paused.advance(duration);
318                // Virtual sleep still represents a scheduling boundary. Give
319                // worker-thread children woken by the advance a chance to
320                // observe it before the caller performs its next snapshot.
321                tokio::task::yield_now().await;
322                return;
323            }
324        }
325        clock.sleep(duration).await;
326    }
327
328    async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
329        let clock = self.active();
330        if let Some(paused) = self
331            .override_clock
332            .lock()
333            .expect("harness clock override poisoned")
334            .clone()
335        {
336            let now = paused.now_utc();
337            if deadline > now {
338                paused.advance_time(deadline - now);
339            }
340            return;
341        }
342        clock.sleep_until_utc(deadline).await;
343    }
344}
345
346/// Shared, refcounted state backing every sub-handle of a single `Harness`.
347///
348/// Method implementations (in `crate::vm::methods::harness`) borrow this to
349/// reach the concrete OS-backed primitives. Wrapped in `Arc` so handles are
350/// `Send + Sync` for VM contexts that move work onto other tasks.
351pub struct HarnessInner {
352    clock: Arc<dyn Clock>,
353    clock_control: Arc<HarnessClockRouter>,
354    mode: HarnessMode,
355    /// Per-harness `harness.net.*` access policy. `None` means the
356    /// handle inherits the legacy unrestricted behaviour (subject to
357    /// the process-wide `crate::egress` allowlist, if configured).
358    /// See `Harness::with_net_policy` and `crate::harness_net`.
359    net_policy: Option<crate::harness_net::NetPolicy>,
360    /// Optional provider backing `harness.secrets.*`. Runtime embedders install
361    /// the managed provider that owns custody, audit, leases, and rotation.
362    secret_provider: Option<Arc<dyn crate::secrets::SecretProvider>>,
363    /// `true` once a request denied under `OnViolation::Quarantine`
364    /// has fired. Sticky for the lifetime of the underlying
365    /// `Arc<HarnessInner>` so downstream consumers can pin on the
366    /// signal even after the originating call has returned. The flag
367    /// is per-`Arc` (i.e. per-`Harness` build) so unrelated harnesses
368    /// stay independent.
369    quarantined: Mutex<bool>,
370    fixtures: Arc<CapabilityFixtureState>,
371}
372
373impl fmt::Debug for HarnessInner {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        f.debug_struct("HarnessInner")
376            .field("clock", &"<dyn Clock>")
377            .field("mode", &self.mode)
378            .field("net_policy", &self.net_policy)
379            .field(
380                "secret_provider",
381                &self
382                    .secret_provider
383                    .as_ref()
384                    .map(|provider| provider.namespace().to_string()),
385            )
386            .field("quarantined", &self.is_quarantined())
387            .field("fixtures", &self.fixtures)
388            .finish()
389    }
390}
391
392impl HarnessInner {
393    pub fn clock(&self) -> &Arc<dyn Clock> {
394        &self.clock
395    }
396
397    pub(crate) fn set_test_clock(&self, unix_ms: i64) -> Result<(), crate::VmError> {
398        self.clock_control.set_unix_ms(unix_ms)
399    }
400
401    pub(crate) fn advance_test_clock(&self, milliseconds: i64) -> Result<i64, crate::VmError> {
402        self.clock_control.advance_ms(milliseconds)
403    }
404
405    pub(crate) fn clear_test_clock(&self) {
406        self.clock_control.clear_override();
407    }
408
409    pub(crate) async fn wait_for_clock_advance(&self, duration: Duration) {
410        self.clock_control.wait_for_advance(duration).await;
411    }
412
413    pub(crate) fn mode(&self) -> &HarnessMode {
414        &self.mode
415    }
416
417    pub fn net_policy(&self) -> Option<&crate::harness_net::NetPolicy> {
418        self.net_policy.as_ref()
419    }
420
421    pub fn secret_provider(&self) -> Option<&Arc<dyn crate::secrets::SecretProvider>> {
422        self.secret_provider.as_ref()
423    }
424
425    pub(crate) fn mark_quarantined(&self) {
426        if let Ok(mut guard) = self.quarantined.lock() {
427            *guard = true;
428        }
429    }
430
431    pub fn is_quarantined(&self) -> bool {
432        self.quarantined.lock().map(|guard| *guard).unwrap_or(false)
433    }
434
435    pub(crate) fn fixtures(&self) -> &CapabilityFixtureState {
436        &self.fixtures
437    }
438
439    pub(crate) fn fixtures_arc(&self) -> Arc<CapabilityFixtureState> {
440        Arc::clone(&self.fixtures)
441    }
442}
443
444#[derive(Debug)]
445pub(crate) enum HarnessMode {
446    Real,
447    Null(NullHarnessState),
448    Mock(Arc<MockHarnessState>),
449}
450
451#[derive(Debug, Default)]
452pub(crate) struct NullHarnessState {
453    deny_events: Mutex<Vec<DenyEvent>>,
454}
455
456impl NullHarnessState {
457    pub(crate) fn record_deny(
458        &self,
459        sub_handle: HarnessKind,
460        method: &str,
461        args: &[crate::VmValue],
462    ) {
463        self.deny_events
464            .lock()
465            .expect("deny events poisoned")
466            .push(DenyEvent::new(
467                sub_handle,
468                method,
469                args.iter().map(crate::VmValue::display).collect(),
470            ));
471    }
472
473    pub(crate) fn deny_events(&self) -> Vec<DenyEvent> {
474        self.deny_events
475            .lock()
476            .expect("deny events poisoned")
477            .clone()
478    }
479}
480
481#[derive(Debug, Clone, PartialEq, Eq)]
482pub struct DenyEvent {
483    pub sub_handle: HarnessKind,
484    pub method: String,
485    pub args: Vec<String>,
486}
487
488impl DenyEvent {
489    fn new(sub_handle: HarnessKind, method: &str, args: Vec<String>) -> Self {
490        Self {
491            sub_handle,
492            method: method.to_string(),
493            args,
494        }
495    }
496}
497
498#[derive(Debug)]
499pub(crate) struct MockHarnessState {
500    calls: Mutex<Vec<HarnessCall>>,
501    clock: Arc<PausedClock>,
502    env: BTreeMap<String, String>,
503    fs_reads: BTreeMap<String, Vec<u8>>,
504    net_gets: BTreeMap<String, String>,
505    random_u64: Mutex<VecDeque<u64>>,
506    capability_responses:
507        Mutex<BTreeMap<(harn_builtin_meta::CapabilityId, String), VecDeque<crate::VmValue>>>,
508    stdin_lines: Mutex<VecDeque<String>>,
509    stdio: Mutex<String>,
510    stderr: Mutex<String>,
511}
512
513impl MockHarnessState {
514    pub(crate) fn record_call(
515        &self,
516        sub_handle: HarnessKind,
517        method: &str,
518        args: &[crate::VmValue],
519    ) {
520        self.calls
521            .lock()
522            .expect("calls poisoned")
523            .push(HarnessCall::new(
524                sub_handle,
525                method,
526                args.iter().map(crate::VmValue::display).collect(),
527            ));
528    }
529
530    pub(crate) fn calls(&self) -> Vec<HarnessCall> {
531        self.calls.lock().expect("calls poisoned").clone()
532    }
533
534    pub(crate) fn env_get(&self, key: &str) -> Option<&str> {
535        self.env.get(key).map(String::as_str)
536    }
537
538    pub(crate) fn fs_read(&self, path: &str) -> Option<&[u8]> {
539        self.fs_reads.get(path).map(Vec::as_slice)
540    }
541
542    pub(crate) fn net_get(&self, url: &str) -> Option<&str> {
543        self.net_gets.get(url).map(String::as_str)
544    }
545
546    pub(crate) fn next_random_u64(&self) -> Option<u64> {
547        let mut values = self.random_u64.lock().expect("random values poisoned");
548        values.pop_front()
549    }
550
551    pub(crate) fn capability_response(
552        &self,
553        capability: harn_builtin_meta::CapabilityId,
554        method: &str,
555    ) -> Option<crate::VmValue> {
556        self.capability_responses
557            .lock()
558            .expect("capability responses poisoned")
559            .get_mut(&(capability, method.to_string()))
560            .and_then(VecDeque::pop_front)
561    }
562
563    /// Whether a canned response is still queued for this capability method.
564    ///
565    /// `capability_response` consumes one response per call, so dispatch needs
566    /// this to decide whether the mock owns a method without spending the
567    /// answer it is deciding about.
568    pub(crate) fn has_capability_response(
569        &self,
570        capability: harn_builtin_meta::CapabilityId,
571        method: &str,
572    ) -> bool {
573        self.capability_responses
574            .lock()
575            .expect("capability responses poisoned")
576            .get(&(capability, method.to_string()))
577            .is_some_and(|queued| !queued.is_empty())
578    }
579
580    pub(crate) fn advance_clock(&self, duration: std::time::Duration) {
581        self.clock.advance(duration);
582    }
583
584    pub(crate) fn push_stdio(&self, text: &str) {
585        self.stdio
586            .lock()
587            .expect("stdio buffer poisoned")
588            .push_str(text);
589    }
590
591    pub(crate) fn stdio(&self) -> String {
592        self.stdio.lock().expect("stdio buffer poisoned").clone()
593    }
594
595    pub(crate) fn push_stderr(&self, text: &str) {
596        self.stderr
597            .lock()
598            .expect("stderr buffer poisoned")
599            .push_str(text);
600    }
601
602    pub(crate) fn stderr(&self) -> String {
603        self.stderr.lock().expect("stderr buffer poisoned").clone()
604    }
605
606    pub(crate) fn pop_stdin_line(&self) -> Option<String> {
607        self.stdin_lines
608            .lock()
609            .expect("stdin queue poisoned")
610            .pop_front()
611    }
612}
613
614#[derive(Debug, Clone, PartialEq, Eq)]
615pub struct HarnessCall {
616    pub sub_handle: HarnessKind,
617    pub method: String,
618    pub args: Vec<String>,
619}
620
621impl HarnessCall {
622    fn new(sub_handle: HarnessKind, method: &str, args: Vec<String>) -> Self {
623        Self {
624            sub_handle,
625            method: method.to_string(),
626            args,
627        }
628    }
629}
630
631#[derive(Debug)]
632pub struct MockHarnessBuilder {
633    clock: Arc<PausedClock>,
634    env: BTreeMap<String, String>,
635    fs_reads: BTreeMap<String, Vec<u8>>,
636    net_gets: BTreeMap<String, String>,
637    random_u64: Vec<u64>,
638    capability_responses: BTreeMap<(harn_builtin_meta::CapabilityId, String), Vec<crate::VmValue>>,
639    stdin_lines: Vec<String>,
640}
641
642impl MockHarnessBuilder {
643    fn new() -> Self {
644        Self {
645            clock: paused_clock_at_unix_ms(0),
646            env: BTreeMap::new(),
647            fs_reads: BTreeMap::new(),
648            net_gets: BTreeMap::new(),
649            random_u64: Vec::new(),
650            capability_responses: BTreeMap::new(),
651            stdin_lines: Vec::new(),
652        }
653    }
654
655    pub fn clock_at_unix_ms(mut self, unix_ms: i64) -> Self {
656        self.clock = paused_clock_at_unix_ms(unix_ms);
657        self
658    }
659
660    pub fn clock_at(mut self, origin: OffsetDateTime) -> Self {
661        self.clock = PausedClock::new(origin);
662        self
663    }
664
665    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
666        self.env.insert(key.into(), value.into());
667        self
668    }
669
670    pub fn fs_read(mut self, path: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
671        self.fs_reads.insert(path.into(), data.into());
672        self
673    }
674
675    pub fn net_get(mut self, url: impl Into<String>, body: impl Into<String>) -> Self {
676        self.net_gets.insert(url.into(), body.into());
677        self
678    }
679
680    pub fn random_u64(mut self, value: u64) -> Self {
681        self.random_u64.push(value);
682        self
683    }
684
685    /// Queue an exact return value for a capability method that does not have
686    /// a purpose-built fixture helper. Responses are consumed FIFO and belong
687    /// to this harness instance; no process registry or thread-local mock
688    /// state participates.
689    pub fn capability_response(
690        mut self,
691        capability: harn_builtin_meta::CapabilityId,
692        method: impl Into<String>,
693        value: crate::VmValue,
694    ) -> Self {
695        self.capability_responses
696            .entry((capability, method.into()))
697            .or_default()
698            .push(value);
699        self
700    }
701
702    /// Queue a line that `harness.stdio.read_line()` or
703    /// `harness.stdio.prompt(...)` will return next. Lines are dequeued
704    /// FIFO; once the queue is empty subsequent reads surface EOF
705    /// (`nil` for the unstructured form, `{ok: false, status: "eof"}`
706    /// for the structured form).
707    pub fn stdin_line(mut self, line: impl Into<String>) -> Self {
708        self.stdin_lines.push(line.into());
709        self
710    }
711
712    pub fn build(self) -> Harness {
713        let clock = self.clock;
714        Harness::with_mode(
715            clock.clone() as Arc<dyn Clock>,
716            HarnessMode::Mock(Arc::new(MockHarnessState {
717                calls: Mutex::new(Vec::new()),
718                clock,
719                env: self.env,
720                fs_reads: self.fs_reads,
721                net_gets: self.net_gets,
722                random_u64: Mutex::new(self.random_u64.into()),
723                capability_responses: Mutex::new(
724                    self.capability_responses
725                        .into_iter()
726                        .map(|(key, values)| (key, values.into()))
727                        .collect(),
728                ),
729                stdin_lines: Mutex::new(self.stdin_lines.into()),
730                stdio: Mutex::new(String::new()),
731                stderr: Mutex::new(String::new()),
732            })),
733        )
734    }
735}
736
737/// The runtime handle threaded into `main(harness: Harness)`.
738///
739/// Cheap to clone; sub-handles share the same `Arc` inner state.
740#[derive(Debug, Clone)]
741pub struct Harness {
742    inner: Arc<HarnessInner>,
743}
744
745impl Harness {
746    /// Build the production handle wired to wall-clock time.
747    ///
748    /// Test-time overrides are scoped to this handle and are reachable only
749    /// through `harness.testing`; production construction never consults
750    /// thread-local clock state.
751    pub fn real() -> Self {
752        Self::with_mode(Arc::new(RealClock::new()), HarnessMode::Real)
753    }
754
755    /// Build a deny-by-default test handle. Every sub-handle method records a
756    /// [`DenyEvent`] and fails with a categorized VM error.
757    pub fn null() -> Self {
758        Self::with_mode(
759            paused_clock_at_unix_ms(0) as Arc<dyn Clock>,
760            HarnessMode::Null(NullHarnessState::default()),
761        )
762    }
763
764    /// Build a record/replay test handle backed by a paused clock.
765    pub fn mock() -> MockHarnessBuilder {
766        MockHarnessBuilder::new()
767    }
768
769    /// Build a handle wired to a caller-supplied clock. Most callers want
770    /// [`Self::test`] (which constructs the `PausedClock` for you);
771    /// reach for this when an existing `Arc<dyn Clock>` is already in
772    /// hand — e.g. a `RecordedClock` wrapper.
773    pub fn with_clock(clock: Arc<dyn Clock>) -> Self {
774        Self::with_mode(clock, HarnessMode::Real)
775    }
776
777    /// Construct a `Harness` from a pre-built `Arc<HarnessInner>`.
778    /// Used by VM method dispatch when it needs to re-wrap a sub-handle's
779    /// inner state into a root `Harness` (e.g. to invoke
780    /// [`Self::with_net_policy`] from inside the method dispatcher).
781    pub fn from_inner(inner: Arc<HarnessInner>) -> Self {
782        Self { inner }
783    }
784
785    fn with_mode(clock: Arc<dyn Clock>, mode: HarnessMode) -> Self {
786        let clock_control = Arc::new(HarnessClockRouter::new(clock));
787        let clock: Arc<dyn Clock> = clock_control.clone();
788        let inner = Arc::new(HarnessInner {
789            clock,
790            clock_control,
791            mode,
792            net_policy: None,
793            secret_provider: None,
794            quarantined: Mutex::new(false),
795            fixtures: Arc::new(CapabilityFixtureState::default()),
796        });
797        Self { inner }
798    }
799
800    /// Attach a per-harness `harness.net.*` access policy.
801    ///
802    /// Returns a new `Harness` value whose sub-handles share a fresh
803    /// `Arc<HarnessInner>`. Existing handles built off the prior inner
804    /// keep operating without the policy — so calling
805    /// `harness.with_net_policy(...)` does NOT retroactively gate
806    /// references to `harness` held elsewhere. Per issue #1913.
807    ///
808    /// The clock and mode are propagated verbatim. Mock canned
809    /// responses (`net_gets`, `random_u64`, etc.) live behind the
810    /// shared `HarnessMode::Mock` payload, so the new handle observes
811    /// the same recorded calls and the same canned responses as the
812    /// source handle.
813    pub fn with_net_policy(&self, policy: crate::harness_net::NetPolicy) -> Self {
814        let clock = Arc::clone(&self.inner.clock);
815        let clock_control = Arc::clone(&self.inner.clock_control);
816        let mode = self.clone_mode_for_child();
817        // See `with_mode` for the rationale on this suppression.
818        #[allow(clippy::arc_with_non_send_sync)]
819        let inner = Arc::new(HarnessInner {
820            clock,
821            clock_control,
822            mode,
823            net_policy: Some(policy),
824            secret_provider: self.inner.secret_provider.clone(),
825            quarantined: Mutex::new(self.is_quarantined()),
826            fixtures: Arc::clone(&self.inner.fixtures),
827        });
828        Self { inner }
829    }
830
831    /// Return the provider attached to `harness.secrets.*`, when present.
832    pub fn secret_provider(&self) -> Option<&Arc<dyn crate::secrets::SecretProvider>> {
833        self.inner.secret_provider()
834    }
835
836    /// Attach a provider for `harness.secrets.*`.
837    ///
838    /// The provider is intentionally embedder-supplied. Harn owns the typed
839    /// method contract; the host owns custody details such as KMS wrapping,
840    /// lease storage, audit sinks, and scope policy.
841    pub fn with_secret_provider(&self, provider: Arc<dyn crate::secrets::SecretProvider>) -> Self {
842        let clock = Arc::clone(&self.inner.clock);
843        let clock_control = Arc::clone(&self.inner.clock_control);
844        let mode = self.clone_mode_for_child();
845        #[allow(clippy::arc_with_non_send_sync)]
846        let inner = Arc::new(HarnessInner {
847            clock,
848            clock_control,
849            mode,
850            net_policy: self.inner.net_policy.clone(),
851            secret_provider: Some(provider),
852            quarantined: Mutex::new(self.is_quarantined()),
853            fixtures: Arc::clone(&self.inner.fixtures),
854        });
855        Self { inner }
856    }
857
858    fn clone_mode_for_child(&self) -> HarnessMode {
859        match &self.inner.mode {
860            HarnessMode::Real => HarnessMode::Real,
861            HarnessMode::Null(_) => HarnessMode::Null(NullHarnessState::default()),
862            HarnessMode::Mock(state) => HarnessMode::Mock(Arc::clone(state)),
863        }
864    }
865
866    /// `true` if the harness has been marked quarantined by an
867    /// `OnViolation::Quarantine` deny event.
868    pub fn is_quarantined(&self) -> bool {
869        self.inner.is_quarantined()
870    }
871
872    pub fn deny_events(&self) -> Vec<DenyEvent> {
873        match self.inner.mode() {
874            HarnessMode::Null(state) => state.deny_events(),
875            HarnessMode::Real | HarnessMode::Mock(_) => Vec::new(),
876        }
877    }
878
879    pub fn calls(&self) -> Vec<HarnessCall> {
880        match self.inner.mode() {
881            HarnessMode::Mock(state) => state.calls(),
882            HarnessMode::Real | HarnessMode::Null(_) => Vec::new(),
883        }
884    }
885
886    pub fn captured_stdio(&self) -> String {
887        match self.inner.mode() {
888            HarnessMode::Mock(state) => state.stdio(),
889            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
890        }
891    }
892
893    pub fn captured_stderr(&self) -> String {
894        match self.inner.mode() {
895            HarnessMode::Mock(state) => state.stderr(),
896            HarnessMode::Real | HarnessMode::Null(_) => String::new(),
897        }
898    }
899
900    /// Build a deterministic test handle wired to a fresh
901    /// [`PausedClock`] pinned at the Unix epoch.
902    ///
903    /// Returns the harness paired with the underlying `PausedClock` so
904    /// tests can drive virtual time through `PausedClock::advance`
905    /// while passing the same `Harness` value into the VM. The two
906    /// share the underlying `Arc<dyn Clock>`, so the harness reflects
907    /// every advance immediately.
908    ///
909    /// Pairs with [`PausedClock::advance`] / [`PausedClock::set`] — see
910    /// [`Self::with_paused_clock`] for picking a non-epoch origin.
911    pub fn test() -> (Self, Arc<PausedClock>) {
912        Self::with_paused_clock(OffsetDateTime::UNIX_EPOCH)
913    }
914
915    /// Like [`Self::test`], but pins the paused clock's wall origin to
916    /// `origin`. Lets tests anchor virtual time to a meaningful date
917    /// without manually advancing past the epoch first.
918    pub fn with_paused_clock(origin: OffsetDateTime) -> (Self, Arc<PausedClock>) {
919        let paused = PausedClock::new(origin);
920        let as_dyn: Arc<dyn Clock> = paused.clone();
921        (Self::with_clock(as_dyn), paused)
922    }
923
924    /// Field access for `harness.stdio`.
925    pub fn stdio(&self) -> HarnessStdio {
926        HarnessStdio {
927            inner: Arc::clone(&self.inner),
928        }
929    }
930
931    /// Field access for `harness.term`.
932    pub fn term(&self) -> HarnessTerm {
933        HarnessTerm {
934            inner: Arc::clone(&self.inner),
935        }
936    }
937
938    /// Field access for `harness.clock`.
939    pub fn clock(&self) -> HarnessClock {
940        HarnessClock {
941            inner: Arc::clone(&self.inner),
942        }
943    }
944
945    /// Field access for `harness.fs`.
946    pub fn fs(&self) -> HarnessFs {
947        HarnessFs {
948            inner: Arc::clone(&self.inner),
949        }
950    }
951
952    /// Field access for `harness.env`.
953    pub fn env(&self) -> HarnessEnv {
954        HarnessEnv {
955            inner: Arc::clone(&self.inner),
956        }
957    }
958
959    /// Field access for `harness.random`.
960    pub fn random(&self) -> HarnessRandom {
961        HarnessRandom {
962            inner: Arc::clone(&self.inner),
963        }
964    }
965
966    /// Field access for `harness.net`.
967    pub fn net(&self) -> HarnessNet {
968        HarnessNet {
969            inner: Arc::clone(&self.inner),
970        }
971    }
972
973    /// Field access for `harness.process`.
974    pub fn process(&self) -> HarnessProcess {
975        HarnessProcess {
976            inner: Arc::clone(&self.inner),
977        }
978    }
979
980    /// Field access for `harness.channels`.
981    pub fn channels(&self) -> HarnessChannels {
982        HarnessChannels {
983            inner: Arc::clone(&self.inner),
984        }
985    }
986
987    /// Field access for `harness.system`.
988    pub fn system(&self) -> HarnessSystem {
989        HarnessSystem {
990            inner: Arc::clone(&self.inner),
991        }
992    }
993
994    /// Field access for `harness.secrets`.
995    pub fn secrets(&self) -> HarnessSecrets {
996        HarnessSecrets {
997            inner: Arc::clone(&self.inner),
998        }
999    }
1000
1001    /// Field access for `harness.llm`.
1002    pub fn llm(&self) -> HarnessLlm {
1003        HarnessLlm {
1004            inner: Arc::clone(&self.inner),
1005        }
1006    }
1007
1008    /// Field access for `harness.tenant`.
1009    pub fn tenant(&self) -> HarnessTenant {
1010        HarnessTenant {
1011            inner: Arc::clone(&self.inner),
1012        }
1013    }
1014
1015    /// Field access for `harness.auth`.
1016    pub fn auth(&self) -> HarnessAuth {
1017        HarnessAuth {
1018            inner: Arc::clone(&self.inner),
1019        }
1020    }
1021
1022    /// Field access for `harness.obs`.
1023    pub fn obs(&self) -> HarnessObs {
1024        HarnessObs {
1025            inner: Arc::clone(&self.inner),
1026        }
1027    }
1028
1029    /// Field access for `harness.testing`.
1030    pub fn testing(&self) -> HarnessTesting {
1031        HarnessTesting {
1032            inner: Arc::clone(&self.inner),
1033        }
1034    }
1035
1036    /// Field access for `harness.memory`.
1037    pub fn memory(&self) -> HarnessMemory {
1038        HarnessMemory {
1039            inner: Arc::clone(&self.inner),
1040        }
1041    }
1042
1043    pub fn sqlite(&self) -> HarnessSqlite {
1044        HarnessSqlite {
1045            inner: Arc::clone(&self.inner),
1046        }
1047    }
1048
1049    pub fn postgres(&self) -> HarnessPostgres {
1050        HarnessPostgres {
1051            inner: Arc::clone(&self.inner),
1052        }
1053    }
1054
1055    pub fn agent(&self) -> HarnessAgent {
1056        HarnessAgent {
1057            inner: Arc::clone(&self.inner),
1058        }
1059    }
1060
1061    /// Lower this handle into the `VmValue::Harness` payload.
1062    pub fn into_vm_value(self) -> crate::value::VmValue {
1063        crate::value::VmValue::harness(VmHarness {
1064            inner: self.inner,
1065            kind: HarnessKind::Root,
1066        })
1067    }
1068}
1069
1070fn paused_clock_at_unix_ms(unix_ms: i64) -> Arc<PausedClock> {
1071    let nanos = (unix_ms as i128).saturating_mul(1_000_000);
1072    let origin =
1073        OffsetDateTime::from_unix_timestamp_nanos(nanos).unwrap_or(OffsetDateTime::UNIX_EPOCH);
1074    PausedClock::new(origin)
1075}
1076
1077pub(crate) fn vm_string(value: impl Into<String>) -> crate::VmValue {
1078    crate::VmValue::String(arcstr::ArcStr::from(value.into()))
1079}
1080
1081impl Default for Harness {
1082    fn default() -> Self {
1083        Self::real()
1084    }
1085}
1086
1087/// stdio sub-handle: `print`, `println`, `eprint`, `eprintln`, `prompt`,
1088/// `read_line`.
1089#[derive(Debug, Clone)]
1090pub struct HarnessStdio {
1091    inner: Arc<HarnessInner>,
1092}
1093
1094/// term sub-handle: `width`, `height`, `read_password`.
1095#[derive(Debug, Clone)]
1096pub struct HarnessTerm {
1097    inner: Arc<HarnessInner>,
1098}
1099
1100/// clock sub-handle: `now`, `monotonic_now`, `sleep`.
1101#[derive(Debug, Clone)]
1102pub struct HarnessClock {
1103    inner: Arc<HarnessInner>,
1104}
1105
1106impl HarnessClock {
1107    pub fn clock(&self) -> &Arc<dyn Clock> {
1108        self.inner.clock()
1109    }
1110}
1111
1112include!("harness/fixtures.rs");
1113include!("harness/value.rs");
1114include!("harness/tests.rs");
1115/// fs sub-handle: `read_file`, `write_file`, `exists`, `list_dir`,
1116/// `delete_file`, ...
1117#[derive(Debug, Clone)]
1118pub struct HarnessFs {
1119    inner: Arc<HarnessInner>,
1120}
1121
1122/// env sub-handle: `get`, `set`, `vars`.
1123#[derive(Debug, Clone)]
1124pub struct HarnessEnv {
1125    inner: Arc<HarnessInner>,
1126}
1127
1128/// random sub-handle: `u64`, `range`, `f64`, ...
1129#[derive(Debug, Clone)]
1130pub struct HarnessRandom {
1131    inner: Arc<HarnessInner>,
1132}
1133
1134/// net sub-handle: `http_get`, `http_post`, ...
1135#[derive(Debug, Clone)]
1136pub struct HarnessNet {
1137    inner: Arc<HarnessInner>,
1138}
1139
1140/// process sub-handle: structured process execution and shell discovery.
1141#[derive(Debug, Clone)]
1142pub struct HarnessProcess {
1143    inner: Arc<HarnessInner>,
1144}
1145
1146/// Durable transcript channel sub-handle.
1147#[derive(Debug, Clone)]
1148pub struct HarnessChannels {
1149    inner: Arc<HarnessInner>,
1150}
1151
1152/// system sub-handle: `cpu`, `memory`, `gpus`, `temperature`, `platform`,
1153/// `processes`. Read-only host introspection — no side effects on the host
1154/// system. Gated by the harness handle so scripts running under
1155/// `Harness::null()` or restricted policies cannot fingerprint the runner
1156/// without an explicit grant (issue #1912 / epic #1765).
1157#[derive(Debug, Clone)]
1158pub struct HarnessSystem {
1159    inner: Arc<HarnessInner>,
1160}
1161
1162/// secrets sub-handle: `read`, `write`, `rotate`, `lease`.
1163#[derive(Debug, Clone)]
1164pub struct HarnessSecrets {
1165    inner: Arc<HarnessInner>,
1166}
1167
1168/// llm sub-handle: `catalog`, `providers`.
1169#[derive(Debug, Clone)]
1170pub struct HarnessLlm {
1171    inner: Arc<HarnessInner>,
1172}
1173
1174/// tenant sub-handle: `id`, `try_id`. Surfaces the ambient `TenantId`
1175/// bound by the dispatching host (see [`crate::harness_tenant`]). No
1176/// host state — the methods consult a thread-local stack — but the
1177/// handle still rides the shared `Arc<HarnessInner>` so null/mock-mode
1178/// gating in [`crate::vm::methods::harness`] applies uniformly.
1179#[derive(Debug, Clone)]
1180pub struct HarnessTenant {
1181    inner: Arc<HarnessInner>,
1182}
1183
1184/// auth sub-handle: `is_authenticated`, `subject` / `try_subject`,
1185/// `scheme` / `try_scheme`, `kind`, `scopes`, `has_scope`. Surfaces the
1186/// ambient authenticated principal bound by the dispatching host (see
1187/// [`crate::harness_auth`]). Like [`HarnessTenant`] it holds no host
1188/// state — the methods consult a thread-local stack — but rides the
1189/// shared `Arc<HarnessInner>` so null/mock-mode gating in
1190/// [`crate::vm::methods::harness`] applies uniformly.
1191#[derive(Debug, Clone)]
1192pub struct HarnessAuth {
1193    inner: Arc<HarnessInner>,
1194}
1195
1196/// obs sub-handle: `span` / `start_span` / `end_span` / `counter` /
1197/// `histogram` / `gauge` / `log` / `request_id`. Wraps the existing
1198/// `__obs_*` builtins and the request_id ambient pushed by the
1199/// dispatching host (see [`crate::observability::request_id`]) behind a
1200/// typed surface so handlers don't reach into the lower-level builtins
1201/// directly. Backend selection / exporter wiring still lives in
1202/// [`crate::events`] (OTel sink) and `std/observability` (`configure`,
1203/// backend factories) — the sub-handle is the *emit-side* surface that
1204/// every harn-serve primitive shares.
1205#[derive(Debug, Clone)]
1206pub struct HarnessObs {
1207    inner: Arc<HarnessInner>,
1208}
1209
1210/// Per-harness deterministic fixture control. Responses and call records are
1211/// owned by this Harness instance rather than a process registry or
1212/// thread-local scope.
1213#[derive(Debug, Clone)]
1214pub struct HarnessTesting {
1215    inner: Arc<HarnessInner>,
1216}
1217
1218/// Durable memory sub-handle. Keeping this distinct from [`HarnessEmbed`]
1219/// prevents a caller that can compute vectors from implicitly gaining
1220/// persistent read/write authority.
1221#[derive(Debug, Clone)]
1222pub struct HarnessMemory {
1223    inner: Arc<HarnessInner>,
1224}
1225
1226#[derive(Debug, Clone)]
1227pub struct HarnessSqlite {
1228    inner: Arc<HarnessInner>,
1229}
1230
1231#[derive(Debug, Clone)]
1232pub struct HarnessPostgres {
1233    inner: Arc<HarnessInner>,
1234}
1235
1236#[derive(Debug, Clone)]
1237pub struct HarnessAgent {
1238    inner: Arc<HarnessInner>,
1239}
1240
1241macro_rules! sub_handle_inner {
1242    ($($ty:ty),* $(,)?) => {
1243        $(
1244            impl $ty {
1245                #[allow(dead_code)]
1246                pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1247                    &self.inner
1248                }
1249            }
1250        )*
1251    };
1252}
1253sub_handle_inner!(
1254    HarnessStdio,
1255    HarnessTerm,
1256    HarnessFs,
1257    HarnessEnv,
1258    HarnessRandom,
1259    HarnessNet,
1260    HarnessProcess,
1261    HarnessChannels,
1262    HarnessSystem,
1263    HarnessSecrets,
1264    HarnessLlm,
1265    HarnessTenant,
1266    HarnessAuth,
1267    HarnessObs,
1268    HarnessMemory,
1269    HarnessSqlite,
1270    HarnessPostgres,
1271    HarnessAgent,
1272    HarnessTesting,
1273);
1274
1275impl HarnessClock {
1276    #[allow(dead_code)]
1277    pub(crate) fn inner(&self) -> &Arc<HarnessInner> {
1278        &self.inner
1279    }
1280}