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,506 @@
1
+ //! Uploader behaviour against a real HTTP server.
2
+ //!
3
+ //! The unit tests in the module cover the pure parts (filename retry state,
4
+ //! line splitting). These cover the parts that only show up over the wire, and
5
+ //! each one guards a way of losing data silently rather than loudly.
6
+
7
+ use std::fs;
8
+ use std::path::{Path, PathBuf};
9
+ use std::sync::atomic::Ordering;
10
+ use std::time::{Duration, SystemTime, UNIX_EPOCH};
11
+
12
+ use fpai_collect::Uploader;
13
+ use wiremock::matchers::{header, method, path};
14
+ use wiremock::{Mock, MockServer, ResponseTemplate};
15
+
16
+ fn tmpdir(name: &str) -> PathBuf {
17
+ let d = std::env::temp_dir().join(format!(
18
+ "fpai-up-{}-{}-{}",
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
+ fn write_batch(dir: &Path, name: &str, lines: usize) -> PathBuf {
31
+ let p = dir.join(name);
32
+ let body: String = (0..lines)
33
+ .map(|i| format!("{{\"type\":\"tool_use\",\"n\":{i}}}\n"))
34
+ .collect();
35
+ fs::write(&p, body).unwrap();
36
+ p
37
+ }
38
+
39
+ /// Fast retries so a test does not wait out a real backoff.
40
+ fn uploader(server: &MockServer, failed: &Path) -> Uploader {
41
+ Uploader::new(
42
+ format!("{}/events", server.uri()),
43
+ "test-key".into(),
44
+ failed.to_path_buf(),
45
+ )
46
+ .unwrap()
47
+ .with_retry_base(Duration::from_millis(1))
48
+ }
49
+
50
+ fn parked(dir: &Path) -> Vec<String> {
51
+ let mut v: Vec<String> = fs::read_dir(dir)
52
+ .map(|rd| {
53
+ rd.filter_map(|e| e.ok())
54
+ .map(|e| e.file_name().to_string_lossy().to_string())
55
+ .collect()
56
+ })
57
+ .unwrap_or_default();
58
+ v.sort();
59
+ v
60
+ }
61
+
62
+ #[tokio::test]
63
+ async fn a_2xx_with_an_accepting_ack_deletes_the_batch() {
64
+ let server = MockServer::start().await;
65
+ Mock::given(method("POST"))
66
+ .and(path("/events"))
67
+ .and(header("content-type", "application/x-ndjson"))
68
+ .and(header("authorization", "Bearer test-key"))
69
+ .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
70
+ "accepted": 3, "skipped": 0
71
+ })))
72
+ .mount(&server)
73
+ .await;
74
+
75
+ let spool = tmpdir("ok-spool");
76
+ let failed = tmpdir("ok-failed");
77
+ let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
78
+
79
+ let up = uploader(&server, &failed);
80
+ up.upload_file(&batch).await.unwrap();
81
+
82
+ assert!(!batch.exists(), "a delivered batch must be deleted");
83
+ assert!(parked(&failed).is_empty());
84
+ assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 3);
85
+ assert!(up.metrics().last_ok_ts.load(Ordering::Relaxed) > 0);
86
+
87
+ fs::remove_dir_all(&spool).ok();
88
+ fs::remove_dir_all(&failed).ok();
89
+ }
90
+
91
+ #[tokio::test]
92
+ async fn a_200_that_stored_nothing_is_counted_as_fully_skipped() {
93
+ // The failure this exists for: a systematically malformed transform gets
94
+ // HTTP 200 for every batch while the server stores none of it. Without
95
+ // reading the ack body that is indistinguishable from working perfectly.
96
+ let server = MockServer::start().await;
97
+ Mock::given(method("POST"))
98
+ .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
99
+ "accepted": 0, "skipped": 5
100
+ })))
101
+ .mount(&server)
102
+ .await;
103
+
104
+ let spool = tmpdir("skip-spool");
105
+ let failed = tmpdir("skip-failed");
106
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 5);
107
+
108
+ let up = uploader(&server, &failed);
109
+ up.upload_file(&batch).await.unwrap();
110
+
111
+ let m = up.metrics();
112
+ assert_eq!(m.batches_fully_skipped.load(Ordering::Relaxed), 1);
113
+ assert_eq!(m.skipped_total.load(Ordering::Relaxed), 5);
114
+ assert_eq!(m.accepted_total.load(Ordering::Relaxed), 0);
115
+
116
+ fs::remove_dir_all(&spool).ok();
117
+ fs::remove_dir_all(&failed).ok();
118
+ }
119
+
120
+ #[tokio::test]
121
+ async fn a_4xx_parks_immediately_with_its_status_and_is_not_retried() {
122
+ let server = MockServer::start().await;
123
+ Mock::given(method("POST"))
124
+ .respond_with(ResponseTemplate::new(401))
125
+ .expect(1) // exactly one attempt: retrying a rotated key is pointless
126
+ .mount(&server)
127
+ .await;
128
+
129
+ let spool = tmpdir("4xx-spool");
130
+ let failed = tmpdir("4xx-failed");
131
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 2);
132
+
133
+ let up = uploader(&server, &failed);
134
+ let err = up.upload_file(&batch).await.unwrap_err();
135
+ assert!(format!("{err}").contains("401"));
136
+
137
+ let files = parked(&failed);
138
+ assert_eq!(files.len(), 1, "got {files:?}");
139
+ assert!(
140
+ files[0].contains(".c401"),
141
+ "the status must be recorded so an automatic retry pass skips it: {files:?}"
142
+ );
143
+ assert!(!batch.exists(), "the batch moved rather than being copied");
144
+
145
+ fs::remove_dir_all(&spool).ok();
146
+ fs::remove_dir_all(&failed).ok();
147
+ }
148
+
149
+ #[tokio::test]
150
+ async fn a_429_is_retried_rather_than_treated_as_definitive() {
151
+ // 408 and 429 are the two 4xx that mean "try again". Parking them would
152
+ // strand a batch the server explicitly asked us to resend.
153
+ let server = MockServer::start().await;
154
+ Mock::given(method("POST"))
155
+ .respond_with(ResponseTemplate::new(429).insert_header("retry-after", "0"))
156
+ .expect(5) // max_retries, not 1
157
+ .mount(&server)
158
+ .await;
159
+
160
+ let spool = tmpdir("429-spool");
161
+ let failed = tmpdir("429-failed");
162
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
163
+
164
+ let up = uploader(&server, &failed);
165
+ let err = up.upload_file(&batch).await.unwrap_err();
166
+ assert!(format!("{err}").contains("429"));
167
+
168
+ // Parked WITHOUT a client status, so it stays auto-retryable.
169
+ let files = parked(&failed);
170
+ assert_eq!(files.len(), 1, "got {files:?}");
171
+ assert!(
172
+ !files[0].contains(".c429"),
173
+ "a retryable status must not be recorded as definitive: {files:?}"
174
+ );
175
+
176
+ fs::remove_dir_all(&spool).ok();
177
+ fs::remove_dir_all(&failed).ok();
178
+ }
179
+
180
+ #[tokio::test]
181
+ async fn a_5xx_retries_then_parks_without_a_client_status() {
182
+ let server = MockServer::start().await;
183
+ Mock::given(method("POST"))
184
+ .respond_with(ResponseTemplate::new(503))
185
+ .expect(5)
186
+ .mount(&server)
187
+ .await;
188
+
189
+ let spool = tmpdir("5xx-spool");
190
+ let failed = tmpdir("5xx-failed");
191
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
192
+
193
+ let up = uploader(&server, &failed);
194
+ let err = up.upload_file(&batch).await.unwrap_err();
195
+ assert!(format!("{err}").contains("503"));
196
+
197
+ let files = parked(&failed);
198
+ assert_eq!(files.len(), 1);
199
+ assert!(files[0].contains(".a1"), "attempt count encoded: {files:?}");
200
+ assert!(
201
+ !files[0].ends_with(".poison"),
202
+ "one failure is not poison yet"
203
+ );
204
+
205
+ fs::remove_dir_all(&spool).ok();
206
+ fs::remove_dir_all(&failed).ok();
207
+ }
208
+
209
+ #[tokio::test]
210
+ async fn a_transient_failure_that_recovers_still_delivers() {
211
+ let server = MockServer::start().await;
212
+ // Fail twice, then succeed. Mounted newest-first by wiremock, so the
213
+ // limited mock is consulted before the fallback.
214
+ Mock::given(method("POST"))
215
+ .respond_with(ResponseTemplate::new(500))
216
+ .up_to_n_times(2)
217
+ .mount(&server)
218
+ .await;
219
+ Mock::given(method("POST"))
220
+ .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
221
+ "accepted": 1, "skipped": 0
222
+ })))
223
+ .mount(&server)
224
+ .await;
225
+
226
+ let spool = tmpdir("flap-spool");
227
+ let failed = tmpdir("flap-failed");
228
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
229
+
230
+ let up = uploader(&server, &failed);
231
+ up.upload_file(&batch).await.unwrap();
232
+
233
+ assert!(!batch.exists());
234
+ assert!(
235
+ parked(&failed).is_empty(),
236
+ "a recovered batch must not be parked"
237
+ );
238
+ assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 1);
239
+
240
+ fs::remove_dir_all(&spool).ok();
241
+ fs::remove_dir_all(&failed).ok();
242
+ }
243
+
244
+ #[tokio::test]
245
+ async fn an_oversized_batch_is_split_and_every_chunk_delivered() {
246
+ let server = MockServer::start().await;
247
+ Mock::given(method("POST"))
248
+ .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
249
+ "accepted": 1, "skipped": 0
250
+ })))
251
+ .mount(&server)
252
+ .await;
253
+
254
+ let spool = tmpdir("split-spool");
255
+ let failed = tmpdir("split-failed");
256
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", 50);
257
+
258
+ // Cap well under the file size, forcing several requests.
259
+ let up = uploader(&server, &failed).with_max_upload_bytes(64);
260
+ up.upload_file(&batch).await.unwrap();
261
+
262
+ let requests = server.received_requests().await.unwrap();
263
+ assert!(
264
+ requests.len() > 1,
265
+ "expected a split, got {} request(s)",
266
+ requests.len()
267
+ );
268
+
269
+ // Splitting happens IN MEMORY. Chunks written to disk beside the original
270
+ // would be files the watcher had never seen, so it would post them
271
+ // concurrently — the same payload delivered twice.
272
+ let leftovers: Vec<String> = fs::read_dir(&spool)
273
+ .unwrap()
274
+ .filter_map(|e| e.ok())
275
+ .map(|e| e.file_name().to_string_lossy().to_string())
276
+ .collect();
277
+ assert!(
278
+ leftovers.is_empty(),
279
+ "split chunks must never hit the spool: {leftovers:?}"
280
+ );
281
+
282
+ fs::remove_dir_all(&spool).ok();
283
+ fs::remove_dir_all(&failed).ok();
284
+ }
285
+
286
+ #[tokio::test]
287
+ async fn a_vanished_batch_is_not_an_error() {
288
+ // Two tasks can race for the same file; whoever loses must not report a
289
+ // failure for work the other already did.
290
+ let server = MockServer::start().await;
291
+ let spool = tmpdir("gone-spool");
292
+ let failed = tmpdir("gone-failed");
293
+ let up = uploader(&server, &failed);
294
+
295
+ up.upload_file(&spool.join("never-existed.jsonl"))
296
+ .await
297
+ .unwrap();
298
+
299
+ fs::remove_dir_all(&spool).ok();
300
+ fs::remove_dir_all(&failed).ok();
301
+ }
302
+
303
+ #[tokio::test]
304
+ async fn parking_never_overwrites_an_existing_parked_batch() {
305
+ // A parked batch is the last copy of data the server does not have, so a
306
+ // name collision must not destroy one.
307
+ let server = MockServer::start().await;
308
+ Mock::given(method("POST"))
309
+ .respond_with(ResponseTemplate::new(401))
310
+ .mount(&server)
311
+ .await;
312
+
313
+ let spool = tmpdir("collide-spool");
314
+ let failed = tmpdir("collide-failed");
315
+ let up = uploader(&server, &failed);
316
+
317
+ for i in 0..2 {
318
+ let batch = write_batch(&spool, "claude-s-1-0.jsonl", i + 1);
319
+ let _ = up.upload_file(&batch).await;
320
+ }
321
+
322
+ let files = parked(&failed);
323
+ assert_eq!(
324
+ files.len(),
325
+ 2,
326
+ "both parked batches must survive: {files:?}"
327
+ );
328
+
329
+ fs::remove_dir_all(&spool).ok();
330
+ fs::remove_dir_all(&failed).ok();
331
+ }
332
+
333
+ #[tokio::test]
334
+ async fn a_batch_written_by_the_spool_writer_is_delivered_end_to_end() {
335
+ // The two halves have to agree on the filename shape and the NDJSON body.
336
+ // Testing them separately would let a change to either drift silently.
337
+ let server = MockServer::start().await;
338
+ Mock::given(method("POST"))
339
+ .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
340
+ "accepted": 2, "skipped": 0
341
+ })))
342
+ .mount(&server)
343
+ .await;
344
+
345
+ let spool = tmpdir("e2e-spool");
346
+ let failed = tmpdir("e2e-failed");
347
+
348
+ let mut w = fpai_collect::SpoolWriter::new(
349
+ spool.clone(),
350
+ fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
351
+ "hooks",
352
+ "18efefd6",
353
+ );
354
+ w.push(serde_json::json!({
355
+ "timestamp": "2026-08-03T10:00:00.000000Z", "session_id": "s1",
356
+ "agent_id": "claude", "type": "hook_triggered", "hook_name": "PreToolUse"
357
+ }))
358
+ .await
359
+ .unwrap();
360
+ w.push(serde_json::json!({
361
+ "timestamp": "2026-08-03T10:00:00.000100Z", "session_id": "s1",
362
+ "agent_id": "claude", "type": "hook_completed", "hook_name": "PreToolUse"
363
+ }))
364
+ .await
365
+ .unwrap();
366
+ w.flush().await.unwrap();
367
+
368
+ let batch = fs::read_dir(&spool)
369
+ .unwrap()
370
+ .filter_map(|e| e.ok())
371
+ .map(|e| e.path())
372
+ .find(|p| fpai_collect::spool::is_batch_file(p))
373
+ .expect("the spool writer should have produced a batch");
374
+
375
+ let up = uploader(&server, &failed);
376
+ up.upload_file(&batch).await.unwrap();
377
+
378
+ assert!(!batch.exists());
379
+ assert!(parked(&failed).is_empty());
380
+ assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 2);
381
+
382
+ // The body the server received is exactly the NDJSON the spool wrote.
383
+ let reqs = server.received_requests().await.unwrap();
384
+ assert_eq!(reqs.len(), 1);
385
+ let body = String::from_utf8(reqs[0].body.clone()).unwrap();
386
+ assert_eq!(body.lines().count(), 2);
387
+ assert!(body.contains("hook_triggered") && body.contains("hook_completed"));
388
+
389
+ fs::remove_dir_all(&spool).ok();
390
+ fs::remove_dir_all(&failed).ok();
391
+ }
392
+
393
+ // ---------------------------------------------------------------------------
394
+ // Redirect / non-ack hardening
395
+ //
396
+ // An adversarial red-team pointed a live daemon at a dashboard-lookalike
397
+ // (POST /events -> 307 -> /login -> 200 text/html). reqwest followed the
398
+ // redirect, re-POSTed the batch to the login page, parsed the HTML as a
399
+ // default ack, and DELETED the spool file. Cross-host, the same follow shipped
400
+ // full event payloads to an attacker. These pin the fix.
401
+ // ---------------------------------------------------------------------------
402
+
403
+ #[tokio::test]
404
+ async fn a_redirect_is_parked_not_followed() {
405
+ // The exfiltration vector: a 3xx must never be followed, or the batch (and
406
+ // its prompts/command text) is re-POSTed to wherever Location points.
407
+ let server = MockServer::start().await;
408
+ Mock::given(method("POST"))
409
+ .and(path("/events"))
410
+ .respond_with(ResponseTemplate::new(307).insert_header("location", "/login?next=%2Fevents"))
411
+ .expect(1) // exactly one POST — the redirect is NOT followed to /login
412
+ .mount(&server)
413
+ .await;
414
+ // If the client followed the redirect it would hit this; it must not.
415
+ Mock::given(method("POST"))
416
+ .and(path("/login"))
417
+ .respond_with(ResponseTemplate::new(200).set_body_string("<html>login</html>"))
418
+ .expect(0)
419
+ .mount(&server)
420
+ .await;
421
+
422
+ let spool = tmpdir("redir-spool");
423
+ let failed = tmpdir("redir-failed");
424
+ let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
425
+
426
+ let up = uploader(&server, &failed);
427
+ let err = up.upload_file(&batch).await.unwrap_err();
428
+ assert!(format!("{err}").contains("307"), "got {err}");
429
+
430
+ let files = parked(&failed);
431
+ assert_eq!(
432
+ files.len(),
433
+ 1,
434
+ "the batch must be parked, not lost: {files:?}"
435
+ );
436
+ assert!(
437
+ files[0].contains(".c307"),
438
+ "the redirect status must be recorded: {files:?}"
439
+ );
440
+ assert!(
441
+ !batch.exists(),
442
+ "the batch moved to failed/, it was not deleted as delivered"
443
+ );
444
+
445
+ fs::remove_dir_all(&spool).ok();
446
+ fs::remove_dir_all(&failed).ok();
447
+ }
448
+
449
+ #[tokio::test]
450
+ async fn a_200_that_is_not_an_ingest_ack_is_parked_not_deleted() {
451
+ // A login page, a proxy or a static host can answer 200. Without a numeric
452
+ // `accepted` it is not delivery, and treating it as such deletes the only
453
+ // copy of the data.
454
+ let server = MockServer::start().await;
455
+ Mock::given(method("POST"))
456
+ .respond_with(ResponseTemplate::new(200).set_body_string("<html>hello</html>"))
457
+ .expect(1)
458
+ .mount(&server)
459
+ .await;
460
+
461
+ let spool = tmpdir("noack-spool");
462
+ let failed = tmpdir("noack-failed");
463
+ let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
464
+
465
+ let up = uploader(&server, &failed);
466
+ let err = up.upload_file(&batch).await.unwrap_err();
467
+ assert!(format!("{err}").contains("200"), "got {err}");
468
+
469
+ let files = parked(&failed);
470
+ assert_eq!(files.len(), 1, "a non-ack 200 must be parked: {files:?}");
471
+ assert!(
472
+ !batch.exists(),
473
+ "the batch moved to failed/ rather than vanishing"
474
+ );
475
+
476
+ fs::remove_dir_all(&spool).ok();
477
+ fs::remove_dir_all(&failed).ok();
478
+ }
479
+
480
+ #[tokio::test]
481
+ async fn a_200_whose_json_lacks_accepted_is_parked() {
482
+ // `{"status":"ok"}` parses as JSON but is not an ingest ack. The required
483
+ // `accepted` field is what rejects it.
484
+ let server = MockServer::start().await;
485
+ Mock::given(method("POST"))
486
+ .respond_with(
487
+ ResponseTemplate::new(200)
488
+ .insert_header("content-type", "application/json")
489
+ .set_body_string(r#"{"status":"ok"}"#),
490
+ )
491
+ .expect(1)
492
+ .mount(&server)
493
+ .await;
494
+
495
+ let spool = tmpdir("badack-spool");
496
+ let failed = tmpdir("badack-failed");
497
+ let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 2);
498
+
499
+ let up = uploader(&server, &failed);
500
+ assert!(up.upload_file(&batch).await.is_err());
501
+ assert_eq!(parked(&failed).len(), 1, "must be parked");
502
+ assert!(!batch.exists());
503
+
504
+ fs::remove_dir_all(&spool).ok();
505
+ fs::remove_dir_all(&failed).ok();
506
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -76,19 +76,19 @@
76
76
  "@anthropic-ai/sdk": "^0.115.0",
77
77
  "@mdx-js/mdx": "^3.1.1",
78
78
  "@tailwindcss/postcss": "^4.3.1",
79
- "@tanstack/react-virtual": "^3.14.3",
79
+ "@tanstack/react-virtual": "^3.14.9",
80
80
  "@testing-library/jest-dom": "^7.0.0",
81
81
  "@testing-library/react": "^16.3.2",
82
82
  "@testing-library/user-event": "^14.6.1",
83
- "@types/node": "26.1.1",
83
+ "@types/node": "26.1.2",
84
84
  "@types/react": "19.2.17",
85
85
  "@types/react-dom": "^19.2.3",
86
- "@vitejs/plugin-react": "^6.0.1",
86
+ "@vitejs/plugin-react": "^6.0.5",
87
87
  "clsx": "^2.1.1",
88
88
  "eslint": "^10.5.0",
89
89
  "eslint-config-next": "^16.2.9",
90
- "jsdom": "^30.0.0",
91
- "lucide-react": "^1.18.0",
90
+ "jsdom": "^30.0.1",
91
+ "lucide-react": "^1.28.0",
92
92
  "next": "^16.2.11",
93
93
  "react": "^19.2.4",
94
94
  "react-dom": "^19.2.4",
@@ -100,7 +100,7 @@
100
100
  "dependencies": {
101
101
  "html-to-image": "^1.11.13",
102
102
  "html2canvas": "^1.4.1",
103
- "posthog-node": "^5.37.1",
103
+ "posthog-node": "^5.47.7",
104
104
  "sql.js": "^1.14.1",
105
105
  "yaml": "^2.9.0"
106
106
  },
@@ -108,8 +108,14 @@
108
108
  "postcss": "8.5.23",
109
109
  "eslint-plugin-react-hooks": "7.0.1",
110
110
  "vite": "8.0.16",
111
- "undici": "7.28.0",
112
- "brace-expansion": "5.0.8",
111
+ "undici": "7.29.0",
112
+ "brace-expansion": "5.0.9",
113
113
  "sharp": "0.35.0"
114
+ },
115
+ "optionalDependencies": {
116
+ "@failproofai/failproofaid-linux-x64": "1.0.0-beta.4",
117
+ "@failproofai/failproofaid-linux-arm64": "1.0.0-beta.4",
118
+ "@failproofai/failproofaid-darwin-x64": "1.0.0-beta.4",
119
+ "@failproofai/failproofaid-darwin-arm64": "1.0.0-beta.4"
114
120
  }
115
121
  }
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
9
9
  const hostname = process.env.HOSTNAME || '0.0.0.0'
10
10
 
11
11
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
12
- const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.0-beta.2"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
12
+ const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.0-beta.4"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":false,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"partialFallbacks":false,"dynamicOnHover":false,"varyParams":false,"prefetchInlining":false,"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":false,"viewTransition":false,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":false,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":false,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"distDirRoot":".next"}
13
13
 
14
14
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
15
15