1use std::collections::BTreeMap;
12
13use serde::{Deserialize, Serialize};
14
15use crate::deployment::DeploymentGrade;
16
17#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
27pub struct FeatureFlags {
28 pub global_chat: bool,
34 pub notifications: bool,
36 pub evals: bool,
38 pub skills: bool,
40 pub memory: bool,
42 pub knowledge: bool,
44 pub plugins: bool,
46 pub app_budgets: bool,
49 pub agent_versions: bool,
52 pub voice: bool,
54 pub agent_delegation: bool,
58 pub observers: bool,
60 pub public_chat: bool,
64 pub webmcp: bool,
68}
69
70#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
77#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
78#[serde(transparent)]
79pub struct FeatureFlagMap(pub BTreeMap<String, bool>);
80
81impl From<&FeatureFlags> for FeatureFlagMap {
82 fn from(flags: &FeatureFlags) -> Self {
83 flags.to_map()
84 }
85}
86
87impl From<FeatureFlags> for FeatureFlagMap {
88 fn from(flags: FeatureFlags) -> Self {
89 flags.to_map()
90 }
91}
92
93#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
95pub struct FeatureFlagDefinition {
96 pub name: &'static str,
98 pub label: &'static str,
100 pub description: &'static str,
102 pub experimental: bool,
104}
105
106pub const API_FEATURE_FLAG_DEFINITIONS: &[FeatureFlagDefinition] = &[
108 FeatureFlagDefinition {
109 name: "global_chat",
110 label: "Platform Chat",
111 description: "Adds a personal Platform Chat assistant you can open from the sidebar or the \
112 /chat page anywhere in the app. It's a quick scratchpad to talk to your agents \
113 without first setting up a dedicated app or channel. Turning it off disables the \
114 whole feature — the chat page, the sidebar entry, and its session and voice APIs all \
115 become unavailable.",
116 experimental: true,
117 },
118 FeatureFlagDefinition {
119 name: "notifications",
120 label: "Notifications",
121 description: "Turns on the in-app notification bell, toasts, and live updates. You get \
122 alerted in real time when something you care about happens, instead of refreshing \
123 or checking back manually.",
124 experimental: true,
125 },
126 FeatureFlagDefinition {
127 name: "evals",
128 label: "Evals",
129 description: "Lets you define and run behavioral evals against your agents. Use it to \
130 confirm an agent responds the way you expect and to catch regressions as you change \
131 prompts or models.",
132 experimental: true,
133 },
134 FeatureFlagDefinition {
135 name: "skills",
136 label: "Skills",
137 description: "Create and manage reusable instruction packages that teach agents \
138 specialized workflows.",
139 experimental: true,
140 },
141 FeatureFlagDefinition {
142 name: "memory",
143 label: "Memory",
144 description: "Manage knowledge stores agents can read, including manual notes and \
145 synchronized files.",
146 experimental: true,
147 },
148 FeatureFlagDefinition {
149 name: "knowledge",
150 label: "Knowledge indexes",
151 description: "Connect external document collections and make them searchable by agents.",
152 experimental: true,
153 },
154 FeatureFlagDefinition {
155 name: "plugins",
156 label: "Plugins",
157 description: "Install and manage extensions that add integrations, skills, and other \
158 capabilities.",
159 experimental: true,
160 },
161 FeatureFlagDefinition {
162 name: "app_budgets",
163 label: "App budgets",
164 description: "Adds spending limits scoped to individual apps and channels, with automatic \
165 resets on a schedule. It helps you cap and control costs so a single app or channel \
166 can't run away with your usage.",
167 experimental: true,
168 },
169 FeatureFlagDefinition {
170 name: "agent_versions",
171 label: "Agent versions",
172 description: "Captures immutable snapshots of your agents so you can fork, roll back, and \
173 pin apps to a specific version. This gives you a safety net to experiment freely \
174 and return to a known-good agent at any time.",
175 experimental: true,
176 },
177 FeatureFlagDefinition {
178 name: "voice",
179 label: "Voice",
180 description: "Enables realtime voice in chat with microphone controls. You can talk to \
181 your agents and hear responses instead of typing, for a hands-free conversation.",
182 experimental: true,
183 },
184 FeatureFlagDefinition {
185 name: "observers",
186 label: "Observers",
187 description: "Runs automatic online scoring on your production sessions. It continuously \
188 evaluates live conversations so you can monitor quality on real traffic without \
189 manually reviewing each one.",
190 experimental: true,
191 },
192 FeatureFlagDefinition {
193 name: "public_chat",
194 label: "Public Chat",
195 description: "Isolated, public-facing chat web app and the public_chat channel.",
196 experimental: true,
197 },
198 FeatureFlagDefinition {
199 name: "webmcp",
200 label: "WebMCP UI tools",
201 description: "Exposes a small browser-native tool surface from the authenticated UI so a \
202 browser agent can search, navigate, and perform confirmed actions in Everruns.",
203 experimental: true,
204 },
205];
206
207impl FeatureFlags {
208 pub fn for_org(system: &Self, org_enabled: &std::collections::HashMap<String, bool>) -> Self {
213 let opt_in = |name: &str, system_on: bool| -> bool {
214 system_on && org_enabled.get(name).copied().unwrap_or(false)
215 };
216 Self {
217 global_chat: opt_in("global_chat", system.global_chat),
218 notifications: opt_in("notifications", system.notifications),
219 evals: opt_in("evals", system.evals),
220 skills: opt_in("skills", system.skills),
221 memory: opt_in("memory", system.memory),
222 knowledge: opt_in("knowledge", system.knowledge),
223 plugins: opt_in("plugins", system.plugins),
224 app_budgets: opt_in("app_budgets", system.app_budgets),
225 agent_versions: opt_in("agent_versions", system.agent_versions),
226 voice: opt_in("voice", system.voice),
227 agent_delegation: opt_in("agent_delegation", system.agent_delegation),
228 observers: opt_in("observers", system.observers),
229 public_chat: opt_in("public_chat", system.public_chat),
230 webmcp: opt_in("webmcp", system.webmcp),
231 }
232 }
233
234 pub fn from_env(grade: &DeploymentGrade) -> Self {
236 Self {
237 global_chat: experimental_flag("FEATURE_GLOBAL_CHAT", grade),
238 notifications: experimental_flag("FEATURE_NOTIFICATIONS", grade),
239 evals: experimental_flag("FEATURE_EVALS", grade),
240 skills: experimental_flag("FEATURE_SKILLS", grade),
241 memory: experimental_flag("FEATURE_MEMORY", grade),
242 knowledge: experimental_flag("FEATURE_KNOWLEDGE", grade),
243 plugins: experimental_flag("FEATURE_PLUGINS", grade),
244 app_budgets: experimental_flag("FEATURE_APP_BUDGETS", grade),
245 agent_versions: experimental_flag("FEATURE_AGENT_VERSIONS", grade),
246 voice: experimental_flag("FEATURE_VOICE", grade),
247 agent_delegation: experimental_flag("FEATURE_AGENT_DELEGATION", grade),
248 observers: experimental_flag("FEATURE_OBSERVERS", grade),
249 public_chat: experimental_flag("FEATURE_PUBLIC_CHAT", grade),
250 webmcp: experimental_flag("FEATURE_WEBMCP", grade),
251 }
252 }
253
254 pub fn current() -> Self {
257 Self::from_env(&DeploymentGrade::from_env())
258 }
259
260 pub fn to_map(&self) -> FeatureFlagMap {
267 FeatureFlagMap(BTreeMap::from([
268 ("global_chat".to_string(), self.global_chat),
269 ("notifications".to_string(), self.notifications),
270 ("evals".to_string(), self.evals),
271 ("skills".to_string(), self.skills),
272 ("memory".to_string(), self.memory),
273 ("knowledge".to_string(), self.knowledge),
274 ("plugins".to_string(), self.plugins),
275 ("app_budgets".to_string(), self.app_budgets),
276 ("agent_versions".to_string(), self.agent_versions),
277 ("voice".to_string(), self.voice),
278 ("agent_delegation".to_string(), self.agent_delegation),
279 ("observers".to_string(), self.observers),
280 ("public_chat".to_string(), self.public_chat),
281 ("webmcp".to_string(), self.webmcp),
282 ]))
283 }
284
285 pub fn is_enabled(&self, flag: &str) -> bool {
287 match flag {
288 "global_chat" => self.global_chat,
289 "notifications" => self.notifications,
290 "evals" => self.evals,
291 "skills" => self.skills,
292 "memory" => self.memory,
293 "knowledge" => self.knowledge,
294 "plugins" => self.plugins,
295 "app_budgets" => self.app_budgets,
296 "agent_versions" => self.agent_versions,
297 "voice" => self.voice,
298 "agent_delegation" => self.agent_delegation,
299 "observers" => self.observers,
300 "public_chat" => self.public_chat,
301 "webmcp" => self.webmcp,
302 _ => false,
303 }
304 }
305
306 #[cfg(test)]
308 pub fn all_enabled() -> Self {
309 Self {
310 global_chat: true,
311 notifications: true,
312 evals: true,
313 skills: true,
314 memory: true,
315 knowledge: true,
316 plugins: true,
317 app_budgets: true,
318 agent_versions: true,
319 voice: true,
320 agent_delegation: true,
321 observers: true,
322 public_chat: true,
323 webmcp: true,
324 }
325 }
326}
327
328#[derive(Debug, Default, Clone, PartialEq, Eq)]
332pub struct InternalFeatureFlags {
333 pub docker_capability: bool,
336 pub container_sandbox: bool,
342 pub session_sandbox: bool,
345 pub machine_payments: bool,
350 pub lua: bool,
354}
355
356impl InternalFeatureFlags {
357 pub fn from_env() -> Self {
359 let docker_capability = standard_flag("FEATURE_DOCKER_CAPABILITY", false);
360
361 Self {
362 docker_capability,
363 container_sandbox: standard_flag("FEATURE_CONTAINER_SANDBOX", docker_capability),
364 session_sandbox: standard_flag("FEATURE_SESSION_SANDBOX", false),
365 machine_payments: standard_flag("FEATURE_MACHINE_PAYMENTS", false),
366 lua: standard_flag("FEATURE_LUA", false),
367 }
368 }
369
370 pub fn is_enabled(&self, flag: &str) -> bool {
372 match flag {
373 "docker_capability" => self.docker_capability,
374 "container_sandbox" => self.container_sandbox,
375 "session_sandbox" => self.session_sandbox,
376 "machine_payments" => self.machine_payments,
377 "lua" => self.lua,
378 _ => false,
379 }
380 }
381}
382
383fn experimental_flag(env_var: &str, grade: &DeploymentGrade) -> bool {
387 if let Ok(val) = std::env::var(env_var) {
388 return val == "true" || val == "1";
389 }
390 grade.experimental_features_enabled()
391}
392
393fn standard_flag(env_var: &str, default: bool) -> bool {
397 std::env::var(env_var)
398 .map(|v| v == "true" || v == "1")
399 .unwrap_or(default)
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
408
409 fn lock_env() -> std::sync::MutexGuard<'static, ()> {
410 ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
411 }
412
413 fn restore_env(key: &str, prev: Option<String>) {
415 match prev {
416 Some(value) => unsafe { std::env::set_var(key, value) },
417 None => unsafe { std::env::remove_var(key) },
418 }
419 }
420
421 #[test]
422 fn test_default_flags() {
423 let flags = FeatureFlags::default();
424 assert!(!flags.global_chat);
425 assert!(!flags.notifications);
426 }
427
428 #[test]
432 fn test_experimental_enabled_in_dev() {
433 let _lock = lock_env();
434 unsafe { std::env::remove_var("FEATURE_GLOBAL_CHAT") };
435 unsafe { std::env::remove_var("FEATURE_EVALS") };
436 let flags = FeatureFlags::from_env(&DeploymentGrade::Dev);
437 assert!(flags.global_chat);
438 assert!(flags.evals);
439 }
440
441 #[test]
442 fn test_experimental_disabled_in_prod() {
443 let _lock = lock_env();
444 unsafe { std::env::remove_var("FEATURE_GLOBAL_CHAT") };
445 unsafe { std::env::remove_var("FEATURE_EVALS") };
446 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
447 assert!(!flags.global_chat);
448 assert!(!flags.evals);
449 }
450
451 #[test]
452 fn test_env_override_enables_in_prod() {
453 let _lock = lock_env();
454 unsafe { std::env::set_var("FEATURE_GLOBAL_CHAT", "true") };
455 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
456 assert!(flags.global_chat);
457 unsafe { std::env::remove_var("FEATURE_GLOBAL_CHAT") };
458 }
459
460 #[test]
461 fn test_env_override_disables_in_dev() {
462 let _lock = lock_env();
463 unsafe { std::env::set_var("FEATURE_GLOBAL_CHAT", "false") };
464 let flags = FeatureFlags::from_env(&DeploymentGrade::Dev);
465 assert!(!flags.global_chat);
466 unsafe { std::env::remove_var("FEATURE_GLOBAL_CHAT") };
467 }
468
469 #[test]
470 fn test_is_enabled_dynamic() {
471 let flags = FeatureFlags {
472 global_chat: true,
473 notifications: true,
474 evals: true,
475 skills: true,
476 memory: true,
477 knowledge: true,
478 plugins: true,
479 app_budgets: true,
480 agent_versions: true,
481 voice: true,
482 agent_delegation: true,
483 observers: true,
484 public_chat: true,
485 webmcp: true,
486 };
487 assert!(flags.is_enabled("global_chat"));
488 assert!(flags.is_enabled("notifications"));
489 assert!(flags.is_enabled("evals"));
490 assert!(flags.is_enabled("skills"));
491 assert!(flags.is_enabled("memory"));
492 assert!(flags.is_enabled("knowledge"));
493 assert!(flags.is_enabled("plugins"));
494 assert!(flags.is_enabled("app_budgets"));
495 assert!(flags.is_enabled("agent_versions"));
496 assert!(flags.is_enabled("voice"));
497 assert!(flags.is_enabled("agent_delegation"));
498 assert!(flags.is_enabled("observers"));
499 assert!(flags.is_enabled("public_chat"));
500 assert!(
501 !flags.is_enabled("mcp_endpoint"),
502 "MCP is a product surface, not a feature flag"
503 );
504 assert!(flags.is_enabled("webmcp"));
505 assert!(!flags.is_enabled("nonexistent"));
506 }
507
508 #[test]
509 fn test_serialization() {
510 let flags = FeatureFlags {
511 global_chat: true,
512 notifications: true,
513 evals: true,
514 skills: true,
515 memory: true,
516 knowledge: true,
517 plugins: true,
518 app_budgets: true,
519 agent_versions: true,
520 voice: true,
521 agent_delegation: true,
522 observers: true,
523 public_chat: true,
524 webmcp: true,
525 };
526 let json = serde_json::to_string(&flags).unwrap();
527 assert!(json.contains("\"global_chat\":true"));
528 assert!(json.contains("\"notifications\":true"));
529 assert!(json.contains("\"app_budgets\":true"));
530 assert!(json.contains("\"agent_versions\":true"));
531 assert!(json.contains("\"voice\":true"));
532 assert!(json.contains("\"agent_delegation\":true"));
533 assert!(json.contains("\"observers\":true"));
534 assert!(json.contains("\"webmcp\":true"));
535
536 let parsed: FeatureFlags = serde_json::from_str(&json).unwrap();
537 assert_eq!(flags, parsed);
538 }
539
540 #[test]
541 fn test_to_map_matches_serialized_flags() {
542 let flags = FeatureFlags::all_enabled();
547 let typed: serde_json::Value = serde_json::to_value(&flags).unwrap();
548 let map: serde_json::Value = serde_json::to_value(flags.to_map()).unwrap();
549 assert_eq!(typed, map);
550
551 let default_typed: serde_json::Value =
552 serde_json::to_value(FeatureFlags::default()).unwrap();
553 let default_map: serde_json::Value =
554 serde_json::to_value(FeatureFlags::default().to_map()).unwrap();
555 assert_eq!(default_typed, default_map);
556 }
557
558 #[test]
559 fn test_mcp_endpoint_is_not_in_api_feature_flag_catalog() {
560 assert!(
561 API_FEATURE_FLAG_DEFINITIONS
562 .iter()
563 .all(|definition| definition.name != "mcp_endpoint")
564 );
565 }
566
567 #[test]
568 fn test_agent_delegation_enabled_in_dev() {
569 let _lock = lock_env();
570 unsafe { std::env::remove_var("FEATURE_AGENT_DELEGATION") };
571 let flags = FeatureFlags::from_env(&DeploymentGrade::Dev);
572 assert!(flags.agent_delegation);
573 }
574
575 #[test]
576 fn test_agent_delegation_disabled_in_prod() {
577 let _lock = lock_env();
578 unsafe { std::env::remove_var("FEATURE_AGENT_DELEGATION") };
579 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
580 assert!(!flags.agent_delegation);
581 }
582
583 #[test]
584 fn test_agent_delegation_env_override_in_prod() {
585 let _lock = lock_env();
586 unsafe { std::env::set_var("FEATURE_AGENT_DELEGATION", "true") };
587 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
588 assert!(flags.agent_delegation);
589 unsafe { std::env::remove_var("FEATURE_AGENT_DELEGATION") };
590 }
591
592 #[test]
593 fn test_standard_flag() {
594 let _lock = lock_env();
595 unsafe { std::env::remove_var("FEATURE_TEST_STD") };
596 assert!(!standard_flag("FEATURE_TEST_STD", false));
597 assert!(standard_flag("FEATURE_TEST_STD", true));
598
599 unsafe { std::env::set_var("FEATURE_TEST_STD", "1") };
600 assert!(standard_flag("FEATURE_TEST_STD", false));
601 unsafe { std::env::remove_var("FEATURE_TEST_STD") };
602 }
603
604 #[test]
605 fn test_notifications_enabled_in_dev() {
606 let _lock = lock_env();
607 unsafe { std::env::remove_var("FEATURE_NOTIFICATIONS") };
608 let flags = FeatureFlags::from_env(&DeploymentGrade::Dev);
609 assert!(flags.notifications);
610 }
611
612 #[test]
613 fn test_notifications_disabled_in_prod() {
614 let _lock = lock_env();
615 unsafe { std::env::remove_var("FEATURE_NOTIFICATIONS") };
616 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
617 assert!(!flags.notifications);
618 }
619
620 #[test]
621 fn test_for_org_requires_system_and_opt_in() {
622 let system = FeatureFlags {
623 global_chat: true,
624 evals: true,
625 ..FeatureFlags::default()
626 };
627 let mut org = std::collections::HashMap::new();
628 org.insert("global_chat".to_string(), true);
629 let effective = FeatureFlags::for_org(&system, &org);
630 assert!(effective.global_chat);
631 assert!(!effective.evals);
632
633 let effective_none = FeatureFlags::for_org(&system, &std::collections::HashMap::new());
634 assert!(!effective_none.global_chat);
635 }
636
637 #[test]
638 fn test_optional_ui_modules_require_org_opt_in() {
639 let system = FeatureFlags {
640 evals: true,
641 skills: true,
642 memory: true,
643 knowledge: true,
644 plugins: true,
645 ..FeatureFlags::default()
646 };
647 let disabled = FeatureFlags::for_org(&system, &std::collections::HashMap::new());
648 assert!(!disabled.evals);
649 assert!(!disabled.skills);
650 assert!(!disabled.memory);
651 assert!(!disabled.knowledge);
652 assert!(!disabled.plugins);
653
654 let org = std::collections::HashMap::from([
655 ("evals".to_string(), true),
656 ("skills".to_string(), true),
657 ("memory".to_string(), true),
658 ("knowledge".to_string(), true),
659 ("plugins".to_string(), true),
660 ]);
661 let enabled = FeatureFlags::for_org(&system, &org);
662 assert!(enabled.evals);
663 assert!(enabled.skills);
664 assert!(enabled.memory);
665 assert!(enabled.knowledge);
666 assert!(enabled.plugins);
667 }
668
669 #[test]
670 fn test_for_org_cannot_enable_when_system_off() {
671 let system = FeatureFlags::default();
672 let mut org = std::collections::HashMap::new();
673 org.insert("global_chat".to_string(), true);
674 let effective = FeatureFlags::for_org(&system, &org);
675 assert!(!effective.global_chat);
676 }
677
678 #[test]
679 fn test_notifications_respects_env_override() {
680 let _lock = lock_env();
681 unsafe { std::env::set_var("FEATURE_NOTIFICATIONS", "true") };
682 let flags = FeatureFlags::from_env(&DeploymentGrade::Prod);
683 assert!(flags.notifications);
684 unsafe { std::env::remove_var("FEATURE_NOTIFICATIONS") };
685 }
686
687 #[test]
692 fn test_internal_default_flags() {
693 let flags = InternalFeatureFlags::default();
694 assert!(!flags.docker_capability);
695 assert!(!flags.container_sandbox);
696 assert!(!flags.session_sandbox);
697 assert!(!flags.machine_payments);
698 }
699
700 #[test]
701 fn test_docker_capability_flag_disabled_by_default_in_dev() {
702 let _lock = lock_env();
703 unsafe { std::env::remove_var("FEATURE_DOCKER_CAPABILITY") };
704 let flags = InternalFeatureFlags::from_env();
705 assert!(
706 !flags.docker_capability,
707 "docker_capability should be disabled by default even in dev"
708 );
709 }
710
711 #[test]
712 fn test_docker_capability_flag_enabled_by_env_override() {
713 let _lock = lock_env();
714 unsafe { std::env::set_var("FEATURE_DOCKER_CAPABILITY", "true") };
715 let flags = InternalFeatureFlags::from_env();
716 assert!(flags.docker_capability);
717 unsafe { std::env::remove_var("FEATURE_DOCKER_CAPABILITY") };
718 }
719
720 #[test]
721 fn test_container_sandbox_flag_enabled_by_env_override() {
722 let _lock = lock_env();
723 unsafe { std::env::set_var("FEATURE_CONTAINER_SANDBOX", "true") };
724 unsafe { std::env::remove_var("FEATURE_DOCKER_CAPABILITY") };
725 let flags = InternalFeatureFlags::from_env();
726 assert!(flags.container_sandbox);
727 unsafe { std::env::remove_var("FEATURE_CONTAINER_SANDBOX") };
728 }
729
730 #[test]
731 fn test_container_sandbox_flag_falls_back_to_legacy_docker_flag() {
732 let _lock = lock_env();
733 unsafe { std::env::remove_var("FEATURE_CONTAINER_SANDBOX") };
734 unsafe { std::env::set_var("FEATURE_DOCKER_CAPABILITY", "true") };
735 let flags = InternalFeatureFlags::from_env();
736 assert!(flags.container_sandbox);
737 unsafe { std::env::remove_var("FEATURE_DOCKER_CAPABILITY") };
738 }
739
740 #[test]
741 fn test_internal_is_enabled_dynamic() {
742 let flags = InternalFeatureFlags {
743 docker_capability: true,
744 container_sandbox: true,
745 session_sandbox: true,
746 machine_payments: true,
747 lua: true,
748 };
749 assert!(flags.is_enabled("docker_capability"));
750 assert!(flags.is_enabled("container_sandbox"));
751 assert!(flags.is_enabled("session_sandbox"));
752 assert!(flags.is_enabled("machine_payments"));
753 assert!(flags.is_enabled("lua"));
754 assert!(!flags.is_enabled("nonexistent"));
755 }
756
757 #[test]
758 fn test_machine_payments_disabled_by_default() {
759 let _lock = lock_env();
760 let prev = std::env::var("FEATURE_MACHINE_PAYMENTS").ok();
761 unsafe { std::env::remove_var("FEATURE_MACHINE_PAYMENTS") };
762 let flags = InternalFeatureFlags::from_env();
763 assert!(
764 !flags.machine_payments,
765 "machine_payments should be disabled by default on all envs"
766 );
767 restore_env("FEATURE_MACHINE_PAYMENTS", prev);
768 }
769
770 #[test]
771 fn test_machine_payments_enabled_by_env_override() {
772 let _lock = lock_env();
773 let prev = std::env::var("FEATURE_MACHINE_PAYMENTS").ok();
774 unsafe { std::env::set_var("FEATURE_MACHINE_PAYMENTS", "true") };
775 let flags = InternalFeatureFlags::from_env();
776 assert!(flags.machine_payments);
777 restore_env("FEATURE_MACHINE_PAYMENTS", prev);
778 }
779
780 #[test]
781 fn test_session_sandbox_flag_enabled_by_env_override() {
782 let _lock = lock_env();
783 unsafe { std::env::set_var("FEATURE_SESSION_SANDBOX", "true") };
784 let flags = InternalFeatureFlags::from_env();
785 assert!(flags.session_sandbox);
786 unsafe { std::env::remove_var("FEATURE_SESSION_SANDBOX") };
787 }
788}