failproofai 1.0.0-beta.0 → 1.0.0-beta.10
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 +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/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]__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]__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]__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} +2 -2
- 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/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]__04pwh4t._.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]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.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]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
- 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]__01od0m6._.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/_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/_1e19atl._.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/_1y4qr7u._.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/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 +5 -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/00adubtgov21s.js +1 -0
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
- package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
- package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -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/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/policies/hooks-client.tsx +81 -6
- 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 +17 -10
- package/.next/standalone/proxy.ts +89 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +518 -79
- package/dist/cli.mjs +11484 -6046
- package/dist/worker.mjs +2019 -272
- 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 +17 -10
- package/scripts/build-daemon-packages.mjs +210 -0
- package/scripts/daemon-platforms.mjs +42 -0
- 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 +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -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/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +390 -2
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +325 -0
- package/src/hooks/cloud-enrollment.ts +243 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +169 -0
- package/src/hooks/collector-config.ts +234 -0
- package/src/hooks/configure-wizard.ts +915 -158
- package/src/hooks/custom-hooks-loader.ts +191 -33
- package/src/hooks/daemon-client.ts +136 -17
- package/src/hooks/daemon-download.ts +192 -30
- package/src/hooks/daemon-service.ts +1190 -92
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/fp-config.ts +497 -0
- package/src/hooks/fp-home.ts +375 -0
- package/src/hooks/fp-reset.ts +464 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/hook-activity-store.ts +85 -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 +171 -8
- package/src/hooks/manager.ts +5 -4
- package/src/hooks/onboarding-attempt.ts +179 -0
- package/src/hooks/onboarding-lock.ts +119 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +268 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +119 -4
- package/src/hooks/uninstall-cli.ts +345 -0
- package/src/hooks/worker-server.ts +65 -3
- 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]__0259h38._.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]__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]__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]__1zj-87g._.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_1-upb14.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/0u9hc6oxx-gpj.js +0 -6
- 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/3wrgbih-516uj.js +0 -1
- 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/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
package/src/hooks/handler.ts
CHANGED
|
@@ -41,6 +41,9 @@ import { resolveTranscriptPath } from "./resolve-transcript-path";
|
|
|
41
41
|
import { getInstanceId } from "../../lib/telemetry-id";
|
|
42
42
|
import { hookLogInfo, hookLogWarn } from "./hook-logger";
|
|
43
43
|
import { readStdinPayload } from "./read-stdin";
|
|
44
|
+
import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from "./cloud-managed-policies";
|
|
45
|
+
import { readActivePause, type ActivePause } from "./session-pause";
|
|
46
|
+
import { layoutWarningForHook } from "./fp-reset";
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Canonicalize an event name to PascalCase. Codex sends snake_case event names
|
|
@@ -134,6 +137,37 @@ export interface EvaluateHookEventOptions {
|
|
|
134
137
|
fallbackCwd?: string;
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Runs an observe-mode policy under the same timeout and error handling as an
|
|
142
|
+
* enforcing one, so what gets measured is what would actually have happened —
|
|
143
|
+
* including a policy that times out, which in enforce mode is an allow and must
|
|
144
|
+
* be recorded as one rather than as a would-deny.
|
|
145
|
+
*/
|
|
146
|
+
async function runObserved(
|
|
147
|
+
hook: CustomHook,
|
|
148
|
+
ctx: Parameters<CustomHook["fn"]>[0],
|
|
149
|
+
hookName: string,
|
|
150
|
+
eventType: string,
|
|
151
|
+
cli: IntegrationType,
|
|
152
|
+
): Promise<PolicyResult> {
|
|
153
|
+
try {
|
|
154
|
+
return await Promise.race([
|
|
155
|
+
hook.fn(ctx),
|
|
156
|
+
new Promise<PolicyResult>((_, reject) => setTimeout(() => reject(new Error("timeout")), 10_000)),
|
|
157
|
+
]);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
+
void trackHookEvent(getInstanceId(), "custom_hook_error", {
|
|
161
|
+
hook_name: hookName,
|
|
162
|
+
error_type: msg === "timeout" ? "timeout" : "exception",
|
|
163
|
+
event_type: eventType,
|
|
164
|
+
cli,
|
|
165
|
+
is_observe_mode: true,
|
|
166
|
+
});
|
|
167
|
+
return { decision: "allow" };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
137
171
|
/**
|
|
138
172
|
* The core hook-evaluation logic, decoupled from process stdin/stdout so it
|
|
139
173
|
* can be called repeatedly inside a long-lived process (the daemon's warm
|
|
@@ -149,6 +183,13 @@ export async function evaluateHookEvent(
|
|
|
149
183
|
opts?: EvaluateHookEventOptions,
|
|
150
184
|
): Promise<HookEventOutcome> {
|
|
151
185
|
const startTime = performance.now();
|
|
186
|
+
// A home from another layout resolves to no global config, so every builtin
|
|
187
|
+
// silently stops firing — the machine looks protected and is not. Say so on
|
|
188
|
+
// every call rather than deleting anything (a hook is unattended) or denying
|
|
189
|
+
// (a blanket deny takes UserPromptSubmit with it and locks the user out of
|
|
190
|
+
// their agent entirely — demonstrated on a real machine during this work).
|
|
191
|
+
const layoutWarning = layoutWarningForHook();
|
|
192
|
+
if (layoutWarning) hookLogWarn(layoutWarning);
|
|
152
193
|
try {
|
|
153
194
|
let parsed: Record<string, unknown> = {};
|
|
154
195
|
if (stdinPayload) {
|
|
@@ -209,6 +250,20 @@ export async function evaluateHookEvent(
|
|
|
209
250
|
let config: HooksConfig;
|
|
210
251
|
let customHooksList: CustomHook[] = [];
|
|
211
252
|
let conventionHookNames = new Set<string>();
|
|
253
|
+
let activePause: ActivePause | null = null;
|
|
254
|
+
/** Registered policy name → where it came from. See the set() below. */
|
|
255
|
+
const policyAttribution = new Map<
|
|
256
|
+
string,
|
|
257
|
+
{ source: "custom" | "convention" | "cloud"; cloudPolicyId?: string; cloudRevision?: number }
|
|
258
|
+
>();
|
|
259
|
+
let cloudGeneration: number | undefined;
|
|
260
|
+
/** What observe-mode policies WOULD have done, had they been enforcing. */
|
|
261
|
+
const observedResults: Array<{
|
|
262
|
+
policyId: string;
|
|
263
|
+
revision: number;
|
|
264
|
+
decision: "deny" | "instruct";
|
|
265
|
+
reason: string | null;
|
|
266
|
+
}> = [];
|
|
212
267
|
|
|
213
268
|
if (opts?.forceDecision) {
|
|
214
269
|
// Fail-closed: no config/custom-hook loading at all — a daemon that
|
|
@@ -228,25 +283,113 @@ export async function evaluateHookEvent(
|
|
|
228
283
|
// Load enabled policies (merge across project/local/global scopes)
|
|
229
284
|
config = readMergedHooksConfig(session.cwd);
|
|
230
285
|
clearPolicies();
|
|
231
|
-
|
|
286
|
+
|
|
287
|
+
// A session pause suspends LOCAL policy only, for a bounded time. Cloud
|
|
288
|
+
// assignments are exempt below for the same reason `disabledCustomPolicies`
|
|
289
|
+
// already exempts them: a locally-issued command that could switch off a
|
|
290
|
+
// centrally assigned policy would make cloud enforcement decorative.
|
|
291
|
+
activePause = readActivePause(session.sessionId);
|
|
292
|
+
registerBuiltinPolicies(activePause ? [] : config.enabledPolicies);
|
|
293
|
+
|
|
294
|
+
// Cloud-managed policies are daemon-reconciled artifacts, but they use
|
|
295
|
+
// the same public JS policy API as local custom policies. Verify and add
|
|
296
|
+
// only the paths referenced by the atomically active generation.
|
|
297
|
+
//
|
|
298
|
+
// Wrapped, like `readConfigAt` and `readActivePause` above it. This call
|
|
299
|
+
// has fourteen throw sites — a malformed manifest, an unsafe id, an
|
|
300
|
+
// integrity mismatch, an unknown effect — and it sat bare inside a `try`
|
|
301
|
+
// whose only handler is a `finally`, so any of them aborted the entire
|
|
302
|
+
// evaluation. What that cost depended on where the hook ran, and none of
|
|
303
|
+
// the outcomes were the intended one: on a daemon machine the client
|
|
304
|
+
// fail-closed denies every tool call, and off the daemon path the throw
|
|
305
|
+
// reaches `bin/failproofai.mjs`'s outer catch, which exits 2 with nothing
|
|
306
|
+
// on stdout — a deny on Claude and Factory, but a logged warning followed
|
|
307
|
+
// by an ALLOW on Copilot, Cursor, Goose, Pi and Hermes, which read a
|
|
308
|
+
// decision off stdout and ignore the exit code. One corrupt byte was
|
|
309
|
+
// either a permanent machine-wide lockout or silent non-enforcement,
|
|
310
|
+
// depending on the CLI.
|
|
311
|
+
//
|
|
312
|
+
// Failing open here degrades ONE layer: builtins and local custom
|
|
313
|
+
// policies were already registered above and keep enforcing. That is the
|
|
314
|
+
// same trade `2ad735e5` made on the Rust side, and it is strictly better
|
|
315
|
+
// than the status quo, which already failed open on five of the twelve —
|
|
316
|
+
// just accidentally, and without saying so.
|
|
317
|
+
let cloudManagedPolicies: CloudManagedPolicyArtifact[] = [];
|
|
318
|
+
try {
|
|
319
|
+
cloudManagedPolicies = readActiveCloudManagedPolicies();
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
322
|
+
hookLogWarn(
|
|
323
|
+
`[failproofai] cloud-managed policies could NOT be loaded and are not being enforced: ${msg}. ` +
|
|
324
|
+
`Local policies are unaffected. Run \`failproofai config --status\` to check this machine's enrolment.`,
|
|
325
|
+
);
|
|
326
|
+
void trackHookEvent(getInstanceId(), "cloud_managed_load_error", {
|
|
327
|
+
event_type: eventType,
|
|
328
|
+
cli,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// Recorded on every row once a machine is managed, whether or not a cloud
|
|
332
|
+
// policy decided this event: "what was deployed here at the time" is a
|
|
333
|
+
// separate question from "what decided", and only the former can tell a
|
|
334
|
+
// rollout that changed nothing from one that never reached the machine.
|
|
335
|
+
cloudGeneration = cloudManagedPolicies[0]?.generation;
|
|
336
|
+
const configuredCustomPaths = config.customPoliciesPaths ?? config.customPoliciesPath;
|
|
337
|
+
const allExplicitPaths =
|
|
338
|
+
cloudManagedPolicies.length === 0
|
|
339
|
+
? configuredCustomPaths
|
|
340
|
+
: [
|
|
341
|
+
...(typeof configuredCustomPaths === "string" ? [configuredCustomPaths] : configuredCustomPaths ?? []),
|
|
342
|
+
...cloudManagedPolicies.map((policy) => policy.path),
|
|
343
|
+
];
|
|
232
344
|
|
|
233
345
|
// Load and register custom hooks (layer 2, after builtins)
|
|
234
|
-
const loadResult = await loadAllCustomHooks(
|
|
346
|
+
const loadResult = await loadAllCustomHooks(allExplicitPaths, {
|
|
235
347
|
sessionCwd: session.cwd,
|
|
236
348
|
customPoliciesEnabled: config.customPoliciesEnabled,
|
|
349
|
+
...(cloudManagedPolicies.length > 0 ? { cloudManagedPolicies } : {}),
|
|
237
350
|
});
|
|
238
351
|
customHooksList = loadResult.hooks;
|
|
239
352
|
const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
|
|
240
353
|
conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
|
|
241
354
|
|
|
242
355
|
for (const hook of customHooksList) {
|
|
243
|
-
const
|
|
244
|
-
|
|
356
|
+
const taggedHook = hook as CustomHook & {
|
|
357
|
+
__policyId?: string;
|
|
358
|
+
__cloudManaged?: CloudManagedPolicyArtifact;
|
|
359
|
+
};
|
|
360
|
+
const policyId = taggedHook.__policyId;
|
|
361
|
+
const cloudManaged = taggedHook.__cloudManaged;
|
|
362
|
+
// Local config cannot disable a centrally assigned policy merely by
|
|
363
|
+
// copying its generated ID into disabledCustomPolicies.
|
|
364
|
+
if (!cloudManaged && policyId && disabledCustomPolicies.has(policyId)) continue;
|
|
365
|
+
// Same rule for a session pause — it suspends local policy, never cloud.
|
|
366
|
+
if (!cloudManaged && activePause) continue;
|
|
245
367
|
const hookName = hook.name;
|
|
246
368
|
const conventionScope = (hook as CustomHook & { __conventionScope?: string }).__conventionScope;
|
|
247
369
|
const isConvention = !!conventionScope;
|
|
248
|
-
const prefix =
|
|
370
|
+
const prefix = cloudManaged
|
|
371
|
+
? `cloud/${cloudManaged.id}@${cloudManaged.revision}`
|
|
372
|
+
: isConvention
|
|
373
|
+
? `.failproofai-${conventionScope}`
|
|
374
|
+
: "custom";
|
|
375
|
+
// Observe mode: run it for real, record what it decided, then hand back
|
|
376
|
+
// an allow. Evaluating and discarding is the whole point — a rollout is
|
|
377
|
+
// measured against real traffic before it can break anyone's work, and
|
|
378
|
+
// a policy that did not actually run would measure nothing.
|
|
379
|
+
const observeOnly = cloudManaged?.effect === "observe";
|
|
249
380
|
const fn: PolicyFunction = async (ctx): Promise<PolicyResult> => {
|
|
381
|
+
if (observeOnly) {
|
|
382
|
+
const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
|
|
383
|
+
if (shadow.decision !== "allow") {
|
|
384
|
+
observedResults.push({
|
|
385
|
+
policyId: cloudManaged!.id,
|
|
386
|
+
revision: cloudManaged!.revision,
|
|
387
|
+
decision: shadow.decision,
|
|
388
|
+
reason: shadow.reason ?? null,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return { decision: "allow" };
|
|
392
|
+
}
|
|
250
393
|
try {
|
|
251
394
|
const result = await Promise.race([
|
|
252
395
|
hook.fn(ctx),
|
|
@@ -268,8 +411,18 @@ export async function evaluateHookEvent(
|
|
|
268
411
|
return { decision: "allow" };
|
|
269
412
|
}
|
|
270
413
|
};
|
|
414
|
+
const registeredName = `${prefix}/${hookName}`;
|
|
415
|
+
// Record where this policy came from as structured data, keyed by the
|
|
416
|
+
// exact name the evaluator will report back. The display name already
|
|
417
|
+
// encodes it ("cloud/org-guard@7/…"), but only as a string — so the one
|
|
418
|
+
// question cloud attribution has to answer, "which rollout produced
|
|
419
|
+
// this decision", could only be answered by re-parsing our own label.
|
|
420
|
+
policyAttribution.set(registeredName, {
|
|
421
|
+
source: cloudManaged ? "cloud" : isConvention ? "convention" : "custom",
|
|
422
|
+
...(cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudRevision: cloudManaged.revision } : {}),
|
|
423
|
+
});
|
|
271
424
|
registerPolicy(
|
|
272
|
-
|
|
425
|
+
registeredName,
|
|
273
426
|
hook.description ?? "",
|
|
274
427
|
fn,
|
|
275
428
|
hook.match ?? {},
|
|
@@ -335,6 +488,32 @@ export async function evaluateHookEvent(
|
|
|
335
488
|
cwd: session.cwd,
|
|
336
489
|
permissionMode: session.permissionMode,
|
|
337
490
|
hookEventName: session.hookEventName,
|
|
491
|
+
// Attribution. A builtin is anything registered that is not in the map,
|
|
492
|
+
// so its absence is meaningful rather than missing — but only when a
|
|
493
|
+
// policy actually decided; a plain allow names nobody.
|
|
494
|
+
...(result.policyName
|
|
495
|
+
? (() => {
|
|
496
|
+
const attribution = policyAttribution.get(result.policyName);
|
|
497
|
+
return {
|
|
498
|
+
policySource: attribution?.source ?? ("builtin" as const),
|
|
499
|
+
...(attribution?.cloudPolicyId ? { cloudPolicyId: attribution.cloudPolicyId } : {}),
|
|
500
|
+
...(attribution?.cloudRevision !== undefined
|
|
501
|
+
? { cloudRevision: attribution.cloudRevision }
|
|
502
|
+
: {}),
|
|
503
|
+
};
|
|
504
|
+
})()
|
|
505
|
+
: {}),
|
|
506
|
+
...(cloudGeneration !== undefined ? { cloudGeneration } : {}),
|
|
507
|
+
// The point of observe mode is this record. Without it the rollout is
|
|
508
|
+
// unmeasurable and the row is indistinguishable from one where the policy
|
|
509
|
+
// never matched at all.
|
|
510
|
+
...(observedResults.length > 0 ? { observed: observedResults } : {}),
|
|
511
|
+
// Without these, a row logged during a pause is indistinguishable from
|
|
512
|
+
// one where every policy ran and allowed — the log would assert a clean
|
|
513
|
+
// window over exactly the window that was not enforced.
|
|
514
|
+
...(activePause
|
|
515
|
+
? { pausedBy: activePause.setBy, pauseExpiresAt: activePause.expiresAt }
|
|
516
|
+
: {}),
|
|
338
517
|
};
|
|
339
518
|
try {
|
|
340
519
|
persistHookActivity(activityEntry);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Disk persistence for hook activity entries using page-sized JSONL files.
|
|
3
3
|
*
|
|
4
|
-
* Storage location: ~/.failproofai/
|
|
4
|
+
* Storage location: ~/.failproofai/hook-activity/ (see fp-home.ts)
|
|
5
5
|
*
|
|
6
6
|
* File structure:
|
|
7
7
|
* - current.jsonl — actively written to, 0–PAGE_SIZE entries
|
|
@@ -22,19 +22,32 @@ import {
|
|
|
22
22
|
unlinkSync,
|
|
23
23
|
} from "node:fs";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import { homedir } from "node:os";
|
|
26
25
|
import type { IntegrationType } from "./types";
|
|
26
|
+
import { hookActivityDir } from "./fp-home";
|
|
27
27
|
|
|
28
28
|
export const PAGE_SIZE = 25;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Layout 2 promoted this out of `cache/`. It was never a cache — nothing
|
|
32
|
+
* regenerates it, and deleting it loses the decision history the dashboard's
|
|
33
|
+
* activity tab is built on. Resolved lazily (not at module load) so
|
|
34
|
+
* FAILPROOFAI_HOME is honoured by tests and containers that set it after
|
|
35
|
+
* import.
|
|
36
|
+
*/
|
|
37
|
+
const defaultStoreDir = () => hookActivityDir();
|
|
31
38
|
const CURRENT_FILE = "current.jsonl";
|
|
32
39
|
const COUNT_FILE = "current.count"; // tracks line count; O(1) read/write vs rereading current.jsonl
|
|
33
40
|
const STATS_FILE = "stats.json";
|
|
34
41
|
const LOCK_FILE = "current.lock"; // advisory lock for concurrent hook processes
|
|
35
42
|
const LOCK_STALE_MS = 2000; // steal lock if older than 2 s (covers crashed processes)
|
|
36
43
|
|
|
37
|
-
let
|
|
44
|
+
let storeDirOverride: string | null = null;
|
|
45
|
+
/**
|
|
46
|
+
* A getter, not a module-load constant: `FAILPROOFAI_HOME` is routinely set by
|
|
47
|
+
* a test or a container AFTER this module is imported, and a value captured at
|
|
48
|
+
* import time would silently point every write at the real home.
|
|
49
|
+
*/
|
|
50
|
+
const storeDirValue = () => storeDirOverride ?? defaultStoreDir();
|
|
38
51
|
let rotateSeq = 0;
|
|
39
52
|
|
|
40
53
|
// ── Types ──
|
|
@@ -69,6 +82,45 @@ export interface HookActivityEntry {
|
|
|
69
82
|
cwd?: string;
|
|
70
83
|
permissionMode?: string;
|
|
71
84
|
hookEventName?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Set only on events evaluated while a session pause was active. Absent on
|
|
87
|
+
* every ordinary row and on every row written before pausing existed, so —
|
|
88
|
+
* like `matchedPolicies` — consumers must treat `undefined` as "not paused"
|
|
89
|
+
* rather than rendering it as unknown.
|
|
90
|
+
*/
|
|
91
|
+
pausedBy?: string;
|
|
92
|
+
/** Epoch ms the pause lifts. Present iff `pausedBy` is. */
|
|
93
|
+
pauseExpiresAt?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Where the policy that DECIDED came from. Absent when nothing decided (a
|
|
96
|
+
* plain allow) and on rows written before this existed — so, like
|
|
97
|
+
* `matchedPolicies`, `undefined` means "unknown", not "builtin".
|
|
98
|
+
*/
|
|
99
|
+
policySource?: "builtin" | "custom" | "convention" | "cloud";
|
|
100
|
+
/** Cloud policy id of the decider. Present only when `policySource` is "cloud". */
|
|
101
|
+
cloudPolicyId?: string;
|
|
102
|
+
/** Immutable revision of that policy — the half of attribution that identifies WHICH version ran. */
|
|
103
|
+
cloudRevision?: number;
|
|
104
|
+
/**
|
|
105
|
+
* The cloud generation active when this event was evaluated, recorded on
|
|
106
|
+
* every row of a managed machine regardless of what decided. "What was
|
|
107
|
+
* deployed here" is a different question from "what decided", and only this
|
|
108
|
+
* distinguishes a rollout that changed no outcomes from one that never
|
|
109
|
+
* arrived.
|
|
110
|
+
*/
|
|
111
|
+
cloudGeneration?: number;
|
|
112
|
+
/**
|
|
113
|
+
* What observe-mode policies WOULD have done, had they been enforcing. This
|
|
114
|
+
* record is the entire point of observe mode: without it the row is
|
|
115
|
+
* indistinguishable from one where the policy never matched, and the rollout
|
|
116
|
+
* being trialled is unmeasurable.
|
|
117
|
+
*/
|
|
118
|
+
observed?: Array<{
|
|
119
|
+
policyId: string;
|
|
120
|
+
revision: number;
|
|
121
|
+
decision: "deny" | "instruct";
|
|
122
|
+
reason: string | null;
|
|
123
|
+
}>;
|
|
72
124
|
}
|
|
73
125
|
|
|
74
126
|
export interface HookActivityFilters {
|
|
@@ -77,6 +129,12 @@ export interface HookActivityFilters {
|
|
|
77
129
|
policyName?: string;
|
|
78
130
|
sessionId?: string;
|
|
79
131
|
integration?: IntegrationType;
|
|
132
|
+
/**
|
|
133
|
+
* Filter by where the deciding policy came from. "Show me everything my
|
|
134
|
+
* organization's policies decided" is the question cloud rollout reporting
|
|
135
|
+
* is built on, and it is unanswerable without this.
|
|
136
|
+
*/
|
|
137
|
+
source?: "builtin" | "custom" | "convention" | "cloud";
|
|
80
138
|
}
|
|
81
139
|
|
|
82
140
|
export interface HookActivityStats {
|
|
@@ -89,8 +147,8 @@ export interface HookActivityStats {
|
|
|
89
147
|
// ── Directory setup ──
|
|
90
148
|
|
|
91
149
|
function ensureDir(): void {
|
|
92
|
-
if (!existsSync(
|
|
93
|
-
mkdirSync(
|
|
150
|
+
if (!existsSync(storeDirValue())) {
|
|
151
|
+
mkdirSync(storeDirValue(), { recursive: true });
|
|
94
152
|
}
|
|
95
153
|
}
|
|
96
154
|
|
|
@@ -98,7 +156,7 @@ function ensureDir(): void {
|
|
|
98
156
|
|
|
99
157
|
function acquireLock(): void {
|
|
100
158
|
ensureDir();
|
|
101
|
-
const lockPath = join(
|
|
159
|
+
const lockPath = join(storeDirValue(), LOCK_FILE);
|
|
102
160
|
const deadline = Date.now() + LOCK_STALE_MS;
|
|
103
161
|
while (Date.now() < deadline) {
|
|
104
162
|
try {
|
|
@@ -121,7 +179,7 @@ function acquireLock(): void {
|
|
|
121
179
|
}
|
|
122
180
|
|
|
123
181
|
function releaseLock(): void {
|
|
124
|
-
try { unlinkSync(join(
|
|
182
|
+
try { unlinkSync(join(storeDirValue(), LOCK_FILE)); } catch { /* ignore */ }
|
|
125
183
|
}
|
|
126
184
|
|
|
127
185
|
// ── Writing (synchronous — hook handler is short-lived) ──
|
|
@@ -130,8 +188,8 @@ export function persistHookActivity(entry: HookActivityEntry): void {
|
|
|
130
188
|
ensureDir();
|
|
131
189
|
acquireLock();
|
|
132
190
|
try {
|
|
133
|
-
const currentPath = join(
|
|
134
|
-
const countPath = join(
|
|
191
|
+
const currentPath = join(storeDirValue(), CURRENT_FILE);
|
|
192
|
+
const countPath = join(storeDirValue(), COUNT_FILE);
|
|
135
193
|
|
|
136
194
|
const lineCount = readCount(countPath);
|
|
137
195
|
if (lineCount >= PAGE_SIZE) {
|
|
@@ -153,7 +211,7 @@ export function persistHookActivity(entry: HookActivityEntry): void {
|
|
|
153
211
|
|
|
154
212
|
function rotate(currentPath: string, countPath: string): void {
|
|
155
213
|
const archiveName = `page-${Date.now()}-${rotateSeq++}.jsonl`;
|
|
156
|
-
const archivePath = join(
|
|
214
|
+
const archivePath = join(storeDirValue(), archiveName);
|
|
157
215
|
renameSync(currentPath, archivePath);
|
|
158
216
|
// Reset count for the fresh file (write 0 — next append will set it to 1)
|
|
159
217
|
writeCount(countPath, 0);
|
|
@@ -195,7 +253,7 @@ interface StoredStats {
|
|
|
195
253
|
|
|
196
254
|
function readStoredStats(): StoredStats {
|
|
197
255
|
try {
|
|
198
|
-
return JSON.parse(readFileSync(join(
|
|
256
|
+
return JSON.parse(readFileSync(join(storeDirValue(), STATS_FILE), "utf-8")) as StoredStats;
|
|
199
257
|
} catch {
|
|
200
258
|
return { totalEvents: 0, denyCount: 0, policyMap: {} };
|
|
201
259
|
}
|
|
@@ -213,10 +271,10 @@ function updateStats(entry: HookActivityEntry): void {
|
|
|
213
271
|
s.policyMap[entry.policyName] = (s.policyMap[entry.policyName] ?? 0) + 1;
|
|
214
272
|
}
|
|
215
273
|
// Write atomically: write to a PID-unique temp file then rename — prevents partial reads.
|
|
216
|
-
const tmpPath = join(
|
|
274
|
+
const tmpPath = join(storeDirValue(), `stats.json.${process.pid}.tmp`);
|
|
217
275
|
try {
|
|
218
276
|
writeFileSync(tmpPath, JSON.stringify(s), "utf-8");
|
|
219
|
-
renameSync(tmpPath, join(
|
|
277
|
+
renameSync(tmpPath, join(storeDirValue(), STATS_FILE));
|
|
220
278
|
} catch {
|
|
221
279
|
try { unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
222
280
|
// Non-fatal: stats file write failure doesn't block the hook
|
|
@@ -243,7 +301,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
|
|
|
243
301
|
if (page < 1) return [];
|
|
244
302
|
|
|
245
303
|
if (page === 1) {
|
|
246
|
-
const currentPath = join(
|
|
304
|
+
const currentPath = join(storeDirValue(), CURRENT_FILE);
|
|
247
305
|
return readJsonlFile(currentPath).reverse();
|
|
248
306
|
}
|
|
249
307
|
|
|
@@ -251,7 +309,7 @@ export function getHookActivityPage(page: number): HookActivityEntry[] {
|
|
|
251
309
|
const archiveIndex = page - 2;
|
|
252
310
|
if (archiveIndex >= archives.length) return [];
|
|
253
311
|
|
|
254
|
-
return readJsonlFile(join(
|
|
312
|
+
return readJsonlFile(join(storeDirValue(), archives[archiveIndex])).reverse();
|
|
255
313
|
}
|
|
256
314
|
|
|
257
315
|
export function getHookActivityPageCount(): number {
|
|
@@ -262,13 +320,13 @@ export function getHookActivityPageCount(): number {
|
|
|
262
320
|
export function getAllHookActivityEntries(): HookActivityEntry[] {
|
|
263
321
|
ensureDir();
|
|
264
322
|
|
|
265
|
-
const currentPath = join(
|
|
323
|
+
const currentPath = join(storeDirValue(), CURRENT_FILE);
|
|
266
324
|
const currentEntries = readJsonlFile(currentPath).reverse();
|
|
267
325
|
|
|
268
326
|
const archives = getArchiveFiles();
|
|
269
327
|
const archiveEntries: HookActivityEntry[] = [];
|
|
270
328
|
for (const file of archives) {
|
|
271
|
-
const entries = readJsonlFile(join(
|
|
329
|
+
const entries = readJsonlFile(join(storeDirValue(), file));
|
|
272
330
|
archiveEntries.push(...entries.reverse());
|
|
273
331
|
}
|
|
274
332
|
|
|
@@ -298,6 +356,11 @@ export function searchHookActivity(
|
|
|
298
356
|
return false;
|
|
299
357
|
}
|
|
300
358
|
if (filters.integration && entry.integration !== filters.integration) return false;
|
|
359
|
+
// Rows written before attribution existed have no policySource. They are
|
|
360
|
+
// excluded from every source filter rather than guessed at — a wrong
|
|
361
|
+
// attribution is worse than a missing one when the point is proving which
|
|
362
|
+
// rollout decided something.
|
|
363
|
+
if (filters.source && entry.policySource !== filters.source) return false;
|
|
301
364
|
return true;
|
|
302
365
|
});
|
|
303
366
|
|
|
@@ -342,7 +405,7 @@ function readJsonlFile(filePath: string): HookActivityEntry[] {
|
|
|
342
405
|
|
|
343
406
|
function getArchiveFiles(): string[] {
|
|
344
407
|
try {
|
|
345
|
-
const files = readdirSync(
|
|
408
|
+
const files = readdirSync(storeDirValue());
|
|
346
409
|
return files
|
|
347
410
|
.filter((f) => f.startsWith("page-") && f.endsWith(".jsonl"))
|
|
348
411
|
.sort((a, b) => {
|
|
@@ -364,5 +427,7 @@ function getArchiveFiles(): string[] {
|
|
|
364
427
|
|
|
365
428
|
export function _resetForTest(testDir?: string): void {
|
|
366
429
|
rotateSeq = 0;
|
|
367
|
-
|
|
430
|
+
// null, not the default path: clearing the override lets the getter re-read
|
|
431
|
+
// FAILPROOFAI_HOME, which a test may have changed since this module loaded.
|
|
432
|
+
storeDirOverride = testDir ?? null;
|
|
368
433
|
}
|
package/src/hooks/hook-logger.ts
CHANGED
|
@@ -22,13 +22,14 @@ import {
|
|
|
22
22
|
} from "node:fs";
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
|
+
import { logsDir } from "./fp-home";
|
|
25
26
|
|
|
26
27
|
export type LogLevel = "info" | "warn" | "error";
|
|
27
28
|
|
|
28
29
|
const LEVEL_ORDER: Record<LogLevel, number> = { info: 0, warn: 1, error: 2 };
|
|
29
30
|
const MAX_FILE_SIZE = 512 * 1024; // 500 KB
|
|
30
31
|
const LOG_FILENAME = "hooks.log";
|
|
31
|
-
const DEFAULT_LOG_DIR =
|
|
32
|
+
const DEFAULT_LOG_DIR = logsDir();
|
|
32
33
|
|
|
33
34
|
// ── State (lazy-initialized) ──
|
|
34
35
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { version } from "../../package.json";
|
|
9
9
|
import { POSTHOG_API_KEY, POSTHOG_PRODUCT } from "../posthog-key";
|
|
10
|
+
import { isTelemetryEnabled } from "../../lib/telemetry-enabled";
|
|
10
11
|
|
|
11
12
|
const API_KEY = POSTHOG_API_KEY;
|
|
12
13
|
const CAPTURE_URL = "https://us.i.posthog.com/capture/";
|
|
@@ -57,7 +58,10 @@ export async function trackHookEvent(
|
|
|
57
58
|
event: string,
|
|
58
59
|
properties?: Record<string, unknown>,
|
|
59
60
|
): Promise<void> {
|
|
60
|
-
|
|
61
|
+
// Honours the config file as well as the env var — the env var alone cannot
|
|
62
|
+
// reach a system-scope daemon. Memoised, so this stays off the hook path's
|
|
63
|
+
// latency budget. See lib/telemetry-enabled.ts.
|
|
64
|
+
if (!isTelemetryEnabled()) return;
|
|
61
65
|
|
|
62
66
|
const p = sendEvent(distinctId, event, properties);
|
|
63
67
|
pending.add(p);
|
|
@@ -7,6 +7,7 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
|
|
8
8
|
import type { HookScope } from "./types";
|
|
9
9
|
import { hookLogInfo, hookLogWarn } from "./hook-logger";
|
|
10
|
+
import { globalPolicyConfigFile } from "./fp-home";
|
|
10
11
|
|
|
11
12
|
/** Normalize plural and legacy singular explicit policy paths for consumers. */
|
|
12
13
|
export function configuredCustomPolicyPaths(
|
|
@@ -69,7 +70,7 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
|
|
|
69
70
|
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
70
71
|
const projectPath = resolve(base, ".failproofai", "policies-config.json");
|
|
71
72
|
const localPath = resolve(base, ".failproofai", "policies-config.local.json");
|
|
72
|
-
const globalPath =
|
|
73
|
+
const globalPath = globalPolicyConfigFile();
|
|
73
74
|
|
|
74
75
|
const project = readConfigAt(projectPath);
|
|
75
76
|
const local = readConfigAt(localPath);
|
|
@@ -128,7 +129,7 @@ export function readMergedHooksConfig(cwd?: string): HooksConfig {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
function getConfigPath(): string {
|
|
131
|
-
return
|
|
132
|
+
return globalPolicyConfigFile();
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
export function readHooksConfig(): HooksConfig {
|
|
@@ -160,7 +161,7 @@ export function writeHooksConfig(config: HooksConfig): void {
|
|
|
160
161
|
export function getConfigPathForScope(scope: HookScope, cwd?: string): string {
|
|
161
162
|
switch (scope) {
|
|
162
163
|
case "user":
|
|
163
|
-
return
|
|
164
|
+
return globalPolicyConfigFile();
|
|
164
165
|
case "project":
|
|
165
166
|
return resolve(findProjectConfigDir(cwd ?? process.cwd()), ".failproofai", "policies-config.json");
|
|
166
167
|
case "local":
|