failproofai 1.0.0-beta.5 → 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 (367) 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/{2cqk4a9s893oy.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/{1bw4dfzflelcv.js → 23557z4e6dtsr.js} +1 -1
  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/{2k285z07qce47.js → 34fa65k0cldkj.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{34sjdw8lv46e3.js → 35v07j9gc1pvr.js} +2 -2
  158. package/.next/standalone/.next/static/chunks/{1bkynffp3q1f7.js → 3f_939wi3kfnx.js} +22 -22
  159. package/.next/standalone/.next/static/chunks/{2v1rpzrh2i1wv.js → 3p1vpm2_-tfwm.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{0cr85wuc9gc9k.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 +17636 -15284
  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 +160 -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 +25 -43
  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]__094nop3._.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]__0u02miy._.js +0 -4
  267. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  268. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1gak4pd._.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_12c_2cz.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/1tp9dex23zs8y.js +0 -1
  277. package/.next/standalone/.next/static/chunks/2s9g1dsde_ts1.js +0 -1
  278. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  279. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
  280. package/.next/standalone/Cargo.lock +0 -2222
  281. package/.next/standalone/Cargo.toml +0 -9
  282. package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
  283. package/.next/standalone/crates/PROTOCOL.md +0 -122
  284. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
  285. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
  286. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
  287. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  288. package/.next/standalone/crates/failproofaid/src/main.rs +0 -639
  289. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
  290. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  291. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  292. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  293. package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
  294. package/.next/standalone/crates/fpai-collect/src/config.rs +0 -420
  295. package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
  296. package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
  297. package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -487
  298. package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
  299. package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
  300. package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
  301. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
  302. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
  303. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
  304. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
  305. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
  306. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
  307. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
  308. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
  309. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
  310. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
  311. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -314
  312. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
  313. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
  314. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
  315. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
  316. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
  317. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
  318. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
  319. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
  320. package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -535
  321. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -390
  322. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
  323. package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
  324. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -739
  325. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -394
  326. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -976
  327. package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
  328. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1020
  329. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
  330. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1009
  331. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1130
  332. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -622
  333. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -855
  334. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
  335. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1192
  336. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
  337. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
  338. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  339. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  340. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  341. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  342. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  343. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  344. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  345. package/.next/standalone/integration-suite/Dockerfile +0 -47
  346. package/.next/standalone/integration-suite/README.md +0 -129
  347. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  348. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  349. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  350. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  351. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  352. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  353. package/.next/standalone/integration-suite/report.js +0 -120
  354. package/.next/standalone/integration-suite/run.sh +0 -159
  355. package/.next/standalone/openclaw-plugin/index.js +0 -223
  356. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  357. package/.next/standalone/openclaw-plugin/package.json +0 -12
  358. package/.next/standalone/osv-scanner.toml +0 -17
  359. package/.next/standalone/pi-extension/index.ts +0 -524
  360. package/.next/standalone/pi-extension/package.json +0 -12
  361. package/.next/standalone/postcss.config.mjs +0 -8
  362. package/.next/standalone/readme-arch-hq.gif +0 -0
  363. package/.next/standalone/rust-toolchain.toml +0 -4
  364. package/src/auth/cli.ts +0 -371
  365. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → Ptn8aBQl_cnNgOd0ebyv0}/_buildManifest.js +0 -0
  366. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → Ptn8aBQl_cnNgOd0ebyv0}/_clientMiddlewareManifest.js +0 -0
  367. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → Ptn8aBQl_cnNgOd0ebyv0}/_ssgManifest.js +0 -0
@@ -1,535 +0,0 @@
1
- //! Durable hand-off between a source and the uploader.
2
- //!
3
- //! A source transforms records into events and pushes them here; this writes
4
- //! them as `<prefix>-<tag>-<run>-<seq>.jsonl` batches into the spool directory,
5
- //! where the watcher and sweeper pick them up. The agent's own files and
6
- //! databases are never handed to the uploader, so they are never touched.
7
- //!
8
- //! # Three properties that are load-bearing
9
- //!
10
- //! **Writes are atomic.** Every batch is written to `.tmp`, fsynced, then
11
- //! renamed into place. A reader therefore never observes a partial file — and
12
- //! `.tmp` is not `.jsonl`, so the watcher skips it without needing to know
13
- //! this. Combined with "flush before advancing the cursor", a crash costs at
14
- //! most a re-ship, never a lost event.
15
- //!
16
- //! **No single line may exceed the batch cap.** A line larger than one request
17
- //! could never be delivered: it would fail, be parked, be retried at the same
18
- //! size, and fail identically forever. So oversized events are first shrunk by
19
- //! truncating their long string fields, and dropped with a warning if they are
20
- //! still too big. Dropping is the honest outcome — the record remains in the
21
- //! agent's own store, so nothing is lost from the source, whereas parking it
22
- //! would quietly accumulate undeliverable files.
23
- //!
24
- //! **Truncation is deterministic.** The server dedups on a content hash, so
25
- //! the same input must always produce the same bytes. That rules out anything
26
- //! involving time, randomness or a sampled decision, and is why truncation
27
- //! marks the original length rather than, say, the moment it was cut.
28
-
29
- use std::path::{Path, PathBuf};
30
- use std::time::{SystemTime, UNIX_EPOCH};
31
-
32
- use serde_json::Value;
33
- use tokio::io::AsyncWriteExt;
34
-
35
- use crate::config::Redact;
36
-
37
- /// Longest single string field kept intact. One oversized tool output must not
38
- /// be able to push a whole batch past the body cap.
39
- const MAX_FIELD_BYTES: usize = 1024 * 1024;
40
-
41
- /// Floor for the batch cap. A cap below this would roll a new file per event.
42
- const MIN_BATCH_BYTES: u64 = 64 * 1024;
43
-
44
- /// Default batch cap, matching the ingest endpoint's body limit.
45
- pub const DEFAULT_MAX_BATCH_BYTES: u64 = 8 * 1024 * 1024;
46
-
47
- /// Accumulates events and writes them out as size-bounded batches.
48
- pub struct SpoolWriter {
49
- dir: PathBuf,
50
- max_batch_bytes: u64,
51
- prefix: String,
52
- tag: String,
53
- run_id: u128,
54
- seq: u64,
55
- buf: String,
56
- buf_bytes: u64,
57
- redact: Redact,
58
- redacted: u64,
59
- /// The machine this daemon runs on, stamped on every event so the server
60
- /// can tell one machine from another. Without it, events carry only
61
- /// `agent_id` — a per-project/per-harness identity — and the fleet views
62
- /// mistake every project for a separate machine. `None` on a machine that
63
- /// was never enrolled with a machine id (older config); such events simply
64
- /// carry no machine and are excluded from machine-level counts rather than
65
- /// guessed into one.
66
- machine_id: Option<String>,
67
- }
68
-
69
- impl SpoolWriter {
70
- /// `prefix` is the source kind (`claude`, `hooks`, …) and `tag` a short
71
- /// session identifier; together they make a derived batch recognisable in
72
- /// a shared spool directory.
73
- ///
74
- /// `run_id` is nanoseconds since the epoch, which distinguishes files
75
- /// across restarts. Without it a fresh run would reuse `seq` 0 and could
76
- /// clobber a batch the previous run had written but not yet uploaded.
77
- pub fn new(dir: PathBuf, max_batch_bytes: u64, prefix: &str, tag: &str) -> Self {
78
- let run_id = SystemTime::now()
79
- .duration_since(UNIX_EPOCH)
80
- .map(|d| d.as_nanos())
81
- .unwrap_or(0);
82
- SpoolWriter {
83
- dir,
84
- max_batch_bytes: max_batch_bytes.max(MIN_BATCH_BYTES),
85
- prefix: sanitize_tag(prefix),
86
- tag: sanitize_tag(tag),
87
- run_id,
88
- seq: 0,
89
- buf: String::new(),
90
- buf_bytes: 0,
91
- // Default ON. A source that forgets to opt in still gets scrubbed,
92
- // which is the safe direction for the mistake to fall.
93
- redact: Redact::Minimal,
94
- redacted: 0,
95
- machine_id: None,
96
- }
97
- }
98
-
99
- /// Override the redaction mode. Only `Redact::Off` changes anything —
100
- /// scrubbing is on by default so a new source cannot ship secrets by
101
- /// omission.
102
- pub fn with_redact(mut self, mode: Redact) -> Self {
103
- self.redact = mode;
104
- self
105
- }
106
-
107
- /// Set the machine id stamped on every event. An empty string is treated as
108
- /// absent, so a misconfigured blank id never becomes a machine of its own.
109
- pub fn with_machine_id(mut self, id: Option<String>) -> Self {
110
- self.machine_id = id.filter(|s| !s.is_empty());
111
- self
112
- }
113
-
114
- /// How many string leaves have been scrubbed. Reported without ever
115
- /// logging what was removed.
116
- pub fn redacted_count(&self) -> u64 {
117
- self.redacted
118
- }
119
-
120
- /// Events buffered but not yet written. Used by callers that must flush
121
- /// before advancing a cursor.
122
- pub fn pending_bytes(&self) -> u64 {
123
- self.buf_bytes
124
- }
125
-
126
- /// Append one event, rolling to a new batch first if it would not fit.
127
- pub async fn push(&mut self, mut event: Value) -> std::io::Result<()> {
128
- // The single choke point every event passes through, which is why
129
- // redaction happens here rather than in each transform: a source
130
- // cannot forget it, and a new source inherits it for free. It runs
131
- // BEFORE serialization, so what gets hashed, spooled and uploaded is
132
- // the scrubbed form — there is no window where the raw value exists on
133
- // disk.
134
- self.redacted += crate::redact::scrub_value(&mut event, self.redact) as u64;
135
-
136
- // Stamp the machine id here, at the one point every event passes
137
- // through — a source cannot forget it and a new source inherits it. Set
138
- // only when absent, so an event that already names its machine (a
139
- // re-shipped batch, say) is never overwritten.
140
- if let Some(id) = &self.machine_id
141
- && let Some(obj) = event.as_object_mut()
142
- {
143
- obj.entry("machine_id")
144
- .or_insert_with(|| Value::String(id.clone()));
145
- }
146
-
147
- let mut line = serialize(&event)?;
148
-
149
- if line.len() as u64 > self.max_batch_bytes {
150
- truncate_strings(&mut event, MAX_FIELD_BYTES);
151
- line = serialize(&event)?;
152
- }
153
-
154
- if line.len() as u64 > self.max_batch_bytes {
155
- // Still over cap after truncation — thousands of small fields, say.
156
- // Shipping it would produce a batch the server refuses, which then
157
- // parks and retries forever at the same size.
158
- let event_type = event.get("type").and_then(|v| v.as_str()).unwrap_or("?");
159
- tracing::warn!(
160
- source = %self.prefix,
161
- bytes = line.len(),
162
- max = self.max_batch_bytes,
163
- event_type,
164
- "dropping an event that exceeds the batch cap even after truncation; \
165
- it remains in the agent's own store"
166
- );
167
- return Ok(());
168
- }
169
-
170
- let needed = line.len() as u64 + 1; // +1 for the newline
171
- if !self.buf.is_empty() && self.buf_bytes + needed > self.max_batch_bytes {
172
- self.flush().await?;
173
- }
174
- self.buf.push_str(&line);
175
- self.buf.push('\n');
176
- self.buf_bytes += needed;
177
- Ok(())
178
- }
179
-
180
- /// Durably write the buffered events as one batch. No-op when empty.
181
- ///
182
- /// Callers MUST await this before advancing any persisted cursor. The
183
- /// ordering is the whole durability argument: flush-then-advance costs a
184
- /// re-ship on crash (which the server dedups), advance-then-flush loses
185
- /// the events outright.
186
- pub async fn flush(&mut self) -> std::io::Result<()> {
187
- if self.buf.is_empty() {
188
- return Ok(());
189
- }
190
- tokio::fs::create_dir_all(&self.dir).await?;
191
-
192
- let stem = format!("{}-{}-{}-{}", self.prefix, self.tag, self.run_id, self.seq);
193
- let tmp = self.dir.join(format!("{stem}.tmp"));
194
- let final_path = self.dir.join(format!("{stem}.jsonl"));
195
-
196
- let mut f = tokio::fs::File::create(&tmp).await?;
197
- f.write_all(self.buf.as_bytes()).await?;
198
- // fsync before rename: a rename is atomic with respect to readers, but
199
- // it does not guarantee the CONTENT reached disk. Without this, a
200
- // power loss can leave a correctly-named, zero-length batch — which
201
- // the uploader would happily deliver as an empty file.
202
- f.sync_all().await?;
203
- drop(f);
204
- tokio::fs::rename(&tmp, &final_path).await?;
205
-
206
- self.seq += 1;
207
- self.buf.clear();
208
- self.buf_bytes = 0;
209
- Ok(())
210
- }
211
- }
212
-
213
- fn serialize(event: &Value) -> std::io::Result<String> {
214
- serde_json::to_string(event).map_err(std::io::Error::other)
215
- }
216
-
217
- /// Keep a tag filename-safe and bounded.
218
- ///
219
- /// The charset matters beyond hygiene: batch filenames encode retry state as
220
- /// suffixes, so a tag containing a `.` could make a fresh batch parse as one
221
- /// that had already been retried.
222
- fn sanitize_tag(tag: &str) -> String {
223
- let cleaned: String = tag
224
- .chars()
225
- .filter(|c| c.is_ascii_alphanumeric() || *c == '-')
226
- .take(16)
227
- .collect();
228
- if cleaned.is_empty() {
229
- "session".to_string()
230
- } else {
231
- cleaned
232
- }
233
- }
234
-
235
- /// Recursively truncate long string leaves to `cap` bytes at a char boundary.
236
- ///
237
- /// Deterministic by construction: the marker records the ORIGINAL length, so
238
- /// the same input always yields identical bytes. The truncated payload feeds
239
- /// the server's dedup hash, so a marker containing a timestamp or a counter
240
- /// would make every re-read of the same event look like a new one.
241
- fn truncate_strings(v: &mut Value, cap: usize) {
242
- match v {
243
- Value::String(s) if s.len() > cap => {
244
- let orig = s.len();
245
- let mut end = cap;
246
- while end > 0 && !s.is_char_boundary(end) {
247
- end -= 1;
248
- }
249
- let mut truncated = s[..end].to_string();
250
- truncated.push_str(&format!("…[truncated {orig} bytes]"));
251
- *s = truncated;
252
- }
253
- Value::Array(a) => a.iter_mut().for_each(|e| truncate_strings(e, cap)),
254
- Value::Object(o) => o.values_mut().for_each(|e| truncate_strings(e, cap)),
255
- _ => {}
256
- }
257
- }
258
-
259
- /// True if `path` is a batch the uploader should pick up.
260
- ///
261
- /// `.tmp` files are excluded by the extension check rather than by a separate
262
- /// rule, which is exactly why the atomic write works: a batch is invisible
263
- /// until the instant it is renamed.
264
- pub fn is_batch_file(path: &Path) -> bool {
265
- path.extension().and_then(|e| e.to_str()) == Some("jsonl")
266
- }
267
-
268
- #[cfg(test)]
269
- mod tests {
270
- use super::*;
271
- use serde_json::json;
272
-
273
- fn tmpdir(name: &str) -> PathBuf {
274
- let d = std::env::temp_dir().join(format!(
275
- "fpai-spool-{}-{}-{}",
276
- name,
277
- std::process::id(),
278
- SystemTime::now()
279
- .duration_since(UNIX_EPOCH)
280
- .unwrap()
281
- .as_nanos()
282
- ));
283
- std::fs::create_dir_all(&d).unwrap();
284
- d
285
- }
286
-
287
- fn batches(dir: &Path) -> Vec<String> {
288
- let mut v: Vec<String> = std::fs::read_dir(dir)
289
- .unwrap()
290
- .filter_map(|e| e.ok())
291
- .map(|e| e.file_name().to_string_lossy().to_string())
292
- .filter(|n| n.ends_with(".jsonl"))
293
- .collect();
294
- v.sort();
295
- v
296
- }
297
-
298
- #[tokio::test]
299
- async fn writes_one_batch_and_leaves_no_tmp_behind() {
300
- let dir = tmpdir("basic");
301
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "hooks", "sess1");
302
- w.push(json!({"type": "hook_triggered", "n": 1}))
303
- .await
304
- .unwrap();
305
- w.push(json!({"type": "hook_completed", "n": 1}))
306
- .await
307
- .unwrap();
308
- w.flush().await.unwrap();
309
-
310
- let files = batches(&dir);
311
- assert_eq!(files.len(), 1, "expected one batch, got {files:?}");
312
- assert!(files[0].starts_with("hooks-sess1-"));
313
-
314
- let leftover_tmp = std::fs::read_dir(&dir)
315
- .unwrap()
316
- .filter_map(|e| e.ok())
317
- .any(|e| e.file_name().to_string_lossy().ends_with(".tmp"));
318
- assert!(!leftover_tmp, "a .tmp file survived the rename");
319
-
320
- let body = std::fs::read_to_string(dir.join(&files[0])).unwrap();
321
- assert_eq!(body.lines().count(), 2);
322
- std::fs::remove_dir_all(&dir).ok();
323
- }
324
-
325
- #[tokio::test]
326
- async fn rolls_to_a_new_batch_at_the_size_cap() {
327
- let dir = tmpdir("roll");
328
- let mut w = SpoolWriter::new(dir.clone(), MIN_BATCH_BYTES, "claude", "s");
329
- for i in 0..100 {
330
- w.push(json!({"type": "tool_use", "n": i, "pad": "x".repeat(2000)}))
331
- .await
332
- .unwrap();
333
- }
334
- w.flush().await.unwrap();
335
- assert!(
336
- batches(&dir).len() > 1,
337
- "200KB of events under a 64KB cap must roll into several batches"
338
- );
339
- std::fs::remove_dir_all(&dir).ok();
340
- }
341
-
342
- #[tokio::test]
343
- async fn no_written_line_ever_exceeds_the_batch_cap() {
344
- // The invariant the uploader depends on: a line bigger than one
345
- // request can never be delivered, so it must never reach the spool.
346
- let dir = tmpdir("cap");
347
- let mut w = SpoolWriter::new(dir.clone(), MIN_BATCH_BYTES, "claude", "s");
348
- w.push(json!({"type": "tool_result", "output": "y".repeat(3 * 1024 * 1024)}))
349
- .await
350
- .unwrap();
351
- w.flush().await.unwrap();
352
-
353
- for f in batches(&dir) {
354
- let body = std::fs::read_to_string(dir.join(&f)).unwrap();
355
- for line in body.lines() {
356
- assert!(
357
- line.len() as u64 <= MIN_BATCH_BYTES,
358
- "a {}-byte line was written under a {MIN_BATCH_BYTES}-byte cap",
359
- line.len()
360
- );
361
- }
362
- }
363
- std::fs::remove_dir_all(&dir).ok();
364
- }
365
-
366
- #[tokio::test]
367
- async fn drops_an_event_that_is_still_over_cap_after_truncation() {
368
- // Many small fields: no single field is big enough for truncation to
369
- // shrink, so the whole line stays over cap and must be dropped rather
370
- // than written into a batch that could never be delivered.
371
- let dir = tmpdir("drop");
372
- let mut w = SpoolWriter::new(dir.clone(), MIN_BATCH_BYTES, "claude", "s");
373
- let mut obj = serde_json::Map::new();
374
- obj.insert("type".into(), json!("tool_result"));
375
- for i in 0..2000 {
376
- obj.insert(format!("f{i}"), json!("x".repeat(64)));
377
- }
378
- w.push(Value::Object(obj)).await.unwrap();
379
- w.flush().await.unwrap();
380
-
381
- assert!(
382
- batches(&dir).is_empty(),
383
- "an undeliverable event must be dropped, never spooled"
384
- );
385
- std::fs::remove_dir_all(&dir).ok();
386
- }
387
-
388
- #[test]
389
- fn truncation_is_deterministic() {
390
- // Non-negotiable: the server dedups on a content hash, so a re-read of
391
- // the same source bytes must produce identical output.
392
- let big = "a".repeat(3 * 1024 * 1024);
393
- let mut a = json!({"output": big.clone()});
394
- let mut b = json!({"output": big});
395
- truncate_strings(&mut a, MAX_FIELD_BYTES);
396
- truncate_strings(&mut b, MAX_FIELD_BYTES);
397
- assert_eq!(a, b);
398
- let s = a["output"].as_str().unwrap();
399
- assert!(s.contains("truncated"));
400
- assert!(s.len() < 2 * 1024 * 1024);
401
- }
402
-
403
- #[test]
404
- fn truncation_cuts_on_a_char_boundary() {
405
- // Cutting mid-codepoint would panic on the slice, or emit invalid
406
- // UTF-8 that the server rejects for the whole batch.
407
- let mut v = json!({ "output": "é".repeat(200) });
408
- truncate_strings(&mut v, 101); // lands mid-codepoint: 'é' is 2 bytes
409
- assert!(v["output"].as_str().unwrap().contains("truncated"));
410
- }
411
-
412
- #[test]
413
- fn a_tag_cannot_smuggle_retry_state_into_a_filename() {
414
- // Retry state is encoded as filename suffixes, so a tag containing a
415
- // dot could make a brand-new batch parse as an already-retried one.
416
- assert_eq!(sanitize_tag("sess.a1.c404"), "sessa1c404");
417
- assert_eq!(sanitize_tag("../../etc/passwd"), "etcpasswd");
418
- assert_eq!(sanitize_tag(""), "session");
419
- assert_eq!(sanitize_tag("!!!"), "session");
420
- assert_eq!(sanitize_tag(&"a".repeat(64)).len(), 16);
421
- }
422
-
423
- #[tokio::test]
424
- async fn flushing_an_empty_writer_creates_nothing() {
425
- let dir = tmpdir("empty");
426
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "hooks", "s");
427
- w.flush().await.unwrap();
428
- assert!(batches(&dir).is_empty());
429
- std::fs::remove_dir_all(&dir).ok();
430
- }
431
-
432
- #[tokio::test]
433
- async fn secrets_are_scrubbed_before_anything_touches_disk() {
434
- // The choke-point guarantee: a source that never thinks about
435
- // redaction still cannot write a credential into the spool.
436
- let dir = tmpdir("redact");
437
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s");
438
- w.push(json!({
439
- "type": "tool_use",
440
- "input": {"command": "curl -H 'Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz0123'"}
441
- }))
442
- .await
443
- .unwrap();
444
- w.flush().await.unwrap();
445
-
446
- let name = batches(&dir).remove(0);
447
- let body = std::fs::read_to_string(dir.join(&name)).unwrap();
448
- assert!(
449
- !body.contains("ghp_abcdefghij"),
450
- "a credential reached disk: {body}"
451
- );
452
- assert!(
453
- body.contains("[redacted:"),
454
- "redaction marker missing: {body}"
455
- );
456
- assert!(w.redacted_count() >= 1);
457
- std::fs::remove_dir_all(&dir).ok();
458
- }
459
-
460
- #[tokio::test]
461
- async fn machine_id_is_stamped_on_every_event_when_set() {
462
- // Without this the server can only group by agent_id — a per-project
463
- // identity — so one machine's many projects each read as a machine.
464
- let dir = tmpdir("machine-id");
465
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
466
- .with_machine_id(Some("beta-test".into()));
467
- w.push(json!({"type": "agent_start", "agent_id": "claude-foo"}))
468
- .await
469
- .unwrap();
470
- w.flush().await.unwrap();
471
- let name = batches(&dir).remove(0);
472
- let body = std::fs::read_to_string(dir.join(&name)).unwrap();
473
- assert!(body.contains(r#""machine_id":"beta-test""#), "got {body}");
474
- std::fs::remove_dir_all(&dir).ok();
475
- }
476
-
477
- #[tokio::test]
478
- async fn an_absent_machine_id_stamps_nothing() {
479
- // A machine that was never given an id must not invent one — the server
480
- // excludes machine-less events rather than bucket them.
481
- let dir = tmpdir("no-machine-id");
482
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s");
483
- w.push(json!({"type": "agent_start"})).await.unwrap();
484
- w.flush().await.unwrap();
485
- let name = batches(&dir).remove(0);
486
- assert!(
487
- !std::fs::read_to_string(dir.join(&name))
488
- .unwrap()
489
- .contains("machine_id")
490
- );
491
- std::fs::remove_dir_all(&dir).ok();
492
- }
493
-
494
- #[tokio::test]
495
- async fn an_empty_machine_id_is_treated_as_absent() {
496
- let dir = tmpdir("blank-machine-id");
497
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
498
- .with_machine_id(Some(String::new()));
499
- w.push(json!({"type": "agent_start"})).await.unwrap();
500
- w.flush().await.unwrap();
501
- let name = batches(&dir).remove(0);
502
- assert!(
503
- !std::fs::read_to_string(dir.join(&name))
504
- .unwrap()
505
- .contains("machine_id")
506
- );
507
- std::fs::remove_dir_all(&dir).ok();
508
- }
509
-
510
- #[tokio::test]
511
- async fn redaction_can_be_turned_off() {
512
- let dir = tmpdir("redact-off");
513
- let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
514
- .with_redact(Redact::Off);
515
- w.push(json!({"output": "ghp_abcdefghijklmnopqrstuvwxyz0123"}))
516
- .await
517
- .unwrap();
518
- w.flush().await.unwrap();
519
- let name = batches(&dir).remove(0);
520
- assert!(
521
- std::fs::read_to_string(dir.join(&name))
522
- .unwrap()
523
- .contains("ghp_abcdefghij")
524
- );
525
- std::fs::remove_dir_all(&dir).ok();
526
- }
527
-
528
- #[test]
529
- fn only_jsonl_is_a_batch() {
530
- assert!(is_batch_file(Path::new("/s/hooks-a-1-0.jsonl")));
531
- // The atomic-write contract depends on this exclusion.
532
- assert!(!is_batch_file(Path::new("/s/hooks-a-1-0.tmp")));
533
- assert!(!is_batch_file(Path::new("/s/hooks-a-1-0.jsonl.poison")));
534
- }
535
- }