failproofai 0.0.14-beta.2 → 0.0.15-beta.0

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 (269) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +2 -2
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  47. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  49. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/index.html +1 -1
  52. package/.next/standalone/.next/server/app/index.rsc +15 -15
  53. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  54. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  55. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  56. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  57. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  59. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/page.js +2 -2
  61. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  64. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  65. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  66. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  69. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
  71. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  76. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  80. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  81. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  82. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0hv08mx._.js → [root-of-the-server]__0-dh5jy._.js} +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0obaznk._.js → [root-of-the-server]__0h3lfmp._.js} +1 -1
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xvsnvt._.js → [root-of-the-server]__0l4vuqm._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sb_5m8._.js +4 -4
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +6 -4
  94. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1hkdkmz._.js → [root-of-the-server]__10oa-v9._.js} +1 -1
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1i9rknc._.js → [root-of-the-server]__18d-9oi._.js} +1 -1
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +6 -6
  102. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0hdsupo._.js → [root-of-the-server]__0114ewg._.js} +3 -3
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01od0m6._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0s89hoe._.js → [root-of-the-server]__02r5bgf._.js} +3 -3
  108. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09v9cwb._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qeuy9c._.js → [root-of-the-server]__0e8sjqm._.js} +3 -3
  110. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkjqka._.js → [root-of-the-server]__0lkzqax._.js} +3 -3
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lpvwqm._.js +21 -0
  112. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ynf7tx._.js → [root-of-the-server]__0qucxyj._.js} +3 -3
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__03lv-pe._.js → [root-of-the-server]__0u02miy._.js} +3 -3
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +8 -8
  116. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cz9as._.js → [root-of-the-server]__1apb7k_._.js} +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1i21qhv._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__015_i4t._.js → [root-of-the-server]__1zqz4v8._.js} +3 -3
  123. package/.next/standalone/.next/server/chunks/ssr/_077y5np._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/app_audit_loading_tsx_1j1kc6j._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_06xsmte.js → node_modules_html-to-image_es_index_0w82veb.js} +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  137. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  138. package/.next/standalone/.next/server/pages/404.html +1 -1
  139. package/.next/standalone/.next/server/pages/500.html +1 -1
  140. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  141. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  142. package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +6 -0
  143. package/.next/standalone/.next/static/chunks/{2llkdafnaejw_.js → 1_20f96f1253b.js} +1 -1
  144. package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
  145. package/.next/standalone/.next/static/chunks/{33u59vf_8xpd-.js → 1j2xm011_aoae.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{388hgfqye67xa.js → 1qzsbft-jtnf8.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/{3-ogr79xqbw0h.js → 2mazwb1qmur1p.js} +1 -1
  148. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +2 -0
  149. package/.next/standalone/.next/static/chunks/2xbkqlma_-61k.js +1 -0
  150. package/.next/standalone/.next/static/chunks/{29zkom4s9de2d.js → 2y7acsw0--8xf.js} +1 -1
  151. package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +1 -0
  152. package/.next/standalone/.next/static/chunks/312ihvhxeuoi_.js +1 -0
  153. package/.next/standalone/.next/static/chunks/31c084gsop1bb.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{1gg-8e9q_m__9.js → 3q9y67-fobgpc.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{turbopack-10hj8t5pf33tu.js → turbopack-1lsv92x7klwrn.js} +1 -1
  158. package/.next/standalone/app/actions/get-hooks-config.ts +85 -0
  159. package/.next/standalone/app/components/log-viewer/entry-row.tsx +8 -3
  160. package/.next/standalone/app/components/project-list.tsx +229 -42
  161. package/.next/standalone/app/components/raw-log-viewer.tsx +33 -15
  162. package/.next/standalone/app/policies/hooks-client.tsx +113 -15
  163. package/.next/standalone/app/project/[name]/page.tsx +12 -2
  164. package/.next/standalone/integration-suite/README.md +43 -1
  165. package/.next/standalone/integration-suite/ci-entrypoint.sh +21 -6
  166. package/.next/standalone/integration-suite/install-clis.sh +58 -13
  167. package/.next/standalone/integration-suite/probe-cli.sh +51 -11
  168. package/.next/standalone/integration-suite/report.js +52 -6
  169. package/.next/standalone/integration-suite/run.sh +55 -3
  170. package/.next/standalone/lib/entry-keys.ts +36 -0
  171. package/.next/standalone/lib/hermes-profiles.ts +156 -0
  172. package/.next/standalone/lib/hermes-projects.ts +91 -25
  173. package/.next/standalone/lib/hermes-sessions.ts +65 -10
  174. package/.next/standalone/lib/openclaw-projects.ts +111 -18
  175. package/.next/standalone/lib/openclaw-sessions.ts +20 -0
  176. package/.next/standalone/lib/paths.ts +36 -0
  177. package/.next/standalone/lib/project-tree.ts +193 -0
  178. package/.next/standalone/lib/projects.ts +22 -0
  179. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  180. package/.next/standalone/node_modules/next/dist/build/next-config-ts/transpile-config.js +3 -114
  181. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/cli/next-test.js +2 -1
  183. package/.next/standalone/node_modules/next/dist/compiled/http-proxy/index.js +5 -5
  184. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +11 -10
  185. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -10
  186. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
  187. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +9 -9
  188. package/.next/standalone/node_modules/next/dist/lib/install-dependencies.js +1 -1
  189. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  190. package/.next/standalone/node_modules/next/dist/lib/typescript/loadTsConfig.js +181 -0
  191. package/.next/standalone/node_modules/next/dist/lib/typescript/missingDependencyError.js +1 -1
  192. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeCheckCli.js +53 -0
  193. package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +256 -0
  194. package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +97 -56
  195. package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +129 -34
  196. package/.next/standalone/node_modules/next/dist/server/app-render/manifests-singleton.js +52 -4
  197. package/.next/standalone/node_modules/next/dist/server/config-schema.js +1 -1
  198. package/.next/standalone/node_modules/next/dist/server/config-shared.js +1 -2
  199. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  201. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  202. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +8 -27
  203. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  204. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +90 -53
  205. package/.next/standalone/node_modules/next/dist/server/lib/patch-fetch.js +15 -2
  206. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/proxy-request.js +4 -0
  207. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  208. package/.next/standalone/node_modules/next/dist/server/web/spec-extension/unstable-cache.js +1 -1
  209. package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
  210. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/prepare-destination.js +2 -1
  211. package/.next/standalone/node_modules/next/dist/shared/lib/server-reference-info.js +12 -0
  212. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  213. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  214. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  215. package/.next/standalone/node_modules/next/package.json +10 -10
  216. package/.next/standalone/package.json +9 -7
  217. package/.next/standalone/pi-extension/index.ts +38 -5
  218. package/.next/standalone/server.js +1 -1
  219. package/dist/cli.mjs +623 -308
  220. package/lib/entry-keys.ts +36 -0
  221. package/lib/hermes-profiles.ts +156 -0
  222. package/lib/hermes-projects.ts +91 -25
  223. package/lib/hermes-sessions.ts +65 -10
  224. package/lib/openclaw-projects.ts +111 -18
  225. package/lib/openclaw-sessions.ts +20 -0
  226. package/lib/paths.ts +36 -0
  227. package/lib/project-tree.ts +193 -0
  228. package/lib/projects.ts +22 -0
  229. package/package.json +9 -7
  230. package/pi-extension/index.ts +38 -5
  231. package/scripts/launch.ts +7 -0
  232. package/src/audit/cli-adapters/hermes.ts +6 -3
  233. package/src/audit/cli-adapters/openclaw.ts +17 -13
  234. package/src/hooks/configure-wizard.ts +24 -2
  235. package/src/hooks/custom-hooks-loader.ts +58 -4
  236. package/src/hooks/enforcement-capability.ts +266 -0
  237. package/src/hooks/handler.ts +12 -1
  238. package/src/hooks/hook-activity-store.ts +14 -0
  239. package/src/hooks/hooks-config.ts +59 -1
  240. package/src/hooks/integrations.ts +123 -19
  241. package/src/hooks/manager.ts +93 -27
  242. package/src/hooks/policy-evaluator.ts +59 -7
  243. package/src/hooks/policy-types.ts +26 -0
  244. package/src/hooks/types.ts +50 -2
  245. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +0 -3
  247. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0twi4fj._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +0 -4
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +0 -3
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fax1sl._.js +0 -19
  256. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +0 -3
  260. package/.next/standalone/.next/static/chunks/0h-6ymo9v86b0.js +0 -6
  261. package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +0 -1
  262. package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +0 -1
  263. package/.next/standalone/.next/static/chunks/1bg_g3g359c_d.js +0 -1
  264. package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
  265. package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +0 -1
  266. package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
  267. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
  268. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
  269. /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_ssgManifest.js +0 -0
@@ -45,6 +45,48 @@ failproofai's HEAD changed since its last green run — unchanged ⇒ can't have
45
45
  drifted ⇒ skip, protecting LLM/vendor quota. `FAIL`/`INCONCLUSIVE`/`ERROR` always
46
46
  re-probe until they recover. Dispatch with **force** to probe everything.
47
47
 
48
+ ## Two channels: `stable` and `beta`
49
+
50
+ The workflow runs as a **matrix over `CANARY_CHANNEL`**, in two concurrent legs
51
+ answering different questions:
52
+
53
+ | Leg | Installs | Question | On failure |
54
+ |-----|----------|----------|------------|
55
+ | `stable` | what users get, all 12 CLIs | *is enforcement broken now?* | **fails the job** + Slack |
56
+ | `beta` | each vendor's public pre-release ref, 6 CLIs | *is it about to break?* | **advisory only** — never fails |
57
+
58
+ Only six vendors publish something usable ahead of their release, so the beta leg
59
+ skips the rest rather than re-installing a stable build and reporting it as
60
+ pre-release coverage:
61
+
62
+ | CLI | Pre-release ref | Typical lead |
63
+ |-----|-----------------|--------------|
64
+ | codex | npm `alpha` | up to ~12 d (minor bumps only — patches ship blind) |
65
+ | copilot | npm `prerelease` | 0.6–5.8 d |
66
+ | openclaw | npm `beta` | 0.3–11.4 d |
67
+ | cursor | `install?channel=lab` | 2–4 d |
68
+ | goose | `CANARY=true` (rolling tag) | ~1 release cycle |
69
+ | claude | `latest` — **inverted**, see below | ~13 d |
70
+
71
+ **claude runs backwards.** Nothing ships ahead of `latest`, which *is* the
72
+ bleeding edge (~1 release/day); what exists is `stable`, ~13 days behind. So the
73
+ stable leg pins `bash -s stable` — testing what conservative users run, and
74
+ stopping a same-day Anthropic release from red-lighting an unrelated PR — and
75
+ `latest` becomes the early-warning ref. **hermes** is inverted too but has no fix:
76
+ its installer git-clones `main`, which is also what every hermes user gets, so
77
+ there is nothing ahead of us to probe.
78
+
79
+ **Escalation is a cross-leg comparison, not a beta failure.** A CLI red on both
80
+ legs is already broken and belongs to the stable leg's alarm. The early warning is
81
+ `stable green + beta not-green`, held for **two consecutive runs** so a broken
82
+ alpha that gets reverted before release doesn't burn attention. Note the signal is
83
+ usually `INCONCLUSIVE`/`ERROR`, not `FAIL` — a vendor payload change stops the
84
+ model before it ever calls a tool — so a FAIL-only rule would miss it entirely.
85
+
86
+ Each leg has its own Docker volume and Actions cache key: a pre-release install
87
+ overwrites the stable binary in a shared `$HOME`, and a beta result must never be
88
+ able to overwrite the stable leg's gating record.
89
+
48
90
  ## Auth & secrets (the `cli-integration` Environment)
49
91
 
50
92
  > The GitHub **Environment** is still named `cli-integration` — it's configured in
@@ -77,7 +119,7 @@ re-login on the capture machine and refresh the secret.
77
119
  ```
78
120
  ci-entrypoint.sh CI front door: build -> sandbox -> install -> probe -> cleanup
79
121
  Dockerfile non-root sandbox base image
80
- install-clis.sh install/upgrade all 12 CLIs @latest (with retries)
122
+ install-clis.sh install/upgrade the CLIs for $CANARY_CHANNEL (with retries)
81
123
  inject-tokens.sh write captured OAuth creds into the fresh volume
82
124
  probe-cli.sh live enforcement probe for ONE CLI (the oracle)
83
125
  canary-policies.mjs benign-marker custom policies the probe trips
@@ -27,6 +27,7 @@
27
27
  # CANARY_LLM_MODEL (default deepseek-v4-pro)
28
28
  # CANARY_CLAUDE_MODEL (default claude-haiku-4-5)
29
29
  # CANARY_PI_MODEL (default claude-haiku-4-5)
30
+ # CANARY_CODEX_MODEL (default gpt-5.1-codex-mini)
30
31
  # COPILOT_GITHUB_TOKEN copilot PAT
31
32
  # CURSOR_TOKEN_TGZ_B64 } base64 gzip-tars of each OAuth credential tree,
32
33
  # DEVIN_TOKEN_TGZ_B64 } rooted at $HOME (see capture-tokens.sh)
@@ -43,11 +44,20 @@ set -u
43
44
  HERE="$(cd "$(dirname "$0")" && pwd)"
44
45
  REPO="${GITHUB_WORKSPACE:-$(dirname "$HERE")}"
45
46
 
46
- VOL="${CANARY_VOL:-integration-suite}"
47
+ # Each channel gets its OWN volume: installing a pre-release into the same $HOME
48
+ # overwrites the stable binary (npm -g, and the vendor installers all rewrite
49
+ # their own shims), so the two refs cannot coexist in one volume.
50
+ CHANNEL="${CANARY_CHANNEL:-stable}"
51
+ SUFFIX=""; [ "$CHANNEL" != stable ] && SUFFIX="-$CHANNEL"
52
+ VOL="${CANARY_VOL:-integration-suite$SUFFIX}"
47
53
  IMAGE="${CANARY_IMAGE:-failproofai-integration-suite:base}"
48
- ENVFILE="${CANARY_ENVFILE:-$REPO/canary.env}"
49
- TOKENS_DIR="${CANARY_TOKENS_DIR:-$REPO/tokens}"
50
- STATE="${CANARY_STATE:-$REPO/integration-suite-state.json}"
54
+ ENVFILE="${CANARY_ENVFILE:-$REPO/canary$SUFFIX.env}"
55
+ TOKENS_DIR="${CANARY_TOKENS_DIR:-$REPO/tokens$SUFFIX}"
56
+ STATE="${CANARY_STATE:-$REPO/integration-suite-state$SUFFIX.json}"
57
+ # The stable leg's state, so the beta leg can tell "about to break" from
58
+ # "already broken" (see report.js). Unset on the stable leg.
59
+ PEER_STATE="${CANARY_PEER_STATE:-}"
60
+ [ "$CHANNEL" != stable ] && [ -z "$PEER_STATE" ] && PEER_STATE="$REPO/integration-suite-state.json"
51
61
 
52
62
  step() { echo "── $* ──" >&2; }
53
63
 
@@ -123,8 +133,8 @@ docker volume rm "$VOL" -f >/dev/null 2>&1 || true
123
133
  docker volume create "$VOL" >/dev/null || { echo "✗ volume create failed" >&2; exit 1; }
124
134
 
125
135
  # ── 4. install CLIs @latest, then inject tokens ─────────────────────────────
126
- step "installing 12 CLIs @latest into the fresh volume"
127
- docker run --rm -v "$VOL:/home/canary" -v "$HERE:/opt/canary:ro" \
136
+ step "installing CLIs (channel=$CHANNEL) into the fresh volume"
137
+ docker run --rm -e CANARY_CHANNEL="$CHANNEL" -v "$VOL:/home/canary" -v "$HERE:/opt/canary:ro" \
128
138
  "$IMAGE" bash /opt/canary/install-clis.sh \
129
139
  || { echo "✗ CLI install failed" >&2; exit 1; }
130
140
 
@@ -145,6 +155,9 @@ step "assembling gateway env-file"
145
155
  echo "CANARY_LLM_MODEL=${CANARY_LLM_MODEL:-deepseek-v4-pro}"
146
156
  echo "CANARY_CLAUDE_MODEL=${CANARY_CLAUDE_MODEL:-claude-haiku-4-5}"
147
157
  echo "CANARY_PI_MODEL=${CANARY_PI_MODEL:-claude-haiku-4-5}"
158
+ # Without this line probe-cli.sh's CANARY_CODEX_MODEL override was dead plumbing:
159
+ # the var is read inside the container, and only what is written here crosses into it.
160
+ echo "CANARY_CODEX_MODEL=${CANARY_CODEX_MODEL:-gpt-5.1-codex-mini}"
148
161
  echo "COPILOT_GITHUB_TOKEN=${COPILOT_GITHUB_TOKEN:-}"
149
162
  } > "$ENVFILE"
150
163
  )
@@ -161,4 +174,6 @@ CANARY_VOL="$VOL" \
161
174
  CANARY_IMAGE="$IMAGE" \
162
175
  CANARY_STATE="$STATE" \
163
176
  CANARY_ENVFILE="$ENVFILE" \
177
+ CANARY_CHANNEL="$CHANNEL" \
178
+ CANARY_PEER_STATE="$PEER_STATE" \
164
179
  bash "$HERE/run.sh" ${CANARY_CLIS:-}
@@ -2,10 +2,22 @@
2
2
  # ─────────────────────────────────────────────────────────────────────────────
3
3
  # Tier-0 install probe — runs INSIDE the canary sandbox container.
4
4
  #
5
- # Installs/upgrades all 12 agent CLIs to @latest into the persistent HOME volume,
6
- # then records which resolve to a runnable binary + their version. This is both
7
- # the daily "fresh upgrade" step AND the Tier-0 canary signal (packaging/binary
8
- # breaks show up here as a FAIL).
5
+ # Installs/upgrades the agent CLIs into the persistent HOME volume, then records
6
+ # which resolve to a runnable binary + their version. This is both the daily
7
+ # "fresh upgrade" step AND the Tier-0 canary signal (packaging/binary breaks show
8
+ # up here as a FAIL).
9
+ #
10
+ # CANARY_CHANNEL selects WHICH ref of each CLI to install:
11
+ # stable (default) — what users get. All 12 CLIs.
12
+ # beta — the vendor's public pre-release ref, for early warning.
13
+ # Only the CLIs that actually publish one; the rest are
14
+ # SKIPPED, never silently re-installed at stable (that would
15
+ # be duplicate work reported as coverage).
16
+ #
17
+ # Every beta ref below was verified live against the vendor's registry/CDN on
18
+ # 2026-07-23; see ~/Desktop/failproofai-integration-suite-beta-channels-design.md
19
+ # for the evidence, lead times, and the CLIs that have no pre-release ref at all
20
+ # (factory, antigravity, pi, opencode, devin, hermes).
9
21
  #
10
22
  # Install methods verified 2026-07-16 (see failproofai-cli-canary-design.md).
11
23
  # Vendor installers scatter binaries across several dirs, so we search an
@@ -18,6 +30,7 @@ set -u
18
30
 
19
31
  RESULTS="${CANARY_RESULTS:-$HOME/canary-tier0.json}"
20
32
  LOGDIR="${CANARY_LOGDIR:-$HOME/canary-logs}"
33
+ CHANNEL="${CANARY_CHANNEL:-stable}"
21
34
  mkdir -p "$LOGDIR"
22
35
 
23
36
  # Cover every dir the vendor installers are known to drop binaries into.
@@ -25,8 +38,19 @@ export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$HOME/.factory/bin:$HOME/.he
25
38
 
26
39
  ROWS=()
27
40
 
41
+ # probe <id> <binary> <--version flag> <stable install cmd> [beta install cmd]
42
+ # Omitting the 5th argument declares "this CLI has no public pre-release ref",
43
+ # which skips it entirely on the beta channel.
28
44
  probe() {
29
- local id="$1" bin="$2" vflag="$3" install="$4"
45
+ local id="$1" bin="$2" vflag="$3" install="$4" beta="${5:-}"
46
+ if [ "$CHANNEL" != stable ]; then
47
+ if [ -z "$beta" ]; then
48
+ echo "════════════════════ $id ════════════════════"
49
+ echo " skipped — no public pre-release ref for this CLI"
50
+ return 0
51
+ fi
52
+ install="$beta"
53
+ fi
30
54
  echo "════════════════════ $id ════════════════════"
31
55
  local t0 t1 rc=1; t0=$SECONDS
32
56
  # Retry transient install failures. Flaky vendor CDNs / HTTP-2 stream resets are
@@ -64,19 +88,40 @@ probe() {
64
88
  "$id" "$status" "${resolved:-}" "${ver:-}" "$rc" "$((t1 - t0))")")
65
89
  }
66
90
 
67
- # id binary --ver flag install command (@latest)
68
- probe claude claude "--version" 'curl -fsSL https://claude.ai/install.sh | bash'
69
- probe codex codex "--version" 'npm install -g @openai/codex@latest'
70
- probe copilot copilot "--version" 'npm install -g @github/copilot@latest'
71
- probe cursor cursor-agent "--version" 'curl https://cursor.com/install -fsS | bash'
91
+ # id binary --ver flag stable ref beta ref (omitted = none exists)
92
+ #
93
+ # claude is the one CLI whose channels run BACKWARDS: nothing ships ahead of
94
+ # `latest`, which is the bleeding edge (~1 release/day). What exists is `stable`,
95
+ # ~13 days behind. So the stable leg pins `stable` — both to test what
96
+ # conservative users actually run and to stop a same-day Anthropic release
97
+ # red-lighting an unrelated PR — and `latest` becomes the early-warning ref.
98
+ probe claude claude "--version" 'curl -fsSL https://claude.ai/install.sh | bash -s stable' \
99
+ 'curl -fsSL https://claude.ai/install.sh | bash'
100
+ probe codex codex "--version" 'npm install -g @openai/codex@latest' \
101
+ 'npm install -g @openai/codex@alpha'
102
+ probe copilot copilot "--version" 'npm install -g @github/copilot@latest' \
103
+ 'npm install -g @github/copilot@prerelease'
104
+ # Cursor validates the channel server-side: only prod/lab/static/prod-stable-internal
105
+ # return 200, anything else is a 400. `lab` is Anysphere's own dogfood channel.
106
+ probe cursor cursor-agent "--version" 'curl https://cursor.com/install -fsS | bash' \
107
+ 'curl "https://cursor.com/install?channel=lab" -fsS | bash'
108
+ # opencode's beta/dev tags are ~31 branch snapshots a day (0.0.0-<branch>-<ts>),
109
+ # not release candidates — deliberately no beta ref.
72
110
  probe opencode opencode "--version" 'npm install -g opencode-ai@latest'
73
111
  probe pi pi "--version" 'npm install -g @mariozechner/pi-coding-agent@latest'
112
+ # hermes install.sh git-clones main, which is ALSO what every hermes user gets —
113
+ # its tags are markers nobody installs. So there is nothing ahead of us to probe,
114
+ # and pinning a tag would test something no user runs.
74
115
  probe hermes hermes "--version" 'curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash'
75
- probe openclaw openclaw "--version" 'npm install -g openclaw@latest'
116
+ probe openclaw openclaw "--version" 'npm install -g openclaw@latest' \
117
+ 'npm install -g openclaw@beta'
76
118
  probe factory droid "--version" 'curl -fsSL https://app.factory.ai/cli | sh'
77
119
  probe devin devin "--version" 'curl -fsSL https://cli.devin.ai/install.sh | bash'
78
120
  probe antigravity agy "--version" 'curl -fsSL https://antigravity.google/cli/install.sh | bash'
79
- probe goose goose "--version" 'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash'
121
+ # goose's canary is a ROLLING TAG (prerelease:false), selected by env var — not a
122
+ # GitHub prerelease, so channel detection that scans for prerelease:true misses it.
123
+ probe goose goose "--version" 'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash' \
124
+ 'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CANARY=true CONFIGURE=false bash'
80
125
 
81
126
  # Emit JSON array.
82
127
  { printf '['; IFS=,; printf '%s' "${ROWS[*]}"; printf ']\n'; } > "$RESULTS"
@@ -87,5 +132,5 @@ ok=0; fail=0
87
132
  for r in "${ROWS[@]}"; do
88
133
  case "$r" in *'"status":"OK"'*) ok=$((ok+1));; *) fail=$((fail+1));; esac
89
134
  done
90
- printf 'installed OK: %s / 12 failed: %s\n' "$ok" "$fail"
135
+ printf 'channel: %s installed OK: %s / %s failed: %s\n' "$CHANNEL" "$ok" "$((ok + fail))" "$fail"
91
136
  echo "results JSON: $RESULTS"
@@ -14,14 +14,33 @@
14
14
  set -u
15
15
  CLI="${1:?usage: probe-cli.sh <cli>}"
16
16
  : "${CANARY_LLM_API_KEY:?gateway key missing}"
17
- # Gateway default model. deepseek-v4-pro is cheapest AND works on both the OpenAI
18
- # chat-completions and Responses paths. EXCEPTION: the claude CLI speaks Anthropic
19
- # tool-use, which deepseek-via-/v1/messages doesn't emit correctly (all probes go
20
- # INCONCLUSIVE), so claude pins to the cheapest Anthropic model via CANARY_CLAUDE_MODEL.
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.
21
23
  : "${CANARY_LLM_MODEL:=deepseek-v4-pro}"
22
24
  : "${CANARY_CLAUDE_MODEL:=claude-haiku-4-5}"
23
- # pi sends an `include` (encrypted reasoning) param deepseek rejects (400) → pin to Anthropic.
25
+ # EXCEPTION 2: pi sends an `include` (encrypted reasoning) param deepseek rejects (400).
24
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}"
25
44
  # Gateway base URL — overridable via env (CI supplies it as a secret). Strip any
26
45
  # trailing slash so the `$GW/v1` joins below never produce `//v1`.
27
46
  GW="${CANARY_LLM_BASE_URL:-https://models.aikin.club}"; GW="${GW%/}"
@@ -129,7 +148,7 @@ drive() { # $1 = prompt ; run ONE prompt headless, executing tools without appro
129
148
  codex) ( cd "$BASE" && codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust \
130
149
  -c model_providers.gw.name="gw" -c model_providers.gw.base_url="$GW/v1" -c model_providers.gw.wire_api="responses" \
131
150
  -c model_providers.gw.env_key="CANARY_LLM_API_KEY" -c model_provider="gw" \
132
- -c model="${CANARY_CODEX_MODEL:-deepseek-v4-pro}" "$1" 2>&1 ) ;; # Claude models 400 on Bedrock metadata; deepseek is non-Bedrock
151
+ -c model="$CANARY_CODEX_MODEL" "$1" 2>&1 ) ;;
133
152
  cursor) ( cd "$BASE" && cursor-agent -p --force "$1" 2>&1 ) ;;
134
153
  copilot) ( cd "$BASE" && copilot -p "$1" --allow-all-tools 2>&1 ) ;;
135
154
  devin) ( cd "$BASE" && devin -p "$1" --permission-mode dangerous --respect-workspace-trust false 2>&1 ) ;;
@@ -163,7 +182,21 @@ read_denied() { grep -qE "result=deny policy=(failproofai/|custom/)?(canary-read
163
182
  # Vendor quota / auth errors (Copilot-Free credits, antigravity Google quota,
164
183
  # expired logins) → the CLI errors before any tool call. Report these DISTINCTLY
165
184
  # (not as plain INCONCLUSIVE) so "can't test right now" ≠ "model just didn't try".
166
- 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"; }
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"; }
167
200
 
168
201
  ATTEMPTS=3 # retry up to N times to absorb LLM nondeterminism (flaky tool-callers)
169
202
 
@@ -180,8 +213,13 @@ for _ in $(seq 1 $ATTEMPTS); do
180
213
  denied canary-bash "$LOGA/hooks.log" && break
181
214
  [ -f "$BASE/CANARY_PROBE_ran" ] && break
182
215
  done
183
- if denied canary-bash "$LOGA/hooks.log"; then VA=PASS
184
- elif [ -f "$BASE/CANARY_PROBE_ran" ]; then VA=FAIL
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
185
223
  elif is_error "$OUTA"; then VA=ERROR
186
224
  else VA=INCONCLUSIVE; fi
187
225
 
@@ -195,8 +233,10 @@ for _ in $(seq 1 $ATTEMPTS); do
195
233
  read_denied "$LOGB/hooks.log" && break
196
234
  printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT" && break
197
235
  done
198
- if read_denied "$LOGB/hooks.log"; then VB=PASS
199
- elif printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT"; then VB=FAIL
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
200
240
  elif is_error "$OUTB"; then VB=ERROR
201
241
  else VB=INCONCLUSIVE; fi
202
242
 
@@ -1,15 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  // Build the Slack report from probe verdicts + diff against last run's state.
3
- // Args: <resultsJson> <statePath> <model>
3
+ // Args: <resultsJson> <statePath> <model> [channel] [peerStatePath]
4
4
  // Each result: {cli, probes:{...}, version?, gated?}. Prints report to stdout
5
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).
6
10
  const fs = require("fs");
7
- const [, , resultsJson, statePath, model] = process.argv;
11
+ const [, , resultsJson, statePath, model, channel = "stable", peerStatePath = "", eligible = ""] = process.argv;
8
12
  const results = JSON.parse(resultsJson);
13
+ const isBeta = channel !== "stable";
9
14
 
10
15
  let prev = { clis: {}, lastRun: null };
11
16
  try { prev = JSON.parse(fs.readFileSync(statePath, "utf8")); } catch {}
12
17
 
18
+ let peer = { clis: {} };
19
+ if (peerStatePath) { try { peer = JSON.parse(fs.readFileSync(peerStatePath, "utf8")); } catch {} }
20
+
13
21
  const probesOf = (entry) => (entry ? (entry.probes || entry) : null); // compat old flat schema
14
22
  const statusOf = (probes) => {
15
23
  const v = Object.values(probes || {});
@@ -23,16 +31,41 @@ const emo = { green: "🟢", yellow: "🟡", red: "🔴", grey: "⚪", error: "
23
31
  const rank = { green: 0, grey: 1, yellow: 1, error: 1, red: 2 };
24
32
 
25
33
  const now = new Date().toISOString().replace(/\.\d+Z$/, "Z");
26
- const lines = [`🧪 *failproofai CLI integration tests* · ${now} · model=${model}`];
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];
27
38
  let worst = "green";
28
- const breaks = [], recoveries = [];
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 = {};
29
47
 
30
48
  for (const r of results) {
31
49
  const st = statusOf(r.probes);
32
50
  const prevEntry = prev.clis && prev.clis[r.cli];
33
51
  const prevSt = prevEntry ? statusOf(probesOf(prevEntry)) : null;
34
52
  let mark = "";
35
- if (r.gated) {
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) {
36
69
  mark = ` 🔵 _gated: CLI v${r.version || "?"} + failproofai ${r.fpSha || "?"} unchanged, last green_`;
37
70
  } else if (prevSt && prevSt !== "red" && st === "red") {
38
71
  mark = " ⚠️ *BROKE*"; breaks.push(r.cli);
@@ -48,7 +81,19 @@ for (const r of results) {
48
81
 
49
82
  const errored = results.filter((r) => statusOf(r.probes) === "error").map((r) => r.cli);
50
83
  let hdr;
51
- if (breaks.length) hdr = `🔴 *ENFORCEMENT BROKEN*: ${breaks.join(", ")} — investigate now`;
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`;
52
97
  // Any CLI still red dominates the header — a recovery elsewhere must not mask it.
53
98
  else if (worst === "red") hdr = "🔴 still broken (unchanged)";
54
99
  else if (recoveries.length) hdr = `✅ recovered: ${recoveries.join(", ")}`;
@@ -66,6 +111,7 @@ for (const r of results) {
66
111
  probes: r.probes,
67
112
  version: r.version || (prevEntry && prevEntry.version) || null,
68
113
  fpSha: r.fpSha || (prevEntry && prevEntry.fpSha) || null,
114
+ ...(isBeta ? { strikes: newStrikes[r.cli] || 0 } : {}),
69
115
  };
70
116
  }
71
117
  try { fs.writeFileSync(statePath, JSON.stringify(newState, null, 2)); }
@@ -30,7 +30,29 @@ ENVFILE="${CANARY_ENVFILE:?CANARY_ENVFILE (docker --env-file with gateway creds)
30
30
  MODEL="${CANARY_LLM_MODEL:-deepseek-v4-pro}"
31
31
  GATED="${CANARY_VERSION_GATED-all}"
32
32
 
33
- CLIS=("$@"); [ ${#CLIS[@]} -eq 0 ] && CLIS=(claude codex copilot cursor factory devin antigravity goose opencode pi hermes openclaw)
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
34
56
 
35
57
  # failproofai main HEAD — the second gate dimension. Prefer the value the workflow
36
58
  # computed; fall back to git in the checkout.
@@ -72,14 +94,33 @@ for cli in "${CLIS[@]}"; do
72
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")"
73
95
  else
74
96
  echo ">> probing $cli ..." >&2
75
- vj="$(run_probe "$cli" | sed -n 's/^VERDICT_JSON //p' | tail -1)"
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"
76
117
  [ -z "$vj" ] && vj="{\"cli\":\"$cli\",\"probes\":{}}"
77
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")"
78
119
  fi
79
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")"
80
121
  done
81
122
 
82
- report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL")"
123
+ report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL" "$CHANNEL" "$PEER_STATE" "${#BETA_CLIS[@]}")"
83
124
  echo "════ report ════" >&2; printf '%s\n' "$report" >&2; echo "════════════════" >&2
84
125
  printf '%s\n' "$report" # also to stdout for the workflow log / artifact
85
126
 
@@ -94,6 +135,17 @@ else
94
135
  echo "(no CANARY_SLACK_WEBHOOK set — report not posted)" >&2
95
136
  fi
96
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
+
97
149
  # Fail the job when any probe reported a hard FAIL (broken enforcement) so this
98
150
  # scheduled check actually blocks regressions. ERROR (vendor quota/auth) and
99
151
  # INCONCLUSIVE (model didn't attempt the tool) are NOT failures — they mean
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Stable per-entry identity for the log viewer.
3
+ *
4
+ * Deliberately standalone and dependency-free: `lib/log-entries.ts` reaches
5
+ * fs/promises through its project-resolution imports, so a *value* import of
6
+ * it from a client component drags node:fs into the browser bundle and the
7
+ * session page 500s. Client code may only import types from there.
8
+ */
9
+ import type { LogEntry } from "./log-entries";
10
+
11
+ /**
12
+ * Assigns every entry a unique, render-stable identity.
13
+ *
14
+ * Not every CLI writes a per-record `uuid` — Codex, Copilot, Cursor and Pi
15
+ * transcripts have none, so `baseEntry` leaves it "". Keying rows off
16
+ * `uuid || timestamp` therefore collapses to the timestamp, which those CLIs
17
+ * reuse freely (one 771-record Codex session: 93 timestamps shared by 2-5
18
+ * records each). Duplicate React keys break reconciliation, and in the
19
+ * virtualized log list that strands orphaned DOM nodes stacked on top of live
20
+ * rows — they keep their old transform and never recover (#292 follow-up).
21
+ *
22
+ * Collisions are disambiguated by occurrence order, so keys stay stable for as
23
+ * long as the parsed entry list is — which is what React and the virtualizer's
24
+ * measurement cache both require.
25
+ */
26
+ export function buildEntryKeys(entries: LogEntry[]): Map<LogEntry, string> {
27
+ const keys = new Map<LogEntry, string>();
28
+ const seen = new Map<string, number>();
29
+ for (const entry of entries) {
30
+ const base = entry.uuid || `${entry._source}:${entry.timestamp}`;
31
+ const n = seen.get(base) ?? 0;
32
+ seen.set(base, n + 1);
33
+ keys.set(entry, n === 0 ? base : `${base}#${n}`);
34
+ }
35
+ return keys;
36
+ }