failproofai 1.0.0-beta.0 → 1.0.0-beta.10

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 (357) 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 +2 -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]__06hexd0._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  119. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  120. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  121. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  122. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04pwh4t._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  139. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1y4qr7u._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  153. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  155. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  157. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  159. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  160. package/.next/standalone/.next/server/middleware.js +2 -2
  161. package/.next/standalone/.next/server/pages/404.html +1 -1
  162. package/.next/standalone/.next/server/pages/500.html +1 -1
  163. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  165. package/.next/standalone/.next/static/chunks/00adubtgov21s.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  170. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  171. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
  172. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
  173. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  174. package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
  175. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  176. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
  177. package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
  178. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  179. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
  180. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  181. package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
  182. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  184. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  185. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  186. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  187. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  188. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  189. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  190. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  191. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  192. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  193. package/.next/standalone/app/audit/audit-styles.css +6 -149
  194. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  195. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  196. package/.next/standalone/app/settings/page.tsx +31 -0
  197. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  198. package/.next/standalone/components/navbar.tsx +2 -0
  199. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  200. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  201. package/.next/standalone/lib/codex-sessions.ts +2 -1
  202. package/.next/standalone/lib/dashboard-host.ts +101 -0
  203. package/.next/standalone/lib/install-check.ts +22 -7
  204. package/.next/standalone/lib/pi-sessions.ts +99 -14
  205. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  206. package/.next/standalone/lib/telemetry-id.ts +52 -1
  207. package/.next/standalone/lib/telemetry.ts +8 -4
  208. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  209. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  210. package/.next/standalone/node_modules/react/package.json +3 -3
  211. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  212. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  213. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  214. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  215. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  216. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  217. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  218. package/.next/standalone/package.json +17 -10
  219. package/.next/standalone/proxy.ts +89 -1
  220. package/.next/standalone/server.js +1 -1
  221. package/bin/failproofai.mjs +518 -79
  222. package/dist/cli.mjs +11484 -6046
  223. package/dist/worker.mjs +2019 -272
  224. package/lib/auth/api-server-client.ts +2 -3
  225. package/lib/auth/auth-store.ts +4 -4
  226. package/lib/codex-sessions.ts +2 -1
  227. package/lib/dashboard-host.ts +101 -0
  228. package/lib/install-check.ts +22 -7
  229. package/lib/pi-sessions.ts +99 -14
  230. package/lib/telemetry-enabled.ts +54 -0
  231. package/lib/telemetry-id.ts +52 -1
  232. package/lib/telemetry.ts +8 -4
  233. package/package.json +17 -10
  234. package/scripts/build-daemon-packages.mjs +210 -0
  235. package/scripts/daemon-platforms.mjs +42 -0
  236. package/scripts/install-telemetry.mjs +8 -1
  237. package/scripts/launch.ts +25 -3
  238. package/scripts/parse-script-args.ts +17 -1
  239. package/scripts/prune-standalone.mjs +26 -0
  240. package/scripts/publish-aliases.mjs +62 -41
  241. package/scripts/repro-npm-install.sh +155 -0
  242. package/scripts/translate-docs/readme-translator.ts +163 -4
  243. package/scripts/validate-mdx.ts +153 -13
  244. package/src/audit/audit-lock.ts +285 -0
  245. package/src/audit/audit-schedule.ts +111 -0
  246. package/src/audit/cache.ts +18 -4
  247. package/src/audit/cli-adapters/goose.ts +2 -1
  248. package/src/audit/cli-adapters/hermes.ts +28 -10
  249. package/src/audit/cli.ts +191 -35
  250. package/src/audit/dashboard-cache.ts +2 -1
  251. package/src/hooks/backfill-cli.ts +247 -0
  252. package/src/hooks/builtin-policies.ts +390 -2
  253. package/src/hooks/cloud-connection.ts +354 -0
  254. package/src/hooks/cloud-enrollment-cli.ts +325 -0
  255. package/src/hooks/cloud-enrollment.ts +243 -0
  256. package/src/hooks/cloud-introspect.ts +147 -0
  257. package/src/hooks/cloud-managed-policies.ts +169 -0
  258. package/src/hooks/collector-config.ts +234 -0
  259. package/src/hooks/configure-wizard.ts +915 -158
  260. package/src/hooks/custom-hooks-loader.ts +191 -33
  261. package/src/hooks/daemon-client.ts +136 -17
  262. package/src/hooks/daemon-download.ts +192 -30
  263. package/src/hooks/daemon-service.ts +1190 -92
  264. package/src/hooks/first-run-gate.ts +50 -0
  265. package/src/hooks/fp-config.ts +497 -0
  266. package/src/hooks/fp-home.ts +375 -0
  267. package/src/hooks/fp-reset.ts +464 -0
  268. package/src/hooks/handler.ts +185 -6
  269. package/src/hooks/hook-activity-store.ts +85 -20
  270. package/src/hooks/hook-logger.ts +2 -1
  271. package/src/hooks/hook-telemetry.ts +5 -1
  272. package/src/hooks/hooks-config.ts +4 -3
  273. package/src/hooks/loader-utils.ts +171 -8
  274. package/src/hooks/manager.ts +5 -4
  275. package/src/hooks/onboarding-attempt.ts +179 -0
  276. package/src/hooks/onboarding-lock.ts +119 -0
  277. package/src/hooks/policy-types.ts +28 -0
  278. package/src/hooks/session-pause-cli.ts +159 -0
  279. package/src/hooks/session-pause.ts +268 -0
  280. package/src/hooks/setup-state.ts +256 -0
  281. package/src/hooks/tui.ts +119 -4
  282. package/src/hooks/uninstall-cli.ts +345 -0
  283. package/src/hooks/worker-server.ts +65 -3
  284. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  293. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  295. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0259h38._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  297. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  298. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  299. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  300. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  301. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  302. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  303. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  304. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
  305. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  306. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  307. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1-upb14.js +0 -3
  308. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  309. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  310. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  311. package/.next/standalone/.next/static/chunks/0u9hc6oxx-gpj.js +0 -6
  312. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  313. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  314. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  315. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  316. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  317. package/.next/standalone/.next/static/chunks/3wrgbih-516uj.js +0 -1
  318. package/.next/standalone/Cargo.lock +0 -428
  319. package/.next/standalone/Cargo.toml +0 -9
  320. package/.next/standalone/crates/PROTOCOL.md +0 -122
  321. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  322. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  323. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  324. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  325. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  326. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  327. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  328. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  329. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  330. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  331. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  332. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  333. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  334. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  335. package/.next/standalone/integration-suite/Dockerfile +0 -47
  336. package/.next/standalone/integration-suite/README.md +0 -129
  337. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  338. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  339. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  340. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  341. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  342. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  343. package/.next/standalone/integration-suite/report.js +0 -120
  344. package/.next/standalone/integration-suite/run.sh +0 -159
  345. package/.next/standalone/openclaw-plugin/index.js +0 -223
  346. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  347. package/.next/standalone/openclaw-plugin/package.json +0 -12
  348. package/.next/standalone/osv-scanner.toml +0 -17
  349. package/.next/standalone/pi-extension/index.ts +0 -524
  350. package/.next/standalone/pi-extension/package.json +0 -12
  351. package/.next/standalone/postcss.config.mjs +0 -8
  352. package/.next/standalone/readme-arch-hq.gif +0 -0
  353. package/.next/standalone/rust-toolchain.toml +0 -4
  354. package/src/auth/cli.ts +0 -371
  355. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
@@ -41,6 +41,9 @@ import { resolveTranscriptPath } from "./resolve-transcript-path";
41
41
  import { getInstanceId } from "../../lib/telemetry-id";
42
42
  import { hookLogInfo, hookLogWarn } from "./hook-logger";
43
43
  import { readStdinPayload } from "./read-stdin";
44
+ import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from "./cloud-managed-policies";
45
+ import { readActivePause, type ActivePause } from "./session-pause";
46
+ import { layoutWarningForHook } from "./fp-reset";
44
47
 
45
48
  /**
46
49
  * Canonicalize an event name to PascalCase. Codex sends snake_case event names
@@ -134,6 +137,37 @@ export interface EvaluateHookEventOptions {
134
137
  fallbackCwd?: string;
135
138
  }
136
139
 
140
+ /**
141
+ * Runs an observe-mode policy under the same timeout and error handling as an
142
+ * enforcing one, so what gets measured is what would actually have happened —
143
+ * including a policy that times out, which in enforce mode is an allow and must
144
+ * be recorded as one rather than as a would-deny.
145
+ */
146
+ async function runObserved(
147
+ hook: CustomHook,
148
+ ctx: Parameters<CustomHook["fn"]>[0],
149
+ hookName: string,
150
+ eventType: string,
151
+ cli: IntegrationType,
152
+ ): Promise<PolicyResult> {
153
+ try {
154
+ return await Promise.race([
155
+ hook.fn(ctx),
156
+ new Promise<PolicyResult>((_, reject) => setTimeout(() => reject(new Error("timeout")), 10_000)),
157
+ ]);
158
+ } catch (err) {
159
+ const msg = err instanceof Error ? err.message : String(err);
160
+ void trackHookEvent(getInstanceId(), "custom_hook_error", {
161
+ hook_name: hookName,
162
+ error_type: msg === "timeout" ? "timeout" : "exception",
163
+ event_type: eventType,
164
+ cli,
165
+ is_observe_mode: true,
166
+ });
167
+ return { decision: "allow" };
168
+ }
169
+ }
170
+
137
171
  /**
138
172
  * The core hook-evaluation logic, decoupled from process stdin/stdout so it
139
173
  * can be called repeatedly inside a long-lived process (the daemon's warm
@@ -149,6 +183,13 @@ export async function evaluateHookEvent(
149
183
  opts?: EvaluateHookEventOptions,
150
184
  ): Promise<HookEventOutcome> {
151
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);
152
193
  try {
153
194
  let parsed: Record<string, unknown> = {};
154
195
  if (stdinPayload) {
@@ -209,6 +250,20 @@ export async function evaluateHookEvent(
209
250
  let config: HooksConfig;
210
251
  let customHooksList: CustomHook[] = [];
211
252
  let conventionHookNames = new Set<string>();
253
+ let activePause: ActivePause | null = null;
254
+ /** Registered policy name → where it came from. See the set() below. */
255
+ const policyAttribution = new Map<
256
+ string,
257
+ { source: "custom" | "convention" | "cloud"; cloudPolicyId?: string; cloudRevision?: number }
258
+ >();
259
+ let cloudGeneration: number | undefined;
260
+ /** What observe-mode policies WOULD have done, had they been enforcing. */
261
+ const observedResults: Array<{
262
+ policyId: string;
263
+ revision: number;
264
+ decision: "deny" | "instruct";
265
+ reason: string | null;
266
+ }> = [];
212
267
 
213
268
  if (opts?.forceDecision) {
214
269
  // Fail-closed: no config/custom-hook loading at all — a daemon that
@@ -228,25 +283,113 @@ export async function evaluateHookEvent(
228
283
  // Load enabled policies (merge across project/local/global scopes)
229
284
  config = readMergedHooksConfig(session.cwd);
230
285
  clearPolicies();
231
- registerBuiltinPolicies(config.enabledPolicies);
286
+
287
+ // A session pause suspends LOCAL policy only, for a bounded time. Cloud
288
+ // assignments are exempt below for the same reason `disabledCustomPolicies`
289
+ // already exempts them: a locally-issued command that could switch off a
290
+ // centrally assigned policy would make cloud enforcement decorative.
291
+ activePause = readActivePause(session.sessionId);
292
+ registerBuiltinPolicies(activePause ? [] : config.enabledPolicies);
293
+
294
+ // Cloud-managed policies are daemon-reconciled artifacts, but they use
295
+ // the same public JS policy API as local custom policies. Verify and add
296
+ // only the paths referenced by the atomically active generation.
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
+ }
331
+ // Recorded on every row once a machine is managed, whether or not a cloud
332
+ // policy decided this event: "what was deployed here at the time" is a
333
+ // separate question from "what decided", and only the former can tell a
334
+ // rollout that changed nothing from one that never reached the machine.
335
+ cloudGeneration = cloudManagedPolicies[0]?.generation;
336
+ const configuredCustomPaths = config.customPoliciesPaths ?? config.customPoliciesPath;
337
+ const allExplicitPaths =
338
+ cloudManagedPolicies.length === 0
339
+ ? configuredCustomPaths
340
+ : [
341
+ ...(typeof configuredCustomPaths === "string" ? [configuredCustomPaths] : configuredCustomPaths ?? []),
342
+ ...cloudManagedPolicies.map((policy) => policy.path),
343
+ ];
232
344
 
233
345
  // Load and register custom hooks (layer 2, after builtins)
234
- const loadResult = await loadAllCustomHooks(config.customPoliciesPaths ?? config.customPoliciesPath, {
346
+ const loadResult = await loadAllCustomHooks(allExplicitPaths, {
235
347
  sessionCwd: session.cwd,
236
348
  customPoliciesEnabled: config.customPoliciesEnabled,
349
+ ...(cloudManagedPolicies.length > 0 ? { cloudManagedPolicies } : {}),
237
350
  });
238
351
  customHooksList = loadResult.hooks;
239
352
  const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
240
353
  conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
241
354
 
242
355
  for (const hook of customHooksList) {
243
- const policyId = (hook as CustomHook & { __policyId?: string }).__policyId;
244
- if (policyId && disabledCustomPolicies.has(policyId)) continue;
356
+ const taggedHook = hook as CustomHook & {
357
+ __policyId?: string;
358
+ __cloudManaged?: CloudManagedPolicyArtifact;
359
+ };
360
+ const policyId = taggedHook.__policyId;
361
+ const cloudManaged = taggedHook.__cloudManaged;
362
+ // Local config cannot disable a centrally assigned policy merely by
363
+ // copying its generated ID into disabledCustomPolicies.
364
+ if (!cloudManaged && policyId && disabledCustomPolicies.has(policyId)) continue;
365
+ // Same rule for a session pause — it suspends local policy, never cloud.
366
+ if (!cloudManaged && activePause) continue;
245
367
  const hookName = hook.name;
246
368
  const conventionScope = (hook as CustomHook & { __conventionScope?: string }).__conventionScope;
247
369
  const isConvention = !!conventionScope;
248
- const prefix = isConvention ? `.failproofai-${conventionScope}` : "custom";
370
+ const prefix = cloudManaged
371
+ ? `cloud/${cloudManaged.id}@${cloudManaged.revision}`
372
+ : isConvention
373
+ ? `.failproofai-${conventionScope}`
374
+ : "custom";
375
+ // Observe mode: run it for real, record what it decided, then hand back
376
+ // an allow. Evaluating and discarding is the whole point — a rollout is
377
+ // measured against real traffic before it can break anyone's work, and
378
+ // a policy that did not actually run would measure nothing.
379
+ const observeOnly = cloudManaged?.effect === "observe";
249
380
  const fn: PolicyFunction = async (ctx): Promise<PolicyResult> => {
381
+ if (observeOnly) {
382
+ const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
383
+ if (shadow.decision !== "allow") {
384
+ observedResults.push({
385
+ policyId: cloudManaged!.id,
386
+ revision: cloudManaged!.revision,
387
+ decision: shadow.decision,
388
+ reason: shadow.reason ?? null,
389
+ });
390
+ }
391
+ return { decision: "allow" };
392
+ }
250
393
  try {
251
394
  const result = await Promise.race([
252
395
  hook.fn(ctx),
@@ -268,8 +411,18 @@ export async function evaluateHookEvent(
268
411
  return { decision: "allow" };
269
412
  }
270
413
  };
414
+ const registeredName = `${prefix}/${hookName}`;
415
+ // Record where this policy came from as structured data, keyed by the
416
+ // exact name the evaluator will report back. The display name already
417
+ // encodes it ("cloud/org-guard@7/…"), but only as a string — so the one
418
+ // question cloud attribution has to answer, "which rollout produced
419
+ // this decision", could only be answered by re-parsing our own label.
420
+ policyAttribution.set(registeredName, {
421
+ source: cloudManaged ? "cloud" : isConvention ? "convention" : "custom",
422
+ ...(cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudRevision: cloudManaged.revision } : {}),
423
+ });
271
424
  registerPolicy(
272
- `${prefix}/${hookName}`,
425
+ registeredName,
273
426
  hook.description ?? "",
274
427
  fn,
275
428
  hook.match ?? {},
@@ -335,6 +488,32 @@ export async function evaluateHookEvent(
335
488
  cwd: session.cwd,
336
489
  permissionMode: session.permissionMode,
337
490
  hookEventName: session.hookEventName,
491
+ // Attribution. A builtin is anything registered that is not in the map,
492
+ // so its absence is meaningful rather than missing — but only when a
493
+ // policy actually decided; a plain allow names nobody.
494
+ ...(result.policyName
495
+ ? (() => {
496
+ const attribution = policyAttribution.get(result.policyName);
497
+ return {
498
+ policySource: attribution?.source ?? ("builtin" as const),
499
+ ...(attribution?.cloudPolicyId ? { cloudPolicyId: attribution.cloudPolicyId } : {}),
500
+ ...(attribution?.cloudRevision !== undefined
501
+ ? { cloudRevision: attribution.cloudRevision }
502
+ : {}),
503
+ };
504
+ })()
505
+ : {}),
506
+ ...(cloudGeneration !== undefined ? { cloudGeneration } : {}),
507
+ // The point of observe mode is this record. Without it the rollout is
508
+ // unmeasurable and the row is indistinguishable from one where the policy
509
+ // never matched at all.
510
+ ...(observedResults.length > 0 ? { observed: observedResults } : {}),
511
+ // Without these, a row logged during a pause is indistinguishable from
512
+ // one where every policy ran and allowed — the log would assert a clean
513
+ // window over exactly the window that was not enforced.
514
+ ...(activePause
515
+ ? { pausedBy: activePause.setBy, pauseExpiresAt: activePause.expiresAt }
516
+ : {}),
338
517
  };
339
518
  try {
340
519
  persistHookActivity(activityEntry);
@@ -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 revision of that policy — the half of attribution that identifies WHICH version ran. */
103
+ cloudRevision?: number;
104
+ /**
105
+ * The cloud generation 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
+ cloudGeneration?: 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
+ revision: 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,7 +253,7 @@ 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
  }
@@ -213,10 +271,10 @@ function updateStats(entry: HookActivityEntry): void {
213
271
  s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
214
272
  }
215
273
  // Write atomically: write to a PID-unique temp file then rename — prevents partial reads.
216
- const tmpPath = join(storeDir, `stats.json.${process.pid}.tmp`);
274
+ const tmpPath = join(storeDirValue(), `stats.json.${process.pid}.tmp`);
217
275
  try {
218
276
  writeFileSync(tmpPath, JSON.stringify(s), "utf-8");
219
- renameSync(tmpPath, join(storeDir, STATS_FILE));
277
+ renameSync(tmpPath, join(storeDirValue(), STATS_FILE));
220
278
  } catch {
221
279
  try { unlinkSync(tmpPath); } catch { /* ignore */ }
222
280
  // Non-fatal: stats file write failure doesn't block the hook
@@ -243,7 +301,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
243
301
  if (page < 1) return [];
244
302
 
245
303
  if (page === 1) {
246
- const currentPath = join(storeDir, CURRENT_FILE);
304
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
247
305
  return readJsonlFile(currentPath).reverse();
248
306
  }
249
307
 
@@ -251,7 +309,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
251
309
  const archiveIndex = page - 2;
252
310
  if (archiveIndex >= archives.length) return [];
253
311
 
254
- return readJsonlFile(join(storeDir, archives[archiveIndex])).reverse();
312
+ return readJsonlFile(join(storeDirValue(), archives[archiveIndex])).reverse();
255
313
  }
256
314
 
257
315
  export function getHookActivityPageCount(): number {
@@ -262,13 +320,13 @@ export function getHookActivityPageCount(): number {
262
320
  export function getAllHookActivityEntries(): HookActivityEntry[] {
263
321
  ensureDir();
264
322
 
265
- const currentPath = join(storeDir, CURRENT_FILE);
323
+ const currentPath = join(storeDirValue(), CURRENT_FILE);
266
324
  const currentEntries = readJsonlFile(currentPath).reverse();
267
325
 
268
326
  const archives = getArchiveFiles();
269
327
  const archiveEntries: HookActivityEntry[] = [];
270
328
  for (const file of archives) {
271
- const entries = readJsonlFile(join(storeDir, file));
329
+ const entries = readJsonlFile(join(storeDirValue(), file));
272
330
  archiveEntries.push(...entries.reverse());
273
331
  }
274
332
 
@@ -298,6 +356,11 @@ export function searchHookActivity(
298
356
  return false;
299
357
  }
300
358
  if (filters.integration && entry.integration !== filters.integration) return false;
359
+ // Rows written before attribution existed have no policySource. They are
360
+ // excluded from every source filter rather than guessed at — a wrong
361
+ // attribution is worse than a missing one when the point is proving which
362
+ // rollout decided something.
363
+ if (filters.source && entry.policySource !== filters.source) return false;
301
364
  return true;
302
365
  });
303
366
 
@@ -342,7 +405,7 @@ function readJsonlFile(filePath: string): HookActivityEntry[] {
342
405
 
343
406
  function getArchiveFiles(): string[] {
344
407
  try {
345
- const files = readdirSync(storeDir);
408
+ const files = readdirSync(storeDirValue());
346
409
  return files
347
410
  .filter((f) => f.startsWith("page-") && f.endsWith(".jsonl"))
348
411
  .sort((a, b) => {
@@ -364,5 +427,7 @@ function getArchiveFiles(): string[] {
364
427
 
365
428
  export function _resetForTest(testDir?: string): void {
366
429
  rotateSeq = 0;
367
- 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;
368
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":