failproofai 1.0.6-beta.1 → 1.0.7-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 +3 -3
- 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]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +1 -1
- 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]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
- 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]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
- 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]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
- 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]__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]__1x3j9mj._.js → [root-of-the-server]__1p28m0u._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +1 -1
- 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/{_0qlygw6._.js → _0cf1olg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1_ddpm3._.js → _135-c3y._.js} +1 -1
- 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/_next-internal_server_app_policies_page_actions_1sp2-yo.js +4 -4
- 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/{06fjx8-0_qufs.js → 00i9thnpht4l1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ht0xsco46pul.js → 0289oapk24zql.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1qf2uiipatr80.js → 0pyy9m5cm8zku.js} +1 -1
- package/.next/standalone/.next/static/chunks/15heikmy3yjzc.js +1 -0
- package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
- package/.next/standalone/.next/static/chunks/{02vwr5js1x28j.js → 2plg9qae1yejv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/{2x776s0x3n_tu.js → 2tqszh84mp_2l.js} +1 -1
- package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
- package/.next/standalone/.next/static/chunks/3cdkxrukvlvul.js +1 -0
- package/.next/standalone/.next/static/chunks/3dnd-_29uiaj3.js +6 -0
- package/.next/standalone/.next/static/chunks/{0y227_ohdqwvn.js → 3vp7c3knkh8mr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{27_bwbg8sv6vj.js → 3vwnzfx9q80yl.js} +1 -1
- 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 +10982 -3787
- 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/daemon-client.ts +128 -50
- package/src/hooks/daemon-service.ts +30 -0
- 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/integrations.ts +3 -2
- 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/manager.ts +4 -4
- package/src/hooks/pack-cli.ts +237 -11
- 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]__01wy8d-._.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]__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]__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/0cbvytf8epn34.js +0 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
- package/.next/standalone/.next/static/chunks/1p4xauf-zu6zk.js +0 -1
- package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +0 -6
- 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/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{QCNYz2yCxxdeggg1k0iEq → qbVI48kgBOf9CTOKcFIk2}/_ssgManifest.js +0 -0
|
@@ -1070,6 +1070,36 @@ export async function probeDaemonEndToEnd(): Promise<boolean> {
|
|
|
1070
1070
|
return (await probeDaemon()).ok;
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* Proves that the daemon supports the structured request native plugins use.
|
|
1075
|
+
*
|
|
1076
|
+
* This deliberately follows the ordinary end-to-end hook probe. Besides
|
|
1077
|
+
* retaining its startup-race retry, that makes the failure meaningful: an old
|
|
1078
|
+
* daemon can be fully healthy for shell hooks while still lacking the
|
|
1079
|
+
* `policyEvaluation` variant added for Hermes. Protocol v1 predates that
|
|
1080
|
+
* variant, so version equality alone cannot establish the capability.
|
|
1081
|
+
*/
|
|
1082
|
+
export async function probeDaemonPolicyEvaluation(): Promise<boolean> {
|
|
1083
|
+
if (!(await probeDaemonEndToEnd())) return false;
|
|
1084
|
+
try {
|
|
1085
|
+
const { attemptDaemonPolicyEvaluation } = await import("./daemon-client");
|
|
1086
|
+
const attempt = await attemptDaemonPolicyEvaluation(
|
|
1087
|
+
{
|
|
1088
|
+
integration: "hermes",
|
|
1089
|
+
event: "on_session_start",
|
|
1090
|
+
payload: {
|
|
1091
|
+
hook_event_name: "on_session_start",
|
|
1092
|
+
source: "failproofai-native-policy-health-probe",
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
{ responseTimeoutMs: DAEMON_PROBE_TIMEOUT_MS },
|
|
1096
|
+
);
|
|
1097
|
+
return attempt.ok;
|
|
1098
|
+
} catch {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1073
1103
|
/**
|
|
1074
1104
|
* Waits for the service to report running and to HOLD it — a `Type=simple` unit
|
|
1075
1105
|
* is active the moment it forks, so one optimistic reading passes a daemon that
|
|
@@ -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" },
|
package/src/hooks/handler.ts
CHANGED
|
@@ -24,18 +24,26 @@ import {
|
|
|
24
24
|
OPENCLAW_EVENT_MAP,
|
|
25
25
|
ANTIGRAVITY_EVENT_MAP,
|
|
26
26
|
} from "./types";
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
27
28
|
import { canonicalizeToolName, canonicalizeToolInput } from "./tool-name-canonicalize";
|
|
28
29
|
import { normalizeCliPayload } from "./normalize-cli-payload";
|
|
29
30
|
import type { PolicyFunction, PolicyResult, HooksConfig } from "./policy-types";
|
|
30
31
|
import { readMergedHooksConfig } from "./hooks-config";
|
|
31
32
|
import { registerBuiltinPolicies } from "./builtin-policies";
|
|
32
33
|
import { evaluatePolicies } from "./policy-evaluator";
|
|
34
|
+
// Types only: the semantic (Jev) modules are loaded with a dynamic import, and
|
|
35
|
+
// only once a Jev config exists, so an unconfigured machine never loads them.
|
|
36
|
+
import type { TwoTierReview } from "./semantic/combine";
|
|
37
|
+
import type { JevActivityFields } from "./semantic/combine";
|
|
38
|
+
import type { JevConfig } from "./semantic/jev-config";
|
|
33
39
|
import { clearPolicies, registerPolicy, getPoliciesForEvent } from "./policy-registry";
|
|
34
40
|
import { loadAllCustomHooks } from "./custom-hooks-loader";
|
|
41
|
+
import { authorityDeclarationFor, resolvePolicyAuthority, warnAuthority } from "./policy-authority";
|
|
35
42
|
import type { CustomHook } from "./policy-types";
|
|
36
43
|
import { persistHookActivity } from "./hook-activity-store";
|
|
37
44
|
import { deliveryHealth, deliveryHealthLine } from "./delivery-health";
|
|
38
45
|
import { trackHookEvent, flushHookTelemetry } from "./hook-telemetry";
|
|
46
|
+
import * as hookTelemetry from "./hook-telemetry";
|
|
39
47
|
import { resolveCwd } from "./resolve-cwd";
|
|
40
48
|
import { resolvePermissionMode } from "./resolve-permission-mode";
|
|
41
49
|
import { resolveTranscriptPath } from "./resolve-transcript-path";
|
|
@@ -46,6 +54,7 @@ import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from
|
|
|
46
54
|
import { hasInstalledPacks, readInstalledPacks, type PackError, type ResolvedPack } from "./pack-manifest";
|
|
47
55
|
import { missingGuards, packFailureReason, combinedGuardMatch, guardsCover } from "./pack-failclosed";
|
|
48
56
|
import { readActivePause, type ActivePause } from "./session-pause";
|
|
57
|
+
import { jevConfigFile } from "./fp-home";
|
|
49
58
|
import { layoutWarningForHook } from "./fp-reset";
|
|
50
59
|
|
|
51
60
|
/**
|
|
@@ -153,6 +162,16 @@ export interface EvaluateHookEventOptions {
|
|
|
153
162
|
* written for.
|
|
154
163
|
*/
|
|
155
164
|
fallbackCwd?: string;
|
|
165
|
+
/**
|
|
166
|
+
* The warm worker's hook into its request queue. Called at most once, and
|
|
167
|
+
* only on the two-tier path, at the point this evaluation stops reading the
|
|
168
|
+
* process-global policy registry and starts waiting on Jev's network answer
|
|
169
|
+
* — so the next queued hook can run instead of every hook on the machine
|
|
170
|
+
* queueing behind that round trip. After it is called nothing in this
|
|
171
|
+
* evaluation reads the registry again. The one-shot path has no queue and
|
|
172
|
+
* leaves it unset.
|
|
173
|
+
*/
|
|
174
|
+
releaseRegistry?: () => void;
|
|
156
175
|
}
|
|
157
176
|
|
|
158
177
|
/**
|
|
@@ -186,6 +205,211 @@ async function runObserved(
|
|
|
186
205
|
}
|
|
187
206
|
}
|
|
188
207
|
|
|
208
|
+
// ── Two-tier (Jev) opt-in ────────────────────────────────────────────────────
|
|
209
|
+
//
|
|
210
|
+
// Jev reviews a call only when ALL of these hold, and the regex engine runs
|
|
211
|
+
// alone — exactly as it did before two tiers existed — otherwise:
|
|
212
|
+
//
|
|
213
|
+
// - a valid BYOK config exists (`~/.failproofai/jev.json`, global only);
|
|
214
|
+
// - `FAILPROOFAI_EVALUATOR` is not `legacy` (see "Turning Jev off" below);
|
|
215
|
+
// - this is not the fail-closed `forceDecision` path and no session pause is
|
|
216
|
+
// active — a pause suspends local policy, and Jev must not become a way to
|
|
217
|
+
// evaluate what the pause switched off;
|
|
218
|
+
// - the event is a gate (`PreToolUse` / `PermissionRequest`) for a named tool
|
|
219
|
+
// that the AGENT requested (see `isHumanAuthoredGate`).
|
|
220
|
+
//
|
|
221
|
+
// There is no cloud-deployment exclusion: cloud policies default to `hard`, so
|
|
222
|
+
// Jev can only make a centrally managed machine stricter, never weaker.
|
|
223
|
+
//
|
|
224
|
+
// Turning Jev off. The switch that works everywhere is the config file itself:
|
|
225
|
+
// it is read on every event, here, in whichever process evaluates — the
|
|
226
|
+
// daemon's warm worker included — so `failproofai jev remove` (or a `mode:
|
|
227
|
+
// "shadow"` config, which keeps enforcing the regex result) applies from the
|
|
228
|
+
// next tool call, with no restart. `FAILPROOFAI_EVALUATOR=legacy` is the dev
|
|
229
|
+
// escape hatch, read from the EVALUATING process's environment: it covers a
|
|
230
|
+
// one-shot hook (no daemon) and a worker whose own environment sets it, but
|
|
231
|
+
// not a variable exported in a shell on a daemon machine — the daemon
|
|
232
|
+
// forwards a hook's event, cli, stdin and cwd to the worker, never the hook
|
|
233
|
+
// process's environment. `failproofai jev status` says so when it sees the
|
|
234
|
+
// variable. A per-session switch for daemon machines would be a new field in
|
|
235
|
+
// the daemon protocol; there is none today.
|
|
236
|
+
|
|
237
|
+
/** The two gate events Jev reviews. Everything else is regex-only. */
|
|
238
|
+
const JEV_GATE_EVENTS: ReadonlySet<string> = new Set(["PreToolUse", "PermissionRequest"]);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* A gate event that carries a command the HUMAN typed, not one the agent
|
|
242
|
+
* requested: Pi's `user_bash` (`!cmd` at Pi's prompt) canonicalizes to
|
|
243
|
+
* `PreToolUse` like an agent's `tool_call`. Jev judges an agent's request
|
|
244
|
+
* against what the human asked for, and says so to the model ("A coding agent
|
|
245
|
+
* has REQUESTED the tool call"); the human's own command is the human asking,
|
|
246
|
+
* and never appears in `user_said`, so Jev would judge it as an unrequested
|
|
247
|
+
* agent action and could block the human's own command. The regex policies
|
|
248
|
+
* still run on it exactly as they always have.
|
|
249
|
+
*/
|
|
250
|
+
function isHumanAuthoredGate(rawEventType: string | undefined, cli: IntegrationType): boolean {
|
|
251
|
+
return cli === "pi" && rawEventType === "user_bash";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function jevForcedOff(opts: EvaluateHookEventOptions | undefined): boolean {
|
|
255
|
+
return process.env.FAILPROOFAI_EVALUATOR === "legacy" || !!opts?.forceDecision;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The BYOK config (with the build's default mode, D2), or null (Jev off). A
|
|
260
|
+
* config that cannot be read is off, never a failure. Logged at info, like
|
|
261
|
+
* every configured-but-broken Jev path: warn-level lines reach the hook's
|
|
262
|
+
* stderr, which is the deny text itself on some CLIs (Factory's exit 2).
|
|
263
|
+
* `failproofai jev status` is where a broken config is made visible.
|
|
264
|
+
*
|
|
265
|
+
* The file is checked for BEFORE the import, and that order is the point. With
|
|
266
|
+
* no `jev.json` the answer is null however the module would have read it — so
|
|
267
|
+
* the stat decides it, and a machine that never configured Jev never evaluates
|
|
268
|
+
* a line of the semantic modules to be told what their absence already says.
|
|
269
|
+
* The same stat-rather-than-load trade `warnAuthority` makes, for the same
|
|
270
|
+
* reason. It short-circuits nothing but a null `loadJevConfig` would have
|
|
271
|
+
* returned itself: that function reads this path, and an absent file, an
|
|
272
|
+
* unreadable directory above it and a path that is not a file are all null
|
|
273
|
+
* there too.
|
|
274
|
+
*/
|
|
275
|
+
async function readJevConfig(): Promise<{ config: JevConfig; defaultMode: TwoTierReview["mode"] } | null> {
|
|
276
|
+
try {
|
|
277
|
+
if (!existsSync(jevConfigFile())) return null;
|
|
278
|
+
const { loadJevConfig, DEFAULT_JEV_MODE } = await import("./semantic/jev-config");
|
|
279
|
+
const config = loadJevConfig();
|
|
280
|
+
return config ? { config, defaultMode: DEFAULT_JEV_MODE } : null;
|
|
281
|
+
} catch (err) {
|
|
282
|
+
hookLogInfo(
|
|
283
|
+
`Jev config could not be read (${err instanceof Error ? err.message : String(err)}); the regex engine decides alone`,
|
|
284
|
+
);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Start Jev's review of this call — before the regex policies run, so the two
|
|
291
|
+
* proceed in parallel — or return null for the regex-only path.
|
|
292
|
+
*/
|
|
293
|
+
async function startTwoTier(
|
|
294
|
+
canonicalEventType: string,
|
|
295
|
+
parsed: Record<string, unknown>,
|
|
296
|
+
session: SessionMetadata,
|
|
297
|
+
cli: IntegrationType,
|
|
298
|
+
opts: EvaluateHookEventOptions | undefined,
|
|
299
|
+
activePause: ActivePause | null,
|
|
300
|
+
): Promise<TwoTierReview | null> {
|
|
301
|
+
if (!JEV_GATE_EVENTS.has(canonicalEventType)) return null;
|
|
302
|
+
if (isHumanAuthoredGate(session.rawHookEventName, cli)) return null;
|
|
303
|
+
if (typeof parsed.tool_name !== "string" || parsed.tool_name.length === 0) return null;
|
|
304
|
+
if (jevForcedOff(opts) || activePause) return null;
|
|
305
|
+
const loaded = await readJevConfig();
|
|
306
|
+
if (!loaded) return null;
|
|
307
|
+
const cfg = loaded.config;
|
|
308
|
+
try {
|
|
309
|
+
const { startJevReview } = await import("./semantic/jev-review");
|
|
310
|
+
return startJevReview(cfg, {
|
|
311
|
+
eventType: canonicalEventType,
|
|
312
|
+
toolName: parsed.tool_name,
|
|
313
|
+
toolInput: parsed.tool_input,
|
|
314
|
+
cwd: session.cwd,
|
|
315
|
+
permissionMode: session.permissionMode,
|
|
316
|
+
sessionId: session.sessionId,
|
|
317
|
+
cli,
|
|
318
|
+
});
|
|
319
|
+
} catch (err) {
|
|
320
|
+
// Configured but unable to start: that is a fallback, and it is recorded
|
|
321
|
+
// as one — every regex verdict hard, the regex result final. Recorded as
|
|
322
|
+
// `error`, one of the reason codes the activity store and the collector
|
|
323
|
+
// know (T8's closed list); an unknown code would ship as `other`. Logged
|
|
324
|
+
// at info, off the hook's stderr (see `readJevConfig`); the activity row
|
|
325
|
+
// carries the fallback.
|
|
326
|
+
hookLogInfo(`Jev review could not start (${err instanceof Error ? err.message : String(err)})`);
|
|
327
|
+
return {
|
|
328
|
+
mode: cfg.mode === "shadow" || cfg.mode === "enforce" ? cfg.mode : loaded.defaultMode,
|
|
329
|
+
review: Promise.resolve({ kind: "fallback", reason: "error", latencyMs: null, model: null }),
|
|
330
|
+
abort: () => {},
|
|
331
|
+
authorityOf: () => ({ authority: "hard", reviewedBy: [] }),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The Jev properties for `hook_policy_triggered`, from T8's
|
|
338
|
+
* `jevTelemetryProperties` (decisions, reason codes, policy and model names,
|
|
339
|
+
* a latency — never command or prompt text). `{}` when the two-tier path did
|
|
340
|
+
* not run, so an unconfigured machine's event is unchanged, and `{}` when the
|
|
341
|
+
* helper is not in this build (it is T8's, not a §7 contract) or throws:
|
|
342
|
+
* telemetry never costs a hook its answer.
|
|
343
|
+
*
|
|
344
|
+
* Only its `jev_`-prefixed keys are kept. These are spread into an event whose
|
|
345
|
+
* core properties (`decision`, `policy_name`, `event_type`, …) are what the
|
|
346
|
+
* rollout is read from, and a helper owned by another task must not be able to
|
|
347
|
+
* overwrite one of them — silently, and only on two-tier machines — by
|
|
348
|
+
* emitting a key of that name. The helper is treated as untrusted in every
|
|
349
|
+
* other respect (absent, non-object, throwing); this is the same guard for
|
|
350
|
+
* what it returns.
|
|
351
|
+
*/
|
|
352
|
+
function jevTelemetry(activity: JevActivityFields | undefined): Record<string, unknown> {
|
|
353
|
+
if (!activity) return {};
|
|
354
|
+
try {
|
|
355
|
+
const build = (hookTelemetry as unknown as { jevTelemetryProperties?: (entry: JevActivityFields) => unknown })
|
|
356
|
+
.jevTelemetryProperties;
|
|
357
|
+
const props = typeof build === "function" ? build(activity) : null;
|
|
358
|
+
if (!props || typeof props !== "object" || Array.isArray(props)) return {};
|
|
359
|
+
return Object.fromEntries(Object.entries(props as Record<string, unknown>).filter(([key]) => key.startsWith("jev_")));
|
|
360
|
+
} catch {
|
|
361
|
+
return {};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Record what the human just typed, for Jev to judge later calls against.
|
|
367
|
+
* Only when Jev is configured. Every canonical `UserPromptSubmit` is passed,
|
|
368
|
+
* except a prompt a policy denied on a CLI where that deny is known to hold
|
|
369
|
+
* (`ENFORCEMENT_CAPABILITY` says `block`): the agent never receives that
|
|
370
|
+
* prompt, so it cannot be what the agent is working on. Where a prompt deny
|
|
371
|
+
* is only observed — Goose, OpenCode, Antigravity — or not verified, the
|
|
372
|
+
* agent does get the prompt, and it is passed like any other. Which CLIs'
|
|
373
|
+
* prompt events count as human is decided inside `captureIntent` (T4).
|
|
374
|
+
* Never throws; a failure is logged at info (see `readJevConfig`).
|
|
375
|
+
*
|
|
376
|
+
* `captureIntent` gets the whole normalized payload (`payload`): the prompt
|
|
377
|
+
* text is in a different field on some harnesses (Goose: `message`), and the
|
|
378
|
+
* marks that tell a human's prompt from a subagent's or an extension's are
|
|
379
|
+
* elsewhere in it. `prompt` is §7's original field, still read by the
|
|
380
|
+
* contract stub; T4's implementation reads `payload` and ignores it.
|
|
381
|
+
*/
|
|
382
|
+
async function captureJevIntent(
|
|
383
|
+
canonicalEventType: string,
|
|
384
|
+
decision: "allow" | "deny" | "instruct",
|
|
385
|
+
parsed: Record<string, unknown>,
|
|
386
|
+
session: SessionMetadata,
|
|
387
|
+
cli: IntegrationType,
|
|
388
|
+
opts: EvaluateHookEventOptions | undefined,
|
|
389
|
+
): Promise<void> {
|
|
390
|
+
if (canonicalEventType !== "UserPromptSubmit" || jevForcedOff(opts)) return;
|
|
391
|
+
try {
|
|
392
|
+
if (!(await readJevConfig())) return;
|
|
393
|
+
if (decision === "deny") {
|
|
394
|
+
const { ENFORCEMENT_CAPABILITY } = await import("./enforcement-capability");
|
|
395
|
+
if (ENFORCEMENT_CAPABILITY[cli]?.UserPromptSubmit === "block") return;
|
|
396
|
+
}
|
|
397
|
+
const { captureIntent } = await import("./semantic/intent");
|
|
398
|
+
// Built first, then passed: the one object satisfies §7's shape and T4's.
|
|
399
|
+
const event = {
|
|
400
|
+
eventType: canonicalEventType,
|
|
401
|
+
sessionId: session.sessionId,
|
|
402
|
+
transcriptPath: session.transcriptPath,
|
|
403
|
+
cli,
|
|
404
|
+
payload: parsed,
|
|
405
|
+
prompt: parsed.prompt,
|
|
406
|
+
};
|
|
407
|
+
captureIntent(event);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
hookLogInfo(`Jev intent capture failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
189
413
|
/**
|
|
190
414
|
* The core hook-evaluation logic, decoupled from process stdin/stdout so it
|
|
191
415
|
* can be called repeatedly inside a long-lived process (the daemon's warm
|
|
@@ -561,6 +785,14 @@ export async function evaluateHookEvent(
|
|
|
561
785
|
...(cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudVersion: cloudManaged.version } : {}),
|
|
562
786
|
...(pack ? { packId: pack.id, packVersion: pack.version } : {}),
|
|
563
787
|
});
|
|
788
|
+
// Whether Jev may clear this policy's verdict. The cloud assignment or
|
|
789
|
+
// the pack manifest declares it for those routes, the hook itself only
|
|
790
|
+
// for the user's own files; anything unclear registers as hard. Said
|
|
791
|
+
// aloud when a `reviewable` claim is refused and Jev is configured, so
|
|
792
|
+
// an author is not left wondering why Jev never clears it.
|
|
793
|
+
const authority = authorityDeclarationFor(hook, { cloudManaged, pack });
|
|
794
|
+
const refused = resolvePolicyAuthority(authority).downgraded;
|
|
795
|
+
if (refused) warnAuthority(`${registeredName} asks to be reviewable, but ${refused} — it stays hard`);
|
|
564
796
|
registerPolicy(
|
|
565
797
|
registeredName,
|
|
566
798
|
hook.description ?? "",
|
|
@@ -572,6 +804,7 @@ export async function evaluateHookEvent(
|
|
|
572
804
|
// configured. Matched by the pack's own name for the policy, which is
|
|
573
805
|
// the name before the `pack/<id>@<version>/` prefix is applied.
|
|
574
806
|
pack?.policies.find((p) => p.name === hook.name)?.params,
|
|
807
|
+
authority,
|
|
575
808
|
);
|
|
576
809
|
}
|
|
577
810
|
|
|
@@ -660,19 +893,40 @@ export async function evaluateHookEvent(
|
|
|
660
893
|
);
|
|
661
894
|
}
|
|
662
895
|
|
|
896
|
+
// Two-tier: when a Jev config exists and this is a gate, Jev's request
|
|
897
|
+
// starts HERE, before any regex policy runs, and evaluatePolicies combines
|
|
898
|
+
// the two (see semantic/combine.ts). Otherwise this is null and the call
|
|
899
|
+
// below is exactly the regex-only evaluation it always was.
|
|
900
|
+
const twoTier = await startTwoTier(canonicalEventType, parsed, session, cli, opts, activePause);
|
|
901
|
+
// On the two-tier path the registry is read for the activity row BEFORE
|
|
902
|
+
// evaluating, because evaluatePolicies may hand the registry back to the
|
|
903
|
+
// warm worker's queue (`releaseRegistry`) while it waits on Jev, and the
|
|
904
|
+
// next request's clearPolicies() could then run under this one. Same
|
|
905
|
+
// cached lookup, same answer: nothing between here and there registers.
|
|
906
|
+
const matchedBeforeRelease = twoTier
|
|
907
|
+
? getPoliciesForEvent(canonicalEventType, parsed.tool_name as string | undefined).map((p) => p.name)
|
|
908
|
+
: null;
|
|
909
|
+
|
|
663
910
|
// Evaluate policies (use canonical PascalCase event type)
|
|
664
|
-
const result =
|
|
911
|
+
const result = twoTier
|
|
912
|
+
? await evaluatePolicies(canonicalEventType, parsed, session, config, {
|
|
913
|
+
...twoTier,
|
|
914
|
+
releaseRegistry: opts?.releaseRegistry,
|
|
915
|
+
})
|
|
916
|
+
: await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
665
917
|
const durationMs = Math.round(performance.now() - startTime);
|
|
666
918
|
hookLogInfo(`result=${result.decision} policy=${result.policyName ?? "none"} duration=${durationMs}ms`);
|
|
667
919
|
|
|
920
|
+
await captureJevIntent(canonicalEventType, result.decision, parsed, session, cli, opts);
|
|
921
|
+
|
|
668
922
|
// Which policies actually ran for this event, regardless of how they
|
|
669
923
|
// decided. `result.policyName` names only the decider — null on a plain
|
|
670
924
|
// allow — so without this a row cannot tell "your policy ran and allowed"
|
|
671
925
|
// from "no policy covers this event". The lookup is the same cached call
|
|
672
926
|
// the evaluator already made, so it costs nothing.
|
|
673
|
-
const matchedPolicies =
|
|
674
|
-
|
|
675
|
-
|
|
927
|
+
const matchedPolicies =
|
|
928
|
+
matchedBeforeRelease ??
|
|
929
|
+
getPoliciesForEvent(canonicalEventType, parsed.tool_name as string | undefined).map((p) => p.name);
|
|
676
930
|
|
|
677
931
|
// Persist activity to disk (visible in /policies activity tab)
|
|
678
932
|
const activityEntry = {
|
|
@@ -686,6 +940,8 @@ export async function evaluateHookEvent(
|
|
|
686
940
|
decision: result.decision,
|
|
687
941
|
reason: result.reason,
|
|
688
942
|
durationMs,
|
|
943
|
+
// How Jev took part (§7 fields). Absent unless the two-tier path ran.
|
|
944
|
+
...(result.twoTier ? result.twoTier.activity : {}),
|
|
689
945
|
sessionId: session.sessionId,
|
|
690
946
|
transcriptPath: session.transcriptPath,
|
|
691
947
|
cwd: session.cwd,
|
|
@@ -693,8 +949,10 @@ export async function evaluateHookEvent(
|
|
|
693
949
|
hookEventName: session.hookEventName,
|
|
694
950
|
// Attribution. A builtin is anything registered that is not in the map,
|
|
695
951
|
// so its absence is meaningful rather than missing — but only when a
|
|
696
|
-
// policy actually decided; a plain allow names nobody.
|
|
697
|
-
|
|
952
|
+
// policy actually decided; a plain allow names nobody. When Jev's own
|
|
953
|
+
// verdict decided, no registered policy did: `evaluator` + `jevDecision`
|
|
954
|
+
// say so, and claiming "builtin" here would be false.
|
|
955
|
+
...(result.policyName && !result.twoTier?.decidedByJev
|
|
698
956
|
? (() => {
|
|
699
957
|
const attribution = policyAttribution.get(result.policyName);
|
|
700
958
|
return {
|
|
@@ -751,6 +1009,7 @@ export async function evaluateHookEvent(
|
|
|
751
1009
|
convention_scope: conventionScope,
|
|
752
1010
|
has_custom_params: hasCustomParams,
|
|
753
1011
|
param_keys_overridden: paramKeysOverridden,
|
|
1012
|
+
...jevTelemetry(result.twoTier?.activity),
|
|
754
1013
|
});
|
|
755
1014
|
// Deny/instruct is exactly the response the daemon's warm worker
|
|
756
1015
|
// needs to return fast — a live network POST to PostHog on this path
|