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