failproofai 1.0.7 → 1.0.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +9 -7
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +6 -6
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +11 -10
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +7 -6
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +52 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -7
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bd3mje._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0o4xkpl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1-i_gzc._.js → _1-7sqrb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
- package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0ahfwmbkpgfw4.js} +1 -1
- package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
- package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 1v_tp3hm8wyhe.js} +1 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/2vo7qbdbvnc0o.js +69 -0
- package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 2z7i-yg59w4pn.js} +1 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
- package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 3bgot5v6f6s3c.js} +1 -1
- package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +6 -0
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +604 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +569 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +14 -3
- package/.next/standalone/app/settings/jev-panel.tsx +630 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +3 -2
- package/bin/failproofai.mjs +148 -4
- package/dist/cli.mjs +18537 -9492
- package/dist/index.js +19 -1
- package/dist/worker.mjs +10421 -3770
- package/package.json +10 -10
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +53 -2
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +195 -8
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +57 -8
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +98 -10
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/effective-reviewers.ts +243 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +303 -7
- package/src/hooks/hook-activity-store.ts +118 -6
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +2172 -0
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +45 -7
- package/src/hooks/pack-cli.ts +568 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +489 -7
- package/src/hooks/pack-store.ts +194 -11
- package/src/hooks/policy-authority.ts +386 -0
- package/src/hooks/policy-catalog.ts +206 -0
- package/src/hooks/policy-evaluator.ts +963 -796
- package/src/hooks/policy-registry.ts +27 -1
- package/src/hooks/policy-reviewability.ts +258 -0
- package/src/hooks/policy-types.ts +128 -0
- package/src/hooks/semantic/combine.ts +581 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +408 -0
- package/src/hooks/semantic/envelope.ts +1255 -0
- package/src/hooks/semantic/evaluator.ts +556 -0
- package/src/hooks/semantic/facts.ts +298 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +1155 -0
- package/src/hooks/semantic/jev-config.ts +1142 -0
- package/src/hooks/semantic/jev-review.ts +389 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +427 -0
- package/src/hooks/semantic/pack-policies.ts +294 -0
- package/src/hooks/semantic/policies.ts +597 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2937 -0
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +156 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +121 -27
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
- package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Jev (two-tier evaluator) fields of a hook activity row: their allowed
|
|
3
|
+
* shapes, the one normaliser every writer and shipper goes through, and a
|
|
4
|
+
* plain-language summary for the dashboard.
|
|
5
|
+
*
|
|
6
|
+
* WHY A NORMALISER AT ALL. These fields leave the machine: the collector ships
|
|
7
|
+
* them to FailproofAI Cloud (`crates/fpai-collect/src/sources/hooks/transform.rs`)
|
|
8
|
+
* and a few ride PostHog (`hook-telemetry.ts`). The rule for everything shipped
|
|
9
|
+
* from a hook row is "decisions and tool names, never file contents", and most
|
|
10
|
+
* of these fields are closed sets that cannot break it. `jevFallbackReason` is
|
|
11
|
+
* the exception: the evaluator's degraded reasons include free text
|
|
12
|
+
* (`prepare: <error message>`, `error: <message>`), and an error message is
|
|
13
|
+
* exactly where a fragment of the judged command or the human's prompt can end
|
|
14
|
+
* up. So a reason is reduced to one of a closed list of CODES before it is
|
|
15
|
+
* stored ({@link JEV_REASON_CODES}), and anything else becomes `other` — even a
|
|
16
|
+
* short kebab-case word, which could be the command's own first word. The Rust
|
|
17
|
+
* side applies the same rule again, for rows written by some other build.
|
|
18
|
+
*
|
|
19
|
+
* WHAT THE VALIDATORS DO NOT COVER. `jevCleared` and `jevModel` are checked for
|
|
20
|
+
* shape only. A cleared name is a registered policy name: no control characters
|
|
21
|
+
* or line breaks, and no whitespace unless it sits under a registered namespace
|
|
22
|
+
* (`custom/`, `pack/`, `cloud/`, `.failproofai-<scope>/`, `failproofai/`),
|
|
23
|
+
* where a user's own hook name may contain spaces. A model id is the alphabet
|
|
24
|
+
* `jev.json` accepts. That keeps a bare sentence, a command line or a prompt
|
|
25
|
+
* out, but a fragment of the right shape — a path, a file name — would pass.
|
|
26
|
+
* Those two fields rely on the writer supplying registered policy names and the
|
|
27
|
+
* provider's model id, which is what the combine rules write.
|
|
28
|
+
*
|
|
29
|
+
* WHAT `evaluator: "jev"` MEANS. The two-tier path ran for this call. It does
|
|
30
|
+
* NOT by itself mean Jev answered. When a hard policy denies, the combine rules
|
|
31
|
+
* abort Jev and record `{ evaluator: "jev", jevMode }`; when no semantic policy
|
|
32
|
+
* applies to the call (TodoWrite, Task, …), no request is sent and they record
|
|
33
|
+
* `{ evaluator: "jev", jevDecision: "allow", jevMode }`. So every reader
|
|
34
|
+
* classifies a row with {@link jevOutcome} — answered, fell back, not consulted
|
|
35
|
+
* or no request — rather than reading `evaluator` alone.
|
|
36
|
+
*
|
|
37
|
+
* Pure on purpose — no node imports — so the client dashboard can use
|
|
38
|
+
* {@link describeJevActivity} without pulling `node:fs` into a browser bundle.
|
|
39
|
+
*/
|
|
40
|
+
import type { HookActivityEntry } from "./hook-activity-store";
|
|
41
|
+
|
|
42
|
+
export type JevActivityFields = Pick<
|
|
43
|
+
HookActivityEntry,
|
|
44
|
+
"evaluator" | "jevDecision" | "jevCleared" | "jevFallbackReason" | "jevLatencyMs" | "jevModel" | "jevMode"
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
/** Every Jev key on an activity row, in the order they are documented. */
|
|
48
|
+
export const JEV_ACTIVITY_KEYS = [
|
|
49
|
+
"evaluator",
|
|
50
|
+
"jevDecision",
|
|
51
|
+
"jevCleared",
|
|
52
|
+
"jevFallbackReason",
|
|
53
|
+
"jevLatencyMs",
|
|
54
|
+
"jevModel",
|
|
55
|
+
"jevMode",
|
|
56
|
+
] as const satisfies ReadonlyArray<keyof JevActivityFields>;
|
|
57
|
+
|
|
58
|
+
const EVALUATORS = new Set(["jev", "jev-fallback"]);
|
|
59
|
+
const DECISIONS = new Set(["allow", "instruct", "deny"]);
|
|
60
|
+
const MODES = new Set(["shadow", "enforce"]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A reason's leading code — lowercase kebab-case — either alone or followed by
|
|
64
|
+
* `:` / `(` and free text: `timeout`, `http-429`, `prepare: <message>`.
|
|
65
|
+
*/
|
|
66
|
+
const REASON_HEAD_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\s*(?:$|[:(])/;
|
|
67
|
+
const HTTP_CODE_RE = /^http-\d{3}$/;
|
|
68
|
+
export const JEV_REASON_MAX_CHARS = 40;
|
|
69
|
+
/** What a reason that is not a known code is stored as. */
|
|
70
|
+
export const JEV_REASON_OTHER = "other";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Every fallback reason code a row may carry, besides `http-NNN`. The codes the
|
|
74
|
+
* Jev client, the evaluator, the throttle and the combine rules produce; any
|
|
75
|
+
* other reason is stored as `other`. A code added to a producer must be added
|
|
76
|
+
* here AND to `JEV_REASON_CODES` in the collector's `transform.rs` (a test
|
|
77
|
+
* keeps the two identical), or it ships as `other` — safe, but less useful.
|
|
78
|
+
*
|
|
79
|
+
* WHY A TUPLE AND A TYPE, not just a set. A producer that writes the code as a
|
|
80
|
+
* bare string literal can be renamed without this list hearing about it, and
|
|
81
|
+
* the result is silent: the row still stores, as `other`, and only a reader
|
|
82
|
+
* months later notices the diagnosis is gone. That is exactly what happened to
|
|
83
|
+
* `request-cut` — the combine rules' name for "Jev answered, but was shown
|
|
84
|
+
* only part of the call" — which was renamed from `truncated` after this list
|
|
85
|
+
* was written and spent its whole life being stored as `other`. So the codes
|
|
86
|
+
* a producer writes DIRECTLY are exported from here as constants typed
|
|
87
|
+
* {@link JevReasonCode} (see {@link JEV_REASON_REQUEST_CUT}): the producer
|
|
88
|
+
* imports the constant instead of retyping the string, and a code this list
|
|
89
|
+
* does not name is a compile error rather than a quiet `other`.
|
|
90
|
+
*
|
|
91
|
+
* Codes no producer writes any more (`truncated`, `request-too-large`) stay
|
|
92
|
+
* here on purpose: rows written by an older build still carry them, and this
|
|
93
|
+
* list is also what the collector applies to THOSE rows.
|
|
94
|
+
*/
|
|
95
|
+
export const JEV_REASON_CODE_LIST = [
|
|
96
|
+
"aborted",
|
|
97
|
+
"cloudflare-error",
|
|
98
|
+
"cloudflare-incomplete",
|
|
99
|
+
"config",
|
|
100
|
+
"error",
|
|
101
|
+
"malformed",
|
|
102
|
+
"model-mismatch",
|
|
103
|
+
"network",
|
|
104
|
+
"no-api-key",
|
|
105
|
+
"no-transport",
|
|
106
|
+
"other",
|
|
107
|
+
"out-of-credits",
|
|
108
|
+
"prepare-error",
|
|
109
|
+
"provider-refused",
|
|
110
|
+
"rate-limited",
|
|
111
|
+
"request-cut",
|
|
112
|
+
"request-too-large",
|
|
113
|
+
"timeout",
|
|
114
|
+
"truncated",
|
|
115
|
+
"unavailable",
|
|
116
|
+
"upstream-error",
|
|
117
|
+
] as const;
|
|
118
|
+
|
|
119
|
+
/** One of {@link JEV_REASON_CODE_LIST}. */
|
|
120
|
+
export type JevReasonCode = (typeof JEV_REASON_CODE_LIST)[number];
|
|
121
|
+
|
|
122
|
+
export const JEV_REASON_CODES: ReadonlySet<string> = new Set<string>(JEV_REASON_CODE_LIST);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* "Jev answered, but the request it answered had to be cut to fit the
|
|
126
|
+
* envelope, so it cleared nothing" — the one fallback reason the combine rules
|
|
127
|
+
* write themselves (`semantic/combine.ts`), rather than passing on from the
|
|
128
|
+
* client or the evaluator. It is a value, not a literal, so that renaming the
|
|
129
|
+
* code in {@link JEV_REASON_CODE_LIST} breaks the build at the producer
|
|
130
|
+
* instead of silently storing `other`.
|
|
131
|
+
*/
|
|
132
|
+
export const JEV_REASON_REQUEST_CUT: JevReasonCode = "request-cut";
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* "The provider would not run the model on this request" — the OTHER thing an
|
|
136
|
+
* HTTP 402 means on these routes, and the one that is not about the operator's
|
|
137
|
+
* balance. The Cloudflare route answers a request whose content it declines
|
|
138
|
+
* with 402 and a `Payment error` category — the same status a real empty
|
|
139
|
+
* account gets — so until that call was split both landed under
|
|
140
|
+
* `out-of-credits`, and `failproofai jev status` told operators to top up
|
|
141
|
+
* accounts that were fine. What can and cannot be told apart from the response
|
|
142
|
+
* is written out in `semantic/jev-client.ts` (`paymentRequiredCode`), which is
|
|
143
|
+
* the only producer; it imports this constant rather than retyping the string,
|
|
144
|
+
* for the reason {@link JEV_REASON_CODE_LIST} gives.
|
|
145
|
+
*/
|
|
146
|
+
export const JEV_REASON_PROVIDER_REFUSED: JevReasonCode = "provider-refused";
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Leading words that are renamed on the way in: the evaluator's free-text
|
|
150
|
+
* prefixes, each mapped to its code. `prepare` is the one that differs — the
|
|
151
|
+
* evaluator writes `prepare: <message>`, and the combine rules cut that down
|
|
152
|
+
* to a bare `prepare` — and both are stored as `prepare-error`, so one failure
|
|
153
|
+
* never shows up under two names. `FREE_TEXT_PREFIXES` in the collector's
|
|
154
|
+
* `transform.rs` is the same list; a test keeps the two identical.
|
|
155
|
+
*/
|
|
156
|
+
export const JEV_FREE_TEXT_PREFIXES: ReadonlyMap<string, string> = new Map<string, string>([
|
|
157
|
+
["prepare", "prepare-error"],
|
|
158
|
+
["error", "error"],
|
|
159
|
+
["timeout", "timeout"],
|
|
160
|
+
["network", "network"],
|
|
161
|
+
["malformed", "malformed"],
|
|
162
|
+
["truncated", "truncated"],
|
|
163
|
+
["rate-limited", "rate-limited"],
|
|
164
|
+
["out-of-credits", "out-of-credits"],
|
|
165
|
+
["model-mismatch", "model-mismatch"],
|
|
166
|
+
["request-too-large", "request-too-large"],
|
|
167
|
+
["config", "config"],
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Model ids: `jev-1.13.0`, `typesafe/jev-1.13-20260917`, `~typesafe/jev-latest`,
|
|
172
|
+
* `@cf/typesafe/jev`. Exactly what `jev.json` accepts for `model` (`MODEL_RE`
|
|
173
|
+
* in `semantic/jev-config.ts`), since the recorded model must match the
|
|
174
|
+
* configured one; `is_model_id` in the collector's `transform.rs` is the same.
|
|
175
|
+
*/
|
|
176
|
+
export const JEV_MODEL_RE = /^[A-Za-z0-9._:/@~+-]{1,200}$/;
|
|
177
|
+
export const JEV_POLICY_NAME_MAX_CHARS = 200;
|
|
178
|
+
/** Control characters (C0, DEL, C1) and the Unicode line and paragraph separators. */
|
|
179
|
+
const NAME_FORBIDDEN_RE = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
180
|
+
/**
|
|
181
|
+
* The namespaces the handler registers a loaded hook under
|
|
182
|
+
* (`${prefix}/${hook.name}`): `custom/`, `pack/<id>@<v>/`, `cloud/<id>@<v>/`,
|
|
183
|
+
* `.failproofai-<scope>/`, plus the builtins' `failproofai/`. The hook's own
|
|
184
|
+
* name after the prefix is whatever its author wrote, spaces included.
|
|
185
|
+
*/
|
|
186
|
+
const REGISTERED_NAMESPACE_RE = /^(?:custom|pack|cloud|failproofai|\.failproofai-[A-Za-z0-9_-]{1,32})\//;
|
|
187
|
+
export const JEV_CLEARED_MAX = 64;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* True for a string shaped like a registered policy name: 1–200 characters,
|
|
191
|
+
* no control characters or line breaks, and whitespace only after a registered
|
|
192
|
+
* namespace (`custom/No secrets in logs`). A bare sentence or command line
|
|
193
|
+
* (`curl -d @payroll.csv`) is not a policy name. `is_policy_name` in the
|
|
194
|
+
* collector's `transform.rs` applies the same rule.
|
|
195
|
+
*/
|
|
196
|
+
export function isJevPolicyName(s: unknown): s is string {
|
|
197
|
+
if (typeof s !== "string" || s.length === 0 || [...s].length > JEV_POLICY_NAME_MAX_CHARS) return false;
|
|
198
|
+
if (NAME_FORBIDDEN_RE.test(s)) return false;
|
|
199
|
+
return !/\s/.test(s) || REGISTERED_NAMESPACE_RE.test(s);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Reduce a fallback reason to a known code, or undefined when there is none.
|
|
204
|
+
*
|
|
205
|
+
* - A known code (`timeout`, `http-429`, `model-mismatch`), alone or in front
|
|
206
|
+
* of free text (`prepare: Unexpected token…`, `http-500: upstream said …`):
|
|
207
|
+
* that code, lowercased, renamed through {@link JEV_FREE_TEXT_PREFIXES}.
|
|
208
|
+
* - Anything else: `other`. Never the text — not even a short kebab-case word
|
|
209
|
+
* that merely looks like a code (`curl`, `secret-project`).
|
|
210
|
+
*/
|
|
211
|
+
export function normalizeJevFallbackReason(raw: unknown): string | undefined {
|
|
212
|
+
if (typeof raw !== "string") return undefined;
|
|
213
|
+
const s = raw.trim().toLowerCase();
|
|
214
|
+
if (s.length === 0) return undefined;
|
|
215
|
+
const head = REASON_HEAD_RE.exec(s)?.[1];
|
|
216
|
+
if (head === undefined || head.length > JEV_REASON_MAX_CHARS) return JEV_REASON_OTHER;
|
|
217
|
+
if (HTTP_CODE_RE.test(head)) return head;
|
|
218
|
+
const code = JEV_FREE_TEXT_PREFIXES.get(head) ?? head;
|
|
219
|
+
return JEV_REASON_CODES.has(code) ? code : JEV_REASON_OTHER;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The Jev fields of `entry`, each one valid or absent. Non-Jev keys pass
|
|
224
|
+
* through untouched, and an entry with no Jev keys comes back as the same
|
|
225
|
+
* object — the unconfigured path must not be rewritten on its way to disk.
|
|
226
|
+
*
|
|
227
|
+
* Deliberately field-by-field rather than all-or-nothing: an invalid model id
|
|
228
|
+
* is no reason to lose the fact that Jev fell back.
|
|
229
|
+
*/
|
|
230
|
+
export function sanitizeJevActivity<T extends JevActivityFields>(entry: T): T {
|
|
231
|
+
if (!JEV_ACTIVITY_KEYS.some((k) => entry[k] !== undefined)) return entry;
|
|
232
|
+
const out: T = { ...entry };
|
|
233
|
+
const drop = (k: keyof JevActivityFields) => {
|
|
234
|
+
delete out[k];
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (!(typeof out.evaluator === "string" && EVALUATORS.has(out.evaluator))) drop("evaluator");
|
|
238
|
+
if (!(typeof out.jevDecision === "string" && DECISIONS.has(out.jevDecision))) drop("jevDecision");
|
|
239
|
+
if (!(typeof out.jevMode === "string" && MODES.has(out.jevMode))) drop("jevMode");
|
|
240
|
+
|
|
241
|
+
if (Array.isArray(out.jevCleared)) {
|
|
242
|
+
const names = [
|
|
243
|
+
...new Set(out.jevCleared.filter(isJevPolicyName)),
|
|
244
|
+
].slice(0, JEV_CLEARED_MAX);
|
|
245
|
+
out.jevCleared = names;
|
|
246
|
+
} else {
|
|
247
|
+
drop("jevCleared");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const reason = normalizeJevFallbackReason(out.jevFallbackReason);
|
|
251
|
+
if (reason === undefined) drop("jevFallbackReason");
|
|
252
|
+
else out.jevFallbackReason = reason;
|
|
253
|
+
|
|
254
|
+
if (typeof out.jevLatencyMs === "number" && Number.isFinite(out.jevLatencyMs) && out.jevLatencyMs >= 0) {
|
|
255
|
+
out.jevLatencyMs = Math.round(out.jevLatencyMs);
|
|
256
|
+
} else {
|
|
257
|
+
drop("jevLatencyMs");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (typeof out.jevModel === "string" && JEV_MODEL_RE.test(out.jevModel.trim())) out.jevModel = out.jevModel.trim();
|
|
261
|
+
else drop("jevModel");
|
|
262
|
+
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** True when the row says which evaluator ran, i.e. a Jev config was present for the call. */
|
|
267
|
+
export function hasJevActivity(entry: JevActivityFields): boolean {
|
|
268
|
+
return entry.evaluator === "jev" || entry.evaluator === "jev-fallback";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* What happened to Jev on one call:
|
|
273
|
+
*
|
|
274
|
+
* - `answered`: Jev's answer was read and the combine rules applied it.
|
|
275
|
+
* - `fallback`: Jev was asked and was unavailable, truncated or mismatched, so
|
|
276
|
+
* the regex result stood (`evaluator: "jev-fallback"`).
|
|
277
|
+
* - `not-consulted`: a hard policy denied first, so Jev was aborted and its
|
|
278
|
+
* answer never read. The combine rules record exactly
|
|
279
|
+
* `{ evaluator: "jev", jevMode }` for that.
|
|
280
|
+
* - `no-request`: no semantic policy applies to the call (TodoWrite, Task,
|
|
281
|
+
* Skill, …), so Jev had nothing to ask and no request was sent. The
|
|
282
|
+
* evaluator's answer to an empty question list is always `allow`, and the
|
|
283
|
+
* combine rules record `{ evaluator: "jev", jevDecision: "allow", jevMode }`
|
|
284
|
+
* — a verdict with no latency and no model.
|
|
285
|
+
*
|
|
286
|
+
* Null when Jev was not part of the call (no config, or not a gate).
|
|
287
|
+
*
|
|
288
|
+
* A `jev` row counts as answered when it carries anything only a request
|
|
289
|
+
* produces: a cleared list (even an empty one), a latency, a model id, or a
|
|
290
|
+
* `deny` / `instruct` verdict (the evaluator reaches those only from answers).
|
|
291
|
+
* An `allow` and nothing else is `no-request`; no Jev field at all (or only a
|
|
292
|
+
* verdict this build cannot read) is `not-consulted`. `transform.rs` applies
|
|
293
|
+
* the same rule (`JevOutcome`).
|
|
294
|
+
*/
|
|
295
|
+
export type JevOutcome = "answered" | "fallback" | "not-consulted" | "no-request";
|
|
296
|
+
|
|
297
|
+
export function jevOutcome(raw: JevActivityFields): JevOutcome | null {
|
|
298
|
+
return outcomeOf(sanitizeJevActivity(raw));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** {@link jevOutcome} for a row that is already sanitized. */
|
|
302
|
+
function outcomeOf(e: JevActivityFields): JevOutcome | null {
|
|
303
|
+
if (e.evaluator === "jev-fallback") return "fallback";
|
|
304
|
+
if (e.evaluator !== "jev") return null;
|
|
305
|
+
if (e.jevCleared !== undefined || e.jevLatencyMs !== undefined || e.jevModel !== undefined) return "answered";
|
|
306
|
+
if (e.jevDecision === "deny" || e.jevDecision === "instruct") return "answered";
|
|
307
|
+
if (e.jevDecision === "allow") return "no-request";
|
|
308
|
+
return "not-consulted";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Fallback reasons that mean Jev DID answer and the combine rules set the
|
|
313
|
+
* answer aside: the call was cut to fit the envelope (Jev judged only part of
|
|
314
|
+
* it), or a model other than the configured one answered. `truncated` is the
|
|
315
|
+
* name a build before the rename wrote for the first of those, kept so an old
|
|
316
|
+
* row still reads correctly.
|
|
317
|
+
*/
|
|
318
|
+
const ANSWERED_BUT_NOT_APPLIED_REASONS: ReadonlySet<string> = new Set<string>([
|
|
319
|
+
JEV_REASON_REQUEST_CUT,
|
|
320
|
+
"truncated",
|
|
321
|
+
"model-mismatch",
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* True for a fallback row where Jev answered but its answer was not applied —
|
|
326
|
+
* the row carries Jev's verdict (the combine rules record one only when Jev
|
|
327
|
+
* answered), or its reason is one of {@link ANSWERED_BUT_NOT_APPLIED_REASONS}.
|
|
328
|
+
* Such a row is not "Jev unavailable". Expects a sanitized row.
|
|
329
|
+
*/
|
|
330
|
+
export function jevAnsweredButNotApplied(e: JevActivityFields): boolean {
|
|
331
|
+
if (e.evaluator !== "jev-fallback") return false;
|
|
332
|
+
return e.jevDecision !== undefined || ANSWERED_BUT_NOT_APPLIED_REASONS.has(e.jevFallbackReason ?? "");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** What the dashboard says about a call Jev was not consulted on. */
|
|
336
|
+
export const JEV_NOT_CONSULTED_FACT = "Jev not consulted: a hard policy's deny is final";
|
|
337
|
+
/** What the dashboard says about a call no semantic policy applied to. */
|
|
338
|
+
export const JEV_NO_REQUEST_FACT = "Jev not asked: no semantic policy applies to this call";
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* What Jev did on one row, as short facts in display order — e.g.
|
|
342
|
+
* `["Jev verdict: allow", "cleared block-env-files", "38 ms", "jev-1.13.0"]` —
|
|
343
|
+
* or null when Jev was not involved. Plain language, no probabilities: those
|
|
344
|
+
* live in the verdict log. Never an empty list: a call Jev was not consulted
|
|
345
|
+
* on says so ({@link JEV_NOT_CONSULTED_FACT}). A fallback where Jev answered
|
|
346
|
+
* but was set aside (truncated, model mismatch) says "Jev's answer not
|
|
347
|
+
* applied" and shows the verdict it gave, not "Jev unavailable".
|
|
348
|
+
*/
|
|
349
|
+
export function describeJevActivity(raw: JevActivityFields): string[] | null {
|
|
350
|
+
const e = sanitizeJevActivity(raw);
|
|
351
|
+
const outcome = outcomeOf(e);
|
|
352
|
+
if (outcome === null) return null;
|
|
353
|
+
if (outcome === "not-consulted") return [JEV_NOT_CONSULTED_FACT];
|
|
354
|
+
if (outcome === "no-request") return [JEV_NO_REQUEST_FACT];
|
|
355
|
+
const facts: string[] = [];
|
|
356
|
+
|
|
357
|
+
if (outcome === "fallback") {
|
|
358
|
+
const reason = e.jevFallbackReason ?? "unknown reason";
|
|
359
|
+
if (jevAnsweredButNotApplied(e)) {
|
|
360
|
+
// Jev did answer — the call was cut to fit the envelope, or the wrong
|
|
361
|
+
// model answered — so "unavailable" would be false, and its verdict is
|
|
362
|
+
// exactly what someone reading this row wants to see.
|
|
363
|
+
facts.push(`Jev's answer not applied: ${reason}`);
|
|
364
|
+
if (e.jevDecision) facts.push(`Jev verdict (not applied): ${e.jevDecision}`);
|
|
365
|
+
} else {
|
|
366
|
+
facts.push(`Jev unavailable: ${reason}`);
|
|
367
|
+
}
|
|
368
|
+
facts.push("the regex policies decided alone");
|
|
369
|
+
if (e.jevLatencyMs !== undefined) facts.push(`${e.jevLatencyMs} ms`);
|
|
370
|
+
if (e.jevModel && jevAnsweredButNotApplied(e)) facts.push(e.jevModel);
|
|
371
|
+
return facts;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (e.jevDecision) facts.push(`Jev verdict: ${e.jevDecision}`);
|
|
375
|
+
const cleared = e.jevCleared ?? [];
|
|
376
|
+
if (cleared.length > 0) {
|
|
377
|
+
facts.push(`${e.jevMode === "shadow" ? "would have cleared" : "cleared"} ${cleared.join(", ")}`);
|
|
378
|
+
}
|
|
379
|
+
if (e.jevMode === "shadow") facts.push("shadow mode: the regex result was enforced");
|
|
380
|
+
if (e.jevLatencyMs !== undefined) facts.push(`${e.jevLatencyMs} ms`);
|
|
381
|
+
if (e.jevModel) facts.push(e.jevModel);
|
|
382
|
+
// Answered, but nothing this build can show (a verdict in a shape it cannot read).
|
|
383
|
+
if (facts.length === 0) facts.push("Jev answered");
|
|
384
|
+
return facts;
|
|
385
|
+
}
|