failproofai 1.0.7 → 1.0.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- 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 +4 -4
- 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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
- 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/_not-found/__PAGE__.segment.rsc +14 -14
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +9 -7
- 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 +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +6 -6
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +11 -10
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +7 -6
- 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/server-reference-manifest.json +52 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -7
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bd3mje._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0o4xkpl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1-i_gzc._.js → _1-7sqrb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_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/app_settings_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- 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 +67 -23
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
- package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0ahfwmbkpgfw4.js} +1 -1
- package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
- package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 1v_tp3hm8wyhe.js} +1 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/2vo7qbdbvnc0o.js +69 -0
- package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 2z7i-yg59w4pn.js} +1 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
- package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 3bgot5v6f6s3c.js} +1 -1
- package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +6 -0
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +604 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +569 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +14 -3
- package/.next/standalone/app/settings/jev-panel.tsx +630 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +3 -2
- package/bin/failproofai.mjs +148 -4
- package/dist/cli.mjs +18537 -9492
- package/dist/index.js +19 -1
- package/dist/worker.mjs +10421 -3770
- package/package.json +10 -10
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +53 -2
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +195 -8
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +57 -8
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +98 -10
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/effective-reviewers.ts +243 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +303 -7
- package/src/hooks/hook-activity-store.ts +118 -6
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +2172 -0
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +45 -7
- package/src/hooks/pack-cli.ts +568 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +489 -7
- package/src/hooks/pack-store.ts +194 -11
- package/src/hooks/policy-authority.ts +386 -0
- package/src/hooks/policy-catalog.ts +206 -0
- package/src/hooks/policy-evaluator.ts +963 -796
- package/src/hooks/policy-registry.ts +27 -1
- package/src/hooks/policy-reviewability.ts +258 -0
- package/src/hooks/policy-types.ts +128 -0
- package/src/hooks/semantic/combine.ts +581 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +408 -0
- package/src/hooks/semantic/envelope.ts +1255 -0
- package/src/hooks/semantic/evaluator.ts +556 -0
- package/src/hooks/semantic/facts.ts +298 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +1155 -0
- package/src/hooks/semantic/jev-config.ts +1142 -0
- package/src/hooks/semantic/jev-review.ts +389 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +427 -0
- package/src/hooks/semantic/pack-policies.ts +294 -0
- package/src/hooks/semantic/policies.ts +597 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2937 -0
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +156 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +121 -27
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
- package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What connecting to, and disconnecting from, FailproofAI Cloud does to Jev.
|
|
3
|
+
*
|
|
4
|
+
* `config --token` with a key that carries `jev:evaluate` stores the key in the
|
|
5
|
+
* `jev` slot of `credentials.json` (`cloud-connection.ts`) and then, ONLY when
|
|
6
|
+
* this machine has no `jev.json` at all, writes one that turns Jev on through
|
|
7
|
+
* FailproofAI Cloud in shadow mode — logged, never enforced, until its owner
|
|
8
|
+
* says otherwise. Never under `--no-transcripts`: that connection asked for
|
|
9
|
+
* decisions only, so it stores the key and says Jev is available, and
|
|
10
|
+
* `jev setup --provider failproofai` is the opt-in. `config --disconnect`
|
|
11
|
+
* clears the slot and deletes `jev.json` only when that file names the Cloud
|
|
12
|
+
* provider and is not switched off.
|
|
13
|
+
*
|
|
14
|
+
* # Never overwrite
|
|
15
|
+
*
|
|
16
|
+
* A `jev.json` that exists is somebody's decision: a BYOK endpoint with its own
|
|
17
|
+
* key, a Cloud file switched to enforce or off, a hand-written one. Connecting
|
|
18
|
+
* is not a reason to replace any of those, so the file is created with a
|
|
19
|
+
* no-clobber primitive — written to a temp file, then HARD-LINKED into place,
|
|
20
|
+
* which fails if anything is already there — rather than checked for and then
|
|
21
|
+
* written: between a check and a rename another writer can land, and a rename
|
|
22
|
+
* replaces whatever it finds. The link is also what keeps a hook from ever
|
|
23
|
+
* reading a half-written file.
|
|
24
|
+
*
|
|
25
|
+
* # Only the Cloud's own file is removed
|
|
26
|
+
*
|
|
27
|
+
* Disconnect removes the Cloud key, so a Cloud `jev.json` would be left naming a
|
|
28
|
+
* route with no key — harmless (`not-connected`, Jev off) but a lie on every
|
|
29
|
+
* status screen. A BYOK file keeps working without the Cloud and is not the
|
|
30
|
+
* Cloud's to delete, so it stays; so does a file this build cannot read, and a
|
|
31
|
+
* Cloud file switched off — `--mode off` is the owner's opt-out, and deleting
|
|
32
|
+
* it would let the next connect write a fresh shadow file. The
|
|
33
|
+
* file is moved aside before it is judged, so the file judged is the file
|
|
34
|
+
* deleted — see `removeCloudJevConfig`.
|
|
35
|
+
*
|
|
36
|
+
* Loaded lazily by its two callers, so connecting without Jev pulls in nothing
|
|
37
|
+
* of it and the hook path never does.
|
|
38
|
+
*/
|
|
39
|
+
import {
|
|
40
|
+
chmodSync,
|
|
41
|
+
closeSync,
|
|
42
|
+
constants as fsConstants,
|
|
43
|
+
existsSync,
|
|
44
|
+
fstatSync,
|
|
45
|
+
linkSync,
|
|
46
|
+
lstatSync,
|
|
47
|
+
mkdirSync,
|
|
48
|
+
openSync,
|
|
49
|
+
readFileSync,
|
|
50
|
+
readSync,
|
|
51
|
+
renameSync,
|
|
52
|
+
rmSync,
|
|
53
|
+
statSync,
|
|
54
|
+
unlinkSync,
|
|
55
|
+
writeFileSync,
|
|
56
|
+
type Stats,
|
|
57
|
+
} from "node:fs";
|
|
58
|
+
import { dirname } from "node:path";
|
|
59
|
+
import { randomBytes } from "node:crypto";
|
|
60
|
+
import {
|
|
61
|
+
DEFAULT_JEV_MODE,
|
|
62
|
+
JEV_CLOUD_PROVIDER,
|
|
63
|
+
inspectJevConfig,
|
|
64
|
+
jevCloudBaseUrl,
|
|
65
|
+
jevConfigPath,
|
|
66
|
+
readJevConfigFileForUpdate,
|
|
67
|
+
} from "./semantic/jev-config";
|
|
68
|
+
|
|
69
|
+
/** The mode a Cloud `jev.json` starts in (the user's decision: log first, enforce later). */
|
|
70
|
+
export const CLOUD_JEV_INITIAL_MODE = "shadow" as const;
|
|
71
|
+
|
|
72
|
+
export type CloudJevConfigWrite =
|
|
73
|
+
| { status: "written"; path: string; mode: typeof CLOUD_JEV_INITIAL_MODE; baseUrl: string }
|
|
74
|
+
/** A `jev.json` already exists and was left exactly as it was. */
|
|
75
|
+
| {
|
|
76
|
+
status: "kept";
|
|
77
|
+
path: string;
|
|
78
|
+
/** The provider it names, when it can be read. */
|
|
79
|
+
provider: string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Set when it names the Cloud provider on ANOTHER origin than the one just
|
|
82
|
+
* connected to: it stays off (`the key is only sent where it was issued`)
|
|
83
|
+
* until pointed at this one, which is worth one line at connect time.
|
|
84
|
+
*/
|
|
85
|
+
otherOrigin?: string;
|
|
86
|
+
/** Set when the kept file leaves Jev off here: refused, or switched off. */
|
|
87
|
+
jevOff?: { why: "refused"; problem: string; fix?: string } | { why: "off" };
|
|
88
|
+
}
|
|
89
|
+
| { status: "error"; path: string; problem: string };
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create `jev.json` for the Cloud provider unless one exists. Never throws and
|
|
93
|
+
* never replaces a file.
|
|
94
|
+
*/
|
|
95
|
+
export function writeCloudJevConfigIfAbsent(cloudBase: string): CloudJevConfigWrite {
|
|
96
|
+
const path = jevConfigPath();
|
|
97
|
+
let baseUrl: string;
|
|
98
|
+
try {
|
|
99
|
+
baseUrl = jevCloudBaseUrl(cloudBase);
|
|
100
|
+
} catch {
|
|
101
|
+
return { status: "error", path, problem: "the FailproofAI Cloud URL is not a URL" };
|
|
102
|
+
}
|
|
103
|
+
if (existsSync(path)) return kept(path, cloudBase);
|
|
104
|
+
|
|
105
|
+
const body = `${JSON.stringify({ provider: JEV_CLOUD_PROVIDER, baseUrl, mode: CLOUD_JEV_INITIAL_MODE }, null, 2)}\n`;
|
|
106
|
+
const dir = dirname(path);
|
|
107
|
+
const tmp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
108
|
+
try {
|
|
109
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
110
|
+
writeFileSync(tmp, body, { mode: 0o600, flag: "wx" });
|
|
111
|
+
chmodSync(tmp, 0o600);
|
|
112
|
+
try {
|
|
113
|
+
linkSync(tmp, path);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if ((err as NodeJS.ErrnoException).code === "EEXIST") return kept(path, cloudBase);
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return { status: "error", path, problem: `could not write ${path} (${(err as NodeJS.ErrnoException).code ?? "error"})` };
|
|
120
|
+
} finally {
|
|
121
|
+
try {
|
|
122
|
+
rmSync(tmp, { force: true });
|
|
123
|
+
} catch {
|
|
124
|
+
// A stray temp file holds no key; nothing to do.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
tightenDir(dir);
|
|
128
|
+
return { status: "written", path, mode: CLOUD_JEV_INITIAL_MODE, baseUrl };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* What `writeCloudJevConfigIfAbsent` would report for a `jev.json` that is
|
|
133
|
+
* already there — without writing anything when there is none. For a connect
|
|
134
|
+
* that must not switch Jev on (`--no-transcripts`) but still says what an
|
|
135
|
+
* existing file does. Null when there is no file.
|
|
136
|
+
*/
|
|
137
|
+
export function existingJevConfig(cloudBase: string): CloudJevConfigWrite | null {
|
|
138
|
+
const path = jevConfigPath();
|
|
139
|
+
return existsSync(path) ? kept(path, cloudBase) : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The mode Jev runs in through FailproofAI Cloud right now, or null when it
|
|
144
|
+
* does not: no `jev.json`, another provider, switched off, or no usable key.
|
|
145
|
+
* Asked the way a hook asks (`inspectJevConfig`), so "on" here means a tool
|
|
146
|
+
* call's next Jev request really goes to FailproofAI Cloud.
|
|
147
|
+
*/
|
|
148
|
+
export function cloudJevRunningMode(): "shadow" | "enforce" | null {
|
|
149
|
+
try {
|
|
150
|
+
const r = inspectJevConfig();
|
|
151
|
+
if (r.status !== "ok" || r.config.provider !== JEV_CLOUD_PROVIDER) return null;
|
|
152
|
+
return r.config.mode === "off" ? null : (r.config.mode ?? DEFAULT_JEV_MODE);
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function kept(path: string, cloudBase: string): CloudJevConfigWrite {
|
|
159
|
+
const raw = readJevConfigFileForUpdate()?.raw ?? null;
|
|
160
|
+
const provider = typeof raw?.provider === "string" ? raw.provider : null;
|
|
161
|
+
let otherOrigin: string | undefined;
|
|
162
|
+
if (provider === JEV_CLOUD_PROVIDER && typeof raw?.baseUrl === "string") {
|
|
163
|
+
try {
|
|
164
|
+
const theirs = new URL(raw.baseUrl).origin;
|
|
165
|
+
if (theirs !== new URL(cloudBase).origin) otherOrigin = theirs;
|
|
166
|
+
} catch {
|
|
167
|
+
// Unreadable: the loader will say so; nothing to add here.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Not inspected (that would add the origin mismatch as a second refusal), but
|
|
171
|
+
// a stored `off` survives `jev setup --provider failproofai`, so say it.
|
|
172
|
+
if (otherOrigin) return { status: "kept", path, provider, otherOrigin, ...(raw?.mode === "off" ? { jevOff: { why: "off" as const } } : {}) };
|
|
173
|
+
// Asked the way a hook asks, after the new Jev key was stored, so "off" here
|
|
174
|
+
// is what the next tool call sees.
|
|
175
|
+
let jevOff: Extract<CloudJevConfigWrite, { status: "kept" }>["jevOff"];
|
|
176
|
+
try {
|
|
177
|
+
const r = inspectJevConfig();
|
|
178
|
+
if (r.status === "refused") jevOff = { why: "refused", problem: r.problem, ...(r.fix ? { fix: r.fix } : {}) };
|
|
179
|
+
else if (r.status === "off") jevOff = { why: "off" };
|
|
180
|
+
} catch {
|
|
181
|
+
// Nothing to add; the kept line stands on its own.
|
|
182
|
+
}
|
|
183
|
+
return { status: "kept", path, provider, ...(jevOff ? { jevOff } : {}) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The directory's group/other WRITE bits, taken off exactly as `jev setup`
|
|
188
|
+
* takes them off: a directory others can write into defeats the file's 0600.
|
|
189
|
+
*/
|
|
190
|
+
function tightenDir(dir: string): void {
|
|
191
|
+
if (process.platform === "win32") return;
|
|
192
|
+
try {
|
|
193
|
+
const before = statSync(dir).mode & 0o777;
|
|
194
|
+
if ((before & 0o022) !== 0) chmodSync(dir, before & ~0o022);
|
|
195
|
+
} catch {
|
|
196
|
+
// Not fatal: the loader refuses a directory it will not read from.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type CloudJevConfigRemoval =
|
|
201
|
+
| { status: "removed"; path: string }
|
|
202
|
+
| { status: "absent"; path: string }
|
|
203
|
+
/** The Cloud's `jev.json`, switched off: left in place so the opt-out outlives a reconnect. */
|
|
204
|
+
| { status: "kept-off"; path: string }
|
|
205
|
+
/** A `jev.json` that is not the Cloud's — BYOK, or unreadable — left in place. */
|
|
206
|
+
| { status: "kept"; path: string; provider: string | null }
|
|
207
|
+
/**
|
|
208
|
+
* Not the Cloud's, and another `jev.json` was written in its place while it
|
|
209
|
+
* was being checked. Neither is deleted: the new one stays at `path`, the one
|
|
210
|
+
* that was there is kept at `setAside`.
|
|
211
|
+
*/
|
|
212
|
+
| { status: "set-aside"; path: string; provider: string | null; setAside: string }
|
|
213
|
+
| {
|
|
214
|
+
status: "error";
|
|
215
|
+
path: string;
|
|
216
|
+
problem: string;
|
|
217
|
+
/** Where a file that is not the Cloud's was left, when it could not be put back at `path`. */
|
|
218
|
+
setAside?: string;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Delete `jev.json` iff it names the FailproofAI Cloud provider and is not
|
|
223
|
+
* switched off (`mode: "off"`). Never throws,
|
|
224
|
+
* and never deletes a file it has not judged.
|
|
225
|
+
*
|
|
226
|
+
* # Moved aside, then judged
|
|
227
|
+
*
|
|
228
|
+
* Checking the file's provider and then unlinking the PATH is a race: `jev
|
|
229
|
+
* setup` (or the dashboard's save) replaces jev.json by atomic rename, and a
|
|
230
|
+
* BYOK file renamed into place between the check and the unlink is the file
|
|
231
|
+
* the unlink removes. So the file is first RENAMED to a name only this call
|
|
232
|
+
* knows — atomic, and whatever lands at the path afterwards is untouched by
|
|
233
|
+
* anything below — and it is that file, which nobody else can now replace,
|
|
234
|
+
* whose provider decides. The Cloud's is deleted. Anything else is linked back
|
|
235
|
+
* to the path (same inode, bytes and mode) with a no-clobber link — or, where
|
|
236
|
+
* the filesystem has no hard links, put back by the fallbacks in `putBack` —
|
|
237
|
+
* so a file written at the path meanwhile is never overwritten either; when one
|
|
238
|
+
* was, the original is left beside it under the set-aside name rather than
|
|
239
|
+
* deleted, and when nothing can put it back, the result says where it is.
|
|
240
|
+
*/
|
|
241
|
+
export function removeCloudJevConfig(): CloudJevConfigRemoval {
|
|
242
|
+
const path = jevConfigPath();
|
|
243
|
+
let st: Stats;
|
|
244
|
+
try {
|
|
245
|
+
st = lstatSync(path);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
248
|
+
if (code === "ENOENT" || code === "ENOTDIR") return { status: "absent", path };
|
|
249
|
+
return { status: "error", path, problem: `could not read ${path} (${code ?? "error"})` };
|
|
250
|
+
}
|
|
251
|
+
// Only a regular file can be the one `config --token` wrote. A symlink, a
|
|
252
|
+
// directory or a FIFO in its place is somebody else's, and is not touched.
|
|
253
|
+
if (!st.isFile()) return { status: "kept", path, provider: null };
|
|
254
|
+
|
|
255
|
+
const aside = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.disconnecting`;
|
|
256
|
+
try {
|
|
257
|
+
renameSync(path, aside);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
260
|
+
if (code === "ENOENT") return { status: "absent", path };
|
|
261
|
+
return { status: "error", path, problem: `could not remove ${path} (${code ?? "error"})` };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { provider, off } = fieldsOf(aside);
|
|
265
|
+
if (provider === JEV_CLOUD_PROVIDER && !off) {
|
|
266
|
+
try {
|
|
267
|
+
unlinkSync(aside);
|
|
268
|
+
return { status: "removed", path };
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return { status: "error", path, problem: `could not remove ${aside} (${(err as NodeJS.ErrnoException).code ?? "error"})` };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Not the Cloud's: back where it was, and never over a file written since.
|
|
275
|
+
const back = putBack(aside, path, st.mode & 0o777);
|
|
276
|
+
if (back === "restored") return provider === JEV_CLOUD_PROVIDER ? { status: "kept-off", path } : { status: "kept", path, provider };
|
|
277
|
+
if (back === "occupied") return { status: "set-aside", path, provider, setAside: aside };
|
|
278
|
+
return {
|
|
279
|
+
status: "error",
|
|
280
|
+
path,
|
|
281
|
+
problem: `${path} is not FailproofAI Cloud's and was kept at ${aside}; putting it back failed (${back.code})`,
|
|
282
|
+
setAside: aside,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Put a set-aside file back at `path`, never over a file that is there now:
|
|
288
|
+
* `restored`, `occupied` (another file is at `path`, and `aside` is left as it
|
|
289
|
+
* is), or the error that stopped every attempt.
|
|
290
|
+
*
|
|
291
|
+
* 1. A hard link: atomic, the same inode, bytes and mode, and it fails with
|
|
292
|
+
* EEXIST when anything is at the path.
|
|
293
|
+
* 2. Some filesystems have no hard links (FAT and exFAT, some network and FUSE
|
|
294
|
+
* mounts answer EPERM or ENOTSUP), so next a COPY created exclusively (`wx`,
|
|
295
|
+
* O_EXCL): just as no-clobber, with the file's own mode (less the umask,
|
|
296
|
+
* so never looser). A hook reading the file mid-copy sees a truncated one
|
|
297
|
+
* and refuses it — Jev off for that call, the regex verdict stands — never
|
|
298
|
+
* another config.
|
|
299
|
+
* 3. When no copy can be created either (no room for one, say), a rename —
|
|
300
|
+
* which replaces what it finds, so only while the path is still empty. The
|
|
301
|
+
* moment between that check and the rename is the one place a write landing
|
|
302
|
+
* at the path could be lost, and it is reached only when neither no-clobber
|
|
303
|
+
* way works.
|
|
304
|
+
*
|
|
305
|
+
* Until one works, the file sits at `aside`, and the caller says where.
|
|
306
|
+
*/
|
|
307
|
+
function putBack(aside: string, path: string, mode: number): "restored" | "occupied" | { code: string } {
|
|
308
|
+
const codeOf = (err: unknown) => (err as NodeJS.ErrnoException).code ?? "error";
|
|
309
|
+
try {
|
|
310
|
+
linkSync(aside, path);
|
|
311
|
+
discard(aside);
|
|
312
|
+
return "restored";
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (codeOf(err) === "EEXIST") return "occupied";
|
|
315
|
+
}
|
|
316
|
+
let created = false;
|
|
317
|
+
try {
|
|
318
|
+
const bytes = readFileSync(aside);
|
|
319
|
+
const fd = openSync(path, "wx", mode);
|
|
320
|
+
created = true;
|
|
321
|
+
try {
|
|
322
|
+
writeFileSync(fd, bytes);
|
|
323
|
+
} finally {
|
|
324
|
+
closeSync(fd);
|
|
325
|
+
}
|
|
326
|
+
discard(aside);
|
|
327
|
+
return "restored";
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (codeOf(err) === "EEXIST") return "occupied";
|
|
330
|
+
// Created, then the write failed: a truncated file is at the path now, and
|
|
331
|
+
// the original is still at `aside`. Not renamed over — the path is not
|
|
332
|
+
// empty, and whose file is there by now is not knowable.
|
|
333
|
+
if (created) return { code: codeOf(err) };
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
lstatSync(path);
|
|
337
|
+
return "occupied";
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if (codeOf(err) !== "ENOENT") return { code: codeOf(err) };
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
renameSync(aside, path);
|
|
343
|
+
return "restored";
|
|
344
|
+
} catch (err) {
|
|
345
|
+
return { code: codeOf(err) };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Drop the set-aside name once the file is back. A leftover is 0600 in an owner-only directory, and holds nothing the file at the path does not. */
|
|
350
|
+
function discard(aside: string): void {
|
|
351
|
+
try {
|
|
352
|
+
unlinkSync(aside);
|
|
353
|
+
} catch {
|
|
354
|
+
// Harmless; see above.
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** The `provider` a config file names, and whether it is switched off, read without following links or blocking on a FIFO. */
|
|
359
|
+
function fieldsOf(file: string): { provider: string | null; off: boolean } {
|
|
360
|
+
const none = { provider: null, off: false };
|
|
361
|
+
let fd: number;
|
|
362
|
+
try {
|
|
363
|
+
fd = openSync(file, fsConstants.O_RDONLY | (fsConstants.O_NONBLOCK ?? 0) | (fsConstants.O_NOFOLLOW ?? 0));
|
|
364
|
+
} catch {
|
|
365
|
+
return none;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const st = fstatSync(fd);
|
|
369
|
+
if (!st.isFile() || st.size > MAX_JEV_CONFIG_BYTES) return none;
|
|
370
|
+
const buf = Buffer.alloc(st.size);
|
|
371
|
+
let off = 0;
|
|
372
|
+
while (off < buf.length) {
|
|
373
|
+
const n = readSync(fd, buf, off, buf.length - off, off);
|
|
374
|
+
if (n === 0) break;
|
|
375
|
+
off += n;
|
|
376
|
+
}
|
|
377
|
+
const parsed: unknown = JSON.parse(buf.subarray(0, off).toString("utf8"));
|
|
378
|
+
const obj = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
|
|
379
|
+
return { provider: typeof obj.provider === "string" ? obj.provider : null, off: obj.mode === "off" };
|
|
380
|
+
} catch {
|
|
381
|
+
return none;
|
|
382
|
+
} finally {
|
|
383
|
+
try {
|
|
384
|
+
closeSync(fd);
|
|
385
|
+
} catch {
|
|
386
|
+
// Nothing useful to do.
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** The loader's own bound on a config file (`jev-config.ts`); nothing legitimate is near it. */
|
|
392
|
+
const MAX_JEV_CONFIG_BYTES = 64 * 1024;
|
|
@@ -191,7 +191,13 @@ export async function createEsmShim(
|
|
|
191
191
|
const shimCode = [
|
|
192
192
|
`import _cjs from '${distUrl}';`,
|
|
193
193
|
`export const customPolicies = _cjs.customPolicies;`,
|
|
194
|
+
// Every name a policy file may import has to be listed HERE as well as in
|
|
195
|
+
// `src/index.ts`: this shim is an ESM module, so importing a name it does not
|
|
196
|
+
// declare is a link-time error, not an undefined at runtime. A pack entry
|
|
197
|
+
// calling `semanticPolicies.add` would fail to load entirely.
|
|
198
|
+
`export const semanticPolicies = _cjs.semanticPolicies;`,
|
|
194
199
|
`export const getCustomHooks = _cjs.getCustomHooks;`,
|
|
200
|
+
`export const getSemanticRegistrations = _cjs.getSemanticRegistrations;`,
|
|
195
201
|
`export const clearCustomHooks = _cjs.clearCustomHooks;`,
|
|
196
202
|
`export const allow = _cjs.allow;`,
|
|
197
203
|
`export const deny = _cjs.deny;`,
|
package/src/hooks/manager.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { configuredCustomPolicyPaths, readMergedHooksConfig, readScopedHooksConf
|
|
|
19
19
|
import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
|
|
20
20
|
import { BUILTIN_POLICIES } from "./builtin-policies";
|
|
21
21
|
import { loadCustomHooks, discoverPolicyFiles } from "./custom-hooks-loader";
|
|
22
|
+
import { getSemanticRegistrations } from "./custom-hooks-registry";
|
|
22
23
|
import { trackHookEvent } from "./hook-telemetry";
|
|
23
24
|
import { getInstanceId, hashToId } from "../../lib/telemetry-id";
|
|
24
25
|
import { CliError } from "../cli-error";
|
|
@@ -589,6 +590,13 @@ async function installHooksImpl(
|
|
|
589
590
|
console.error(`Error: ${msg}`);
|
|
590
591
|
process.exit(1);
|
|
591
592
|
}
|
|
593
|
+
const semanticCount = getSemanticRegistrations().length;
|
|
594
|
+
if (semanticCount > 0) {
|
|
595
|
+
console.error(
|
|
596
|
+
`Note: ${path} declares ${semanticCount} Jev check(s) with semanticPolicies.add. They take effect only in a ` +
|
|
597
|
+
"pack published with `failproofai publish`, and are never asked from a policy file.",
|
|
598
|
+
);
|
|
599
|
+
}
|
|
592
600
|
if (validatedHooks.length === 0) {
|
|
593
601
|
try {
|
|
594
602
|
await trackHookEvent(getInstanceId(), "custom_policy_validation_failed", {
|
|
@@ -1108,17 +1116,29 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
1108
1116
|
// `packCount` counts ENABLED policies, so a pack installed with everything
|
|
1109
1117
|
// switched off reaches zero the same way an empty machine does — and the
|
|
1110
1118
|
// advice for the two could not be more different. Telling somebody who has
|
|
1111
|
-
// just unticked all
|
|
1119
|
+
// just unticked all 39 to `policies add FailproofAI/policies` sends them to
|
|
1112
1120
|
// install what they already have, and doing it would change nothing: the
|
|
1113
1121
|
// selection is what is empty, not the shelf.
|
|
1114
|
-
|
|
1122
|
+
//
|
|
1123
|
+
// Nor is it "nothing" while a refused pack fails closed (the warning above
|
|
1124
|
+
// says what is denied), or while a pack's Jev checks are live with no regex
|
|
1125
|
+
// policy on.
|
|
1126
|
+
const { packsInstalled, jevChecks, failClosed } = (() => {
|
|
1115
1127
|
try {
|
|
1116
|
-
|
|
1128
|
+
const { packs, errors } = readInstalledPacks();
|
|
1129
|
+
return {
|
|
1130
|
+
packsInstalled: packs.length,
|
|
1131
|
+
jevChecks: packs.reduce((n, p) => n + (p.effect === "observe" ? 0 : (p.semantic?.length ?? 0)), 0),
|
|
1132
|
+
// The refusals `missingGuards` denies over (pack-failclosed.ts).
|
|
1133
|
+
failClosed: errors.some((e) => e.effect !== "observe" && !e.semanticOnly),
|
|
1134
|
+
};
|
|
1117
1135
|
} catch {
|
|
1118
|
-
return 0;
|
|
1136
|
+
return { packsInstalled: 0, jevChecks: 0, failClosed: false };
|
|
1119
1137
|
}
|
|
1120
1138
|
})();
|
|
1121
|
-
if (
|
|
1139
|
+
if (failClosed) {
|
|
1140
|
+
// Said by the pack section's warning, where the refused pack is named.
|
|
1141
|
+
} else if (packCount === 0 && packsInstalled === 0) {
|
|
1122
1142
|
footer.unshift(
|
|
1123
1143
|
nextStep(
|
|
1124
1144
|
`failproofai policies add ${CORE_SOURCE}`,
|
|
@@ -1128,6 +1148,14 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
1128
1148
|
note("Someone else's: failproofai policies add <owner>/<repo>", opts),
|
|
1129
1149
|
note("Look first: failproofai policies show <owner>/<repo>", opts),
|
|
1130
1150
|
);
|
|
1151
|
+
} else if (packCount === 0 && jevChecks > 0) {
|
|
1152
|
+
footer.unshift(
|
|
1153
|
+
note(
|
|
1154
|
+
`No regex policy is on; ${jevChecks} Jev check${jevChecks === 1 ? "" : "s"} from ` +
|
|
1155
|
+
`${packsInstalled === 1 ? "the pack" : "the packs"} above apply where Jev is configured (failproofai jev status).`,
|
|
1156
|
+
opts,
|
|
1157
|
+
),
|
|
1158
|
+
);
|
|
1131
1159
|
} else if (packCount === 0) {
|
|
1132
1160
|
footer.unshift(
|
|
1133
1161
|
nextStep(
|
|
@@ -1292,7 +1320,9 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
1292
1320
|
const { packs, errors } = readInstalledPacks();
|
|
1293
1321
|
for (const pack of packs) {
|
|
1294
1322
|
const taken = pack.enabled ?? pack.policies.map((p) => p.name);
|
|
1295
|
-
|
|
1323
|
+
// The source too: the id is whatever the manifest claims, the source is
|
|
1324
|
+
// the repository this CLI actually fetched it from.
|
|
1325
|
+
groups.push(rule(`Pack — ${pack.id}@${pack.version} · ${pack.source}`, opts));
|
|
1296
1326
|
groups.push(
|
|
1297
1327
|
table(
|
|
1298
1328
|
{
|
|
@@ -1319,7 +1349,15 @@ export async function listHooks(cwd?: string): Promise<void> {
|
|
|
1319
1349
|
if (errors.length > 0) {
|
|
1320
1350
|
groups.push(
|
|
1321
1351
|
warning(
|
|
1322
|
-
errors.
|
|
1352
|
+
errors.flatMap((err) => [
|
|
1353
|
+
`pack ${err.id ?? "(unnamed)"} will not load: ${err.reason}`,
|
|
1354
|
+
...(err.effect !== "observe" && !err.semanticOnly
|
|
1355
|
+
? [
|
|
1356
|
+
"Until it loads, the tool calls its policies cover are DENIED. Fix it, or remove it with: " +
|
|
1357
|
+
`failproofai policies remove ${err.id ?? "<id>"}`,
|
|
1358
|
+
]
|
|
1359
|
+
: []),
|
|
1360
|
+
]),
|
|
1323
1361
|
opts,
|
|
1324
1362
|
),
|
|
1325
1363
|
);
|