failproofai 1.0.6 → 1.0.7-beta.1
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 +7 -5
- 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 +13 -11
- 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 +7 -7
- 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 +41 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -9
- 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]__1j-zsg5._.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]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
- 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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.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/[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]__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]__0qxnccm._.js +5 -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]__0s_yomn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -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]__1svlq8e._.js → [root-of-the-server]__0yxwl6j._.js} +2 -2
- 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]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1b2dbet._.js +1 -1
- 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]__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/{_0do6d26._.js → _0bn2oo8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
- 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/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_17o744m._.js → _214wgrp._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- 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/lib_utils_ts_0az0sfq._.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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.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/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 +56 -23
- package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +6 -0
- package/.next/standalone/.next/static/chunks/{40-wncjnguy4e.js → 0qrbdkv9qmvli.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3-6zhytp6adkk.js → 0uldbut9y2-e8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2y4ycwcwfjv4k.js → 0vmd180qfntfb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{25m8mg7bosgip.js → 12tvm75t5ffui.js} +1 -1
- package/.next/standalone/.next/static/chunks/1u5zsejmgrir_.js +1 -0
- package/.next/standalone/.next/static/chunks/258668t68du6b.js +1 -0
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
- package/.next/standalone/.next/static/chunks/{35pw7zxhh76n7.js → 29fql3nbnfc9q.js} +1 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/{0bepqeawy72n6.js → 2vkvu9-opa_1z.js} +1 -1
- package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
- package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +1 -0
- package/.next/standalone/app/actions/get-jev-config.ts +409 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +420 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +9 -0
- package/.next/standalone/app/settings/jev-panel.tsx +469 -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 +51 -3
- package/.next/standalone/fp-cloud-cli/README.md +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
- package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
- package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
- package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/package.json +11 -11
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
- package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
- package/.next/standalone/sdk/python/uv.lock +15 -15
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +115 -2
- package/dist/cli.mjs +9299 -2151
- package/dist/index.js +19 -1
- package/dist/worker.mjs +13761 -7834
- package/package.json +11 -11
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +39 -2
- package/src/hooks/builtin-policies.ts +21 -3
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/custom-hooks-loader.ts +45 -7
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/effective-reviewers.ts +79 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/handler.ts +265 -6
- package/src/hooks/hook-activity-store.ts +105 -1
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +1193 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/pack-cli.ts +278 -17
- package/src/hooks/pack-manifest.ts +479 -7
- package/src/hooks/pack-store.ts +156 -10
- package/src/hooks/policy-authority.ts +333 -0
- package/src/hooks/policy-catalog.ts +65 -0
- package/src/hooks/policy-evaluator.ts +940 -796
- package/src/hooks/policy-registry.ts +25 -0
- package/src/hooks/policy-reviewability.ts +229 -0
- package/src/hooks/policy-types.ts +126 -0
- package/src/hooks/semantic/combine.ts +541 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +392 -0
- package/src/hooks/semantic/envelope.ts +1296 -0
- package/src/hooks/semantic/evaluator.ts +547 -0
- package/src/hooks/semantic/facts.ts +292 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +643 -0
- package/src/hooks/semantic/jev-config.ts +594 -0
- package/src/hooks/semantic/jev-review.ts +374 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +421 -0
- package/src/hooks/semantic/pack-policies.ts +251 -0
- package/src/hooks/semantic/policies.ts +596 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2910 -0
- package/src/hooks/semantic/types.ts +145 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/worker-server.ts +119 -26
- package/src/index.ts +6 -0
- 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/_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_1cv9_c4._.js +0 -10
- package/.next/standalone/.next/static/chunks/0h-gz8e00478q.js +0 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
- package/.next/standalone/.next/static/chunks/319c_91le7-h_.js +0 -1
- package/.next/standalone/.next/static/chunks/35m53kix7uaii.js +0 -6
- package/.next/standalone/.next/static/chunks/3xacd7qmuqlvs.js +0 -1
- /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7-beta.1",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -81,14 +81,14 @@
|
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@anthropic-ai/sdk": "^0.
|
|
84
|
+
"@anthropic-ai/sdk": "^0.127.0",
|
|
85
85
|
"@mdx-js/mdx": "^3.1.1",
|
|
86
86
|
"@tailwindcss/postcss": "^4.3.1",
|
|
87
87
|
"@tanstack/react-virtual": "^3.14.10",
|
|
88
88
|
"@testing-library/jest-dom": "^7.0.0",
|
|
89
89
|
"@testing-library/react": "^16.3.2",
|
|
90
90
|
"@testing-library/user-event": "^14.6.5",
|
|
91
|
-
"@types/node": "26.
|
|
91
|
+
"@types/node": "26.6.1",
|
|
92
92
|
"@types/react": "19.3.0",
|
|
93
93
|
"@types/react-dom": "^19.2.3",
|
|
94
94
|
"@vitejs/plugin-react": "^6.1.0",
|
|
@@ -122,15 +122,15 @@
|
|
|
122
122
|
"browserslist": "4.28.8"
|
|
123
123
|
},
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@failproofai/failproofaid-linux-x64": "1.0.
|
|
126
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.
|
|
127
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.
|
|
128
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.
|
|
125
|
+
"@failproofai/failproofaid-linux-x64": "1.0.7-beta.1",
|
|
126
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.7-beta.1",
|
|
127
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.7-beta.1",
|
|
128
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.7-beta.1"
|
|
129
129
|
},
|
|
130
130
|
"failproofaidBinaries": {
|
|
131
|
-
"linux-x64": "
|
|
132
|
-
"linux-arm64": "
|
|
133
|
-
"darwin-x64": "
|
|
134
|
-
"darwin-arm64": "
|
|
131
|
+
"linux-x64": "8fc6dcf94834a332791a76b24c9906ef63371ee91b5a8dc2c764db202d5608a7",
|
|
132
|
+
"linux-arm64": "ce48b73245adc6a0d7cef2ba74e37693bd00d7cff75a8d27b6bdbd8ba0251613",
|
|
133
|
+
"darwin-x64": "27d8f9d740d409741d43e75850eea5f956ac6b578628cbf2f364d711f89621a6",
|
|
134
|
+
"darwin-arm64": "0ee02f87dd6e3f0761b18fd5d60ebbbfd5328f3e99bc3d2c909d41d540ee68a0"
|
|
135
135
|
}
|
|
136
136
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -404,6 +404,17 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
404
404
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
405
405
|
cwd: resolveCwd(e.cwd),
|
|
406
406
|
hook_event_name: "UserPromptSubmit",
|
|
407
|
+
// "interactive" | "rpc" | "extension": which channel the input came
|
|
408
|
+
// through, and the only mark Pi gives about who wrote the prompt. It
|
|
409
|
+
// names the channel, not the author — `pi -p "<text>"` reports
|
|
410
|
+
// `interactive` exactly as a person typing does — so the Jev evaluator
|
|
411
|
+
// uses it only in the direction it is reliable in (`piMachineTurn`,
|
|
412
|
+
// src/hooks/semantic/intent.ts): `extension`, another extension's
|
|
413
|
+
// `sendUserMessage()` whose text can be model-written or repo-derived,
|
|
414
|
+
// is refused, while `interactive`, `rpc` and an absent value are all
|
|
415
|
+
// recorded as the human's request. Policies read it too, and a later Pi
|
|
416
|
+
// build may say more here.
|
|
417
|
+
input_source: e.source,
|
|
407
418
|
});
|
|
408
419
|
if (decision.block) {
|
|
409
420
|
console.error(`[failproofai] prompt blocked: ${decision.reason ?? "blocked by policy"}`);
|
|
@@ -31,6 +31,24 @@
|
|
|
31
31
|
* that stops an agent disabling failproofai, it ships compiled in, and
|
|
32
32
|
* `pack-manifest.ts` REFUSES any pack that declares `alwaysOn`. A pack containing
|
|
33
33
|
* it would be rejected by our own loader — correctly.
|
|
34
|
+
*
|
|
35
|
+
* **Every policy states its authority** — whether Jev may clear its verdict —
|
|
36
|
+
* as the registry would resolve it (`manifestAuthority`), not as a copy of
|
|
37
|
+
* whatever the catalog happened to spell. A machine running this pack reads
|
|
38
|
+
* authority from the MANIFEST, so a field left off here is a policy that is
|
|
39
|
+
* silently hard everywhere it is installed. The entry passes the same fields to
|
|
40
|
+
* `customPolicies.add`, so a manifest rebuilt from the entry (`failproofai
|
|
41
|
+
* publish`) says the same thing.
|
|
42
|
+
*
|
|
43
|
+
* **It emits no `semantic` array, deliberately.** A pack that declares one
|
|
44
|
+
* REPLACES this build's compiled-in semantic set wholesale
|
|
45
|
+
* (`semantic/pack-policies.ts`), and this pack is the dress rehearsal for the
|
|
46
|
+
* regex floor leaving the package — not for Jev's question set leaving it. So
|
|
47
|
+
* the 39 builtins travel and the 16 semantic policies stay compiled in, which
|
|
48
|
+
* also keeps `manifestAuthority` below judging `reviewedBy` against the right
|
|
49
|
+
* list: the builtin reviewer names, which is what a machine installing this pack
|
|
50
|
+
* will still be running. The separate `FailproofAI/jev-policies` pack is where
|
|
51
|
+
* the semantic half ships, and it declares its own `minCliVersion`.
|
|
34
52
|
*/
|
|
35
53
|
import { execFileSync } from "node:child_process";
|
|
36
54
|
import { createHash } from "node:crypto";
|
|
@@ -40,6 +58,7 @@ import { fileURLToPath } from "node:url";
|
|
|
40
58
|
// The one place the pack's id is decided. Imported rather than restated — see
|
|
41
59
|
// PACK_ID below for what restating it cost.
|
|
42
60
|
import { CORE_SOURCE } from "../src/hooks/pack-store.ts";
|
|
61
|
+
import { manifestAuthority } from "../src/hooks/policy-authority.ts";
|
|
43
62
|
|
|
44
63
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
45
64
|
// `--out <dir>` lets the conformance test generate into a temp directory rather
|
|
@@ -95,6 +114,9 @@ for (const policy of BUILTIN_POLICIES) {
|
|
|
95
114
|
defaultEnabled: policy.defaultEnabled === true,
|
|
96
115
|
match: policy.match,
|
|
97
116
|
fn: policy.fn,
|
|
117
|
+
// Whether Jev may clear it; see the note on authority at the top.
|
|
118
|
+
...(policy.authority !== undefined ? { authority: policy.authority } : {}),
|
|
119
|
+
...(policy.reviewedBy !== undefined ? { reviewedBy: policy.reviewedBy } : {}),
|
|
98
120
|
});
|
|
99
121
|
}
|
|
100
122
|
`;
|
|
@@ -120,7 +142,20 @@ try {
|
|
|
120
142
|
// validates these with the loader's own rules, so a shape the loader would
|
|
121
143
|
// refuse fails this build rather than shipping.
|
|
122
144
|
const { POLICY_CATALOG } = await import(join(ROOT, "src/hooks/policy-catalog.ts"));
|
|
123
|
-
const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) =>
|
|
145
|
+
const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) => {
|
|
146
|
+
// Throws on a declaration the registry would quietly downgrade: in the
|
|
147
|
+
// catalog that is a typo or a renamed semantic policy, and publishing it
|
|
148
|
+
// would turn a reviewed decision into an unannounced hard one. The fields
|
|
149
|
+
// come from the same call, so the check cannot be skipped without the
|
|
150
|
+
// manifest losing its authority too.
|
|
151
|
+
const resolved = manifestAuthority(p);
|
|
152
|
+
// Spread first so every field keeps its catalog position; only the two
|
|
153
|
+
// authority fields are replaced by their resolved values.
|
|
154
|
+
const entry = { ...p, authority: resolved.authority };
|
|
155
|
+
if (resolved.reviewedBy) entry.reviewedBy = resolved.reviewedBy;
|
|
156
|
+
else delete entry.reviewedBy;
|
|
157
|
+
return entry;
|
|
158
|
+
});
|
|
124
159
|
|
|
125
160
|
const manifest = JSON.stringify(
|
|
126
161
|
{ id: PACK_ID, version, effect: "enforce", policies },
|
|
@@ -138,8 +173,10 @@ writeFileSync(
|
|
|
138
173
|
);
|
|
139
174
|
|
|
140
175
|
const omitted = POLICY_CATALOG.filter((p) => p.alwaysOn).map((p) => p.name);
|
|
176
|
+
const reviewable = policies.filter((p) => p.authority === "reviewable").length;
|
|
141
177
|
console.log(
|
|
142
|
-
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies
|
|
178
|
+
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies ` +
|
|
179
|
+
`(${reviewable} reviewable by Jev, ${policies.length - reviewable} hard), ` +
|
|
143
180
|
`${(entryBytes.length / 1024).toFixed(1)} KB entry` +
|
|
144
181
|
(omitted.length ? `; omitted alwaysOn: ${omitted.join(", ")}` : ""),
|
|
145
182
|
);
|
|
@@ -2215,17 +2215,29 @@ const READ_LIKE_CMDS =
|
|
|
2215
2215
|
* separator characters that appear in compound argv tokens (':' for Docker
|
|
2216
2216
|
* volume mounts and PATH-like lists, '=' for env var assignments) so that a
|
|
2217
2217
|
* suffix like '/dashboard.mdx' in 'docs/STAR/dashboard.mdx' or '/docs' in
|
|
2218
|
-
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path.
|
|
2218
|
+
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path. '/' is in
|
|
2219
|
+
* that list too, so a match cannot START on the second slash of a protocol
|
|
2220
|
+
* separator: 'http://localhost:3000/x' used to yield '/localhost:3000/x'.
|
|
2221
|
+
* A match that starts on the FIRST slash is untouched, so '//etc/passwd' —
|
|
2222
|
+
* a real, working spelling of /etc/passwd — is still extracted and still
|
|
2223
|
+
* resolves to /etc/passwd.
|
|
2224
|
+
*
|
|
2225
|
+
* Runs of slashes with nothing else ('//', '///') are dropped: those are a
|
|
2226
|
+
* line-comment marker, not a directory. They used to resolve to the filesystem
|
|
2227
|
+
* root, which denied any read-like command carrying a '// …' comment — a
|
|
2228
|
+
* heredoc writing a TypeScript file into the project, say. A lone '/' is the
|
|
2229
|
+
* root and is kept.
|
|
2219
2230
|
*/
|
|
2220
2231
|
function extractAbsolutePaths(command: string): string[] {
|
|
2221
2232
|
const paths: string[] = [];
|
|
2222
|
-
const pathRe = /(?<![a-zA-Z0-9_
|
|
2233
|
+
const pathRe = /(?<![a-zA-Z0-9_.\-~\\*?:=/])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
|
|
2223
2234
|
|
|
2224
2235
|
function addPaths(s: string): void {
|
|
2225
2236
|
pathRe.lastIndex = 0;
|
|
2226
2237
|
let m: RegExpExecArray | null;
|
|
2227
2238
|
while ((m = pathRe.exec(s)) !== null) {
|
|
2228
2239
|
let p = m[0];
|
|
2240
|
+
if (/^\/{2,}$/.test(p)) continue; // a `//` comment marker, not the root
|
|
2229
2241
|
if (p === "~") p = homedir();
|
|
2230
2242
|
else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
|
|
2231
2243
|
paths.push(p);
|
|
@@ -3178,7 +3190,13 @@ export function registerBuiltinPolicies(enabledNames: string[]): void {
|
|
|
3178
3190
|
// A guard against the agent disabling failproofai that any of those can
|
|
3179
3191
|
// switch off is not a guard.
|
|
3180
3192
|
if (policy.alwaysOn || enabledSet.has(normalizePolicyName(policy.name))) {
|
|
3181
|
-
|
|
3193
|
+
// `alwaysOn` travels with the declaration so the self-protection guard
|
|
3194
|
+
// resolves to hard even if its catalog entry ever said otherwise.
|
|
3195
|
+
registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params, {
|
|
3196
|
+
authority: policy.authority,
|
|
3197
|
+
reviewedBy: policy.reviewedBy,
|
|
3198
|
+
alwaysOn: policy.alwaysOn,
|
|
3199
|
+
});
|
|
3182
3200
|
}
|
|
3183
3201
|
}
|
|
3184
3202
|
}
|
|
@@ -377,7 +377,7 @@ export function versionStatusLines(): string[] {
|
|
|
377
377
|
const daemon = recorded?.daemon ?? "not installed";
|
|
378
378
|
return [
|
|
379
379
|
`CLI ${cliVersion} · daemon ${daemon}${skew ? " (STALE)" : ""} · layout ${recorded?.layout ?? "-"}`,
|
|
380
|
-
...(skew ? [" Run `failproofai
|
|
380
|
+
...(skew ? [" Run `failproofai update` to update the daemon."] : []),
|
|
381
381
|
];
|
|
382
382
|
}
|
|
383
383
|
|
|
@@ -8,6 +8,8 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
import { existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
9
9
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
10
10
|
import { cloudPoliciesDir } from "./fp-home";
|
|
11
|
+
import { authorityFieldsOf } from "./policy-authority";
|
|
12
|
+
import type { PolicyAuthority } from "./policy-types";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Active-manifest schema versions this reader accepts.
|
|
@@ -44,6 +46,19 @@ export interface CloudManagedPolicyArtifact {
|
|
|
44
46
|
sha256: string;
|
|
45
47
|
path: string;
|
|
46
48
|
deployment: number;
|
|
49
|
+
/**
|
|
50
|
+
* Whether Jev may clear this policy's verdict once it is configured, as the
|
|
51
|
+
* ASSIGNMENT declares it — code inside the artifact cannot grant itself this.
|
|
52
|
+
* Absent means `hard`, which is what keeps Jev from weakening central
|
|
53
|
+
* enforcement by default: a cloud policy is only ever reviewable because
|
|
54
|
+
* whoever deployed it said so. Applies to every hook the artifact registers.
|
|
55
|
+
*
|
|
56
|
+
* Read and validated here; the daemon has to carry it into `active.json`
|
|
57
|
+
* (`ActivePolicy` in `cloud_policies.rs`) before a deployment can set it.
|
|
58
|
+
*/
|
|
59
|
+
authority?: PolicyAuthority;
|
|
60
|
+
/** The semantic policies that must all be asked and none answer `deny`; see `authority`. */
|
|
61
|
+
reviewedBy?: string[];
|
|
47
62
|
}
|
|
48
63
|
|
|
49
64
|
interface ActiveManifest {
|
|
@@ -55,6 +70,8 @@ interface ActiveManifest {
|
|
|
55
70
|
effect?: string;
|
|
56
71
|
sha256: string;
|
|
57
72
|
path: string;
|
|
73
|
+
authority?: unknown;
|
|
74
|
+
reviewedBy?: unknown;
|
|
58
75
|
}>;
|
|
59
76
|
}
|
|
60
77
|
|
|
@@ -206,6 +223,11 @@ export function readActiveCloudManagedPolicies(): CloudManagedPolicyArtifact[] {
|
|
|
206
223
|
sha256: policy.sha256,
|
|
207
224
|
path,
|
|
208
225
|
deployment: manifest.deployment,
|
|
226
|
+
// Unlike `effect`, a malformed authority is dropped rather than refused:
|
|
227
|
+
// dropping it makes this policy `hard`, the default that keeps enforcing,
|
|
228
|
+
// while refusing would take the whole deployment down over an optional
|
|
229
|
+
// field. Absent stays absent.
|
|
230
|
+
...authorityFieldsOf(policy as unknown as Record<string, unknown>),
|
|
209
231
|
};
|
|
210
232
|
});
|
|
211
233
|
}
|
|
@@ -33,6 +33,7 @@ import type { CustomHook, PolicyCatalogEntry } from "./policy-types";
|
|
|
33
33
|
import type { CloudManagedPolicyArtifact } from "./cloud-managed-policies";
|
|
34
34
|
import type { ResolvedPack } from "./pack-manifest";
|
|
35
35
|
import { customPoliciesDir, shimsDir } from "./fp-home";
|
|
36
|
+
import { warnAuthority, withMergedAuthority } from "./policy-authority";
|
|
36
37
|
|
|
37
38
|
const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
|
|
38
39
|
|
|
@@ -448,9 +449,19 @@ export async function loadAllCustomHooks(
|
|
|
448
449
|
`cloud-managed policies ${existing.id} and ${policy.id} have identical source, so they share ` +
|
|
449
450
|
`one artifact and load as one policy; enforcing it if either asks to enforce`,
|
|
450
451
|
);
|
|
451
|
-
|
|
452
|
-
|
|
452
|
+
// Authority resolves toward HARD, for the same reason: reviewable only if
|
|
453
|
+
// every assignment behind these bytes says so. Keeping one record's
|
|
454
|
+
// declaration let the order of active.json decide, so a team's reviewable
|
|
455
|
+
// assignment could clear what an org-wide hard one enforces.
|
|
456
|
+
const winner = existing.effect !== "enforce" && policy.effect === "enforce" ? policy : existing;
|
|
457
|
+
const { merged, overruled } = withMergedAuthority(winner, [existing, policy]);
|
|
458
|
+
if (overruled) {
|
|
459
|
+
warnAuthority(
|
|
460
|
+
`cloud-managed policies ${existing.id} and ${policy.id} share one artifact and do not all declare it ` +
|
|
461
|
+
`reviewable, so it stays hard`,
|
|
462
|
+
);
|
|
453
463
|
}
|
|
464
|
+
cloudManagedByPath.set(key, merged);
|
|
454
465
|
}
|
|
455
466
|
|
|
456
467
|
// Installed packs, keyed by artifact path — the same content-addressing, and
|
|
@@ -482,14 +493,34 @@ export async function loadAllCustomHooks(
|
|
|
482
493
|
*
|
|
483
494
|
* Winner precedence on a name both declare, because the merged record carries
|
|
484
495
|
* the winner's id and version and there is no merging two different defaults.
|
|
496
|
+
*
|
|
497
|
+
* Except AUTHORITY, which resolves toward hard like the effect resolves toward
|
|
498
|
+
* enforce (`withMergedAuthority`): a name is reviewable only if every pack
|
|
499
|
+
* behind the artifact declares it reviewable. A pack whose manifest does not
|
|
500
|
+
* list the name at all counts as hard — its copy of the artifact registers
|
|
501
|
+
* the policy all the same, undeclared, and an undeclared pack policy is hard.
|
|
502
|
+
* Winner precedence here would let one pack's manifest make another pack's
|
|
503
|
+
* policy reviewable, which is the one thing a manifest may never do.
|
|
485
504
|
*/
|
|
486
505
|
const unionCatalog = (
|
|
487
506
|
winner: PolicyCatalogEntry[],
|
|
488
507
|
other: PolicyCatalogEntry[],
|
|
489
|
-
): PolicyCatalogEntry[] =>
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
508
|
+
): { policies: PolicyCatalogEntry[]; overruled: string[] } => {
|
|
509
|
+
/** Names a pack asked to be reviewable and did not get, for the warning. */
|
|
510
|
+
const overruled: string[] = [];
|
|
511
|
+
const harden = (entry: PolicyCatalogEntry, peer: PolicyCatalogEntry | undefined) => {
|
|
512
|
+
const r = withMergedAuthority(entry, [entry, peer ?? {}]);
|
|
513
|
+
if (r.overruled) overruled.push(entry.name);
|
|
514
|
+
return r.merged;
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
policies: [
|
|
518
|
+
...winner.map((w) => harden(w, other.find((p) => p.name === w.name))),
|
|
519
|
+
...other.filter((p) => !winner.some((w) => w.name === p.name)).map((p) => harden(p, undefined)),
|
|
520
|
+
],
|
|
521
|
+
overruled,
|
|
522
|
+
};
|
|
523
|
+
};
|
|
493
524
|
|
|
494
525
|
const packByPath = new Map<string, ResolvedPack>();
|
|
495
526
|
/**
|
|
@@ -535,12 +566,19 @@ export async function loadAllCustomHooks(
|
|
|
535
566
|
// being intersected away.
|
|
536
567
|
const winner = existing.effect !== "enforce" && pack.effect === "enforce" ? pack : existing;
|
|
537
568
|
const other = winner === existing ? pack : existing;
|
|
569
|
+
const catalog = unionCatalog(winner.policies, other.policies);
|
|
538
570
|
packByPath.set(key, {
|
|
539
571
|
...winner,
|
|
540
|
-
policies:
|
|
572
|
+
policies: catalog.policies,
|
|
541
573
|
enabled: unionSelection(winner.enabled, other.enabled),
|
|
542
574
|
clis: unionSelection(winner.clis, other.clis),
|
|
543
575
|
});
|
|
576
|
+
if (catalog.overruled.length > 0) {
|
|
577
|
+
warnAuthority(
|
|
578
|
+
`packs ${existing.id} and ${pack.id} share one artifact and do not all declare ` +
|
|
579
|
+
`${catalog.overruled.join(", ")} reviewable, so ${catalog.overruled.length === 1 ? "it stays" : "they stay"} hard`,
|
|
580
|
+
);
|
|
581
|
+
}
|
|
544
582
|
}
|
|
545
583
|
|
|
546
584
|
// 1. Explicit custom policy paths. Accept a string for callers/configs using
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
* from 'failproofai') and the hook handler (which reads from this file) share the
|
|
5
5
|
* same in-process state when running within the same Node.js process.
|
|
6
6
|
*/
|
|
7
|
-
import type { CustomHook } from "./policy-types";
|
|
7
|
+
import type { CustomHook, SemanticPolicyDeclaration } from "./policy-types";
|
|
8
8
|
|
|
9
9
|
const REGISTRY_KEY = "__failproofai_custom_hooks__";
|
|
10
|
+
/**
|
|
11
|
+
* Beside the hook registry, on the same key namespace and cleared by the same
|
|
12
|
+
* call — the two are one registration pass and a build step reads both.
|
|
13
|
+
*/
|
|
14
|
+
const SEMANTIC_KEY = "__failproofai_semantic_policies__";
|
|
10
15
|
|
|
11
16
|
function getRegistry(): CustomHook[] {
|
|
12
17
|
const g = globalThis as Record<string, unknown>;
|
|
@@ -14,17 +19,56 @@ function getRegistry(): CustomHook[] {
|
|
|
14
19
|
return g[REGISTRY_KEY] as CustomHook[];
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
function getSemanticRegistry(): SemanticPolicyDeclaration[] {
|
|
23
|
+
const g = globalThis as Record<string, unknown>;
|
|
24
|
+
if (!Array.isArray(g[SEMANTIC_KEY])) g[SEMANTIC_KEY] = [];
|
|
25
|
+
return g[SEMANTIC_KEY] as SemanticPolicyDeclaration[];
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export const customPolicies = {
|
|
18
29
|
add(hook: CustomHook): void {
|
|
19
30
|
getRegistry().push(hook);
|
|
20
31
|
},
|
|
21
32
|
};
|
|
22
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Register a semantic (Jev) policy — a question set for the classifier, read by
|
|
36
|
+
* `failproofai publish` and emitted as the pack manifest's `semantic` array.
|
|
37
|
+
*
|
|
38
|
+
* A SEPARATE namespace from `customPolicies`, deliberately, rather than a
|
|
39
|
+
* `customPolicies.addSemantic`. A semantic policy has no `fn` and no `match`,
|
|
40
|
+
* and nothing about it ever executes on a user's machine: what installs is the
|
|
41
|
+
* declaration in the manifest, and the artifact's call here matters only at
|
|
42
|
+
* build time. Putting it behind the object whose entries return
|
|
43
|
+
* `allow()`/`deny()` invites exactly the shape confusion the manifest parser
|
|
44
|
+
* would then have to catch — a function on a semantic entry, a probe list on a
|
|
45
|
+
* regex one — and both mistakes would be silent until somebody read a manifest.
|
|
46
|
+
*
|
|
47
|
+
* Nothing is validated here. `parsePackSemanticPolicy` in `pack-manifest.ts`
|
|
48
|
+
* owns every rule, and it is applied at BUILD time (where the author can still
|
|
49
|
+
* fix it) and again at load time (where the bytes may have come from anywhere).
|
|
50
|
+
* Validating a third time in the setter would be a third copy of the rules.
|
|
51
|
+
*/
|
|
52
|
+
export const semanticPolicies = {
|
|
53
|
+
add(policy: SemanticPolicyDeclaration): void {
|
|
54
|
+
getSemanticRegistry().push(policy);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
23
58
|
export function getCustomHooks(): CustomHook[] {
|
|
24
59
|
return getRegistry();
|
|
25
60
|
}
|
|
26
61
|
|
|
62
|
+
export function getSemanticRegistrations(): SemanticPolicyDeclaration[] {
|
|
63
|
+
return getSemanticRegistry();
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
export function clearCustomHooks(): void {
|
|
28
67
|
const g = globalThis as Record<string, unknown>;
|
|
29
68
|
g[REGISTRY_KEY] = [];
|
|
69
|
+
// Both maps, on one call. A loader that cleared only the hooks would carry
|
|
70
|
+
// one entry file's semantic policies into the next file's build — and since
|
|
71
|
+
// the semantic half is read after the load rather than during it, the extras
|
|
72
|
+
// would be published under the wrong pack with nothing to notice.
|
|
73
|
+
g[SEMANTIC_KEY] = [];
|
|
30
74
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The semantic checks a `reviewedBy` may name ON THIS MACHINE.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is not simply `SEMANTIC_REVIEWER_NAMES`
|
|
5
|
+
*
|
|
6
|
+
* A pack that declares its own `semantic` entries replaces the compiled-in
|
|
7
|
+
* `SEMANTIC_POLICIES` wholesale (`semantic/pack-policies.ts`). So on that
|
|
8
|
+
* machine the sixteen builtin names are checks that will never be asked, and the
|
|
9
|
+
* pack's own names are the only ones that will — while
|
|
10
|
+
* `resolvePolicyAuthority`, judging against the builtin set, would read the
|
|
11
|
+
* pack's `reviewedBy: ["its-own-check"]` as naming a check "this build does not
|
|
12
|
+
* have" and downgrade the policy to `hard` with nothing but a warning in the
|
|
13
|
+
* hook log. That is a rewritten policy set arriving with its reviewability
|
|
14
|
+
* quietly removed: the deny half of the two-tier evaluator working exactly as
|
|
15
|
+
* documented while the clearing half cannot fire once — the failure
|
|
16
|
+
* `policy-reviewability.ts` was written to make audible, reintroduced one layer
|
|
17
|
+
* down.
|
|
18
|
+
*
|
|
19
|
+
* The names come from the MANIFEST, which is already where `authority` and
|
|
20
|
+
* `reviewedBy` themselves are read from, so this adds no new trust and imports
|
|
21
|
+
* no semantic module.
|
|
22
|
+
*
|
|
23
|
+
* ## Why it is a module of its own, and cached the way it is
|
|
24
|
+
*
|
|
25
|
+
* `registerPolicy` asks this for every policy that declares an authority — forty
|
|
26
|
+
* times per hook event — and the honest answer costs a digest verification per
|
|
27
|
+
* installed pack. So it is cached, and the cache is dropped by `clearPolicies()`:
|
|
28
|
+
* one read per registration pass, and the answer's lifetime is exactly the
|
|
29
|
+
* registry's. That is the right coupling rather than a convenient one — this set
|
|
30
|
+
* describes the policies sitting in the registry, and the handler rebuilds both
|
|
31
|
+
* together, so they cannot come to describe different machines.
|
|
32
|
+
*
|
|
33
|
+
* It lives beside `policy-registry.ts` rather than inside `pack-manifest.ts`
|
|
34
|
+
* because the registry may not depend on the whole manifest reader: several
|
|
35
|
+
* tests isolate themselves from the running machine's real packs by mocking
|
|
36
|
+
* `pack-manifest` down to `readInstalledPacks` and `hasInstalledPacks`, and a
|
|
37
|
+
* registration path reaching for a third export of that module breaks them all
|
|
38
|
+
* — loudly here, but it is the shape of a change that would later break them
|
|
39
|
+
* silently.
|
|
40
|
+
*/
|
|
41
|
+
import { readInstalledPacks } from "./pack-manifest";
|
|
42
|
+
import { SEMANTIC_REVIEWER_NAMES } from "./policy-authority";
|
|
43
|
+
|
|
44
|
+
let cached: ReadonlySet<string> | null = null;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The installed packs' semantic policy names when any pack declares some, and
|
|
48
|
+
* this build's compiled-in set otherwise.
|
|
49
|
+
*
|
|
50
|
+
* Never throws: an unreadable manifest declares nothing, and the builtin set
|
|
51
|
+
* stands. That is the same fail-open posture every other reader of the pack
|
|
52
|
+
* manifest takes, and here it also fails in the safe direction — a pack's
|
|
53
|
+
* `reviewedBy` will not match a builtin name, so its policies stay `hard`
|
|
54
|
+
* rather than being cleared by a question nobody could read.
|
|
55
|
+
*/
|
|
56
|
+
export function effectiveReviewerNames(): ReadonlySet<string> {
|
|
57
|
+
if (cached) return cached;
|
|
58
|
+
let names: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES;
|
|
59
|
+
try {
|
|
60
|
+
const declared = readInstalledPacks().packs.flatMap((p) => (p.semantic ?? []).map((s) => s.name));
|
|
61
|
+
// Only when a pack actually declared some. A machine whose packs carry only
|
|
62
|
+
// the regex floor still runs the compiled-in semantic set, so its builtin
|
|
63
|
+
// reviewer names are the live ones.
|
|
64
|
+
if (declared.length > 0) names = new Set(declared);
|
|
65
|
+
} catch {
|
|
66
|
+
// See above: silence here is the builtin set, not an empty one.
|
|
67
|
+
}
|
|
68
|
+
cached = names;
|
|
69
|
+
return names;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Drop the cached answer. Called by `clearPolicies()`, which every evaluation
|
|
74
|
+
* runs before it registers anything — so a pack installed under a long-lived
|
|
75
|
+
* warm worker is picked up on the next event rather than at the next restart.
|
|
76
|
+
*/
|
|
77
|
+
export function forgetEffectiveReviewerNames(): void {
|
|
78
|
+
cached = null;
|
|
79
|
+
}
|
|
@@ -35,6 +35,11 @@ export const FIRST_RUN_EXEMPT_SUBCOMMANDS: readonly string[] = [
|
|
|
35
35
|
// Same reason: a backfill is an explicit instruction about an already-set-up
|
|
36
36
|
// machine, and interrupting it to offer setup answers a question nobody asked.
|
|
37
37
|
"backfill",
|
|
38
|
+
// `jev setup` is configuration in its own right, and `jev status` / `test` /
|
|
39
|
+
// `remove` inspect or undo it. It is also the command most likely to be
|
|
40
|
+
// scripted with a key on stdin — a wizard in front of it would read that key
|
|
41
|
+
// as the answer to its first question.
|
|
42
|
+
"jev",
|
|
38
43
|
];
|
|
39
44
|
|
|
40
45
|
export function shouldOfferFirstRun(args: readonly string[]): boolean {
|
package/src/hooks/fp-home.ts
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
* VERSION layout / cli / daemon versions
|
|
52
52
|
* config.json 0644 non-secret: mode, daemon, collector prefs
|
|
53
53
|
* credentials.json 0600 every token
|
|
54
|
+
* jev.json 0600 the customer's own Jev endpoint + key (BYOK opt-in)
|
|
54
55
|
* policies-config.json the builtin enable/disable set + params
|
|
55
56
|
* bin/ downloaded daemon binaries, one per version
|
|
56
57
|
* policies/ every policy: the user's *.mjs sit directly here
|
|
@@ -148,6 +149,14 @@ export const configFile = (home?: string) => atHome(home, "config.json");
|
|
|
148
149
|
*/
|
|
149
150
|
export const credentialsFile = (home?: string) => atHome(home, "credentials.json");
|
|
150
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The customer's own Jev endpoint and key (BYOK), owner-only. Its presence is
|
|
154
|
+
* the whole opt-in to the two-tier evaluator: absent, hooks run the regex
|
|
155
|
+
* engine exactly as before. GLOBAL only — a repository can never set it. See
|
|
156
|
+
* `src/hooks/semantic/jev-config.ts`.
|
|
157
|
+
*/
|
|
158
|
+
export const jevConfigFile = (home?: string) => atHome(home, "jev.json");
|
|
159
|
+
|
|
151
160
|
// ── Daemon binaries ──────────────────────────────────────────────────────────
|
|
152
161
|
|
|
153
162
|
export const binDir = (home?: string) => atHome(home, "bin");
|
|
@@ -389,6 +398,12 @@ export const failedDir = (home?: string) => resolve(stateDir(home), "failed");
|
|
|
389
398
|
export const collectorHealthFile = (home?: string) => resolve(stateDir(home), "collector-health.json");
|
|
390
399
|
/** Per-session enforcement pauses, keyed by a hash of the session id. */
|
|
391
400
|
export const sessionPauseDir = () => resolve(stateDir(), "sessions");
|
|
401
|
+
/**
|
|
402
|
+
* The semantic (Jev) evaluator's local state: `sessions/<id>.json` holds the
|
|
403
|
+
* human prompts recorded at `UserPromptSubmit`, and `verdicts.jsonl` one row
|
|
404
|
+
* per semantic evaluation. Written only when a Jev config (BYOK) is present.
|
|
405
|
+
*/
|
|
406
|
+
export const semanticDir = () => resolve(stateDir(), "semantic");
|
|
392
407
|
/**
|
|
393
408
|
* When the scheduled audit last ran, and when the next one is due.
|
|
394
409
|
*
|
|
@@ -554,6 +569,9 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
|
|
|
554
569
|
// policy silently — it keeps enforcing whatever it last had, reports healthy,
|
|
555
570
|
// and never reconciles again. Nothing re-derives a token.
|
|
556
571
|
{ path: credentialsFile, class: "user-typed" },
|
|
572
|
+
// The Jev BYOK config. A person typed the key; nothing regenerates it, and
|
|
573
|
+
// losing it silently turns the two-tier evaluator off.
|
|
574
|
+
{ path: jevConfigFile, class: "user-typed" },
|
|
557
575
|
// Holds `daemon.configured` (the flag that makes the machine fail closed),
|
|
558
576
|
// the collector preferences, `[audit] auto`, the telemetry opt-out, and
|
|
559
577
|
// `collector.sources.*.extra_paths` — the entire output of `harness add-path`.
|
|
@@ -642,6 +660,11 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
|
|
|
642
660
|
{ path: codexSessionPathsFile, class: "derived" },
|
|
643
661
|
{ path: shimsDir, class: "derived" },
|
|
644
662
|
{ path: sessionPauseDir, class: "derived" },
|
|
663
|
+
// Recorded prompts (a six-hour window that only ever lets Jev clear a
|
|
664
|
+
// reviewable policy) and a local diagnostic log of semantic verdicts that is
|
|
665
|
+
// never shipped. Losing either costs a clear and some history, never
|
|
666
|
+
// enforcement.
|
|
667
|
+
{ path: semanticDir, class: "derived" },
|
|
645
668
|
{ path: logsDir, class: "derived" },
|
|
646
669
|
{ path: lastVersionFile, class: "derived" },
|
|
647
670
|
{ path: launcherMarker, class: "derived" },
|