failproofai 1.0.0-beta.2 → 1.0.0-beta.21

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 (381) 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 +6 -6
  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/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  125. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
  134. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  139. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
  153. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  154. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  157. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  160. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  161. package/.next/standalone/.next/server/middleware.js +2 -2
  162. package/.next/standalone/.next/server/pages/404.html +1 -1
  163. package/.next/standalone/.next/server/pages/500.html +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
  168. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
  170. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  171. package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
  172. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  173. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
  174. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
  175. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
  176. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  177. package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
  178. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
  179. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
  180. package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
  181. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  182. package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  184. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  185. package/.next/standalone/SECURITY.md +8 -2
  186. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  187. package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
  188. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  189. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  190. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  191. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  192. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  193. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  194. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  195. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  196. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  197. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  198. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  199. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  200. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  201. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  202. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  203. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  204. package/.next/standalone/app/audit/audit-styles.css +6 -149
  205. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  206. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  207. package/.next/standalone/app/components/toast.tsx +1 -1
  208. package/.next/standalone/app/policies/hooks-client.tsx +84 -8
  209. package/.next/standalone/app/settings/page.tsx +31 -0
  210. package/.next/standalone/app/settings/settings-client.tsx +487 -0
  211. package/.next/standalone/components/navbar.tsx +2 -0
  212. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  213. package/.next/standalone/lib/auth/auth-store.ts +4 -5
  214. package/.next/standalone/lib/codex-sessions.ts +2 -1
  215. package/.next/standalone/lib/dashboard-host.ts +101 -0
  216. package/.next/standalone/lib/install-check.ts +22 -7
  217. package/.next/standalone/lib/pi-sessions.ts +99 -14
  218. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  219. package/.next/standalone/lib/telemetry-id.ts +52 -1
  220. package/.next/standalone/lib/telemetry.ts +8 -4
  221. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  222. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  223. package/.next/standalone/node_modules/react/package.json +3 -3
  224. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  225. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  226. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  227. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  228. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  229. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  230. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  231. package/.next/standalone/package.json +23 -12
  232. package/.next/standalone/proxy.ts +111 -1
  233. package/.next/standalone/server.js +1 -1
  234. package/bin/failproofai-worker.mjs +1 -1
  235. package/bin/failproofai.mjs +1041 -81
  236. package/bin/failproofaid-shim.mjs +4 -4
  237. package/dist/cli.mjs +18513 -10474
  238. package/dist/worker.mjs +1223 -299
  239. package/lib/auth/api-server-client.ts +2 -3
  240. package/lib/auth/auth-store.ts +4 -5
  241. package/lib/codex-sessions.ts +2 -1
  242. package/lib/dashboard-host.ts +101 -0
  243. package/lib/install-check.ts +22 -7
  244. package/lib/pi-sessions.ts +99 -14
  245. package/lib/telemetry-enabled.ts +54 -0
  246. package/lib/telemetry-id.ts +52 -1
  247. package/lib/telemetry.ts +8 -4
  248. package/package.json +23 -12
  249. package/scripts/build-daemon-packages.mjs +247 -0
  250. package/scripts/daemon-platforms.mjs +42 -0
  251. package/scripts/dev-hook.mjs +46 -5
  252. package/scripts/install-telemetry.mjs +8 -1
  253. package/scripts/launch.ts +25 -3
  254. package/scripts/parse-script-args.ts +27 -1
  255. package/scripts/prune-standalone.mjs +26 -0
  256. package/scripts/publish-aliases.mjs +62 -41
  257. package/scripts/repro-npm-install.sh +155 -0
  258. package/scripts/translate-docs/cache.ts +1 -1
  259. package/scripts/translate-docs/cli.ts +2 -2
  260. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  261. package/scripts/translate-docs/readme-translator.ts +163 -4
  262. package/scripts/validate-mdx.ts +153 -13
  263. package/src/audit/audit-lock.ts +285 -0
  264. package/src/audit/audit-schedule.ts +111 -0
  265. package/src/audit/cache.ts +18 -5
  266. package/src/audit/cli-adapters/goose.ts +2 -1
  267. package/src/audit/cli-adapters/hermes.ts +28 -10
  268. package/src/audit/cli.ts +191 -35
  269. package/src/audit/dashboard-cache.ts +2 -3
  270. package/src/audit/report.ts +0 -1
  271. package/src/hooks/backfill-cli.ts +247 -0
  272. package/src/hooks/builtin-policies.ts +391 -3
  273. package/src/hooks/cloud-connection.ts +354 -0
  274. package/src/hooks/cloud-enrollment-cli.ts +439 -0
  275. package/src/hooks/cloud-enrollment.ts +284 -0
  276. package/src/hooks/cloud-introspect.ts +147 -0
  277. package/src/hooks/cloud-managed-policies.ts +206 -0
  278. package/src/hooks/collector-config.ts +218 -0
  279. package/src/hooks/configure-wizard.ts +1211 -233
  280. package/src/hooks/custom-hooks-loader.ts +243 -37
  281. package/src/hooks/daemon-client.ts +136 -33
  282. package/src/hooks/daemon-download.ts +264 -31
  283. package/src/hooks/daemon-service.ts +1124 -76
  284. package/src/hooks/first-run-gate.ts +50 -0
  285. package/src/hooks/flush-cli.ts +180 -0
  286. package/src/hooks/fp-config.ts +835 -0
  287. package/src/hooks/fp-home.ts +714 -0
  288. package/src/hooks/fp-reset.ts +1363 -0
  289. package/src/hooks/handler.ts +185 -6
  290. package/src/hooks/harness-cli.ts +386 -0
  291. package/src/hooks/hook-activity-store.ts +171 -24
  292. package/src/hooks/hook-logger.ts +2 -2
  293. package/src/hooks/hook-telemetry.ts +5 -1
  294. package/src/hooks/hooks-config.ts +4 -3
  295. package/src/hooks/integrations.ts +0 -1
  296. package/src/hooks/loader-utils.ts +172 -9
  297. package/src/hooks/manager.ts +41 -5
  298. package/src/hooks/migrations.ts +450 -0
  299. package/src/hooks/onboarding-attempt.ts +195 -0
  300. package/src/hooks/onboarding-lock.ts +118 -0
  301. package/src/hooks/policy-types.ts +28 -0
  302. package/src/hooks/session-pause-cli.ts +159 -0
  303. package/src/hooks/session-pause.ts +267 -0
  304. package/src/hooks/setup-state.ts +256 -0
  305. package/src/hooks/tui.ts +209 -9
  306. package/src/hooks/uninstall-cli.ts +418 -0
  307. package/src/hooks/worker-server.ts +134 -15
  308. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  319. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  321. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  322. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  330. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  333. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  334. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  335. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  336. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  337. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  338. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  340. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  341. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  342. package/.next/standalone/Cargo.lock +0 -428
  343. package/.next/standalone/Cargo.toml +0 -9
  344. package/.next/standalone/crates/PROTOCOL.md +0 -122
  345. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  346. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  347. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  348. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  349. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  350. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  351. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  352. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  353. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  354. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  355. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  356. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  357. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  358. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  359. package/.next/standalone/integration-suite/Dockerfile +0 -47
  360. package/.next/standalone/integration-suite/README.md +0 -129
  361. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  362. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  363. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  364. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  365. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  366. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  367. package/.next/standalone/integration-suite/report.js +0 -120
  368. package/.next/standalone/integration-suite/run.sh +0 -159
  369. package/.next/standalone/openclaw-plugin/index.js +0 -223
  370. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  371. package/.next/standalone/openclaw-plugin/package.json +0 -12
  372. package/.next/standalone/osv-scanner.toml +0 -17
  373. package/.next/standalone/pi-extension/index.ts +0 -524
  374. package/.next/standalone/pi-extension/package.json +0 -12
  375. package/.next/standalone/postcss.config.mjs +0 -8
  376. package/.next/standalone/readme-arch-hq.gif +0 -0
  377. package/.next/standalone/rust-toolchain.toml +0 -4
  378. package/src/auth/cli.ts +0 -371
  379. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
  380. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
  381. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Disk persistence for hook activity entries using page-sized JSONL files.
3
3
  *
4
- * Storage location: ~/.failproofai/cache/hook-activity/
4
+ * Storage location: ~/.failproofai/hook-activity/ (see fp-home.ts)
5
5
  *
6
6
  * File structure:
7
7
  * - current.jsonl — actively written to, 0–PAGE_SIZE entries
@@ -22,19 +22,32 @@ import {
22
22
  unlinkSync,
23
23
  } from "node:fs";
24
24
  import { join } from "node:path";
25
- import { homedir } from "node:os";
26
25
  import type { IntegrationType } from "./types";
26
+ import { hookActivityDir } from "./fp-home";
27
27
 
28
28
  export const PAGE_SIZE = 25;
29
29
 
30
- const DEFAULT_STORE_DIR = join(homedir(), ".failproofai", "cache", "hook-activity");
30
+ /**
31
+ * Layout 2 promoted this out of `cache/`. It was never a cache — nothing
32
+ * regenerates it, and deleting it loses the decision history the dashboard's
33
+ * activity tab is built on. Resolved lazily (not at module load) so
34
+ * FAILPROOFAI_HOME is honoured by tests and containers that set it after
35
+ * import.
36
+ */
37
+ const defaultStoreDir = () => hookActivityDir();
31
38
  const CURRENT_FILE = "current.jsonl";
32
39
  const COUNT_FILE = "current.count"; // tracks line count; O(1) read/write vs rereading current.jsonl
33
40
  const STATS_FILE = "stats.json";
34
41
  const LOCK_FILE = "current.lock"; // advisory lock for concurrent hook processes
35
42
  const LOCK_STALE_MS = 2000; // steal lock if older than 2 s (covers crashed processes)
36
43
 
37
- let storeDir = DEFAULT_STORE_DIR;
44
+ let storeDirOverride: string | null = null;
45
+ /**
46
+ * A getter, not a module-load constant: `FAILPROOFAI_HOME` is routinely set by
47
+ * a test or a container AFTER this module is imported, and a value captured at
48
+ * import time would silently point every write at the real home.
49
+ */
50
+ const storeDirValue = () => storeDirOverride ?? defaultStoreDir();
38
51
  let rotateSeq = 0;
39
52
 
40
53
  // ── Types ──
@@ -69,6 +82,45 @@ export interface HookActivityEntry {
69
82
  cwd?: string;
70
83
  permissionMode?: string;
71
84
  hookEventName?: string;
85
+ /**
86
+ * Set only on events evaluated while a session pause was active. Absent on
87
+ * every ordinary row and on every row written before pausing existed, so —
88
+ * like `matchedPolicies` — consumers must treat `undefined` as "not paused"
89
+ * rather than rendering it as unknown.
90
+ */
91
+ pausedBy?: string;
92
+ /** Epoch ms the pause lifts. Present iff `pausedBy` is. */
93
+ pauseExpiresAt?: number;
94
+ /**
95
+ * Where the policy that DECIDED came from. Absent when nothing decided (a
96
+ * plain allow) and on rows written before this existed — so, like
97
+ * `matchedPolicies`, `undefined` means "unknown", not "builtin".
98
+ */
99
+ policySource?: "builtin" | "custom" | "convention" | "cloud";
100
+ /** Cloud policy id of the decider. Present only when `policySource` is "cloud". */
101
+ cloudPolicyId?: string;
102
+ /** Immutable version of that policy — the half of attribution that identifies WHICH version ran. */
103
+ cloudVersion?: number;
104
+ /**
105
+ * The cloud deployment active when this event was evaluated, recorded on
106
+ * every row of a managed machine regardless of what decided. "What was
107
+ * deployed here" is a different question from "what decided", and only this
108
+ * distinguishes a rollout that changed no outcomes from one that never
109
+ * arrived.
110
+ */
111
+ cloudDeployment?: number;
112
+ /**
113
+ * What observe-mode policies WOULD have done, had they been enforcing. This
114
+ * record is the entire point of observe mode: without it the row is
115
+ * indistinguishable from one where the policy never matched, and the rollout
116
+ * being trialled is unmeasurable.
117
+ */
118
+ observed?: Array<{
119
+ policyId: string;
120
+ version: number;
121
+ decision: "deny" | "instruct";
122
+ reason: string | null;
123
+ }>;
72
124
  }
73
125
 
74
126
  export interface HookActivityFilters {
@@ -77,6 +129,12 @@ export interface HookActivityFilters {
77
129
  policyName?: string;
78
130
  sessionId?: string;
79
131
  integration?: IntegrationType;
132
+ /**
133
+ * Filter by where the deciding policy came from. "Show me everything my
134
+ * organization's policies decided" is the question cloud rollout reporting
135
+ * is built on, and it is unanswerable without this.
136
+ */
137
+ source?: "builtin" | "custom" | "convention" | "cloud";
80
138
  }
81
139
 
82
140
  export interface HookActivityStats {
@@ -89,8 +147,8 @@ export interface HookActivityStats {
89
147
  // ── Directory setup ──
90
148
 
91
149
  function ensureDir(): void {
92
- if (!existsSync(storeDir)) {
93
- mkdirSync(storeDir, { recursive: true });
150
+ if (!existsSync(storeDirValue())) {
151
+ mkdirSync(storeDirValue(), { recursive: true });
94
152
  }
95
153
  }
96
154
 
@@ -98,7 +156,7 @@ function ensureDir(): void {
98
156
 
99
157
  function acquireLock(): void {
100
158
  ensureDir();
101
- const lockPath = join(storeDir, LOCK_FILE);
159
+ const lockPath = join(storeDirValue(), LOCK_FILE);
102
160
  const deadline = Date.now() + LOCK_STALE_MS;
103
161
  while (Date.now() < deadline) {
104
162
  try {
@@ -121,7 +179,7 @@ function acquireLock(): void {
121
179
  }
122
180
 
123
181
  function releaseLock(): void {
124
- try { unlinkSync(join(storeDir, LOCK_FILE)); } catch { /* ignore */ }
182
+ try { unlinkSync(join(storeDirValue(), LOCK_FILE)); } catch { /* ignore */ }
125
183
  }
126
184
 
127
185
  // ── Writing (synchronous — hook handler is short-lived) ──
@@ -130,8 +188,8 @@ export function persistHookActivity(entry: HookActivityEntry): void {
130
188
  ensureDir();
131
189
  acquireLock();
132
190
  try {
133
- const currentPath = join(storeDir, CURRENT_FILE);
134
- const countPath = join(storeDir, COUNT_FILE);
191
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
192
+ const countPath = join(storeDirValue(), COUNT_FILE);
135
193
 
136
194
  const lineCount = readCount(countPath);
137
195
  if (lineCount >= PAGE_SIZE) {
@@ -153,7 +211,7 @@ export function persistHookActivity(entry: HookActivityEntry): void {
153
211
 
154
212
  function rotate(currentPath: string, countPath: string): void {
155
213
  const archiveName = `page-${Date.now()}-${rotateSeq++}.jsonl`;
156
- const archivePath = join(storeDir, archiveName);
214
+ const archivePath = join(storeDirValue(), archiveName);
157
215
  renameSync(currentPath, archivePath);
158
216
  // Reset count for the fresh file (write 0 — next append will set it to 1)
159
217
  writeCount(countPath, 0);
@@ -195,14 +253,21 @@ interface StoredStats {
195
253
 
196
254
  function readStoredStats(): StoredStats {
197
255
  try {
198
- return JSON.parse(readFileSync(join(storeDir, STATS_FILE), "utf-8")) as StoredStats;
256
+ return JSON.parse(readFileSync(join(storeDirValue(), STATS_FILE), "utf-8")) as StoredStats;
199
257
  } catch {
200
258
  return { totalEvents: 0, denyCount: 0, policyMap: {} };
201
259
  }
202
260
  }
203
261
 
204
- function updateStats(entry: HookActivityEntry): void {
205
- const s = readStoredStats();
262
+ /**
263
+ * Fold one entry into a running total, in place.
264
+ *
265
+ * Extracted so the incremental path and {@link rebuildHookActivityStats} cannot
266
+ * disagree: a rebuild that counted differently from the appends would produce
267
+ * numbers that silently drift from the log they claim to summarise, and nothing
268
+ * would catch it because both sides look plausible on their own.
269
+ */
270
+ function foldEntry(s: StoredStats, entry: HookActivityEntry): void {
206
271
  s.totalEvents += 1;
207
272
  if (entry.decision === "deny") s.denyCount += 1;
208
273
  if (entry.policyNames && entry.policyNames.length > 0) {
@@ -212,17 +277,64 @@ function updateStats(entry: HookActivityEntry): void {
212
277
  } else if (entry.policyName) {
213
278
  s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
214
279
  }
215
- // Write atomically: write to a PID-unique temp file then rename — prevents partial reads.
216
- const tmpPath = join(storeDir, `stats.json.${process.pid}.tmp`);
280
+ }
281
+
282
+ /** Write stats atomically: PID-unique temp then rename, so no reader sees a partial file. */
283
+ function writeStoredStats(s: StoredStats): boolean {
284
+ const tmpPath = join(storeDirValue(), `stats.json.${process.pid}.tmp`);
217
285
  try {
218
286
  writeFileSync(tmpPath, JSON.stringify(s), "utf-8");
219
- renameSync(tmpPath, join(storeDir, STATS_FILE));
287
+ renameSync(tmpPath, join(storeDirValue(), STATS_FILE));
288
+ return true;
220
289
  } catch {
221
290
  try { unlinkSync(tmpPath); } catch { /* ignore */ }
222
291
  // Non-fatal: stats file write failure doesn't block the hook
292
+ return false;
223
293
  }
224
294
  }
225
295
 
296
+ function updateStats(entry: HookActivityEntry): void {
297
+ const s = readStoredStats();
298
+ foldEntry(s, entry);
299
+ writeStoredStats(s);
300
+ }
301
+
302
+ /**
303
+ * Recompute `stats.json` from every page on disk.
304
+ *
305
+ * WHY THIS EXISTS. `stats.json` is INCREMENTAL — `updateStats` adds one entry per
306
+ * append and nothing ever rescans — so it is the only part of the decision log
307
+ * that cannot survive being lost. The layout migration carries the pages (real
308
+ * records) and deliberately drops `stats.json` and `current.count` as derived
309
+ * state, on the stated grounds that "the store rebuilds them". It did not: there
310
+ * was no rebuild anywhere in the store, so `readStoredStats()` fell through its
311
+ * catch to zeroes and began re-accumulating from the next event. A user upgrading
312
+ * from a pre-daemon home kept every record and lost every total — the dashboard
313
+ * listed their history while reporting 0 events, 0 denies and no top policy.
314
+ *
315
+ * It is exactly recomputable, which is what makes dropping it the right call and
316
+ * this function the missing half: pages are never pruned (there is no retention
317
+ * anywhere in this module), so the files on disk ARE the whole history.
318
+ *
319
+ * Returns the totals it wrote, or null if there was nothing to read.
320
+ */
321
+ export function rebuildHookActivityStats(): StoredStats | null {
322
+ ensureDir();
323
+ const files = [CURRENT_FILE, ...getArchiveFiles()];
324
+ const s: StoredStats = { totalEvents: 0, denyCount: 0, policyMap: {} };
325
+ let sawAny = false;
326
+ for (const file of files) {
327
+ const path = join(storeDirValue(), file);
328
+ if (!existsSync(path)) continue;
329
+ sawAny = true;
330
+ // `readJsonlFile` already skips malformed lines, so a truncated tail costs
331
+ // that line rather than the whole rebuild.
332
+ for (const entry of readJsonlFile(path)) foldEntry(s, entry);
333
+ }
334
+ if (!sawAny) return null;
335
+ return writeStoredStats(s) ? s : null;
336
+ }
337
+
226
338
  function readStats(): HookActivityStats {
227
339
  const s = readStoredStats();
228
340
  let topPolicy: string | null = null;
@@ -243,7 +355,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
243
355
  if (page < 1) return [];
244
356
 
245
357
  if (page === 1) {
246
- const currentPath = join(storeDir, CURRENT_FILE);
358
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
247
359
  return readJsonlFile(currentPath).reverse();
248
360
  }
249
361
 
@@ -251,7 +363,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
251
363
  const archiveIndex = page - 2;
252
364
  if (archiveIndex >= archives.length) return [];
253
365
 
254
- return readJsonlFile(join(storeDir, archives[archiveIndex])).reverse();
366
+ return readJsonlFile(join(storeDirValue(), archives[archiveIndex])).reverse();
255
367
  }
256
368
 
257
369
  export function getHookActivityPageCount(): number {
@@ -262,13 +374,13 @@ export function getHookActivityPageCount(): number {
262
374
  export function getAllHookActivityEntries(): HookActivityEntry[] {
263
375
  ensureDir();
264
376
 
265
- const currentPath = join(storeDir, CURRENT_FILE);
377
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
266
378
  const currentEntries = readJsonlFile(currentPath).reverse();
267
379
 
268
380
  const archives = getArchiveFiles();
269
381
  const archiveEntries: HookActivityEntry[] = [];
270
382
  for (const file of archives) {
271
- const entries = readJsonlFile(join(storeDir, file));
383
+ const entries = readJsonlFile(join(storeDirValue(), file));
272
384
  archiveEntries.push(...entries.reverse());
273
385
  }
274
386
 
@@ -298,6 +410,11 @@ export function searchHookActivity(
298
410
  return false;
299
411
  }
300
412
  if (filters.integration && entry.integration !== filters.integration) return false;
413
+ // Rows written before attribution existed have no policySource. They are
414
+ // excluded from every source filter rather than guessed at — a wrong
415
+ // attribution is worse than a missing one when the point is proving which
416
+ // rollout decided something.
417
+ if (filters.source && entry.policySource !== filters.source) return false;
301
418
  return true;
302
419
  });
303
420
 
@@ -325,6 +442,34 @@ export function getHookActivityHistory(page: number): {
325
442
 
326
443
  // ── Internal helpers ──
327
444
 
445
+ /**
446
+ * Accept the pre-rename spelling of the two cloud-attribution fields.
447
+ *
448
+ * These pages are written by the DAEMON, so a machine that was cloud-connected
449
+ * before `cloudRevision`→`cloudVersion` / `cloudGeneration`→`cloudDeployment` has
450
+ * real rows on disk naming the old keys. Nothing here validates the shape — the
451
+ * line is `JSON.parse`d and cast — so those rows do not error, they simply carry
452
+ * keys nothing reads: every pre-upgrade cloud-decided decision renders as
453
+ * unattributed, which is the exact question these fields were added to answer.
454
+ *
455
+ * The Rust writer takes the same two aliases (`transform.rs`). Renaming a symbol
456
+ * is safe; renaming the name of data an older build already wrote is not.
457
+ *
458
+ * One-directional and non-destructive: the new key wins if both somehow appear,
459
+ * and the old key is left in place rather than deleted, since nothing reads it and
460
+ * removing it would rewrite history to look like it was always current.
461
+ */
462
+ function withRenamedCloudFields(raw: Record<string, unknown>): HookActivityEntry {
463
+ const entry = raw as HookActivityEntry & Record<string, unknown>;
464
+ if (entry.cloudVersion === undefined && typeof raw.cloudRevision === "number") {
465
+ entry.cloudVersion = raw.cloudRevision;
466
+ }
467
+ if (entry.cloudDeployment === undefined && typeof raw.cloudGeneration === "number") {
468
+ entry.cloudDeployment = raw.cloudGeneration;
469
+ }
470
+ return entry;
471
+ }
472
+
328
473
  function readJsonlFile(filePath: string): HookActivityEntry[] {
329
474
  const content = readFileSafe(filePath);
330
475
  if (!content.trim()) return [];
@@ -332,7 +477,7 @@ function readJsonlFile(filePath: string): HookActivityEntry[] {
332
477
  const entries: HookActivityEntry[] = [];
333
478
  for (const line of content.trim().split("\n")) {
334
479
  try {
335
- entries.push(JSON.parse(line) as HookActivityEntry);
480
+ entries.push(withRenamedCloudFields(JSON.parse(line) as Record<string, unknown>));
336
481
  } catch {
337
482
  // Skip malformed lines
338
483
  }
@@ -342,7 +487,7 @@ function readJsonlFile(filePath: string): HookActivityEntry[] {
342
487
 
343
488
  function getArchiveFiles(): string[] {
344
489
  try {
345
- const files = readdirSync(storeDir);
490
+ const files = readdirSync(storeDirValue());
346
491
  return files
347
492
  .filter((f) => f.startsWith("page-") && f.endsWith(".jsonl"))
348
493
  .sort((a, b) => {
@@ -364,5 +509,7 @@ function getArchiveFiles(): string[] {
364
509
 
365
510
  export function _resetForTest(testDir?: string): void {
366
511
  rotateSeq = 0;
367
- storeDir = testDir ?? DEFAULT_STORE_DIR;
512
+ // null, not the default path: clearing the override lets the getter re-read
513
+ // FAILPROOFAI_HOME, which a test may have changed since this module loaded.
514
+ storeDirOverride = testDir ?? null;
368
515
  }
@@ -21,14 +21,14 @@ import {
21
21
  statSync,
22
22
  } from "node:fs";
23
23
  import { join } from "node:path";
24
- import { homedir } from "node:os";
24
+ import { logsDir } from "./fp-home";
25
25
 
26
26
  export type LogLevel = "info" | "warn" | "error";
27
27
 
28
28
  const LEVEL_ORDER: Record<LogLevel, number> = { info: 0, warn: 1, error: 2 };
29
29
  const MAX_FILE_SIZE = 512 * 1024; // 500 KB
30
30
  const LOG_FILENAME = "hooks.log";
31
- const DEFAULT_LOG_DIR = join(homedir(), ".failproofai", "logs");
31
+ const DEFAULT_LOG_DIR = logsDir();
32
32
 
33
33
  // ── State (lazy-initialized) ──
34
34
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { version } from "../../package.json";
9
9
  import { POSTHOG_API_KEY, POSTHOG_PRODUCT } from "../posthog-key";
10
+ import { isTelemetryEnabled } from "../../lib/telemetry-enabled";
10
11
 
11
12
  const API_KEY = POSTHOG_API_KEY;
12
13
  const CAPTURE_URL = "https://us.i.posthog.com/capture/";
@@ -57,7 +58,10 @@ export async function trackHookEvent(
57
58
  event: string,
58
59
  properties?: Record<string, unknown>,
59
60
  ): Promise<void> {
60
- if (process.env.FAILPROOFAI_TELEMETRY_DISABLED === "1") return;
61
+ // Honours the config file as well as the env var the env var alone cannot
62
+ // reach a system-scope daemon. Memoised, so this stays off the hook path's
63
+ // latency budget. See lib/telemetry-enabled.ts.
64
+ if (!isTelemetryEnabled()) return;
61
65
 
62
66
  const p = sendEvent(distinctId, event, properties);
63
67
  pending.add(p);
@@ -7,6 +7,7 @@ import { homedir } from "node:os";
7
7
  import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
8
8
  import type { HookScope } from "./types";
9
9
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
10
+ import { globalPolicyConfigFile } from "./fp-home";
10
11
 
11
12
  /** Normalize plural and legacy singular explicit policy paths for consumers. */
12
13
  export function configuredCustomPolicyPaths(
@@ -69,7 +70,7 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
69
70
  const base = findProjectConfigDir(cwd ?? process.cwd());
70
71
  const projectPath = resolve(base, ".failproofai", "policies-config.json");
71
72
  const localPath = resolve(base, ".failproofai", "policies-config.local.json");
72
- const globalPath = resolve(homedir(), ".failproofai", "policies-config.json");
73
+ const globalPath = globalPolicyConfigFile();
73
74
 
74
75
  const project = readConfigAt(projectPath);
75
76
  const local = readConfigAt(localPath);
@@ -128,7 +129,7 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
128
129
  }
129
130
 
130
131
  function getConfigPath(): string {
131
- return resolve(homedir(), ".failproofai", "policies-config.json");
132
+ return globalPolicyConfigFile();
132
133
  }
133
134
 
134
135
  export function readHooksConfig(): HooksConfig {
@@ -160,7 +161,7 @@ export function writeHooksConfig(config: HooksConfig): void {
160
161
  export function getConfigPathForScope(scope: HookScope, cwd?: string): string {
161
162
  switch (scope) {
162
163
  case "user":
163
- return resolve(homedir(), ".failproofai", "policies-config.json");
164
+ return globalPolicyConfigFile();
164
165
  case "project":
165
166
  return resolve(findProjectConfigDir(cwd ?? process.cwd()), ".failproofai", "policies-config.json");
166
167
  case "local":
@@ -14,7 +14,6 @@ import { homedir } from "node:os";
14
14
  import { parseDocument, type Document } from "yaml";
15
15
  import { listHermesProfiles, hermesRoot } from "../../lib/hermes-profiles";
16
16
  import {
17
- HOOK_EVENT_TYPES,
18
17
  CLAUDE_INSTALL_EVENT_TYPES,
19
18
  HOOK_SCOPES,
20
19
  CODEX_HOOK_EVENT_TYPES,
@@ -11,12 +11,75 @@
11
11
  *
12
12
  * The ESM shim includes hooks API exports.
13
13
  */
14
- import { readFile, writeFile, unlink, access } from "fs/promises";
15
- import { resolve, dirname, relative } from "path";
14
+ import { readFile, writeFile, unlink, access, mkdir, lstat } from "fs/promises";
15
+ import { tmpdir } from "os";
16
+ import { resolve, dirname, relative, join } from "path";
16
17
  import { pathToFileURL } from "url";
18
+ import { createHash } from "crypto";
19
+ import { shimsDir } from "./fp-home";
20
+ import { hookLogWarn } from "./hook-logger";
17
21
 
18
22
  export const TMP_SUFFIX = ".__failproofai_tmp__.mjs";
19
23
 
24
+ /**
25
+ * Anything this loader generated, by name.
26
+ *
27
+ * The temporary tree is written BESIDE the user's sources, because that is the
28
+ * only place a rewritten relative import still resolves. That was harmless
29
+ * while the name was fixed — an abnormally terminated hook left at most one
30
+ * stale file per source, overwritten on the next load — but the name now
31
+ * carries a pid and a sequence number, so every kill (a CLI hook timeout, a
32
+ * Ctrl-C) leaves a uniquely-named file behind forever.
33
+ *
34
+ * Two consequences, and the second is the visible one: the directory fills up,
35
+ * and `findSkippedPolicyFiles` reports each leftover back to the user as a
36
+ * policy file that will not load — an accusation about a file failproofai
37
+ * wrote itself.
38
+ */
39
+ const TMP_ARTIFACT_RE = /\.__failproofai_tmp__\./;
40
+
41
+ /** True for a file this loader generated rather than one the user wrote. */
42
+ export function isTmpArtifact(name: string): boolean {
43
+ return TMP_ARTIFACT_RE.test(name);
44
+ }
45
+
46
+ /**
47
+ * How stale a generated file must be before a sweep will remove it.
48
+ *
49
+ * Another process may be mid-load right now, and its temporary tree is live
50
+ * until its `import()` resolves. Well past the 10s a module load is allowed,
51
+ * so a sweep can never delete a file another loader is still using.
52
+ */
53
+ const TMP_SWEEP_MIN_AGE_MS = 60_000;
54
+
55
+ /**
56
+ * Remove generated files left behind by a load that was killed.
57
+ *
58
+ * Best-effort and never throws: this runs on the hook path, and failing to
59
+ * tidy up is not worth failing an evaluation over.
60
+ */
61
+ export async function sweepStaleTmpArtifacts(dir: string, now = Date.now()): Promise<number> {
62
+ let removed = 0;
63
+ try {
64
+ const { readdir, stat } = await import("fs/promises");
65
+ for (const name of await readdir(dir)) {
66
+ if (!isTmpArtifact(name)) continue;
67
+ const path = resolve(dir, name);
68
+ try {
69
+ const info = await stat(path);
70
+ if (now - info.mtimeMs < TMP_SWEEP_MIN_AGE_MS) continue;
71
+ await unlink(path);
72
+ removed++;
73
+ } catch {
74
+ /* raced with another sweep, or not ours to remove */
75
+ }
76
+ }
77
+ } catch {
78
+ /* unreadable directory — nothing to sweep */
79
+ }
80
+ return removed;
81
+ }
82
+
20
83
  /** Regex to find local relative import specifiers (ESM). */
21
84
  const LOCAL_IMPORT_RE = /(?:import\s+(?:[\s\S]*?\s+from\s+)?|export\s+(?:[\s\S]*?\s+from\s+))(['"])(\.\.?\/[^'"]+)\1/g;
22
85
 
@@ -74,11 +137,57 @@ export async function resolveLocalImport(
74
137
  * Exports the full public API of failproofai: customPolicies, allow, deny, instruct,
75
138
  * getCustomHooks, clearCustomHooks.
76
139
  */
140
+ /**
141
+ * Create the ESM shim that lets a policy file's `import ... from 'failproofai'`
142
+ * resolve, and return where it landed.
143
+ *
144
+ * NOT beside `dist/index.js`, which is where this used to go. That directory
145
+ * belongs to whoever installed the package, and on a system-wide install
146
+ * (`sudo npm i -g`, a container image, a shared build host, a CI runner) it is
147
+ * root-owned — so every non-root user running a hook failed with EACCES, the
148
+ * policy never loaded, and the hook exited 0. Builtins kept firing while
149
+ * cloud-managed and custom policies silently stopped, so the machine looked
150
+ * protected. That is the worst available failure for a policy engine.
151
+ *
152
+ * Two locations, and the ORDER OF OPERATIONS matters more than the choice:
153
+ *
154
+ * 1. `<home>/state/shims` — created at 0700 and owned by the user running the
155
+ * hook. Unlike a shared `/tmp`, no other local user can pre-plant a file at
156
+ * a path we are about to `import`.
157
+ * 2. `os.tmpdir()` — only when the first is unusable.
158
+ *
159
+ * **The write lives inside the same `try` as the mkdir on purpose.** `mkdir`
160
+ * with `recursive` RESOLVES on a directory that already exists, whatever its
161
+ * mode — so a `state/shims` left behind unwritable (a container that ran the
162
+ * CLI as root and then dropped to a non-root user; this very function creates
163
+ * that chain) would sail past a mkdir-only guard and throw EACCES on the write,
164
+ * reproducing the exact fail-open this function exists to fix. Any failure of
165
+ * either step falls through to the fallback, and says so — a silent slide into
166
+ * the weaker path is the same class of bug again.
167
+ */
168
+ async function writeShim(
169
+ dir: string,
170
+ name: string,
171
+ code: string,
172
+ exclusive: boolean,
173
+ ): Promise<string> {
174
+ const shimPath = join(dir, name);
175
+ // 0600 explicitly: an unset mode is `0666 & ~umask`, and `umask 000` is
176
+ // routine in containers — which in the fallback means a world-writable file
177
+ // in shared /tmp that this process then imports.
178
+ await writeFile(shimPath, code, {
179
+ encoding: "utf-8",
180
+ mode: 0o600,
181
+ ...(exclusive ? { flag: "wx" as const } : {}),
182
+ });
183
+ return shimPath;
184
+ }
185
+
77
186
  export async function createEsmShim(
78
- distIndex: string,
187
+ _distIndex: string,
79
188
  distUrl: string,
189
+ tmpSuffix = TMP_SUFFIX,
80
190
  ): Promise<{ shimPath: string; shimUrl: string }> {
81
- const shimPath = distIndex + ".__failproofai_esm_shim__.mjs";
82
191
  const shimCode = [
83
192
  `import _cjs from '${distUrl}';`,
84
193
  `export const customPolicies = _cjs.customPolicies;`,
@@ -89,7 +198,35 @@ export async function createEsmShim(
89
198
  `export const instruct = _cjs.instruct;`,
90
199
  `export default _cjs;`,
91
200
  ].join("\n");
92
- await writeFile(shimPath, shimCode, "utf-8");
201
+ // The name still carries `tmpSuffix` (pid + load sequence + a random id).
202
+ // `fingerprintTemporaryTree` normalises exactly that substring away before
203
+ // hashing, so the policy module cache still hits; a name it could not
204
+ // normalise would force a cold module load on every single hook call.
205
+ const name = `failproofai-dist${tmpSuffix}.shim.mjs`;
206
+
207
+ try {
208
+ const dir = shimsDir();
209
+ await mkdir(dir, { recursive: true, mode: 0o700 });
210
+ // `mkdir` accepts a pre-existing SYMLINK here and a plain write would
211
+ // follow it out of the home. Confirm we got a real directory we own,
212
+ // unreadable to anyone else, before writing something we are about to
213
+ // execute.
214
+ const st = await lstat(dir);
215
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
216
+ if (!st.isDirectory() || st.isSymbolicLink() || (uid !== null && st.uid !== uid) || st.mode & 0o077) {
217
+ throw new Error(`${dir} is not a private directory owned by this user`);
218
+ }
219
+ const shimPath = await writeShim(dir, name, shimCode, false);
220
+ return { shimPath, shimUrl: pathToFileURL(shimPath).href };
221
+ } catch (err) {
222
+ hookLogWarn(
223
+ `policy shim directory unusable (${err instanceof Error ? err.message : String(err)}); falling back to ${tmpdir()}`,
224
+ );
225
+ }
226
+
227
+ // Predictable name in a shared directory, so O_EXCL: refuse rather than write
228
+ // through something another user planted.
229
+ const shimPath = await writeShim(tmpdir(), name, shimCode, true);
93
230
  return { shimPath, shimUrl: pathToFileURL(shimPath).href };
94
231
  }
95
232
 
@@ -101,6 +238,17 @@ export async function rewriteFileTree(
101
238
  entryPath: string,
102
239
  distUrl: string | null,
103
240
  distIndex: string | null,
241
+ tmpSuffix = TMP_SUFFIX,
242
+ /**
243
+ * SHA-256 the ENTRY file's raw bytes must match, re-checked here at the moment
244
+ * they are read for rewriting and import. Cloud-managed policies pass their
245
+ * pinned digest so the bytes that get imported are the same bytes that were
246
+ * verified — closing a TOCTOU where an earlier caller hashed the file, then
247
+ * this function independently re-read it (a window in which a same-user
248
+ * attacker could swap the bytes so a verified file imports unverified code).
249
+ * Only the entry is content-addressed; transitive local imports are not.
250
+ */
251
+ entrySha256?: string | null,
104
252
  ): Promise<string[]> {
105
253
  const queue: string[] = [entryPath];
106
254
  const visited = new Set<string>();
@@ -108,7 +256,7 @@ export async function rewriteFileTree(
108
256
 
109
257
  let esmShimUrl: string | null = null;
110
258
  if (distIndex && distUrl) {
111
- const shim = await createEsmShim(distIndex, distUrl);
259
+ const shim = await createEsmShim(distIndex, distUrl, tmpSuffix);
112
260
  tmpFiles.push(shim.shimPath);
113
261
  esmShimUrl = shim.shimUrl;
114
262
  }
@@ -118,7 +266,22 @@ export async function rewriteFileTree(
118
266
  if (visited.has(filePath)) continue;
119
267
  visited.add(filePath);
120
268
 
121
- let code = await readFile(filePath, "utf-8");
269
+ let code: string;
270
+ if (entrySha256 && filePath === entryPath) {
271
+ // Read the raw bytes, verify the pin against THEM, then decode and rewrite
272
+ // that same in-memory content — filePath is never read again, so the bytes
273
+ // imported below are exactly the bytes verified here.
274
+ const buf = await readFile(filePath);
275
+ const actual = createHash("sha256").update(buf).digest("hex");
276
+ if (actual !== entrySha256) {
277
+ throw new Error(
278
+ `cloud-managed policy failed integrity re-verification at load: expected ${entrySha256}, got ${actual}`,
279
+ );
280
+ }
281
+ code = buf.toString("utf-8");
282
+ } else {
283
+ code = await readFile(filePath, "utf-8");
284
+ }
122
285
 
123
286
  // Rewrite 'failproofai' or legacy 'claudeye' imports to the ESM shim (or direct CJS for require)
124
287
  if (esmShimUrl) {
@@ -148,7 +311,7 @@ export async function rewriteFileTree(
148
311
  if (!visited.has(resolved) && !queue.includes(resolved)) {
149
312
  queue.push(resolved);
150
313
  }
151
- let relPath = relative(dir, resolved + TMP_SUFFIX).split("\\").join("/");
314
+ let relPath = relative(dir, resolved + tmpSuffix).split("\\").join("/");
152
315
  if (!relPath.startsWith(".")) relPath = "./" + relPath;
153
316
  rewrites.set(specifier, relPath);
154
317
  }
@@ -163,7 +326,7 @@ export async function rewriteFileTree(
163
326
  code = code.replace(new RegExp(`"${escaped}"`, "g"), `"${replacement}"`);
164
327
  }
165
328
 
166
- const tmpPath = filePath + TMP_SUFFIX;
329
+ const tmpPath = filePath + tmpSuffix;
167
330
  await writeFile(tmpPath, code, "utf-8");
168
331
  tmpFiles.push(tmpPath);
169
332
  }