1use 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#[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 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 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 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 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 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#[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
312pub struct HarnessInner {
318 clock: Arc<dyn Clock>,
319 clock_control: Arc<HarnessClockRouter>,
320 mode: HarnessMode,
321 net_policy: Option<crate::harness_net::NetPolicy>,
326 secret_provider: Option<Arc<dyn crate::secrets::SecretProvider>>,
329 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
449pub(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 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 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#[derive(Debug, Clone)]
920pub struct Harness {
921 inner: Arc<HarnessInner>,
922}
923
924impl Harness {
925 pub fn real() -> Self {
931 Self::with_mode(Arc::new(RealClock::new()), HarnessMode::Real)
932 }
933
934 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 pub fn mock() -> MockHarnessBuilder {
945 MockHarnessBuilder::new()
946 }
947
948 pub fn with_clock(clock: Arc<dyn Clock>) -> Self {
953 Self::with_mode(clock, HarnessMode::Real)
954 }
955
956 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 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 #[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 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 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 pub fn test() -> (Self, Arc<PausedClock>) {
1086 Self::with_paused_clock(OffsetDateTime::UNIX_EPOCH)
1087 }
1088
1089 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 pub fn stdio(&self) -> HarnessStdio {
1100 HarnessStdio {
1101 inner: Arc::clone(&self.inner),
1102 }
1103 }
1104
1105 pub fn term(&self) -> HarnessTerm {
1107 HarnessTerm {
1108 inner: Arc::clone(&self.inner),
1109 }
1110 }
1111
1112 pub fn clock(&self) -> HarnessClock {
1114 HarnessClock {
1115 inner: Arc::clone(&self.inner),
1116 }
1117 }
1118
1119 pub fn fs(&self) -> HarnessFs {
1121 HarnessFs {
1122 inner: Arc::clone(&self.inner),
1123 }
1124 }
1125
1126 pub fn env(&self) -> HarnessEnv {
1128 HarnessEnv {
1129 inner: Arc::clone(&self.inner),
1130 }
1131 }
1132
1133 pub fn random(&self) -> HarnessRandom {
1135 HarnessRandom {
1136 inner: Arc::clone(&self.inner),
1137 }
1138 }
1139
1140 pub fn net(&self) -> HarnessNet {
1142 HarnessNet {
1143 inner: Arc::clone(&self.inner),
1144 }
1145 }
1146
1147 pub fn process(&self) -> HarnessProcess {
1149 HarnessProcess {
1150 inner: Arc::clone(&self.inner),
1151 }
1152 }
1153
1154 pub fn channels(&self) -> HarnessChannels {
1156 HarnessChannels {
1157 inner: Arc::clone(&self.inner),
1158 }
1159 }
1160
1161 pub fn system(&self) -> HarnessSystem {
1163 HarnessSystem {
1164 inner: Arc::clone(&self.inner),
1165 }
1166 }
1167
1168 pub fn secrets(&self) -> HarnessSecrets {
1170 HarnessSecrets {
1171 inner: Arc::clone(&self.inner),
1172 }
1173 }
1174
1175 pub fn llm(&self) -> HarnessLlm {
1177 HarnessLlm {
1178 inner: Arc::clone(&self.inner),
1179 }
1180 }
1181
1182 pub fn tenant(&self) -> HarnessTenant {
1184 HarnessTenant {
1185 inner: Arc::clone(&self.inner),
1186 }
1187 }
1188
1189 pub fn auth(&self) -> HarnessAuth {
1191 HarnessAuth {
1192 inner: Arc::clone(&self.inner),
1193 }
1194 }
1195
1196 pub fn obs(&self) -> HarnessObs {
1198 HarnessObs {
1199 inner: Arc::clone(&self.inner),
1200 }
1201 }
1202
1203 pub fn testing(&self) -> HarnessTesting {
1205 HarnessTesting {
1206 inner: Arc::clone(&self.inner),
1207 }
1208 }
1209
1210 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 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#[derive(Debug, Clone)]
1264pub struct HarnessStdio {
1265 inner: Arc<HarnessInner>,
1266}
1267
1268#[derive(Debug, Clone)]
1270pub struct HarnessTerm {
1271 inner: Arc<HarnessInner>,
1272}
1273
1274#[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#[derive(Debug, Clone)]
1291pub struct HarnessFs {
1292 inner: Arc<HarnessInner>,
1293}
1294
1295#[derive(Debug, Clone)]
1297pub struct HarnessEnv {
1298 inner: Arc<HarnessInner>,
1299}
1300
1301#[derive(Debug, Clone)]
1303pub struct HarnessRandom {
1304 inner: Arc<HarnessInner>,
1305}
1306
1307#[derive(Debug, Clone)]
1309pub struct HarnessNet {
1310 inner: Arc<HarnessInner>,
1311}
1312
1313#[derive(Debug, Clone)]
1315pub struct HarnessProcess {
1316 inner: Arc<HarnessInner>,
1317}
1318
1319#[derive(Debug, Clone)]
1321pub struct HarnessChannels {
1322 inner: Arc<HarnessInner>,
1323}
1324
1325#[derive(Debug, Clone)]
1331pub struct HarnessSystem {
1332 inner: Arc<HarnessInner>,
1333}
1334
1335#[derive(Debug, Clone)]
1337pub struct HarnessSecrets {
1338 inner: Arc<HarnessInner>,
1339}
1340
1341#[derive(Debug, Clone)]
1343pub struct HarnessLlm {
1344 inner: Arc<HarnessInner>,
1345}
1346
1347#[derive(Debug, Clone)]
1353pub struct HarnessTenant {
1354 inner: Arc<HarnessInner>,
1355}
1356
1357#[derive(Debug, Clone)]
1365pub struct HarnessAuth {
1366 inner: Arc<HarnessInner>,
1367}
1368
1369#[derive(Debug, Clone)]
1379pub struct HarnessObs {
1380 inner: Arc<HarnessInner>,
1381}
1382
1383#[derive(Debug, Clone)]
1387pub struct HarnessTesting {
1388 inner: Arc<HarnessInner>,
1389}
1390
1391#[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}