failproofai 1.0.0-beta.4 → 1.0.0-beta.6

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 (369) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -1
  37. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  47. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  50. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/index.html +1 -1
  53. package/.next/standalone/.next/server/app/index.rsc +15 -15
  54. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  55. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  56. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  57. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  58. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  59. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/page.js +3 -1
  61. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  64. package/.next/standalone/.next/server/app/policies/page.js +4 -2
  65. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  69. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  81. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  82. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  83. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  84. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  85. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  86. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  87. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  88. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  89. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  90. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  97. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  98. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p7s8-d._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  109. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  110. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  111. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  112. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_a_gwn._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e7-pa7._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rtdfof._.js → [root-of-the-server]__1rdruw4._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rag5dg._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  125. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/_0igtyj5._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  135. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  138. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  141. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  143. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  144. package/.next/standalone/.next/server/middleware.js +2 -2
  145. package/.next/standalone/.next/server/pages/404.html +1 -1
  146. package/.next/standalone/.next/server/pages/500.html +1 -1
  147. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  148. package/.next/standalone/.next/server/server-reference-manifest.json +54 -12
  149. package/.next/standalone/.next/static/chunks/{2c90h1g6x8z8y.js → 0_2n13i94rnw5.js} +1 -1
  150. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  151. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  152. package/.next/standalone/.next/static/chunks/23557z4e6dtsr.js +1 -0
  153. package/.next/standalone/.next/static/chunks/28zkwzu50puki.js +1 -0
  154. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  155. package/.next/standalone/.next/static/chunks/3081gykdubv8b.js +1 -0
  156. package/.next/standalone/.next/static/chunks/{06jakt_xhoolk.js → 34fa65k0cldkj.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/35v07j9gc1pvr.js +6 -0
  158. package/.next/standalone/.next/static/chunks/{2zb3m1tzq6h98.js → 3f_939wi3kfnx.js} +22 -22
  159. package/.next/standalone/.next/static/chunks/{31cqiv_cpgj9m.js → 3p1vpm2_-tfwm.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{0ue9ak_xexiqh.js → 3rbpw_waieafm.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/40ffnfpkeyvb0.js +1 -0
  162. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  163. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  164. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  165. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  166. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  167. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  168. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  169. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  170. package/.next/standalone/app/audit/audit-styles.css +6 -149
  171. package/.next/standalone/app/settings/page.tsx +31 -0
  172. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  173. package/.next/standalone/components/navbar.tsx +2 -0
  174. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  175. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  176. package/.next/standalone/lib/codex-sessions.ts +2 -1
  177. package/.next/standalone/lib/dashboard-host.ts +101 -0
  178. package/.next/standalone/lib/install-check.ts +22 -7
  179. package/.next/standalone/lib/pi-sessions.ts +99 -14
  180. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  181. package/.next/standalone/lib/telemetry-id.ts +52 -1
  182. package/.next/standalone/lib/telemetry.ts +8 -4
  183. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  184. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  185. package/.next/standalone/node_modules/react/package.json +3 -3
  186. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  187. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  188. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  189. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  190. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  191. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  192. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  193. package/.next/standalone/package.json +7 -6
  194. package/.next/standalone/proxy.ts +89 -1
  195. package/.next/standalone/server.js +1 -1
  196. package/bin/failproofai.mjs +197 -80
  197. package/dist/cli.mjs +17699 -15345
  198. package/dist/worker.mjs +1620 -257
  199. package/lib/auth/api-server-client.ts +2 -3
  200. package/lib/auth/auth-store.ts +4 -4
  201. package/lib/codex-sessions.ts +2 -1
  202. package/lib/dashboard-host.ts +101 -0
  203. package/lib/install-check.ts +22 -7
  204. package/lib/pi-sessions.ts +99 -14
  205. package/lib/telemetry-enabled.ts +54 -0
  206. package/lib/telemetry-id.ts +52 -1
  207. package/lib/telemetry.ts +8 -4
  208. package/package.json +7 -6
  209. package/scripts/install-telemetry.mjs +8 -1
  210. package/scripts/launch.ts +25 -3
  211. package/scripts/parse-script-args.ts +17 -1
  212. package/scripts/prune-standalone.mjs +20 -0
  213. package/scripts/translate-docs/readme-translator.ts +163 -4
  214. package/scripts/validate-mdx.ts +153 -13
  215. package/src/audit/audit-lock.ts +285 -0
  216. package/src/audit/audit-schedule.ts +111 -0
  217. package/src/audit/cache.ts +18 -4
  218. package/src/audit/cli-adapters/goose.ts +2 -1
  219. package/src/audit/cli-adapters/hermes.ts +28 -10
  220. package/src/audit/cli.ts +191 -35
  221. package/src/audit/dashboard-cache.ts +2 -1
  222. package/src/hooks/builtin-policies.ts +100 -3
  223. package/src/hooks/cloud-connection.ts +163 -14
  224. package/src/hooks/cloud-enrollment-cli.ts +119 -21
  225. package/src/hooks/cloud-enrollment.ts +87 -17
  226. package/src/hooks/cloud-introspect.ts +147 -0
  227. package/src/hooks/cloud-managed-policies.ts +33 -2
  228. package/src/hooks/collector-config.ts +32 -40
  229. package/src/hooks/configure-wizard.ts +696 -318
  230. package/src/hooks/custom-hooks-loader.ts +125 -11
  231. package/src/hooks/daemon-client.ts +83 -17
  232. package/src/hooks/daemon-download.ts +54 -2
  233. package/src/hooks/daemon-service.ts +595 -27
  234. package/src/hooks/first-run-gate.ts +39 -0
  235. package/src/hooks/fp-config.ts +497 -0
  236. package/src/hooks/fp-home.ts +342 -0
  237. package/src/hooks/fp-reset.ts +332 -0
  238. package/src/hooks/handler.ts +42 -1
  239. package/src/hooks/hook-activity-store.ts +35 -20
  240. package/src/hooks/hook-logger.ts +2 -1
  241. package/src/hooks/hook-telemetry.ts +5 -1
  242. package/src/hooks/hooks-config.ts +4 -3
  243. package/src/hooks/loader-utils.ts +166 -5
  244. package/src/hooks/manager.ts +5 -4
  245. package/src/hooks/onboarding-lock.ts +119 -0
  246. package/src/hooks/policy-types.ts +0 -7
  247. package/src/hooks/session-pause-cli.ts +15 -13
  248. package/src/hooks/session-pause.ts +36 -4
  249. package/src/hooks/setup-state.ts +256 -0
  250. package/src/hooks/tui.ts +4 -4
  251. package/src/hooks/worker-server.ts +53 -1
  252. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cmdyn_._.js +0 -3
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +0 -4
  264. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.js +0 -3
  265. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  266. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0sdrz-j._.js +0 -3
  267. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  268. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
  270. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  271. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  272. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1q9xxca.js +0 -3
  273. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  274. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  275. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  276. package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +0 -6
  277. package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +0 -1
  278. package/.next/standalone/.next/static/chunks/3h5_3u46gu9jx.js +0 -1
  279. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  280. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
  281. package/.next/standalone/.next/static/chunks/43a1ywqsg9bq9.js +0 -1
  282. package/.next/standalone/Cargo.lock +0 -2222
  283. package/.next/standalone/Cargo.toml +0 -9
  284. package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
  285. package/.next/standalone/crates/PROTOCOL.md +0 -122
  286. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
  287. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
  288. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
  289. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  290. package/.next/standalone/crates/failproofaid/src/main.rs +0 -532
  291. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
  292. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  293. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  294. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  295. package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
  296. package/.next/standalone/crates/fpai-collect/src/config.rs +0 -412
  297. package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
  298. package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
  299. package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -484
  300. package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
  301. package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
  302. package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
  303. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
  304. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
  305. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
  306. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
  307. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
  308. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
  309. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
  310. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
  311. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
  312. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
  313. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -310
  314. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
  315. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
  316. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
  317. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
  318. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
  319. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
  320. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
  321. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
  322. package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -458
  323. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -387
  324. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
  325. package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
  326. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -738
  327. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -393
  328. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -975
  329. package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
  330. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1019
  331. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
  332. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1008
  333. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1129
  334. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -621
  335. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -854
  336. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
  337. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1191
  338. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
  339. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
  340. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  341. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  342. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  343. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  344. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  345. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  346. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  347. package/.next/standalone/integration-suite/Dockerfile +0 -47
  348. package/.next/standalone/integration-suite/README.md +0 -129
  349. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  350. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  351. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  352. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  353. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  354. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  355. package/.next/standalone/integration-suite/report.js +0 -120
  356. package/.next/standalone/integration-suite/run.sh +0 -159
  357. package/.next/standalone/openclaw-plugin/index.js +0 -223
  358. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  359. package/.next/standalone/openclaw-plugin/package.json +0 -12
  360. package/.next/standalone/osv-scanner.toml +0 -17
  361. package/.next/standalone/pi-extension/index.ts +0 -524
  362. package/.next/standalone/pi-extension/package.json +0 -12
  363. package/.next/standalone/postcss.config.mjs +0 -8
  364. package/.next/standalone/readme-arch-hq.gif +0 -0
  365. package/.next/standalone/rust-toolchain.toml +0 -4
  366. package/src/auth/cli.ts +0 -371
  367. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_buildManifest.js +0 -0
  368. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_clientMiddlewareManifest.js +0 -0
  369. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_ssgManifest.js +0 -0
@@ -1,506 +0,0 @@
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,16 +0,0 @@
1
- [package]
2
- name = "fpai-ipc"
3
- version.workspace = true
4
- edition.workspace = true
5
- license-file.workspace = true
6
- repository.workspace = true
7
- description = "Wire protocol (framing + envelope + peer verification) shared by failproofaid and its tests"
8
- publish = false
9
-
10
- [dependencies]
11
- serde = { version = "1", features = ["derive"] }
12
- serde_json = "1"
13
- libc = "0.2"
14
-
15
- [dev-dependencies]
16
- proptest = "1"
@@ -1,175 +0,0 @@
1
- //! The JSON envelope carried inside each [`crate::framing`] frame.
2
- //!
3
- //! This mirrors the `(argv --hook <Event> --cli <cli>, stdin JSON payload)
4
- //! -> (stdout, stderr, exitCode)` contract every hook invocation already
5
- //! has today — see `src/hooks/handler.ts`'s `handleHookEvent`. The daemon
6
- //! adds nothing to that contract; it only relays it over a socket instead
7
- //! of a fresh process's argv/stdin/stdout.
8
-
9
- use serde::{Deserialize, Serialize};
10
-
11
- /// Bumped whenever a wire-incompatible change is made to either message
12
- /// enum below. The client treats any mismatch identically to "daemon
13
- /// unreachable" (see `docs/configuration.mdx` and the failproofaid plan) —
14
- /// there is no negotiation, only agree-or-fall-back.
15
- pub const PROTOCOL_VERSION: u32 = 1;
16
-
17
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18
- #[serde(tag = "type", rename_all = "camelCase")]
19
- pub enum ClientMessage {
20
- /// Cheap liveness/handshake check — used by tests and by a client that
21
- /// only wants to confirm a compatible daemon is listening.
22
- #[serde(rename_all = "camelCase")]
23
- Ping { protocol_version: u32 },
24
- /// One hook evaluation request, corresponding 1:1 to one
25
- /// `failproofai --hook <Event> --cli <cli>` invocation.
26
- #[serde(rename_all = "camelCase")]
27
- Hook {
28
- protocol_version: u32,
29
- hook_event: String,
30
- cli: String,
31
- /// The raw stdin payload the calling agent CLI wrote to the
32
- /// one-shot `failproofai` process — forwarded verbatim.
33
- stdin: String,
34
- /// Best-effort cwd of the *originating* CLI process, not the
35
- /// daemon's own cwd (the daemon is a single long-lived process; its
36
- /// own `cwd` does not vary per request and must never be used to
37
- /// resolve project config or custom policies).
38
- cwd: Option<String>,
39
- },
40
- }
41
-
42
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
43
- #[serde(tag = "type", rename_all = "camelCase")]
44
- pub enum ServerMessage {
45
- #[serde(rename_all = "camelCase")]
46
- Pong { protocol_version: u32 },
47
- #[serde(rename_all = "camelCase")]
48
- HookResult {
49
- protocol_version: u32,
50
- exit_code: i32,
51
- stdout: String,
52
- stderr: String,
53
- },
54
- /// The daemon accepted the connection and parsed the request, but could
55
- /// not produce a verdict (e.g. the worker is down/hung). Distinct from
56
- /// `HookResult` so a client can tell "ran and decided" apart from
57
- /// "daemon couldn't evaluate at all" — the latter is what drives the
58
- /// client's fail-closed path.
59
- #[serde(rename_all = "camelCase")]
60
- Error {
61
- protocol_version: u32,
62
- message: String,
63
- },
64
- }
65
-
66
- impl ClientMessage {
67
- pub fn protocol_version(&self) -> u32 {
68
- match self {
69
- ClientMessage::Ping { protocol_version } => *protocol_version,
70
- ClientMessage::Hook {
71
- protocol_version, ..
72
- } => *protocol_version,
73
- }
74
- }
75
- }
76
-
77
- impl ServerMessage {
78
- pub fn protocol_version(&self) -> u32 {
79
- match self {
80
- ServerMessage::Pong { protocol_version } => *protocol_version,
81
- ServerMessage::HookResult {
82
- protocol_version, ..
83
- } => *protocol_version,
84
- ServerMessage::Error {
85
- protocol_version, ..
86
- } => *protocol_version,
87
- }
88
- }
89
- }
90
-
91
- #[cfg(test)]
92
- mod tests {
93
- use super::*;
94
-
95
- #[test]
96
- fn ping_serializes_with_a_type_tag() {
97
- let msg = ClientMessage::Ping {
98
- protocol_version: PROTOCOL_VERSION,
99
- };
100
- let json = serde_json::to_value(&msg).unwrap();
101
- assert_eq!(json["type"], "ping");
102
- assert_eq!(json["protocolVersion"], PROTOCOL_VERSION);
103
- }
104
-
105
- #[test]
106
- fn hook_request_uses_camel_case_field_names_on_the_wire() {
107
- let msg = ClientMessage::Hook {
108
- protocol_version: PROTOCOL_VERSION,
109
- hook_event: "PreToolUse".to_string(),
110
- cli: "claude".to_string(),
111
- stdin: "{}".to_string(),
112
- cwd: Some("/repo".to_string()),
113
- };
114
- let json = serde_json::to_value(&msg).unwrap();
115
- assert_eq!(json["type"], "hook");
116
- assert_eq!(json["hookEvent"], "PreToolUse");
117
- assert_eq!(json["cli"], "claude");
118
- assert_eq!(json["cwd"], "/repo");
119
- }
120
-
121
- #[test]
122
- fn hook_request_cwd_is_optional() {
123
- let json = serde_json::json!({
124
- "type": "hook",
125
- "protocolVersion": 1,
126
- "hookEvent": "Stop",
127
- "cli": "codex",
128
- "stdin": "{}"
129
- });
130
- let msg: ClientMessage = serde_json::from_value(json).unwrap();
131
- match msg {
132
- ClientMessage::Hook { cwd, .. } => assert_eq!(cwd, None),
133
- _ => panic!("expected Hook variant"),
134
- }
135
- }
136
-
137
- #[test]
138
- fn unknown_message_type_fails_to_deserialize() {
139
- let json = serde_json::json!({ "type": "bogus", "protocolVersion": 1 });
140
- let result: Result<ClientMessage, _> = serde_json::from_value(json);
141
- assert!(result.is_err());
142
- }
143
-
144
- #[test]
145
- fn server_hook_result_round_trips() {
146
- let msg = ServerMessage::HookResult {
147
- protocol_version: PROTOCOL_VERSION,
148
- exit_code: 2,
149
- stdout: String::new(),
150
- stderr: "blocked".to_string(),
151
- };
152
- let json = serde_json::to_string(&msg).unwrap();
153
- let decoded: ServerMessage = serde_json::from_str(&json).unwrap();
154
- assert_eq!(decoded, msg);
155
- }
156
-
157
- #[test]
158
- fn protocol_version_accessor_matches_every_variant() {
159
- assert_eq!(
160
- ClientMessage::Ping {
161
- protocol_version: 7
162
- }
163
- .protocol_version(),
164
- 7
165
- );
166
- assert_eq!(
167
- ServerMessage::Error {
168
- protocol_version: 9,
169
- message: "x".to_string()
170
- }
171
- .protocol_version(),
172
- 9
173
- );
174
- }
175
- }