failproofai 1.0.0-beta.4 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +3 -1
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
- 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 +3 -2
- 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 +4 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -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 +3 -2
- 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 +3 -2
- 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 +3 -2
- 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 +3 -2
- 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/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +5 -3
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +3 -1
- 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/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/server/app/policies/page.js +4 -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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
- 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/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 +6 -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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +4 -2
- 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/app/settings/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
- package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
- package/.next/standalone/.next/server/app/settings/page.js +18 -0
- package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p7s8-d._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- 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]__06bp_fg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +10 -10
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_a_gwn._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e7-pa7._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rtdfof._.js → [root-of-the-server]__1rdruw4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0rag5dg._.js → [root-of-the-server]__1yw368c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0igtyj5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- 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/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware.js +2 -2
- 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 +54 -12
- package/.next/standalone/.next/static/chunks/{2c90h1g6x8z8y.js → 0_2n13i94rnw5.js} +1 -1
- package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/23557z4e6dtsr.js +1 -0
- package/.next/standalone/.next/static/chunks/28zkwzu50puki.js +1 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/3081gykdubv8b.js +1 -0
- package/.next/standalone/.next/static/chunks/{06jakt_xhoolk.js → 34fa65k0cldkj.js} +1 -1
- package/.next/standalone/.next/static/chunks/35v07j9gc1pvr.js +6 -0
- package/.next/standalone/.next/static/chunks/{2zb3m1tzq6h98.js → 3f_939wi3kfnx.js} +22 -22
- package/.next/standalone/.next/static/chunks/{31cqiv_cpgj9m.js → 3p1vpm2_-tfwm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ue9ak_xexiqh.js → 3rbpw_waieafm.js} +1 -1
- package/.next/standalone/.next/static/chunks/40ffnfpkeyvb0.js +1 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
- package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
- package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
- package/.next/standalone/app/api/audit/run/route.ts +28 -0
- package/.next/standalone/app/api/audit/status/route.ts +13 -1
- package/.next/standalone/app/api/auth/status/route.ts +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +488 -0
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/lib/auth/api-server-client.ts +2 -3
- package/.next/standalone/lib/auth/auth-store.ts +4 -4
- package/.next/standalone/lib/codex-sessions.ts +2 -1
- package/.next/standalone/lib/dashboard-host.ts +101 -0
- package/.next/standalone/lib/install-check.ts +22 -7
- package/.next/standalone/lib/pi-sessions.ts +99 -14
- package/.next/standalone/lib/telemetry-enabled.ts +54 -0
- package/.next/standalone/lib/telemetry-id.ts +52 -1
- package/.next/standalone/lib/telemetry.ts +8 -4
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +7 -6
- package/.next/standalone/proxy.ts +89 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +197 -80
- package/dist/cli.mjs +17699 -15345
- package/dist/worker.mjs +1620 -257
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -4
- package/lib/codex-sessions.ts +2 -1
- package/lib/dashboard-host.ts +101 -0
- package/lib/install-check.ts +22 -7
- package/lib/pi-sessions.ts +99 -14
- package/lib/telemetry-enabled.ts +54 -0
- package/lib/telemetry-id.ts +52 -1
- package/lib/telemetry.ts +8 -4
- package/package.json +7 -6
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +17 -1
- package/scripts/prune-standalone.mjs +20 -0
- package/scripts/translate-docs/readme-translator.ts +163 -4
- package/scripts/validate-mdx.ts +153 -13
- package/src/audit/audit-lock.ts +285 -0
- package/src/audit/audit-schedule.ts +111 -0
- package/src/audit/cache.ts +18 -4
- package/src/audit/cli-adapters/goose.ts +2 -1
- package/src/audit/cli-adapters/hermes.ts +28 -10
- package/src/audit/cli.ts +191 -35
- package/src/audit/dashboard-cache.ts +2 -1
- package/src/hooks/builtin-policies.ts +100 -3
- package/src/hooks/cloud-connection.ts +163 -14
- package/src/hooks/cloud-enrollment-cli.ts +119 -21
- package/src/hooks/cloud-enrollment.ts +87 -17
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +33 -2
- package/src/hooks/collector-config.ts +32 -40
- package/src/hooks/configure-wizard.ts +696 -318
- package/src/hooks/custom-hooks-loader.ts +125 -11
- package/src/hooks/daemon-client.ts +83 -17
- package/src/hooks/daemon-download.ts +54 -2
- package/src/hooks/daemon-service.ts +595 -27
- package/src/hooks/first-run-gate.ts +39 -0
- package/src/hooks/fp-config.ts +497 -0
- package/src/hooks/fp-home.ts +342 -0
- package/src/hooks/fp-reset.ts +332 -0
- package/src/hooks/handler.ts +42 -1
- package/src/hooks/hook-activity-store.ts +35 -20
- package/src/hooks/hook-logger.ts +2 -1
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/loader-utils.ts +166 -5
- package/src/hooks/manager.ts +5 -4
- package/src/hooks/onboarding-lock.ts +119 -0
- package/src/hooks/policy-types.ts +0 -7
- package/src/hooks/session-pause-cli.ts +15 -13
- package/src/hooks/session-pause.ts +36 -4
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +4 -4
- package/src/hooks/worker-server.ts +53 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0szqzl8._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cmdyn_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n_uvv0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0sdrz-j._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1q9xxca.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
- package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +0 -6
- package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +0 -1
- package/.next/standalone/.next/static/chunks/3h5_3u46gu9jx.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +0 -2
- package/.next/standalone/.next/static/chunks/43a1ywqsg9bq9.js +0 -1
- package/.next/standalone/Cargo.lock +0 -2222
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/CLOUD_POLICIES.md +0 -136
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -25
- package/.next/standalone/crates/failproofaid/src/cloud_client.rs +0 -484
- package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +0 -921
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -532
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -201
- package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
- package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
- package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
- package/.next/standalone/crates/fpai-collect/Cargo.toml +0 -43
- package/.next/standalone/crates/fpai-collect/src/config.rs +0 -412
- package/.next/standalone/crates/fpai-collect/src/cursor.rs +0 -520
- package/.next/standalone/crates/fpai-collect/src/delivery.rs +0 -472
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +0 -484
- package/.next/standalone/crates/fpai-collect/src/health.rs +0 -369
- package/.next/standalone/crates/fpai-collect/src/lib.rs +0 -34
- package/.next/standalone/crates/fpai-collect/src/redact.rs +0 -516
- package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +0 -279
- package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +0 -619
- package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +0 -156
- package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +0 -715
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +0 -158
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +0 -512
- package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +0 -511
- package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +0 -586
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +0 -318
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +0 -579
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +0 -310
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +0 -448
- package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +0 -23
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +0 -176
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +0 -500
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +0 -315
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +0 -493
- package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +0 -201
- package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +0 -533
- package/.next/standalone/crates/fpai-collect/src/spool.rs +0 -458
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +0 -387
- package/.next/standalone/crates/fpai-collect/src/supervisor.rs +0 -423
- package/.next/standalone/crates/fpai-collect/src/uploader.rs +0 -600
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +0 -738
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +0 -393
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +0 -975
- package/.next/standalone/crates/fpai-collect/tests/config.rs +0 -286
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +0 -1019
- package/.next/standalone/crates/fpai-collect/tests/delivery.rs +0 -281
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +0 -1008
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +0 -1129
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +0 -621
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +0 -854
- package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +0 -1478
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +0 -1191
- package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +0 -290
- package/.next/standalone/crates/fpai-collect/tests/uploader.rs +0 -506
- package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
- package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
- package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
- package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
- package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
- package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
- package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
- package/.next/standalone/integration-suite/Dockerfile +0 -47
- package/.next/standalone/integration-suite/README.md +0 -129
- package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
- package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
- package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
- package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
- package/.next/standalone/integration-suite/install-clis.sh +0 -136
- package/.next/standalone/integration-suite/probe-cli.sh +0 -248
- package/.next/standalone/integration-suite/report.js +0 -120
- package/.next/standalone/integration-suite/run.sh +0 -159
- package/.next/standalone/openclaw-plugin/index.js +0 -223
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
- package/.next/standalone/openclaw-plugin/package.json +0 -12
- package/.next/standalone/osv-scanner.toml +0 -17
- package/.next/standalone/pi-extension/index.ts +0 -524
- package/.next/standalone/pi-extension/package.json +0 -12
- package/.next/standalone/postcss.config.mjs +0 -8
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/rust-toolchain.toml +0 -4
- package/src/auth/cli.ts +0 -371
- /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → Ptn8aBQl_cnNgOd0ebyv0}/_ssgManifest.js +0 -0
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
//! Uploader behaviour against a real HTTP server.
|
|
2
|
-
//!
|
|
3
|
-
//! The unit tests in the module cover the pure parts (filename retry state,
|
|
4
|
-
//! line splitting). These cover the parts that only show up over the wire, and
|
|
5
|
-
//! each one guards a way of losing data silently rather than loudly.
|
|
6
|
-
|
|
7
|
-
use std::fs;
|
|
8
|
-
use std::path::{Path, PathBuf};
|
|
9
|
-
use std::sync::atomic::Ordering;
|
|
10
|
-
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
11
|
-
|
|
12
|
-
use fpai_collect::Uploader;
|
|
13
|
-
use wiremock::matchers::{header, method, path};
|
|
14
|
-
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
15
|
-
|
|
16
|
-
fn tmpdir(name: &str) -> PathBuf {
|
|
17
|
-
let d = std::env::temp_dir().join(format!(
|
|
18
|
-
"fpai-up-{}-{}-{}",
|
|
19
|
-
name,
|
|
20
|
-
std::process::id(),
|
|
21
|
-
SystemTime::now()
|
|
22
|
-
.duration_since(UNIX_EPOCH)
|
|
23
|
-
.unwrap()
|
|
24
|
-
.as_nanos()
|
|
25
|
-
));
|
|
26
|
-
fs::create_dir_all(&d).unwrap();
|
|
27
|
-
d
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
fn write_batch(dir: &Path, name: &str, lines: usize) -> PathBuf {
|
|
31
|
-
let p = dir.join(name);
|
|
32
|
-
let body: String = (0..lines)
|
|
33
|
-
.map(|i| format!("{{\"type\":\"tool_use\",\"n\":{i}}}\n"))
|
|
34
|
-
.collect();
|
|
35
|
-
fs::write(&p, body).unwrap();
|
|
36
|
-
p
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// Fast retries so a test does not wait out a real backoff.
|
|
40
|
-
fn uploader(server: &MockServer, failed: &Path) -> Uploader {
|
|
41
|
-
Uploader::new(
|
|
42
|
-
format!("{}/events", server.uri()),
|
|
43
|
-
"test-key".into(),
|
|
44
|
-
failed.to_path_buf(),
|
|
45
|
-
)
|
|
46
|
-
.unwrap()
|
|
47
|
-
.with_retry_base(Duration::from_millis(1))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fn parked(dir: &Path) -> Vec<String> {
|
|
51
|
-
let mut v: Vec<String> = fs::read_dir(dir)
|
|
52
|
-
.map(|rd| {
|
|
53
|
-
rd.filter_map(|e| e.ok())
|
|
54
|
-
.map(|e| e.file_name().to_string_lossy().to_string())
|
|
55
|
-
.collect()
|
|
56
|
-
})
|
|
57
|
-
.unwrap_or_default();
|
|
58
|
-
v.sort();
|
|
59
|
-
v
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#[tokio::test]
|
|
63
|
-
async fn a_2xx_with_an_accepting_ack_deletes_the_batch() {
|
|
64
|
-
let server = MockServer::start().await;
|
|
65
|
-
Mock::given(method("POST"))
|
|
66
|
-
.and(path("/events"))
|
|
67
|
-
.and(header("content-type", "application/x-ndjson"))
|
|
68
|
-
.and(header("authorization", "Bearer test-key"))
|
|
69
|
-
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
70
|
-
"accepted": 3, "skipped": 0
|
|
71
|
-
})))
|
|
72
|
-
.mount(&server)
|
|
73
|
-
.await;
|
|
74
|
-
|
|
75
|
-
let spool = tmpdir("ok-spool");
|
|
76
|
-
let failed = tmpdir("ok-failed");
|
|
77
|
-
let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
|
|
78
|
-
|
|
79
|
-
let up = uploader(&server, &failed);
|
|
80
|
-
up.upload_file(&batch).await.unwrap();
|
|
81
|
-
|
|
82
|
-
assert!(!batch.exists(), "a delivered batch must be deleted");
|
|
83
|
-
assert!(parked(&failed).is_empty());
|
|
84
|
-
assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 3);
|
|
85
|
-
assert!(up.metrics().last_ok_ts.load(Ordering::Relaxed) > 0);
|
|
86
|
-
|
|
87
|
-
fs::remove_dir_all(&spool).ok();
|
|
88
|
-
fs::remove_dir_all(&failed).ok();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#[tokio::test]
|
|
92
|
-
async fn a_200_that_stored_nothing_is_counted_as_fully_skipped() {
|
|
93
|
-
// The failure this exists for: a systematically malformed transform gets
|
|
94
|
-
// HTTP 200 for every batch while the server stores none of it. Without
|
|
95
|
-
// reading the ack body that is indistinguishable from working perfectly.
|
|
96
|
-
let server = MockServer::start().await;
|
|
97
|
-
Mock::given(method("POST"))
|
|
98
|
-
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
99
|
-
"accepted": 0, "skipped": 5
|
|
100
|
-
})))
|
|
101
|
-
.mount(&server)
|
|
102
|
-
.await;
|
|
103
|
-
|
|
104
|
-
let spool = tmpdir("skip-spool");
|
|
105
|
-
let failed = tmpdir("skip-failed");
|
|
106
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 5);
|
|
107
|
-
|
|
108
|
-
let up = uploader(&server, &failed);
|
|
109
|
-
up.upload_file(&batch).await.unwrap();
|
|
110
|
-
|
|
111
|
-
let m = up.metrics();
|
|
112
|
-
assert_eq!(m.batches_fully_skipped.load(Ordering::Relaxed), 1);
|
|
113
|
-
assert_eq!(m.skipped_total.load(Ordering::Relaxed), 5);
|
|
114
|
-
assert_eq!(m.accepted_total.load(Ordering::Relaxed), 0);
|
|
115
|
-
|
|
116
|
-
fs::remove_dir_all(&spool).ok();
|
|
117
|
-
fs::remove_dir_all(&failed).ok();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#[tokio::test]
|
|
121
|
-
async fn a_4xx_parks_immediately_with_its_status_and_is_not_retried() {
|
|
122
|
-
let server = MockServer::start().await;
|
|
123
|
-
Mock::given(method("POST"))
|
|
124
|
-
.respond_with(ResponseTemplate::new(401))
|
|
125
|
-
.expect(1) // exactly one attempt: retrying a rotated key is pointless
|
|
126
|
-
.mount(&server)
|
|
127
|
-
.await;
|
|
128
|
-
|
|
129
|
-
let spool = tmpdir("4xx-spool");
|
|
130
|
-
let failed = tmpdir("4xx-failed");
|
|
131
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 2);
|
|
132
|
-
|
|
133
|
-
let up = uploader(&server, &failed);
|
|
134
|
-
let err = up.upload_file(&batch).await.unwrap_err();
|
|
135
|
-
assert!(format!("{err}").contains("401"));
|
|
136
|
-
|
|
137
|
-
let files = parked(&failed);
|
|
138
|
-
assert_eq!(files.len(), 1, "got {files:?}");
|
|
139
|
-
assert!(
|
|
140
|
-
files[0].contains(".c401"),
|
|
141
|
-
"the status must be recorded so an automatic retry pass skips it: {files:?}"
|
|
142
|
-
);
|
|
143
|
-
assert!(!batch.exists(), "the batch moved rather than being copied");
|
|
144
|
-
|
|
145
|
-
fs::remove_dir_all(&spool).ok();
|
|
146
|
-
fs::remove_dir_all(&failed).ok();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#[tokio::test]
|
|
150
|
-
async fn a_429_is_retried_rather_than_treated_as_definitive() {
|
|
151
|
-
// 408 and 429 are the two 4xx that mean "try again". Parking them would
|
|
152
|
-
// strand a batch the server explicitly asked us to resend.
|
|
153
|
-
let server = MockServer::start().await;
|
|
154
|
-
Mock::given(method("POST"))
|
|
155
|
-
.respond_with(ResponseTemplate::new(429).insert_header("retry-after", "0"))
|
|
156
|
-
.expect(5) // max_retries, not 1
|
|
157
|
-
.mount(&server)
|
|
158
|
-
.await;
|
|
159
|
-
|
|
160
|
-
let spool = tmpdir("429-spool");
|
|
161
|
-
let failed = tmpdir("429-failed");
|
|
162
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
|
|
163
|
-
|
|
164
|
-
let up = uploader(&server, &failed);
|
|
165
|
-
let err = up.upload_file(&batch).await.unwrap_err();
|
|
166
|
-
assert!(format!("{err}").contains("429"));
|
|
167
|
-
|
|
168
|
-
// Parked WITHOUT a client status, so it stays auto-retryable.
|
|
169
|
-
let files = parked(&failed);
|
|
170
|
-
assert_eq!(files.len(), 1, "got {files:?}");
|
|
171
|
-
assert!(
|
|
172
|
-
!files[0].contains(".c429"),
|
|
173
|
-
"a retryable status must not be recorded as definitive: {files:?}"
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
fs::remove_dir_all(&spool).ok();
|
|
177
|
-
fs::remove_dir_all(&failed).ok();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
#[tokio::test]
|
|
181
|
-
async fn a_5xx_retries_then_parks_without_a_client_status() {
|
|
182
|
-
let server = MockServer::start().await;
|
|
183
|
-
Mock::given(method("POST"))
|
|
184
|
-
.respond_with(ResponseTemplate::new(503))
|
|
185
|
-
.expect(5)
|
|
186
|
-
.mount(&server)
|
|
187
|
-
.await;
|
|
188
|
-
|
|
189
|
-
let spool = tmpdir("5xx-spool");
|
|
190
|
-
let failed = tmpdir("5xx-failed");
|
|
191
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
|
|
192
|
-
|
|
193
|
-
let up = uploader(&server, &failed);
|
|
194
|
-
let err = up.upload_file(&batch).await.unwrap_err();
|
|
195
|
-
assert!(format!("{err}").contains("503"));
|
|
196
|
-
|
|
197
|
-
let files = parked(&failed);
|
|
198
|
-
assert_eq!(files.len(), 1);
|
|
199
|
-
assert!(files[0].contains(".a1"), "attempt count encoded: {files:?}");
|
|
200
|
-
assert!(
|
|
201
|
-
!files[0].ends_with(".poison"),
|
|
202
|
-
"one failure is not poison yet"
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
fs::remove_dir_all(&spool).ok();
|
|
206
|
-
fs::remove_dir_all(&failed).ok();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
#[tokio::test]
|
|
210
|
-
async fn a_transient_failure_that_recovers_still_delivers() {
|
|
211
|
-
let server = MockServer::start().await;
|
|
212
|
-
// Fail twice, then succeed. Mounted newest-first by wiremock, so the
|
|
213
|
-
// limited mock is consulted before the fallback.
|
|
214
|
-
Mock::given(method("POST"))
|
|
215
|
-
.respond_with(ResponseTemplate::new(500))
|
|
216
|
-
.up_to_n_times(2)
|
|
217
|
-
.mount(&server)
|
|
218
|
-
.await;
|
|
219
|
-
Mock::given(method("POST"))
|
|
220
|
-
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
221
|
-
"accepted": 1, "skipped": 0
|
|
222
|
-
})))
|
|
223
|
-
.mount(&server)
|
|
224
|
-
.await;
|
|
225
|
-
|
|
226
|
-
let spool = tmpdir("flap-spool");
|
|
227
|
-
let failed = tmpdir("flap-failed");
|
|
228
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 1);
|
|
229
|
-
|
|
230
|
-
let up = uploader(&server, &failed);
|
|
231
|
-
up.upload_file(&batch).await.unwrap();
|
|
232
|
-
|
|
233
|
-
assert!(!batch.exists());
|
|
234
|
-
assert!(
|
|
235
|
-
parked(&failed).is_empty(),
|
|
236
|
-
"a recovered batch must not be parked"
|
|
237
|
-
);
|
|
238
|
-
assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 1);
|
|
239
|
-
|
|
240
|
-
fs::remove_dir_all(&spool).ok();
|
|
241
|
-
fs::remove_dir_all(&failed).ok();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
#[tokio::test]
|
|
245
|
-
async fn an_oversized_batch_is_split_and_every_chunk_delivered() {
|
|
246
|
-
let server = MockServer::start().await;
|
|
247
|
-
Mock::given(method("POST"))
|
|
248
|
-
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
249
|
-
"accepted": 1, "skipped": 0
|
|
250
|
-
})))
|
|
251
|
-
.mount(&server)
|
|
252
|
-
.await;
|
|
253
|
-
|
|
254
|
-
let spool = tmpdir("split-spool");
|
|
255
|
-
let failed = tmpdir("split-failed");
|
|
256
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", 50);
|
|
257
|
-
|
|
258
|
-
// Cap well under the file size, forcing several requests.
|
|
259
|
-
let up = uploader(&server, &failed).with_max_upload_bytes(64);
|
|
260
|
-
up.upload_file(&batch).await.unwrap();
|
|
261
|
-
|
|
262
|
-
let requests = server.received_requests().await.unwrap();
|
|
263
|
-
assert!(
|
|
264
|
-
requests.len() > 1,
|
|
265
|
-
"expected a split, got {} request(s)",
|
|
266
|
-
requests.len()
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
// Splitting happens IN MEMORY. Chunks written to disk beside the original
|
|
270
|
-
// would be files the watcher had never seen, so it would post them
|
|
271
|
-
// concurrently — the same payload delivered twice.
|
|
272
|
-
let leftovers: Vec<String> = fs::read_dir(&spool)
|
|
273
|
-
.unwrap()
|
|
274
|
-
.filter_map(|e| e.ok())
|
|
275
|
-
.map(|e| e.file_name().to_string_lossy().to_string())
|
|
276
|
-
.collect();
|
|
277
|
-
assert!(
|
|
278
|
-
leftovers.is_empty(),
|
|
279
|
-
"split chunks must never hit the spool: {leftovers:?}"
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
fs::remove_dir_all(&spool).ok();
|
|
283
|
-
fs::remove_dir_all(&failed).ok();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
#[tokio::test]
|
|
287
|
-
async fn a_vanished_batch_is_not_an_error() {
|
|
288
|
-
// Two tasks can race for the same file; whoever loses must not report a
|
|
289
|
-
// failure for work the other already did.
|
|
290
|
-
let server = MockServer::start().await;
|
|
291
|
-
let spool = tmpdir("gone-spool");
|
|
292
|
-
let failed = tmpdir("gone-failed");
|
|
293
|
-
let up = uploader(&server, &failed);
|
|
294
|
-
|
|
295
|
-
up.upload_file(&spool.join("never-existed.jsonl"))
|
|
296
|
-
.await
|
|
297
|
-
.unwrap();
|
|
298
|
-
|
|
299
|
-
fs::remove_dir_all(&spool).ok();
|
|
300
|
-
fs::remove_dir_all(&failed).ok();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
#[tokio::test]
|
|
304
|
-
async fn parking_never_overwrites_an_existing_parked_batch() {
|
|
305
|
-
// A parked batch is the last copy of data the server does not have, so a
|
|
306
|
-
// name collision must not destroy one.
|
|
307
|
-
let server = MockServer::start().await;
|
|
308
|
-
Mock::given(method("POST"))
|
|
309
|
-
.respond_with(ResponseTemplate::new(401))
|
|
310
|
-
.mount(&server)
|
|
311
|
-
.await;
|
|
312
|
-
|
|
313
|
-
let spool = tmpdir("collide-spool");
|
|
314
|
-
let failed = tmpdir("collide-failed");
|
|
315
|
-
let up = uploader(&server, &failed);
|
|
316
|
-
|
|
317
|
-
for i in 0..2 {
|
|
318
|
-
let batch = write_batch(&spool, "claude-s-1-0.jsonl", i + 1);
|
|
319
|
-
let _ = up.upload_file(&batch).await;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
let files = parked(&failed);
|
|
323
|
-
assert_eq!(
|
|
324
|
-
files.len(),
|
|
325
|
-
2,
|
|
326
|
-
"both parked batches must survive: {files:?}"
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
fs::remove_dir_all(&spool).ok();
|
|
330
|
-
fs::remove_dir_all(&failed).ok();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
#[tokio::test]
|
|
334
|
-
async fn a_batch_written_by_the_spool_writer_is_delivered_end_to_end() {
|
|
335
|
-
// The two halves have to agree on the filename shape and the NDJSON body.
|
|
336
|
-
// Testing them separately would let a change to either drift silently.
|
|
337
|
-
let server = MockServer::start().await;
|
|
338
|
-
Mock::given(method("POST"))
|
|
339
|
-
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
340
|
-
"accepted": 2, "skipped": 0
|
|
341
|
-
})))
|
|
342
|
-
.mount(&server)
|
|
343
|
-
.await;
|
|
344
|
-
|
|
345
|
-
let spool = tmpdir("e2e-spool");
|
|
346
|
-
let failed = tmpdir("e2e-failed");
|
|
347
|
-
|
|
348
|
-
let mut w = fpai_collect::SpoolWriter::new(
|
|
349
|
-
spool.clone(),
|
|
350
|
-
fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
|
|
351
|
-
"hooks",
|
|
352
|
-
"18efefd6",
|
|
353
|
-
);
|
|
354
|
-
w.push(serde_json::json!({
|
|
355
|
-
"timestamp": "2026-08-03T10:00:00.000000Z", "session_id": "s1",
|
|
356
|
-
"agent_id": "claude", "type": "hook_triggered", "hook_name": "PreToolUse"
|
|
357
|
-
}))
|
|
358
|
-
.await
|
|
359
|
-
.unwrap();
|
|
360
|
-
w.push(serde_json::json!({
|
|
361
|
-
"timestamp": "2026-08-03T10:00:00.000100Z", "session_id": "s1",
|
|
362
|
-
"agent_id": "claude", "type": "hook_completed", "hook_name": "PreToolUse"
|
|
363
|
-
}))
|
|
364
|
-
.await
|
|
365
|
-
.unwrap();
|
|
366
|
-
w.flush().await.unwrap();
|
|
367
|
-
|
|
368
|
-
let batch = fs::read_dir(&spool)
|
|
369
|
-
.unwrap()
|
|
370
|
-
.filter_map(|e| e.ok())
|
|
371
|
-
.map(|e| e.path())
|
|
372
|
-
.find(|p| fpai_collect::spool::is_batch_file(p))
|
|
373
|
-
.expect("the spool writer should have produced a batch");
|
|
374
|
-
|
|
375
|
-
let up = uploader(&server, &failed);
|
|
376
|
-
up.upload_file(&batch).await.unwrap();
|
|
377
|
-
|
|
378
|
-
assert!(!batch.exists());
|
|
379
|
-
assert!(parked(&failed).is_empty());
|
|
380
|
-
assert_eq!(up.metrics().accepted_total.load(Ordering::Relaxed), 2);
|
|
381
|
-
|
|
382
|
-
// The body the server received is exactly the NDJSON the spool wrote.
|
|
383
|
-
let reqs = server.received_requests().await.unwrap();
|
|
384
|
-
assert_eq!(reqs.len(), 1);
|
|
385
|
-
let body = String::from_utf8(reqs[0].body.clone()).unwrap();
|
|
386
|
-
assert_eq!(body.lines().count(), 2);
|
|
387
|
-
assert!(body.contains("hook_triggered") && body.contains("hook_completed"));
|
|
388
|
-
|
|
389
|
-
fs::remove_dir_all(&spool).ok();
|
|
390
|
-
fs::remove_dir_all(&failed).ok();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// ---------------------------------------------------------------------------
|
|
394
|
-
// Redirect / non-ack hardening
|
|
395
|
-
//
|
|
396
|
-
// An adversarial red-team pointed a live daemon at a dashboard-lookalike
|
|
397
|
-
// (POST /events -> 307 -> /login -> 200 text/html). reqwest followed the
|
|
398
|
-
// redirect, re-POSTed the batch to the login page, parsed the HTML as a
|
|
399
|
-
// default ack, and DELETED the spool file. Cross-host, the same follow shipped
|
|
400
|
-
// full event payloads to an attacker. These pin the fix.
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
#[tokio::test]
|
|
404
|
-
async fn a_redirect_is_parked_not_followed() {
|
|
405
|
-
// The exfiltration vector: a 3xx must never be followed, or the batch (and
|
|
406
|
-
// its prompts/command text) is re-POSTed to wherever Location points.
|
|
407
|
-
let server = MockServer::start().await;
|
|
408
|
-
Mock::given(method("POST"))
|
|
409
|
-
.and(path("/events"))
|
|
410
|
-
.respond_with(ResponseTemplate::new(307).insert_header("location", "/login?next=%2Fevents"))
|
|
411
|
-
.expect(1) // exactly one POST — the redirect is NOT followed to /login
|
|
412
|
-
.mount(&server)
|
|
413
|
-
.await;
|
|
414
|
-
// If the client followed the redirect it would hit this; it must not.
|
|
415
|
-
Mock::given(method("POST"))
|
|
416
|
-
.and(path("/login"))
|
|
417
|
-
.respond_with(ResponseTemplate::new(200).set_body_string("<html>login</html>"))
|
|
418
|
-
.expect(0)
|
|
419
|
-
.mount(&server)
|
|
420
|
-
.await;
|
|
421
|
-
|
|
422
|
-
let spool = tmpdir("redir-spool");
|
|
423
|
-
let failed = tmpdir("redir-failed");
|
|
424
|
-
let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
|
|
425
|
-
|
|
426
|
-
let up = uploader(&server, &failed);
|
|
427
|
-
let err = up.upload_file(&batch).await.unwrap_err();
|
|
428
|
-
assert!(format!("{err}").contains("307"), "got {err}");
|
|
429
|
-
|
|
430
|
-
let files = parked(&failed);
|
|
431
|
-
assert_eq!(
|
|
432
|
-
files.len(),
|
|
433
|
-
1,
|
|
434
|
-
"the batch must be parked, not lost: {files:?}"
|
|
435
|
-
);
|
|
436
|
-
assert!(
|
|
437
|
-
files[0].contains(".c307"),
|
|
438
|
-
"the redirect status must be recorded: {files:?}"
|
|
439
|
-
);
|
|
440
|
-
assert!(
|
|
441
|
-
!batch.exists(),
|
|
442
|
-
"the batch moved to failed/, it was not deleted as delivered"
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
fs::remove_dir_all(&spool).ok();
|
|
446
|
-
fs::remove_dir_all(&failed).ok();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
#[tokio::test]
|
|
450
|
-
async fn a_200_that_is_not_an_ingest_ack_is_parked_not_deleted() {
|
|
451
|
-
// A login page, a proxy or a static host can answer 200. Without a numeric
|
|
452
|
-
// `accepted` it is not delivery, and treating it as such deletes the only
|
|
453
|
-
// copy of the data.
|
|
454
|
-
let server = MockServer::start().await;
|
|
455
|
-
Mock::given(method("POST"))
|
|
456
|
-
.respond_with(ResponseTemplate::new(200).set_body_string("<html>hello</html>"))
|
|
457
|
-
.expect(1)
|
|
458
|
-
.mount(&server)
|
|
459
|
-
.await;
|
|
460
|
-
|
|
461
|
-
let spool = tmpdir("noack-spool");
|
|
462
|
-
let failed = tmpdir("noack-failed");
|
|
463
|
-
let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 3);
|
|
464
|
-
|
|
465
|
-
let up = uploader(&server, &failed);
|
|
466
|
-
let err = up.upload_file(&batch).await.unwrap_err();
|
|
467
|
-
assert!(format!("{err}").contains("200"), "got {err}");
|
|
468
|
-
|
|
469
|
-
let files = parked(&failed);
|
|
470
|
-
assert_eq!(files.len(), 1, "a non-ack 200 must be parked: {files:?}");
|
|
471
|
-
assert!(
|
|
472
|
-
!batch.exists(),
|
|
473
|
-
"the batch moved to failed/ rather than vanishing"
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
fs::remove_dir_all(&spool).ok();
|
|
477
|
-
fs::remove_dir_all(&failed).ok();
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
#[tokio::test]
|
|
481
|
-
async fn a_200_whose_json_lacks_accepted_is_parked() {
|
|
482
|
-
// `{"status":"ok"}` parses as JSON but is not an ingest ack. The required
|
|
483
|
-
// `accepted` field is what rejects it.
|
|
484
|
-
let server = MockServer::start().await;
|
|
485
|
-
Mock::given(method("POST"))
|
|
486
|
-
.respond_with(
|
|
487
|
-
ResponseTemplate::new(200)
|
|
488
|
-
.insert_header("content-type", "application/json")
|
|
489
|
-
.set_body_string(r#"{"status":"ok"}"#),
|
|
490
|
-
)
|
|
491
|
-
.expect(1)
|
|
492
|
-
.mount(&server)
|
|
493
|
-
.await;
|
|
494
|
-
|
|
495
|
-
let spool = tmpdir("badack-spool");
|
|
496
|
-
let failed = tmpdir("badack-failed");
|
|
497
|
-
let batch = write_batch(&spool, "hooks-s-1-0.jsonl", 2);
|
|
498
|
-
|
|
499
|
-
let up = uploader(&server, &failed);
|
|
500
|
-
assert!(up.upload_file(&batch).await.is_err());
|
|
501
|
-
assert_eq!(parked(&failed).len(), 1, "must be parked");
|
|
502
|
-
assert!(!batch.exists());
|
|
503
|
-
|
|
504
|
-
fs::remove_dir_all(&spool).ok();
|
|
505
|
-
fs::remove_dir_all(&failed).ok();
|
|
506
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "fpai-ipc"
|
|
3
|
-
version.workspace = true
|
|
4
|
-
edition.workspace = true
|
|
5
|
-
license-file.workspace = true
|
|
6
|
-
repository.workspace = true
|
|
7
|
-
description = "Wire protocol (framing + envelope + peer verification) shared by failproofaid and its tests"
|
|
8
|
-
publish = false
|
|
9
|
-
|
|
10
|
-
[dependencies]
|
|
11
|
-
serde = { version = "1", features = ["derive"] }
|
|
12
|
-
serde_json = "1"
|
|
13
|
-
libc = "0.2"
|
|
14
|
-
|
|
15
|
-
[dev-dependencies]
|
|
16
|
-
proptest = "1"
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
//! The JSON envelope carried inside each [`crate::framing`] frame.
|
|
2
|
-
//!
|
|
3
|
-
//! This mirrors the `(argv --hook <Event> --cli <cli>, stdin JSON payload)
|
|
4
|
-
//! -> (stdout, stderr, exitCode)` contract every hook invocation already
|
|
5
|
-
//! has today — see `src/hooks/handler.ts`'s `handleHookEvent`. The daemon
|
|
6
|
-
//! adds nothing to that contract; it only relays it over a socket instead
|
|
7
|
-
//! of a fresh process's argv/stdin/stdout.
|
|
8
|
-
|
|
9
|
-
use serde::{Deserialize, Serialize};
|
|
10
|
-
|
|
11
|
-
/// Bumped whenever a wire-incompatible change is made to either message
|
|
12
|
-
/// enum below. The client treats any mismatch identically to "daemon
|
|
13
|
-
/// unreachable" (see `docs/configuration.mdx` and the failproofaid plan) —
|
|
14
|
-
/// there is no negotiation, only agree-or-fall-back.
|
|
15
|
-
pub const PROTOCOL_VERSION: u32 = 1;
|
|
16
|
-
|
|
17
|
-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
18
|
-
#[serde(tag = "type", rename_all = "camelCase")]
|
|
19
|
-
pub enum ClientMessage {
|
|
20
|
-
/// Cheap liveness/handshake check — used by tests and by a client that
|
|
21
|
-
/// only wants to confirm a compatible daemon is listening.
|
|
22
|
-
#[serde(rename_all = "camelCase")]
|
|
23
|
-
Ping { protocol_version: u32 },
|
|
24
|
-
/// One hook evaluation request, corresponding 1:1 to one
|
|
25
|
-
/// `failproofai --hook <Event> --cli <cli>` invocation.
|
|
26
|
-
#[serde(rename_all = "camelCase")]
|
|
27
|
-
Hook {
|
|
28
|
-
protocol_version: u32,
|
|
29
|
-
hook_event: String,
|
|
30
|
-
cli: String,
|
|
31
|
-
/// The raw stdin payload the calling agent CLI wrote to the
|
|
32
|
-
/// one-shot `failproofai` process — forwarded verbatim.
|
|
33
|
-
stdin: String,
|
|
34
|
-
/// Best-effort cwd of the *originating* CLI process, not the
|
|
35
|
-
/// daemon's own cwd (the daemon is a single long-lived process; its
|
|
36
|
-
/// own `cwd` does not vary per request and must never be used to
|
|
37
|
-
/// resolve project config or custom policies).
|
|
38
|
-
cwd: Option<String>,
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
43
|
-
#[serde(tag = "type", rename_all = "camelCase")]
|
|
44
|
-
pub enum ServerMessage {
|
|
45
|
-
#[serde(rename_all = "camelCase")]
|
|
46
|
-
Pong { protocol_version: u32 },
|
|
47
|
-
#[serde(rename_all = "camelCase")]
|
|
48
|
-
HookResult {
|
|
49
|
-
protocol_version: u32,
|
|
50
|
-
exit_code: i32,
|
|
51
|
-
stdout: String,
|
|
52
|
-
stderr: String,
|
|
53
|
-
},
|
|
54
|
-
/// The daemon accepted the connection and parsed the request, but could
|
|
55
|
-
/// not produce a verdict (e.g. the worker is down/hung). Distinct from
|
|
56
|
-
/// `HookResult` so a client can tell "ran and decided" apart from
|
|
57
|
-
/// "daemon couldn't evaluate at all" — the latter is what drives the
|
|
58
|
-
/// client's fail-closed path.
|
|
59
|
-
#[serde(rename_all = "camelCase")]
|
|
60
|
-
Error {
|
|
61
|
-
protocol_version: u32,
|
|
62
|
-
message: String,
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
impl ClientMessage {
|
|
67
|
-
pub fn protocol_version(&self) -> u32 {
|
|
68
|
-
match self {
|
|
69
|
-
ClientMessage::Ping { protocol_version } => *protocol_version,
|
|
70
|
-
ClientMessage::Hook {
|
|
71
|
-
protocol_version, ..
|
|
72
|
-
} => *protocol_version,
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
impl ServerMessage {
|
|
78
|
-
pub fn protocol_version(&self) -> u32 {
|
|
79
|
-
match self {
|
|
80
|
-
ServerMessage::Pong { protocol_version } => *protocol_version,
|
|
81
|
-
ServerMessage::HookResult {
|
|
82
|
-
protocol_version, ..
|
|
83
|
-
} => *protocol_version,
|
|
84
|
-
ServerMessage::Error {
|
|
85
|
-
protocol_version, ..
|
|
86
|
-
} => *protocol_version,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#[cfg(test)]
|
|
92
|
-
mod tests {
|
|
93
|
-
use super::*;
|
|
94
|
-
|
|
95
|
-
#[test]
|
|
96
|
-
fn ping_serializes_with_a_type_tag() {
|
|
97
|
-
let msg = ClientMessage::Ping {
|
|
98
|
-
protocol_version: PROTOCOL_VERSION,
|
|
99
|
-
};
|
|
100
|
-
let json = serde_json::to_value(&msg).unwrap();
|
|
101
|
-
assert_eq!(json["type"], "ping");
|
|
102
|
-
assert_eq!(json["protocolVersion"], PROTOCOL_VERSION);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
#[test]
|
|
106
|
-
fn hook_request_uses_camel_case_field_names_on_the_wire() {
|
|
107
|
-
let msg = ClientMessage::Hook {
|
|
108
|
-
protocol_version: PROTOCOL_VERSION,
|
|
109
|
-
hook_event: "PreToolUse".to_string(),
|
|
110
|
-
cli: "claude".to_string(),
|
|
111
|
-
stdin: "{}".to_string(),
|
|
112
|
-
cwd: Some("/repo".to_string()),
|
|
113
|
-
};
|
|
114
|
-
let json = serde_json::to_value(&msg).unwrap();
|
|
115
|
-
assert_eq!(json["type"], "hook");
|
|
116
|
-
assert_eq!(json["hookEvent"], "PreToolUse");
|
|
117
|
-
assert_eq!(json["cli"], "claude");
|
|
118
|
-
assert_eq!(json["cwd"], "/repo");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
#[test]
|
|
122
|
-
fn hook_request_cwd_is_optional() {
|
|
123
|
-
let json = serde_json::json!({
|
|
124
|
-
"type": "hook",
|
|
125
|
-
"protocolVersion": 1,
|
|
126
|
-
"hookEvent": "Stop",
|
|
127
|
-
"cli": "codex",
|
|
128
|
-
"stdin": "{}"
|
|
129
|
-
});
|
|
130
|
-
let msg: ClientMessage = serde_json::from_value(json).unwrap();
|
|
131
|
-
match msg {
|
|
132
|
-
ClientMessage::Hook { cwd, .. } => assert_eq!(cwd, None),
|
|
133
|
-
_ => panic!("expected Hook variant"),
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
#[test]
|
|
138
|
-
fn unknown_message_type_fails_to_deserialize() {
|
|
139
|
-
let json = serde_json::json!({ "type": "bogus", "protocolVersion": 1 });
|
|
140
|
-
let result: Result<ClientMessage, _> = serde_json::from_value(json);
|
|
141
|
-
assert!(result.is_err());
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
#[test]
|
|
145
|
-
fn server_hook_result_round_trips() {
|
|
146
|
-
let msg = ServerMessage::HookResult {
|
|
147
|
-
protocol_version: PROTOCOL_VERSION,
|
|
148
|
-
exit_code: 2,
|
|
149
|
-
stdout: String::new(),
|
|
150
|
-
stderr: "blocked".to_string(),
|
|
151
|
-
};
|
|
152
|
-
let json = serde_json::to_string(&msg).unwrap();
|
|
153
|
-
let decoded: ServerMessage = serde_json::from_str(&json).unwrap();
|
|
154
|
-
assert_eq!(decoded, msg);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
#[test]
|
|
158
|
-
fn protocol_version_accessor_matches_every_variant() {
|
|
159
|
-
assert_eq!(
|
|
160
|
-
ClientMessage::Ping {
|
|
161
|
-
protocol_version: 7
|
|
162
|
-
}
|
|
163
|
-
.protocol_version(),
|
|
164
|
-
7
|
|
165
|
-
);
|
|
166
|
-
assert_eq!(
|
|
167
|
-
ServerMessage::Error {
|
|
168
|
-
protocol_version: 9,
|
|
169
|
-
message: "x".to_string()
|
|
170
|
-
}
|
|
171
|
-
.protocol_version(),
|
|
172
|
-
9
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|