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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -1
  37. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  47. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  50. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/index.html +1 -1
  53. package/.next/standalone/.next/server/app/index.rsc +15 -15
  54. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  55. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  56. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  57. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  58. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  59. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/page.js +3 -1
  61. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  64. package/.next/standalone/.next/server/app/policies/page.js +4 -2
  65. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  69. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  81. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  82. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  83. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  84. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  85. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  86. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  87. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  88. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  89. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  90. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  97. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  98. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p7s8-d._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  109. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  110. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  111. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  112. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_a_gwn._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e7-pa7._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rtdfof._.js → [root-of-the-server]__1rdruw4._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rag5dg._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  125. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/_0igtyj5._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  135. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  138. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  141. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  143. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  144. package/.next/standalone/.next/server/middleware.js +2 -2
  145. package/.next/standalone/.next/server/pages/404.html +1 -1
  146. package/.next/standalone/.next/server/pages/500.html +1 -1
  147. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  148. package/.next/standalone/.next/server/server-reference-manifest.json +54 -12
  149. package/.next/standalone/.next/static/chunks/{2c90h1g6x8z8y.js → 0_2n13i94rnw5.js} +1 -1
  150. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  151. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  152. package/.next/standalone/.next/static/chunks/23557z4e6dtsr.js +1 -0
  153. package/.next/standalone/.next/static/chunks/28zkwzu50puki.js +1 -0
  154. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  155. package/.next/standalone/.next/static/chunks/3081gykdubv8b.js +1 -0
  156. package/.next/standalone/.next/static/chunks/{06jakt_xhoolk.js → 34fa65k0cldkj.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/35v07j9gc1pvr.js +6 -0
  158. package/.next/standalone/.next/static/chunks/{2zb3m1tzq6h98.js → 3f_939wi3kfnx.js} +22 -22
  159. package/.next/standalone/.next/static/chunks/{31cqiv_cpgj9m.js → 3p1vpm2_-tfwm.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{0ue9ak_xexiqh.js → 3rbpw_waieafm.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/40ffnfpkeyvb0.js +1 -0
  162. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  163. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  164. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  165. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  166. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  167. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  168. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  169. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  170. package/.next/standalone/app/audit/audit-styles.css +6 -149
  171. package/.next/standalone/app/settings/page.tsx +31 -0
  172. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  173. package/.next/standalone/components/navbar.tsx +2 -0
  174. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  175. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  176. package/.next/standalone/lib/codex-sessions.ts +2 -1
  177. package/.next/standalone/lib/dashboard-host.ts +101 -0
  178. package/.next/standalone/lib/install-check.ts +22 -7
  179. package/.next/standalone/lib/pi-sessions.ts +99 -14
  180. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  181. package/.next/standalone/lib/telemetry-id.ts +52 -1
  182. package/.next/standalone/lib/telemetry.ts +8 -4
  183. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  184. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  185. package/.next/standalone/node_modules/react/package.json +3 -3
  186. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  187. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  188. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  189. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  190. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  191. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  192. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  193. package/.next/standalone/package.json +7 -6
  194. package/.next/standalone/proxy.ts +89 -1
  195. package/.next/standalone/server.js +1 -1
  196. package/bin/failproofai.mjs +197 -80
  197. package/dist/cli.mjs +17699 -15345
  198. package/dist/worker.mjs +1620 -257
  199. package/lib/auth/api-server-client.ts +2 -3
  200. package/lib/auth/auth-store.ts +4 -4
  201. package/lib/codex-sessions.ts +2 -1
  202. package/lib/dashboard-host.ts +101 -0
  203. package/lib/install-check.ts +22 -7
  204. package/lib/pi-sessions.ts +99 -14
  205. package/lib/telemetry-enabled.ts +54 -0
  206. package/lib/telemetry-id.ts +52 -1
  207. package/lib/telemetry.ts +8 -4
  208. package/package.json +7 -6
  209. package/scripts/install-telemetry.mjs +8 -1
  210. package/scripts/launch.ts +25 -3
  211. package/scripts/parse-script-args.ts +17 -1
  212. package/scripts/prune-standalone.mjs +20 -0
  213. package/scripts/translate-docs/readme-translator.ts +163 -4
  214. package/scripts/validate-mdx.ts +153 -13
  215. package/src/audit/audit-lock.ts +285 -0
  216. package/src/audit/audit-schedule.ts +111 -0
  217. package/src/audit/cache.ts +18 -4
  218. package/src/audit/cli-adapters/goose.ts +2 -1
  219. package/src/audit/cli-adapters/hermes.ts +28 -10
  220. package/src/audit/cli.ts +191 -35
  221. package/src/audit/dashboard-cache.ts +2 -1
  222. package/src/hooks/builtin-policies.ts +100 -3
  223. package/src/hooks/cloud-connection.ts +163 -14
  224. package/src/hooks/cloud-enrollment-cli.ts +119 -21
  225. package/src/hooks/cloud-enrollment.ts +87 -17
  226. package/src/hooks/cloud-introspect.ts +147 -0
  227. package/src/hooks/cloud-managed-policies.ts +33 -2
  228. package/src/hooks/collector-config.ts +32 -40
  229. package/src/hooks/configure-wizard.ts +696 -318
  230. package/src/hooks/custom-hooks-loader.ts +125 -11
  231. package/src/hooks/daemon-client.ts +83 -17
  232. package/src/hooks/daemon-download.ts +54 -2
  233. package/src/hooks/daemon-service.ts +595 -27
  234. package/src/hooks/first-run-gate.ts +39 -0
  235. package/src/hooks/fp-config.ts +497 -0
  236. package/src/hooks/fp-home.ts +342 -0
  237. package/src/hooks/fp-reset.ts +332 -0
  238. package/src/hooks/handler.ts +42 -1
  239. package/src/hooks/hook-activity-store.ts +35 -20
  240. package/src/hooks/hook-logger.ts +2 -1
  241. package/src/hooks/hook-telemetry.ts +5 -1
  242. package/src/hooks/hooks-config.ts +4 -3
  243. package/src/hooks/loader-utils.ts +166 -5
  244. package/src/hooks/manager.ts +5 -4
  245. package/src/hooks/onboarding-lock.ts +119 -0
  246. package/src/hooks/policy-types.ts +0 -7
  247. package/src/hooks/session-pause-cli.ts +15 -13
  248. package/src/hooks/session-pause.ts +36 -4
  249. package/src/hooks/setup-state.ts +256 -0
  250. package/src/hooks/tui.ts +4 -4
  251. package/src/hooks/worker-server.ts +53 -1
  252. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  260. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cmdyn_._.js +0 -3
  261. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  262. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  263. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +0 -4
  264. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.js +0 -3
  265. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  266. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0sdrz-j._.js +0 -3
  267. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  268. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  269. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
  270. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  271. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  272. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1q9xxca.js +0 -3
  273. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  274. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  275. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  276. package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +0 -6
  277. package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +0 -1
  278. package/.next/standalone/.next/static/chunks/3h5_3u46gu9jx.js +0 -1
  279. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  280. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
  281. package/.next/standalone/.next/static/chunks/43a1ywqsg9bq9.js +0 -1
  282. package/.next/standalone/Cargo.lock +0 -2222
  283. package/.next/standalone/Cargo.toml +0 -9
  284. package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
  285. package/.next/standalone/crates/PROTOCOL.md +0 -122
  286. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
  287. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
  288. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
  289. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  290. package/.next/standalone/crates/failproofaid/src/main.rs +0 -532
  291. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
  292. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  293. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  294. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  295. package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
  296. package/.next/standalone/crates/fpai-collect/src/config.rs +0 -412
  297. package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
  298. package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
  299. package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -484
  300. package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
  301. package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
  302. package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
  303. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
  304. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
  305. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
  306. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
  307. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
  308. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
  309. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
  310. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
  311. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
  312. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
  313. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -310
  314. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
  315. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
  316. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
  317. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
  318. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
  319. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
  320. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
  321. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
  322. package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -458
  323. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -387
  324. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
  325. package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
  326. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -738
  327. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -393
  328. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -975
  329. package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
  330. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1019
  331. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
  332. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1008
  333. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1129
  334. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -621
  335. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -854
  336. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
  337. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1191
  338. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
  339. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
  340. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  341. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  342. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  343. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  344. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  345. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  346. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  347. package/.next/standalone/integration-suite/Dockerfile +0 -47
  348. package/.next/standalone/integration-suite/README.md +0 -129
  349. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  350. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  351. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  352. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  353. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  354. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  355. package/.next/standalone/integration-suite/report.js +0 -120
  356. package/.next/standalone/integration-suite/run.sh +0 -159
  357. package/.next/standalone/openclaw-plugin/index.js +0 -223
  358. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  359. package/.next/standalone/openclaw-plugin/package.json +0 -12
  360. package/.next/standalone/osv-scanner.toml +0 -17
  361. package/.next/standalone/pi-extension/index.ts +0 -524
  362. package/.next/standalone/pi-extension/package.json +0 -12
  363. package/.next/standalone/postcss.config.mjs +0 -8
  364. package/.next/standalone/readme-arch-hq.gif +0 -0
  365. package/.next/standalone/rust-toolchain.toml +0 -4
  366. package/src/auth/cli.ts +0 -371
  367. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_buildManifest.js +0 -0
  368. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_clientMiddlewareManifest.js +0 -0
  369. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_ssgManifest.js +0 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * One audit at a time on this machine, across processes.
3
+ *
4
+ * Three entry points can start a scan — a scheduled run, an explicit
5
+ * `failproofai audit`, and the dashboard's `/api/audit/run` — and every one of
6
+ * them writes the same sha1-keyed per-transcript cache files and the same
7
+ * single-slot `audit/dashboard.json`. The only lock that existed before this
8
+ * was `app/api/audit/_state.ts`, a module-level singleton inside the Next.js
9
+ * server: invisible to the other two, and by its own admission invisible to a
10
+ * second Next.js worker as well.
11
+ *
12
+ * ## Why a lockfile and not a flock
13
+ *
14
+ * `flock()` releases itself when the holder dies, which is exactly the property
15
+ * we would want — but it needs a held file descriptor, and the interactive path
16
+ * hands off to `launch()` and lives on for as long as the user leaves the
17
+ * dashboard open. A lockfile whose contents name the holder can be released at
18
+ * the end of the SCAN, which is the thing being serialised, and it can be
19
+ * inspected by a human wondering why their audit refused to start. The cost is
20
+ * that a crash leaves the file behind, which the stale rules below cover.
21
+ *
22
+ * ## The two stale rules
23
+ *
24
+ * **A dead pid** is the primary one and the one that fires in practice:
25
+ * Ctrl+C, SIGKILL and a laptop lid are all "the lock is on disk, the process is
26
+ * not". **Age** is the backstop for the cases a pid check cannot see — a
27
+ * recycled pid, or a lock written by a machine that shares this home over a
28
+ * network filesystem.
29
+ */
30
+ import {
31
+ closeSync,
32
+ linkSync,
33
+ mkdirSync,
34
+ openSync,
35
+ readFileSync,
36
+ unlinkSync,
37
+ writeFileSync,
38
+ writeSync,
39
+ } from "node:fs";
40
+ import { auditLockFile, failproofaiHome, runDir } from "../hooks/fp-home";
41
+
42
+ /**
43
+ * How old a lock has to be before its holder is presumed gone even though the
44
+ * pid still resolves.
45
+ *
46
+ * A full scan measured ~104 seconds over 3,277 transcripts on a development
47
+ * machine with a warm cache. The ceiling has to clear the worst plausible
48
+ * HONEST run by a wide margin, because stealing from a live audit is the one
49
+ * outcome this module exists to prevent — it puts two writers on the same cache
50
+ * files. A cold cache, a much longer history, a spinning disk, or the `nice(19)`
51
+ * scheduled run competing with a busy laptop can each cost an order of
52
+ * magnitude, so: one hour, roughly 35× the measured run. Nothing is lost by
53
+ * being generous here, since the dead-pid rule already collects every crash.
54
+ */
55
+ export const AUDIT_LOCK_MAX_AGE_MS = 60 * 60_000;
56
+
57
+ /**
58
+ * Which entry point holds the lock. Recorded so a refusal can say who it lost
59
+ * to. All four are wired: `cli` and `scheduled` in `src/audit/cli.ts`,
60
+ * `dashboard` in `app/api/audit/run/route.ts`, and `onboarding` in
61
+ * `runPostSetupAudit` — every process that writes the shared cache takes this
62
+ * lock, so no two of them can co-write it.
63
+ */
64
+ export type AuditLockSource = "cli" | "scheduled" | "dashboard" | "onboarding";
65
+
66
+ export interface AuditLockInfo {
67
+ pid: number;
68
+ /** Wall-clock ms. Monotonic time would reset every process start. */
69
+ startedAt: number;
70
+ source: AuditLockSource;
71
+ }
72
+
73
+ export interface AuditLockHandle {
74
+ info: AuditLockInfo;
75
+ /** Idempotent, never throws, and only ever removes a lock this handle owns. */
76
+ release(): void;
77
+ }
78
+
79
+ export type AcquireAuditLockResult =
80
+ | { ok: true; lock: AuditLockHandle }
81
+ /** `heldBy` is null when the holder's lock could not be read at all. */
82
+ | { ok: false; heldBy: AuditLockInfo | null };
83
+
84
+ /**
85
+ * Take the lock, stealing a provably-abandoned one.
86
+ *
87
+ * Never throws: a caller deciding whether to run an audit must not have to
88
+ * handle an exception from the thing that decides.
89
+ */
90
+ export function acquireAuditLock(source: AuditLockSource, now: number = Date.now()): AcquireAuditLockResult {
91
+ const path = auditLockFile();
92
+ const info: AuditLockInfo = { pid: process.pid, startedAt: now, source };
93
+
94
+ try {
95
+ ensureRunDir();
96
+ } catch {
97
+ // We could not even create the directory the lock lives in, so we cannot
98
+ // prove exclusivity. Report contention rather than running: a refused audit
99
+ // is a retry, whereas two unserialised ones corrupt a shared cache.
100
+ return { ok: false, heldBy: null };
101
+ }
102
+
103
+ if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
104
+
105
+ const held = readLock(path);
106
+ if (!isStale(held, now)) return { ok: false, heldBy: held };
107
+
108
+ try {
109
+ unlinkSync(path);
110
+ } catch {
111
+ // Already gone — another process judged it stale at the same moment. The
112
+ // O_EXCL retry below is what decides between us.
113
+ }
114
+ // Exactly one retry, deliberately. Two processes that both called the lock
115
+ // stale race here and O_EXCL picks one; looping would let the loser keep
116
+ // going until it stole the WINNER's brand-new lock.
117
+ if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
118
+ return { ok: false, heldBy: readLock(path) };
119
+ }
120
+
121
+ /** Whoever holds the lock right now, or null if it is free/unreadable. */
122
+ export function readAuditLock(): AuditLockInfo | null {
123
+ return readLock(auditLockFile());
124
+ }
125
+
126
+ /**
127
+ * The holder only when its lock is still ACTIVE — holder alive and not aged
128
+ * out; null when the lock is free, unreadable, or provably abandoned.
129
+ *
130
+ * For status/UI, not for acquiring: a settings page or `/api/audit/status`
131
+ * asking "is a scan running somewhere on this machine right now?" must answer
132
+ * off the same staleness rules `acquireAuditLock` uses, or it would report
133
+ * "running" forever off a crashed run's leftover lockfile — a dead pid or an
134
+ * hour-old file the next real acquire would rightly steal. Read-only: unlike
135
+ * `acquireAuditLock` it never unlinks or steals, so it is safe to call from a
136
+ * request handler that only wants to describe the machine's state.
137
+ */
138
+ export function readActiveAuditLock(now: number = Date.now()): AuditLockInfo | null {
139
+ const held = readLock(auditLockFile());
140
+ return isStale(held, now) ? null : held;
141
+ }
142
+
143
+ function isStale(held: AuditLockInfo | null, now: number): boolean {
144
+ // EEXIST with nothing parseable inside it: either a truncated write or a file
145
+ // from a version that wrote a different shape. Neither names a holder we
146
+ // could ever wait for.
147
+ if (!held) return true;
148
+ if (!isProcessAlive(held.pid)) return true;
149
+ // A startedAt in the future (clock skew, a shared home across machines) reads
150
+ // as fresh, not as stale — the subtraction goes negative and the comparison
151
+ // fails, which is the direction that does not steal from a live run.
152
+ return now - held.startedAt > AUDIT_LOCK_MAX_AGE_MS;
153
+ }
154
+
155
+ function isProcessAlive(pid: number): boolean {
156
+ if (!Number.isInteger(pid) || pid <= 0) return false;
157
+ try {
158
+ process.kill(pid, 0);
159
+ return true;
160
+ } catch (err) {
161
+ // EPERM means the process exists and belongs to somebody else — two OS
162
+ // users on one box, or a shared home. Alive is the safe reading of that.
163
+ return (err as NodeJS.ErrnoException).code === "EPERM";
164
+ }
165
+ }
166
+
167
+ function handleFor(path: string, info: AuditLockInfo): AuditLockHandle {
168
+ let released = false;
169
+ const release = (): void => {
170
+ if (released) return;
171
+ released = true;
172
+ process.removeListener("exit", release);
173
+ // Only ever remove OUR lock. After a steal the file on disk can already
174
+ // belong to a third process, and deleting that one would hand a live audit
175
+ // the co-writer this whole module exists to prevent.
176
+ const current = readLock(path);
177
+ if (current && current.pid !== info.pid) return;
178
+ try {
179
+ unlinkSync(path);
180
+ } catch {
181
+ /* already gone */
182
+ }
183
+ };
184
+
185
+ // `process.exit()` does not unwind, so a `finally` cannot be relied on here:
186
+ // `failproofai audit` exits directly on both its failure and its
187
+ // empty-history path. An exit hook covers those, a normal return, and an
188
+ // uncaught throw. It does NOT run when the default SIGINT disposition kills
189
+ // the process — Ctrl+C during a scan is precisely what the dead-pid rule is
190
+ // there for.
191
+ process.once("exit", release);
192
+ return { info, release };
193
+ }
194
+
195
+ function readLock(path: string): AuditLockInfo | null {
196
+ try {
197
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<AuditLockInfo>;
198
+ if (typeof parsed?.pid !== "number" || typeof parsed?.startedAt !== "number") return null;
199
+ return {
200
+ pid: parsed.pid,
201
+ startedAt: parsed.startedAt,
202
+ source: (parsed.source ?? "cli") as AuditLockSource,
203
+ };
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * True when this call published the lock; false when somebody else already had
211
+ * it (or the write failed for any other reason, which is equally "not ours").
212
+ *
213
+ * Written to a staging file and `link()`ed into place rather than created with
214
+ * `O_EXCL` and then written. Both are atomic about who WINS, but only this one
215
+ * is atomic about what the winner's file CONTAINS: `open(O_EXCL)` publishes the
216
+ * final name while the file is still empty, and an empty lock is an unreadable
217
+ * lock, which `isStale` — correctly — calls abandoned. A competitor landing in
218
+ * that window steals the lock from a holder that is mid-acquire and both
219
+ * processes then scan at once, which is the two-writers-on-one-cache outcome
220
+ * this whole module exists to prevent. `link()` makes the name appear only once
221
+ * the bytes behind it are already there. (It is also the idiom that survives a
222
+ * home shared over NFS, where `O_EXCL` has historically not been atomic at all
223
+ * — one of the cases the age rule above is a backstop for.)
224
+ */
225
+ function writeLockExclusive(path: string, info: AuditLockInfo): boolean {
226
+ const body = JSON.stringify(info);
227
+ // Same directory, so `link()` stays within one filesystem; keyed by pid so
228
+ // two processes staging at once cannot overwrite each other's bytes.
229
+ const staging = `${path}.${process.pid}.tmp`;
230
+ try {
231
+ writeFileSync(staging, body, { mode: 0o600 });
232
+ } catch {
233
+ return false;
234
+ }
235
+ try {
236
+ linkSync(staging, path);
237
+ return true;
238
+ } catch (err) {
239
+ // EEXIST is the answer we asked the kernel for: somebody else holds it.
240
+ // Anything else means link() itself is unavailable — a filesystem with no
241
+ // hard links — and refusing there would mean the audit can never run on
242
+ // that machine at all, so fall back to the create-then-write form.
243
+ if ((err as NodeJS.ErrnoException).code === "EEXIST") return false;
244
+ return createThenWrite(path, body);
245
+ } finally {
246
+ try {
247
+ unlinkSync(staging);
248
+ } catch {
249
+ /* the link (or the failure) is what mattered; a leftover staging file is not */
250
+ }
251
+ }
252
+ }
253
+
254
+ /** The `O_EXCL` form, kept only as the no-hard-links fallback above. */
255
+ function createThenWrite(path: string, body: string): boolean {
256
+ let fd: number | undefined;
257
+ try {
258
+ fd = openSync(path, "wx", 0o600);
259
+ writeSync(fd, body);
260
+ return true;
261
+ } catch {
262
+ return false;
263
+ } finally {
264
+ if (fd !== undefined) {
265
+ try {
266
+ closeSync(fd);
267
+ } catch {
268
+ /* nothing useful to do with a failed close on a lockfile */
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ function ensureRunDir(): void {
275
+ // The home first and at the default mode: `run/` below is created 0700, and
276
+ // letting a recursive mkdir apply that to `~/.failproofai` itself would hide
277
+ // config.toml, which is world-readable by design.
278
+ mkdirSync(failproofaiHome(), { recursive: true });
279
+ // 0700 explicitly, because failproofaid REFUSES TO START when run/ exists
280
+ // with any other mode (`ensure_run_dir` in crates/failproofaid/src/paths.rs).
281
+ // A lock file that created this directory under the ambient umask would
282
+ // therefore stop the daemon coming up — and on a daemon-configured machine a
283
+ // daemon that cannot start denies every tool call across all 12 CLIs.
284
+ mkdirSync(runDir(), { recursive: true, mode: 0o700 });
285
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The first (and only) TypeScript reader of `~/.failproofai/state/audit-schedule.json`.
3
+ *
4
+ * The **daemon is the sole writer** of that file (`crates/failproofaid/src/
5
+ * audit_lane.rs`); everything on this side only reads it. This module is that
6
+ * read side: `getScheduledAuditAction` calls it to answer "when did the last
7
+ * scheduled scan run, and when is the next one due?" on the /settings page.
8
+ *
9
+ * ## Why this tolerates a file it did not write
10
+ *
11
+ * The daemon and the CLI ship and upgrade independently — a machine can be
12
+ * running a newer `failproofaid` than its `failproofai`, so the schedule file
13
+ * on disk can be a schema *ahead* of this build. A settings page that threw, or
14
+ * blanked its whole scheduled-audit section, because one derived state file was
15
+ * a version in front would be strictly worse than showing slightly less. So we
16
+ * read every field **by name, independent of `schema`**: a future file that
17
+ * still carries `next_due_at_ms` still renders a next-due time. Absent,
18
+ * unreadable, malformed, and non-object all resolve to `null` (the caller shows
19
+ * "no scheduled scan yet"); a recognised-but-unknown-higher schema still yields
20
+ * whatever numeric fields it shares, plus `schemaAhead` so the UI can say "a
21
+ * newer daemon wrote this" rather than silently dropping a field.
22
+ *
23
+ * ## What happened to `next-audit.json` — the one open sub-decision (resolved)
24
+ *
25
+ * `~/.failproofai/next-audit.json` (`lib/auth/auth-store.ts`
26
+ * `readReminder`/`writeReminder`) predates the scheduler and looks like a second
27
+ * source of truth for "the next audit". It is **not**, and after this change
28
+ * there is exactly one answer to "when does the next *scan* run": this file's
29
+ * `next_due_at_ms`. The two are kept **separate with deliberately distinct
30
+ * meanings**, because they answer different questions:
31
+ *
32
+ * - `audit-schedule.json` (here) = when the machine will next **run a scan
33
+ * itself**, headless, via the daemon. This is the scan schedule. It needs
34
+ * no human and no sign-in. `[audit] auto` + `interval_days` in `config.toml`
35
+ * drive it; the daemon derives and writes this file.
36
+ * - `next-audit.json` = an **email nudge to a signed-in human** ("come back
37
+ * and audit again"), set from the /audit "come back better" section,
38
+ * delivered by Failproof Cloud over SES via the api-server. It runs nothing
39
+ * locally; it is re-engagement, not a schedule.
40
+ *
41
+ * Collapsing them would merge "the box scans on a timer" with "email this person
42
+ * a reminder", which are neither the same trigger, the same actor, nor the same
43
+ * effect. So the /settings scheduled-audit section reads and writes **only** the
44
+ * scan schedule (this file + `[audit]`) and never touches `next-audit.json`,
45
+ * leaving the reminder as the /audit page's own concern. That keeps the scan
46
+ * schedule single-writer (the daemon) and single-file, which is the property the
47
+ * two-sources-of-truth worry was really about.
48
+ */
49
+ import { readFileSync } from "node:fs";
50
+ import { auditScheduleFile } from "../hooks/fp-home";
51
+
52
+ /**
53
+ * The schema the daemon currently writes — mirrors `SCHEMA` in
54
+ * `crates/failproofaid/src/audit_lane.rs`. Only used to *flag* a file that
55
+ * declares a higher one (`schemaAhead`); reads never gate on it, on purpose.
56
+ */
57
+ export const AUDIT_SCHEDULE_SCHEMA = 1;
58
+
59
+ export interface AuditScheduleView {
60
+ /** The `schema` the file declared, or null if absent/non-numeric. */
61
+ schema: number | null;
62
+ /** Absolute wall-clock ms of the next scheduled scan, or null. */
63
+ nextDueAtMs: number | null;
64
+ /** When a scan was last STARTED (written before the child spawns), or null. */
65
+ lastAttemptAtMs: number | null;
66
+ /** When a scan last finished successfully (never advanced by an exit-75
67
+ * "lock held"), or null. */
68
+ lastRunAtMs: number | null;
69
+ /** The scheduled child's last exit code, or null. */
70
+ lastExitCode: number | null;
71
+ /** The file declares a schema newer than this build knows. Fields are still
72
+ * read best-effort; this only tells the UI to caveat what it shows. */
73
+ schemaAhead: boolean;
74
+ }
75
+
76
+ /**
77
+ * Read the scheduled-audit state, or null when there is nothing usable to show.
78
+ *
79
+ * Never throws: every failure mode (absent file, EACCES, truncated write,
80
+ * non-JSON, JSON that is not an object) collapses to null, which the caller
81
+ * renders as "no scheduled scan has run yet".
82
+ */
83
+ export function readAuditSchedule(home?: string): AuditScheduleView | null {
84
+ let raw: string;
85
+ try {
86
+ raw = readFileSync(auditScheduleFile(home), "utf8");
87
+ } catch {
88
+ return null;
89
+ }
90
+ let parsed: unknown;
91
+ try {
92
+ parsed = JSON.parse(raw);
93
+ } catch {
94
+ return null;
95
+ }
96
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
97
+
98
+ const obj = parsed as Record<string, unknown>;
99
+ const num = (v: unknown): number | null =>
100
+ typeof v === "number" && Number.isFinite(v) ? v : null;
101
+
102
+ const schema = num(obj.schema);
103
+ return {
104
+ schema,
105
+ nextDueAtMs: num(obj.next_due_at_ms),
106
+ lastAttemptAtMs: num(obj.last_attempt_at_ms),
107
+ lastRunAtMs: num(obj.last_run_at_ms),
108
+ lastExitCode: num(obj.last_exit_code),
109
+ schemaAhead: schema !== null && schema > AUDIT_SCHEDULE_SCHEMA,
110
+ };
111
+ }
@@ -16,6 +16,7 @@ import { homedir } from "node:os";
16
16
  import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
17
17
  import { AUDIT_DETECTORS } from "./detectors";
18
18
  import type { TranscriptAuditResult } from "./types";
19
+ import { auditCacheDir } from "../hooks/fp-home";
19
20
 
20
21
  let cachedEngineVersion: string | null = null;
21
22
  let cachedDetectorVersion: string | null = null;
@@ -44,7 +45,7 @@ function getDetectorVersion(): string {
44
45
  }
45
46
 
46
47
  function getCachePathFor(transcriptPath: string): string {
47
- const root = join(homedir(), ".failproofai", "cache", "audit");
48
+ const root = auditCacheDir();
48
49
  const key = createHash("sha1").update(transcriptPath).digest("hex");
49
50
  return join(root, `${key}.json`);
50
51
  }
@@ -73,8 +74,21 @@ export const CACHE_SCHEMA_VERSION = 3;
73
74
  /** Hard expiry: a cached transcript result is rejected on read once it's
74
75
  * this old. Keeps the audit pipeline honest about long-lived results that
75
76
  * may no longer reflect current detector intent even if the transcript
76
- * bytes and policy hashes haven't changed. */
77
- export const CACHE_TTL_MS = 7 * 24 * 60 * 60_000;
77
+ * bytes and policy hashes haven't changed.
78
+ *
79
+ * 30 days, and it MUST stay clear of the scheduled-audit interval
80
+ * (`DEFAULT_AUDIT_INTERVAL_DAYS`, 7). It was 7 days — exactly equal — which
81
+ * meant a scheduled run at T+7d found every entry cached at T+0 already
82
+ * expired and cold-scanned the entire history: ~104 seconds and megabytes of
83
+ * rewrites, on every single run, for a lane whose whole point is to be cheap.
84
+ * The margin was one scan duration, so any suspend, missed tick, or deferral
85
+ * tipped all of it over at once.
86
+ *
87
+ * Correctness does not rest on this number: `engineVersion` (a hash over every
88
+ * builtin's source) and `detectorVersion` already invalidate the cache the
89
+ * moment detection logic actually changes. The TTL only bounds staleness for
90
+ * changes those two cannot see, so it can be generous. */
91
+ export const CACHE_TTL_MS = 30 * 24 * 60 * 60_000;
78
92
 
79
93
  interface CacheEntry {
80
94
  /** Bumped whenever the on-disk shape changes incompatibly. */
@@ -123,7 +137,7 @@ export function writeCachedTranscriptResult(
123
137
  if (sizeBytes === 0) return;
124
138
  const cachePath = getCachePathFor(transcriptPath);
125
139
  try {
126
- mkdirSync(join(homedir(), ".failproofai", "cache", "audit"), { recursive: true });
140
+ mkdirSync(auditCacheDir(), { recursive: true });
127
141
  const entry: CacheEntry = {
128
142
  schemaVersion: CACHE_SCHEMA_VERSION,
129
143
  cachedAt: Date.now(),
@@ -7,7 +7,8 @@
7
7
  * lib/goose-sessions.ts parses each session's `messages`), producing the same
8
8
  * LogEntry[] shape the other adapters do — so `logEntriesToEvents` handles the
9
9
  * rest. Like Devin, each Goose session carries a real `working_dir`, so
10
- * `audit --project <cwd>` filters work (unlike the cwd-less Hermes gateway).
10
+ * `audit --project <cwd>` filters work. (Hermes does too, for its `source='cli'`
11
+ * sessions — only its Slack/Telegram gateway runs are genuinely cwd-less.)
11
12
  */
12
13
  import { getGooseSessions } from "../../../lib/goose-projects";
13
14
  import { getGooseSessionLog } from "../../../lib/goose-sessions";
@@ -7,35 +7,53 @@
7
7
  * producing the same LogEntry[] shape the other adapters do — so
8
8
  * `logEntriesToEvents` handles the rest.
9
9
  *
10
- * Gateway sessions have no `cwd` (Slack/Telegram runs aren't in a repo), so they
11
- * group by (profile, `source`) instead of working directory — a Hermes profile
12
- * is a whole separate home dir with its own state.db (lib/hermes-profiles.ts).
10
+ * Hermes sessions are NOT uniformly cwd-less. This file used to assert they were
11
+ * and returned nothing at all for `audit --project <cwd>`, so a Hermes session
12
+ * driven in a repo silently contributed zero findings to that project's audit.
13
+ * Verified against hermes-agent 0.19.0: `sessions` carries real `cwd`,
14
+ * `git_branch` and `git_repo_root` columns, and every `source='cli'` session
15
+ * populated them.
16
+ *
17
+ * Both shapes are real, so both are handled:
18
+ * • a session WITH a cwd groups by working directory like Claude/Goose/Devin,
19
+ * and takes part in cwd-scoped audits;
20
+ * • a session WITHOUT one — a Slack/Telegram gateway run, which genuinely
21
+ * isn't in a repo — keeps grouping by (profile, `source`), since a Hermes
22
+ * profile is a whole separate home dir with its own state.db
23
+ * (lib/hermes-profiles.ts), and is correctly excluded from a cwd filter.
13
24
  */
14
25
  import { getHermesSessions } from "../../../lib/hermes-projects";
15
26
  import { hermesProjectPath } from "../../../lib/hermes-profiles";
16
27
  import { getHermesSessionLog } from "../../../lib/hermes-sessions";
28
+ import { encodeFolderName } from "../../../lib/paths";
17
29
  import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
18
30
  import type { ListOpts } from "./claude";
19
31
  import { logEntriesToEvents } from "./shared";
20
32
 
33
+ /** Grouping label for one session: its working directory when it has one,
34
+ * else the (profile, source) bucket gateway sessions have always used. */
35
+ function hermesProjectName(s: { profile: string; source?: string; cwd?: string }): string {
36
+ if (s.cwd) return encodeFolderName(s.cwd);
37
+ return s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`;
38
+ }
39
+
21
40
  export async function listHermesTranscriptMetadata(
22
41
  opts: ListOpts = {},
23
42
  ): Promise<TranscriptMetadata[]> {
24
- // `audit --project <cwd>` filters on working directory; gateway sessions have
25
- // none, so Hermes contributes nothing to a cwd-scoped audit.
26
- if (opts.projects && opts.projects.length > 0) return [];
27
-
43
+ const projectFilter = opts.projects ? new Set(opts.projects) : null;
28
44
  const sinceMs = opts.sinceMs ?? 0;
29
45
  const sessions = await getHermesSessions();
30
46
  const out: TranscriptMetadata[] = [];
31
47
  for (const s of sessions) {
32
48
  if (s.mtimeMs < sinceMs) continue;
33
49
  if (s.messageCount <= 0 && !s.hasMessages) continue; // empty → no events (message_count can lag; trust real messages)
50
+ // `audit --project <cwd>` filters on the session's working directory, the
51
+ // same comparison Claude and Goose make. A session with no cwd is not in
52
+ // any project, so it drops out here rather than at the top of the function.
53
+ if (projectFilter && (!s.cwd || !projectFilter.has(s.cwd))) continue;
34
54
  out.push({
35
55
  cli: "hermes",
36
- // Group by (profile, channel); gateway sessions are cwd-less, and each
37
- // profile is a separate Hermes home with its own state.db.
38
- projectName: s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`,
56
+ projectName: hermesProjectName(s),
39
57
  sessionId: s.sessionId,
40
58
  transcriptPath: `hermes://${s.sessionId}`,
41
59
  mtimeMs: s.mtimeMs,