failproofai 1.0.7-beta.2 → 1.0.7

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 (252) 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 +7 -8
  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 +5 -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 -13
  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 +7 -8
  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 +7 -7
  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 +6 -7
  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 +8 -41
  63. package/.next/standalone/.next/server/app/settings/page.js +9 -12
  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]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
  71. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  72. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
  98. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-i_gzc._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _166t73i._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
  104. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +23 -56
  121. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 0__8a7m868fvf.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 0o6qlkgubtoex.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 13i7-9is-vhys.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
  126. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  127. package/.next/standalone/.next/static/chunks/{2_pltstd8-xgs.js → 2klitrtzpaoe0.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 2mdh397ghgnvv.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
  130. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
  131. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +69 -0
  132. package/.next/standalone/.next/static/chunks/{1qd741hzlmjbo.js → 43ufqrz8qo3h-.js} +1 -1
  133. package/.next/standalone/SECURITY.md +53 -0
  134. package/.next/standalone/app/actions/pack-actions.ts +0 -12
  135. package/.next/standalone/app/policies/hooks-client.tsx +0 -9
  136. package/.next/standalone/app/settings/page.tsx +1 -20
  137. package/.next/standalone/app/settings/settings-client.tsx +1 -27
  138. package/.next/standalone/app/settings/settings.css +0 -79
  139. package/.next/standalone/package.json +10 -10
  140. package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
  141. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
  142. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
  143. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
  144. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
  145. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
  146. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
  147. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
  148. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
  150. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
  151. package/.next/standalone/server.js +1 -1
  152. package/README.md +2 -2
  153. package/bin/failproofai.mjs +2 -115
  154. package/dist/cli.mjs +6543 -13872
  155. package/dist/index.js +1 -19
  156. package/dist/worker.mjs +2022 -8055
  157. package/package.json +10 -10
  158. package/pi-extension/index.ts +0 -11
  159. package/scripts/build-policy-pack.mjs +2 -53
  160. package/src/audit/features.ts +2 -3
  161. package/src/hooks/builtin-policies.ts +8 -177
  162. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  163. package/src/hooks/cloud-managed-policies.ts +0 -22
  164. package/src/hooks/custom-hooks-loader.ts +7 -45
  165. package/src/hooks/custom-hooks-registry.ts +1 -45
  166. package/src/hooks/first-run-gate.ts +0 -5
  167. package/src/hooks/fp-home.ts +0 -23
  168. package/src/hooks/handler.ts +6 -265
  169. package/src/hooks/hook-activity-store.ts +1 -105
  170. package/src/hooks/hook-telemetry.ts +0 -41
  171. package/src/hooks/loader-utils.ts +0 -6
  172. package/src/hooks/manager.ts +1 -1
  173. package/src/hooks/pack-cli.ts +19 -412
  174. package/src/hooks/pack-manifest.ts +7 -479
  175. package/src/hooks/pack-store.ts +11 -157
  176. package/src/hooks/policy-catalog.ts +0 -204
  177. package/src/hooks/policy-evaluator.ts +796 -940
  178. package/src/hooks/policy-registry.ts +0 -25
  179. package/src/hooks/policy-types.ts +0 -126
  180. package/src/hooks/types.ts +1 -1
  181. package/src/hooks/worker-server.ts +26 -119
  182. package/src/index.ts +0 -6
  183. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  184. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  185. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
  186. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
  187. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
  189. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
  190. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  191. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
  192. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
  193. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  194. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  195. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
  196. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  197. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  198. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
  199. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
  200. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
  201. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  202. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +0 -151
  210. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  211. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  213. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  215. package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +0 -2
  216. package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
  217. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
  218. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
  219. package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +0 -6
  220. package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +0 -69
  221. package/.next/standalone/PROBE-FOLLOWUP.md +0 -186
  222. package/.next/standalone/app/actions/get-jev-config.ts +0 -424
  223. package/.next/standalone/app/actions/update-jev-config.ts +0 -420
  224. package/.next/standalone/app/components/jev-notices.tsx +0 -96
  225. package/.next/standalone/app/settings/jev-panel.tsx +0 -473
  226. package/src/hooks/effective-reviewers.ts +0 -79
  227. package/src/hooks/jev-activity.ts +0 -385
  228. package/src/hooks/jev-cli.ts +0 -1249
  229. package/src/hooks/policy-authority.ts +0 -333
  230. package/src/hooks/policy-reviewability.ts +0 -229
  231. package/src/hooks/semantic/combine.ts +0 -541
  232. package/src/hooks/semantic/compile.ts +0 -176
  233. package/src/hooks/semantic/decide.ts +0 -392
  234. package/src/hooks/semantic/envelope.ts +0 -1296
  235. package/src/hooks/semantic/evaluator.ts +0 -547
  236. package/src/hooks/semantic/facts.ts +0 -292
  237. package/src/hooks/semantic/intent.ts +0 -1190
  238. package/src/hooks/semantic/jev-client.ts +0 -643
  239. package/src/hooks/semantic/jev-config.ts +0 -594
  240. package/src/hooks/semantic/jev-review.ts +0 -374
  241. package/src/hooks/semantic/jev-stats.ts +0 -289
  242. package/src/hooks/semantic/jev-throttle.ts +0 -421
  243. package/src/hooks/semantic/pack-policies.ts +0 -251
  244. package/src/hooks/semantic/policies.ts +0 -596
  245. package/src/hooks/semantic/precondition-names.ts +0 -60
  246. package/src/hooks/semantic/preconditions.ts +0 -58
  247. package/src/hooks/semantic/redact.ts +0 -2910
  248. package/src/hooks/semantic/types.ts +0 -145
  249. package/src/hooks/semver-precedence.ts +0 -128
  250. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
  251. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
  252. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
@@ -1,541 +0,0 @@
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 did not
66
- * come back `deny` — that is, it answered `none`, `overridden` or `instruct`.
67
- * A name Jev was not asked about — its precondition was false, the tool class
68
- * did not apply, the name is misspelled — keeps the regex verdict standing.
69
- *
70
- * ## A warning-level answer clears the deny, and leaves the warning
71
- *
72
- * The rule above is the one thing about this module that was chosen on
73
- * measured numbers rather than argued from first principles, so this is what
74
- * it decides and what it deliberately does not.
75
- *
76
- * An `instruct` answer means the named check looked at the call, found the
77
- * thing the regex policy is worried about, and judged it worth a WARNING —
78
- * not a block. Counting that as "not cleared" left the regex DENY standing,
79
- * which threw the distinction away: the agent was stopped by a string match
80
- * on exactly the calls where the check that was supposed to review it had
81
- * already said "warn, do not stop". Over the 1,332 labelled cases that was
82
- * the single largest remaining group of false blocks. Measured on this
83
- * commit, over recorded provider answers for all 1,332 (the live Cloudflare
84
- * run they were recorded from reproduces case for case under the previous
85
- * rule): real work blocked 13.9% → 8.7% — 12.5% → 7.3% leaving out the
86
- * always-on self-protection guard, which is hard and unclearable — against
87
- * 33.3% for the regex tier alone, attacks blocked 134 → 132 of 234, exact
88
- * agreement with the labels 70.6% → 70.8%. The live run on which the rule was
89
- * chosen put the same four at 14.3% → 8.9%, 129 → 127, 70.6% → 70.8%; the few
90
- * cases between the two are drift in what gets ASKED since the answers were
91
- * recorded, not in this rule.
92
- *
93
- * All 54 calls the change moves go deny → warning. None goes to allow, and
94
- * neither the regex tier's floor nor any hard policy moves at all.
95
- *
96
- * Clearing on `instruct` does not silence anything. The same answer that
97
- * clears makes Jev's OWN decision an instruct (`decide.ts` — any instruct
98
- * outcome makes the verdict an instruct), and that instruct joins the
99
- * most-severe merge below. So the call comes out a WARNING that says what is
100
- * actually wrong with it, in Jev's words, instead of a block in the regex
101
- * policy's words. A clear here converts a deny into a warning; it does not
102
- * convert it into silence.
103
- *
104
- * What this deliberately does NOT relax is WHICH questions must have been
105
- * asked. The gate is still `asked.has(name) && …` for every name in
106
- * `reviewedBy`: a check whose precondition was false, that does not apply to
107
- * this tool class, or that is misspelled was never put to Jev, so there is no
108
- * answer to read and the regex verdict stands. Rule B widens what counts as a
109
- * clear ANSWER (`none`, `overridden`, `instruct`), never what counts as an
110
- * asked QUESTION — a call Jev was not asked about is not a call Jev approved,
111
- * and an unmeasured concern is not an absent one. `deny` is the one answer
112
- * that keeps the block: the check looked and said stop.
113
- *
114
- * ## One rule about a partial picture
115
- *
116
- * Three things mean Jev judged less than the call: part of `agent_request` or
117
- * of the computed `facts` was cut (`requestCut`), injection is suspected, or
118
- * the injection probe was never asked. Each withdraws every CLEAR and nothing
119
- * else. Jev's own deny or instruct still joins the most-severe rule, because
120
- * an answer given on part of the evidence can only ever ADD severity —
121
- * `combineTwoTier`'s result is never less severe than `regexOnly(verdicts)`
122
- * unless a clear fired, and a clear fires only when the call was read whole,
123
- * injection was asked about, and the answer was no.
124
- *
125
- * That is what makes padding useless AGAINST THE CALL'S OWN TEXT. Five review
126
- * rounds tried to prevent the hiding instead — a head-and-tail window, a token
127
- * skeleton, per-field caps — and each time the next spelling walked through
128
- * it, because a bounded projection of an unbounded string always drops
129
- * SOMETHING and the attacker picks what. So this rule does not try: dropping a
130
- * byte of `agent_request` requires a cut, every such cut sets `requestCut`
131
- * (`envelope.ts`), and a call with `requestCut` can clear nothing. Spending the
132
- * budget can cost a call its clears; it can never buy one.
133
- *
134
- * The same sentence is NOT true of the derived `facts`, and saying so is
135
- * better than asserting a property the code does not have. `facts` are
136
- * computed from the first `MAX_SCAN_CHARS` characters of a command and from a
137
- * capped number of paths (`facts.ts`), and neither bound sets a flag. What a
138
- * long enough command can therefore cost is a QUESTION — `selectPolicies` may
139
- * not pick a probe whose precondition reads those facts — not a clear on
140
- * evidence Jev never saw: the command text itself is carried whole and judged.
141
- * Flagging it from here was measured and rejected (it charges an ordinary
142
- * 20,000-character heredoc, and `prettier --write` over thirteen files, their
143
- * clears); the narrow fix is for `facts.ts` to report that it stopped and for
144
- * `policies.ts` to read incomplete evidence as a reason to ASK MORE. Until
145
- * that lands, this paragraph — not the one above it — is what holds. See
146
- * `envelope.ts`'s header for the same statement at the source.
147
- *
148
- * ## What size may NOT do
149
- *
150
- * A previous revision went one step further and DENIED a call whose own text
151
- * had been cut — `semantic/request-too-large-to-review`, telling the agent to
152
- * split it up. That is gone, and it is worth saying why, because the argument
153
- * for it was not silly: in the case this tier exists for, the regex tier has
154
- * no rule, so its floor is `allow`, and withdrawing clears protects nothing.
155
- *
156
- * It was still wrong. The refusal was a deny this product invented, on work no
157
- * policy objected to, at a threshold ordinary calls reach — a ~1,400-line
158
- * `Write`, a large MCP body, a long generated migration. The whole reason this
159
- * tier exists is that regex policies block a third of real work; a new
160
- * refusal of our own, firing on size rather than on behaviour, spends the
161
- * benefit it was built to deliver. And it is not even the strongest available
162
- * answer: Jev is still ASKED, with whatever fitted, and a cut call that is
163
- * actually dangerous is denied on Jev's own verdict, with a reason that names
164
- * what is wrong with it rather than how long it is.
165
- *
166
- * So: size can only ever make a call stricter through Jev's own verdict or
167
- * through a regex policy, never through a refusal here. What it costs is the
168
- * clears.
169
- *
170
- * ## What a cut MESSAGE does: nothing
171
- *
172
- * `truncated` — a human turn, an agent message or a store-capped prompt that
173
- * did not fit — is recorded and read no further. It used to withdraw clears
174
- * too, and that made the length of the human's own prompt decide the verdict:
175
- * the same call came out `allow` after a 77-character prompt and `deny` after
176
- * a 3,000-character one, because the clearing half of the tier switched off.
177
- * Pasting a spec or a stack trace is not an attack, the human's turn is not a
178
- * channel the agent can write, and consent read out of it is checked locally
179
- * against the UNCUT text (see `Envelope.evidence`), so there is nothing here
180
- * for a cut to protect.
181
- *
182
- * What the clearing gate claims, exactly. The envelope keeps a BOUNDED window
183
- * by design — the last `MAX_USER_MESSAGES` human turns — and dropping older
184
- * turns does not set `truncated`. So a clear rests on a complete view of what
185
- * was SENT, not on the whole session: an instruction from twenty turns ago is
186
- * not in the picture, and the tier is not a record of consent over a session.
187
- *
188
- * The local `targetNamedByUser` check reads `Envelope.evidence`, which is that
189
- * window and nothing wider — a turn Jev never saw cannot supply the consent
190
- * for an `op-requested` override (it could, and flipped a reviewable deny to
191
- * allow). The two channels differ inside the window,
192
- * deliberately, and the guarantee is per channel rather than per character:
193
- * - `user_said` is reported UNCUT. A target named in the cut middle of a
194
- * long prompt is consent the human typed, and treating it as absent turned
195
- * explicit requests into instructs and denies.
196
- * - `agent_last_message` is reported as SENT, capped and redacted. The agent
197
- * writes that channel and it repeats third-party text, so consent found
198
- * only in a part of it Jev never read is exactly the subtraction this
199
- * design refuses everywhere else.
200
- *
201
- * "Uncut" there means uncut by the ENVELOPE, and there is one cut it cannot
202
- * undo: T4's intent store caps a prompt as it READS it (`intent.ts`), so a
203
- * 12,000-character prompt arrives already head-and-tail cut and its middle is
204
- * not recoverable by anyone. Run over that, the local substring check read
205
- * "the user did not name it" off a string that could not have contained it —
206
- * the same subtraction one channel further out, and the same symptom: the
207
- * length of the human's paste decided the verdict. So a human turn that
208
- * arrived cut makes the local check INCONCLUSIVE rather than negative
209
- * (`PreparedCall.userSaidCut`, `DecideV1Options.userSaidCut`), and the
210
- * override rests on what it rests on when a call names no identifiable target
211
- * at all — Jev's own `op_requested` and `scope` answers. It can only restore a
212
- * clear a short prompt would have kept, it is read from the human channel
213
- * only, and `targetNamedByUser` still reports FALSE, because nothing in what
214
- * we hold named the target; what changes is that "false" no longer decides.
215
- *
216
- * Severity, meanwhile, is never subtracted by a cut at all. §4's table files a
217
- * truncated envelope under "fall back to the regex result", and reading that
218
- * as "throw Jev's answer away" made padding a command past the envelope's
219
- * per-field cap a working way to stop Jev's OWN deny applying (a real repro:
220
- * `rm -rf / --no-preserve-root` + 2,100 spaces flipped deny → allow).
221
- * Truncation is attacker-influenceable, so it may never subtract severity.
222
- * What §4 asks for is still there — every regex deny counts, and the call is
223
- * recorded as `jev-fallback` — the answer is just not thrown away on the way
224
- * in.
225
- *
226
- * Size was the same class one step further out, and is no longer a case at
227
- * all: a request over `MAX_REQUEST_CHARS` used to be
228
- * `degraded("request-too-large")` — a `fallback`, no verdict, regex-only —
229
- * which one ignored 70,000-character `file_path`, one long object KEY, or
230
- * 36,000 empty strings in an array was enough to trigger. The envelope is now
231
- * built inside a hard budget whose accounting charges what serialization
232
- * really costs, and being over it is not a degrade at all: the request is sent
233
- * with what fitted and arrives here `answered`, with `requestCut`.
234
- *
235
- * Structurally: a `fallback` review carries no `decision` at all, so a verdict
236
- * Jev actually produced cannot be filed as one. If Jev decided, it comes
237
- * through `answered` (possibly `requestCut`), and `answered` always reaches
238
- * the most-severe merge.
239
- *
240
- * A clear is only as good as the check that the call is not acting on text
241
- * planted by a repo, a tool result or the agent itself, so the injection probe
242
- * is asked on EVERY call — including one with no recorded human message, where
243
- * there is no consent to withdraw but there is still planted text to notice.
244
- * `injectionAsked` false therefore means the request was never sent at all,
245
- * and then there is nothing to clear anyway.
246
- */
247
- import type { PolicyAuthority } from "../policy-types";
248
- // The one value import, and deliberately from the activity vocabulary rather
249
- // than a local literal: the fallback reason this module writes itself has to
250
- // be a code the activity store's closed list names, or it is stored and
251
- // shipped as `other`. Importing the constant makes a rename a compile error
252
- // here. `jev-activity.ts` is pure (no node imports, no semantic modules), so
253
- // this costs the hook path nothing.
254
- import { JEV_REASON_REQUEST_CUT } from "../jev-activity";
255
-
256
- export type Decision = "allow" | "deny" | "instruct";
257
- export type JevMode = "shadow" | "enforce";
258
-
259
- /** One regex policy's verdict, in evaluation order. */
260
- export interface RegexVerdict {
261
- policyName: string;
262
- decision: Decision;
263
- /**
264
- * deny / instruct: the reason exactly as it would be shown (hint appended).
265
- * allow: the informational note, or null for a silent allow.
266
- */
267
- reason: string | null;
268
- /** EFFECTIVE authority (see `effectiveAuthority`), never the declared one. */
269
- authority: PolicyAuthority;
270
- /** The semantic policies that must all be asked and none answer `deny`. Empty for `hard`. */
271
- reviewedBy: readonly string[];
272
- }
273
-
274
- /** What Jev said, reduced to exactly what the combine rules read. */
275
- export type JevReview =
276
- | {
277
- /** Aborted because a hard deny already decided; Jev's answer was never read. */
278
- kind: "not-consulted";
279
- }
280
- | {
281
- /**
282
- * Jev produced no verdict: degraded (timeout, 429, HTTP/parse error,
283
- * model mismatch, no transport, a config that cannot be built, …).
284
- *
285
- * Deliberately carries NO `decision`. A verdict Jev did produce must not
286
- * be droppable on the way into the combine — that is how padding a
287
- * command used to make Jev's own deny disappear — so anything Jev
288
- * decided comes through `answered`, whose severity is always merged.
289
- */
290
- kind: "fallback";
291
- reason: string;
292
- latencyMs: number | null;
293
- model: string | null;
294
- }
295
- | {
296
- kind: "answered";
297
- decision: Decision;
298
- reason: string | null;
299
- /** Attribution for Jev's own deny/instruct, e.g. `semantic/destructive-deletion`. */
300
- policyName: string;
301
- /** Semantic policies whose questions were in the request that was answered. */
302
- asked: readonly string[];
303
- /**
304
- * Of `asked`, the ones whose outcome was not `deny` — `none`,
305
- * `overridden` or `instruct`. Those are the answers that let a
306
- * reviewable regex verdict be cleared; see "A warning-level answer
307
- * clears the deny, and leaves the warning" above. Named for what it
308
- * holds rather than for what it is used for, because "clear" once meant
309
- * `none`/`overridden` only and a silent widening of that set is exactly
310
- * the mistake this name prevents.
311
- */
312
- notDenied: readonly string[];
313
- /**
314
- * The injection probe was in the request and answered. False → no clear:
315
- * an unmeasured injection is not an absent one.
316
- */
317
- injectionAsked: boolean;
318
- /** The injection probe held: every clear is withdrawn. */
319
- injected: boolean;
320
- /**
321
- * Something did not fit: a human turn, the agent's last message, or what
322
- * the intent store had already capped (`SemanticOutcome.truncated`).
323
- *
324
- * READ BY NOTHING HERE, on purpose — see "What a cut MESSAGE does"
325
- * above. It stays on the review so the verdict log and a future
326
- * diagnostic can see it, and so that the field's absence is not mistaken
327
- * for the cut's absence.
328
- */
329
- truncated: boolean;
330
- /**
331
- * Part of what the CALL DOES was cut: the tool input did not fit the
332
- * envelope's request budget, a redaction removed a span of it that could
333
- * have been executable, or a computed fact about it was dropped
334
- * (`SemanticOutcome.requestCut`). Implies `truncated`.
335
- *
336
- * Withdraws every clear — a clear resting on a call half of which was
337
- * never read is not a clear — and nothing else: the decision below still
338
- * joins the most-severe rule, so padding cannot subtract severity, and
339
- * it cannot add a refusal of our own either. Recorded as `jev-fallback`
340
- * / `request-cut`.
341
- */
342
- requestCut: boolean;
343
- /**
344
- * Null when nothing had to be sent (no semantic policy applied), or when
345
- * the answer came from the throttle's cache: a hit's ~0 ms is not a
346
- * provider latency.
347
- */
348
- latencyMs: number | null;
349
- model: string | null;
350
- };
351
-
352
- /**
353
- * What `evaluatePolicies` receives when the two-tier path is on: an in-flight
354
- * Jev review (started before the regex policies run) and how to read each
355
- * registered policy's effective authority. Built by `jev-review.ts`.
356
- */
357
- export interface TwoTierReview {
358
- mode: JevMode;
359
- /** Resolves once Jev answered, failed, or was aborted. Never rejects. */
360
- review: Promise<JevReview>;
361
- /** Abort the in-flight request (a hard deny decided). No-op once settled. */
362
- abort(): void;
363
- /**
364
- * Called once, right before the evaluator awaits `review` — by then every
365
- * read of the process-global policy registry for this call is done. The
366
- * daemon's warm worker serializes requests around that registry, and uses
367
- * this to let the next queued request run while this one waits on the
368
- * network instead of holding every hook on the machine behind it.
369
- */
370
- releaseRegistry?: () => void;
371
- /** Effective authority + the reviewing semantic policies (empty unless reviewable). */
372
- authorityOf(p: { name: string; authority?: PolicyAuthority; reviewedBy?: string[] }): {
373
- authority: PolicyAuthority;
374
- reviewedBy: string[];
375
- };
376
- }
377
-
378
- export interface FinalVerdict {
379
- decision: Decision;
380
- /** deny: exactly the one decider; instruct: every instruct; allow: every note. */
381
- entries: Array<{ policyName: string; reason: string }>;
382
- }
383
-
384
- /** The hook-activity fields (§7 contract) for one two-tier evaluation. */
385
- export interface JevActivityFields {
386
- evaluator: "jev" | "jev-fallback";
387
- jevDecision?: Decision;
388
- jevCleared?: string[];
389
- jevFallbackReason?: string;
390
- jevLatencyMs?: number;
391
- jevModel?: string;
392
- jevMode: JevMode;
393
- }
394
-
395
- export interface CombineOutcome {
396
- final: FinalVerdict;
397
- /** Reviewable regex policies Jev cleared (in shadow: would have cleared). */
398
- cleared: string[];
399
- /**
400
- * True when the first entry of `final` came from THIS TIER rather than from
401
- * a registered policy — today that means Jev's own verdict.
402
- *
403
- * `handler.ts` reads it to decide whether to attribute the decision to a
404
- * registered policy — a name it cannot find in the registry is reported as a
405
- * builtin, so a name that never was one must say so here.
406
- */
407
- decidedByJev: boolean;
408
- activity: JevActivityFields;
409
- }
410
-
411
- /**
412
- * The regex engine's answer on its own — exactly what `evaluatePolicies`
413
- * returned before two tiers existed: the first deny in evaluation order wins;
414
- * otherwise every instruct, joined; otherwise every allow that carried a note.
415
- */
416
- export function regexOnly(verdicts: readonly RegexVerdict[]): FinalVerdict {
417
- const deny = verdicts.find((v) => v.decision === "deny");
418
- if (deny) return { decision: "deny", entries: [{ policyName: deny.policyName, reason: deny.reason ?? "" }] };
419
- const instructs = verdicts.filter((v) => v.decision === "instruct");
420
- if (instructs.length > 0) {
421
- return { decision: "instruct", entries: instructs.map((v) => ({ policyName: v.policyName, reason: v.reason ?? "" })) };
422
- }
423
- return {
424
- decision: "allow",
425
- entries: verdicts
426
- .filter((v) => v.decision === "allow" && v.reason)
427
- .map((v) => ({ policyName: v.policyName, reason: v.reason as string })),
428
- };
429
- }
430
-
431
- /**
432
- * Whether Jev's answer clears this regex verdict.
433
- *
434
- * The rule, exactly: a REVIEWABLE deny or instruct that names at least one
435
- * reviewer is cleared when EVERY name in its `reviewedBy` was ASKED on this
436
- * call AND did not answer `deny` (`none`, `overridden` or `instruct` — see "A
437
- * warning-level answer clears the deny, and leaves the warning" above).
438
- *
439
- * The two halves are not interchangeable, and only the second was relaxed:
440
- * a name that was never asked still blocks the clear, whatever the others
441
- * said, because an unasked question has no answer to read. A `hard` verdict
442
- * and a reviewable one naming nobody are never cleared at all.
443
- */
444
- function clears(v: RegexVerdict, asked: ReadonlySet<string>, notDenied: ReadonlySet<string>): boolean {
445
- if (v.decision === "allow" || v.authority !== "reviewable") return false;
446
- if (v.reviewedBy.length === 0) return false;
447
- return v.reviewedBy.every((name) => asked.has(name) && notDenied.has(name));
448
- }
449
-
450
- export function combineTwoTier(
451
- verdicts: readonly RegexVerdict[],
452
- review: JevReview,
453
- mode: JevMode,
454
- ): CombineOutcome {
455
- const legacy = regexOnly(verdicts);
456
-
457
- if (review.kind === "not-consulted") {
458
- // A hard deny decided. Nothing was asked, so nothing is cleared, and the
459
- // regex result is final — byte for byte what the regex engine says alone.
460
- return { final: legacy, cleared: [], decidedByJev: false, activity: { evaluator: "jev", jevMode: mode } };
461
- }
462
-
463
- if (review.kind === "fallback") {
464
- return {
465
- final: legacy,
466
- cleared: [],
467
- decidedByJev: false,
468
- activity: {
469
- evaluator: "jev-fallback",
470
- jevFallbackReason: review.reason,
471
- ...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
472
- ...(review.model ? { jevModel: review.model } : {}),
473
- jevMode: mode,
474
- },
475
- };
476
- }
477
-
478
- // The one gate on clearing. Every reason Jev's picture of the CALL is
479
- // partial lives here and here only, so the next one added cannot
480
- // accidentally take Jev's own severity with it (see "One rule about a
481
- // partial picture" above). `truncated` is deliberately not among them: see
482
- // "What a cut MESSAGE does".
483
- const wholePicture = review.injectionAsked && !review.injected && !review.requestCut;
484
- const asked = new Set(review.asked);
485
- const notDenied = new Set(review.notDenied);
486
- const cleared = wholePicture ? verdicts.filter((v) => clears(v, asked, notDenied)).map((v) => v.policyName) : [];
487
- const activity: JevActivityFields = {
488
- // §4 records a call the tier could not read whole as a fallback, and so do
489
- // we — its clearing half really was off. The decision below is still
490
- // applied (upward only), so `jev-fallback` + `request-cut` means "Jev
491
- // cleared nothing", while every other reason means "Jev never answered".
492
- // A cut MESSAGE is not a fallback: nothing about the call was missing and
493
- // nothing was withheld, so reporting one would only inflate the rate.
494
- evaluator: review.requestCut ? "jev-fallback" : "jev",
495
- ...(review.requestCut ? { jevFallbackReason: JEV_REASON_REQUEST_CUT } : {}),
496
- jevDecision: review.decision,
497
- ...(cleared.length > 0 ? { jevCleared: cleared } : {}),
498
- ...(review.latencyMs !== null ? { jevLatencyMs: review.latencyMs } : {}),
499
- ...(review.model ? { jevModel: review.model } : {}),
500
- jevMode: mode,
501
- };
502
-
503
- if (mode === "shadow") return { final: legacy, cleared, decidedByJev: false, activity };
504
-
505
- const clearedSet = new Set(cleared);
506
- const remaining = verdicts.filter((v) => !clearedSet.has(v.policyName));
507
- const jevEntry = { policyName: review.policyName, reason: review.reason ?? `Flagged by semantic review (${review.policyName})` };
508
-
509
- // Most severe wins; within a severity, the regex engine's own order first.
510
- const regexDeny = remaining.find((v) => v.decision === "deny");
511
- if (regexDeny) {
512
- return {
513
- final: { decision: "deny", entries: [{ policyName: regexDeny.policyName, reason: regexDeny.reason ?? "" }] },
514
- cleared,
515
- decidedByJev: false,
516
- activity,
517
- };
518
- }
519
- if (review.decision === "deny") {
520
- return { final: { decision: "deny", entries: [jevEntry] }, cleared, decidedByJev: true, activity };
521
- }
522
- // A call part of which was not shown to Jev stops HERE and no further: it
523
- // cleared nothing (`wholePicture` above), so every regex verdict still
524
- // stands and Jev's own deny already decided if there was one. There is
525
- // deliberately no third outcome — no refusal of this module's own — because
526
- // a deny nobody's policy asked for, fired by size on work nobody objected
527
- // to, costs more than it protects. See "What size may NOT do" above.
528
- const instructs = remaining
529
- .filter((v) => v.decision === "instruct")
530
- .map((v) => ({ policyName: v.policyName, reason: v.reason ?? "" }));
531
- if (review.decision === "instruct") instructs.push(jevEntry);
532
- if (instructs.length > 0) {
533
- return {
534
- final: { decision: "instruct", entries: instructs },
535
- cleared,
536
- decidedByJev: instructs[0] === jevEntry,
537
- activity,
538
- };
539
- }
540
- return { final: regexOnly(remaining), cleared, decidedByJev: false, activity };
541
- }