Skip to main content

agentmux_srv/muxbus/wan_presence/
policy.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Which installs publish their presence. A normal signed-in desktop does,
5//! unless the user turns off "Publish this computer to my devices"
6//! (`cloud:publishpresence`, [`setting_on`]). A `task dev` build, a headless
7//! srv, an isolated home and a test harness don't by default: each would
8//! leave an entry in the account's list of computers that is not a computer
9//! anyone uses. [`FORCE_ENV`] turns publishing on for one of those, for
10//! testing presence itself; the setting still wins over it.
11
12use crate::backend::rpc_types::PresenceOffReason;
13use crate::backend::wconfig::SettingsType;
14
15/// `1` publishes from an install that is off by default (a dev build under
16/// test). Not a setting: nobody should come across it by accident.
17pub(crate) const FORCE_ENV: &str = "AGENTMUX_PUBLISH_PRESENCE";
18
19/// Set by a test harness that starts srv. `AGENTMUX_DISABLE_CLOUD_SUBSCRIBER`
20/// (the srv integration tests) counts too.
21pub(crate) const TEST_HARNESS_ENV: &str = "AGENTMUX_TEST_HARNESS";
22
23/// The facts about this install that decide whether it publishes by default,
24/// read once at start.
25#[derive(Debug, Clone, Default, PartialEq, Eq)]
26pub(crate) struct Install {
27    pub headless: bool,
28    pub home_override: bool,
29    pub channel: String,
30    pub test_harness: bool,
31    pub forced: bool,
32}
33
34impl Install {
35    /// This process's install. `headless` is srv's own `--headless`.
36    pub(crate) fn from_env(headless: bool) -> Self {
37        Self::from_vars(headless, crate::backend::reactive::registry::local_channel_id(), |name| {
38            std::env::var(name).ok()
39        })
40    }
41
42    /// [`Self::from_env`] over any variables, so tests don't touch the
43    /// process's own.
44    pub(crate) fn from_vars(headless: bool, channel: String, var: impl Fn(&str) -> Option<String>) -> Self {
45        Self {
46            headless,
47            home_override: var("AGENTMUX_HOME_OVERRIDE").is_some_and(|v| !v.is_empty()),
48            channel,
49            // Present at all, like `AGENTMUX_DISABLE_CLOUD_SUBSCRIBER` itself.
50            test_harness: var(TEST_HARNESS_ENV).is_some() || var("AGENTMUX_DISABLE_CLOUD_SUBSCRIBER").is_some(),
51            forced: var(FORCE_ENV).as_deref() == Some("1"),
52        }
53    }
54
55    /// Why this install doesn't publish by default, if it doesn't.
56    fn default_off(&self) -> Option<PresenceOffReason> {
57        // Headless first: a headless srv sets AGENTMUX_DISABLE_CLOUD_SUBSCRIBER
58        // itself (headless::prepare_env), which would otherwise read as a test
59        // harness.
60        if self.headless {
61            Some(PresenceOffReason::Headless)
62        } else if self.test_harness {
63            Some(PresenceOffReason::TestHarness)
64        } else if self.home_override {
65            Some(PresenceOffReason::IsolatedHome)
66        } else if self.channel.starts_with("dev-") {
67            Some(PresenceOffReason::DevBuild)
68        } else {
69            None
70        }
71    }
72
73    /// Why nothing is published, given the setting; `None` to publish. The
74    /// setting turned off always wins; [`FORCE_ENV`] overrides only the
75    /// install's own default.
76    pub(crate) fn off_reason(&self, setting_on: bool) -> Option<PresenceOffReason> {
77        if !setting_on {
78            return Some(PresenceOffReason::Setting);
79        }
80        if self.forced {
81            return None;
82        }
83        self.default_off()
84    }
85}
86
87/// "Publish this computer to my devices" as `settings.json` has it. Absent
88/// means on. The key is written out at the read so the settings-defaults
89/// check (`settings-defaults.test.ts`) finds this default.
90pub(crate) fn setting_on(settings: &SettingsType) -> bool {
91    settings.extra.get("cloud:publishpresence").and_then(|v| v.as_bool()).unwrap_or(true)
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    fn normal() -> Install {
99        Install { channel: "stable".into(), ..Install::default() }
100    }
101
102    #[test]
103    fn a_normal_install_publishes_and_the_others_are_off_by_default() {
104        let cases: [(&str, Install, Option<PresenceOffReason>); 7] = [
105            ("stable", normal(), None),
106            ("a local package build", Install { channel: "agentmux-0-59-12-k3j9".into(), ..normal() }, None),
107            ("a dev build", Install { channel: "dev-feature-x".into(), ..normal() }, Some(PresenceOffReason::DevBuild)),
108            ("headless", Install { headless: true, ..normal() }, Some(PresenceOffReason::Headless)),
109            ("an isolated home", Install { home_override: true, ..normal() }, Some(PresenceOffReason::IsolatedHome)),
110            ("a test harness", Install { test_harness: true, ..normal() }, Some(PresenceOffReason::TestHarness)),
111            (
112                "a headless dev build under test",
113                Install { headless: true, test_harness: true, channel: "dev-x".into(), ..normal() },
114                Some(PresenceOffReason::Headless),
115            ),
116        ];
117        for (what, install, expect) in cases {
118            assert_eq!(install.off_reason(true), expect, "{what}");
119        }
120    }
121
122    #[test]
123    fn the_setting_wins_and_the_override_only_lifts_the_default() {
124        assert_eq!(normal().off_reason(false), Some(PresenceOffReason::Setting));
125        let dev = Install { channel: "dev-x".into(), ..normal() };
126        let forced = Install { forced: true, ..dev.clone() };
127        assert_eq!(dev.off_reason(true), Some(PresenceOffReason::DevBuild));
128        assert_eq!(forced.off_reason(true), None, "a dev build under test publishes");
129        assert_eq!(forced.off_reason(false), Some(PresenceOffReason::Setting), "the setting still wins");
130        let everything = Install { headless: true, home_override: true, test_harness: true, ..forced };
131        assert_eq!(everything.off_reason(true), None);
132    }
133
134    #[test]
135    fn the_setting_is_on_unless_it_is_false() {
136        let read = |json: serde_json::Value| setting_on(&serde_json::from_value::<SettingsType>(json).unwrap());
137        assert!(read(serde_json::json!({})));
138        assert!(read(serde_json::json!({ "cloud:publishpresence": true })));
139        assert!(!read(serde_json::json!({ "cloud:publishpresence": false })));
140        assert!(read(serde_json::json!({ "cloud:publishpresence": "no" })), "not a boolean: the default");
141    }
142
143    fn with(vars: &[(&str, &str)]) -> Install {
144        let vars: Vec<(String, String)> = vars.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
145        Install::from_vars(false, "stable".into(), move |name| {
146            vars.iter().find(|(k, _)| k == name).map(|(_, v)| v.clone())
147        })
148    }
149
150    #[test]
151    fn the_environment_is_read() {
152        assert_eq!(with(&[]), normal());
153        assert!(Install::from_vars(true, "stable".into(), |_| None).headless);
154        assert!(with(&[(TEST_HARNESS_ENV, "1")]).test_harness);
155        assert!(with(&[("AGENTMUX_DISABLE_CLOUD_SUBSCRIBER", "")]).test_harness, "present is enough");
156        assert!(with(&[("AGENTMUX_HOME_OVERRIDE", "/tmp/h")]).home_override);
157        assert!(!with(&[("AGENTMUX_HOME_OVERRIDE", "")]).home_override, "empty is unset");
158        assert!(with(&[(FORCE_ENV, "1")]).forced);
159        assert!(!with(&[(FORCE_ENV, "true")]).forced, "only 1");
160    }
161
162    /// A real headless srv sets AGENTMUX_DISABLE_CLOUD_SUBSCRIBER itself, so it
163    /// must still read as headless, not as a test harness.
164    #[test]
165    fn a_headless_srv_says_headless_not_test_harness() {
166        let install = Install::from_vars(true, "stable".into(), |name| {
167            (name == "AGENTMUX_DISABLE_CLOUD_SUBSCRIBER").then(|| "1".to_string())
168        });
169        assert_eq!(install.off_reason(true), Some(PresenceOffReason::Headless));
170    }
171}