failproofai 1.0.0-beta.5 → 1.0.0-beta.7

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 (368) 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]__0c_qcgp._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kbc9bs._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.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/_0r-dsre._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/_194yh9u._.js +3 -0
  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/0945ybz3fsvba.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{0cr85wuc9gc9k.js → 0gr76ps8_zm26.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/{2cqk4a9s893oy.js → 0m-m7wgtxdoav.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/{1tp9dex23zs8y.js → 11l6oh9cyb35a.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/166f8qnh03jqc.js +6 -0
  154. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  155. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  156. package/.next/standalone/.next/static/chunks/{2s9g1dsde_ts1.js → 2aej0vi1fm87m.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{1bkynffp3q1f7.js → 2bxld6-rym3ah.js} +22 -22
  158. package/.next/standalone/.next/static/chunks/{2v1rpzrh2i1wv.js → 2p2kz4aj1tooj.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  160. package/.next/standalone/.next/static/chunks/36pc3_8fpb6ic.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{2k285z07qce47.js → 3t1wr_kx6brwc.js} +1 -1
  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 +202 -86
  197. package/dist/cli.mjs +17803 -15346
  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 +750 -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 +605 -29
  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 +355 -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-attempt.ts +179 -0
  246. package/src/hooks/onboarding-lock.ts +119 -0
  247. package/src/hooks/policy-types.ts +0 -7
  248. package/src/hooks/session-pause-cli.ts +15 -13
  249. package/src/hooks/session-pause.ts +36 -4
  250. package/src/hooks/setup-state.ts +256 -0
  251. package/src/hooks/tui.ts +4 -4
  252. package/src/hooks/worker-server.ts +53 -1
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__094nop3._.js +0 -3
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  264. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +0 -4
  265. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.js +0 -3
  266. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  267. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  268. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1gak4pd._.js +0 -3
  270. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
  271. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  272. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  273. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_12c_2cz.js +0 -3
  274. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  275. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  276. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  277. package/.next/standalone/.next/static/chunks/1bw4dfzflelcv.js +0 -1
  278. package/.next/standalone/.next/static/chunks/34sjdw8lv46e3.js +0 -6
  279. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  280. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
  281. package/.next/standalone/Cargo.lock +0 -2222
  282. package/.next/standalone/Cargo.toml +0 -9
  283. package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
  284. package/.next/standalone/crates/PROTOCOL.md +0 -122
  285. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
  286. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
  287. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
  288. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  289. package/.next/standalone/crates/failproofaid/src/main.rs +0 -639
  290. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
  291. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  292. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  293. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  294. package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
  295. package/.next/standalone/crates/fpai-collect/src/config.rs +0 -420
  296. package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
  297. package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
  298. package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -487
  299. package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
  300. package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
  301. package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
  302. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
  303. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
  304. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
  305. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
  306. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
  307. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
  308. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
  309. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
  310. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
  311. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
  312. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -314
  313. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
  314. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
  315. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
  316. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
  317. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
  318. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
  319. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
  320. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
  321. package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -535
  322. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -390
  323. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
  324. package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
  325. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -739
  326. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -394
  327. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -976
  328. package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
  329. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1020
  330. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
  331. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1009
  332. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1130
  333. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -622
  334. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -855
  335. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
  336. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1192
  337. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
  338. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
  339. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  340. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  341. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  342. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  343. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  344. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  345. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  346. package/.next/standalone/integration-suite/Dockerfile +0 -47
  347. package/.next/standalone/integration-suite/README.md +0 -129
  348. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  349. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  350. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  351. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  352. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  353. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  354. package/.next/standalone/integration-suite/report.js +0 -120
  355. package/.next/standalone/integration-suite/run.sh +0 -159
  356. package/.next/standalone/openclaw-plugin/index.js +0 -223
  357. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  358. package/.next/standalone/openclaw-plugin/package.json +0 -12
  359. package/.next/standalone/osv-scanner.toml +0 -17
  360. package/.next/standalone/pi-extension/index.ts +0 -524
  361. package/.next/standalone/pi-extension/package.json +0 -12
  362. package/.next/standalone/postcss.config.mjs +0 -8
  363. package/.next/standalone/readme-arch-hq.gif +0 -0
  364. package/.next/standalone/rust-toolchain.toml +0 -4
  365. package/src/auth/cli.ts +0 -371
  366. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_buildManifest.js +0 -0
  367. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_clientMiddlewareManifest.js +0 -0
  368. /package/.next/standalone/.next/static/{LOuwMmfomO6hQYfMQYs4K → LUaLkKg40nSkHrIajexIk}/_ssgManifest.js +0 -0
@@ -43,6 +43,7 @@ import { hookLogInfo, hookLogWarn } from "./hook-logger";
43
43
  import { readStdinPayload } from "./read-stdin";
44
44
  import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from "./cloud-managed-policies";
45
45
  import { readActivePause, type ActivePause } from "./session-pause";
46
+ import { layoutWarningForHook } from "./fp-reset";
46
47
 
47
48
  /**
48
49
  * Canonicalize an event name to PascalCase. Codex sends snake_case event names
@@ -182,6 +183,13 @@ export async function evaluateHookEvent(
182
183
  opts?: EvaluateHookEventOptions,
183
184
  ): Promise<HookEventOutcome> {
184
185
  const startTime = performance.now();
186
+ // A home from another layout resolves to no global config, so every builtin
187
+ // silently stops firing — the machine looks protected and is not. Say so on
188
+ // every call rather than deleting anything (a hook is unattended) or denying
189
+ // (a blanket deny takes UserPromptSubmit with it and locks the user out of
190
+ // their agent entirely — demonstrated on a real machine during this work).
191
+ const layoutWarning = layoutWarningForHook();
192
+ if (layoutWarning) hookLogWarn(layoutWarning);
185
193
  try {
186
194
  let parsed: Record<string, unknown> = {};
187
195
  if (stdinPayload) {
@@ -286,7 +294,40 @@ export async function evaluateHookEvent(
286
294
  // Cloud-managed policies are daemon-reconciled artifacts, but they use
287
295
  // the same public JS policy API as local custom policies. Verify and add
288
296
  // only the paths referenced by the atomically active generation.
289
- const cloudManagedPolicies = readActiveCloudManagedPolicies();
297
+ //
298
+ // Wrapped, like `readConfigAt` and `readActivePause` above it. This call
299
+ // has fourteen throw sites — a malformed manifest, an unsafe id, an
300
+ // integrity mismatch, an unknown effect — and it sat bare inside a `try`
301
+ // whose only handler is a `finally`, so any of them aborted the entire
302
+ // evaluation. What that cost depended on where the hook ran, and none of
303
+ // the outcomes were the intended one: on a daemon machine the client
304
+ // fail-closed denies every tool call, and off the daemon path the throw
305
+ // reaches `bin/failproofai.mjs`'s outer catch, which exits 2 with nothing
306
+ // on stdout — a deny on Claude and Factory, but a logged warning followed
307
+ // by an ALLOW on Copilot, Cursor, Goose, Pi and Hermes, which read a
308
+ // decision off stdout and ignore the exit code. One corrupt byte was
309
+ // either a permanent machine-wide lockout or silent non-enforcement,
310
+ // depending on the CLI.
311
+ //
312
+ // Failing open here degrades ONE layer: builtins and local custom
313
+ // policies were already registered above and keep enforcing. That is the
314
+ // same trade `2ad735e5` made on the Rust side, and it is strictly better
315
+ // than the status quo, which already failed open on five of the twelve —
316
+ // just accidentally, and without saying so.
317
+ let cloudManagedPolicies: CloudManagedPolicyArtifact[] = [];
318
+ try {
319
+ cloudManagedPolicies = readActiveCloudManagedPolicies();
320
+ } catch (err) {
321
+ const msg = err instanceof Error ? err.message : String(err);
322
+ hookLogWarn(
323
+ `[failproofai] cloud-managed policies could NOT be loaded and are not being enforced: ${msg}. ` +
324
+ `Local policies are unaffected. Run \`failproofai config --status\` to check this machine's enrolment.`,
325
+ );
326
+ void trackHookEvent(getInstanceId(), "cloud_managed_load_error", {
327
+ event_type: eventType,
328
+ cli,
329
+ });
330
+ }
290
331
  // Recorded on every row once a machine is managed, whether or not a cloud
291
332
  // policy decided this event: "what was deployed here at the time" is a
292
333
  // separate question from "what decided", and only the former can tell a
@@ -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 ──
@@ -134,8 +147,8 @@ export interface HookActivityStats {
134
147
  // ── Directory setup ──
135
148
 
136
149
  function ensureDir(): void {
137
- if (!existsSync(storeDir)) {
138
- mkdirSync(storeDir, { recursive: true });
150
+ if (!existsSync(storeDirValue())) {
151
+ mkdirSync(storeDirValue(), { recursive: true });
139
152
  }
140
153
  }
141
154
 
@@ -143,7 +156,7 @@ function ensureDir(): void {
143
156
 
144
157
  function acquireLock(): void {
145
158
  ensureDir();
146
- const lockPath = join(storeDir, LOCK_FILE);
159
+ const lockPath = join(storeDirValue(), LOCK_FILE);
147
160
  const deadline = Date.now() + LOCK_STALE_MS;
148
161
  while (Date.now() < deadline) {
149
162
  try {
@@ -166,7 +179,7 @@ function acquireLock(): void {
166
179
  }
167
180
 
168
181
  function releaseLock(): void {
169
- try { unlinkSync(join(storeDir, LOCK_FILE)); } catch { /* ignore */ }
182
+ try { unlinkSync(join(storeDirValue(), LOCK_FILE)); } catch { /* ignore */ }
170
183
  }
171
184
 
172
185
  // ── Writing (synchronous — hook handler is short-lived) ──
@@ -175,8 +188,8 @@ export function persistHookActivity(entry: HookActivityEntry): void {
175
188
  ensureDir();
176
189
  acquireLock();
177
190
  try {
178
- const currentPath = join(storeDir, CURRENT_FILE);
179
- const countPath = join(storeDir, COUNT_FILE);
191
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
192
+ const countPath = join(storeDirValue(), COUNT_FILE);
180
193
 
181
194
  const lineCount = readCount(countPath);
182
195
  if (lineCount >= PAGE_SIZE) {
@@ -198,7 +211,7 @@ export function persistHookActivity(entry: HookActivityEntry): void {
198
211
 
199
212
  function rotate(currentPath: string, countPath: string): void {
200
213
  const archiveName = `page-${Date.now()}-${rotateSeq++}.jsonl`;
201
- const archivePath = join(storeDir, archiveName);
214
+ const archivePath = join(storeDirValue(), archiveName);
202
215
  renameSync(currentPath, archivePath);
203
216
  // Reset count for the fresh file (write 0 — next append will set it to 1)
204
217
  writeCount(countPath, 0);
@@ -240,7 +253,7 @@ interface StoredStats {
240
253
 
241
254
  function readStoredStats(): StoredStats {
242
255
  try {
243
- 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;
244
257
  } catch {
245
258
  return { totalEvents: 0, denyCount: 0, policyMap: {} };
246
259
  }
@@ -258,10 +271,10 @@ function updateStats(entry: HookActivityEntry): void {
258
271
  s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
259
272
  }
260
273
  // Write atomically: write to a PID-unique temp file then rename — prevents partial reads.
261
- const tmpPath = join(storeDir, `stats.json.${process.pid}.tmp`);
274
+ const tmpPath = join(storeDirValue(), `stats.json.${process.pid}.tmp`);
262
275
  try {
263
276
  writeFileSync(tmpPath, JSON.stringify(s), "utf-8");
264
- renameSync(tmpPath, join(storeDir, STATS_FILE));
277
+ renameSync(tmpPath, join(storeDirValue(), STATS_FILE));
265
278
  } catch {
266
279
  try { unlinkSync(tmpPath); } catch { /* ignore */ }
267
280
  // Non-fatal: stats file write failure doesn't block the hook
@@ -288,7 +301,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
288
301
  if (page < 1) return [];
289
302
 
290
303
  if (page === 1) {
291
- const currentPath = join(storeDir, CURRENT_FILE);
304
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
292
305
  return readJsonlFile(currentPath).reverse();
293
306
  }
294
307
 
@@ -296,7 +309,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
296
309
  const archiveIndex = page - 2;
297
310
  if (archiveIndex >= archives.length) return [];
298
311
 
299
- return readJsonlFile(join(storeDir, archives[archiveIndex])).reverse();
312
+ return readJsonlFile(join(storeDirValue(), archives[archiveIndex])).reverse();
300
313
  }
301
314
 
302
315
  export function getHookActivityPageCount(): number {
@@ -307,13 +320,13 @@ export function getHookActivityPageCount(): number {
307
320
  export function getAllHookActivityEntries(): HookActivityEntry[] {
308
321
  ensureDir();
309
322
 
310
- const currentPath = join(storeDir, CURRENT_FILE);
323
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
311
324
  const currentEntries = readJsonlFile(currentPath).reverse();
312
325
 
313
326
  const archives = getArchiveFiles();
314
327
  const archiveEntries: HookActivityEntry[] = [];
315
328
  for (const file of archives) {
316
- const entries = readJsonlFile(join(storeDir, file));
329
+ const entries = readJsonlFile(join(storeDirValue(), file));
317
330
  archiveEntries.push(...entries.reverse());
318
331
  }
319
332
 
@@ -392,7 +405,7 @@ function readJsonlFile(filePath: string): HookActivityEntry[] {
392
405
 
393
406
  function getArchiveFiles(): string[] {
394
407
  try {
395
- const files = readdirSync(storeDir);
408
+ const files = readdirSync(storeDirValue());
396
409
  return files
397
410
  .filter((f) => f.startsWith("page-") && f.endsWith(".jsonl"))
398
411
  .sort((a, b) => {
@@ -414,5 +427,7 @@ function getArchiveFiles(): string[] {
414
427
 
415
428
  export function _resetForTest(testDir?: string): void {
416
429
  rotateSeq = 0;
417
- storeDir = testDir ?? DEFAULT_STORE_DIR;
430
+ // null, not the default path: clearing the override lets the getter re-read
431
+ // FAILPROOFAI_HOME, which a test may have changed since this module loaded.
432
+ storeDirOverride = testDir ?? null;
418
433
  }
@@ -22,13 +22,14 @@ import {
22
22
  } from "node:fs";
23
23
  import { join } from "node:path";
24
24
  import { homedir } from "node:os";
25
+ import { logsDir } from "./fp-home";
25
26
 
26
27
  export type LogLevel = "info" | "warn" | "error";
27
28
 
28
29
  const LEVEL_ORDER: Record<LogLevel, number> = { info: 0, warn: 1, error: 2 };
29
30
  const MAX_FILE_SIZE = 512 * 1024; // 500 KB
30
31
  const LOG_FILENAME = "hooks.log";
31
- const DEFAULT_LOG_DIR = join(homedir(), ".failproofai", "logs");
32
+ const DEFAULT_LOG_DIR = logsDir();
32
33
 
33
34
  // ── State (lazy-initialized) ──
34
35
 
@@ -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":
@@ -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,12 +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
187
  distIndex: string,
79
188
  distUrl: string,
80
189
  tmpSuffix = TMP_SUFFIX,
81
190
  ): Promise<{ shimPath: string; shimUrl: string }> {
82
- const shimPath = `${distIndex}${tmpSuffix}.shim.mjs`;
83
191
  const shimCode = [
84
192
  `import _cjs from '${distUrl}';`,
85
193
  `export const customPolicies = _cjs.customPolicies;`,
@@ -90,7 +198,35 @@ export async function createEsmShim(
90
198
  `export const instruct = _cjs.instruct;`,
91
199
  `export default _cjs;`,
92
200
  ].join("\n");
93
- 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);
94
230
  return { shimPath, shimUrl: pathToFileURL(shimPath).href };
95
231
  }
96
232
 
@@ -103,6 +239,16 @@ export async function rewriteFileTree(
103
239
  distUrl: string | null,
104
240
  distIndex: string | null,
105
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,
106
252
  ): Promise<string[]> {
107
253
  const queue: string[] = [entryPath];
108
254
  const visited = new Set<string>();
@@ -120,7 +266,22 @@ export async function rewriteFileTree(
120
266
  if (visited.has(filePath)) continue;
121
267
  visited.add(filePath);
122
268
 
123
- 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
+ }
124
285
 
125
286
  // Rewrite 'failproofai' or legacy 'claudeye' imports to the ESM shim (or direct CJS for require)
126
287
  if (esmShimUrl) {
@@ -24,6 +24,7 @@ import { trackHookEvent } from "./hook-telemetry";
24
24
  import { getInstanceId, hashToId } from "../../lib/telemetry-id";
25
25
  import { CliError } from "../cli-error";
26
26
  import { hookLogWarn } from "./hook-logger";
27
+ import { customPoliciesDir, globalPolicyConfigFile } from "./fp-home";
27
28
 
28
29
  const VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
29
30
 
@@ -645,7 +646,7 @@ export async function listHooks(cwd?: string): Promise<void> {
645
646
  } else {
646
647
  console.log("\n Run `failproofai policies --install` to get started.");
647
648
  }
648
- console.log(" Config: ~/.failproofai/policies-config.json\n");
649
+ console.log(` Config: ${globalPolicyConfigFile()}\n`);
649
650
  } else if (installedScopes.length === 1) {
650
651
  // State B: Single scope — table with header row
651
652
  const scope = installedScopes[0];
@@ -657,7 +658,7 @@ export async function listHooks(cwd?: string): Promise<void> {
657
658
  for (const policy of regularPolicies) printSimpleRow(policy);
658
659
  printBetaSection(printSimpleRow);
659
660
 
660
- console.log("\n Config: ~/.failproofai/policies-config.json\n");
661
+ console.log(`\n Config: ${globalPolicyConfigFile()}\n`);
661
662
  } else {
662
663
  // State C: Multiple scopes — column table
663
664
  const COL = 9;
@@ -701,7 +702,7 @@ export async function listHooks(cwd?: string): Promise<void> {
701
702
  for (const policy of betaPolicies) printMultiScopeRow(policy);
702
703
  }
703
704
 
704
- console.log("\n Config: ~/.failproofai/policies-config.json");
705
+ console.log(`\n Config: ${globalPolicyConfigFile()}`);
705
706
 
706
707
  // Multi-scope warning
707
708
  const scopeNames = installedScopes.join(", ");
@@ -765,7 +766,7 @@ export async function listHooks(cwd?: string): Promise<void> {
765
766
  // hook path was loading them.
766
767
  const base = findProjectConfigDir(cwd ?? process.cwd());
767
768
  const projectDir = resolve(base, ".failproofai", "policies");
768
- const userDir = resolve(homedir(), ".failproofai", "policies");
769
+ const userDir = customPoliciesDir();
769
770
  const sameDir = userDir === projectDir;
770
771
  const conventionDirs: { label: string; dir: string }[] = [
771
772
  { label: sameDir ? "Project + User" : "Project", dir: projectDir },