failproofai 1.0.7 → 1.0.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +9 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -10
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +52 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -7
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +4 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bd3mje._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0o4xkpl._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1-i_gzc._.js → _1-7sqrb._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  110. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  113. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  117. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  131. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  132. package/.next/standalone/.next/server/pages/404.html +1 -1
  133. package/.next/standalone/.next/server/pages/500.html +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
  136. package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
  137. package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0ahfwmbkpgfw4.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
  139. package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 1v_tp3hm8wyhe.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
  142. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  143. package/.next/standalone/.next/static/chunks/2vo7qbdbvnc0o.js +69 -0
  144. package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 2z7i-yg59w4pn.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
  146. package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 3bgot5v6f6s3c.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +6 -0
  148. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  149. package/.next/standalone/app/actions/get-jev-config.ts +604 -0
  150. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  151. package/.next/standalone/app/actions/update-jev-config.ts +569 -0
  152. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  153. package/.next/standalone/app/policies/hooks-client.tsx +14 -3
  154. package/.next/standalone/app/settings/jev-panel.tsx +630 -0
  155. package/.next/standalone/app/settings/page.tsx +20 -1
  156. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  157. package/.next/standalone/app/settings/settings.css +79 -0
  158. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  159. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  160. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  161. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  164. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  165. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  166. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  167. package/.next/standalone/package.json +10 -10
  168. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  169. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  170. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  171. package/.next/standalone/server.js +1 -1
  172. package/README.md +3 -2
  173. package/bin/failproofai.mjs +148 -4
  174. package/dist/cli.mjs +18537 -9492
  175. package/dist/index.js +19 -1
  176. package/dist/worker.mjs +10421 -3770
  177. package/package.json +10 -10
  178. package/pi-extension/index.ts +11 -0
  179. package/scripts/build-policy-pack.mjs +53 -2
  180. package/src/audit/features.ts +3 -2
  181. package/src/hooks/builtin-policies.ts +195 -8
  182. package/src/hooks/cloud-connection.ts +190 -1
  183. package/src/hooks/cloud-enrollment-cli.ts +57 -8
  184. package/src/hooks/cloud-introspect.ts +6 -0
  185. package/src/hooks/cloud-managed-policies.ts +22 -0
  186. package/src/hooks/configure-wizard.ts +26 -6
  187. package/src/hooks/custom-hooks-loader.ts +98 -10
  188. package/src/hooks/custom-hooks-registry.ts +45 -1
  189. package/src/hooks/effective-reviewers.ts +243 -0
  190. package/src/hooks/first-run-gate.ts +5 -0
  191. package/src/hooks/flush-cli.ts +35 -8
  192. package/src/hooks/fp-config.ts +266 -1
  193. package/src/hooks/fp-home.ts +23 -0
  194. package/src/hooks/fp-reset.ts +22 -4
  195. package/src/hooks/handler.ts +303 -7
  196. package/src/hooks/hook-activity-store.ts +118 -6
  197. package/src/hooks/hook-telemetry.ts +41 -0
  198. package/src/hooks/jev-activity.ts +385 -0
  199. package/src/hooks/jev-cli.ts +2172 -0
  200. package/src/hooks/jev-cloud-connection.ts +392 -0
  201. package/src/hooks/loader-utils.ts +6 -0
  202. package/src/hooks/manager.ts +45 -7
  203. package/src/hooks/pack-cli.ts +568 -37
  204. package/src/hooks/pack-failclosed.ts +3 -0
  205. package/src/hooks/pack-manifest.ts +489 -7
  206. package/src/hooks/pack-store.ts +194 -11
  207. package/src/hooks/policy-authority.ts +386 -0
  208. package/src/hooks/policy-catalog.ts +206 -0
  209. package/src/hooks/policy-evaluator.ts +963 -796
  210. package/src/hooks/policy-registry.ts +27 -1
  211. package/src/hooks/policy-reviewability.ts +258 -0
  212. package/src/hooks/policy-types.ts +128 -0
  213. package/src/hooks/semantic/combine.ts +581 -0
  214. package/src/hooks/semantic/compile.ts +176 -0
  215. package/src/hooks/semantic/decide.ts +408 -0
  216. package/src/hooks/semantic/envelope.ts +1255 -0
  217. package/src/hooks/semantic/evaluator.ts +556 -0
  218. package/src/hooks/semantic/facts.ts +298 -0
  219. package/src/hooks/semantic/intent.ts +1190 -0
  220. package/src/hooks/semantic/jev-client.ts +1155 -0
  221. package/src/hooks/semantic/jev-config.ts +1142 -0
  222. package/src/hooks/semantic/jev-review.ts +389 -0
  223. package/src/hooks/semantic/jev-stats.ts +289 -0
  224. package/src/hooks/semantic/jev-throttle.ts +427 -0
  225. package/src/hooks/semantic/pack-policies.ts +294 -0
  226. package/src/hooks/semantic/policies.ts +597 -0
  227. package/src/hooks/semantic/precondition-names.ts +60 -0
  228. package/src/hooks/semantic/preconditions.ts +58 -0
  229. package/src/hooks/semantic/redact.ts +2937 -0
  230. package/src/hooks/semantic/session-root.ts +116 -0
  231. package/src/hooks/semantic/types.ts +156 -0
  232. package/src/hooks/semver-precedence.ts +128 -0
  233. package/src/hooks/tui.ts +4 -0
  234. package/src/hooks/types.ts +1 -1
  235. package/src/hooks/worker-server.ts +121 -27
  236. package/src/index.ts +6 -0
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
  239. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
  247. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
  248. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  257. package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  261. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
  262. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
  263. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  264. package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
  265. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
  266. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
  267. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
  268. package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
  269. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
  270. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
  271. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
@@ -151,6 +151,9 @@ export function missingGuards(input: {
151
151
  // loaded, would have allowed.
152
152
  if (err.effect === "observe") continue;
153
153
  if (outOfScope(err.clis, input.cli)) continue;
154
+ // Jev checks only: nothing in the regex tier is missing. The refusal is
155
+ // still reported (hook log, `failproofai policies`); it just denies nothing.
156
+ if (err.semanticOnly) continue;
154
157
  const declared = err.declared ?? [];
155
158
  out.push({
156
159
  packId: err.id ?? "(unnamed pack)",
@@ -41,9 +41,16 @@
41
41
  import { createHash } from "node:crypto";
42
42
  import { existsSync, readFileSync } from "node:fs";
43
43
  import { resolve } from "node:path";
44
+ import { version as cliVersion } from "../../package.json";
44
45
  import { packsDir, packsInstalledFile } from "./fp-home";
45
46
  import { resolveManagedPath } from "./cloud-managed-policies";
46
- import type { PolicyCatalogEntry } from "./policy-types";
47
+ import { authorityFieldsOf } from "./policy-authority";
48
+ import { compareVersions } from "./semver-precedence";
49
+ // Strings only, and the one semantic module this file may touch: it runs on
50
+ // every hook event, and the predicate bodies pull in all sixteen of Jev's
51
+ // prompts. See the header of `semantic/precondition-names.ts`.
52
+ import { isPackPreconditionName, PACK_PRECONDITION_NAMES } from "./semantic/precondition-names";
53
+ import type { SemanticPolicyDeclaration, SemanticProbeDeclaration, PolicyCatalogEntry } from "./policy-types";
47
54
  import type { PolicyEffect } from "./cloud-managed-policies";
48
55
 
49
56
  /** Manifest schemas this reader accepts. */
@@ -82,6 +89,52 @@ export const PACK_COMMIT_RE = /^[0-9a-f]{7,40}$/;
82
89
  */
83
90
  const PACK_POLICY_NAME_RE = /^[A-Za-z0-9._-]{1,128}$/;
84
91
 
92
+ /**
93
+ * A semantic probe's id. Lowercase and underscore only, because it is half of a
94
+ * Jev question id (`<policy>.<probe>`) that `decide.ts` reads answers back by.
95
+ */
96
+ const PROBE_ID_RE = /^[a-z][a-z0-9_]{0,31}$/;
97
+
98
+ /**
99
+ * Probe ids `decide.ts` has already taken in the answer map: `<policy>.exempt`
100
+ * is the documented-exception question and `<policy>.user_asked` is the "did the
101
+ * human ask for this" one. A probe claiming either name would not collide
102
+ * loudly — it would OVERWRITE that question in the compiled request, so the
103
+ * exemption or the override silently becomes whatever the pack wrote.
104
+ */
105
+ const RESERVED_PROBE_IDS: ReadonlySet<string> = new Set(["exempt", "user_asked"]);
106
+
107
+ const SEMANTIC_TOOL_CLASSES: ReadonlySet<string> = new Set(["shell", "write", "read", "network", "other"]);
108
+
109
+ /**
110
+ * Cheap count guards on the question set, applied where the manifest is read.
111
+ *
112
+ * They are NOT the real budget. Every probe is a question in one Jev request,
113
+ * and what that request has to fit is a character budget
114
+ * (`MAX_REQUEST_CHARS - MAX_STATE_CHARS`) — which these counts cannot express,
115
+ * because a probe is between a sentence and a paragraph long. Counting to 24
116
+ * and 6 here rejects the obviously abusive shapes for free, on the hook path,
117
+ * without reaching a semantic module; `semantic/pack-policies.ts` owns the
118
+ * budget that actually decides, measured the way `compile.ts` measures it.
119
+ *
120
+ * There is deliberately no total probe count across packs. An earlier draft had
121
+ * one (80), and at the real average probe length that ceiling permits a question
122
+ * set 23,032 characters over what one request can carry — a cap that reads as a
123
+ * guarantee and is not one.
124
+ */
125
+ export const MAX_SEMANTIC_POLICIES_PER_PACK = 24;
126
+ /** Per-policy conjunction width. The builtin set's widest policy uses three probes. */
127
+ export const MAX_PROBES_PER_POLICY = 6;
128
+ const MAX_TITLE_CHARS = 120;
129
+ const MAX_INSTRUCTIONS_CHARS = 600;
130
+ const MAX_CRITERION_CHARS = 300;
131
+ const MAX_GUIDANCE_CHARS = 600;
132
+
133
+ /** A semantic policy as a manifest carries it: validated, normalized, code-free. */
134
+ export type SemanticManifestEntry = SemanticPolicyDeclaration & {
135
+ probes: SemanticProbeDeclaration[];
136
+ };
137
+
85
138
  export interface InstalledPackRecord {
86
139
  id: string;
87
140
  version: string;
@@ -94,6 +147,25 @@ export interface InstalledPackRecord {
94
147
  commit?: string;
95
148
  effect?: string;
96
149
  policies?: unknown;
150
+ /**
151
+ * The pack's semantic (Jev) policies, as the manifest declared them.
152
+ *
153
+ * `unknown` like `policies`, and optional like it is not: a pack published
154
+ * before this field existed carries none, and every one of them must keep
155
+ * parsing. A pack that declares at least one REPLACES the compiled-in
156
+ * semantic set wholesale — see `semantic/pack-policies.ts`.
157
+ */
158
+ semantic?: unknown;
159
+ /**
160
+ * The oldest CLI that may run this pack, as a semver string.
161
+ *
162
+ * Optional, and the ordering of its release is the reason it has to exist at
163
+ * all: a CLI with no support for this field ignores it AND ignores `semantic`,
164
+ * so it installs a pack whose Jev half does nothing and says so nowhere. A
165
+ * publisher can only protect against that by naming a minimum that the old
166
+ * CLI cannot read — which is why this ships before any pack declares one.
167
+ */
168
+ minCliVersion?: string;
97
169
  /**
98
170
  * Which of the pack's policies to register. Absent means ALL of them.
99
171
  *
@@ -131,6 +203,22 @@ export interface ResolvedPack {
131
203
  effect: PolicyEffect;
132
204
  /** The pack's own catalog, in declared order. */
133
205
  policies: PolicyCatalogEntry[];
206
+ /**
207
+ * The pack's semantic policies, in declared order — EMPTY when it declared
208
+ * none, which is what every pack published before this release says. Entries
209
+ * that failed validation are not here; they were dropped, and the reason is in
210
+ * `PackReadResult.warnings`.
211
+ *
212
+ * `readInstalledPacks` always sets it, so on a record that came from a
213
+ * manifest this is an array. It is optional only so that a hand-built record —
214
+ * the loader fixtures, `pack-failclosed`'s guards — does not have to name a
215
+ * field it has no opinion about. Read it through {@link packSemantic} rather
216
+ * than `?? []` at each site, so "absent" and "empty" cannot come to mean
217
+ * different things.
218
+ */
219
+ semantic?: SemanticManifestEntry[];
220
+ /** The minimum CLI this pack declared, once it has been checked against ours. */
221
+ minCliVersion?: string;
134
222
  /** Selected policy names, or null when the user took the whole pack. */
135
223
  enabled: string[] | null;
136
224
  /** Agent CLIs this pack guards, or null for all of them. */
@@ -165,12 +253,37 @@ export interface PackError {
165
253
  * deny on their behalf locks an agent out over enforcement it never had.
166
254
  */
167
255
  clis?: string[] | null;
256
+ /**
257
+ * The record's regex `policies` list was readable and empty: a Jev-checks-only
258
+ * pack. It guards nothing in the regex tier, so its refusal has nothing to
259
+ * fail closed on. Read from the RAW list, never from `declared`: a list whose
260
+ * entries are all malformed also filters to nothing, and that must widen.
261
+ */
262
+ semanticOnly?: true;
168
263
  }
169
264
 
170
265
  export interface PackReadResult {
171
266
  packs: ResolvedPack[];
172
267
  /** Every pack that was declared and refused, and why. Never silently dropped. */
173
268
  errors: PackError[];
269
+ /**
270
+ * What was dropped from a pack that LOADED anyway, and why: a malformed
271
+ * semantic policy, an unknown precondition name, one entry past the cap, a
272
+ * `minCliVersion` nobody can compare.
273
+ *
274
+ * A SEPARATE channel from `errors`, and it has to be. An entry in `errors`
275
+ * means a pack was refused, and `pack-failclosed.ts` turns that into a deny
276
+ * for every event the missing policies claimed. Everything here is the
277
+ * opposite situation: the pack's regex policies are registered and enforcing,
278
+ * and what was lost is a reviewer (so a deny can no longer be CLEARED) or an
279
+ * unreadable version claim. Filing either as a pack failure would deny a
280
+ * machine over a typo in the half of the system whose job is to let more real
281
+ * work through.
282
+ *
283
+ * Absent rather than an empty array when there is nothing to say, so the
284
+ * common result stays the exact shape it has always been.
285
+ */
286
+ warnings?: string[];
174
287
  }
175
288
 
176
289
  export function packsRoot(): string {
@@ -226,14 +339,56 @@ export function hasInstalledPacks(): boolean {
226
339
  }
227
340
  }
228
341
 
229
- function installedFilePath(): string {
342
+ export function installedFilePath(): string {
230
343
  return process.env.FAILPROOFAI_PACK_DIR
231
344
  ? resolve(process.env.FAILPROOFAI_PACK_DIR, "installed.json")
232
345
  : packsInstalledFile();
233
346
  }
234
347
 
348
+
349
+ /** The param types a schema may declare. Mirrors {@link PolicyParamsSchema}. */
350
+ const PARAM_TYPES: ReadonlySet<string> = new Set(["string", "number", "boolean", "string[]", "pattern[]"]);
351
+
352
+ /**
353
+ * Why a `params` schema cannot be used, or `undefined` when it can.
354
+ *
355
+ * A pack's schema is what `registerPolicy` stores and `evaluatePolicies` merges
356
+ * the user's configured values ON TOP OF, so a malformed one is not cosmetic: it
357
+ * decides what `ctx.params` contains, and a policy reading a param that is not
358
+ * there falls back to whatever its own code says — usually the stricter thing.
359
+ *
360
+ * Validated as a whole rather than per param. A partial schema would leave one
361
+ * param live and another silently absent inside the same policy, which is the
362
+ * hardest version of this to diagnose from the outside.
363
+ */
364
+ function paramsSchemaProblem(value: unknown): string | undefined {
365
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "params is not an object";
366
+ for (const [name, raw] of Object.entries(value as Record<string, unknown>)) {
367
+ if (name.length === 0) return "params has an empty parameter name";
368
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return `params.${name} is not an object`;
369
+ const spec = raw as Record<string, unknown>;
370
+ if (typeof spec.type !== "string" || !PARAM_TYPES.has(spec.type)) {
371
+ return `params.${name} has type ${JSON.stringify(spec.type)}, which is not one of ${[...PARAM_TYPES].join(", ")}`;
372
+ }
373
+ if (typeof spec.description !== "string" || spec.description.length === 0) {
374
+ return `params.${name} is missing a description`;
375
+ }
376
+ // The KEY has to be there, whatever it holds: an absent default and a
377
+ // `default: undefined` merge the same way, but only one of them says the
378
+ // author thought about it, and the merge is what the user's own value lands on.
379
+ if (!("default" in spec)) return `params.${name} is missing a default`;
380
+ }
381
+ return undefined;
382
+ }
383
+
235
384
  /** Validate one serialized catalog entry carried by a pack. */
236
- export function parsePackPolicy(packId: string, value: unknown, index: number): PolicyCatalogEntry {
385
+ export function parsePackPolicy(
386
+ packId: string,
387
+ value: unknown,
388
+ index: number,
389
+ /** Where a DROPPED field is reported. Optional: most callers only care that the entry parsed. */
390
+ warnings?: string[],
391
+ ): PolicyCatalogEntry {
237
392
  const where = `${packId} policy #${index}`;
238
393
  if (!value || typeof value !== "object") throw new Error(`${where} is not an object`);
239
394
  const raw = value as Record<string, unknown>;
@@ -274,10 +429,322 @@ export function parsePackPolicy(packId: string, value: unknown, index: number):
274
429
  throw new Error(`${where} has a match.${key} that is not a list of names`);
275
430
  }
276
431
  }
432
+ // Authority: whether Jev may clear this policy's verdict. Unlike `match`, a
433
+ // malformed value is DROPPED rather than refused — refusing the pack fails it
434
+ // closed and denies every tool call it covers, while dropping the field makes
435
+ // this one policy `hard`, which is what an unreadable declaration means
436
+ // anyway. A pack can only describe its OWN policies this way: the name above
437
+ // cannot contain `/`, so no entry can reach a builtin or another pack's
438
+ // policy. Absent fields stay absent, valid ones stay where they were, and an
439
+ // entry with nothing to drop is returned as is.
440
+ const valid = authorityFieldsOf(raw);
441
+ const invalid = (["authority", "reviewedBy"] as const).filter((k) => k in raw && !(k in valid));
442
+ // `params` follows the same rule, and is dropped for the same reason: refusing
443
+ // the pack fails it closed and denies every tool call it covers, while dropping
444
+ // the schema leaves the policy running on its own internal fallbacks. Recorded
445
+ // rather than silent, because the drop is a change to what the policy DOES —
446
+ // `block-sudo` without its `allowPatterns` is stricter than the user asked for,
447
+ // and stricter is the direction nobody reports as a bug.
448
+ if ("params" in raw) {
449
+ const problem = paramsSchemaProblem(raw.params);
450
+ if (problem) {
451
+ warnings?.push(`${where} declares a params schema that was dropped: ${problem}`);
452
+ invalid.push("params" as never);
453
+ }
454
+ }
455
+ if (invalid.length > 0) {
456
+ return Object.fromEntries(
457
+ Object.entries(raw).filter(([k]) => !(invalid as readonly string[]).includes(k)),
458
+ ) as unknown as PolicyCatalogEntry;
459
+ }
277
460
  return raw as unknown as PolicyCatalogEntry;
278
461
  }
279
462
 
280
- function parsePack(root: string, value: unknown): ResolvedPack {
463
+ /**
464
+ * Why a `params` schema cannot be PUBLISHED, or `undefined` when it can.
465
+ *
466
+ * The build-time half of the rule above: at build time nothing is installed, so
467
+ * refusing costs nobody anything, while shipping the schema means every machine
468
+ * that installs the pack runs the policy without its parameters and says so only
469
+ * in its own log.
470
+ */
471
+ export function packParamsProblem(params: unknown): string | undefined {
472
+ return params === undefined ? undefined : paramsSchemaProblem(params);
473
+ }
474
+
475
+ /**
476
+ * What this build makes of a pack's declared `minCliVersion`.
477
+ *
478
+ * Three outcomes, not two, because "the pack states no requirement" and "the
479
+ * pack states one nobody can read" are different facts and only one of them is
480
+ * worth saying out loud.
481
+ */
482
+ export type MinCliVerdict =
483
+ /** Satisfied, or nothing was claimed. `declared` is present only when it was. */
484
+ | { kind: "satisfied"; declared?: string }
485
+ /** The field was there and unusable. It is DROPPED; the pack still loads. */
486
+ | { kind: "unreadable"; reason: string }
487
+ /** This CLI is genuinely older than the pack requires. The pack is refused. */
488
+ | { kind: "too-old"; reason: string };
489
+
490
+ /**
491
+ * Judge one pack's `minCliVersion` against the running CLI — the ONE place that
492
+ * decision is written down.
493
+ *
494
+ * ## Absent satisfies
495
+ *
496
+ * Every pack published before this release declares nothing, and those packs
497
+ * work. Treating silence as an unmet requirement would refuse them all, and a
498
+ * refused `enforce` pack denies every tool call its policies cover
499
+ * (`pack-failclosed.ts`) — a machine-wide lockout produced by an upgrade.
500
+ *
501
+ * ## Unreadable also satisfies, and says so
502
+ *
503
+ * The rule `parsePackPolicy` already applies to a malformed `authority`: drop
504
+ * the field, record it, do not refuse the pack. A publisher's typo in a version
505
+ * string must not be able to fail somebody's machine closed. It is recorded
506
+ * rather than ignored, because a requirement that quietly evaporates is how a
507
+ * pack ends up enforcing less than its README says.
508
+ *
509
+ * This is the one place the two differ from each other, which is why neither is
510
+ * a boolean: a caller reading `false` to refuse would ignore the typo, and one
511
+ * reading `!== true` would refuse the world.
512
+ *
513
+ * ## Too old refuses, and that is the field's entire purpose
514
+ *
515
+ * A CLI too old to know this field ignores it AND ignores `semantic` with it,
516
+ * installing a pack whose Jev half does nothing on a machine that reports
517
+ * healthy. Only a build new enough to have this check can act on the
518
+ * requirement, so here it acts. It rides the ordinary `PackError` path, which
519
+ * carries `effect` and `clis`, so refusing an `observe` pack does not make the
520
+ * machine deny on its behalf.
521
+ */
522
+ export function checkPackMinCliVersion(
523
+ packId: string,
524
+ declared: unknown,
525
+ current: string = cliVersion,
526
+ ): MinCliVerdict {
527
+ if (declared === undefined) return { kind: "satisfied" };
528
+ const ordered = compareVersions(current, declared);
529
+ if (ordered === null) {
530
+ return {
531
+ kind: "unreadable",
532
+ reason:
533
+ `pack ${packId} declares minCliVersion ${JSON.stringify(declared)}, which is not a version this CLI can ` +
534
+ `compare against its own (${current}) — the requirement was ignored`,
535
+ };
536
+ }
537
+ if (ordered < 0) {
538
+ return {
539
+ kind: "too-old",
540
+ reason:
541
+ `pack ${packId} needs failproofai ${String(declared)} or newer and this is ${current} — ` +
542
+ // As a range: the bare name installs `latest`, which can sit below a
543
+ // prerelease minimum. `declared` compared above, so it is plain semver.
544
+ `run \`npm i -g "failproofai@>=${String(declared)}" && failproofai update\``,
545
+ };
546
+ }
547
+ return { kind: "satisfied", declared: declared as string };
548
+ }
549
+
550
+ /** A bounded, non-empty string field. The cap is part of the envelope budget, not taste. */
551
+ function boundedString(where: string, field: string, value: unknown, max: number): string {
552
+ if (typeof value !== "string" || value.length === 0) {
553
+ throw new Error(`${where} is missing ${field}`);
554
+ }
555
+ if (value.length > max) {
556
+ throw new Error(`${where} has a ${field} of ${value.length} characters, over the ${max}-character cap`);
557
+ }
558
+ return value;
559
+ }
560
+
561
+ /**
562
+ * One probe (or the `exempt` question), validated.
563
+ *
564
+ * `forcedId` is how `exempt` gets its id: the answer map keys it
565
+ * `<policy>.exempt` whatever the manifest wrote, so the field is overwritten
566
+ * rather than checked. Refusing a mismatch there would fail a pack over a
567
+ * value that has no effect.
568
+ */
569
+ function parseSemanticProbe(where: string, value: unknown, forcedId: string | null): SemanticProbeDeclaration {
570
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${where} is not an object`);
571
+ const raw = value as Record<string, unknown>;
572
+
573
+ let id: string;
574
+ if (forcedId !== null) {
575
+ id = forcedId;
576
+ } else {
577
+ if (typeof raw.id !== "string" || !PROBE_ID_RE.test(raw.id)) {
578
+ throw new Error(`${where} has an unsafe id ${JSON.stringify(raw.id)}`);
579
+ }
580
+ if (RESERVED_PROBE_IDS.has(raw.id)) {
581
+ throw new Error(`${where} uses the reserved probe id ${JSON.stringify(raw.id)}`);
582
+ }
583
+ id = raw.id;
584
+ }
585
+
586
+ const instructions = boundedString(where, "instructions", raw.instructions, MAX_INSTRUCTIONS_CHARS);
587
+ if (raw.criteria === undefined) return { id, instructions };
588
+ if (!raw.criteria || typeof raw.criteria !== "object" || Array.isArray(raw.criteria)) {
589
+ throw new Error(`${where} has criteria that is not an object`);
590
+ }
591
+ const criteria = raw.criteria as Record<string, unknown>;
592
+ // Both halves or neither. Jev answers the question as written, and a `true`
593
+ // description with no `false` one is a question with only one side explained —
594
+ // the authoring mistake the criteria exist to prevent.
595
+ return {
596
+ id,
597
+ instructions,
598
+ criteria: {
599
+ true: boundedString(where, "criteria.true", criteria.true, MAX_CRITERION_CHARS),
600
+ false: boundedString(where, "criteria.false", criteria.false, MAX_CRITERION_CHARS),
601
+ },
602
+ };
603
+ }
604
+
605
+ /**
606
+ * Validate one serialized SEMANTIC policy carried by a pack. Sibling of
607
+ * {@link parsePackPolicy}, and the rules are its rules where they overlap.
608
+ *
609
+ * It THROWS on every violation and the CALLER decides what a throw costs.
610
+ * `readInstalledPacks` drops the one entry and records why, because a semantic
611
+ * policy is what CLEARS a reviewable regex verdict: losing one leaves the regex
612
+ * block standing, which is noisier and never weaker. `failproofai publish`
613
+ * fails the whole build on the same throw, because nothing is installed yet and
614
+ * refusing there costs nobody anything.
615
+ *
616
+ * The returned entry is NORMALIZED, unlike a regex entry — which is returned
617
+ * as-is so a field this build does not know about survives into `params`. There
618
+ * is no equivalent here: the entry's entire use is to be compiled into a
619
+ * question set, so a field nothing reads is a field that would be published,
620
+ * digest-pinned and then ignored.
621
+ */
622
+ export function parsePackSemanticPolicy(packId: string, value: unknown, index: number): SemanticManifestEntry {
623
+ const where = `${packId} semantic policy #${index}`;
624
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${where} is not an object`);
625
+ const raw = value as Record<string, unknown>;
626
+
627
+ const name = raw.name;
628
+ if (typeof name !== "string" || !PACK_POLICY_NAME_RE.test(name)) {
629
+ throw new Error(`${where} has an unsafe name ${JSON.stringify(name)}`);
630
+ }
631
+ if ("alwaysOn" in raw) {
632
+ // The same refusal `parsePackPolicy` makes, for the same reason, and it has
633
+ // to be restated rather than inherited: nothing else stops a semantic entry
634
+ // from carrying the field, and a reader that ever honoured it would be
635
+ // honouring it on a downloaded file no local command can switch off.
636
+ throw new Error(`${where} declares alwaysOn, which packs may not set`);
637
+ }
638
+ const title = boundedString(`${packId} semantic policy ${name}`, "title", raw.title, MAX_TITLE_CHARS);
639
+ const scope = `${packId} semantic policy ${name}`;
640
+
641
+ if (!Array.isArray(raw.appliesTo) || raw.appliesTo.length === 0) {
642
+ throw new Error(`${scope} has no appliesTo tool classes`);
643
+ }
644
+ const unknownClass = raw.appliesTo.find((c) => typeof c !== "string" || !SEMANTIC_TOOL_CLASSES.has(c));
645
+ if (unknownClass !== undefined) {
646
+ throw new Error(
647
+ `${scope} applies to ${JSON.stringify(unknownClass)}, which is not a tool class ` +
648
+ `(${[...SEMANTIC_TOOL_CLASSES].join(", ")})`,
649
+ );
650
+ }
651
+
652
+ if (raw.mode !== "deny" && raw.mode !== "instruct") {
653
+ throw new Error(`${scope} has mode ${JSON.stringify(raw.mode)}, which must be "deny" or "instruct"`);
654
+ }
655
+ // REQUIRED, with no default. It decides whether a prompt injection that talks
656
+ // the human's words back at us can clear the policy, so a default here would
657
+ // be a security decision made by absence — and the direction it fell would
658
+ // depend on which of the two engines read the entry.
659
+ if (typeof raw.userCanOverride !== "boolean") {
660
+ throw new Error(`${scope} is missing userCanOverride, which has no default`);
661
+ }
662
+
663
+ if (!Array.isArray(raw.probes) || raw.probes.length === 0) {
664
+ throw new Error(`${scope} declares no probes`);
665
+ }
666
+ if (raw.probes.length > MAX_PROBES_PER_POLICY) {
667
+ throw new Error(`${scope} declares ${raw.probes.length} probes, over the cap of ${MAX_PROBES_PER_POLICY}`);
668
+ }
669
+ const probes = raw.probes.map((p, i) => parseSemanticProbe(`${scope} probe #${i}`, p, null));
670
+ const ids = new Set<string>();
671
+ for (const probe of probes) {
672
+ if (ids.has(probe.id)) throw new Error(`${scope} declares probe ${probe.id} twice`);
673
+ ids.add(probe.id);
674
+ }
675
+
676
+ const exempt = raw.exempt === undefined ? undefined : parseSemanticProbe(`${scope} exempt`, raw.exempt, "exempt");
677
+
678
+ if (raw.precondition !== undefined && !isPackPreconditionName(raw.precondition)) {
679
+ throw new Error(
680
+ `${scope} names precondition ${JSON.stringify(raw.precondition)}, which this build does not have ` +
681
+ `(${PACK_PRECONDITION_NAMES.join(", ")})`,
682
+ );
683
+ }
684
+
685
+ const guidance = boundedString(scope, "guidance", raw.guidance, MAX_GUIDANCE_CHARS);
686
+
687
+ return {
688
+ name,
689
+ title,
690
+ appliesTo: raw.appliesTo as SemanticManifestEntry["appliesTo"],
691
+ mode: raw.mode,
692
+ userCanOverride: raw.userCanOverride,
693
+ probes,
694
+ ...(exempt ? { exempt } : {}),
695
+ ...(raw.precondition !== undefined ? { precondition: raw.precondition } : {}),
696
+ guidance,
697
+ };
698
+ }
699
+
700
+ /** A pack's semantic entries. Absent and empty are the same answer: it declares none. */
701
+ export function packSemantic(pack: Pick<ResolvedPack, "semantic">): ReadonlyArray<SemanticManifestEntry> {
702
+ return pack.semantic ?? [];
703
+ }
704
+
705
+ /** The questions one entry compiles to: one per probe, plus one for the exemption. */
706
+ export function semanticQuestions(
707
+ entry: Pick<SemanticManifestEntry, "probes" | "exempt">,
708
+ ): ReadonlyArray<SemanticProbeDeclaration> {
709
+ return entry.exempt ? [...entry.probes, entry.exempt] : entry.probes;
710
+ }
711
+
712
+ /**
713
+ * The semantic entries a pack declared, with the unusable ones dropped.
714
+ *
715
+ * Per ENTRY, not per pack: one malformed question set must not take the rest of
716
+ * a publisher's semantic policies with it, for the same reason one malformed
717
+ * pack does not take the other packs with it.
718
+ */
719
+ function parsePackSemantic(packId: string, value: unknown, warnings: string[]): SemanticManifestEntry[] {
720
+ if (value === undefined) return [];
721
+ if (!Array.isArray(value)) {
722
+ warnings.push(`pack ${packId} semantic is not an array, so none of it was loaded`);
723
+ return [];
724
+ }
725
+ const entries: SemanticManifestEntry[] = [];
726
+ const names = new Set<string>();
727
+ for (const [index, raw] of value.entries()) {
728
+ if (entries.length >= MAX_SEMANTIC_POLICIES_PER_PACK) {
729
+ warnings.push(
730
+ `pack ${packId} declares more than ${MAX_SEMANTIC_POLICIES_PER_PACK} semantic policies; ` +
731
+ `the ${value.length - MAX_SEMANTIC_POLICIES_PER_PACK} past that cap were dropped`,
732
+ );
733
+ break;
734
+ }
735
+ try {
736
+ const entry = parsePackSemanticPolicy(packId, raw, index);
737
+ if (names.has(entry.name)) throw new Error(`pack ${packId} declares semantic policy ${entry.name} twice`);
738
+ names.add(entry.name);
739
+ entries.push(entry);
740
+ } catch (err) {
741
+ warnings.push(errText(err));
742
+ }
743
+ }
744
+ return entries;
745
+ }
746
+
747
+ function parsePack(root: string, value: unknown, warnings: string[]): ResolvedPack {
281
748
  if (!value || typeof value !== "object") throw new Error("pack entry is not an object");
282
749
  const raw = value as InstalledPackRecord;
283
750
 
@@ -292,6 +759,12 @@ function parsePack(root: string, value: unknown): ResolvedPack {
292
759
  // this pack was for.
293
760
  const effect = identity.effect;
294
761
 
762
+ // Checked before the artifact is read: there is no point hashing bytes this
763
+ // build may have just been told it must not run.
764
+ const minCli = checkPackMinCliVersion(identity.id, raw.minCliVersion);
765
+ if (minCli.kind === "too-old") throw new Error(minCli.reason);
766
+ if (minCli.kind === "unreadable") warnings.push(minCli.reason);
767
+
295
768
  const path = resolveManagedPath(root, raw.entry);
296
769
  const actual = createHash("sha256").update(readFileSync(path)).digest("hex");
297
770
  if (actual !== raw.sha256) {
@@ -301,7 +774,7 @@ function parsePack(root: string, value: unknown): ResolvedPack {
301
774
  }
302
775
 
303
776
  if (!Array.isArray(raw.policies)) throw new Error(`pack ${raw.id} policies is not an array`);
304
- const policies = raw.policies.map((p, i) => parsePackPolicy(raw.id, p, i));
777
+ const policies = raw.policies.map((p, i) => parsePackPolicy(raw.id, p, i, warnings));
305
778
  const names = new Set<string>();
306
779
  for (const p of policies) {
307
780
  if (names.has(p.name)) throw new Error(`pack ${raw.id} declares ${p.name} twice`);
@@ -340,6 +813,8 @@ function parsePack(root: string, value: unknown): ResolvedPack {
340
813
  sha256: raw.sha256,
341
814
  effect,
342
815
  policies,
816
+ semantic: parsePackSemantic(identity.id, raw.semantic, warnings),
817
+ ...(minCli.kind === "satisfied" && minCli.declared ? { minCliVersion: minCli.declared } : {}),
343
818
  enabled,
344
819
  };
345
820
  }
@@ -388,6 +863,7 @@ export function readInstalledPacks(): PackReadResult {
388
863
  const root = packsRoot();
389
864
  const packs: ResolvedPack[] = [];
390
865
  const errors: PackError[] = [];
866
+ const warnings: string[] = [];
391
867
  const seen = new Set<string>();
392
868
 
393
869
  for (const entry of manifest.packs) {
@@ -395,11 +871,16 @@ export function readInstalledPacks(): PackReadResult {
395
871
  entry && typeof entry === "object" && typeof (entry as InstalledPackRecord).id === "string"
396
872
  ? (entry as InstalledPackRecord).id
397
873
  : null;
874
+ // Collected per pack and merged only on success. A pack that throws after
875
+ // some of its semantic entries were dropped is refused whole, and reporting
876
+ // the drops as well would describe a policy set that is not loaded at all.
877
+ const packWarnings: string[] = [];
398
878
  try {
399
- const pack = parsePack(root, entry);
879
+ const pack = parsePack(root, entry, packWarnings);
400
880
  if (seen.has(pack.id)) throw new Error(`duplicate pack id ${pack.id}`);
401
881
  seen.add(pack.id);
402
882
  packs.push(pack);
883
+ warnings.push(...packWarnings);
403
884
  } catch (err) {
404
885
  const rec = entry as InstalledPackRecord | null;
405
886
  const declaredEffect =
@@ -411,6 +892,7 @@ export function readInstalledPacks(): PackReadResult {
411
892
  // Best effort: an entry too malformed to list policies yields nothing
412
893
  // here, and a deny built from it is unavoidably blanket.
413
894
  ...(Array.isArray(rec?.policies) ? { declared: safeDeclared(rec.policies) } : {}),
895
+ ...(Array.isArray(rec?.policies) && rec.policies.length === 0 ? { semanticOnly: true as const } : {}),
414
896
  // A list of names, or nothing. Anything else is unreadable scope, and
415
897
  // unreadable scope has to mean "every agent" — the same reasoning that
416
898
  // widens an unreadable `match`: a narrowing nobody can parse says
@@ -422,7 +904,7 @@ export function readInstalledPacks(): PackReadResult {
422
904
  });
423
905
  }
424
906
  }
425
- return { packs, errors };
907
+ return { packs, errors, ...(warnings.length > 0 ? { warnings } : {}) };
426
908
  }
427
909
 
428
910
  /** Policy entries that at least carry a name and a match, for narrowing a deny. */