Skip to main content

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}