agentmux_srv/registry/paths.rs
1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Resolve the shared registry directory (`<shared_home>/agents/registry/`).
5
6use std::path::PathBuf;
7
8/// Resolve the GLOBAL `<home>/shared/agents/registry/` directory.
9///
10/// P0.3 re-roots the named-**instance** registry from the old
11/// channel-scoped `channels/<ch>/agents/registry/` (which walked
12/// `AGENTMUX_DATA_DIR` up three levels and so landed inside the current
13/// channel) to the channel-independent `~/.agentmux/shared/`. An agent
14/// named in one channel is then visible in every channel, exactly like the
15/// definition store ([`resolve_shared_definitions_dir`]) — the two are now
16/// siblings under `shared/agents/`.
17///
18/// The base directory that instance `working_directory` values are stored
19/// *relative to* is tracked separately (the Store's `registry_agents_base`,
20/// fed from `AGENTMUX_AGENTS_DIR`) — it must stay the current channel's
21/// agents dir even though the registry files are now global.
22///
23/// Returns `None` only if the global shared root can't be resolved — caller
24/// treats this as "registry disabled," no write attempts.
25pub fn resolve_shared_registry_dir() -> Option<PathBuf> {
26 resolve_global_shared_root().map(|h| h.join("agents").join("registry"))
27}
28
29/// Resolve the GLOBAL `<home>/shared/agents/definitions/` directory.
30///
31/// Sibling of [`resolve_shared_registry_dir`]: since P0.3 both the definition
32/// store and the instance registry are **channel-independent**, resolved via
33/// the same [`resolve_global_shared_root`] so an agent created/named in one
34/// channel is visible in every channel (cross-channel agent persistence,
35/// P0.2/P0.3). Resolves via the launcher-exported `AGENTMUX_SHARED_DIR`, with
36/// a test override and a `~/.agentmux/shared` fallback.
37pub fn resolve_shared_definitions_dir() -> Option<PathBuf> {
38 resolve_global_shared_root().map(|h| h.join("agents").join("definitions"))
39}
40
41/// Resolve `~/.agentmux/shared/store.db` — the global store for
42/// accounts, bundles, drone definitions, and MuxBus credentials.
43///
44/// Uses the same root as the agent registry/definitions so
45/// `AGENTMUX_HOME_OVERRIDE` and `AGENTMUX_SHARED_DIR` work consistently.
46/// Returns `None` only when the shared root itself can't be resolved.
47///
48/// When `agentmux_common::isolated_auth_enabled()` is set, resolves to
49/// `<instance_dir>/identity-store.db` instead — a channel-scoped store for
50/// destructive account testing (delete-account flows) that can never touch
51/// the real global store other channels/instances depend on. Falls back
52/// to the global path if `AGENTMUX_INSTANCE_DIR` isn't set (e.g. a bare
53/// `cargo run` outside the launcher) rather than failing outright — this
54/// function's existing `None`-on-unresolvable contract is preserved, just
55/// with an extra source to try first. See
56/// `docs/specs/SPEC_ISOLATED_AUTH_DEV_TESTING_2026_07_27.md`.
57///
58/// IMPORTANT: `MigrationContext.home` (`migrations/runner.rs`) must NEVER
59/// be derived from this function's return value (e.g. via
60/// `.parent().parent()`) — it needs the unconditional global root
61/// regardless of isolation, and must call `resolve_global_shared_root()`
62/// directly instead. See that module's comments.
63pub fn resolve_shared_store_path() -> Option<std::path::PathBuf> {
64 if agentmux_common::isolated_auth_enabled() {
65 if let Some(instance_dir) = std::env::var_os("AGENTMUX_INSTANCE_DIR") {
66 if !instance_dir.is_empty() {
67 return Some(std::path::PathBuf::from(instance_dir).join("identity-store.db"));
68 }
69 }
70 }
71 resolve_global_shared_root().map(|h| h.join("store.db"))
72}
73
74/// Resolve the GLOBAL `<home>/shared/agents/reactive/` directory — the
75/// cross-channel presence registry for live reactive-injection targets
76/// (agent_id -> local_url/block_id/pid/auth_key, one entry per channel),
77/// used by MuxBus Tier 2b same-host cross-channel delivery
78/// (issue #1916 / PR #2350) and by agent admission's other-instance probe.
79///
80/// Sibling of [`resolve_shared_registry_dir`] in *location* (both live
81/// under `shared/agents/`), but a different *concern*: that registry
82/// mirrors durable agent definitions/session-resume state, while this one
83/// tracks live, ephemeral reachability for message forwarding — entries
84/// here are written at reactive-register time and removed at unregister;
85/// any a crashed channel leaves behind are dropped by the startup sweep.
86///
87/// Returns `None` only if the global shared root can't be resolved —
88/// caller treats this as "cross-channel delivery disabled," falling back
89/// to same-channel-only Tier 2 (no behavior regression).
90pub fn resolve_shared_reactive_dir() -> Option<PathBuf> {
91 resolve_global_shared_root().map(|h| h.join("agents").join("reactive"))
92}
93
94/// Resolve the GLOBAL `<home>/shared/lan/instances/` directory: one file per
95/// LAN-enabled AgentMux instance on this host, so whichever instance holds
96/// the UDP probe port can list the others as `siblings`
97/// (SPEC_LAN_FLEET_FEED_2026_10_03.md §3). Beside `agents/`, not inside the
98/// reactive registry: these are instances, not agents.
99pub fn resolve_shared_lan_instances_dir() -> Option<PathBuf> {
100 resolve_global_shared_root().map(|h| h.join("lan").join("instances"))
101}
102
103/// Resolve the GLOBAL `<home>/shared/agents/transcripts/` directory.
104///
105/// Sibling of [`resolve_shared_registry_dir`] / [`resolve_shared_definitions_dir`]:
106/// the agent's *conversation transcript* is the last per-channel agent surface
107/// (definitions, instances, workspaces, and auth all became global in
108/// #1387–#1396). Backing the `agent:<defId>:current` FileStore zone with a store
109/// rooted here makes a conversation load when you open the agent from *any*
110/// build/channel — the open path reads the same zone regardless of which channel
111/// wrote it. See `docs/analysis/ANALYSIS_CROSS_CHANNEL_CONVERSATION_HISTORY_2026_06_14.md`.
112///
113/// Returns `None` only if the global shared root can't be resolved — caller
114/// treats this as "global transcripts disabled" and falls back to the
115/// per-channel store.
116pub fn resolve_shared_transcripts_dir() -> Option<PathBuf> {
117 resolve_global_shared_root().map(|h| h.join("agents").join("transcripts"))
118}
119
120/// Resolve `~/.agentmux/shared/identity-store.db` — the permanently-global
121/// store for agent→account links, bundles, drone definitions,
122/// muxbus credentials, per-agent M2M credentials, native memory, and cron
123/// jobs. See `docs/specs/SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md`.
124///
125/// Unlike [`resolve_shared_store_path`], this is **never** redirected by
126/// [`agentmux_common::isolated_auth_enabled`] — none of the tables this
127/// store holds have a legitimate per-channel-isolation use case (that
128/// audit is §2.3 of the design doc above). Uses the same
129/// `AGENTMUX_HOME_OVERRIDE`/`AGENTMUX_SHARED_DIR` resolution as every
130/// other `resolve_shared_*` function in this file, via
131/// [`resolve_global_shared_root`], so tests/overrides behave consistently.
132/// Returns `None` only when the shared root itself can't be resolved.
133pub fn resolve_identity_store_path() -> Option<std::path::PathBuf> {
134 resolve_global_shared_root().map(|h| h.join("identity-store.db"))
135}
136
137/// The global, channel-independent `<home>/shared` root. Deliberately
138/// UNAFFECTED by `isolated_auth_enabled()` — callers that need the true
139/// `~/.agentmux` home (e.g. `migrations/runner.rs`'s `MigrationContext.home`)
140/// must call this directly rather than deriving it from
141/// `resolve_shared_store_path()`, which DOES vary with isolation.
142pub(crate) fn resolve_global_shared_root() -> Option<PathBuf> {
143 // `var_os` throughout: a path that isn't valid UTF-8 must not read as unset.
144 if let Some(s) = std::env::var_os("AGENTMUX_HOME_OVERRIDE") {
145 if !s.is_empty() {
146 // Consistent with data_paths everywhere else: the override is the
147 // ~/.agentmux root, with the shared dir at root/shared. (reagent
148 // P2 on #1385.)
149 return Some(PathBuf::from(s).join("shared"));
150 }
151 }
152 if let Some(s) = std::env::var_os("AGENTMUX_SHARED_DIR") {
153 if !s.is_empty() {
154 return Some(PathBuf::from(s));
155 }
156 }
157 // Fallible on purpose: this function documents "Returns None only when
158 // the shared root itself can't be resolved", and callers across
159 // migrations/runner, bootstrap, reactive, muxspect and native-memory rely
160 // on that None to disable cleanly ("No shared root (CI, a bare cargo
161 // run) — nothing to re-key"). An infallible resolver made every one of
162 // those branches unreachable.
163 agentmux_common::data_paths::agentmux_root().ok().map(|root| root.join("shared"))
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 // Process-global env access — shared with migrations::runner and
171 // migrations::m0011_shared_store_backfill's tests, which mutate the SAME
172 // AGENTMUX_ISOLATED_AUTH/AGENTMUX_INSTANCE_DIR vars. A module-local lock
173 // only serializes tests within this file; Cargo runs a crate's tests in
174 // one multi-threaded process, so a local-only lock still let this
175 // module's tests race against those two (reagent/codex on PR #2318).
176 use crate::test_support::ISOLATED_AUTH_ENV_LOCK as ENV_LOCK;
177
178 fn clear() {
179 std::env::remove_var("AGENTMUX_HOME_OVERRIDE");
180 std::env::remove_var("AGENTMUX_DATA_DIR");
181 std::env::remove_var("AGENTMUX_SHARED_DIR");
182 std::env::remove_var("AGENTMUX_ISOLATED_AUTH");
183 std::env::remove_var("AGENTMUX_INSTANCE_DIR");
184 std::env::remove_var("AGENTMUX_CHANNEL");
185 }
186
187 #[test]
188 fn override_wins() {
189 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
190 clear();
191 // The override is the ~/.agentmux root; the global registry lives at
192 // root/shared/agents/registry (P0.3 re-root).
193 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
194 let r = resolve_shared_registry_dir().unwrap();
195 assert_eq!(
196 r,
197 PathBuf::from("/tmp/test-home/shared/agents/registry")
198 );
199 clear();
200 }
201
202 #[test]
203 fn registry_uses_shared_dir_and_ignores_data_dir() {
204 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
205 clear();
206 // The instance registry is GLOBAL now: it resolves from the shared
207 // root, NOT by walking up the per-version AGENTMUX_DATA_DIR (which
208 // would land in the current channel). Set both and confirm the
209 // channel-scoped data dir is ignored.
210 std::env::set_var("AGENTMUX_SHARED_DIR", "/home/user/.agentmux/shared");
211 std::env::set_var(
212 "AGENTMUX_DATA_DIR",
213 "/home/user/.agentmux/channels/stable/versions/0.44.2/data",
214 );
215 let r = resolve_shared_registry_dir().unwrap();
216 assert_eq!(
217 r,
218 PathBuf::from("/home/user/.agentmux/shared/agents/registry")
219 );
220 // Sibling of the definition store — both under shared/agents/.
221 let d = resolve_shared_definitions_dir().unwrap();
222 assert_eq!(r.parent(), d.parent());
223 clear();
224 }
225
226 #[test]
227 fn falls_back_to_home() {
228 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
229 clear();
230 // No env set — uses dirs::home_dir()/.agentmux/shared. Non-None.
231 assert!(resolve_shared_registry_dir().is_some());
232 }
233
234 #[test]
235 fn reactive_dir_uses_shared_dir() {
236 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
237 clear();
238 std::env::set_var("AGENTMUX_SHARED_DIR", "/home/user/.agentmux/shared");
239 let r = resolve_shared_reactive_dir().unwrap();
240 assert_eq!(
241 r,
242 PathBuf::from("/home/user/.agentmux/shared/agents/reactive")
243 );
244 clear();
245 }
246
247 #[test]
248 fn reactive_dir_override_wins() {
249 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
250 clear();
251 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
252 let r = resolve_shared_reactive_dir().unwrap();
253 assert_eq!(
254 r,
255 PathBuf::from("/tmp/test-home/shared/agents/reactive")
256 );
257 clear();
258 }
259
260 #[test]
261 fn reactive_dir_is_sibling_of_registry_dir() {
262 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
263 clear();
264 std::env::set_var("AGENTMUX_SHARED_DIR", "/home/user/.agentmux/shared");
265 let reactive = resolve_shared_reactive_dir().unwrap();
266 let registry = resolve_shared_registry_dir().unwrap();
267 assert_eq!(reactive.parent(), registry.parent());
268 clear();
269 }
270
271 #[test]
272 fn definitions_dir_uses_shared_dir() {
273 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
274 clear();
275 std::env::set_var("AGENTMUX_SHARED_DIR", "/home/user/.agentmux/shared");
276 let r = resolve_shared_definitions_dir().unwrap();
277 assert_eq!(
278 r,
279 PathBuf::from("/home/user/.agentmux/shared/agents/definitions")
280 );
281 clear();
282 }
283
284 #[test]
285 fn definitions_dir_override_wins() {
286 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
287 clear();
288 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
289 let r = resolve_shared_definitions_dir().unwrap();
290 // Override is the ~/.agentmux root; shared lives at root/shared.
291 assert_eq!(
292 r,
293 PathBuf::from("/tmp/test-home/shared/agents/definitions")
294 );
295 clear();
296 }
297
298 #[test]
299 fn shared_store_path_default_is_global_on_stable_channel() {
300 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
301 clear();
302 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
303 // Isolation flag unset on the real release channel, with an instance
304 // dir present: shared, as on every channel since
305 // SPEC_SHARED_AUTH_ACROSS_CHANNELS_2026_10_03.md.
306 std::env::set_var("AGENTMUX_CHANNEL", "stable");
307 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/dev/some-branch");
308 let r = resolve_shared_store_path().unwrap();
309 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/store.db"));
310 clear();
311 }
312
313 #[test]
314 fn shared_store_path_default_is_global_when_channel_unset() {
315 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
316 clear();
317 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
318 // No AGENTMUX_CHANNEL at all (e.g. bare `cargo test`) — stay
319 // global rather than guess. Isolation flag also unset.
320 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/dev/some-branch");
321 let r = resolve_shared_store_path().unwrap();
322 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/store.db"));
323 clear();
324 }
325
326 #[test]
327 fn shared_store_path_is_global_by_default_on_every_channel() {
328 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
329 clear();
330 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
331 // SPEC_SHARED_AUTH_ACROSS_CHANNELS_2026_10_03.md: no AGENTMUX_ISOLATED_AUTH
332 // set at all — a dev branch and a local-package build share the global
333 // store, like `stable`. Only an explicit `=1` isolates.
334 for channel in ["dev-some-branch", "local-main-b28b7a-051fbf53"] {
335 std::env::set_var("AGENTMUX_CHANNEL", channel);
336 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/dev/some-branch");
337 let r = resolve_shared_store_path().unwrap();
338 assert_eq!(
339 r,
340 PathBuf::from("/tmp/test-home/shared/store.db"),
341 "{channel}: channels share the global store by default"
342 );
343 }
344 clear();
345 }
346
347 #[test]
348 fn shared_store_path_explicit_opt_out_stays_global_on_non_stable_channel() {
349 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
350 clear();
351 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
352 std::env::set_var("AGENTMUX_CHANNEL", "dev-some-branch");
353 std::env::set_var("AGENTMUX_ISOLATED_AUTH", "0");
354 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/dev/some-branch");
355 let r = resolve_shared_store_path().unwrap();
356 assert_eq!(
357 r,
358 PathBuf::from("/tmp/test-home/shared/store.db"),
359 "AGENTMUX_ISOLATED_AUTH=0 keeps the global store on a non-stable channel"
360 );
361 clear();
362 }
363
364 #[test]
365 fn shared_store_path_isolated_uses_instance_dir() {
366 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
367 clear();
368 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
369 std::env::set_var("AGENTMUX_ISOLATED_AUTH", "1");
370 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/dev/some-branch");
371 let r = resolve_shared_store_path().unwrap();
372 assert_eq!(
373 r,
374 PathBuf::from("/tmp/test-home/dev/some-branch/identity-store.db"),
375 "isolated shared store must live under the channel's own instance dir"
376 );
377 clear();
378 }
379
380 // SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md — the regression class P3
381 // asks for: prove `resolve_identity_store_path` never varies by
382 // channel/isolation, the way `resolve_shared_store_path` deliberately
383 // does. This is the exact test the design doc's audit (§2.3) found
384 // missing for every non-account table in the old shared store.
385 #[test]
386 fn identity_store_path_is_global_on_stable_channel() {
387 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
388 clear();
389 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
390 std::env::set_var("AGENTMUX_CHANNEL", "stable");
391 let r = resolve_identity_store_path().unwrap();
392 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/identity-store.db"));
393 clear();
394 }
395
396 #[test]
397 fn identity_store_path_stays_global_on_non_stable_channel() {
398 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
399 clear();
400 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
401 // The identity store is global whatever the channel or the isolation
402 // flag: a non-stable (local/dev/portable — i.e. every version bump)
403 // channel must NOT redirect this path anywhere per-channel.
404 std::env::set_var("AGENTMUX_CHANNEL", "local-somebranch-abcd1234-ef56789a");
405 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/channels/local-somebranch-abcd1234-ef56789a");
406 let r = resolve_identity_store_path().unwrap();
407 assert_eq!(
408 r,
409 PathBuf::from("/tmp/test-home/shared/identity-store.db"),
410 "identity store must stay global on a non-stable channel — this is the exact \
411 regression class that broke conversation continuity across a version bump \
412 (docs/specs/REPORT_HISTORY_CONTINUITY_ACROSS_VERSION_UPGRADE_2026_08_17.md)"
413 );
414 clear();
415 }
416
417 #[test]
418 fn identity_store_path_stays_global_even_with_isolated_auth_explicitly_on() {
419 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
420 clear();
421 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
422 std::env::set_var("AGENTMUX_CHANNEL", "local-somebranch-abcd1234-ef56789a");
423 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/channels/local-somebranch-abcd1234-ef56789a");
424 // Explicit isolation (the flag account testing relies on for
425 // db_accounts / identities_dir()) must have NO effect here — this
426 // store never had a legitimate isolation use case (design doc §2.3).
427 std::env::set_var("AGENTMUX_ISOLATED_AUTH", "1");
428 let r = resolve_identity_store_path().unwrap();
429 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/identity-store.db"));
430 clear();
431 }
432
433 #[test]
434 fn identity_store_path_diverges_from_shared_store_path_under_isolation() {
435 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
436 clear();
437 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
438 std::env::set_var("AGENTMUX_CHANNEL", "local-somebranch-abcd1234-ef56789a");
439 std::env::set_var("AGENTMUX_INSTANCE_DIR", "/tmp/test-home/channels/local-somebranch-abcd1234-ef56789a");
440 std::env::set_var("AGENTMUX_ISOLATED_AUTH", "1");
441 // Deliberately proves the two resolvers now disagree under
442 // isolation — resolve_shared_store_path (accounts, still
443 // isolatable for account testing) redirects per-channel;
444 // resolve_identity_store_path (links/bundles/drones/muxbus) does
445 // not. If a future change accidentally re-couples them, this test
446 // fails immediately instead of silently reintroducing the bug.
447 let identity = resolve_identity_store_path().unwrap();
448 let shared = resolve_shared_store_path().unwrap();
449 assert_ne!(
450 identity, shared,
451 "identity store and shared store must resolve to DIFFERENT paths under isolation \
452 — same failure mode as identities_dir() vs identity_history_dir(), see \
453 docs/specs/SPEC_IDENTITY_STORE_SPLIT_2026_08_17.md §3.1"
454 );
455 clear();
456 }
457
458 #[test]
459 fn identity_store_path_default_is_global_when_channel_unset() {
460 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
461 clear();
462 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
463 // No AGENTMUX_CHANNEL at all — same as resolve_shared_store_path's
464 // own conservative-stays-global default, but here it's not even a
465 // "default": there's no isolation branch to fall into at all.
466 let r = resolve_identity_store_path().unwrap();
467 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/identity-store.db"));
468 clear();
469 }
470
471 #[test]
472 fn identity_store_path_is_sibling_of_shared_store_path() {
473 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
474 clear();
475 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
476 std::env::set_var("AGENTMUX_CHANNEL", "stable");
477 // Both identity-store.db and store.db live directly under shared/ —
478 // NOT under shared/agents/ (that's resolve_shared_registry_dir's
479 // level, one directory deeper).
480 let identity = resolve_identity_store_path().unwrap();
481 let shared = resolve_shared_store_path().unwrap();
482 assert_eq!(identity.parent(), shared.parent());
483 clear();
484 }
485
486 #[test]
487 fn shared_store_path_isolated_without_instance_dir_falls_back() {
488 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
489 clear();
490 std::env::set_var("AGENTMUX_HOME_OVERRIDE", "/tmp/test-home");
491 std::env::set_var("AGENTMUX_ISOLATED_AUTH", "1");
492 // AGENTMUX_INSTANCE_DIR deliberately left unset — e.g. a bare
493 // `cargo run` outside the launcher. Must degrade to the global
494 // path rather than returning None unnecessarily.
495 let r = resolve_shared_store_path().unwrap();
496 assert_eq!(r, PathBuf::from("/tmp/test-home/shared/store.db"));
497 clear();
498 }
499}