failproofai 0.0.15 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  21. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/index.html +1 -1
  39. package/.next/standalone/.next/server/app/index.rsc +14 -14
  40. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  42. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  43. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
  44. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  46. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +9 -9
  49. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +3 -0
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
  66. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__06b4trb._.js → [root-of-the-server]__0259h38._.js} +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0hs_5mh.js → node_modules_html-to-image_es_index_1-upb14.js} +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  84. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  85. package/.next/standalone/.next/server/pages/404.html +1 -1
  86. package/.next/standalone/.next/server/pages/500.html +1 -1
  87. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/server-reference-manifest.json +11 -11
  89. package/.next/standalone/.next/static/chunks/{1_v8b8mxhk25s.js → 0ip1n8xmgxarv.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/{33rm-y-i4uzvv.js → 0u9hc6oxx-gpj.js} +1 -1
  91. package/.next/standalone/.next/static/chunks/{11qhtpk2cvoxv.js → 1d6ncspjagm53.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/{2f4mxqkkoa_7d.js → 1grd7y94-igz9.js} +1 -1
  93. package/.next/standalone/.next/static/chunks/{3m8wmvlhsy1mx.js → 1s-_swlc80y43.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/{0kf9j0pf_j9-0.js → 29k6ef49zppxj.js} +1 -1
  95. package/.next/standalone/.next/static/chunks/{2h7vw-ojgr9fz.js → 29ydr-8q0zis6.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{1-ng45379-ztb.js → 2vr7aazw6k2-n.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/{2geh8s7d3ead-.js → 3wrgbih-516uj.js} +1 -1
  98. package/.next/standalone/Cargo.lock +428 -0
  99. package/.next/standalone/Cargo.toml +9 -0
  100. package/.next/standalone/crates/PROTOCOL.md +122 -0
  101. package/.next/standalone/crates/failproofaid/Cargo.toml +18 -0
  102. package/.next/standalone/crates/failproofaid/src/lock.rs +81 -0
  103. package/.next/standalone/crates/failproofaid/src/main.rs +83 -0
  104. package/.next/standalone/crates/failproofaid/src/paths.rs +172 -0
  105. package/.next/standalone/crates/failproofaid/src/server.rs +502 -0
  106. package/.next/standalone/crates/failproofaid/src/worker.rs +400 -0
  107. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +241 -0
  108. package/.next/standalone/crates/fpai-ipc/Cargo.toml +16 -0
  109. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +175 -0
  110. package/.next/standalone/crates/fpai-ipc/src/framing.rs +177 -0
  111. package/.next/standalone/crates/fpai-ipc/src/lib.rs +10 -0
  112. package/.next/standalone/crates/fpai-ipc/src/peer.rs +80 -0
  113. package/.next/standalone/package.json +5 -3
  114. package/.next/standalone/rust-toolchain.toml +4 -0
  115. package/.next/standalone/server.js +1 -1
  116. package/bin/failproofai-worker.mjs +51 -0
  117. package/bin/failproofai.mjs +68 -1
  118. package/bin/failproofaid-shim.mjs +67 -0
  119. package/dist/cli.mjs +1202 -624
  120. package/dist/worker.mjs +8441 -0
  121. package/package.json +5 -3
  122. package/src/hooks/builtin-policies.ts +37 -13
  123. package/src/hooks/configure-wizard.ts +73 -1
  124. package/src/hooks/daemon-client.ts +199 -0
  125. package/src/hooks/daemon-download.ts +152 -0
  126. package/src/hooks/daemon-service.ts +449 -0
  127. package/src/hooks/handler.ts +231 -195
  128. package/src/hooks/normalize-cli-payload.ts +61 -0
  129. package/src/hooks/policy-types.ts +15 -0
  130. package/src/hooks/read-stdin.ts +48 -0
  131. package/src/hooks/worker-server.ts +154 -0
  132. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +0 -3
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0fopait._.js +0 -3
  134. /package/.next/standalone/.next/static/{ePKMmXXfXxThNsxwv1EiF → 7Lxtd352rSaHB-pmm6G8M}/_buildManifest.js +0 -0
  135. /package/.next/standalone/.next/static/{ePKMmXXfXxThNsxwv1EiF → 7Lxtd352rSaHB-pmm6G8M}/_clientMiddlewareManifest.js +0 -0
  136. /package/.next/standalone/.next/static/{ePKMmXXfXxThNsxwv1EiF → 7Lxtd352rSaHB-pmm6G8M}/_ssgManifest.js +0 -0
@@ -0,0 +1,83 @@
1
+ mod lock;
2
+ mod paths;
3
+ mod server;
4
+ mod worker;
5
+
6
+ use std::sync::Arc;
7
+ use std::sync::atomic::{AtomicBool, Ordering};
8
+
9
+ fn main() {
10
+ let args: Vec<String> = std::env::args().collect();
11
+ if args.iter().any(|a| a == "--version" || a == "-v") {
12
+ println!("failproofaid {}", env!("CARGO_PKG_VERSION"));
13
+ return;
14
+ }
15
+
16
+ if let Err(err) = run() {
17
+ eprintln!("[failproofaid] {err}");
18
+ std::process::exit(1);
19
+ }
20
+ }
21
+
22
+ fn run() -> Result<(), Box<dyn std::error::Error>> {
23
+ let lock_path = paths::lock_path()?;
24
+ paths::ensure_run_dir()?;
25
+ let _singleton = lock::acquire(&lock_path)?;
26
+
27
+ let socket_path = paths::socket_path()?;
28
+ let worker_socket_path = paths::worker_socket_path()?;
29
+ let worker = Arc::new(worker::Worker::new(
30
+ worker_socket_path,
31
+ worker::WorkerCommand::from_env(),
32
+ ));
33
+ // Pre-warm off the startup path: the socket must be accepting
34
+ // connections promptly (a service manager / health check shouldn't wait
35
+ // on a Node cold start), but a request that arrives before warm-up
36
+ // finishes still gets a correct, just slightly slower, answer — `call()`
37
+ // -> `ensure_started()` shares the same lock and simply waits for
38
+ // whichever spawn (this one or its own) is already in flight.
39
+ {
40
+ let warm_worker = worker.clone();
41
+ std::thread::spawn(move || warm_worker.warm());
42
+ }
43
+ let srv = server::Server::bind(&socket_path, worker)?;
44
+ eprintln!("[failproofaid] listening on {}", socket_path.display());
45
+
46
+ let shutdown = Arc::new(AtomicBool::new(false));
47
+ install_signal_handler(shutdown.clone());
48
+
49
+ srv.run_until(shutdown)?;
50
+ Ok(())
51
+ }
52
+
53
+ /// Requests a clean shutdown (socket file removal, lock release via Drop)
54
+ /// on SIGTERM/SIGINT instead of dying mid-accept-loop — this is how a
55
+ /// systemd `stop`/launchd unload is expected to end the process.
56
+ fn install_signal_handler(shutdown: Arc<AtomicBool>) {
57
+ static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
58
+
59
+ extern "C" fn handle_signal(_sig: libc::c_int) {
60
+ SHUTDOWN_REQUESTED.store(true, Ordering::Relaxed);
61
+ }
62
+
63
+ unsafe {
64
+ libc::signal(
65
+ libc::SIGTERM,
66
+ handle_signal as *const () as libc::sighandler_t,
67
+ );
68
+ libc::signal(
69
+ libc::SIGINT,
70
+ handle_signal as *const () as libc::sighandler_t,
71
+ );
72
+ }
73
+
74
+ std::thread::spawn(move || {
75
+ loop {
76
+ if SHUTDOWN_REQUESTED.load(Ordering::Relaxed) {
77
+ shutdown.store(true, Ordering::Relaxed);
78
+ return;
79
+ }
80
+ std::thread::sleep(std::time::Duration::from_millis(50));
81
+ }
82
+ });
83
+ }
@@ -0,0 +1,172 @@
1
+ //! Resolves where failproofaid's runtime state lives on disk.
2
+ //!
3
+ //! User-scope only (per the plan: no elevation, everything under the
4
+ //! invoking user's home directory) — Linux and macOS only, matching the
5
+ //! rest of this crate.
6
+
7
+ use std::fs;
8
+ use std::io;
9
+ use std::os::unix::fs::PermissionsExt;
10
+ use std::path::PathBuf;
11
+
12
+ /// `~/.failproofai/run` — directory holding the socket and singleton lock
13
+ /// file. Overridable via `FAILPROOFAI_DAEMON_SOCKET`'s parent for local dev
14
+ /// (see `run_dir_override`), so a `bun run daemon:dev` loop never touches a
15
+ /// real installed daemon's state.
16
+ pub fn run_dir() -> io::Result<PathBuf> {
17
+ if let Some(socket_override) = std::env::var_os("FAILPROOFAI_DAEMON_SOCKET") {
18
+ let path = PathBuf::from(socket_override);
19
+ return path
20
+ .parent()
21
+ .map(PathBuf::from)
22
+ .ok_or_else(|| io::Error::other("FAILPROOFAI_DAEMON_SOCKET has no parent directory"));
23
+ }
24
+ let home = std::env::var_os("HOME")
25
+ .ok_or_else(|| io::Error::other("HOME is not set; failproofaid is user-scope only"))?;
26
+ Ok(PathBuf::from(home).join(".failproofai").join("run"))
27
+ }
28
+
29
+ pub fn socket_path() -> io::Result<PathBuf> {
30
+ if let Some(socket_override) = std::env::var_os("FAILPROOFAI_DAEMON_SOCKET") {
31
+ return Ok(PathBuf::from(socket_override));
32
+ }
33
+ Ok(run_dir()?.join("failproofaid.sock"))
34
+ }
35
+
36
+ pub fn lock_path() -> io::Result<PathBuf> {
37
+ Ok(run_dir()?.join("failproofaid.lock"))
38
+ }
39
+
40
+ /// Where the daemon tells the worker subprocess to listen — a second
41
+ /// socket, distinct from `socket_path()`, that only this process ever
42
+ /// connects to. Overridable via `FAILPROOFAI_WORKER_SOCKET` for local dev
43
+ /// (mirrors `FAILPROOFAI_DAEMON_SOCKET`'s override for the client-facing
44
+ /// socket).
45
+ pub fn worker_socket_path() -> io::Result<PathBuf> {
46
+ if let Some(socket_override) = std::env::var_os("FAILPROOFAI_WORKER_SOCKET") {
47
+ return Ok(PathBuf::from(socket_override));
48
+ }
49
+ Ok(run_dir()?.join("worker.sock"))
50
+ }
51
+
52
+ /// Creates the run directory (`0700`) if it doesn't exist yet. This
53
+ /// directory holds a socket that evaluates security-relevant decisions, so
54
+ /// a freshly created one is always locked to owner-only.
55
+ ///
56
+ /// If the directory already exists, this deliberately does **not** chmod
57
+ /// it into shape — only a directory failproofaid created itself gets its
58
+ /// permissions enforced. `FAILPROOFAI_DAEMON_SOCKET` is a raw path (dev/test
59
+ /// override; see `run_dir`), and blindly tightening permissions on whatever
60
+ /// pre-existing directory its parent happens to resolve to would let a
61
+ /// misconfigured override silently reach out and chmod an unrelated shared
62
+ /// directory (worst case, something like `/tmp` itself). Failing loudly is
63
+ /// the safe default; a real deployment's `~/.failproofai/run` is always
64
+ /// failproofaid's own directory and will simply be created fresh the first
65
+ /// time, taking the safe branch below.
66
+ pub fn ensure_run_dir() -> io::Result<PathBuf> {
67
+ let dir = run_dir()?;
68
+ if dir.exists() {
69
+ let mode = fs::metadata(&dir)?.permissions().mode() & 0o777;
70
+ if mode != 0o700 {
71
+ return Err(io::Error::other(format!(
72
+ "run directory {} already exists with permissions {:o} (expected 0700) — \
73
+ refusing to modify a directory failproofaid did not create itself",
74
+ dir.display(),
75
+ mode
76
+ )));
77
+ }
78
+ return Ok(dir);
79
+ }
80
+ fs::create_dir_all(&dir)?;
81
+ fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?;
82
+ Ok(dir)
83
+ }
84
+
85
+ #[cfg(test)]
86
+ mod tests {
87
+ use super::*;
88
+ use std::sync::Mutex;
89
+
90
+ // std::env::set_var affects the whole process, so these tests must not
91
+ // run concurrently with each other or with other tests reading these
92
+ // vars.
93
+ static ENV_LOCK: Mutex<()> = Mutex::new(());
94
+
95
+ #[test]
96
+ fn socket_override_takes_precedence_over_home() {
97
+ let _guard = ENV_LOCK.lock().unwrap();
98
+ unsafe {
99
+ std::env::set_var("FAILPROOFAI_DAEMON_SOCKET", "/tmp/example/daemon.sock");
100
+ }
101
+ assert_eq!(
102
+ socket_path().unwrap(),
103
+ PathBuf::from("/tmp/example/daemon.sock")
104
+ );
105
+ assert_eq!(run_dir().unwrap(), PathBuf::from("/tmp/example"));
106
+ unsafe {
107
+ std::env::remove_var("FAILPROOFAI_DAEMON_SOCKET");
108
+ }
109
+ }
110
+
111
+ #[test]
112
+ fn default_socket_path_lives_under_home_dot_failproofai_run() {
113
+ let _guard = ENV_LOCK.lock().unwrap();
114
+ unsafe {
115
+ std::env::remove_var("FAILPROOFAI_DAEMON_SOCKET");
116
+ std::env::set_var("HOME", "/home/example-user");
117
+ }
118
+ assert_eq!(
119
+ socket_path().unwrap(),
120
+ PathBuf::from("/home/example-user/.failproofai/run/failproofaid.sock")
121
+ );
122
+ }
123
+
124
+ #[test]
125
+ fn ensure_run_dir_creates_it_with_owner_only_permissions() {
126
+ let _guard = ENV_LOCK.lock().unwrap();
127
+ let tmp =
128
+ std::env::temp_dir().join(format!("failproofaid-paths-test-{}", std::process::id()));
129
+ unsafe {
130
+ std::env::set_var(
131
+ "FAILPROOFAI_DAEMON_SOCKET",
132
+ tmp.join("run").join("failproofaid.sock"),
133
+ );
134
+ }
135
+ let dir = ensure_run_dir().unwrap();
136
+ let mode = fs::metadata(&dir).unwrap().permissions().mode() & 0o777;
137
+ assert_eq!(mode, 0o700);
138
+ fs::remove_dir_all(&tmp).ok();
139
+ unsafe {
140
+ std::env::remove_var("FAILPROOFAI_DAEMON_SOCKET");
141
+ }
142
+ }
143
+
144
+ #[test]
145
+ fn ensure_run_dir_refuses_to_touch_a_preexisting_directory_with_the_wrong_permissions() {
146
+ let _guard = ENV_LOCK.lock().unwrap();
147
+ let tmp = std::env::temp_dir().join(format!(
148
+ "failproofaid-paths-test-preexisting-{}",
149
+ std::process::id()
150
+ ));
151
+ // Simulate FAILPROOFAI_DAEMON_SOCKET being pointed at some
152
+ // unrelated, already-existing directory (e.g. a misconfigured
153
+ // override resolving to a shared temp dir) — this must error
154
+ // instead of silently chmod-ing a directory failproofaid doesn't
155
+ // own.
156
+ fs::create_dir_all(&tmp).unwrap();
157
+ fs::set_permissions(&tmp, fs::Permissions::from_mode(0o755)).unwrap();
158
+ unsafe {
159
+ std::env::set_var("FAILPROOFAI_DAEMON_SOCKET", tmp.join("failproofaid.sock"));
160
+ }
161
+
162
+ let result = ensure_run_dir();
163
+ assert!(result.is_err(), "expected an error, got {result:?}");
164
+ let mode_after = fs::metadata(&tmp).unwrap().permissions().mode() & 0o777;
165
+ assert_eq!(mode_after, 0o755, "permissions must be left untouched");
166
+
167
+ fs::remove_dir_all(&tmp).ok();
168
+ unsafe {
169
+ std::env::remove_var("FAILPROOFAI_DAEMON_SOCKET");
170
+ }
171
+ }
172
+ }