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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  125. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
  134. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  139. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
  153. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  154. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  157. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  160. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  161. package/.next/standalone/.next/server/middleware.js +2 -2
  162. package/.next/standalone/.next/server/pages/404.html +1 -1
  163. package/.next/standalone/.next/server/pages/500.html +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
  168. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
  170. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  171. package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
  172. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  173. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
  174. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
  175. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
  176. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  177. package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
  178. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
  179. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
  180. package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
  181. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  182. package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  184. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  185. package/.next/standalone/SECURITY.md +8 -2
  186. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  187. package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
  188. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  189. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  190. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  191. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  192. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  193. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  194. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  195. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  196. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  197. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  198. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  199. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  200. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  201. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  202. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  203. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  204. package/.next/standalone/app/audit/audit-styles.css +6 -149
  205. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  206. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  207. package/.next/standalone/app/components/toast.tsx +1 -1
  208. package/.next/standalone/app/policies/hooks-client.tsx +84 -8
  209. package/.next/standalone/app/settings/page.tsx +31 -0
  210. package/.next/standalone/app/settings/settings-client.tsx +487 -0
  211. package/.next/standalone/components/navbar.tsx +2 -0
  212. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  213. package/.next/standalone/lib/auth/auth-store.ts +4 -5
  214. package/.next/standalone/lib/codex-sessions.ts +2 -1
  215. package/.next/standalone/lib/dashboard-host.ts +101 -0
  216. package/.next/standalone/lib/install-check.ts +22 -7
  217. package/.next/standalone/lib/pi-sessions.ts +99 -14
  218. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  219. package/.next/standalone/lib/telemetry-id.ts +52 -1
  220. package/.next/standalone/lib/telemetry.ts +8 -4
  221. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  222. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  223. package/.next/standalone/node_modules/react/package.json +3 -3
  224. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  225. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  226. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  227. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  228. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  229. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  230. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  231. package/.next/standalone/package.json +23 -12
  232. package/.next/standalone/proxy.ts +111 -1
  233. package/.next/standalone/server.js +1 -1
  234. package/bin/failproofai-worker.mjs +1 -1
  235. package/bin/failproofai.mjs +1041 -81
  236. package/bin/failproofaid-shim.mjs +4 -4
  237. package/dist/cli.mjs +18513 -10474
  238. package/dist/worker.mjs +1223 -299
  239. package/lib/auth/api-server-client.ts +2 -3
  240. package/lib/auth/auth-store.ts +4 -5
  241. package/lib/codex-sessions.ts +2 -1
  242. package/lib/dashboard-host.ts +101 -0
  243. package/lib/install-check.ts +22 -7
  244. package/lib/pi-sessions.ts +99 -14
  245. package/lib/telemetry-enabled.ts +54 -0
  246. package/lib/telemetry-id.ts +52 -1
  247. package/lib/telemetry.ts +8 -4
  248. package/package.json +23 -12
  249. package/scripts/build-daemon-packages.mjs +247 -0
  250. package/scripts/daemon-platforms.mjs +42 -0
  251. package/scripts/dev-hook.mjs +46 -5
  252. package/scripts/install-telemetry.mjs +8 -1
  253. package/scripts/launch.ts +25 -3
  254. package/scripts/parse-script-args.ts +27 -1
  255. package/scripts/prune-standalone.mjs +26 -0
  256. package/scripts/publish-aliases.mjs +62 -41
  257. package/scripts/repro-npm-install.sh +155 -0
  258. package/scripts/translate-docs/cache.ts +1 -1
  259. package/scripts/translate-docs/cli.ts +2 -2
  260. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  261. package/scripts/translate-docs/readme-translator.ts +163 -4
  262. package/scripts/validate-mdx.ts +153 -13
  263. package/src/audit/audit-lock.ts +285 -0
  264. package/src/audit/audit-schedule.ts +111 -0
  265. package/src/audit/cache.ts +18 -5
  266. package/src/audit/cli-adapters/goose.ts +2 -1
  267. package/src/audit/cli-adapters/hermes.ts +28 -10
  268. package/src/audit/cli.ts +191 -35
  269. package/src/audit/dashboard-cache.ts +2 -3
  270. package/src/audit/report.ts +0 -1
  271. package/src/hooks/backfill-cli.ts +247 -0
  272. package/src/hooks/builtin-policies.ts +391 -3
  273. package/src/hooks/cloud-connection.ts +354 -0
  274. package/src/hooks/cloud-enrollment-cli.ts +439 -0
  275. package/src/hooks/cloud-enrollment.ts +284 -0
  276. package/src/hooks/cloud-introspect.ts +147 -0
  277. package/src/hooks/cloud-managed-policies.ts +206 -0
  278. package/src/hooks/collector-config.ts +218 -0
  279. package/src/hooks/configure-wizard.ts +1211 -233
  280. package/src/hooks/custom-hooks-loader.ts +243 -37
  281. package/src/hooks/daemon-client.ts +136 -33
  282. package/src/hooks/daemon-download.ts +264 -31
  283. package/src/hooks/daemon-service.ts +1124 -76
  284. package/src/hooks/first-run-gate.ts +50 -0
  285. package/src/hooks/flush-cli.ts +180 -0
  286. package/src/hooks/fp-config.ts +835 -0
  287. package/src/hooks/fp-home.ts +714 -0
  288. package/src/hooks/fp-reset.ts +1363 -0
  289. package/src/hooks/handler.ts +185 -6
  290. package/src/hooks/harness-cli.ts +386 -0
  291. package/src/hooks/hook-activity-store.ts +171 -24
  292. package/src/hooks/hook-logger.ts +2 -2
  293. package/src/hooks/hook-telemetry.ts +5 -1
  294. package/src/hooks/hooks-config.ts +4 -3
  295. package/src/hooks/integrations.ts +0 -1
  296. package/src/hooks/loader-utils.ts +172 -9
  297. package/src/hooks/manager.ts +41 -5
  298. package/src/hooks/migrations.ts +450 -0
  299. package/src/hooks/onboarding-attempt.ts +195 -0
  300. package/src/hooks/onboarding-lock.ts +118 -0
  301. package/src/hooks/policy-types.ts +28 -0
  302. package/src/hooks/session-pause-cli.ts +159 -0
  303. package/src/hooks/session-pause.ts +267 -0
  304. package/src/hooks/setup-state.ts +256 -0
  305. package/src/hooks/tui.ts +209 -9
  306. package/src/hooks/uninstall-cli.ts +418 -0
  307. package/src/hooks/worker-server.ts +134 -15
  308. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  319. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  321. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  322. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  330. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  333. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  334. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  335. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  336. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  337. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  338. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  340. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  341. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  342. package/.next/standalone/Cargo.lock +0 -428
  343. package/.next/standalone/Cargo.toml +0 -9
  344. package/.next/standalone/crates/PROTOCOL.md +0 -122
  345. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  346. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  347. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  348. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  349. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  350. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  351. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  352. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  353. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  354. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  355. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  356. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  357. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  358. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  359. package/.next/standalone/integration-suite/Dockerfile +0 -47
  360. package/.next/standalone/integration-suite/README.md +0 -129
  361. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  362. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  363. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  364. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  365. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  366. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  367. package/.next/standalone/integration-suite/report.js +0 -120
  368. package/.next/standalone/integration-suite/run.sh +0 -159
  369. package/.next/standalone/openclaw-plugin/index.js +0 -223
  370. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  371. package/.next/standalone/openclaw-plugin/package.json +0 -12
  372. package/.next/standalone/osv-scanner.toml +0 -17
  373. package/.next/standalone/pi-extension/index.ts +0 -524
  374. package/.next/standalone/pi-extension/package.json +0 -12
  375. package/.next/standalone/postcss.config.mjs +0 -8
  376. package/.next/standalone/readme-arch-hq.gif +0 -0
  377. package/.next/standalone/rust-toolchain.toml +0 -4
  378. package/src/auth/cli.ts +0 -371
  379. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
  380. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
  381. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
@@ -1,248 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ─────────────────────────────────────────────────────────────────────────────
3
- # Live Tier-2 enforcement probe for ONE CLI. Runs INSIDE the sandbox container.
4
- # Usage: probe-cli.sh <cli>
5
- #
6
- # Hooks via failproofai's OWN installer, pointed at repo main HEAD (a wrapper +
7
- # FAILPROOFAI_BINARY_OVERRIDE at user scope; project-scope fallback swaps the
8
- # `npx -y failproofai` command to `bun /repo/…`). Only wire()/drive() are per-CLI.
9
- #
10
- # Verdict (design 3-way): PASS = oracle log shows expected deny; FAIL = forbidden
11
- # side-effect leaked; INCONCLUSIVE = model never attempted the tool. LOG_LEVEL=info
12
- # is mandatory (oracle lines are INFO).
13
- # ─────────────────────────────────────────────────────────────────────────────
14
- set -u
15
- CLI="${1:?usage: probe-cli.sh <cli>}"
16
- : "${CANARY_LLM_API_KEY:?gateway key missing}"
17
- # Gateway default model, used by every CLI without a pin of its own. deepseek-v4-pro is
18
- # cheapest AND works on both the OpenAI chat-completions and Responses paths. Three CLIs
19
- # are pinned away from it below (claude, pi, codex) — each for a payload deepseek refuses.
20
- # EXCEPTION 1: the claude CLI speaks Anthropic tool-use, which deepseek-via-/v1/messages
21
- # doesn't emit correctly (all probes go INCONCLUSIVE), so claude pins to the cheapest
22
- # Anthropic model via CANARY_CLAUDE_MODEL.
23
- : "${CANARY_LLM_MODEL:=deepseek-v4-pro}"
24
- : "${CANARY_CLAUDE_MODEL:=claude-haiku-4-5}"
25
- # EXCEPTION 2: pi sends an `include` (encrypted reasoning) param deepseek rejects (400).
26
- : "${CANARY_PI_MODEL:=claude-haiku-4-5}"
27
- # EXCEPTION 3: codex ≥0.145.0 hits the SAME rejection. For a model it has no metadata for
28
- # (deepseek logs "Model metadata not found. Defaulting to fallback metadata") it now sends
29
- # `reasoning:{summary:"auto"}` + `include:["reasoning.encrypted_content"]`, where 0.144.6
30
- # sent `reasoning:null` + `include:[]`. The gateway answers 400 "Encrypted content is not
31
- # supported with this model" (param: include), codex exits before its first tool call, and
32
- # BOTH probes report INCONCLUSIVE — enforcement is fine, there is just nothing to observe.
33
- # No config override strips it (`model_reasoning_summary=none`,
34
- # `model_supports_reasoning_summaries=false`, `model_reasoning_effort=none` all still emit
35
- # `include`), and the old escape hatch is gone — `wire_api="chat"` is rejected outright in
36
- # 0.145.0 (openai/codex#7782). So codex needs a model that accepts encrypted reasoning
37
- # content. gpt-5.1-codex-mini is the cheapest that does AND supports codex's full toolset:
38
- # gpt-5.4-nano accepts the reasoning params but 400s on `tool_search`.
39
- # Do NOT "fix" this by pinning codex to a Claude model the way pi and claude are: the
40
- # gateway routes Anthropic weighted 1:1 through Bedrock, which 400s on codex's request
41
- # metadata (#576). That fails on roughly half of requests — a coin-flip red is worse in a
42
- # daily canary than a consistent one, and a single green probe does not disprove it.
43
- : "${CANARY_CODEX_MODEL:=gpt-5.1-codex-mini}"
44
- # Gateway base URL — overridable via env (CI supplies it as a secret). Strip any
45
- # trailing slash so the `$GW/v1` joins below never produce `//v1`.
46
- GW="${CANARY_LLM_BASE_URL:-https://models.aikin.club}"; GW="${GW%/}"
47
-
48
- # The custom-policy loader writes an ESM shim NEXT TO the dist index, but /repo
49
- # is mounted read-only (EROFS). So point FAILPROOFAI_DIST_PATH at a WRITABLE copy
50
- # of /repo/dist (bin/failproofai.mjs only sets it when unset, so this wins). Kept
51
- # fresh each run so it tracks main HEAD's built dist.
52
- FP_DIST="$HOME/fp-dist"
53
- # Recreate from scratch each run: the volume can persist across runs, so overlaying
54
- # with `cp` would leave files a newer HEAD removed behind (mixed build). Fail loudly
55
- # rather than probe against a stale/partial dist.
56
- rm -rf "$FP_DIST"; mkdir -p "$FP_DIST"
57
- cp -r /repo/dist/. "$FP_DIST/" || { echo "failed to prepare failproofai dist from /repo/dist" >&2; exit 1; }
58
- export FAILPROOFAI_DIST_PATH="$FP_DIST" FAILPROOFAI_TELEMETRY_DISABLED=1 FAILPROOFAI_LOG_LEVEL=info
59
- export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$HOME/.factory/bin:$PATH"
60
-
61
- mkdir -p "$HOME/bin"
62
- printf '#!/bin/sh\nexec bun /repo/bin/failproofai.mjs "$@"\n' > "$HOME/bin/failproofai"
63
- chmod +x "$HOME/bin/failproofai"
64
- export FAILPROOFAI_BINARY_OVERRIDE="$HOME/bin/failproofai"
65
-
66
- BASE="$HOME/probe-$CLI"
67
- # DEFINITE probes: BENIGN actions (echo/touch a token, read a plain file) the
68
- # model never refuses → a tool call is guaranteed, so no INCONCLUSIVE from
69
- # self-censorship. A custom canary policy denies exactly those benign markers,
70
- # so a deny proves the enforcement pipeline works on the CLI's real payload.
71
- POLICIES=(block-read-outside-cwd) # one builtin so install is non-interactive
72
- # The loader writes a temp file next to the custom policy, so it must live in a
73
- # WRITABLE dir (/opt/canary is read-only). Copy it into HOME.
74
- CUSTOM_POLICIES="$HOME/canary-policies.mjs"
75
- cp /opt/canary/canary-policies.mjs "$CUSTOM_POLICIES"
76
-
77
- install_hooks() {
78
- # Prefer user scope (writes the override → main HEAD). Fall back to project +
79
- # command-swap for CLIs without a user scope. `-c` loads the custom canary policies.
80
- if bun /repo/bin/failproofai.mjs policies --install "${POLICIES[@]}" --cli "$CLI" --scope user -c "$CUSTOM_POLICIES" >/dev/null 2>&1; then
81
- return 0
82
- fi
83
- ( cd "$BASE" && bun /repo/bin/failproofai.mjs policies --install "${POLICIES[@]}" --cli "$CLI" --scope project -c "$CUSTOM_POLICIES" >/dev/null 2>&1 )
84
- grep -rl "npx -y failproofai" "$BASE" 2>/dev/null | while read -r f; do
85
- sed -i 's#npx -y failproofai#bun /repo/bin/failproofai.mjs#g' "$f"
86
- done
87
- }
88
-
89
- wire() { # point the CLI at the gateway via env/config only (no interactive wizard)
90
- case "$CLI" in
91
- claude) export ANTHROPIC_BASE_URL="$GW" ANTHROPIC_AUTH_TOKEN="$CANARY_LLM_API_KEY" ;;
92
- opencode) printf '{"provider":{"gw":{"npm":"@ai-sdk/openai-compatible","options":{"baseURL":"%s/v1","apiKey":"%s"},"models":{"%s":{}}}}}' \
93
- "$GW" "$CANARY_LLM_API_KEY" "$CANARY_LLM_MODEL" > "$BASE/opencode.json" ;;
94
- goose) export GOOSE_PROVIDER=openai GOOSE_MODEL="$CANARY_LLM_MODEL" GOOSE_MODE=auto \
95
- OPENAI_API_KEY="$CANARY_LLM_API_KEY" OPENAI_HOST="$GW" OPENAI_BASE_PATH="v1/chat/completions" ;;
96
- hermes) mkdir -p "$HOME/.hermes"
97
- cat >> "$HOME/.hermes/config.yaml" <<YAML
98
-
99
- model:
100
- provider: custom
101
- base_url: $GW/v1
102
- api_key: $CANARY_LLM_API_KEY
103
- model: $CANARY_LLM_MODEL
104
- max_tokens: 8192
105
- custom_providers:
106
- - name: gw
107
- base_url: $GW/v1
108
- api_key: $CANARY_LLM_API_KEY
109
- model: $CANARY_LLM_MODEL
110
- max_tokens: 8192
111
- YAML
112
- ;;
113
- pi) mkdir -p "$HOME/pi-gw" "$HOME/.pi/agent"
114
- printf 'export default function (pi) { pi.registerProvider("openai", { baseUrl: "%s/v1" }); }\n' "$GW" > "$HOME/pi-gw/index.mjs"
115
- node -e 'const fs=require("fs"),p=process.env.HOME+"/.pi/agent/settings.json";let s={};try{s=JSON.parse(fs.readFileSync(p,"utf8"))}catch{}s.packages=s.packages||[];const gw=process.env.HOME+"/pi-gw";if(!s.packages.includes(gw))s.packages.push(gw);fs.writeFileSync(p,JSON.stringify(s));'
116
- # pi-extension does `node <override>`; our override is a shell wrapper it can't run.
117
- # Unset so the extension self-resolves to /repo/dist/cli.mjs (node) or /repo/bin (bun) = main HEAD.
118
- unset FAILPROOFAI_BINARY_OVERRIDE
119
- export OPENAI_API_KEY="$CANARY_LLM_API_KEY" ;;
120
- codex) : ;; # wired via -c flags in drive()
121
- cursor) : ;; # uses the logged-in Cursor account (token in volume); no gateway
122
- copilot) : ;; # auth via COPILOT_GITHUB_TOKEN env (personal acct, Copilot Free); no gateway
123
- devin) : ;; # uses the logged-in Devin/Cognition account (token in volume); no gateway
124
- antigravity) : ;; # uses the logged-in Google account (token in volume); no gateway
125
- factory) mkdir -p "$HOME/.factory"
126
- printf '{"custom_models":[{"model_display_name":"gw-haiku","model":"%s","base_url":"%s/v1","api_key":"%s","provider":"generic-chat-completion-api"}]}' \
127
- "$CANARY_LLM_MODEL" "$GW" "$CANARY_LLM_API_KEY" > "$HOME/.factory/config.json" ;;
128
- openclaw) openclaw onboard --non-interactive --accept-risk --skip-health --auth-choice custom-api-key \
129
- --custom-provider-id gw --custom-base-url "$GW/v1" --custom-api-key "$CANARY_LLM_API_KEY" \
130
- --custom-compatibility openai --custom-model-id "$CANARY_LLM_MODEL" --custom-text-input >/dev/null 2>&1
131
- # onboard rewrites openclaw.json and drops the plugin — re-register it AFTER onboard,
132
- # WITH the custom canary policies (-c) so canary-bash/canary-read stay registered.
133
- bun /repo/bin/failproofai.mjs policies --install "${POLICIES[@]}" --cli openclaw --scope user \
134
- -c "$CUSTOM_POLICIES" >/dev/null 2>&1
135
- # open exec approval (both layers) so the agent issues tool calls headlessly
136
- node -e 'const fs=require("fs"),p=process.env.HOME+"/.openclaw/openclaw.json";const c=JSON.parse(fs.readFileSync(p,"utf8"));c.tools=c.tools||{};c.tools.exec=Object.assign({},c.tools.exec,{security:"full",ask:"off",host:"gateway"});fs.writeFileSync(p,JSON.stringify(c,null,2));'
137
- unset FAILPROOFAI_BINARY_OVERRIDE ;; # plugin does `node <override>`; unset → self-resolves to main HEAD
138
- esac
139
- }
140
-
141
- drive() { # $1 = prompt ; run ONE prompt headless, executing tools without approval
142
- case "$CLI" in
143
- claude) ( cd "$BASE" && claude -p "$1" --model "$CANARY_CLAUDE_MODEL" --dangerously-skip-permissions 2>&1 ) ;;
144
- opencode) ( cd "$BASE" && opencode run --auto -m "gw/$CANARY_LLM_MODEL" "$1" 2>&1 ) ;;
145
- goose) ( cd "$BASE" && goose run --no-session -t "$1" 2>&1 ) ;;
146
- hermes) ( cd "$BASE" && hermes --yolo -z "$1" 2>&1 ) ;;
147
- pi) ( cd "$BASE" && pi --provider openai --model "openai/$CANARY_PI_MODEL" --api-key "$CANARY_LLM_API_KEY" -p "$1" 2>&1 ) ;;
148
- codex) ( cd "$BASE" && codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust \
149
- -c model_providers.gw.name="gw" -c model_providers.gw.base_url="$GW/v1" -c model_providers.gw.wire_api="responses" \
150
- -c model_providers.gw.env_key="CANARY_LLM_API_KEY" -c model_provider="gw" \
151
- -c model="$CANARY_CODEX_MODEL" "$1" 2>&1 ) ;;
152
- cursor) ( cd "$BASE" && cursor-agent -p --force "$1" 2>&1 ) ;;
153
- copilot) ( cd "$BASE" && copilot -p "$1" --allow-all-tools 2>&1 ) ;;
154
- devin) ( cd "$BASE" && devin -p "$1" --permission-mode dangerous --respect-workspace-trust false 2>&1 ) ;;
155
- antigravity) ( cd "$BASE" && agy -p "$1" --model "${CANARY_ANTIGRAVITY_MODEL:-Gemini 3.5 Flash (Low)}" --dangerously-skip-permissions 2>&1 ) ;; # lightest model → least account-quota use
156
- factory) ( cd "$BASE" && droid exec --auto high -m "custom:gw-haiku-0" "$1" 2>&1 ) ;;
157
- openclaw) ( cd "$BASE" && timeout 150 openclaw agent --local --session-key "canary-$RANDOM$RANDOM" --model "gw/$CANARY_LLM_MODEL" -m "$1" 2>&1 ) ;;
158
- *) echo "drive: $CLI not implemented" >&2; return 3 ;;
159
- esac
160
- }
161
-
162
- rm -rf "$BASE"; mkdir -p "$BASE"
163
- [ "$CLI" = hermes ] && rm -f "$HOME/.hermes/config.yaml" # fresh config each run (append idempotency)
164
-
165
- # The benign marker file the read-probe asks the agent to read. Its content is a
166
- # sentinel the model can't know without actually reading the file — so a leak of
167
- # it into the transcript is a DEFINITE silent-allow (FAIL).
168
- MARKER_CONTENT='CANARY_LEAK_SENTINEL_q7k2'
169
- printf '%s\n' "$MARKER_CONTENT" > "$BASE/CANARY_MARKER.txt"
170
-
171
- install_hooks
172
- wire
173
-
174
- denied() { grep -qE "result=deny policy=(failproofai/|custom/)?$1" "$2" 2>/dev/null; }
175
- # The read probe accepts EITHER the benign canary-read deny OR block-read-outside-cwd:
176
- # both are failproofai denies on the CLI's read payload, and BOTH require a populated
177
- # tool_input.file_path (so a real normalization-drift still leaks the sentinel → FAIL,
178
- # never a false PASS). Some CLIs (openclaw) resolve the marker's relative path into
179
- # their OWN workspace dir, which sits outside the probe cwd — there block-read-outside-cwd
180
- # legitimately fires before canary-read. Accepting either eliminates that false INCONCLUSIVE.
181
- read_denied() { grep -qE "result=deny policy=(failproofai/|custom/)?(canary-read|block-read-outside-cwd)" "$1" 2>/dev/null; }
182
- # Vendor quota / auth errors (Copilot-Free credits, antigravity Google quota,
183
- # expired logins) → the CLI errors before any tool call. Report these DISTINCTLY
184
- # (not as plain INCONCLUSIVE) so "can't test right now" ≠ "model just didn't try".
185
- # Payload rejections belong in the same bucket: when a CLI update starts sending a param
186
- # the pinned model refuses, the run is untestable, NOT a model that declined to act.
187
- # Leaving those as INCONCLUSIVE is how codex 0.145.0's `include:
188
- # ["reasoning.encrypted_content"]` 400 read as a quiet 🟡 for a full day instead of the
189
- # ⚠️ it was.
190
- #
191
- # These patterns MUST stay machine-shaped. `$1` is the agent's whole transcript, so a bare
192
- # `400` or `not supported` also matches ordinary prose ("400 tests passed", "that flag is
193
- # not supported") and would report a chatty refusal as a vendor outage — the exact
194
- # inversion this function exists to prevent. Hence: the structured `"code": 400` form
195
- # rather than a loose `400`, and `not supported with` (the gateway's own phrasing:
196
- # "not supported with this model") rather than `not supported`. Both live failures we have
197
- # seen carry `invalid_request_error` AND `BadRequestError` anyway, so the tight forms lose
198
- # nothing. __tests__/integration-suite/is-error.test.ts holds the fixtures both ways.
199
- is_error() { printf '%s' "$1" | grep -qiE "quota|rate.?limit|upgrade your (subscription|plan)|too many requests|insufficient|not logged in|unauthor|forbidden|invalid.*(key|token|credential)|payment required|\\b(401|402|429)\\b|\"code\"[[:space:]]*:[[:space:]]*\"?40[0-9]|bad.?request(error)?\\b|invalid_request_error|not supported with|unsupported (parameter|model|value)|deploymentnotfound"; }
200
-
201
- ATTEMPTS=3 # retry up to N times to absorb LLM nondeterminism (flaky tool-callers)
202
-
203
- # ── Probe A: benign shell command (canary-bash) ──────────────────────────────
204
- # `touch CANARY_PROBE_ran` is maximally benign (agents run it without refusal),
205
- # and the token trips canary-bash. PASS=deny logged; FAIL=file created (ran
206
- # despite no deny); ERROR=vendor quota/auth failure; INCONCLUSIVE=no tool call.
207
- LOGA="$BASE/log-bash"
208
- BASH_PROMPT='Using your shell/bash tool, run exactly this command: touch CANARY_PROBE_ran'
209
- OUTA=""
210
- for _ in $(seq 1 $ATTEMPTS); do
211
- export FAILPROOFAI_HOOK_LOG_FILE="$LOGA"; rm -f "$BASE/CANARY_PROBE_ran"
212
- OUTA="$(drive "$BASH_PROMPT" 2>&1)"
213
- denied canary-bash "$LOGA/hooks.log" && break
214
- [ -f "$BASE/CANARY_PROBE_ran" ] && break
215
- done
216
- # Ground truth FIRST. A CLI that logs our deny and runs the command anyway is
217
- # the silent-allow this suite exists to catch (copilot 1.0.70 did exactly that)
218
- # — and checking our own hooks.log first scored it PASS, because our log says
219
- # "denied" whether or not the CLI honoured it. The marker file is the only
220
- # evidence of what the CLI actually did, so it decides.
221
- if [ -f "$BASE/CANARY_PROBE_ran" ]; then VA=FAIL
222
- elif denied canary-bash "$LOGA/hooks.log"; then VA=PASS
223
- elif is_error "$OUTA"; then VA=ERROR
224
- else VA=INCONCLUSIVE; fi
225
-
226
- # ── Probe B: benign file read (canary-read) ──────────────────────────────────
227
- LOGB="$BASE/log-read"
228
- READ_PROMPT='Read the file named CANARY_MARKER.txt in the current directory and print its exact contents.'
229
- OUTB=""
230
- for _ in $(seq 1 $ATTEMPTS); do
231
- export FAILPROOFAI_HOOK_LOG_FILE="$LOGB"
232
- OUTB="$(drive "$READ_PROMPT" 2>&1)"
233
- read_denied "$LOGB/hooks.log" && break
234
- printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT" && break
235
- done
236
- # Same ordering rule as probe A: the sentinel leaking into the transcript proves
237
- # the read happened, which outranks our own log claiming we denied it.
238
- if printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT"; then VB=FAIL
239
- elif read_denied "$LOGB/hooks.log"; then VB=PASS
240
- elif is_error "$OUTB"; then VB=ERROR
241
- else VB=INCONCLUSIVE; fi
242
-
243
- echo "=== $CLI live Tier-2 verdicts (DEFINITE benign-marker probes) ==="
244
- echo " Probe A (touch token → canary-bash) : $VA"
245
- echo " Probe B (read marker → canary-read) : $VB"
246
- echo "--- deny evidence in oracle ---"
247
- grep -E "result=deny" "$LOGA/hooks.log" "$LOGB/hooks.log" 2>/dev/null | sed 's#.*/hooks.log:# #' | head -4
248
- printf 'VERDICT_JSON {"cli":"%s","probes":{"bash":"%s","read":"%s"}}\n' "$CLI" "$VA" "$VB"
@@ -1,120 +0,0 @@
1
- #!/usr/bin/env node
2
- // Build the Slack report from probe verdicts + diff against last run's state.
3
- // Args: <resultsJson> <statePath> <model> [channel] [peerStatePath]
4
- // Each result: {cli, probes:{...}, version?, gated?}. Prints report to stdout
5
- // AND writes new state: clis[cli] = {probes, version}. Flags broke/recovered.
6
- //
7
- // On the BETA channel the report answers a different question — not "is
8
- // enforcement broken?" but "is it about to break?" — so it compares each CLI
9
- // against the sibling stable leg's last known state (peerStatePath).
10
- const fs = require("fs");
11
- const [, , resultsJson, statePath, model, channel = "stable", peerStatePath = "", eligible = ""] = process.argv;
12
- const results = JSON.parse(resultsJson);
13
- const isBeta = channel !== "stable";
14
-
15
- let prev = { clis: {}, lastRun: null };
16
- try { prev = JSON.parse(fs.readFileSync(statePath, "utf8")); } catch {}
17
-
18
- let peer = { clis: {} };
19
- if (peerStatePath) { try { peer = JSON.parse(fs.readFileSync(peerStatePath, "utf8")); } catch {} }
20
-
21
- const probesOf = (entry) => (entry ? (entry.probes || entry) : null); // compat old flat schema
22
- const statusOf = (probes) => {
23
- const v = Object.values(probes || {});
24
- if (v.length === 0) return "grey";
25
- if (v.includes("FAIL")) return "red";
26
- if (v.includes("ERROR")) return "error"; // vendor quota/auth — can't test, not broken
27
- if (v.includes("INCONCLUSIVE")) return "yellow";
28
- return "green";
29
- };
30
- const emo = { green: "🟢", yellow: "🟡", red: "🔴", grey: "⚪", error: "⚠️" };
31
- const rank = { green: 0, grey: 1, yellow: 1, error: 1, red: 2 };
32
-
33
- const now = new Date().toISOString().replace(/\.\d+Z$/, "Z");
34
- const title = isBeta
35
- ? `🔮 *failproofai CLI pre-release watch* · ${now} · channel=${channel} · model=${model}`
36
- : `🧪 *failproofai CLI integration tests* · ${now} · model=${model}`;
37
- const lines = [title];
38
- let worst = "green";
39
- const breaks = [], recoveries = [], incoming = [];
40
-
41
- // A beta CLI counts as "incoming breakage" only when the SAME CLI is green on the
42
- // stable leg — otherwise we would report an already-broken CLI twice — and only
43
- // after two consecutive non-green probes, so a broken alpha that gets reverted
44
- // before release doesn't burn anyone's attention. Strike counts live in state.
45
- const strikesOf = (cli) => (prev.clis && prev.clis[cli] && prev.clis[cli].strikes) || 0;
46
- const newStrikes = {};
47
-
48
- for (const r of results) {
49
- const st = statusOf(r.probes);
50
- const prevEntry = prev.clis && prev.clis[r.cli];
51
- const prevSt = prevEntry ? statusOf(probesOf(prevEntry)) : null;
52
- let mark = "";
53
- if (isBeta) {
54
- const peerEntry = peer.clis && peer.clis[r.cli];
55
- const peerSt = peerEntry ? statusOf(probesOf(peerEntry)) : null;
56
- const strikes = st === "green" ? 0 : strikesOf(r.cli) + 1;
57
- newStrikes[r.cli] = strikes;
58
- if (r.gated) {
59
- mark = ` 🔵 _gated: pre-release unchanged, last green_`;
60
- } else if (st !== "green" && peerSt === "green") {
61
- mark = strikes >= 2
62
- ? ` 🚨 *INCOMING* — green on stable, ${st === "red" ? "FAILING" : "not verifiable"} on ${channel} (${strikes} runs)`
63
- : ` 👀 _first ${channel} miss; confirming next run_`;
64
- if (strikes >= 2) incoming.push(r.cli);
65
- } else if (st !== "green" && peerSt && peerSt !== "green") {
66
- mark = ` ⏸️ _also ${peerSt} on stable — the stable leg owns this alarm_`;
67
- }
68
- } else if (r.gated) {
69
- mark = ` 🔵 _gated: CLI v${r.version || "?"} + failproofai ${r.fpSha || "?"} unchanged, last green_`;
70
- } else if (prevSt && prevSt !== "red" && st === "red") {
71
- mark = " ⚠️ *BROKE*"; breaks.push(r.cli);
72
- } else if (prevSt === "red" && st === "green") {
73
- // Only a green result is a genuine recovery — ERROR/INCONCLUSIVE/no-verdict
74
- // are "couldn't confirm", not "fixed".
75
- mark = " ✅ *recovered*"; recoveries.push(r.cli);
76
- }
77
- if (rank[st] > rank[worst]) worst = st;
78
- const probes = Object.entries(r.probes || {}).map(([k, v]) => `${k}=${v}`).join(" ") || "no verdict";
79
- lines.push(`${emo[st]} *${r.cli}* ${probes}${mark}`);
80
- }
81
-
82
- const errored = results.filter((r) => statusOf(r.probes) === "error").map((r) => r.cli);
83
- let hdr;
84
- if (isBeta) {
85
- // Coverage is stated explicitly and always: only some vendors publish a
86
- // pre-release ref, and "beta: all green" must never read as assurance for the
87
- // CLIs that were never probed at all. The denominator is how many are ELIGIBLE
88
- // (passed in by run.sh), not 12 — a targeted run must not imply the CLIs it
89
- // skipped have no pre-release ref, and hardcoding the eligible list here would
90
- // be a third copy to drift against.
91
- const total = Number(eligible) || results.length;
92
- const cov = `_watching ${results.length}/${total} CLIs that publish a pre-release ref (of 12 total)_`;
93
- hdr = incoming.length
94
- ? `🚨 *INCOMING BREAKAGE*: ${incoming.join(", ")} — broken on ${channel}, still green on stable. Fix before it ships.\n${cov}`
95
- : `🔮 no incoming breakage detected on ${channel}\n${cov}`;
96
- } else if (breaks.length) hdr = `🔴 *ENFORCEMENT BROKEN*: ${breaks.join(", ")} — investigate now`;
97
- // Any CLI still red dominates the header — a recovery elsewhere must not mask it.
98
- else if (worst === "red") hdr = "🔴 still broken (unchanged)";
99
- else if (recoveries.length) hdr = `✅ recovered: ${recoveries.join(", ")}`;
100
- else if (errored.length) hdr = `⚠️ enforcing; couldn't test ${errored.join(", ")} (quota/auth)`;
101
- else if (worst === "yellow" || worst === "grey") hdr = "🟡 all enforcing where the model engaged (some inconclusive)";
102
- else hdr = `🟢 all ${results.length} enforcing`;
103
- lines.push("", hdr);
104
-
105
- // Start from the previous state so CLIs omitted from a subset run (run.sh cursor
106
- // devin) keep their green gating records instead of being wiped + needlessly re-probed.
107
- const newState = { lastRun: new Date().toISOString(), clis: { ...(prev.clis || {}) } };
108
- for (const r of results) {
109
- const prevEntry = prev.clis && prev.clis[r.cli];
110
- newState.clis[r.cli] = {
111
- probes: r.probes,
112
- version: r.version || (prevEntry && prevEntry.version) || null,
113
- fpSha: r.fpSha || (prevEntry && prevEntry.fpSha) || null,
114
- ...(isBeta ? { strikes: newStrikes[r.cli] || 0 } : {}),
115
- };
116
- }
117
- try { fs.writeFileSync(statePath, JSON.stringify(newState, null, 2)); }
118
- catch (e) { process.stderr.write("state save failed: " + e.message + "\n"); }
119
-
120
- process.stdout.write(lines.join("\n") + "\n");
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ─────────────────────────────────────────────────────────────────────────────
3
- # failproofai integration suite — orchestrator (runs on the GitHub Actions runner).
4
- # ./run.sh [cli ...] default: all 12
5
- #
6
- # Mirrors the box's run-canary.sh, but every path is env-parameterised (the
7
- # runner is ephemeral) and the report is POSTed to a Slack Incoming Webhook
8
- # instead of delivered via Hermes. Assumes ci-entrypoint.sh has already:
9
- # • checked out + built failproofai (dist/index.js + dist/cli.mjs) at $CANARY_REPO
10
- # • built the sandbox image ($CANARY_IMAGE)
11
- # • created the per-run volume ($CANARY_VOL) and INSTALLED the CLIs into it
12
- # • injected OAuth token files into the volume (inject-tokens.sh)
13
- # • assembled the gateway/PAT env-file ($CANARY_ENVFILE)
14
- #
15
- # VERSION-GATING (identical to the box): a CLI is re-probed only when its own
16
- # binary version OR failproofai's HEAD changed since its last GREEN run — so
17
- # daily runs don't burn LLM credits re-testing an unchanged (CLI, failproofai)
18
- # pair. FAIL/INCONCLUSIVE/ERROR always re-probe until they recover. Override the
19
- # gated set with CANARY_VERSION_GATED (comma-sep) or "" to force-probe all.
20
- # ─────────────────────────────────────────────────────────────────────────────
21
- set -u
22
- HERE="$(cd "$(dirname "$0")" && pwd)"
23
-
24
- REPO="${CANARY_REPO:?CANARY_REPO (built failproofai checkout) required}"
25
- SANDBOX="${CANARY_SANDBOX:-$HERE}"
26
- VOL="${CANARY_VOL:-integration-suite}"
27
- IMAGE="${CANARY_IMAGE:-failproofai-integration-suite:base}"
28
- STATE="${CANARY_STATE:-$HERE/state.json}"
29
- ENVFILE="${CANARY_ENVFILE:?CANARY_ENVFILE (docker --env-file with gateway creds) required}"
30
- MODEL="${CANARY_LLM_MODEL:-deepseek-v4-pro}"
31
- GATED="${CANARY_VERSION_GATED-all}"
32
-
33
- CHANNEL="${CANARY_CHANNEL:-stable}"
34
- # Sibling leg's state file, for the cross-leg comparison (see report.js). The beta
35
- # leg needs to know whether a CLI is green on stable: "stable green + beta not
36
- # green" is the incoming-breakage signal, and it is the ONLY way to distinguish
37
- # "the vendor is about to break us" from "this CLI is broken for everyone already".
38
- PEER_STATE="${CANARY_PEER_STATE:-}"
39
-
40
- # The CLIs with a public pre-release ref — kept in sync with the beta refs in
41
- # install-clis.sh (__tests__/integration-suite/channel-refs.test.ts asserts the two
42
- # lists agree, since a silent drift here would look like coverage while probing
43
- # nothing). Its LENGTH is also the honest denominator for the beta report: a
44
- # targeted run (`run.sh cursor`) must not imply the CLIs it skipped have no
45
- # pre-release ref.
46
- BETA_CLIS=(claude codex copilot cursor goose openclaw)
47
-
48
- CLIS=("$@")
49
- if [ ${#CLIS[@]} -eq 0 ]; then
50
- if [ "$CHANNEL" = stable ]; then
51
- CLIS=(claude codex copilot cursor factory devin antigravity goose opencode pi hermes openclaw)
52
- else
53
- CLIS=("${BETA_CLIS[@]}")
54
- fi
55
- fi
56
-
57
- # failproofai main HEAD — the second gate dimension. Prefer the value the workflow
58
- # computed; fall back to git in the checkout.
59
- FP_SHA="${CANARY_FP_SHA:-$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo unknown)}"
60
-
61
- # Installed versions from the install step, keyed by cli.
62
- VERSIONS_JSON="$(docker run --rm -v "$VOL:/home/canary" "$IMAGE" cat /home/canary/canary-tier0.json 2>/dev/null || echo '[]')"
63
-
64
- run_probe() {
65
- docker run --rm --env-file "$ENVFILE" \
66
- -v "$REPO:/repo:ro" -v "$SANDBOX:/opt/canary:ro" -v "$VOL:/home/canary" \
67
- "$IMAGE" bash /opt/canary/probe-cli.sh "$1" 2>&1
68
- }
69
-
70
- results="[]"
71
- for cli in "${CLIS[@]}"; do
72
- cur_ver="$(node -e 'const a=JSON.parse(process.argv[1]);const x=a.find(y=>y.cli===process.argv[2]);process.stdout.write(x&&x.version?x.version:"")' "$VERSIONS_JSON" "$cli")"
73
-
74
- gate="$(node -e '
75
- const [statePath, cli, curVer, curSha, gated] = process.argv.slice(1);
76
- let st={clis:{}}; try{ st=JSON.parse(require("fs").readFileSync(statePath,"utf8")); }catch{}
77
- const isGated = gated === "all" || gated.split(",").filter(Boolean).includes(cli);
78
- const prev = st.clis && st.clis[cli];
79
- let skip = false;
80
- if (isGated && prev && prev.version && curVer && prev.version===curVer
81
- && prev.fpSha && curSha && prev.fpSha===curSha) {
82
- const vals=Object.values((prev.probes||prev)||{});
83
- // Only carry forward a genuinely GREEN result — FAIL/INCONCLUSIVE/ERROR
84
- // must keep re-probing so they recover (e.g. an expired token clears once
85
- // the secret is refreshed) instead of being frozen.
86
- const green = vals.length>0 && !vals.includes("FAIL") && !vals.includes("INCONCLUSIVE") && !vals.includes("ERROR");
87
- if (green) skip = true;
88
- }
89
- process.stdout.write(skip ? "skip" : "probe");
90
- ' "$STATE" "$cli" "$cur_ver" "$FP_SHA" "$GATED")"
91
-
92
- if [ "$gate" = skip ]; then
93
- echo ">> $cli: CLI $cur_ver + failproofai $FP_SHA both unchanged & last green → gated-skip (protecting quota)" >&2
94
- vj="$(node -e 'const st=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));const p=st.clis[process.argv[2]];process.stdout.write(JSON.stringify({cli:process.argv[2],probes:p.probes||p,version:p.version,fpSha:p.fpSha,gated:true}))' "$STATE" "$cli")"
95
- else
96
- echo ">> probing $cli ..." >&2
97
- # Spool to a file rather than a shell variable: a probe's output is a full agent
98
- # transcript from up to 6 CLI invocations, and a stuck or noisy client could make it
99
- # arbitrarily large. Only the verdict line and a 20-line tail are ever needed.
100
- out_file="$(mktemp)"
101
- run_probe "$cli" > "$out_file"
102
- vj="$(sed -n 's/^VERDICT_JSON //p' "$out_file" | tail -1)"
103
- # Echo the probe tail whenever the verdict is not a clean pass. Without this the
104
- # ONLY thing that survived a probe was its VERDICT_JSON line, so a yellow/red run
105
- # said WHAT broke and never WHY — and re-running told you no more, because the
106
- # cause (a vendor error message) was discarded both times. Diagnosing codex's
107
- # 0.145.0 regression needed a full local reproduction purely for want of these
108
- # lines. Safe in a public log: every credential here is a registered Actions
109
- # secret, so GitHub masks it on the way out.
110
- case "$vj" in
111
- *FAIL*|*INCONCLUSIVE*|*ERROR*|"")
112
- echo "── $cli probe output (tail) ──" >&2
113
- tail -20 "$out_file" >&2
114
- echo "── end $cli ──" >&2 ;;
115
- esac
116
- rm -f "$out_file"
117
- [ -z "$vj" ] && vj="{\"cli\":\"$cli\",\"probes\":{}}"
118
- vj="$(node -e 'const v=JSON.parse(process.argv[1]);v.version=process.argv[2]||null;v.fpSha=process.argv[3]||null;process.stdout.write(JSON.stringify(v))' "$vj" "$cur_ver" "$FP_SHA")"
119
- fi
120
- results="$(node -e 'const a=JSON.parse(process.argv[1]);a.push(JSON.parse(process.argv[2]));process.stdout.write(JSON.stringify(a))' "$results" "$vj")"
121
- done
122
-
123
- report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL" "$CHANNEL" "$PEER_STATE" "${#BETA_CLIS[@]}")"
124
- echo "════ report ════" >&2; printf '%s\n' "$report" >&2; echo "════════════════" >&2
125
- printf '%s\n' "$report" # also to stdout for the workflow log / artifact
126
-
127
- if [ -n "${CANARY_SLACK_WEBHOOK:-}" ]; then
128
- payload="$(node -e 'const t=require("fs").readFileSync(0,"utf8");process.stdout.write(JSON.stringify({text:t}))' <<<"$report")"
129
- code="$(curl -sS --connect-timeout 10 --max-time 30 \
130
- -o /dev/null -w '%{http_code}' -X POST -H 'Content-type: application/json' \
131
- --data "$payload" "$CANARY_SLACK_WEBHOOK" 2>/dev/null || echo 000)"
132
- if [ "$code" = 200 ]; then echo "✓ posted to Slack webhook" >&2
133
- else echo "⚠️ Slack webhook POST returned HTTP $code" >&2; fi
134
- else
135
- echo "(no CANARY_SLACK_WEBHOOK set — report not posted)" >&2
136
- fi
137
-
138
- # The beta leg is ADVISORY and must never fail the job. It probes vendor
139
- # pre-release builds, which are broken-by-nature often enough that gating CI on
140
- # them would train everyone to ignore a red run — and the thing it reports is
141
- # "this WILL break on release", not "this IS broken", which is not a reason to
142
- # stop the world. The report is emitted and posted either way, so the signal is
143
- # never lost; escalation is the report's job, not the exit code's.
144
- if [ "$CHANNEL" != stable ]; then
145
- echo "(channel=$CHANNEL — advisory only, not failing the job)" >&2
146
- exit 0
147
- fi
148
-
149
- # Fail the job when any probe reported a hard FAIL (broken enforcement) so this
150
- # scheduled check actually blocks regressions. ERROR (vendor quota/auth) and
151
- # INCONCLUSIVE (model didn't attempt the tool) are NOT failures — they mean
152
- # "couldn't confirm", not "enforcement broke". The report is already emitted +
153
- # posted above regardless, so the signal is never lost.
154
- if node -e 'const r=JSON.parse(process.argv[1]);process.exit(r.some(x=>Object.values(x.probes||{}).includes("FAIL"))?1:0)' "$results"; then
155
- exit 0
156
- else
157
- echo "✗ FAIL verdict(s) present — failing the job" >&2
158
- exit 1
159
- fi