failproofai 1.0.6 → 1.0.7-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) 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 +7 -5
  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 +13 -11
  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 +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 +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 +41 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -9
  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]__1j-zsg5._.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]__1_r2rbg._.js → [root-of-the-server]__1bf34x4._.js} +5 -7
  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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +5 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  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]__0eip4_k._.js +22 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +5 -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]__0s_yomn._.js +4 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1svlq8e._.js → [root-of-the-server]__0yxwl6j._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1b2dbet._.js +1 -1
  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]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/{_0do6d26._.js → _0bn2oo8._.js} +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +5 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  109. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/{_0h_douw._.js → _1w_5l7t._.js} +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/{_17o744m._.js → _214wgrp._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  118. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/lib_utils_ts_0az0sfq._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  121. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  122. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +5 -0
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +12 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  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 +56 -23
  136. package/.next/standalone/.next/static/chunks/0cd-_8-c-m1ea.js +6 -0
  137. package/.next/standalone/.next/static/chunks/{40-wncjnguy4e.js → 0qrbdkv9qmvli.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/{3-6zhytp6adkk.js → 0uldbut9y2-e8.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/{2y4ycwcwfjv4k.js → 0vmd180qfntfb.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/{25m8mg7bosgip.js → 12tvm75t5ffui.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/1u5zsejmgrir_.js +1 -0
  142. package/.next/standalone/.next/static/chunks/258668t68du6b.js +1 -0
  143. package/.next/standalone/.next/static/chunks/285spx855h_3r.css +2 -0
  144. package/.next/standalone/.next/static/chunks/{35pw7zxhh76n7.js → 29fql3nbnfc9q.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  146. package/.next/standalone/.next/static/chunks/{0bepqeawy72n6.js → 2vkvu9-opa_1z.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/34jpdb2xjm-x7.js +1 -0
  148. package/.next/standalone/.next/static/chunks/37lhv7wa3ywt6.js +1 -0
  149. package/.next/standalone/app/actions/get-jev-config.ts +409 -0
  150. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  151. package/.next/standalone/app/actions/update-jev-config.ts +420 -0
  152. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  153. package/.next/standalone/app/policies/hooks-client.tsx +9 -0
  154. package/.next/standalone/app/settings/jev-panel.tsx +469 -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 +51 -3
  159. package/.next/standalone/fp-cloud-cli/README.md +1 -1
  160. package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
  161. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +44 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +226 -5
  163. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +11 -0
  164. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +68 -1
  165. package/.next/standalone/fp-cloud-cli/pyproject.toml +1 -1
  166. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +17 -2
  167. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +7 -4
  168. package/.next/standalone/fp-cloud-cli/tests/test_alerting.py +184 -0
  169. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  170. package/.next/standalone/package.json +11 -11
  171. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  172. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  173. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +2 -0
  174. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +24 -1
  175. package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +41 -6
  176. package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +88 -0
  177. package/.next/standalone/sdk/python/uv.lock +15 -15
  178. package/.next/standalone/server.js +1 -1
  179. package/bin/failproofai.mjs +115 -2
  180. package/dist/cli.mjs +9299 -2151
  181. package/dist/index.js +19 -1
  182. package/dist/worker.mjs +13761 -7834
  183. package/package.json +11 -11
  184. package/pi-extension/index.ts +11 -0
  185. package/scripts/build-policy-pack.mjs +39 -2
  186. package/src/hooks/builtin-policies.ts +21 -3
  187. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  188. package/src/hooks/cloud-managed-policies.ts +22 -0
  189. package/src/hooks/custom-hooks-loader.ts +45 -7
  190. package/src/hooks/custom-hooks-registry.ts +45 -1
  191. package/src/hooks/effective-reviewers.ts +79 -0
  192. package/src/hooks/first-run-gate.ts +5 -0
  193. package/src/hooks/fp-home.ts +23 -0
  194. package/src/hooks/handler.ts +265 -6
  195. package/src/hooks/hook-activity-store.ts +105 -1
  196. package/src/hooks/hook-telemetry.ts +41 -0
  197. package/src/hooks/jev-activity.ts +385 -0
  198. package/src/hooks/jev-cli.ts +1193 -0
  199. package/src/hooks/loader-utils.ts +6 -0
  200. package/src/hooks/pack-cli.ts +278 -17
  201. package/src/hooks/pack-manifest.ts +479 -7
  202. package/src/hooks/pack-store.ts +156 -10
  203. package/src/hooks/policy-authority.ts +333 -0
  204. package/src/hooks/policy-catalog.ts +65 -0
  205. package/src/hooks/policy-evaluator.ts +940 -796
  206. package/src/hooks/policy-registry.ts +25 -0
  207. package/src/hooks/policy-reviewability.ts +229 -0
  208. package/src/hooks/policy-types.ts +126 -0
  209. package/src/hooks/semantic/combine.ts +541 -0
  210. package/src/hooks/semantic/compile.ts +176 -0
  211. package/src/hooks/semantic/decide.ts +392 -0
  212. package/src/hooks/semantic/envelope.ts +1296 -0
  213. package/src/hooks/semantic/evaluator.ts +547 -0
  214. package/src/hooks/semantic/facts.ts +292 -0
  215. package/src/hooks/semantic/intent.ts +1190 -0
  216. package/src/hooks/semantic/jev-client.ts +643 -0
  217. package/src/hooks/semantic/jev-config.ts +594 -0
  218. package/src/hooks/semantic/jev-review.ts +374 -0
  219. package/src/hooks/semantic/jev-stats.ts +289 -0
  220. package/src/hooks/semantic/jev-throttle.ts +421 -0
  221. package/src/hooks/semantic/pack-policies.ts +251 -0
  222. package/src/hooks/semantic/policies.ts +596 -0
  223. package/src/hooks/semantic/precondition-names.ts +60 -0
  224. package/src/hooks/semantic/preconditions.ts +58 -0
  225. package/src/hooks/semantic/redact.ts +2910 -0
  226. package/src/hooks/semantic/types.ts +145 -0
  227. package/src/hooks/semver-precedence.ts +128 -0
  228. package/src/hooks/worker-server.ts +119 -26
  229. package/src/index.ts +6 -0
  230. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  231. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  232. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
  233. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  234. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
  235. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  236. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  237. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
  238. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
  239. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  247. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  248. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  249. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +0 -10
  251. package/.next/standalone/.next/static/chunks/0h-gz8e00478q.js +0 -1
  252. package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +0 -2
  253. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  254. package/.next/standalone/.next/static/chunks/2lvr2pt70qn14.js +0 -1
  255. package/.next/standalone/.next/static/chunks/319c_91le7-h_.js +0 -1
  256. package/.next/standalone/.next/static/chunks/35m53kix7uaii.js +0 -6
  257. package/.next/standalone/.next/static/chunks/3xacd7qmuqlvs.js +0 -1
  258. /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_buildManifest.js +0 -0
  259. /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_clientMiddlewareManifest.js +0 -0
  260. /package/.next/standalone/.next/static/{LzR_etPlaZV9JZH09lEp3 → T8YAYM9h_64fKv705vug7}/_ssgManifest.js +0 -0
@@ -35,8 +35,17 @@
35
35
  import { createHash } from "node:crypto";
36
36
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
37
37
  import { dirname, resolve } from "node:path";
38
- import { PACK_COMMIT_RE, packsRoot, parsePackIdentity, parsePackPolicy, readInstalledPacks } from "./pack-manifest";
39
- import type { ResolvedPack } from "./pack-manifest";
38
+ import {
39
+ MAX_SEMANTIC_POLICIES_PER_PACK,
40
+ PACK_COMMIT_RE,
41
+ checkPackMinCliVersion,
42
+ packsRoot,
43
+ parsePackIdentity,
44
+ parsePackPolicy,
45
+ parsePackSemanticPolicy,
46
+ readInstalledPacks,
47
+ } from "./pack-manifest";
48
+ import type { ResolvedPack, SemanticManifestEntry } from "./pack-manifest";
40
49
  import type { InstalledPackRecord } from "./pack-manifest";
41
50
  import type { PolicyCatalogEntry } from "./policy-types";
42
51
  import type { PolicyEffect } from "./cloud-managed-policies";
@@ -86,6 +95,13 @@ export interface AddPackResult {
86
95
  selection: SelectionReason;
87
96
  /** Category slugs the pack offers, for `--category`. */
88
97
  categories: string[];
98
+ /**
99
+ * How many Jev question sets came with it, and not a list of names: they are
100
+ * not selectable and not switchable, so a name here would invite a `--policy`
101
+ * that cannot work. A non-zero count is what stops a semantic-only install
102
+ * reporting "the pack is installed and enforcing nothing".
103
+ */
104
+ semantic: number;
89
105
  artifact: string;
90
106
  }
91
107
 
@@ -454,6 +470,10 @@ interface FetchedPack {
454
470
  id: string;
455
471
  version: string;
456
472
  policies: PolicyCatalogEntry[];
473
+ /** The pack's Jev question sets, validated. Empty when it declares none. */
474
+ semantic: SemanticManifestEntry[];
475
+ /** The minimum CLI it claims, once this one has been checked against it. */
476
+ minCliVersion?: string;
457
477
  effect?: PolicyEffect;
458
478
  /** The git commit the publisher built this from, when they had one. */
459
479
  commit?: string;
@@ -461,6 +481,66 @@ interface FetchedPack {
461
481
  artifactDigest: string;
462
482
  }
463
483
 
484
+ /**
485
+ * The shape both fetch paths read a manifest's `semantic` array through.
486
+ *
487
+ * REFUSES what the loader would only drop, and the asymmetry is the same one
488
+ * `parsePackPolicy` already has on this path: nothing has been written yet, so
489
+ * refusing costs the user a clear error message and the publisher a fix —
490
+ * whereas dropping an entry here would install a pack that enforces less than
491
+ * the preview just showed, with the difference recorded only in a hook log. The
492
+ * loader drops instead because by then the pack IS installed and refusing it
493
+ * denies every tool call its regex policies cover.
494
+ *
495
+ * The per-pack cap is checked for the same reason. Over it, the loader keeps the
496
+ * first 24 in declared order and records the rest as dropped; letting that
497
+ * happen silently at install time means a machine quietly running a subset of
498
+ * what the user agreed to.
499
+ *
500
+ * The CHARACTER budget is deliberately not checked here. It lives in
501
+ * `semantic/pack-policies.ts`, and importing that module would pull Jev's
502
+ * sixteen prompts into this one — which is reachable from a Next.js server
503
+ * action (`app/actions/pack-actions.ts`), not just from the CLI. `failproofai
504
+ * publish` refuses an over-budget pack at build time, and a pack built by some
505
+ * other tool has its overflow dropped at resolve time with a recorded reason.
506
+ */
507
+ function parseManifestSemantic(packId: string, value: unknown): SemanticManifestEntry[] {
508
+ if (value === undefined) return [];
509
+ if (!Array.isArray(value)) throw new Error(`pack ${packId} semantic is not an array`);
510
+ if (value.length > MAX_SEMANTIC_POLICIES_PER_PACK) {
511
+ throw new Error(
512
+ `pack ${packId} declares ${value.length} semantic policies, over the cap of ${MAX_SEMANTIC_POLICIES_PER_PACK}`,
513
+ );
514
+ }
515
+ const entries = value.map((entry, i) => parsePackSemanticPolicy(packId, entry, i));
516
+ const names = new Set<string>();
517
+ for (const entry of entries) {
518
+ if (names.has(entry.name)) throw new Error(`pack ${packId} declares semantic policy ${entry.name} twice`);
519
+ names.add(entry.name);
520
+ }
521
+ return entries;
522
+ }
523
+
524
+ /**
525
+ * Refuse a pack this CLI is too old to run — HERE, where the message is
526
+ * something the person who typed the command can act on.
527
+ *
528
+ * The loader refuses it too, and that refusal is the fail-closed one: on a
529
+ * machine where it lands, every tool call the pack's policies cover is denied
530
+ * until somebody works out why. Catching it at install turns the same fact into
531
+ * one line at the moment of choosing.
532
+ *
533
+ * A minimum this build cannot compare is IGNORED, exactly as the loader ignores
534
+ * it: a publisher's typo in a version string must not be able to stop anyone
535
+ * installing a pack. `failproofai publish` refuses to write one, so this is
536
+ * only reachable from a pack built by some other tool.
537
+ */
538
+ function assertMinCliVersion(packId: string, declared: unknown): string | undefined {
539
+ const verdict = checkPackMinCliVersion(packId, declared);
540
+ if (verdict.kind === "too-old") throw new Error(verdict.reason);
541
+ return verdict.kind === "satisfied" ? verdict.declared : undefined;
542
+ }
543
+
464
544
  /** Fetch and fully validate a pack, without writing anything. */
465
545
  /**
466
546
  * The short names for the pack Failproof AI publishes.
@@ -543,6 +623,19 @@ export interface PackPreview {
543
623
  /** The git commit the publisher built this from, when they had one. */
544
624
  commit?: string;
545
625
  policies: PolicyCatalogEntry[];
626
+ /**
627
+ * The pack's Jev question sets. Shown, not selectable: a pack's `enabled`
628
+ * narrowing picks which of its REGEX policies register, and its semantic set
629
+ * replaces this build's wholesale or not at all.
630
+ *
631
+ * Carried on the preview because the preview is what somebody consents to. A
632
+ * pack that quietly brings sixteen new questions to the classifier — and
633
+ * replaces the sixteen this build shipped — is not something to discover after
634
+ * installing it.
635
+ */
636
+ semantic: SemanticManifestEntry[];
637
+ /** The minimum CLI this pack declares, when it declares one this build can read. */
638
+ minCliVersion?: string;
546
639
  /** The exact source the preview was read from, tag resolved and pinned. */
547
640
  source: string;
548
641
  /** True when the tag was resolved rather than typed. */
@@ -589,22 +682,52 @@ export async function fetchPackPreview(source: string): Promise<PackPreview> {
589
682
  if (!raw || typeof raw !== "object") throw new Error(`${PACK_MANIFEST_ASSET} is not an object`);
590
683
  const value = raw as {
591
684
  id?: unknown; version?: unknown; policies?: unknown; effect?: unknown; commit?: unknown;
685
+ semantic?: unknown; minCliVersion?: unknown;
592
686
  };
593
687
  const identity = parsePackIdentity(value);
594
- if (!Array.isArray(value.policies) || value.policies.length === 0) {
595
- throw new Error("pack manifest declares no policies");
596
- }
688
+ const minCliVersion = assertMinCliVersion(identity.id, value.minCliVersion);
689
+ const semantic = parseManifestSemantic(identity.id, value.semantic);
690
+ const policies = parseManifestPolicies(identity.id, value.policies);
691
+ assertDeclaresSomething(policies, semantic);
597
692
  return {
598
693
  id: identity.id,
599
694
  version: identity.version,
600
695
  effect: identity.effect,
601
696
  ...(identity.commit ? { commit: identity.commit } : {}),
602
- policies: value.policies.map((policy, i) => parsePackPolicy(identity.id, policy, i)),
697
+ policies,
698
+ semantic,
699
+ ...(minCliVersion ? { minCliVersion } : {}),
603
700
  source: formatPackSpec(spec),
604
701
  resolvedFromLatest,
605
702
  };
606
703
  }
607
704
 
705
+ /**
706
+ * The regex half of a manifest. ABSENT is an empty list, not an error: a pack of
707
+ * Jev questions alone is a legitimate thing to publish now, and ours writes
708
+ * `policies: []` for one. Present and not an array is still malformed.
709
+ */
710
+ function parseManifestPolicies(packId: string, value: unknown): PolicyCatalogEntry[] {
711
+ if (value === undefined) return [];
712
+ if (!Array.isArray(value)) throw new Error(`pack ${packId} policies is not an array`);
713
+ return value.map((policy, i) => parsePackPolicy(packId, policy, i));
714
+ }
715
+
716
+ /**
717
+ * A pack has to bring SOMETHING, and either half counts.
718
+ *
719
+ * It used to be "at least one regex policy", which made a pack of Jev questions
720
+ * alone impossible to install — and that is a legitimate pack: the regex floor
721
+ * may already be somebody else's, and the two halves version independently. This
722
+ * is the same widening `build()` makes to its "registered no policies" refusal,
723
+ * so the two ends of the lane agree on what an empty pack is.
724
+ */
725
+ function assertDeclaresSomething(policies: PolicyCatalogEntry[], semantic: SemanticManifestEntry[]): void {
726
+ if (policies.length === 0 && semantic.length === 0) {
727
+ throw new Error("pack manifest declares no policies");
728
+ }
729
+ }
730
+
608
731
  /**
609
732
  * The record this install is really an update OF — matched by id, or failing
610
733
  * that by artifact digest.
@@ -647,20 +770,26 @@ async function fetchPack(spec: PinnedPackSpec): Promise<FetchedPack> {
647
770
  if (!parsed || typeof parsed !== "object") throw new Error(`${PACK_MANIFEST_ASSET} is not an object`);
648
771
  const raw = parsed as {
649
772
  id?: unknown; version?: unknown; policies?: unknown; effect?: unknown; commit?: unknown;
773
+ semantic?: unknown; minCliVersion?: unknown;
650
774
  };
651
775
  const identity = parsePackIdentity(raw);
652
- if (!Array.isArray(raw.policies) || raw.policies.length === 0) {
653
- throw new Error("pack manifest declares no policies");
654
- }
776
+ // Before the shape checks below, because being too old for the pack is not a
777
+ // complaint about the pack: the message to give is "update the CLI", and a
778
+ // manifest field this build cannot read yet must not be reported as malformed.
779
+ const minCliVersion = assertMinCliVersion(identity.id, raw.minCliVersion);
655
780
  // Validated with the SAME rules the loader applies, so a pack that could never
656
781
  // load is refused here — while nothing has been written — rather than
657
782
  // installing cleanly and failing silently on the next tool call.
658
- const policies = raw.policies.map((p, i) => parsePackPolicy(identity.id, p, i));
783
+ const policies = parseManifestPolicies(identity.id, raw.policies);
784
+ const semantic = parseManifestSemantic(identity.id, raw.semantic);
785
+ assertDeclaresSomething(policies, semantic);
659
786
 
660
787
  return {
661
788
  id: identity.id,
662
789
  version: identity.version,
663
790
  policies,
791
+ semantic,
792
+ ...(minCliVersion ? { minCliVersion } : {}),
664
793
  ...(raw.effect !== undefined ? { effect: identity.effect } : {}),
665
794
  ...(identity.commit ? { commit: identity.commit } : {}),
666
795
  artifact,
@@ -931,6 +1060,22 @@ export async function addPack(
931
1060
  entry: artifactRel,
932
1061
  sha256: fetched.artifactDigest,
933
1062
  policies: fetched.policies,
1063
+ // Both halves of the manifest reach the record, because `installed.json` is
1064
+ // the only thing enforcement reads. Left off, a pack's Jev questions were
1065
+ // fetched, verified, shown in the preview and then dropped on the floor: the
1066
+ // resolver found no `semantic` on any installed pack, kept this build's
1067
+ // compiled set, and every pack policy naming one of the pack's own checks in
1068
+ // `reviewedBy` registered as hard. Nothing failed, and the half the user
1069
+ // installed did nothing.
1070
+ //
1071
+ // Omitted when empty for the same reason the manifest omits it: an empty
1072
+ // array reads as "this pack declares semantic entries", and the replacement
1073
+ // rule would then have it replace the compiled-in set with nothing.
1074
+ ...(fetched.semantic.length > 0 ? { semantic: fetched.semantic } : {}),
1075
+ // Recorded so the READER re-checks it. This CLI has already satisfied it or
1076
+ // refused the install, but the record outlives this CLI: a downgrade, or a
1077
+ // machine restored from another's home directory, has to meet it again.
1078
+ ...(fetched.minCliVersion ? { minCliVersion: fetched.minCliVersion } : {}),
934
1079
  // Provenance, carried through from the manifest so `policies` and the
935
1080
  // dashboard can say which source produced what is installed. NOT part of
936
1081
  // verification — `sha256` above is the pin, and this is a label beside it.
@@ -978,6 +1123,7 @@ export async function addPack(
978
1123
  available,
979
1124
  selection: reason,
980
1125
  categories: [...new Set(fetched.policies.map((p) => slugifyCategory(p.category)))],
1126
+ semantic: fetched.semantic.length,
981
1127
  artifact: artifactAbs,
982
1128
  };
983
1129
  }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Policy authority: who has the last word on a policy's deny or instruct once
3
+ * Jev is configured. See {@link PolicyAuthority} for the two values and
4
+ * `docs/policies/authority.mdx` for the builtin table.
5
+ *
6
+ * Every route a policy arrives by — a builtin, the user's own policy file, an
7
+ * installed pack, a cloud assignment — declares its authority somewhere
8
+ * different. This module is the one place those declarations are read and
9
+ * judged, so the rule cannot drift between them:
10
+ *
11
+ * - `hard` is the default and the answer to anything unclear. Absent, a value
12
+ * that is not one of the two, a `reviewedBy` that is empty or malformed or
13
+ * names a check this build does not have, or an `alwaysOn` policy: all hard.
14
+ * - `reviewable` means Jev may clear the policy's verdict — and only through the
15
+ * semantic policies it names, every one of which must have been asked.
16
+ *
17
+ * Nothing here decides anything at evaluation time. It resolves a declaration
18
+ * once, at registration, and `registerPolicy` stores the result, so what sits in
19
+ * the registry is already the effective authority.
20
+ */
21
+ import { existsSync } from "node:fs";
22
+ import { jevConfigFile } from "./fp-home";
23
+ import { hookLogWarn } from "./hook-logger";
24
+ import { effectiveAuthority } from "./policy-types";
25
+ import type { CustomHook, PolicyAuthority, PolicyCatalogEntry } from "./policy-types";
26
+
27
+ /**
28
+ * A declaration as it arrives, before anything has vouched for it. Fields are
29
+ * `unknown` because three of the four sources are files on disk written by
30
+ * somebody else.
31
+ */
32
+ export interface AuthorityDeclaration {
33
+ authority?: unknown;
34
+ reviewedBy?: unknown;
35
+ /** The self-protection guard. Always hard, whatever else it says. */
36
+ alwaysOn?: boolean;
37
+ }
38
+
39
+ /** The shape-valid authority fields a parsed record (a pack entry, a cloud assignment) carries. */
40
+ export interface AuthorityFields {
41
+ authority?: PolicyAuthority;
42
+ reviewedBy?: string[];
43
+ }
44
+
45
+ /** What a registered policy carries: already judged, and honest to its type. */
46
+ export interface ResolvedAuthority {
47
+ authority: PolicyAuthority;
48
+ /** Present exactly when `authority` is `reviewable`. Deduplicated, in declared order. */
49
+ reviewedBy?: string[];
50
+ /**
51
+ * Why a `reviewable` declaration was not honored, for a warning. Absent when
52
+ * nothing asked for `reviewable` — a policy that is simply hard is not news.
53
+ */
54
+ downgraded?: string;
55
+ }
56
+
57
+ /**
58
+ * The names `reviewedBy` may use: the semantic policies in
59
+ * `src/hooks/semantic/policies.ts`. Not the probes (`INJECTION_PROBE`,
60
+ * `SCOPE_PROBE`, the task probes) — those are inputs to Jev's decision, not
61
+ * checks a regex verdict can be cleared by.
62
+ *
63
+ * Written out rather than read off `SEMANTIC_POLICIES`, and pinned to it by
64
+ * `policy-authority.test.ts`. The registry imports this module, so a runtime
65
+ * import of the semantic policies would load all sixteen of Jev's prompts on
66
+ * every hook event of a machine that never configured Jev, and bundle them into
67
+ * every pack artifact that registers a policy.
68
+ */
69
+ export const SEMANTIC_POLICY_NAMES = [
70
+ "destructive-deletion",
71
+ "production-infra-change",
72
+ "git-history-rewrite",
73
+ "push-to-protected-branch",
74
+ "commit-on-protected-branch",
75
+ "secret-exposure",
76
+ "credential-exfiltration",
77
+ "remote-code-execution",
78
+ "privilege-escalation",
79
+ "database-destruction",
80
+ "read-outside-workspace",
81
+ "agent-config-tampering",
82
+ "system-modification",
83
+ "env-secrets-dump",
84
+ "external-destructive-action",
85
+ "external-data-egress",
86
+ ] as const;
87
+
88
+ /**
89
+ * The compiled-in reviewer set, and the DEFAULT rather than the only one.
90
+ *
91
+ * A pack that declares its own `semantic` entries replaces
92
+ * `SEMANTIC_POLICIES` wholesale on the machine that installed it, so the names
93
+ * a `reviewedBy` may use there are the pack's. Callers that know which set is
94
+ * live pass it (`effectiveReviewerNames()` in `pack-manifest.ts`, which reads
95
+ * the manifest and imports nothing from `semantic/`); everyone else gets this
96
+ * one, which is what a machine with no pack runs.
97
+ */
98
+ export const SEMANTIC_REVIEWER_NAMES: ReadonlySet<string> = new Set(SEMANTIC_POLICY_NAMES);
99
+
100
+ const isName = (n: unknown): n is string => typeof n === "string" && n.length > 0;
101
+
102
+ /**
103
+ * Judge one declaration.
104
+ *
105
+ * Stricter than `effectiveAuthority`, which is the §7 contract and needs only
106
+ * one usable name: here EVERY entry must be a semantic policy this build has,
107
+ * and anything else makes the whole declaration hard rather than being dropped.
108
+ * `reviewedBy` is a conjunction, so dropping a name the author wrote — a typo, a
109
+ * stray `7` — would let Jev clear the policy on fewer checks than they asked
110
+ * for. It is also the safe direction for version skew: a pack built against a
111
+ * newer semantic set, installed on an older build, enforces as hard rather than
112
+ * as reviewable-by-less.
113
+ *
114
+ * What this returns is what `registerPolicy` stores, so a registered policy's
115
+ * `reviewedBy` is always clean and the two functions agree on it.
116
+ *
117
+ * @param knownReviewers - the semantic policies that CAN be asked on this
118
+ * machine. Defaults to the compiled-in set, which is what runs until a pack
119
+ * ships its own; see {@link SEMANTIC_REVIEWER_NAMES}. It is a parameter rather
120
+ * than a lookup because this module is imported by the registry, and reading
121
+ * which set is live means reading a file — a cost registration is willing to
122
+ * pay once and this judgement must not pay per call.
123
+ */
124
+ export function resolvePolicyAuthority(
125
+ decl: AuthorityDeclaration | undefined,
126
+ knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
127
+ ): ResolvedAuthority {
128
+ const declaredReviewable = decl?.authority === "reviewable";
129
+ if (!decl || effectiveAuthority(decl) === "hard") {
130
+ if (!declaredReviewable) return { authority: "hard" };
131
+ return { authority: "hard", downgraded: whyHard(decl!) };
132
+ }
133
+ const names = decl.reviewedBy as unknown[];
134
+ if (!names.every(isName)) {
135
+ return { authority: "hard", downgraded: "reviewedBy is not a list of semantic policy names" };
136
+ }
137
+ const unknown = names.filter((n) => !knownReviewers.has(n));
138
+ if (unknown.length > 0) {
139
+ return {
140
+ authority: "hard",
141
+ downgraded:
142
+ `reviewedBy names ${unknown.map((n) => JSON.stringify(n)).join(", ")}, ` +
143
+ `which ${unknown.length === 1 ? "is not a semantic policy" : "are not semantic policies"} in this build`,
144
+ };
145
+ }
146
+ return { authority: "reviewable", reviewedBy: [...new Set(names)] };
147
+ }
148
+
149
+ function whyHard(decl: AuthorityDeclaration): string {
150
+ if (decl.alwaysOn === true) return "it is alwaysOn, and the self-protection guard is never reviewable";
151
+ if (!Array.isArray(decl.reviewedBy) || decl.reviewedBy.length === 0) {
152
+ return "reviewedBy does not name any semantic policy";
153
+ }
154
+ return "reviewedBy is not a list of semantic policy names";
155
+ }
156
+
157
+ /**
158
+ * The shape-valid authority fields of a serialized policy entry, for parsers.
159
+ *
160
+ * Invalid fields are DROPPED, never thrown on. A pack or cloud deployment that
161
+ * is refused over a typo in an optional field fails closed and denies every
162
+ * tool call its policies cover; dropping the field makes that one policy hard,
163
+ * which is the default the typo would have produced at registration anyway.
164
+ * Names are not checked against the semantic set here — that is a property of
165
+ * the build reading the entry, not of the entry, and is judged at registration.
166
+ */
167
+ export function authorityFieldsOf(raw: Record<string, unknown>): AuthorityFields {
168
+ const out: AuthorityFields = {};
169
+ if (raw.authority === "hard" || raw.authority === "reviewable") out.authority = raw.authority;
170
+ const list = raw.reviewedBy;
171
+ if (Array.isArray(list) && list.length > 0 && list.every(isName)) {
172
+ out.reviewedBy = [...(list as string[])];
173
+ }
174
+ return out;
175
+ }
176
+
177
+ /**
178
+ * Why a declaration cannot be PUBLISHED as written, or `undefined` when it can.
179
+ *
180
+ * For build steps — `scripts/build-policy-pack.mjs` and `failproofai publish`.
181
+ * The loaders drop a malformed field and registration downgrades a refused
182
+ * `reviewable`, both toward hard and both only as a warning, because by then the
183
+ * pack is installed and refusing it would fail it closed. At build time nothing
184
+ * is installed yet, so refusing costs nobody anything, while shipping the
185
+ * declaration would make the policy silently hard on every machine that
186
+ * installs it — a decision its author made, quietly not taking effect.
187
+ *
188
+ * An empty `reviewedBy` on a hard policy is not a problem: it says nothing.
189
+ */
190
+ export function authorityProblem(
191
+ decl: AuthorityDeclaration,
192
+ /**
193
+ * The reviewers the pack being built will SHIP WITH — its own `semantic`
194
+ * entries when it declares any, this build's set otherwise. A pack that
195
+ * carries both halves of the two-tier set names its own checks, and refusing
196
+ * that would make the feature unpublishable by the tool that implements it.
197
+ */
198
+ knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
199
+ ): string | undefined {
200
+ if (decl.authority !== undefined && decl.authority !== "hard" && decl.authority !== "reviewable") {
201
+ const t = typeof decl.authority;
202
+ return `authority must be "hard" or "reviewable", and is ${t === "string" ? JSON.stringify(decl.authority) : `${/^[aeiou]/.test(t) ? "an" : "a"} ${t}`}`;
203
+ }
204
+ if (decl.reviewedBy !== undefined && !(Array.isArray(decl.reviewedBy) && decl.reviewedBy.every(isName))) {
205
+ return "reviewedBy must be a list of semantic policy names";
206
+ }
207
+ const { downgraded } = resolvePolicyAuthority(decl, knownReviewers);
208
+ return downgraded ? `authority "reviewable" was refused — ${downgraded}` : undefined;
209
+ }
210
+
211
+ /**
212
+ * The authority fields a pack MANIFEST should carry for one entry: resolved, as
213
+ * the registry will store them, so the manifest says exactly what takes effect.
214
+ * Throws, naming the entry, on anything {@link authorityProblem} refuses.
215
+ */
216
+ export function manifestAuthority(
217
+ entry: AuthorityDeclaration & { name: string },
218
+ knownReviewers: ReadonlySet<string> = SEMANTIC_REVIEWER_NAMES,
219
+ ): {
220
+ authority: PolicyAuthority;
221
+ reviewedBy?: string[];
222
+ } {
223
+ const problem = authorityProblem(entry, knownReviewers);
224
+ if (problem) throw new Error(`${entry.name}: ${problem}`);
225
+ const { authority, reviewedBy } = resolvePolicyAuthority(entry, knownReviewers);
226
+ return reviewedBy ? { authority, reviewedBy } : { authority };
227
+ }
228
+
229
+ /**
230
+ * One authority for several declarations that land on ONE registration.
231
+ *
232
+ * Artifacts are content-addressed, so two cloud assignments or two packs whose
233
+ * code is byte-identical load as one policy (the collapse in
234
+ * `custom-hooks-loader.ts`). Keeping one of their declarations would let the
235
+ * order of `active.json` or `installed.json` decide — and let one pack's
236
+ * manifest, or one team's assignment, make another's policy reviewable.
237
+ *
238
+ * So it resolves toward HARD, the way the collapse resolves the effect toward
239
+ * enforce: reviewable only when EVERY declaration resolves to reviewable, and
240
+ * then through the UNION of their checks (more checks that must all come back
241
+ * clear is stricter, never weaker). Otherwise it takes the first declaration
242
+ * that is not reviewable, so a refused one is still reported at registration.
243
+ *
244
+ * Returns `record` itself when its own fields already say that, and a copy with
245
+ * only the two authority fields replaced otherwise. `overruled` is true when
246
+ * some declaration asked for reviewable, validly, and did not get it.
247
+ */
248
+ export function withMergedAuthority<T extends AuthorityFields>(
249
+ record: T,
250
+ declarations: ReadonlyArray<AuthorityFields>,
251
+ ): { merged: T; overruled: boolean } {
252
+ const resolved = declarations.map((d) => resolvePolicyAuthority(d));
253
+ const allReviewable = resolved.length > 0 && resolved.every((r) => r.authority === "reviewable");
254
+ const fields: AuthorityFields = allReviewable
255
+ ? { authority: "reviewable", reviewedBy: [...new Set(resolved.flatMap((r) => r.reviewedBy ?? []))] }
256
+ : fieldsOf(declarations[resolved.findIndex((r) => r.authority !== "reviewable")] ?? {});
257
+ const overruled = !allReviewable && resolved.some((r) => r.authority === "reviewable");
258
+ const same =
259
+ record.authority === fields.authority &&
260
+ JSON.stringify(record.reviewedBy) === JSON.stringify(fields.reviewedBy);
261
+ if (same) return { merged: record, overruled };
262
+ const { authority: _a, reviewedBy: _r, ...rest } = record;
263
+ return { merged: { ...rest, ...fields } as T, overruled };
264
+ }
265
+
266
+ /** The authority fields a record actually carries, and nothing else. */
267
+ function fieldsOf(d: AuthorityFields): AuthorityFields {
268
+ return {
269
+ ...(d.authority !== undefined ? { authority: d.authority } : {}),
270
+ ...(d.reviewedBy !== undefined ? { reviewedBy: d.reviewedBy } : {}),
271
+ };
272
+ }
273
+
274
+ const warnedAuthority = new Set<string>();
275
+
276
+ /**
277
+ * Say that an authority declaration was not honored — once per process, and
278
+ * only once Jev is configured (a global `jev.json` exists).
279
+ *
280
+ * Without Jev, authority decides nothing, so there is nothing to tell anyone —
281
+ * and a warning is not free: it goes to the hook's stderr, which some CLIs show
282
+ * as the deny text itself, on every event, for every machine that installed a
283
+ * pack built against a newer semantic set. Once per process keeps the daemon's
284
+ * warm worker from repeating it for every tool call. Authors are told at build
285
+ * time as well: `failproofai publish` refuses what this would warn about.
286
+ *
287
+ * Presence of the file rather than a validated load: a machine where someone
288
+ * has started configuring Jev is where the answer to "why does Jev never clear
289
+ * this policy" is wanted, and a stat keeps the semantic modules unloaded.
290
+ */
291
+ export function warnAuthority(message: string): void {
292
+ try {
293
+ if (!existsSync(jevConfigFile())) return;
294
+ } catch {
295
+ return;
296
+ }
297
+ if (warnedAuthority.has(message)) return;
298
+ warnedAuthority.add(message);
299
+ hookLogWarn(message);
300
+ }
301
+
302
+ /**
303
+ * Where a loaded hook's authority is declared, by the route it arrived on.
304
+ *
305
+ * - **Cloud-managed:** the artifact record in the active deployment. A cloud
306
+ * policy's authority is a central decision, so code inside the artifact
307
+ * cannot grant itself `reviewable`.
308
+ * - **Pack:** the pack's own manifest entry for that policy, matched by name —
309
+ * the same place its `params` schema is read from. A pack can only ever
310
+ * describe its own policies this way: its names cannot contain `/`, and they
311
+ * register under `pack/<id>@<version>/`, so no manifest can reach a builtin
312
+ * or another pack's policy. A policy the artifact registers but the manifest
313
+ * does not declare has no entry and is hard. When packs share one artifact,
314
+ * the loader has already merged their entries toward hard
315
+ * ({@link withMergedAuthority}), so no one of them decides for the others.
316
+ * - **The user's own file:** the hook itself.
317
+ */
318
+ export function authorityDeclarationFor(
319
+ hook: Pick<CustomHook, "name" | "authority" | "reviewedBy">,
320
+ source: {
321
+ cloudManaged?: { authority?: unknown; reviewedBy?: unknown };
322
+ pack?: { policies: PolicyCatalogEntry[] };
323
+ },
324
+ ): AuthorityDeclaration {
325
+ if (source.cloudManaged) {
326
+ return { authority: source.cloudManaged.authority, reviewedBy: source.cloudManaged.reviewedBy };
327
+ }
328
+ if (source.pack) {
329
+ const entry = source.pack.policies.find((p) => p.name === hook.name);
330
+ return { authority: entry?.authority, reviewedBy: entry?.reviewedBy };
331
+ }
332
+ return { authority: hook.authority, reviewedBy: hook.reviewedBy };
333
+ }