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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0hv08mx._.js → [root-of-the-server]__0-dh5jy._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0obaznk._.js → [root-of-the-server]__0h3lfmp._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xvsnvt._.js → [root-of-the-server]__0l4vuqm._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sb_5m8._.js +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +6 -4
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1hkdkmz._.js → [root-of-the-server]__10oa-v9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1i9rknc._.js → [root-of-the-server]__18d-9oi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +6 -6
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0hdsupo._.js → [root-of-the-server]__0114ewg._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01od0m6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0s89hoe._.js → [root-of-the-server]__02r5bgf._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09v9cwb._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qeuy9c._.js → [root-of-the-server]__0e8sjqm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkjqka._.js → [root-of-the-server]__0lkzqax._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lpvwqm._.js +21 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ynf7tx._.js → [root-of-the-server]__0qucxyj._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__03lv-pe._.js → [root-of-the-server]__0u02miy._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +8 -8
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cz9as._.js → [root-of-the-server]__1apb7k_._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1i21qhv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__015_i4t._.js → [root-of-the-server]__1zqz4v8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/_077y5np._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit_loading_tsx_1j1kc6j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- 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
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +6 -0
- package/.next/standalone/.next/static/chunks/{2llkdafnaejw_.js → 1_20f96f1253b.js} +1 -1
- package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
- package/.next/standalone/.next/static/chunks/{33u59vf_8xpd-.js → 1j2xm011_aoae.js} +1 -1
- package/.next/standalone/.next/static/chunks/{388hgfqye67xa.js → 1qzsbft-jtnf8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3-ogr79xqbw0h.js → 2mazwb1qmur1p.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +2 -0
- package/.next/standalone/.next/static/chunks/2xbkqlma_-61k.js +1 -0
- package/.next/standalone/.next/static/chunks/{29zkom4s9de2d.js → 2y7acsw0--8xf.js} +1 -1
- package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +1 -0
- package/.next/standalone/.next/static/chunks/312ihvhxeuoi_.js +1 -0
- package/.next/standalone/.next/static/chunks/31c084gsop1bb.js +1 -0
- package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
- package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1gg-8e9q_m__9.js → 3q9y67-fobgpc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-10hj8t5pf33tu.js → turbopack-1lsv92x7klwrn.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +85 -0
- package/.next/standalone/app/components/log-viewer/entry-row.tsx +8 -3
- package/.next/standalone/app/components/project-list.tsx +229 -42
- package/.next/standalone/app/components/raw-log-viewer.tsx +33 -15
- package/.next/standalone/app/policies/hooks-client.tsx +113 -15
- package/.next/standalone/app/project/[name]/page.tsx +12 -2
- package/.next/standalone/integration-suite/README.md +43 -1
- package/.next/standalone/integration-suite/ci-entrypoint.sh +21 -6
- package/.next/standalone/integration-suite/install-clis.sh +58 -13
- package/.next/standalone/integration-suite/probe-cli.sh +51 -11
- package/.next/standalone/integration-suite/report.js +52 -6
- package/.next/standalone/integration-suite/run.sh +55 -3
- package/.next/standalone/lib/entry-keys.ts +36 -0
- package/.next/standalone/lib/hermes-profiles.ts +156 -0
- package/.next/standalone/lib/hermes-projects.ts +91 -25
- package/.next/standalone/lib/hermes-sessions.ts +65 -10
- package/.next/standalone/lib/openclaw-projects.ts +111 -18
- package/.next/standalone/lib/openclaw-sessions.ts +20 -0
- package/.next/standalone/lib/paths.ts +36 -0
- package/.next/standalone/lib/project-tree.ts +193 -0
- package/.next/standalone/lib/projects.ts +22 -0
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/next-config-ts/transpile-config.js +3 -114
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/cli/next-test.js +2 -1
- package/.next/standalone/node_modules/next/dist/compiled/http-proxy/index.js +5 -5
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +11 -10
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -10
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +9 -9
- package/.next/standalone/node_modules/next/dist/lib/install-dependencies.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/lib/typescript/loadTsConfig.js +181 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/missingDependencyError.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeCheckCli.js +53 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +256 -0
- package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +97 -56
- package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +129 -34
- package/.next/standalone/node_modules/next/dist/server/app-render/manifests-singleton.js +52 -4
- package/.next/standalone/node_modules/next/dist/server/config-schema.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/config-shared.js +1 -2
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +8 -27
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +90 -53
- package/.next/standalone/node_modules/next/dist/server/lib/patch-fetch.js +15 -2
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/proxy-request.js +4 -0
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/web/spec-extension/unstable-cache.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/prepare-destination.js +2 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/server-reference-info.js +12 -0
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +9 -7
- package/.next/standalone/pi-extension/index.ts +38 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +623 -308
- package/lib/entry-keys.ts +36 -0
- package/lib/hermes-profiles.ts +156 -0
- package/lib/hermes-projects.ts +91 -25
- package/lib/hermes-sessions.ts +65 -10
- package/lib/openclaw-projects.ts +111 -18
- package/lib/openclaw-sessions.ts +20 -0
- package/lib/paths.ts +36 -0
- package/lib/project-tree.ts +193 -0
- package/lib/projects.ts +22 -0
- package/package.json +9 -7
- package/pi-extension/index.ts +38 -5
- package/scripts/launch.ts +7 -0
- package/src/audit/cli-adapters/hermes.ts +6 -3
- package/src/audit/cli-adapters/openclaw.ts +17 -13
- package/src/hooks/configure-wizard.ts +24 -2
- package/src/hooks/custom-hooks-loader.ts +58 -4
- package/src/hooks/enforcement-capability.ts +266 -0
- package/src/hooks/handler.ts +12 -1
- package/src/hooks/hook-activity-store.ts +14 -0
- package/src/hooks/hooks-config.ts +59 -1
- package/src/hooks/integrations.ts +123 -19
- package/src/hooks/manager.ts +93 -27
- package/src/hooks/policy-evaluator.ts +59 -7
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/types.ts +50 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0twi4fj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fax1sl._.js +0 -19
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +0 -3
- package/.next/standalone/.next/static/chunks/0h-6ymo9v86b0.js +0 -6
- package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +0 -1
- package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +0 -1
- package/.next/standalone/.next/static/chunks/1bg_g3g359c_d.js +0 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
- package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +0 -1
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
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
|
-
|
|
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
|
|
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
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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 /
|
|
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
|
|
18
|
-
# chat-completions and Responses paths.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
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)
|
|
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="$
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|