failproofai 1.0.0-beta.2 → 1.0.0-beta.4

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 (234) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  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/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  27. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  39. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  40. package/.next/standalone/.next/server/app/audit/page.js +1 -1
  41. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/index.html +1 -1
  44. package/.next/standalone/.next/server/app/index.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  47. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  48. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  49. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  50. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  56. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  57. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  71. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  72. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  73. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-dh5jy._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h3lfmp._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l4vuqm._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0longh-._.js → [root-of-the-server]__0szqzl8._.js} +2 -2
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10oa-v9._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__18d-9oi._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__1p7s8-d._.js} +2 -2
  88. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
  89. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  90. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  91. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0nb0syg._.js → [root-of-the-server]__0cmdyn_._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ath6v_._.js → [root-of-the-server]__0ls0gsr._.js} +3 -3
  98. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0uno39i._.js → [root-of-the-server]__0n_uvv0._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__0rag5dg._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0rtdfof._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1psjybo._.js → [root-of-the-server]__0sdrz-j._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0114ewg._.js → [root-of-the-server]__1j4u3yd._.js} +3 -3
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0oy_-36.js → node_modules_html-to-image_es_index_1q9xxca.js} +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +32 -20
  121. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 06jakt_xhoolk.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +6 -0
  124. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 0ue9ak_xexiqh.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  127. package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +1 -0
  128. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  129. package/.next/standalone/.next/static/chunks/2c90h1g6x8z8y.js +1 -0
  130. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 2zb3m1tzq6h98.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  132. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 31cqiv_cpgj9m.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 3h5_3u46gu9jx.js} +1 -1
  134. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
  135. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 43a1ywqsg9bq9.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  137. package/.next/standalone/Cargo.lock +1992 -198
  138. package/.next/standalone/Cargo.toml +1 -1
  139. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  140. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  141. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  142. package/.next/standalone/crates/CLOUD_POLICIES.md +136 -0
  143. package/.next/standalone/crates/failproofaid/Cargo.toml +7 -0
  144. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +484 -0
  145. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +921 -0
  146. package/.next/standalone/crates/failproofaid/src/main.rs +452 -3
  147. package/.next/standalone/crates/failproofaid/src/paths.rs +29 -0
  148. package/.next/standalone/crates/fpai-collect/Cargo.toml +43 -0
  149. package/.next/standalone/crates/fpai-collect/src/config.rs +412 -0
  150. package/.next/standalone/crates/fpai-collect/src/cursor.rs +520 -0
  151. package/.next/standalone/crates/fpai-collect/src/delivery.rs +472 -0
  152. package/.next/standalone/crates/fpai-collect/src/filetail.rs +484 -0
  153. package/.next/standalone/crates/fpai-collect/src/health.rs +369 -0
  154. package/.next/standalone/crates/fpai-collect/src/lib.rs +34 -0
  155. package/.next/standalone/crates/fpai-collect/src/redact.rs +516 -0
  156. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +279 -0
  157. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +619 -0
  158. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +156 -0
  159. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +715 -0
  160. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +158 -0
  161. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +512 -0
  162. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +511 -0
  163. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +586 -0
  164. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +318 -0
  165. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +579 -0
  166. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +310 -0
  167. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +448 -0
  168. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +23 -0
  169. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +176 -0
  170. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +500 -0
  171. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +315 -0
  172. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +493 -0
  173. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +201 -0
  174. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +533 -0
  175. package/.next/standalone/crates/fpai-collect/src/spool.rs +458 -0
  176. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +387 -0
  177. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +423 -0
  178. package/.next/standalone/crates/fpai-collect/src/uploader.rs +600 -0
  179. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +738 -0
  180. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +393 -0
  181. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +975 -0
  182. package/.next/standalone/crates/fpai-collect/tests/config.rs +286 -0
  183. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1019 -0
  184. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +281 -0
  185. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1008 -0
  186. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1129 -0
  187. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +621 -0
  188. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +854 -0
  189. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +1478 -0
  190. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1191 -0
  191. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +290 -0
  192. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +506 -0
  193. package/.next/standalone/package.json +15 -9
  194. package/.next/standalone/server.js +1 -1
  195. package/bin/failproofai.mjs +129 -0
  196. package/dist/cli.mjs +2255 -779
  197. package/dist/worker.mjs +365 -52
  198. package/package.json +15 -9
  199. package/scripts/build-daemon-packages.mjs +210 -0
  200. package/scripts/daemon-platforms.mjs +42 -0
  201. package/scripts/prune-standalone.mjs +6 -0
  202. package/scripts/publish-aliases.mjs +62 -41
  203. package/src/hooks/builtin-policies.ts +139 -0
  204. package/src/hooks/cloud-connection.ts +205 -0
  205. package/src/hooks/cloud-enrollment-cli.ts +227 -0
  206. package/src/hooks/cloud-enrollment.ts +173 -0
  207. package/src/hooks/cloud-managed-policies.ts +138 -0
  208. package/src/hooks/collector-config.ts +226 -0
  209. package/src/hooks/configure-wizard.ts +221 -5
  210. package/src/hooks/custom-hooks-loader.ts +69 -25
  211. package/src/hooks/daemon-client.ts +18 -0
  212. package/src/hooks/daemon-download.ts +137 -27
  213. package/src/hooks/daemon-service.ts +23 -8
  214. package/src/hooks/handler.ts +144 -6
  215. package/src/hooks/hook-activity-store.ts +50 -0
  216. package/src/hooks/loader-utils.ts +6 -4
  217. package/src/hooks/policy-types.ts +35 -0
  218. package/src/hooks/session-pause-cli.ts +157 -0
  219. package/src/hooks/session-pause.ts +236 -0
  220. package/src/hooks/tui.ts +115 -0
  221. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  222. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  223. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  224. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  225. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  226. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  227. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  228. package/.next/standalone/.next/static/chunks/31feojxhjyjpl.js +0 -1
  229. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  230. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  231. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  232. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_buildManifest.js +0 -0
  233. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_clientMiddlewareManifest.js +0 -0
  234. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_ssgManifest.js +0 -0
@@ -0,0 +1,286 @@
1
+ //! Configuration contract tests.
2
+ //!
3
+ //! Two of these guard a security property rather than a behaviour: the ingest
4
+ //! credential must never be written into a world-readable file, and the home
5
+ //! directory holding it must not stay world-traversable. `policies-config.json`
6
+ //! is 0664 inside a 0775 `~/.failproofai` on a normal machine, which is exactly
7
+ //! why the credential lives in its own file instead.
8
+
9
+ use std::fs;
10
+ use std::path::PathBuf;
11
+ use std::time::{SystemTime, UNIX_EPOCH};
12
+
13
+ use fpai_collect::config::{self, Ingest};
14
+ use fpai_collect::{DEFAULT_INGEST_URL, HooksVerbosity, Redact};
15
+
16
+ fn tmp_home(name: &str) -> PathBuf {
17
+ let d = std::env::temp_dir().join(format!(
18
+ "fpai-cfg-{}-{}-{}",
19
+ name,
20
+ std::process::id(),
21
+ SystemTime::now()
22
+ .duration_since(UNIX_EPOCH)
23
+ .unwrap()
24
+ .as_nanos()
25
+ ));
26
+ fs::create_dir_all(&d).unwrap();
27
+ d
28
+ }
29
+
30
+ /// The env vars this module reads are process-global, so tests that touch them
31
+ /// must not run concurrently with tests that don't expect them.
32
+ fn without_env_overrides<T>(f: impl FnOnce() -> T) -> T {
33
+ // SAFETY: single-threaded within the closure; see the serial guard below.
34
+ unsafe {
35
+ std::env::remove_var("FAILPROOFAI_INGEST_KEY");
36
+ std::env::remove_var("FAILPROOFAI_INGEST_URL");
37
+ }
38
+ f()
39
+ }
40
+
41
+ #[test]
42
+ fn absent_config_means_collection_is_off() {
43
+ let home = tmp_home("absent");
44
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
45
+ assert!(cfg.ingest.is_none());
46
+ assert!(
47
+ !cfg.is_enabled(),
48
+ "with no credential the collector must start nothing at all"
49
+ );
50
+ fs::remove_dir_all(&home).ok();
51
+ }
52
+
53
+ #[test]
54
+ fn a_key_alone_does_not_enable_session_collection() {
55
+ // The privacy default, and the one most likely to be eroded by accident.
56
+ // Transcripts carry prompts, file contents and pasted credentials, so
57
+ // configuring ingest must not start shipping them.
58
+ let home = tmp_home("optin");
59
+ config::write_ingest(
60
+ &home,
61
+ &Ingest {
62
+ url: DEFAULT_INGEST_URL.into(),
63
+ key: "k".into(),
64
+ },
65
+ )
66
+ .unwrap();
67
+
68
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
69
+ assert!(cfg.ingest.is_some());
70
+ assert!(!cfg.settings.sessions, "sessions must be opt-in");
71
+ assert!(
72
+ cfg.settings.hooks,
73
+ "hooks carry no file contents and default on"
74
+ );
75
+ assert!(
76
+ cfg.is_enabled(),
77
+ "hooks alone is enough to run the collector"
78
+ );
79
+ fs::remove_dir_all(&home).ok();
80
+ }
81
+
82
+ #[test]
83
+ fn the_credential_file_is_written_owner_only() {
84
+ #[cfg(unix)]
85
+ {
86
+ use std::os::unix::fs::PermissionsExt;
87
+ let home = tmp_home("mode");
88
+ let path = config::write_ingest(
89
+ &home,
90
+ &Ingest {
91
+ url: DEFAULT_INGEST_URL.into(),
92
+ key: "secret".into(),
93
+ },
94
+ )
95
+ .unwrap();
96
+
97
+ let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
98
+ assert_eq!(
99
+ mode, 0o600,
100
+ "the ingest credential must not be readable by other local users, got {mode:o}"
101
+ );
102
+ fs::remove_dir_all(&home).ok();
103
+ }
104
+ }
105
+
106
+ #[test]
107
+ fn writing_the_credential_tightens_a_world_traversable_home() {
108
+ // A 0600 file inside a 0775 directory is still reachable by every local
109
+ // user. ~/.failproofai really is 0775 on a normal machine.
110
+ #[cfg(unix)]
111
+ {
112
+ use std::os::unix::fs::PermissionsExt;
113
+ let home = tmp_home("tighten");
114
+ fs::set_permissions(&home, fs::Permissions::from_mode(0o775)).unwrap();
115
+
116
+ config::write_ingest(
117
+ &home,
118
+ &Ingest {
119
+ url: DEFAULT_INGEST_URL.into(),
120
+ key: "s".into(),
121
+ },
122
+ )
123
+ .unwrap();
124
+
125
+ let mode = fs::metadata(&home).unwrap().permissions().mode() & 0o777;
126
+ assert_eq!(
127
+ mode, 0o700,
128
+ "the home holding a credential must be owner-only, got {mode:o}"
129
+ );
130
+ fs::remove_dir_all(&home).ok();
131
+ }
132
+ }
133
+
134
+ #[test]
135
+ fn rewriting_an_already_permissive_credential_file_fixes_its_mode() {
136
+ // `OpenOptions::mode()` applies only when the file is CREATED, so an
137
+ // existing 0644 file would silently keep its mode without the explicit
138
+ // set_permissions afterwards.
139
+ #[cfg(unix)]
140
+ {
141
+ use std::os::unix::fs::PermissionsExt;
142
+ let home = tmp_home("rewrite");
143
+ let path = home.join("ingest.json");
144
+ fs::write(&path, "{}").unwrap();
145
+ fs::set_permissions(&path, fs::Permissions::from_mode(0o644)).unwrap();
146
+
147
+ config::write_ingest(
148
+ &home,
149
+ &Ingest {
150
+ url: DEFAULT_INGEST_URL.into(),
151
+ key: "s".into(),
152
+ },
153
+ )
154
+ .unwrap();
155
+
156
+ let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
157
+ assert_eq!(
158
+ mode, 0o600,
159
+ "an existing permissive file must be tightened, got {mode:o}"
160
+ );
161
+ fs::remove_dir_all(&home).ok();
162
+ }
163
+ }
164
+
165
+ #[test]
166
+ fn the_endpoint_defaults_to_the_hosted_one_and_round_trips() {
167
+ let home = tmp_home("url");
168
+ // A file with only a key is the common case — the wizard need not write a
169
+ // url when the user accepts the default.
170
+ fs::write(home.join("ingest.json"), r#"{"key":"abc"}"#).unwrap();
171
+
172
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
173
+ let ingest = cfg.ingest.unwrap();
174
+ assert_eq!(ingest.url, DEFAULT_INGEST_URL);
175
+ assert_eq!(ingest.key, "abc");
176
+
177
+ // A self-hoster replaces the whole endpoint, path included.
178
+ fs::write(
179
+ home.join("ingest.json"),
180
+ r#"{"key":"abc","url":"http://localhost:8080/events"}"#,
181
+ )
182
+ .unwrap();
183
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
184
+ assert_eq!(cfg.ingest.unwrap().url, "http://localhost:8080/events");
185
+ fs::remove_dir_all(&home).ok();
186
+ }
187
+
188
+ #[test]
189
+ fn an_empty_key_is_treated_as_unconfigured_rather_than_sent() {
190
+ // Otherwise every request goes out as `Authorization: Bearer ` and the
191
+ // spool fills with 401s that look like a server problem.
192
+ let home = tmp_home("emptykey");
193
+ fs::write(home.join("ingest.json"), r#"{"key":" "}"#).unwrap();
194
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
195
+ assert!(cfg.ingest.is_none());
196
+ assert!(!cfg.is_enabled());
197
+ fs::remove_dir_all(&home).ok();
198
+ }
199
+
200
+ #[test]
201
+ fn malformed_json_is_an_error_not_a_silent_disable() {
202
+ // Quietly disabling collection because of a stray comma is precisely the
203
+ // silent failure this project exists to remove.
204
+ let home = tmp_home("malformed");
205
+ fs::write(home.join("ingest.json"), r#"{"key": "abc",}"#).unwrap();
206
+ let err = without_env_overrides(|| config::load(&home)).unwrap_err();
207
+ assert!(
208
+ format!("{err}").contains("not valid JSON"),
209
+ "expected a JSON error, got: {err}"
210
+ );
211
+ fs::remove_dir_all(&home).ok();
212
+ }
213
+
214
+ #[test]
215
+ fn settings_come_from_the_policies_config_collector_block() {
216
+ let home = tmp_home("settings");
217
+ fs::write(home.join("ingest.json"), r#"{"key":"abc"}"#).unwrap();
218
+ fs::write(
219
+ home.join("policies-config.json"),
220
+ r#"{"enabledPolicies":["block-sudo"],
221
+ "collector":{"sessions":true,"hooksVerbosity":"all","redact":"off","environment":"ci"}}"#,
222
+ )
223
+ .unwrap();
224
+
225
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
226
+ assert!(cfg.settings.sessions);
227
+ assert_eq!(cfg.settings.hooks_verbosity, HooksVerbosity::All);
228
+ assert_eq!(cfg.settings.redact, Redact::Off);
229
+ assert_eq!(cfg.settings.environment, "ci");
230
+ fs::remove_dir_all(&home).ok();
231
+ }
232
+
233
+ #[test]
234
+ fn a_policies_config_with_no_collector_block_uses_defaults() {
235
+ // Every existing install is this case; it must not error or change.
236
+ let home = tmp_home("nocollector");
237
+ fs::write(home.join("ingest.json"), r#"{"key":"abc"}"#).unwrap();
238
+ fs::write(
239
+ home.join("policies-config.json"),
240
+ r#"{"enabledPolicies":[]}"#,
241
+ )
242
+ .unwrap();
243
+
244
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
245
+ assert!(!cfg.settings.sessions);
246
+ assert!(cfg.settings.hooks);
247
+ assert_eq!(cfg.settings.hooks_verbosity, HooksVerbosity::Decisions);
248
+ fs::remove_dir_all(&home).ok();
249
+ }
250
+
251
+ #[test]
252
+ fn a_comma_in_environment_is_rejected_rather_than_dropped_downstream() {
253
+ // The ingest endpoint skips any line whose `environment` contains a comma.
254
+ // Accepting it here would mean every event from this machine vanished
255
+ // server-side with nothing on this end to show for it.
256
+ let home = tmp_home("comma");
257
+ fs::write(home.join("ingest.json"), r#"{"key":"abc"}"#).unwrap();
258
+ fs::write(
259
+ home.join("policies-config.json"),
260
+ r#"{"collector":{"environment":"local,ci"}}"#,
261
+ )
262
+ .unwrap();
263
+
264
+ let err = without_env_overrides(|| config::load(&home)).unwrap_err();
265
+ assert!(format!("{err}").contains("comma"), "got: {err}");
266
+ fs::remove_dir_all(&home).ok();
267
+ }
268
+
269
+ #[test]
270
+ fn the_sdk_spool_directory_is_watched_alongside_our_own() {
271
+ // This is what lets failproofaid supersede agenteye-collector without
272
+ // every Python SDK user reconfiguring anything.
273
+ let home = tmp_home("spooldirs");
274
+ let cfg = without_env_overrides(|| config::load(&home).unwrap());
275
+
276
+ assert_eq!(cfg.spool_dirs[0], cfg.own_spool_dir);
277
+ assert_eq!(cfg.own_spool_dir, home.join("spool"));
278
+ assert!(
279
+ cfg.spool_dirs
280
+ .iter()
281
+ .any(|d| d.ends_with("events") && d.to_string_lossy().contains("agenteye")),
282
+ "the SDK's drop directory must be watched too, got {:?}",
283
+ cfg.spool_dirs
284
+ );
285
+ fs::remove_dir_all(&home).ok();
286
+ }