failproofai 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6 -6
- 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/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 +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/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 +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 +3 -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/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 +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/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 +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/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +5 -3
- 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 -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/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 +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/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 +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]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.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]__08y94xv._.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]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.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]__0vb5fl2._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.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]__20d06rh._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
- 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]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.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]__1tlvv9a._.js +3 -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/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- 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 +4 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- 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 +74 -20
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/SECURITY.md +8 -2
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
- 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/audit-dashboard.tsx +1 -3
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +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/_components/come-back-better-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
- package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
- package/.next/standalone/app/components/toast.tsx +1 -1
- package/.next/standalone/app/policies/hooks-client.tsx +84 -8
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +487 -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 -5
- 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 +23 -12
- package/.next/standalone/proxy.ts +111 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai-worker.mjs +1 -1
- package/bin/failproofai.mjs +1041 -81
- package/bin/failproofaid-shim.mjs +4 -4
- package/dist/cli.mjs +18513 -10474
- package/dist/worker.mjs +1223 -299
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -5
- 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 +23 -12
- package/scripts/build-daemon-packages.mjs +247 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/dev-hook.mjs +46 -5
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +27 -1
- package/scripts/prune-standalone.mjs +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -0
- package/scripts/translate-docs/cache.ts +1 -1
- package/scripts/translate-docs/cli.ts +2 -2
- package/scripts/translate-docs/mintlify-nav.ts +1 -1
- 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 -5
- 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 -3
- package/src/audit/report.ts +0 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +391 -3
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +439 -0
- package/src/hooks/cloud-enrollment.ts +284 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +206 -0
- package/src/hooks/collector-config.ts +218 -0
- package/src/hooks/configure-wizard.ts +1211 -233
- package/src/hooks/custom-hooks-loader.ts +243 -37
- package/src/hooks/daemon-client.ts +136 -33
- package/src/hooks/daemon-download.ts +264 -31
- package/src/hooks/daemon-service.ts +1124 -76
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/flush-cli.ts +180 -0
- package/src/hooks/fp-config.ts +835 -0
- package/src/hooks/fp-home.ts +714 -0
- package/src/hooks/fp-reset.ts +1363 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/harness-cli.ts +386 -0
- package/src/hooks/hook-activity-store.ts +171 -24
- package/src/hooks/hook-logger.ts +2 -2
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/integrations.ts +0 -1
- package/src/hooks/loader-utils.ts +172 -9
- package/src/hooks/manager.ts +41 -5
- package/src/hooks/migrations.ts +450 -0
- package/src/hooks/onboarding-attempt.ts +195 -0
- package/src/hooks/onboarding-lock.ts +118 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +267 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +209 -9
- package/src/hooks/uninstall-cli.ts +418 -0
- package/src/hooks/worker-server.ts +134 -15
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.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/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
- 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]__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]__0nb0syg._.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]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
- 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]__1ath6v_._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
- 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_0oy_-36.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/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- package/.next/standalone/Cargo.lock +0 -428
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
- 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-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/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
//! Spawns and supervises the warm Node/Bun worker process, and relays
|
|
2
|
-
//! `Hook` requests to it.
|
|
3
|
-
//!
|
|
4
|
-
//! The worker speaks a SEPARATE, simpler internal protocol on its own
|
|
5
|
-
//! socket (no `protocolVersion` — this process always spawns a
|
|
6
|
-
//! version-matched worker, so there's nothing to negotiate) — this module
|
|
7
|
-
//! is the only thing that talks to it, and it's the one place that
|
|
8
|
-
//! translates between that internal protocol and the client-facing
|
|
9
|
-
//! [`fpai_ipc::ServerMessage`] envelope (which DOES carry `protocolVersion`,
|
|
10
|
-
//! since that one crosses a real compatibility boundary against whatever
|
|
11
|
-
//! `failproofai` CLI version happens to be installed).
|
|
12
|
-
|
|
13
|
-
use fpai_ipc::framing::{read_message, write_message};
|
|
14
|
-
use serde_json::json;
|
|
15
|
-
use std::io;
|
|
16
|
-
use std::os::unix::net::UnixStream;
|
|
17
|
-
use std::os::unix::process::CommandExt;
|
|
18
|
-
use std::path::{Path, PathBuf};
|
|
19
|
-
use std::process::{Child, Command, Stdio};
|
|
20
|
-
use std::sync::Mutex;
|
|
21
|
-
use std::time::{Duration, Instant};
|
|
22
|
-
|
|
23
|
-
#[derive(Debug)]
|
|
24
|
-
pub enum WorkerError {
|
|
25
|
-
Io(io::Error),
|
|
26
|
-
/// The worker never created its socket within the startup deadline.
|
|
27
|
-
StartupTimedOut,
|
|
28
|
-
/// A response arrived but wasn't a well-formed hookResult/error.
|
|
29
|
-
BadResponse(String),
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
impl std::fmt::Display for WorkerError {
|
|
33
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
34
|
-
match self {
|
|
35
|
-
WorkerError::Io(e) => write!(f, "worker io error: {e}"),
|
|
36
|
-
WorkerError::StartupTimedOut => write!(f, "worker did not start in time"),
|
|
37
|
-
WorkerError::BadResponse(s) => write!(f, "unexpected worker response: {s}"),
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
impl std::error::Error for WorkerError {}
|
|
43
|
-
|
|
44
|
-
#[derive(Debug)]
|
|
45
|
-
pub struct HookOutcome {
|
|
46
|
-
pub exit_code: i32,
|
|
47
|
-
pub stdout: String,
|
|
48
|
-
pub stderr: String,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/// How to launch the worker process. `FAILPROOFAI_WORKER_CMD` (dev/test
|
|
52
|
-
/// override — a full shell command string, run via `sh -c`) always takes
|
|
53
|
-
/// precedence; otherwise falls back to `node <script>` for a production
|
|
54
|
-
/// install where `dist/worker.mjs` sits next to this binary.
|
|
55
|
-
///
|
|
56
|
-
/// Resolved explicitly by the caller (`from_env`, called once from
|
|
57
|
-
/// `main.rs`) rather than read from the environment inside `Worker` itself
|
|
58
|
-
/// — `std::env::var` is process-global, and Rust's test harness runs tests
|
|
59
|
-
/// in parallel by default, so multiple tests setting
|
|
60
|
-
/// `FAILPROOFAI_WORKER_CMD` to different values would race. Tests instead
|
|
61
|
-
/// construct a `WorkerCommand` directly via `shell`/`node`.
|
|
62
|
-
pub enum WorkerCommand {
|
|
63
|
-
Shell(String),
|
|
64
|
-
Node { script: PathBuf },
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
impl WorkerCommand {
|
|
68
|
-
pub fn from_env() -> Self {
|
|
69
|
-
if let Ok(cmd) = std::env::var("FAILPROOFAI_WORKER_CMD") {
|
|
70
|
-
return WorkerCommand::shell(cmd);
|
|
71
|
-
}
|
|
72
|
-
// Packaging (Stage 5) lands dist/worker.mjs; until then this is only
|
|
73
|
-
// reachable via the explicit override above in dev/test.
|
|
74
|
-
WorkerCommand::Node {
|
|
75
|
-
script: PathBuf::from("dist/worker.mjs"),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
pub fn shell(cmd: impl Into<String>) -> Self {
|
|
80
|
-
WorkerCommand::Shell(cmd.into())
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
fn spawn(&self, worker_socket: &Path) -> io::Result<Child> {
|
|
84
|
-
let mut command = match self {
|
|
85
|
-
WorkerCommand::Shell(shell_cmd) => {
|
|
86
|
-
let mut c = Command::new("sh");
|
|
87
|
-
c.arg("-c").arg(shell_cmd);
|
|
88
|
-
c
|
|
89
|
-
}
|
|
90
|
-
WorkerCommand::Node { script } => {
|
|
91
|
-
let mut c = Command::new("node");
|
|
92
|
-
c.arg(script);
|
|
93
|
-
c
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
command
|
|
97
|
-
.env("FAILPROOFAI_WORKER_SOCKET", worker_socket)
|
|
98
|
-
.stdin(Stdio::null())
|
|
99
|
-
// Piped, not inherited: an inherited stdout/stderr hands the
|
|
100
|
-
// worker its own copy of this process's fds, and if this
|
|
101
|
-
// process's own stdout is itself part of a pipeline (a test
|
|
102
|
-
// harness, `cargo test | tail`, a shell capturing output), that
|
|
103
|
-
// pipe never sees EOF as long as the worker — however it exits
|
|
104
|
-
// — still holds a copy open. Piped fds belong to this process
|
|
105
|
-
// alone and close cleanly when the worker does. Both pipes are
|
|
106
|
-
// drained below: the worker outlives the daemon's whole
|
|
107
|
-
// lifetime running real policy code, so any `console.log` from
|
|
108
|
-
// a user's custom policy accumulates, and an undrained pipe
|
|
109
|
-
// would eventually fill its OS buffer and block the worker
|
|
110
|
-
// mid-write — every later hook call would then fail closed
|
|
111
|
-
// until the daemon restarted.
|
|
112
|
-
.stdout(Stdio::piped())
|
|
113
|
-
.stderr(Stdio::piped())
|
|
114
|
-
// New session + process group leader: `sh -c "<cmd>"` is not
|
|
115
|
-
// guaranteed to exec(2) into `<cmd>` in place (some shells fork
|
|
116
|
-
// instead), so killing only the tracked PID can leave a real
|
|
117
|
-
// grandchild worker process running, reparented and orphaned.
|
|
118
|
-
// Killing the whole group (see `kill_process_group`) reaches it
|
|
119
|
-
// either way.
|
|
120
|
-
.process_group(0);
|
|
121
|
-
let mut child = command.spawn()?;
|
|
122
|
-
if let Some(out) = child.stdout.take() {
|
|
123
|
-
std::thread::spawn(move || forward_worker_output("stdout", out));
|
|
124
|
-
}
|
|
125
|
-
if let Some(err) = child.stderr.take() {
|
|
126
|
-
std::thread::spawn(move || forward_worker_output("stderr", err));
|
|
127
|
-
}
|
|
128
|
-
Ok(child)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/// Drains one of the worker's output pipes for its whole life, relaying it
|
|
133
|
-
/// to the daemon's own stderr (which systemd/launchd already capture into
|
|
134
|
-
/// the service log). Ends on EOF when the worker exits.
|
|
135
|
-
fn forward_worker_output(label: &'static str, pipe: impl io::Read) {
|
|
136
|
-
use std::io::BufRead;
|
|
137
|
-
for line in io::BufReader::new(pipe).lines().map_while(Result::ok) {
|
|
138
|
-
eprintln!("[failproofaid] worker {label}: {line}");
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/// Kills the entire process group `child` leads (see `.process_group(0)` in
|
|
143
|
-
/// `WorkerCommand::spawn`), not just the single tracked PID — reaches a
|
|
144
|
-
/// `sh -c "<cmd>"` grandchild even when `sh` forked rather than exec'd.
|
|
145
|
-
fn kill_process_group(child: &mut Child) {
|
|
146
|
-
let pgid = child.id() as libc::pid_t;
|
|
147
|
-
unsafe {
|
|
148
|
-
libc::kill(-pgid, libc::SIGKILL);
|
|
149
|
-
}
|
|
150
|
-
let _ = child.wait();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
pub struct Worker {
|
|
154
|
-
socket_path: PathBuf,
|
|
155
|
-
cmd: WorkerCommand,
|
|
156
|
-
child: Mutex<Option<Child>>,
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
impl Worker {
|
|
160
|
-
pub fn new(socket_path: PathBuf, cmd: WorkerCommand) -> Self {
|
|
161
|
-
Worker {
|
|
162
|
-
socket_path,
|
|
163
|
-
cmd,
|
|
164
|
-
child: Mutex::new(None),
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/// Spawns the worker if it isn't already running (or has died since the
|
|
169
|
-
/// last check), then waits for it to actually accept connections —
|
|
170
|
-
/// failing fast (rather than waiting out the full startup deadline) if
|
|
171
|
-
/// the process exits before ever getting there, e.g. a broken
|
|
172
|
-
/// FAILPROOFAI_WORKER_CMD.
|
|
173
|
-
///
|
|
174
|
-
/// Readiness is a real `connect()`, not `socket_path.exists()`: a Unix
|
|
175
|
-
/// socket file outlives the process that bound it, so a dead worker
|
|
176
|
-
/// always leaves one behind. `exists()` would see the *previous*
|
|
177
|
-
/// worker's leftover file the instant this one is spawned, break out of
|
|
178
|
-
/// the wait immediately, and hand `call()` a socket nothing is
|
|
179
|
-
/// listening on — `ECONNREFUSED` on every request until the daemon
|
|
180
|
-
/// restarted, which is exactly the crash-recovery path this loop is
|
|
181
|
-
/// here to provide. The stale file is removed first for the same
|
|
182
|
-
/// reason.
|
|
183
|
-
fn ensure_started(&self) -> Result<(), WorkerError> {
|
|
184
|
-
let mut guard = self.child.lock().unwrap();
|
|
185
|
-
let needs_spawn = match guard.as_mut() {
|
|
186
|
-
Some(child) => !matches!(child.try_wait(), Ok(None)), // Ok(None) == still running
|
|
187
|
-
None => true,
|
|
188
|
-
};
|
|
189
|
-
if needs_spawn {
|
|
190
|
-
let _ = std::fs::remove_file(&self.socket_path);
|
|
191
|
-
let mut child = self.cmd.spawn(&self.socket_path).map_err(WorkerError::Io)?;
|
|
192
|
-
let deadline = Instant::now() + Duration::from_secs(5);
|
|
193
|
-
loop {
|
|
194
|
-
if UnixStream::connect(&self.socket_path).is_ok() {
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
if let Ok(Some(status)) = child.try_wait() {
|
|
198
|
-
return Err(WorkerError::Io(io::Error::other(format!(
|
|
199
|
-
"worker process exited before creating its socket (status: {status})"
|
|
200
|
-
))));
|
|
201
|
-
}
|
|
202
|
-
if Instant::now() >= deadline {
|
|
203
|
-
kill_process_group(&mut child);
|
|
204
|
-
let _ = std::fs::remove_file(&self.socket_path);
|
|
205
|
-
return Err(WorkerError::StartupTimedOut);
|
|
206
|
-
}
|
|
207
|
-
std::thread::sleep(Duration::from_millis(20));
|
|
208
|
-
}
|
|
209
|
-
*guard = Some(child);
|
|
210
|
-
}
|
|
211
|
-
Ok(())
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/// Spawns the worker eagerly and blocks until it's ready, without
|
|
215
|
-
/// running a hook through it. Called once from `main.rs` right after
|
|
216
|
-
/// startup so the worker's ~hundreds-of-ms cold start (process spawn +
|
|
217
|
-
/// module load) happens before any real request arrives, not on the
|
|
218
|
-
/// critical path of the first one. The client's fail-closed budget
|
|
219
|
-
/// (`DAEMON_ATTEMPT_TIMEOUT_MS` in `daemon-client.ts`) is deliberately
|
|
220
|
-
/// tight for a *healthy warm* daemon and is not meant to also cover a
|
|
221
|
-
/// cold worker spawn — without this, the very first hook call after
|
|
222
|
-
/// every daemon (re)start would fail closed even though the daemon
|
|
223
|
-
/// itself is up, which defeats the point of keeping evaluation warm.
|
|
224
|
-
/// Errors are logged, not propagated: a failed warm-up isn't fatal to
|
|
225
|
-
/// the daemon itself, since `call()` retries `ensure_started` on the
|
|
226
|
-
/// next real request anyway.
|
|
227
|
-
pub fn warm(&self) {
|
|
228
|
-
if let Err(err) = self.ensure_started() {
|
|
229
|
-
eprintln!("[failproofaid] worker warm-up failed (will retry on first request): {err}");
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/// Relays one hook evaluation to the worker and returns its verdict.
|
|
234
|
-
/// `ensure_started` is called first so a worker that crashed since the
|
|
235
|
-
/// last request gets one restart attempt before this request fails —
|
|
236
|
-
/// but a request already in flight is never retried mid-call; a
|
|
237
|
-
/// failure here surfaces as `WorkerError` and the caller (server.rs)
|
|
238
|
-
/// turns that into a client-facing `Error` response.
|
|
239
|
-
pub fn call(
|
|
240
|
-
&self,
|
|
241
|
-
hook_event: &str,
|
|
242
|
-
cli: &str,
|
|
243
|
-
stdin: &str,
|
|
244
|
-
cwd: Option<&str>,
|
|
245
|
-
) -> Result<HookOutcome, WorkerError> {
|
|
246
|
-
self.ensure_started()?;
|
|
247
|
-
|
|
248
|
-
let mut stream = UnixStream::connect(&self.socket_path).map_err(WorkerError::Io)?;
|
|
249
|
-
stream
|
|
250
|
-
.set_read_timeout(Some(Duration::from_secs(30)))
|
|
251
|
-
.map_err(WorkerError::Io)?;
|
|
252
|
-
|
|
253
|
-
let request = json!({
|
|
254
|
-
"type": "hook",
|
|
255
|
-
"hookEvent": hook_event,
|
|
256
|
-
"cli": cli,
|
|
257
|
-
"stdin": stdin,
|
|
258
|
-
"cwd": cwd,
|
|
259
|
-
});
|
|
260
|
-
write_message(&mut stream, &request).map_err(|e| WorkerError::Io(io::Error::other(e)))?;
|
|
261
|
-
|
|
262
|
-
let response: serde_json::Value =
|
|
263
|
-
read_message(&mut stream).map_err(|e| WorkerError::Io(io::Error::other(e)))?;
|
|
264
|
-
|
|
265
|
-
match response.get("type").and_then(|t| t.as_str()) {
|
|
266
|
-
Some("hookResult") => {
|
|
267
|
-
let exit_code = response
|
|
268
|
-
.get("exitCode")
|
|
269
|
-
.and_then(|v| v.as_i64())
|
|
270
|
-
.ok_or_else(|| WorkerError::BadResponse("missing exitCode".to_string()))?
|
|
271
|
-
as i32;
|
|
272
|
-
let stdout = response
|
|
273
|
-
.get("stdout")
|
|
274
|
-
.and_then(|v| v.as_str())
|
|
275
|
-
.unwrap_or("")
|
|
276
|
-
.to_string();
|
|
277
|
-
let stderr = response
|
|
278
|
-
.get("stderr")
|
|
279
|
-
.and_then(|v| v.as_str())
|
|
280
|
-
.unwrap_or("")
|
|
281
|
-
.to_string();
|
|
282
|
-
Ok(HookOutcome {
|
|
283
|
-
exit_code,
|
|
284
|
-
stdout,
|
|
285
|
-
stderr,
|
|
286
|
-
})
|
|
287
|
-
}
|
|
288
|
-
Some("error") => {
|
|
289
|
-
let message = response
|
|
290
|
-
.get("message")
|
|
291
|
-
.and_then(|v| v.as_str())
|
|
292
|
-
.unwrap_or("worker reported an error")
|
|
293
|
-
.to_string();
|
|
294
|
-
Err(WorkerError::BadResponse(message))
|
|
295
|
-
}
|
|
296
|
-
_ => Err(WorkerError::BadResponse(response.to_string())),
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
impl Drop for Worker {
|
|
302
|
-
fn drop(&mut self) {
|
|
303
|
-
if let Ok(mut guard) = self.child.lock()
|
|
304
|
-
&& let Some(mut child) = guard.take()
|
|
305
|
-
{
|
|
306
|
-
kill_process_group(&mut child);
|
|
307
|
-
// The worker's socket file survives the process that bound it,
|
|
308
|
-
// so a clean shutdown has to clear it too — otherwise the next
|
|
309
|
-
// daemon inherits debris that looks, to anything checking the
|
|
310
|
-
// filesystem, like a live worker.
|
|
311
|
-
let _ = std::fs::remove_file(&self.socket_path);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
#[cfg(test)]
|
|
317
|
-
mod tests {
|
|
318
|
-
use super::*;
|
|
319
|
-
use std::os::unix::net::UnixListener;
|
|
320
|
-
|
|
321
|
-
fn temp_socket_path(name: &str) -> PathBuf {
|
|
322
|
-
let nanos = std::time::SystemTime::now()
|
|
323
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
324
|
-
.unwrap()
|
|
325
|
-
.as_nanos();
|
|
326
|
-
std::env::temp_dir().join(format!(
|
|
327
|
-
"failproofaid-worker-test-{}-{}-{}",
|
|
328
|
-
std::process::id(),
|
|
329
|
-
name,
|
|
330
|
-
nanos
|
|
331
|
-
))
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/// The restart-on-crash path: a worker that died left its socket file
|
|
335
|
-
/// behind, and the supervisor must not read that file's mere existence
|
|
336
|
-
/// as "a worker is listening". Before the readiness probe became a real
|
|
337
|
-
/// `connect()`, this returned `Ok(())` immediately and every later
|
|
338
|
-
/// request hit `ECONNREFUSED` on a dead socket.
|
|
339
|
-
#[test]
|
|
340
|
-
fn a_stale_socket_file_is_not_mistaken_for_a_live_worker() {
|
|
341
|
-
let socket_path = temp_socket_path("stale");
|
|
342
|
-
// Exactly the debris a dead worker leaves: std does not unlink the
|
|
343
|
-
// path when the listener is dropped.
|
|
344
|
-
let listener = UnixListener::bind(&socket_path).unwrap();
|
|
345
|
-
drop(listener);
|
|
346
|
-
assert!(
|
|
347
|
-
socket_path.exists(),
|
|
348
|
-
"premise: a bound-then-dropped socket file outlives its listener"
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
// A "worker" that exits without ever binding. The stale file is the
|
|
352
|
-
// only thing on disk that could be mistaken for readiness.
|
|
353
|
-
let worker = Worker::new(socket_path.clone(), WorkerCommand::shell("exit 0"));
|
|
354
|
-
let err = worker
|
|
355
|
-
.call("PreToolUse", "claude", "{}", None)
|
|
356
|
-
.expect_err("a worker that never bound must not report ready");
|
|
357
|
-
assert!(
|
|
358
|
-
err.to_string()
|
|
359
|
-
.contains("exited before creating its socket"),
|
|
360
|
-
"got: {err}"
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
std::fs::remove_file(&socket_path).ok();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/// Readiness against the real worker, over a socket path that already
|
|
367
|
-
/// holds a stale file — the exact shape of a restart after an unclean
|
|
368
|
-
/// exit. `ensure_started` must clear the debris, wait for the new
|
|
369
|
-
/// worker to genuinely accept connections, and `Drop` must leave the
|
|
370
|
-
/// path clean again so the next daemon starts from a blank slate.
|
|
371
|
-
#[test]
|
|
372
|
-
fn restarts_over_a_stale_socket_file_and_cleans_up_on_drop() {
|
|
373
|
-
let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
374
|
-
.parent()
|
|
375
|
-
.and_then(|p| p.parent())
|
|
376
|
-
.expect("crates/failproofaid should be two levels under the repo root")
|
|
377
|
-
.to_path_buf();
|
|
378
|
-
let worker_script = repo_root.join("bin").join("failproofai-worker.mjs");
|
|
379
|
-
|
|
380
|
-
let socket_path = temp_socket_path("restart-over-stale");
|
|
381
|
-
let listener = UnixListener::bind(&socket_path).unwrap();
|
|
382
|
-
drop(listener);
|
|
383
|
-
|
|
384
|
-
let worker = Worker::new(
|
|
385
|
-
socket_path.clone(),
|
|
386
|
-
WorkerCommand::shell(format!("bun {}", worker_script.display())),
|
|
387
|
-
);
|
|
388
|
-
worker
|
|
389
|
-
.ensure_started()
|
|
390
|
-
.expect("the worker should start over the stale file");
|
|
391
|
-
// Ready means *connectable*, not merely "a file is there".
|
|
392
|
-
UnixStream::connect(&socket_path).expect("a ready worker accepts connections");
|
|
393
|
-
|
|
394
|
-
drop(worker);
|
|
395
|
-
assert!(
|
|
396
|
-
!socket_path.exists(),
|
|
397
|
-
"Drop should remove the worker socket file"
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
//! Black-box tests that spawn the real compiled `failproofaid` binary as a
|
|
2
|
-
//! subprocess and talk to it over a real Unix socket — closer to how a
|
|
3
|
-
//! systemd unit / launchd agent will actually invoke it (Stage 4) than the
|
|
4
|
-
//! in-process unit tests in `src/server.rs` are.
|
|
5
|
-
|
|
6
|
-
use fpai_ipc::{ClientMessage, PROTOCOL_VERSION, ServerMessage, read_message, write_message};
|
|
7
|
-
use std::io::BufRead;
|
|
8
|
-
use std::os::unix::net::UnixStream;
|
|
9
|
-
use std::path::PathBuf;
|
|
10
|
-
use std::process::{Child, Command, ExitStatus, Stdio};
|
|
11
|
-
use std::sync::{Arc, Mutex};
|
|
12
|
-
use std::time::{Duration, Instant};
|
|
13
|
-
|
|
14
|
-
fn binary_path() -> &'static str {
|
|
15
|
-
env!("CARGO_BIN_EXE_failproofaid")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/// A dedicated, never-before-existing run directory per test — mirroring a
|
|
19
|
-
/// real deployment, where `~/.failproofai/run` is always failproofaid's own
|
|
20
|
-
/// directory that it creates itself. `ensure_run_dir` (see `src/paths.rs`)
|
|
21
|
-
/// deliberately refuses to chmod a pre-existing directory it didn't create,
|
|
22
|
-
/// so reusing a shared directory like the bare system temp dir here would
|
|
23
|
-
/// make every test in this file fail with a permissions error instead of
|
|
24
|
-
/// exercising the daemon at all.
|
|
25
|
-
fn unique_socket_path(name: &str) -> PathBuf {
|
|
26
|
-
let nanos = std::time::SystemTime::now()
|
|
27
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
28
|
-
.unwrap()
|
|
29
|
-
.as_nanos();
|
|
30
|
-
std::env::temp_dir()
|
|
31
|
-
.join(format!(
|
|
32
|
-
"failproofaid-e2e-{}-{}-{}",
|
|
33
|
-
std::process::id(),
|
|
34
|
-
name,
|
|
35
|
-
nanos
|
|
36
|
-
))
|
|
37
|
-
.join("failproofaid.sock")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Owns a spawned daemon subprocess and guarantees it is killed and reaped
|
|
41
|
-
/// on scope exit — including during an unwind from a failed assertion.
|
|
42
|
-
/// Without this, any panic between `spawn_daemon` and a test's explicit
|
|
43
|
-
/// SIGTERM orphaned a live daemon (and the worker process group under it)
|
|
44
|
-
/// for the rest of the machine's uptime.
|
|
45
|
-
struct DaemonGuard {
|
|
46
|
-
child: Option<Child>,
|
|
47
|
-
/// Everything the daemon has written to stderr so far, drained
|
|
48
|
-
/// continuously on a background thread. Drained rather than merely
|
|
49
|
-
/// piped: an unread pipe fills at ~64 KiB and blocks the daemon
|
|
50
|
-
/// mid-write, hanging these tests. Kept so a failure can report what
|
|
51
|
-
/// the daemon itself said, which is the part actually worth reading.
|
|
52
|
-
stderr: Arc<Mutex<String>>,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
impl DaemonGuard {
|
|
56
|
-
fn stderr(&self) -> String {
|
|
57
|
-
self.stderr.lock().unwrap().clone()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/// SIGTERM, then wait — the clean-shutdown path a systemd `stop` /
|
|
61
|
-
/// launchd unload takes. Consumes the child, so `Drop` becomes a no-op.
|
|
62
|
-
fn terminate(&mut self) -> ExitStatus {
|
|
63
|
-
let mut child = self.child.take().expect("daemon already reaped");
|
|
64
|
-
unsafe {
|
|
65
|
-
libc::kill(child.id() as libc::pid_t, libc::SIGTERM);
|
|
66
|
-
}
|
|
67
|
-
child.wait().expect("daemon should exit after SIGTERM")
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
impl Drop for DaemonGuard {
|
|
72
|
-
fn drop(&mut self) {
|
|
73
|
-
if let Some(mut child) = self.child.take() {
|
|
74
|
-
child.kill().ok();
|
|
75
|
-
child.wait().ok();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
fn spawn_daemon(socket_path: &PathBuf) -> DaemonGuard {
|
|
81
|
-
let mut child = Command::new(binary_path())
|
|
82
|
-
.env("FAILPROOFAI_DAEMON_SOCKET", socket_path)
|
|
83
|
-
.stdout(Stdio::null())
|
|
84
|
-
.stderr(Stdio::piped())
|
|
85
|
-
.spawn()
|
|
86
|
-
.expect("failed to spawn failproofaid binary");
|
|
87
|
-
|
|
88
|
-
let stderr_log = Arc::new(Mutex::new(String::new()));
|
|
89
|
-
if let Some(pipe) = child.stderr.take() {
|
|
90
|
-
let sink = stderr_log.clone();
|
|
91
|
-
std::thread::spawn(move || {
|
|
92
|
-
for line in std::io::BufReader::new(pipe).lines().map_while(Result::ok) {
|
|
93
|
-
sink.lock().unwrap().push_str(&line);
|
|
94
|
-
sink.lock().unwrap().push('\n');
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
let mut guard = DaemonGuard {
|
|
99
|
-
child: Some(child),
|
|
100
|
-
stderr: stderr_log,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
let deadline = Instant::now() + Duration::from_secs(5);
|
|
104
|
-
while !socket_path.exists() {
|
|
105
|
-
// A daemon that died during startup should report *why* rather than
|
|
106
|
-
// burn the full deadline and then panic with no diagnosis.
|
|
107
|
-
if let Some(status) = guard
|
|
108
|
-
.child
|
|
109
|
-
.as_mut()
|
|
110
|
-
.expect("daemon still owned here")
|
|
111
|
-
.try_wait()
|
|
112
|
-
.expect("try_wait should not fail")
|
|
113
|
-
{
|
|
114
|
-
panic!(
|
|
115
|
-
"daemon exited before creating its socket ({status}). stderr:\n{}",
|
|
116
|
-
guard.stderr()
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
if Instant::now() > deadline {
|
|
120
|
-
panic!(
|
|
121
|
-
"daemon never created its socket file within 5s. stderr:\n{}",
|
|
122
|
-
guard.stderr()
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
std::thread::sleep(Duration::from_millis(20));
|
|
126
|
-
}
|
|
127
|
-
guard
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
#[test]
|
|
131
|
-
fn version_flag_prints_a_version_and_exits_without_binding_a_socket() {
|
|
132
|
-
let output = Command::new(binary_path())
|
|
133
|
-
.arg("--version")
|
|
134
|
-
.output()
|
|
135
|
-
.expect("failed to run --version");
|
|
136
|
-
assert!(output.status.success());
|
|
137
|
-
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
138
|
-
assert!(stdout.starts_with("failproofaid "), "got: {stdout:?}");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
#[test]
|
|
142
|
-
fn real_binary_answers_ping_over_a_real_socket() {
|
|
143
|
-
let socket_path = unique_socket_path("ping");
|
|
144
|
-
let mut daemon = spawn_daemon(&socket_path);
|
|
145
|
-
|
|
146
|
-
let mut stream = UnixStream::connect(&socket_path).expect("connect should succeed");
|
|
147
|
-
write_message(
|
|
148
|
-
&mut stream,
|
|
149
|
-
&ClientMessage::Ping {
|
|
150
|
-
protocol_version: PROTOCOL_VERSION,
|
|
151
|
-
},
|
|
152
|
-
)
|
|
153
|
-
.unwrap();
|
|
154
|
-
let response: ServerMessage = read_message(&mut stream).unwrap();
|
|
155
|
-
assert_eq!(
|
|
156
|
-
response,
|
|
157
|
-
ServerMessage::Pong {
|
|
158
|
-
protocol_version: PROTOCOL_VERSION
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
let status = daemon.terminate();
|
|
163
|
-
assert!(
|
|
164
|
-
status.success(),
|
|
165
|
-
"daemon should exit 0 on SIGTERM, got {status:?}"
|
|
166
|
-
);
|
|
167
|
-
assert!(
|
|
168
|
-
!socket_path.exists(),
|
|
169
|
-
"daemon should remove its socket file on clean shutdown"
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
#[test]
|
|
174
|
-
fn a_second_daemon_on_the_same_socket_refuses_to_start() {
|
|
175
|
-
let socket_path = unique_socket_path("singleton");
|
|
176
|
-
let mut first = spawn_daemon(&socket_path);
|
|
177
|
-
|
|
178
|
-
let second_output = Command::new(binary_path())
|
|
179
|
-
.env("FAILPROOFAI_DAEMON_SOCKET", &socket_path)
|
|
180
|
-
.output()
|
|
181
|
-
.expect("failed to run second instance");
|
|
182
|
-
assert!(
|
|
183
|
-
!second_output.status.success(),
|
|
184
|
-
"a second daemon on the same socket must not exit 0"
|
|
185
|
-
);
|
|
186
|
-
let stderr = String::from_utf8_lossy(&second_output.stderr);
|
|
187
|
-
assert!(
|
|
188
|
-
stderr.contains("already running"),
|
|
189
|
-
"expected an 'already running' message, got: {stderr:?}"
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
// First instance is still healthy and answers requests after the
|
|
193
|
-
// second one's failed startup attempt.
|
|
194
|
-
let mut stream = UnixStream::connect(&socket_path).unwrap();
|
|
195
|
-
write_message(
|
|
196
|
-
&mut stream,
|
|
197
|
-
&ClientMessage::Ping {
|
|
198
|
-
protocol_version: PROTOCOL_VERSION,
|
|
199
|
-
},
|
|
200
|
-
)
|
|
201
|
-
.unwrap();
|
|
202
|
-
let response: ServerMessage = read_message(&mut stream).unwrap();
|
|
203
|
-
assert_eq!(
|
|
204
|
-
response,
|
|
205
|
-
ServerMessage::Pong {
|
|
206
|
-
protocol_version: PROTOCOL_VERSION
|
|
207
|
-
}
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
first.terminate();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
#[test]
|
|
214
|
-
fn a_fresh_daemon_can_bind_again_after_the_previous_one_shut_down_cleanly() {
|
|
215
|
-
let socket_path = unique_socket_path("restart");
|
|
216
|
-
|
|
217
|
-
let mut first = spawn_daemon(&socket_path);
|
|
218
|
-
let status = first.terminate();
|
|
219
|
-
assert!(status.success());
|
|
220
|
-
|
|
221
|
-
// The lock is released by the kernel when the process exits, so a
|
|
222
|
-
// fresh daemon must be able to acquire it and bind immediately.
|
|
223
|
-
let mut second = spawn_daemon(&socket_path);
|
|
224
|
-
let mut stream = UnixStream::connect(&socket_path).unwrap();
|
|
225
|
-
write_message(
|
|
226
|
-
&mut stream,
|
|
227
|
-
&ClientMessage::Ping {
|
|
228
|
-
protocol_version: PROTOCOL_VERSION,
|
|
229
|
-
},
|
|
230
|
-
)
|
|
231
|
-
.unwrap();
|
|
232
|
-
let response: ServerMessage = read_message(&mut stream).unwrap();
|
|
233
|
-
assert_eq!(
|
|
234
|
-
response,
|
|
235
|
-
ServerMessage::Pong {
|
|
236
|
-
protocol_version: PROTOCOL_VERSION
|
|
237
|
-
}
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
second.terminate();
|
|
241
|
-
}
|
|
@@ -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"
|