1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use OnceLock;
use ;
static HARN_STATE_LOCK: = new;
/// Process-global env vars that point harn_vm at a specific state dir
/// or flip the MCP serve auth posture. Any test that leaves these set
/// leaks state into subsequent tests:
/// - State-dir vars leak the previous test's (now-deleted) `TempDir`
/// path into `install_default_for_base_dir(base_dir)` because
/// `state_root()` / `event_log_*` resolvers honor an absolute env-var
/// value over the supplied `base_dir`.
/// - `HARN_MCP_OAUTH_*` vars flip `McpOrchestratorService::new_local`
/// into OAuth-required mode, so a test that constructs a service
/// while a previous OAuth test's env is still live receives 401 on
/// every unauthenticated request.
const LEAKY_STATE_ENV_VARS: & = &;
/// Clear the process-global env vars that leak state between tests. Run
/// on every lock acquisition so each test starts from a clean env
/// instead of inheriting a previous test's absolute state path.
/// Drop provider schemas contributed by earlier tests, leaving the
/// builtin catalog.
///
/// The provider catalog is one `static RwLock<ProviderCatalog>` for the
/// whole process, and loading a package *contributes* to it rather than
/// declaring it — that is deliberate, because an orchestrator harness
/// and a persona command sharing a process must not erase each other's
/// providers. In a test binary the same property means every package a
/// test loads stays registered for the rest of the run.
///
/// Two tests that use the same provider id for different payload
/// schemas therefore collide, and which one fails depends on execution
/// order. Resetting here, under the one lock, gives each test the
/// builtin catalog and nothing else.
/// The `tokio::sync::Mutex` backing both the sync and async acquire
/// paths. A `tokio` mutex (rather than `std::sync::Mutex`) lets async
/// tests hold the guard across `.await` points without tripping
/// `clippy::await_holding_lock`, while still offering a blocking acquire
/// for the handful of plain `#[test]` callers.
/// Whoever currently holds the lock, recorded so that a second acquire
/// from the same holder fails loudly instead of hanging.
///
/// A tokio task id is stable across worker threads, so it stays correct
/// under `flavor = "multi_thread"` where a thread id would not. Plain
/// `#[test]` callers run outside any runtime and get a thread id, which
/// is exact for them because each such test owns its thread.
static HOLDER: Mutex = new;
/// Panic if the caller already holds the lock.
///
/// Re-acquiring a non-reentrant mutex deadlocks, and a deadlocked test
/// binary reports nothing at all — it just stops, and the run has to be
/// killed by hand to learn anything. Reading `HOLDER` before we block is
/// sound: the only value that can name us is one we wrote ourselves, and
/// nobody else can write our name.
/// Guard for the process-global harn-state lock. Releasing it clears the
/// recorded holder, so the next acquire from the same task is legal
/// again.
/// Serialize plain `#[test]` callers that mutate harn_vm process-global
/// state. Async tests must use [`lock_harn_state_async`] instead —
/// `blocking_lock` panics when called from within a tokio runtime.
///
/// This is the *only* lock over the process environment in this crate.
/// It used to have a sibling, `env_lock`, and two mutexes over one
/// environment exclude nothing: a test holding one ran concurrently with
/// a test holding the other and clobbered its `HARN_STATE_DIR` /
/// `HARN_EVENT_LOG_*`. Do not reintroduce a second lock for the same
/// state; add the variable to [`LEAKY_STATE_ENV_VARS`] instead.
///
/// Covers:
/// - `HARN_STATE_DIR` and sibling env vars read by
/// `harn_vm::runtime_paths::state_root()` / `event_log_*`. The lock
/// helper unsets them on entry so each test starts from a clean env
/// instead of inheriting a previous test's absolute state path. No
/// production code writes them any more — `OrchestratorRole::build_vm()`
/// used to, and every test in the binary inherited its state dir — so
/// this now guards only tests that set them deliberately.
/// - The thread-local `ACTIVE_EVENT_LOG`, which is reused across
/// cargo test-thread handoffs.
/// - The process-global provider catalog contributed to by
/// `harn_vm::register_provider_schemas`, which every package load
/// goes through. The lock resets it on entry; see
/// [`reset_contributed_providers`].
///
/// The `harn_vm` trigger registry mutated by `install_manifest_triggers`
/// / `clear_trigger_registry` is *not* in that list. It is a
/// `thread_local!`, so a test owns its own; `reset_thread_local_state()`
/// clears it and needs no lock.
///
/// Tests grabbing this lock should not assume the rest of the global
/// state is clean on entry — call `reset_active_event_log()` as
/// applicable.
/// Async variant for `#[tokio::test]` callers that hold the state guard
/// across `.await`. Same env-clearing semantics as [`lock_harn_state`].
pub async