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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  125. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
  134. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  139. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
  153. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  154. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  157. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  160. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  161. package/.next/standalone/.next/server/middleware.js +2 -2
  162. package/.next/standalone/.next/server/pages/404.html +1 -1
  163. package/.next/standalone/.next/server/pages/500.html +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
  168. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
  170. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  171. package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
  172. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  173. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
  174. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
  175. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
  176. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  177. package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
  178. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
  179. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
  180. package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
  181. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  182. package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  184. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  185. package/.next/standalone/SECURITY.md +8 -2
  186. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  187. package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
  188. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  189. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  190. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  191. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  192. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  193. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  194. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  195. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  196. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  197. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  198. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  199. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  200. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  201. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  202. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  203. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  204. package/.next/standalone/app/audit/audit-styles.css +6 -149
  205. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  206. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  207. package/.next/standalone/app/components/toast.tsx +1 -1
  208. package/.next/standalone/app/policies/hooks-client.tsx +84 -8
  209. package/.next/standalone/app/settings/page.tsx +31 -0
  210. package/.next/standalone/app/settings/settings-client.tsx +487 -0
  211. package/.next/standalone/components/navbar.tsx +2 -0
  212. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  213. package/.next/standalone/lib/auth/auth-store.ts +4 -5
  214. package/.next/standalone/lib/codex-sessions.ts +2 -1
  215. package/.next/standalone/lib/dashboard-host.ts +101 -0
  216. package/.next/standalone/lib/install-check.ts +22 -7
  217. package/.next/standalone/lib/pi-sessions.ts +99 -14
  218. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  219. package/.next/standalone/lib/telemetry-id.ts +52 -1
  220. package/.next/standalone/lib/telemetry.ts +8 -4
  221. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  222. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  223. package/.next/standalone/node_modules/react/package.json +3 -3
  224. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  225. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  226. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  227. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  228. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  229. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  230. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  231. package/.next/standalone/package.json +23 -12
  232. package/.next/standalone/proxy.ts +111 -1
  233. package/.next/standalone/server.js +1 -1
  234. package/bin/failproofai-worker.mjs +1 -1
  235. package/bin/failproofai.mjs +1041 -81
  236. package/bin/failproofaid-shim.mjs +4 -4
  237. package/dist/cli.mjs +18513 -10474
  238. package/dist/worker.mjs +1223 -299
  239. package/lib/auth/api-server-client.ts +2 -3
  240. package/lib/auth/auth-store.ts +4 -5
  241. package/lib/codex-sessions.ts +2 -1
  242. package/lib/dashboard-host.ts +101 -0
  243. package/lib/install-check.ts +22 -7
  244. package/lib/pi-sessions.ts +99 -14
  245. package/lib/telemetry-enabled.ts +54 -0
  246. package/lib/telemetry-id.ts +52 -1
  247. package/lib/telemetry.ts +8 -4
  248. package/package.json +23 -12
  249. package/scripts/build-daemon-packages.mjs +247 -0
  250. package/scripts/daemon-platforms.mjs +42 -0
  251. package/scripts/dev-hook.mjs +46 -5
  252. package/scripts/install-telemetry.mjs +8 -1
  253. package/scripts/launch.ts +25 -3
  254. package/scripts/parse-script-args.ts +27 -1
  255. package/scripts/prune-standalone.mjs +26 -0
  256. package/scripts/publish-aliases.mjs +62 -41
  257. package/scripts/repro-npm-install.sh +155 -0
  258. package/scripts/translate-docs/cache.ts +1 -1
  259. package/scripts/translate-docs/cli.ts +2 -2
  260. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  261. package/scripts/translate-docs/readme-translator.ts +163 -4
  262. package/scripts/validate-mdx.ts +153 -13
  263. package/src/audit/audit-lock.ts +285 -0
  264. package/src/audit/audit-schedule.ts +111 -0
  265. package/src/audit/cache.ts +18 -5
  266. package/src/audit/cli-adapters/goose.ts +2 -1
  267. package/src/audit/cli-adapters/hermes.ts +28 -10
  268. package/src/audit/cli.ts +191 -35
  269. package/src/audit/dashboard-cache.ts +2 -3
  270. package/src/audit/report.ts +0 -1
  271. package/src/hooks/backfill-cli.ts +247 -0
  272. package/src/hooks/builtin-policies.ts +391 -3
  273. package/src/hooks/cloud-connection.ts +354 -0
  274. package/src/hooks/cloud-enrollment-cli.ts +439 -0
  275. package/src/hooks/cloud-enrollment.ts +284 -0
  276. package/src/hooks/cloud-introspect.ts +147 -0
  277. package/src/hooks/cloud-managed-policies.ts +206 -0
  278. package/src/hooks/collector-config.ts +218 -0
  279. package/src/hooks/configure-wizard.ts +1211 -233
  280. package/src/hooks/custom-hooks-loader.ts +243 -37
  281. package/src/hooks/daemon-client.ts +136 -33
  282. package/src/hooks/daemon-download.ts +264 -31
  283. package/src/hooks/daemon-service.ts +1124 -76
  284. package/src/hooks/first-run-gate.ts +50 -0
  285. package/src/hooks/flush-cli.ts +180 -0
  286. package/src/hooks/fp-config.ts +835 -0
  287. package/src/hooks/fp-home.ts +714 -0
  288. package/src/hooks/fp-reset.ts +1363 -0
  289. package/src/hooks/handler.ts +185 -6
  290. package/src/hooks/harness-cli.ts +386 -0
  291. package/src/hooks/hook-activity-store.ts +171 -24
  292. package/src/hooks/hook-logger.ts +2 -2
  293. package/src/hooks/hook-telemetry.ts +5 -1
  294. package/src/hooks/hooks-config.ts +4 -3
  295. package/src/hooks/integrations.ts +0 -1
  296. package/src/hooks/loader-utils.ts +172 -9
  297. package/src/hooks/manager.ts +41 -5
  298. package/src/hooks/migrations.ts +450 -0
  299. package/src/hooks/onboarding-attempt.ts +195 -0
  300. package/src/hooks/onboarding-lock.ts +118 -0
  301. package/src/hooks/policy-types.ts +28 -0
  302. package/src/hooks/session-pause-cli.ts +159 -0
  303. package/src/hooks/session-pause.ts +267 -0
  304. package/src/hooks/setup-state.ts +256 -0
  305. package/src/hooks/tui.ts +209 -9
  306. package/src/hooks/uninstall-cli.ts +418 -0
  307. package/src/hooks/worker-server.ts +134 -15
  308. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  319. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  321. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  322. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  330. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  333. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  334. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  335. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  336. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  337. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  338. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  340. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  341. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  342. package/.next/standalone/Cargo.lock +0 -428
  343. package/.next/standalone/Cargo.toml +0 -9
  344. package/.next/standalone/crates/PROTOCOL.md +0 -122
  345. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  346. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  347. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  348. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  349. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  350. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  351. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  352. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  353. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  354. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  355. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  356. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  357. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  358. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  359. package/.next/standalone/integration-suite/Dockerfile +0 -47
  360. package/.next/standalone/integration-suite/README.md +0 -129
  361. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  362. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  363. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  364. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  365. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  366. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  367. package/.next/standalone/integration-suite/report.js +0 -120
  368. package/.next/standalone/integration-suite/run.sh +0 -159
  369. package/.next/standalone/openclaw-plugin/index.js +0 -223
  370. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  371. package/.next/standalone/openclaw-plugin/package.json +0 -12
  372. package/.next/standalone/osv-scanner.toml +0 -17
  373. package/.next/standalone/pi-extension/index.ts +0 -524
  374. package/.next/standalone/pi-extension/package.json +0 -12
  375. package/.next/standalone/postcss.config.mjs +0 -8
  376. package/.next/standalone/readme-arch-hq.gif +0 -0
  377. package/.next/standalone/rust-toolchain.toml +0 -4
  378. package/src/auth/cli.ts +0 -371
  379. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
  380. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
  381. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Connecting a machine to Failproof Cloud.
3
+ *
4
+ * The credential lives in a file the daemon reads, NOT in the service unit.
5
+ * `daemon-service.ts` installs that unit at mode 0644 into
6
+ * /etc/systemd/system (and the launchd plist likewise), both root-owned and
7
+ * world-readable — so an `Environment="FAILPROOFAI_CLOUD_TOKEN=…"` line would
8
+ * hand an organization-scoped bearer key to every local user, and
9
+ * `systemctl show` would print it back without any privilege at all.
10
+ *
11
+ * Keeping it out of the unit also means enrolment, re-enrolment, token
12
+ * rotation and disconnect need no root — none of them touch a privileged
13
+ * path — and enrolment stops being welded to service installation, so an
14
+ * already-running daemon can be connected without reinstalling anything.
15
+ *
16
+ * Environment variables still win over this file, so CI, containers and the
17
+ * existing tests are unaffected.
18
+ */
19
+ import { randomUUID } from "node:crypto";
20
+ import { existsSync, readFileSync, rmSync } from "node:fs";
21
+ import { hostname } from "node:os";
22
+ import { writeJsonAtomically } from "../../lib/atomic-write";
23
+ import { fetchWithTimeout, isAbortError } from "../../lib/fetch-with-timeout";
24
+ import { credentialsFile } from "./fp-home";
25
+ import { readConfig, readCredentials, writeCredentials } from "./fp-config";
26
+
27
+ const SCHEMA_VERSION = 1;
28
+ const VERIFY_TIMEOUT_MS = 10_000;
29
+
30
+ export interface CloudCredentials {
31
+ url: string;
32
+ machineId: string;
33
+ token: string;
34
+ /**
35
+ * A human-facing name for this machine (e.g. the hostname, or "Chetan's
36
+ * laptop"). Display only — the identity is `machineId`. Optional: absent on
37
+ * credentials written before labels existed, and never required to connect.
38
+ */
39
+ machineLabel?: string;
40
+ }
41
+
42
+ interface StoredCredentials extends CloudCredentials {
43
+ schemaVersion: number;
44
+ }
45
+
46
+ /**
47
+ * Where the cloud credential lives.
48
+ *
49
+ * Layout 2 moved it out of `cloud.json` and into the `[cloud]` table of
50
+ * `credentials.toml`, alongside every other token, so there is exactly one
51
+ * owner-only file to protect rather than three. The env override still names a
52
+ * standalone JSON file, because the daemon and CI both use it that way and it
53
+ * predates the consolidation.
54
+ */
55
+ export function cloudCredentialPath(): string {
56
+ return process.env.FAILPROOFAI_CLOUD_CREDENTIALS ?? credentialsFile();
57
+ }
58
+
59
+ /** True when the credential is coming from the JSON override, not the TOML. */
60
+ function usingJsonOverride(): boolean {
61
+ return Boolean(process.env.FAILPROOFAI_CLOUD_CREDENTIALS);
62
+ }
63
+
64
+ /**
65
+ * Rejects a URL that would put a bearer token on the wire in clear.
66
+ *
67
+ * Loopback stays allowed over http, because that is exactly what the
68
+ * enterprise doc's local walkthrough does (`http://localhost:8080`) and there
69
+ * is no network to intercept.
70
+ */
71
+ export function validateCloudUrl(raw: string): { ok: true; url: string } | { ok: false; reason: string } {
72
+ let parsed: URL;
73
+ try {
74
+ parsed = new URL(raw);
75
+ } catch {
76
+ return { ok: false, reason: `"${raw}" is not a valid URL. Expected something like https://be.failproof.ai` };
77
+ }
78
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
79
+ return { ok: false, reason: `Unsupported scheme "${parsed.protocol}" — use https (or http for localhost).` };
80
+ }
81
+ if (parsed.protocol === "http:") {
82
+ const host = parsed.hostname;
83
+ const loopback = host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
84
+ if (!loopback) {
85
+ return {
86
+ ok: false,
87
+ reason:
88
+ `Refusing to send the machine token to ${parsed.origin} over plain http. ` +
89
+ `Use https, or http only for localhost during development.`,
90
+ };
91
+ }
92
+ }
93
+ return { ok: true, url: parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "")) };
94
+ }
95
+
96
+ /** Last four characters only — enough to tell two keys apart, useless if leaked. */
97
+ export function maskToken(token: string): string {
98
+ if (token.length <= 4) return "****";
99
+ return `****${token.slice(-4)}`;
100
+ }
101
+
102
+ export function readCloudCredentials(): CloudCredentials | null {
103
+ if (!usingJsonOverride()) return readCredentials().cloud ?? null;
104
+ const path = cloudCredentialPath();
105
+ if (!existsSync(path)) return null;
106
+ try {
107
+ const raw = JSON.parse(readFileSync(path, "utf8")) as Partial<StoredCredentials>;
108
+ if (raw.schemaVersion !== SCHEMA_VERSION) return null;
109
+ if (typeof raw.url !== "string" || typeof raw.machineId !== "string" || typeof raw.token !== "string") {
110
+ return null;
111
+ }
112
+ if (!raw.url || !raw.machineId || !raw.token) return null;
113
+ const label = typeof raw.machineLabel === "string" && raw.machineLabel ? raw.machineLabel : undefined;
114
+ return { url: raw.url, machineId: raw.machineId, token: raw.token, machineLabel: label };
115
+ } catch {
116
+ // Unreadable or malformed reads as "not connected" rather than throwing:
117
+ // this is consulted by `--status`, and a corrupt file should be reported
118
+ // as disconnected, not crash the command.
119
+ return null;
120
+ }
121
+ }
122
+
123
+ export function writeCloudCredentials(creds: CloudCredentials): void {
124
+ if (usingJsonOverride()) {
125
+ // 0600 inside a 0700 dir — the default for writeJsonAtomically.
126
+ writeJsonAtomically(cloudCredentialPath(), { schemaVersion: SCHEMA_VERSION, ...creds });
127
+ return;
128
+ }
129
+ // Merge, never replace: credentials.toml also carries the ingest key and the
130
+ // auth session, and rewriting the file from one caller must not drop the
131
+ // others.
132
+ writeCredentials({ ...readCredentials(), cloud: creds });
133
+ }
134
+
135
+ /**
136
+ * The stable key that identifies this machine to the cloud.
137
+ *
138
+ * An explicit `--machine-id` always wins. Otherwise the id already recorded on
139
+ * this machine is reused, so re-running `--connect` is idempotent and never
140
+ * "moves" the machine. Only a machine that has none mints a fresh one — and it
141
+ * mints a random id, NOT the hostname, because two hosts sharing a hostname
142
+ * (fresh cloud VMs, cloned images) would otherwise silently merge into one
143
+ * machine on the server. The hostname becomes the human label instead.
144
+ *
145
+ * ## Why this looks in TWO places before minting
146
+ *
147
+ * `connectToCloud` writes the id to two files under two INDEPENDENT conditions:
148
+ * `credentials.json`'s `cloud` table only when `policies:pull` verified, and
149
+ * `config.json`'s `collector.machine_id` only when `events:add` did. A key
150
+ * carrying one grant and not the other is not a broken setup — it is a
151
+ * first-class state the key-creation drawer offers as two separate presets
152
+ * (`policies` and `collector`), so half-written is a shape this function must
153
+ * expect rather than an anomaly.
154
+ *
155
+ * Consulting only the cloud credential therefore missed the id whenever the
156
+ * FIRST connection was ingest-only, and the consequences all landed on the
157
+ * server, where nothing could undo them:
158
+ *
159
+ * 1. a telemetry-only connect stamps `collector.machine_id = A` on every
160
+ * event and writes no cloud credential;
161
+ * 2. a later connect with a policies-capable key finds no cloud credential,
162
+ * mints B, and overwrites the collector block with it;
163
+ * 3. the fleet list — a union of enrolment rows and event-derived ids — now
164
+ * shows ONE host as TWO machines: A reporting with no label and nothing
165
+ * deployed, B enrolled and empty. A's history is stranded, and A counts
166
+ * toward `unguarded` on the policy page, which is the exact false reading
167
+ * that page exists to surface.
168
+ *
169
+ * Order is most-explicit to least, and the cloud credential stays ABOVE the
170
+ * collector block deliberately: if the two ever disagree, the cloud one is what
171
+ * the server has keyed this machine's enrolment, deployment and history on, and
172
+ * that is the association that must not break. Minting drops to last, where it
173
+ * now means what it says — nothing on this machine has ever carried an id.
174
+ *
175
+ * The collector value is returned VERBATIM rather than trimmed, because the
176
+ * daemon stamps it on events verbatim too (`spool.rs`, which filters only the
177
+ * empty string); normalising it here would hand the cloud a different id than
178
+ * the one the events carry and reintroduce the split this exists to close. A
179
+ * blank or whitespace-only value is not adopted — it identifies nothing, and
180
+ * the server would refuse it.
181
+ */
182
+ export function resolveMachineId(explicit?: string): string {
183
+ const trimmed = explicit?.trim();
184
+ if (trimmed) return trimmed;
185
+ const existing = readCloudCredentials();
186
+ if (existing?.machineId) return existing.machineId;
187
+ // `readConfig` swallows a missing or corrupt file and answers with defaults,
188
+ // so an unreadable config degrades to minting rather than throwing on a path
189
+ // whose whole job is to keep enrolment working.
190
+ const collectorId = readConfig().collector.machineId;
191
+ if (collectorId?.trim()) return collectorId;
192
+ return randomUUID();
193
+ }
194
+
195
+ /**
196
+ * The human-facing name for this machine. Defaults to the hostname as a
197
+ * *suggestion* — mutable, not the identity, and free to collide with another
198
+ * machine's label because `machineId` keeps them apart.
199
+ */
200
+ export function resolveMachineLabel(explicit?: string): string {
201
+ return explicit?.trim() || hostname();
202
+ }
203
+
204
+ /** True if there was something to remove. */
205
+ export function clearCloudCredentials(): boolean {
206
+ if (usingJsonOverride()) {
207
+ const path = cloudCredentialPath();
208
+ if (!existsSync(path)) return false;
209
+ try {
210
+ rmSync(path);
211
+ return true;
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+ const current = readCredentials();
217
+ if (!current.cloud) return false;
218
+ // Drop only the cloud table — disconnecting policy must not also revoke the
219
+ // ingest key or the dashboard session.
220
+ const { cloud: _dropped, ...rest } = current;
221
+ writeCredentials(rest);
222
+ return true;
223
+ }
224
+
225
+ export type VerifyResult =
226
+ | { ok: true; policyCount: number; deployment: number }
227
+ | { ok: false; reason: string };
228
+
229
+ /**
230
+ * Prove the credentials work before reporting success.
231
+ *
232
+ * Writing a credential file and saying nothing means a typo, a revoked key or
233
+ * a key missing `policies:pull` surfaces much later — as a policy that
234
+ * mysteriously never arrives, which is the worst moment to discover it. So
235
+ * enrolment makes exactly the request the daemon will make and reports what
236
+ * came back.
237
+ */
238
+ export async function verifyCloudCredentials(creds: CloudCredentials): Promise<VerifyResult> {
239
+ // The label rides the enrolment request so the server can record a
240
+ // human-facing name alongside the id. An older server simply ignores the
241
+ // extra query param, so sending it is always safe.
242
+ const labelParam = creds.machineLabel
243
+ ? `&label=${encodeURIComponent(creds.machineLabel)}`
244
+ : "";
245
+ const url = `${creds.url}/enforcement/v1/desired-state?machineId=${encodeURIComponent(creds.machineId)}${labelParam}`;
246
+ let response: Response;
247
+ try {
248
+ response = await fetchWithTimeout(
249
+ url,
250
+ { headers: { Authorization: `Bearer ${creds.token}` } },
251
+ VERIFY_TIMEOUT_MS,
252
+ );
253
+ } catch (err) {
254
+ if (isAbortError(err)) return { ok: false, reason: `No response from ${creds.url} within ${VERIFY_TIMEOUT_MS / 1000}s.` };
255
+ return { ok: false, reason: `Could not reach ${creds.url}: ${err instanceof Error ? err.message : String(err)}` };
256
+ }
257
+
258
+ if (response.status === 401) {
259
+ return { ok: false, reason: "The server rejected this token (401). Check the key was copied whole." };
260
+ }
261
+ if (response.status === 403) {
262
+ return {
263
+ ok: false,
264
+ reason: "This key is valid but lacks the `policies:pull` permission (403). Create a machine key with that permission.",
265
+ };
266
+ }
267
+ if (!response.ok) {
268
+ return { ok: false, reason: `The server answered ${response.status} ${response.statusText}.` };
269
+ }
270
+
271
+ try {
272
+ const body = (await response.json()) as { policies?: unknown[]; deployment?: number };
273
+ return {
274
+ ok: true,
275
+ policyCount: Array.isArray(body.policies) ? body.policies.length : 0,
276
+ deployment: typeof body.deployment === "number" ? body.deployment : 0,
277
+ };
278
+ } catch {
279
+ return {
280
+ ok: false,
281
+ reason: `${creds.url} answered, but not with a desired-state document. Is this a Failproof Cloud URL?`,
282
+ };
283
+ }
284
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `GET /v1/auth/introspect` — asking the server what a key actually is.
3
+ *
4
+ * Before this endpoint existed a machine could not describe its own
5
+ * credential: every other `/keys*` route needs a `keys:*` permission that a
6
+ * machine credential must never hold. So a revoked key, a valid key missing
7
+ * one permission, and a valid key pointed at the WRONG ORG all failed
8
+ * identically — at the point of use, later, as an empty dashboard.
9
+ *
10
+ * Introspect answers all three at connect time, in one request:
11
+ *
12
+ * is the key accepted at all? 401 vs 200
13
+ * which org will this machine report into? org_slug / org_name
14
+ * can it do the job it was provisioned for? permissions
15
+ *
16
+ * ## Effective, not granted — the subtle part
17
+ *
18
+ * Permissions imply other permissions, and the server widens a grant at
19
+ * authentication time (`expand_implied`): `alerts:read` also carries
20
+ * `issues:read`, and so on. Authorization is checked against the WIDENED set.
21
+ *
22
+ * The dashboard's key page and the CLI both display the *stored* grant, so both
23
+ * understate what a key can do. Introspect returns what the server actually
24
+ * enforces — which is precisely why a local permission check is only correct
25
+ * when it is built against this response and not against the displayed list.
26
+ *
27
+ * ## Do not poll this
28
+ *
29
+ * `/v1` has no rate limiting and failed auth is not negatively cached: one bad
30
+ * token is one database lookup per request. This runs ONCE, when a machine is
31
+ * provisioned. Revocation and expiry are cached server-side for up to a minute,
32
+ * so treat a positive answer as current to within a minute — never to the
33
+ * second.
34
+ */
35
+
36
+ /** The path introspect is served on, appended to the cloud origin. */
37
+ export const INTROSPECT_PATH = "/v1/auth/introspect";
38
+
39
+ export interface KeyIdentity {
40
+ keyId?: string;
41
+ orgId?: string;
42
+ orgSlug?: string;
43
+ orgName?: string;
44
+ /** The EFFECTIVE permission set — what the server enforces. */
45
+ permissions: string[];
46
+ expiresAt?: string | null;
47
+ }
48
+
49
+ export type IntrospectResult =
50
+ | { kind: "ok"; identity: KeyIdentity }
51
+ /** The key was refused. Nothing more is knowable — the body carries no detail. */
52
+ | { kind: "rejected" }
53
+ /**
54
+ * This server has no introspect endpoint, i.e. it predates the endpoint.
55
+ * The caller must fall back to probing rather than treating it as a bad key:
56
+ * the CLI ships independently of whatever AgentEye a customer runs, so a
57
+ * perfectly good key on an older server must still connect.
58
+ */
59
+ | { kind: "unsupported" }
60
+ /** Network/DNS/TLS — indistinguishable from a typo'd host, so reported as-is. */
61
+ | { kind: "unreachable"; reason: string };
62
+
63
+ export function introspectUrlFor(origin: string): string {
64
+ return `${origin.replace(/\/$/, "")}${INTROSPECT_PATH}`;
65
+ }
66
+
67
+ export async function introspectKey(
68
+ origin: string,
69
+ token: string,
70
+ fetchImpl: typeof fetch = fetch,
71
+ timeoutMs = 10_000,
72
+ ): Promise<IntrospectResult> {
73
+ // AbortController rather than the shared fetchWithTimeout helper: this needs
74
+ // an injectable `fetch` so tests never reach the network, and that helper
75
+ // closes over the global one.
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
78
+ let res: Response;
79
+ try {
80
+ res = await fetchImpl(introspectUrlFor(origin), {
81
+ headers: { Authorization: `Bearer ${token}` },
82
+ // Never follow a redirect. The dashboard app answers unrouted paths and
83
+ // will happily 307 to a login page that then returns 200 — which would
84
+ // read as a valid key against an endpoint that authenticated nothing.
85
+ redirect: "manual",
86
+ signal: controller.signal,
87
+ });
88
+ } catch (err) {
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ // Deliberately does not echo the URL: setup is routinely run while
91
+ // screen-sharing, and the host may be one the user would rather not show.
92
+ return { kind: "unreachable", reason: message };
93
+ } finally {
94
+ clearTimeout(timer);
95
+ }
96
+
97
+ if (res.status === 401 || res.status === 403) return { kind: "rejected" };
98
+ // 404 is "this server has no such route"; a redirect means we reached the web
99
+ // app rather than the API, which on an older deployment means the same thing.
100
+ if (res.status === 404 || (res.status >= 300 && res.status < 400)) {
101
+ return { kind: "unsupported" };
102
+ }
103
+ if (!res.ok) return { kind: "unreachable", reason: `the server answered ${res.status}` };
104
+
105
+ let body: unknown;
106
+ try {
107
+ body = await res.json();
108
+ } catch {
109
+ // A 200 that is not JSON is a proxy or a static host, not introspect.
110
+ return { kind: "unsupported" };
111
+ }
112
+
113
+ const b = body as Record<string, unknown>;
114
+ // `valid: false` is not documented as a 200 shape, but treating a negative
115
+ // assertion as success would be the worst possible reading of it.
116
+ if (b.valid === false) return { kind: "rejected" };
117
+ if (!Array.isArray(b.permissions)) {
118
+ // Shaped like something else entirely — an unrelated 200 from a proxy.
119
+ return { kind: "unsupported" };
120
+ }
121
+
122
+ return {
123
+ kind: "ok",
124
+ identity: {
125
+ keyId: typeof b.key_id === "string" ? b.key_id : undefined,
126
+ orgId: typeof b.org_id === "string" ? b.org_id : undefined,
127
+ orgSlug: typeof b.org_slug === "string" ? b.org_slug : undefined,
128
+ orgName: typeof b.org_name === "string" ? b.org_name : undefined,
129
+ permissions: b.permissions.filter((p): p is string => typeof p === "string"),
130
+ expiresAt: typeof b.expires_at === "string" ? b.expires_at : null,
131
+ },
132
+ };
133
+ }
134
+
135
+ /** The permission each capability needs. Named so the messages can quote them. */
136
+ export const PERMISSION_EVENTS = "events:add";
137
+ export const PERMISSION_POLICIES = "policies:pull";
138
+
139
+ export function hasPermission(identity: KeyIdentity, permission: string): boolean {
140
+ return identity.permissions.includes(permission);
141
+ }
142
+
143
+ /** "Acme Inc (acme)", or the slug/id alone when the server sent less. */
144
+ export function describeOrg(identity: KeyIdentity): string {
145
+ if (identity.orgName && identity.orgSlug) return `${identity.orgName} (${identity.orgSlug})`;
146
+ return identity.orgName ?? identity.orgSlug ?? identity.orgId ?? "an unknown organisation";
147
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Reads the daemon-owned active cloud policy deployment for the TypeScript
3
+ * evaluator. The Rust reconciler is responsible for downloading and repairing
4
+ * artifacts; this boundary independently verifies the digest immediately
5
+ * before import so the worker never knowingly executes modified bytes.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
9
+ import { isAbsolute, relative, resolve } from "node:path";
10
+ import { cloudPoliciesDir } from "./fp-home";
11
+
12
+ /**
13
+ * Active-manifest schema versions this reader accepts.
14
+ *
15
+ * MUST stay in step with `SUPPORTED_SCHEMA_VERSIONS` in `cloud_policies.rs`,
16
+ * which is what WRITES the file this parses. They are two hand-maintained
17
+ * copies of one contract in two languages, and the failure is silent in the
18
+ * worst direction: the daemon reconciles happily, `active.json` is correct on
19
+ * disk, and only the hook path refuses it — so cloud policy stops being
20
+ * enforced while every other signal says the machine is healthy. Reproduced
21
+ * exactly that way while syncing this with AgentEye#559.
22
+ *
23
+ * 1 is accepted for files a pre-rename beta daemon left behind; 2 is what is
24
+ * written now.
25
+ */
26
+ const ACCEPTED_ACTIVE_SCHEMA_VERSIONS: readonly number[] = [1, 2];
27
+ const SHA256_RE = /^[a-f0-9]{64}$/;
28
+ const POLICY_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
29
+
30
+ /** What an assignment may do. See PolicyEffect in cloud_policies.rs. */
31
+ export type PolicyEffect = "enforce" | "observe";
32
+
33
+ export interface CloudManagedPolicyArtifact {
34
+ id: string;
35
+ version: number;
36
+ /**
37
+ * `observe` policies are evaluated exactly like any other and then have their
38
+ * verdict discarded, so a rollout can be measured against real traffic before
39
+ * it can break anyone's work. Absent means `enforce`: the default has to be
40
+ * the one that keeps enforcing, or a manifest written before observe mode
41
+ * would silently downgrade a machine to observation.
42
+ */
43
+ effect: PolicyEffect;
44
+ sha256: string;
45
+ path: string;
46
+ deployment: number;
47
+ }
48
+
49
+ interface ActiveManifest {
50
+ schemaVersion: number;
51
+ deployment: number;
52
+ policies: Array<{
53
+ id: string;
54
+ version: number;
55
+ effect?: string;
56
+ sha256: string;
57
+ path: string;
58
+ }>;
59
+ }
60
+
61
+ export function cloudManagedPolicyRoot(): string {
62
+ return (
63
+ process.env.FAILPROOFAI_CLOUD_POLICY_DIR ??
64
+ cloudPoliciesDir()
65
+ );
66
+ }
67
+
68
+ /**
69
+ * A version-1 manifest, in the spelling a pre-rename daemon actually wrote.
70
+ *
71
+ * Accepting schema 1 while reading only the version-2 FIELD NAMES made the
72
+ * acceptance unreachable: every genuine v1 file threw "active manifest
73
+ * deployment is invalid". The Rust reader of this same file handles it with
74
+ * `#[serde(alias = "generation")]` / `#[serde(alias = "revision")]`; this is the
75
+ * TypeScript half of that pair, and it was missing.
76
+ */
77
+ interface LegacyActiveManifest {
78
+ generation?: number;
79
+ policies?: Array<{ revision?: number }>;
80
+ }
81
+
82
+ function parseManifest(value: unknown): ActiveManifest {
83
+ if (!value || typeof value !== "object") throw new Error("active manifest is not an object");
84
+ const raw = value as Partial<ActiveManifest> & LegacyActiveManifest;
85
+ if (!ACCEPTED_ACTIVE_SCHEMA_VERSIONS.includes(raw.schemaVersion as number)) {
86
+ throw new Error(
87
+ `unsupported active manifest schema ${String(raw.schemaVersion)} ` +
88
+ `(supported: ${ACCEPTED_ACTIVE_SCHEMA_VERSIONS.join(", ")})`,
89
+ );
90
+ }
91
+ // New name wins; the old one is a fallback, not an equal. A file carrying both
92
+ // (written by a mixed-version machine) must resolve to the current field.
93
+ const deployment = raw.deployment ?? raw.generation;
94
+ if (!Number.isSafeInteger(deployment) || (deployment ?? -1) < 0) {
95
+ throw new Error("active manifest deployment is invalid");
96
+ }
97
+ if (!Array.isArray(raw.policies)) throw new Error("active manifest policies is not an array");
98
+ const policies = raw.policies.map((p) => {
99
+ const entry = p as ActiveManifest["policies"][number] & { revision?: number };
100
+ return { ...entry, version: entry.version ?? entry.revision } as ActiveManifest["policies"][number];
101
+ });
102
+ return { schemaVersion: raw.schemaVersion as number, deployment: deployment as number, policies };
103
+ }
104
+
105
+ function resolveManagedPath(root: string, candidate: string): string {
106
+ if (!candidate || isAbsolute(candidate)) throw new Error(`unsafe managed policy path ${JSON.stringify(candidate)}`);
107
+ const absolute = resolve(root, candidate);
108
+ const lexicalRelative = relative(root, absolute);
109
+ if (lexicalRelative.startsWith("..") || isAbsolute(lexicalRelative)) {
110
+ throw new Error(`managed policy path escapes its root: ${JSON.stringify(candidate)}`);
111
+ }
112
+
113
+ // Reject a symlinked artifact or deployment directory escaping the managed
114
+ // root. The current daemon is same-user, but following an arbitrary path is
115
+ // still unnecessary ambient authority and becomes dangerous if its service
116
+ // identity is hardened later.
117
+ const realRoot = realpathSync(root);
118
+ const realFile = realpathSync(absolute);
119
+ const physicalRelative = relative(realRoot, realFile);
120
+ if (physicalRelative.startsWith("..") || isAbsolute(physicalRelative)) {
121
+ throw new Error(`managed policy symlink escapes its root: ${JSON.stringify(candidate)}`);
122
+ }
123
+ return realFile;
124
+ }
125
+
126
+ /**
127
+ * Stop enforcing cloud-managed policies on this machine.
128
+ *
129
+ * Removes `active.json` only — the manifest that says which deployment is
130
+ * live. The deployment directories and content-addressed artifacts are left
131
+ * alone: they are large, hash-verified on use, and inert once nothing points
132
+ * at them, so keeping them makes a reconnect cheap and offline-safe.
133
+ *
134
+ * Called by `--disconnect`, which without it did not disconnect. Clearing the
135
+ * credential stops the daemon REFRESHING policy; every artifact already on disk
136
+ * kept being loaded and enforced on every tool call, so a user who had
137
+ * deliberately left their organisation's cloud went on being governed by
138
+ * whatever deployment happened to be current when they left — indefinitely,
139
+ * with `--status` reporting the machine as unconnected.
140
+ *
141
+ * Returns true when a manifest was actually removed.
142
+ */
143
+ export function clearActiveCloudManagedPolicies(): boolean {
144
+ const activePath = resolve(cloudManagedPolicyRoot(), "active.json");
145
+ if (!existsSync(activePath)) return false;
146
+ try {
147
+ rmSync(activePath, { force: true });
148
+ return true;
149
+ } catch {
150
+ // Best-effort: `--disconnect` still clears the credentials, and reporting
151
+ // a failure to remove one file would obscure that the rest succeeded.
152
+ return false;
153
+ }
154
+ }
155
+
156
+ export function readActiveCloudManagedPolicies(): CloudManagedPolicyArtifact[] {
157
+ const root = cloudManagedPolicyRoot();
158
+ const activePath = resolve(root, "active.json");
159
+ if (!existsSync(activePath)) return [];
160
+
161
+ let raw: unknown;
162
+ try {
163
+ raw = JSON.parse(readFileSync(activePath, "utf8"));
164
+ } catch (err) {
165
+ throw new Error(`failed to read cloud-managed active manifest: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ const manifest = parseManifest(raw);
168
+ const seen = new Set<string>();
169
+
170
+ return manifest.policies.map((policy) => {
171
+ if (!POLICY_ID_RE.test(policy.id) || policy.id === "." || policy.id === "..") {
172
+ throw new Error(`unsafe cloud-managed policy id ${JSON.stringify(policy.id)}`);
173
+ }
174
+ if (!Number.isSafeInteger(policy.version) || policy.version < 0) {
175
+ throw new Error(`invalid version for cloud-managed policy ${policy.id}`);
176
+ }
177
+ if (!SHA256_RE.test(policy.sha256)) {
178
+ throw new Error(`invalid SHA-256 for cloud-managed policy ${policy.id}`);
179
+ }
180
+ if (seen.has(policy.id)) throw new Error(`duplicate cloud-managed policy id ${policy.id}`);
181
+ seen.add(policy.id);
182
+
183
+ const path = resolveManagedPath(root, policy.path);
184
+ const bytes = readFileSync(path);
185
+ const actual = createHash("sha256").update(bytes).digest("hex");
186
+ if (actual !== policy.sha256) {
187
+ throw new Error(
188
+ `cloud-managed policy ${policy.id} failed integrity verification: expected ${policy.sha256}, got ${actual}`,
189
+ );
190
+ }
191
+ if (policy.effect !== undefined && policy.effect !== "enforce" && policy.effect !== "observe") {
192
+ // Guessing would mean either enforcing something cloud did not ask to
193
+ // enforce, or observing something it wanted enforced. Both are worse than
194
+ // refusing the deployment.
195
+ throw new Error(`unknown effect ${JSON.stringify(policy.effect)} for cloud-managed policy ${policy.id}`);
196
+ }
197
+ return {
198
+ id: policy.id,
199
+ version: policy.version,
200
+ effect: (policy.effect as PolicyEffect | undefined) ?? "enforce",
201
+ sha256: policy.sha256,
202
+ path,
203
+ deployment: manifest.deployment,
204
+ };
205
+ });
206
+ }