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
package/src/hooks/manager.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { execSync } from "node:child_process";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { resolve, basename } from "node:path";
|
|
11
|
-
import {
|
|
11
|
+
import { platform, arch, release, hostname } from "node:os";
|
|
12
12
|
import {
|
|
13
13
|
HOOK_SCOPES,
|
|
14
14
|
type HookScope,
|
|
@@ -24,6 +24,8 @@ import { trackHookEvent } from "./hook-telemetry";
|
|
|
24
24
|
import { getInstanceId, hashToId } from "../../lib/telemetry-id";
|
|
25
25
|
import { CliError } from "../cli-error";
|
|
26
26
|
import { hookLogWarn } from "./hook-logger";
|
|
27
|
+
import { customPoliciesDir, globalPolicyConfigFile } from "./fp-home";
|
|
28
|
+
import { readActiveCloudManagedPolicies } from "./cloud-managed-policies";
|
|
27
29
|
|
|
28
30
|
const VALID_POLICY_NAMES = new Set(BUILTIN_POLICIES.map((p) => p.name));
|
|
29
31
|
|
|
@@ -645,7 +647,7 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
645
647
|
} else {
|
|
646
648
|
console.log("\n Run `failproofai policies --install` to get started.");
|
|
647
649
|
}
|
|
648
|
-
console.log(
|
|
650
|
+
console.log(` Config: ${globalPolicyConfigFile()}\n`);
|
|
649
651
|
} else if (installedScopes.length === 1) {
|
|
650
652
|
// State B: Single scope — table with header row
|
|
651
653
|
const scope = installedScopes[0];
|
|
@@ -657,7 +659,7 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
657
659
|
for (const policy of regularPolicies) printSimpleRow(policy);
|
|
658
660
|
printBetaSection(printSimpleRow);
|
|
659
661
|
|
|
660
|
-
console.log(
|
|
662
|
+
console.log(`\n Config: ${globalPolicyConfigFile()}\n`);
|
|
661
663
|
} else {
|
|
662
664
|
// State C: Multiple scopes — column table
|
|
663
665
|
const COL = 9;
|
|
@@ -701,7 +703,7 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
701
703
|
for (const policy of betaPolicies) printMultiScopeRow(policy);
|
|
702
704
|
}
|
|
703
705
|
|
|
704
|
-
console.log(
|
|
706
|
+
console.log(`\n Config: ${globalPolicyConfigFile()}`);
|
|
705
707
|
|
|
706
708
|
// Multi-scope warning
|
|
707
709
|
const scopeNames = installedScopes.join(", ");
|
|
@@ -765,7 +767,7 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
765
767
|
// hook path was loading them.
|
|
766
768
|
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
767
769
|
const projectDir = resolve(base, ".failproofai", "policies");
|
|
768
|
-
const userDir =
|
|
770
|
+
const userDir = customPoliciesDir();
|
|
769
771
|
const sameDir = userDir === projectDir;
|
|
770
772
|
const conventionDirs: { label: string; dir: string }[] = [
|
|
771
773
|
{ label: sameDir ? "Project + User" : "Project", dir: projectDir },
|
|
@@ -838,6 +840,40 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
838
840
|
console.log();
|
|
839
841
|
}
|
|
840
842
|
|
|
843
|
+
// Cloud-managed policies. These enforce on this machine exactly like the two
|
|
844
|
+
// sections above, but nothing here listed them — so `failproofai policies`
|
|
845
|
+
// answered "what is enforcing?" with a subset, and the policies an operator
|
|
846
|
+
// pushed to a fleet were the ones invisible to the person running the
|
|
847
|
+
// command on it.
|
|
848
|
+
//
|
|
849
|
+
// Read-only on purpose: these are owned by the deployment, not by local
|
|
850
|
+
// config. `--uninstall <name>` cannot switch one off, and printing them
|
|
851
|
+
// beside toggleable rows without saying so would imply it can.
|
|
852
|
+
try {
|
|
853
|
+
const cloud = readActiveCloudManagedPolicies();
|
|
854
|
+
if (cloud.length > 0) {
|
|
855
|
+
const gen = cloud[0].deployment;
|
|
856
|
+
console.log(
|
|
857
|
+
`\n \u2500\u2500 Cloud-managed \u2014 deployment ${gen} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
858
|
+
);
|
|
859
|
+
const colWidth = Math.max(nameColWidth, ...cloud.map((c) => c.id.length + 2));
|
|
860
|
+
for (const artifact of cloud) {
|
|
861
|
+
// `observe` is evaluated and then has its verdict discarded, so a row
|
|
862
|
+
// that read "ON" would claim enforcement this policy deliberately is
|
|
863
|
+
// not doing.
|
|
864
|
+
const status =
|
|
865
|
+
artifact.effect === "observe" ? "\x1B[33m\u25D0 OBS\x1B[0m" : "\x1B[32m\u2713 ON\x1B[0m";
|
|
866
|
+
console.log(` ${status} ${artifact.id.padEnd(colWidth)}v${artifact.version}`);
|
|
867
|
+
}
|
|
868
|
+
console.log("\n Managed from the dashboard \u2014 not switchable with `failproofai policies`.");
|
|
869
|
+
console.log();
|
|
870
|
+
}
|
|
871
|
+
} catch {
|
|
872
|
+
// A machine with no deployment, or an unreadable manifest, simply has no
|
|
873
|
+
// section. The hook path reports its own failures; a listing must not be
|
|
874
|
+
// the thing that turns a bad manifest into a broken command.
|
|
875
|
+
}
|
|
876
|
+
|
|
841
877
|
// Mirror what was just listed into the USER config. Safe here because
|
|
842
878
|
// `failproofai policies` is a one-shot command — never do this on the hook
|
|
843
879
|
// path (see the HooksConfig.conventionPolicies doc comment).
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a home written by an older layout reaches this one, step by step.
|
|
3
|
+
*
|
|
4
|
+
* ## The problem this replaces
|
|
5
|
+
*
|
|
6
|
+
* `resetHome(from)` took the layout it found and then largely ignored it: the
|
|
7
|
+
* carry functions each asked "is layout 1's file here? is layout 2's?" and
|
|
8
|
+
* handled whichever they found. That works for two old layouts and does not
|
|
9
|
+
* scale — every carry has to know every historical path, so the knowledge grows
|
|
10
|
+
* with the SQUARE of the layout count, in a set of hand-written functions with
|
|
11
|
+
* nothing forcing them to stay complete. It is also the shape that produced every
|
|
12
|
+
* silent-data-loss bug in `fp-reset.ts`'s history.
|
|
13
|
+
*
|
|
14
|
+
* A registry of steps replaces it. `planMigration()` composes a chain from what
|
|
15
|
+
* is on disk to what this build speaks, and each step only has to know its own
|
|
16
|
+
* ends.
|
|
17
|
+
*
|
|
18
|
+
* ## Keyed on the LAYOUT, never on the npm version
|
|
19
|
+
*
|
|
20
|
+
* The customer case is "on 1.0.0 for four months, then jumps to 1.3.0". The steps
|
|
21
|
+
* are LAYOUT steps, not version steps, because npm versions change on every
|
|
22
|
+
* release — dozens between two layouts — and a customer who skips thirty of them
|
|
23
|
+
* with no layout change should run ZERO migrations, not thirty no-ops. The npm
|
|
24
|
+
* version is recorded in the ledger for diagnostics and decides nothing.
|
|
25
|
+
*
|
|
26
|
+
* ## `to` is not always `from + 1`, and that is deliberate
|
|
27
|
+
*
|
|
28
|
+
* The obvious contract is single-hop steps. Today's two steps both land on layout
|
|
29
|
+
* 3 — one from 1, one from 2 — because `resetHome` was written before this
|
|
30
|
+
* registry existed and handles either source directly. Forcing them into single
|
|
31
|
+
* hops would mean inventing a 1 → 2 step that produces an intermediate layout-2
|
|
32
|
+
* home NO CODE IN THIS REPO WRITES ANY MORE, purely to satisfy the shape, and
|
|
33
|
+
* then a 2 → 3 step that consumes it. That is more moving parts and one more
|
|
34
|
+
* thing to get wrong, for a home state no user will ever hold.
|
|
35
|
+
*
|
|
36
|
+
* So the contract is `to > from`, and composition follows the `to` link rather
|
|
37
|
+
* than counting. A chain from 1 today is one step; when layout 4 lands it becomes
|
|
38
|
+
* `1 → 3` then `3 → 4`, and only the second has to be written.
|
|
39
|
+
*/
|
|
40
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
41
|
+
import { basename, dirname, resolve } from "node:path";
|
|
42
|
+
import { version as cliVersion } from "../../package.json";
|
|
43
|
+
import {
|
|
44
|
+
LAYOUT_VERSION,
|
|
45
|
+
configFile,
|
|
46
|
+
credentialsFile,
|
|
47
|
+
failproofaiHome,
|
|
48
|
+
globalPolicyConfigFile,
|
|
49
|
+
legacy,
|
|
50
|
+
migrationBackupDir,
|
|
51
|
+
migrationLedgerFile,
|
|
52
|
+
migrationsDir,
|
|
53
|
+
versionFile,
|
|
54
|
+
} from "./fp-home";
|
|
55
|
+
import { resetHome, type ResetOutcome } from "./fp-reset";
|
|
56
|
+
|
|
57
|
+
export interface Migration {
|
|
58
|
+
/** The layout this step reads. */
|
|
59
|
+
from: number;
|
|
60
|
+
/** The layout the home speaks once it has run. Always greater than `from`. */
|
|
61
|
+
to: number;
|
|
62
|
+
/** One line, printed by `--dry-run`. Present tense, names what moves. */
|
|
63
|
+
describe: string;
|
|
64
|
+
run(): ResetOutcome;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Every step, in order.
|
|
69
|
+
*
|
|
70
|
+
* Both current entries delegate to `resetHome`, which is where the actual carry
|
|
71
|
+
* logic lives and stays — this module owns the ORDER and the record, not the
|
|
72
|
+
* moves. A layout-4 step would be written as its own function here and would not
|
|
73
|
+
* touch `resetHome` at all, which is the point: the next migration is additive.
|
|
74
|
+
*/
|
|
75
|
+
export const MIGRATIONS: readonly Migration[] = [
|
|
76
|
+
{
|
|
77
|
+
from: 1,
|
|
78
|
+
to: 3,
|
|
79
|
+
describe:
|
|
80
|
+
"layout 1 → 3: carry the decision log out of cache/, keep the policy config in place, drop the layout-1 credential files",
|
|
81
|
+
run: () => resetHome(1),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
from: 2,
|
|
85
|
+
to: 3,
|
|
86
|
+
describe:
|
|
87
|
+
"layout 2 → 3: carry config.toml and credentials.toml into JSON, move custom-policies/ back up into policies/, nest the policy config at the root",
|
|
88
|
+
run: () => resetHome(2),
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The steps that take `from` to {@link LAYOUT_VERSION}.
|
|
94
|
+
*
|
|
95
|
+
* Returns an empty array when the home is already current. THROWS when no step
|
|
96
|
+
* covers a layout, rather than returning a short chain — a partial plan silently
|
|
97
|
+
* leaves a home half-migrated, which is the one outcome `fp-reset.ts`'s header
|
|
98
|
+
* says is worse than refusing. `migrationCoverageGap()` below is the same check
|
|
99
|
+
* run over every layout at once, so CI catches it before a user does.
|
|
100
|
+
*/
|
|
101
|
+
export function planMigration(from: number, registry: readonly Migration[] = MIGRATIONS): Migration[] {
|
|
102
|
+
if (from >= LAYOUT_VERSION) return [];
|
|
103
|
+
const chain: Migration[] = [];
|
|
104
|
+
let at = from;
|
|
105
|
+
while (at < LAYOUT_VERSION) {
|
|
106
|
+
const step = registry.find((m) => m.from === at);
|
|
107
|
+
if (!step) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`no migration from layout ${at} to ${LAYOUT_VERSION} — this build cannot upgrade that home`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
chain.push(step);
|
|
113
|
+
at = step.to;
|
|
114
|
+
}
|
|
115
|
+
return chain;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Layouts with no way forward, and steps that cannot be reached.
|
|
120
|
+
*
|
|
121
|
+
* The CI guard. Bumping `LAYOUT_VERSION` without writing a step leaves every
|
|
122
|
+
* older home unable to upgrade — and the symptom shows up on a user's machine
|
|
123
|
+
* rather than in a test, because nothing in the normal path plans a chain for a
|
|
124
|
+
* layout the developer does not happen to have on disk.
|
|
125
|
+
*/
|
|
126
|
+
export function migrationCoverageGap(registry: readonly Migration[] = MIGRATIONS): string[] {
|
|
127
|
+
const problems: string[] = [];
|
|
128
|
+
for (let layout = 1; layout < LAYOUT_VERSION; layout += 1) {
|
|
129
|
+
try {
|
|
130
|
+
planMigration(layout, registry);
|
|
131
|
+
} catch {
|
|
132
|
+
problems.push(`layout ${layout} has no path to ${LAYOUT_VERSION}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const m of registry) {
|
|
136
|
+
if (m.to <= m.from) problems.push(`step ${m.from} → ${m.to} does not move forward`);
|
|
137
|
+
if (m.to > LAYOUT_VERSION) problems.push(`step ${m.from} → ${m.to} overshoots ${LAYOUT_VERSION}`);
|
|
138
|
+
}
|
|
139
|
+
const froms = registry.map((m) => m.from);
|
|
140
|
+
if (froms.length !== new Set(froms).size) problems.push("two steps read the same layout");
|
|
141
|
+
return problems;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── The ledger ───────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export interface LedgerEntry {
|
|
147
|
+
from: number;
|
|
148
|
+
to: number;
|
|
149
|
+
/** The CLI that ran it. Diagnostics only — the chain is keyed on layout. */
|
|
150
|
+
cli: string;
|
|
151
|
+
/** ISO-8601, so a support request can be lined up against a deploy. */
|
|
152
|
+
at: string;
|
|
153
|
+
durationMs: number;
|
|
154
|
+
ok: boolean;
|
|
155
|
+
/** Counts, so "did my history survive" is answerable from the file alone. */
|
|
156
|
+
removed?: number;
|
|
157
|
+
migrated?: number;
|
|
158
|
+
activity?: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function readLedger(): LedgerEntry[] {
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(readFileSync(migrationLedgerFile(), "utf8")) as unknown;
|
|
164
|
+
return Array.isArray(parsed) ? (parsed as LedgerEntry[]) : [];
|
|
165
|
+
} catch {
|
|
166
|
+
// Absent on every machine that has never migrated, which is most of them.
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Append one entry.
|
|
173
|
+
*
|
|
174
|
+
* Read-modify-write rather than an append-only line format, because the file is
|
|
175
|
+
* read as JSON by support and by `failproofai migrate --dry-run`, and a torn
|
|
176
|
+
* final line would make the whole thing unparseable. Two migrations never race:
|
|
177
|
+
* a migration only runs from a CLI command, and the onboarding lock already
|
|
178
|
+
* serialises those.
|
|
179
|
+
*/
|
|
180
|
+
function appendLedger(entry: LedgerEntry): void {
|
|
181
|
+
try {
|
|
182
|
+
mkdirSync(migrationsDir(), { recursive: true });
|
|
183
|
+
const all = [...readLedger(), entry];
|
|
184
|
+
writeFileSync(migrationLedgerFile(), `${JSON.stringify(all, null, 2)}\n`, "utf8");
|
|
185
|
+
} catch {
|
|
186
|
+
// A ledger we cannot write must never fail the migration it is recording.
|
|
187
|
+
// The `VERSION` stamp is what makes the migration durable; this is the audit
|
|
188
|
+
// trail, and losing a line of it costs a support conversation, not a home.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── The backup ───────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The files worth copying before a step runs.
|
|
196
|
+
*
|
|
197
|
+
* Small, and each irreplaceable: nothing regenerates a cloud token, a policy
|
|
198
|
+
* selection, or the settings that decide whether the machine fails closed. The
|
|
199
|
+
* two retired TOMLs are included because a step is about to delete them, and they
|
|
200
|
+
* are the only remaining copy of a layout-2 machine's enrolment.
|
|
201
|
+
*
|
|
202
|
+
* Deliberately NOT the whole home. `HOME_CLASSES` already stopped the migration
|
|
203
|
+
* deleting anything irreplaceable by design, so a full copy would insure against
|
|
204
|
+
* a threat that no longer exists — while costing a duplicate of `bin/` and
|
|
205
|
+
* `hook-activity/`, and needing a rename window that has to dodge a live daemon.
|
|
206
|
+
* What is left to insure against is a BUG in a step, and these files are where
|
|
207
|
+
* such a bug would hurt.
|
|
208
|
+
*/
|
|
209
|
+
/**
|
|
210
|
+
* One file to back up: where it lives, and what to call it in the backup.
|
|
211
|
+
*
|
|
212
|
+
* `as` exists because the backup directory is FLAT and two of these paths share a
|
|
213
|
+
* basename. It is only set where that happens, so every other entry keeps the
|
|
214
|
+
* name it has always been saved under — which matters, because `backup-layout<n>/`
|
|
215
|
+
* directories written by beta.17 and beta.18 are already on real machines and
|
|
216
|
+
* `restoreBackup()` has to keep finding them. Restructuring the backup into
|
|
217
|
+
* mirrored subdirectories would have been the tidier scheme and would have renamed
|
|
218
|
+
* data an older build already wrote: the same mistake as the four rename bugs
|
|
219
|
+
* above it in this changelog.
|
|
220
|
+
*/
|
|
221
|
+
interface BackedUpFile {
|
|
222
|
+
at: () => string;
|
|
223
|
+
as?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const BACKED_UP: BackedUpFile[] = [
|
|
227
|
+
{ at: configFile },
|
|
228
|
+
{ at: credentialsFile },
|
|
229
|
+
{ at: globalPolicyConfigFile },
|
|
230
|
+
{ at: versionFile },
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const BACKED_UP_LEGACY: BackedUpFile[] = [
|
|
234
|
+
{ at: legacy.configToml },
|
|
235
|
+
{ at: legacy.credentialsToml },
|
|
236
|
+
{ at: legacy.policyConfig },
|
|
237
|
+
// Layout 2's policy selection, at the NESTED path. `legacy.policyConfig` above
|
|
238
|
+
// is the layout-1 ROOT path, which layout 3 reuses and which the migration
|
|
239
|
+
// therefore leaves alone — so on the layout-2 leg it is usually absent, and
|
|
240
|
+
// listing it was the whole of this backup's claim to protect a policy
|
|
241
|
+
// selection. The file layout 2 actually holds it in is under
|
|
242
|
+
// `legacy.localPoliciesDir()`, which IS deleted. So the one leg where the
|
|
243
|
+
// selection is destroyed was the one leg with no copy of it, while
|
|
244
|
+
// `describePlan` reported a backup either way.
|
|
245
|
+
//
|
|
246
|
+
// Saved under a distinct name because the basename collides with the entry
|
|
247
|
+
// above: flat, they would overwrite each other on backup, and on restore the
|
|
248
|
+
// single surviving file would be copied back to BOTH paths — writing layout 2's
|
|
249
|
+
// nested selection over layout 3's live config.
|
|
250
|
+
{ at: legacy.localPolicyConfig, as: "local-policies-policies-config.json" },
|
|
251
|
+
// Layout 1's credentials, which the migration DELETES and which nothing
|
|
252
|
+
// regenerates. They were missing here while the carry that reads them was
|
|
253
|
+
// added, so on the layout-1 leg — the one a user upgrading from the published
|
|
254
|
+
// `latest` actually takes — the cloud token was removed with no copy kept. That
|
|
255
|
+
// is precisely the file this backup exists for, and its absence made the backup
|
|
256
|
+
// most incomplete exactly where it mattered most.
|
|
257
|
+
{ at: legacy.cloudCredentials },
|
|
258
|
+
{ at: legacy.ingestCredentials },
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
/** The name a file is saved under inside `backup-layout<n>/`. */
|
|
262
|
+
function backupNameOf(f: BackedUpFile): string {
|
|
263
|
+
return f.as ?? basename(f.at());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The files that exist right now and would be backed up, each exactly once.
|
|
268
|
+
*
|
|
269
|
+
* ONE function, because there were two: `backupBeforeMigrating` deduped by path
|
|
270
|
+
* and `describePlan` built the same list again without deduping, so
|
|
271
|
+
* `migrate --dry-run` printed `policies-config.json` TWICE while the backup
|
|
272
|
+
* correctly wrote it once. Seen on a real machine, in the one command whose
|
|
273
|
+
* entire job is to state accurately what is about to happen.
|
|
274
|
+
*
|
|
275
|
+
* The duplicate is not a typo waiting to be spotted, it is structural:
|
|
276
|
+
* `legacy.policyConfig()` and `globalPolicyConfigFile()` are the SAME path,
|
|
277
|
+
* because layout 3 put the policy config back exactly where layout 1 kept it. Any
|
|
278
|
+
* caller that walks both lists sees it twice. So the walk lives here once and
|
|
279
|
+
* nobody walks those arrays directly — which is the same "state it once, derive
|
|
280
|
+
* the rest" move `resettablePaths()` makes over `HOME_CLASSES`.
|
|
281
|
+
*/
|
|
282
|
+
function filesToBackUp(): { path: string; as: string }[] {
|
|
283
|
+
const seen = new Map<string, string>();
|
|
284
|
+
for (const f of [...BACKED_UP, ...BACKED_UP_LEGACY]) {
|
|
285
|
+
const path = f.at();
|
|
286
|
+
if (existsSync(path)) seen.set(path, backupNameOf(f));
|
|
287
|
+
}
|
|
288
|
+
return [...seen].map(([path, as]) => ({ path, as }));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Copy the irreplaceable files aside. Returns the basenames it saved. */
|
|
292
|
+
export function backupBeforeMigrating(from: number): string[] {
|
|
293
|
+
const dir = migrationBackupDir(from);
|
|
294
|
+
const saved: string[] = [];
|
|
295
|
+
try {
|
|
296
|
+
// A previous attempt's backup is replaced, not merged: a half-old, half-new
|
|
297
|
+
// set is not a restorable snapshot of anything.
|
|
298
|
+
rmSync(dir, { recursive: true, force: true });
|
|
299
|
+
mkdirSync(dir, { recursive: true });
|
|
300
|
+
} catch {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
for (const { path: source, as } of filesToBackUp()) {
|
|
304
|
+
try {
|
|
305
|
+
copyFileSync(source, resolve(dir, as));
|
|
306
|
+
saved.push(as);
|
|
307
|
+
} catch {
|
|
308
|
+
// One unreadable file must not stop the rest being saved, and must not
|
|
309
|
+
// stop the migration — the backup is insurance, not a precondition.
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return saved.sort((a, b) => a.localeCompare(b));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── Running ──────────────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
export interface MigrationRun {
|
|
318
|
+
/** The layout found on disk. */
|
|
319
|
+
from: number;
|
|
320
|
+
steps: { from: number; to: number; ok: boolean }[];
|
|
321
|
+
/** Basenames copied into `migrations/backup-layout<n>/`. */
|
|
322
|
+
backedUp: string[];
|
|
323
|
+
/** Merged outcome of every step that ran. */
|
|
324
|
+
outcome: ResetOutcome;
|
|
325
|
+
/** Set when a step threw. The home is left where that step got to. */
|
|
326
|
+
failed?: { from: number; to: number; error: string };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Run the chain from `from` to {@link LAYOUT_VERSION}, recording each step.
|
|
331
|
+
*
|
|
332
|
+
* The backup is taken ONCE, before the first step, against the layout actually
|
|
333
|
+
* found — the point is to be able to get back to where the machine started, not
|
|
334
|
+
* to snapshot each intermediate state.
|
|
335
|
+
*
|
|
336
|
+
* A step that throws STOPS the chain. It does not roll back: `resetHome` stamps
|
|
337
|
+
* `VERSION` at the end of a successful run, so a step that failed part-way leaves
|
|
338
|
+
* a home still marked with the old layout, which is the state the next command
|
|
339
|
+
* will correctly try to migrate again. Continuing past a failure would be the
|
|
340
|
+
* genuinely dangerous option — it would run a step against a home whose
|
|
341
|
+
* preconditions the previous step never established.
|
|
342
|
+
*/
|
|
343
|
+
export function runMigrations(
|
|
344
|
+
from: number,
|
|
345
|
+
/**
|
|
346
|
+
* The chain to run. Defaults to the real plan; a caller passes one only to
|
|
347
|
+
* exercise a path the real registry cannot currently produce — a step that
|
|
348
|
+
* THROWS, which is the branch that decides whether a machine can be left
|
|
349
|
+
* half-migrated and is therefore the one most worth a test.
|
|
350
|
+
*/
|
|
351
|
+
chain: readonly Migration[] = planMigration(from),
|
|
352
|
+
): MigrationRun {
|
|
353
|
+
const backedUp = backupBeforeMigrating(from);
|
|
354
|
+
const steps: MigrationRun["steps"] = [];
|
|
355
|
+
const outcome: ResetOutcome = {
|
|
356
|
+
removed: [],
|
|
357
|
+
migrated: [],
|
|
358
|
+
activity: [],
|
|
359
|
+
policyConfig: [],
|
|
360
|
+
spooled: [],
|
|
361
|
+
from,
|
|
362
|
+
};
|
|
363
|
+
let failed: MigrationRun["failed"];
|
|
364
|
+
|
|
365
|
+
for (const step of chain) {
|
|
366
|
+
const startedAt = Date.now();
|
|
367
|
+
try {
|
|
368
|
+
const result = step.run();
|
|
369
|
+
outcome.removed.push(...result.removed);
|
|
370
|
+
outcome.migrated.push(...result.migrated);
|
|
371
|
+
outcome.activity.push(...result.activity);
|
|
372
|
+
outcome.policyConfig.push(...result.policyConfig);
|
|
373
|
+
outcome.spooled.push(...result.spooled);
|
|
374
|
+
steps.push({ from: step.from, to: step.to, ok: true });
|
|
375
|
+
appendLedger({
|
|
376
|
+
from: step.from,
|
|
377
|
+
to: step.to,
|
|
378
|
+
cli: cliVersion,
|
|
379
|
+
at: new Date(startedAt).toISOString(),
|
|
380
|
+
durationMs: Date.now() - startedAt,
|
|
381
|
+
ok: true,
|
|
382
|
+
removed: result.removed.length,
|
|
383
|
+
migrated: result.migrated.length,
|
|
384
|
+
activity: result.activity.length,
|
|
385
|
+
});
|
|
386
|
+
} catch (err) {
|
|
387
|
+
steps.push({ from: step.from, to: step.to, ok: false });
|
|
388
|
+
failed = {
|
|
389
|
+
from: step.from,
|
|
390
|
+
to: step.to,
|
|
391
|
+
error: err instanceof Error ? err.message : String(err),
|
|
392
|
+
};
|
|
393
|
+
appendLedger({
|
|
394
|
+
from: step.from,
|
|
395
|
+
to: step.to,
|
|
396
|
+
cli: cliVersion,
|
|
397
|
+
at: new Date(startedAt).toISOString(),
|
|
398
|
+
durationMs: Date.now() - startedAt,
|
|
399
|
+
ok: false,
|
|
400
|
+
});
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return { from, steps, backedUp, outcome, failed };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** What `--dry-run` prints: the chain, and what was saved last time. */
|
|
409
|
+
export function describePlan(from: number): string[] {
|
|
410
|
+
if (from >= LAYOUT_VERSION) {
|
|
411
|
+
return [`${failproofaiHome()} is already at layout ${LAYOUT_VERSION}. Nothing to migrate.`];
|
|
412
|
+
}
|
|
413
|
+
let chain: Migration[];
|
|
414
|
+
try {
|
|
415
|
+
chain = planMigration(from);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
return [err instanceof Error ? err.message : String(err)];
|
|
418
|
+
}
|
|
419
|
+
return [
|
|
420
|
+
`Layout ${from} on disk; this build speaks ${LAYOUT_VERSION}.`,
|
|
421
|
+
`${chain.length} step(s) would run:`,
|
|
422
|
+
...chain.map((m) => ` ${m.from} → ${m.to} ${m.describe}`),
|
|
423
|
+
``,
|
|
424
|
+
`These would be copied to ${migrationBackupDir(from)} first:`,
|
|
425
|
+
...filesToBackUp().map((f) => ` ${f.as}`),
|
|
426
|
+
``,
|
|
427
|
+
`Settings, cloud enrolment, policy selection, your own policy files, decision`,
|
|
428
|
+
`history and undelivered events are carried, not removed — see \`HOME_CLASSES\`.`,
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Restore a backup taken before the layout-`n` migration. */
|
|
433
|
+
export function restoreBackup(layout: number): string[] {
|
|
434
|
+
const dir = migrationBackupDir(layout);
|
|
435
|
+
if (!existsSync(dir)) return [];
|
|
436
|
+
const restored: string[] = [];
|
|
437
|
+
for (const f of [...BACKED_UP, ...BACKED_UP_LEGACY]) {
|
|
438
|
+
const target = f.at();
|
|
439
|
+
const source = resolve(dir, backupNameOf(f));
|
|
440
|
+
if (!existsSync(source)) continue;
|
|
441
|
+
try {
|
|
442
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
443
|
+
copyFileSync(source, target);
|
|
444
|
+
restored.push(backupNameOf(f));
|
|
445
|
+
} catch {
|
|
446
|
+
// Report what did land rather than claiming a restore that did not.
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return restored.sort((a, b) => a.localeCompare(b));
|
|
450
|
+
}
|