failproofai 1.0.7 → 1.0.8

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 (268) 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 +3 -3
  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]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
  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]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
  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]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
  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]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
  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]__13t1zkw._.js +3 -0
  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]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0-vcssj._.js} +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +23 -0
  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_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  120. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  130. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  131. package/.next/standalone/.next/server/pages/404.html +1 -1
  132. package/.next/standalone/.next/server/pages/500.html +1 -1
  133. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
  135. package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +6 -0
  136. package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
  137. package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0m-9d6yn9hx4j.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 0wbjy0zo-m7is.js} +1 -1
  139. package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 21uv-uusw329x.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +1 -0
  142. package/.next/standalone/.next/static/chunks/2qdpj67x6ifk_.js +69 -0
  143. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  144. package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +1 -0
  145. package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +1 -0
  146. package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 3c3qbmosdjl6w.js} +1 -1
  147. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  148. package/.next/standalone/app/actions/get-jev-config.ts +604 -0
  149. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  150. package/.next/standalone/app/actions/update-jev-config.ts +569 -0
  151. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  152. package/.next/standalone/app/policies/hooks-client.tsx +14 -3
  153. package/.next/standalone/app/settings/jev-panel.tsx +630 -0
  154. package/.next/standalone/app/settings/page.tsx +20 -1
  155. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  156. package/.next/standalone/app/settings/settings.css +79 -0
  157. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  158. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  159. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  160. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  161. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  163. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  164. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  165. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  166. package/.next/standalone/package.json +10 -10
  167. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  168. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  169. package/.next/standalone/sdk/typescript/CHANGELOG.md +18 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +0 -274
  171. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  172. package/.next/standalone/server.js +1 -1
  173. package/README.md +3 -2
  174. package/bin/failproofai.mjs +148 -4
  175. package/dist/cli.mjs +18599 -9478
  176. package/dist/index.js +19 -1
  177. package/dist/worker.mjs +10500 -3773
  178. package/package.json +10 -10
  179. package/pi-extension/index.ts +11 -0
  180. package/scripts/build-policy-pack.mjs +53 -2
  181. package/src/audit/features.ts +3 -2
  182. package/src/hooks/builtin-policies.ts +195 -8
  183. package/src/hooks/cloud-connection.ts +190 -1
  184. package/src/hooks/cloud-enrollment-cli.ts +57 -8
  185. package/src/hooks/cloud-introspect.ts +6 -0
  186. package/src/hooks/cloud-managed-policies.ts +22 -0
  187. package/src/hooks/configure-wizard.ts +26 -6
  188. package/src/hooks/custom-hooks-loader.ts +98 -10
  189. package/src/hooks/custom-hooks-registry.ts +45 -1
  190. package/src/hooks/effective-reviewers.ts +243 -0
  191. package/src/hooks/first-run-gate.ts +5 -0
  192. package/src/hooks/flush-cli.ts +35 -8
  193. package/src/hooks/fp-config.ts +266 -1
  194. package/src/hooks/fp-home.ts +23 -0
  195. package/src/hooks/fp-reset.ts +22 -4
  196. package/src/hooks/handler.ts +303 -7
  197. package/src/hooks/hook-activity-store.ts +118 -6
  198. package/src/hooks/hook-telemetry.ts +41 -0
  199. package/src/hooks/jev-activity.ts +385 -0
  200. package/src/hooks/jev-cli.ts +2172 -0
  201. package/src/hooks/jev-cloud-connection.ts +392 -0
  202. package/src/hooks/loader-utils.ts +6 -0
  203. package/src/hooks/manager.ts +45 -7
  204. package/src/hooks/pack-cli.ts +568 -37
  205. package/src/hooks/pack-failclosed.ts +3 -0
  206. package/src/hooks/pack-manifest.ts +489 -7
  207. package/src/hooks/pack-store.ts +194 -11
  208. package/src/hooks/policy-authority.ts +386 -0
  209. package/src/hooks/policy-catalog.ts +206 -0
  210. package/src/hooks/policy-evaluator.ts +963 -796
  211. package/src/hooks/policy-registry.ts +27 -1
  212. package/src/hooks/policy-reviewability.ts +258 -0
  213. package/src/hooks/policy-types.ts +128 -0
  214. package/src/hooks/semantic/combine.ts +581 -0
  215. package/src/hooks/semantic/compile.ts +176 -0
  216. package/src/hooks/semantic/decide.ts +513 -0
  217. package/src/hooks/semantic/envelope.ts +1255 -0
  218. package/src/hooks/semantic/evaluator.ts +556 -0
  219. package/src/hooks/semantic/facts.ts +387 -0
  220. package/src/hooks/semantic/intent.ts +1190 -0
  221. package/src/hooks/semantic/jev-client.ts +1155 -0
  222. package/src/hooks/semantic/jev-config.ts +1142 -0
  223. package/src/hooks/semantic/jev-review.ts +389 -0
  224. package/src/hooks/semantic/jev-stats.ts +289 -0
  225. package/src/hooks/semantic/jev-throttle.ts +427 -0
  226. package/src/hooks/semantic/pack-policies.ts +294 -0
  227. package/src/hooks/semantic/policies.ts +597 -0
  228. package/src/hooks/semantic/precondition-names.ts +60 -0
  229. package/src/hooks/semantic/preconditions.ts +58 -0
  230. package/src/hooks/semantic/redact.ts +2937 -0
  231. package/src/hooks/semantic/session-root.ts +116 -0
  232. package/src/hooks/semantic/types.ts +162 -0
  233. package/src/hooks/semver-precedence.ts +128 -0
  234. package/src/hooks/tui.ts +4 -0
  235. package/src/hooks/types.ts +1 -1
  236. package/src/hooks/worker-server.ts +121 -27
  237. package/src/index.ts +6 -0
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  239. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
  240. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.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]__0bd3mje._.js +0 -3
  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]__0u3w0ll._.js +0 -22
  247. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  248. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  252. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  253. package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/_1-i_gzc._.js +0 -23
  255. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  257. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  258. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
  259. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
  260. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  261. package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
  262. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
  263. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
  264. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
  265. package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
  266. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_buildManifest.js +0 -0
  267. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_clientMiddlewareManifest.js +0 -0
  268. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_ssgManifest.js +0 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * What connecting to, and disconnecting from, FailproofAI Cloud does to Jev.
3
+ *
4
+ * `config --token` with a key that carries `jev:evaluate` stores the key in the
5
+ * `jev` slot of `credentials.json` (`cloud-connection.ts`) and then, ONLY when
6
+ * this machine has no `jev.json` at all, writes one that turns Jev on through
7
+ * FailproofAI Cloud in shadow mode — logged, never enforced, until its owner
8
+ * says otherwise. Never under `--no-transcripts`: that connection asked for
9
+ * decisions only, so it stores the key and says Jev is available, and
10
+ * `jev setup --provider failproofai` is the opt-in. `config --disconnect`
11
+ * clears the slot and deletes `jev.json` only when that file names the Cloud
12
+ * provider and is not switched off.
13
+ *
14
+ * # Never overwrite
15
+ *
16
+ * A `jev.json` that exists is somebody's decision: a BYOK endpoint with its own
17
+ * key, a Cloud file switched to enforce or off, a hand-written one. Connecting
18
+ * is not a reason to replace any of those, so the file is created with a
19
+ * no-clobber primitive — written to a temp file, then HARD-LINKED into place,
20
+ * which fails if anything is already there — rather than checked for and then
21
+ * written: between a check and a rename another writer can land, and a rename
22
+ * replaces whatever it finds. The link is also what keeps a hook from ever
23
+ * reading a half-written file.
24
+ *
25
+ * # Only the Cloud's own file is removed
26
+ *
27
+ * Disconnect removes the Cloud key, so a Cloud `jev.json` would be left naming a
28
+ * route with no key — harmless (`not-connected`, Jev off) but a lie on every
29
+ * status screen. A BYOK file keeps working without the Cloud and is not the
30
+ * Cloud's to delete, so it stays; so does a file this build cannot read, and a
31
+ * Cloud file switched off — `--mode off` is the owner's opt-out, and deleting
32
+ * it would let the next connect write a fresh shadow file. The
33
+ * file is moved aside before it is judged, so the file judged is the file
34
+ * deleted — see `removeCloudJevConfig`.
35
+ *
36
+ * Loaded lazily by its two callers, so connecting without Jev pulls in nothing
37
+ * of it and the hook path never does.
38
+ */
39
+ import {
40
+ chmodSync,
41
+ closeSync,
42
+ constants as fsConstants,
43
+ existsSync,
44
+ fstatSync,
45
+ linkSync,
46
+ lstatSync,
47
+ mkdirSync,
48
+ openSync,
49
+ readFileSync,
50
+ readSync,
51
+ renameSync,
52
+ rmSync,
53
+ statSync,
54
+ unlinkSync,
55
+ writeFileSync,
56
+ type Stats,
57
+ } from "node:fs";
58
+ import { dirname } from "node:path";
59
+ import { randomBytes } from "node:crypto";
60
+ import {
61
+ DEFAULT_JEV_MODE,
62
+ JEV_CLOUD_PROVIDER,
63
+ inspectJevConfig,
64
+ jevCloudBaseUrl,
65
+ jevConfigPath,
66
+ readJevConfigFileForUpdate,
67
+ } from "./semantic/jev-config";
68
+
69
+ /** The mode a Cloud `jev.json` starts in (the user's decision: log first, enforce later). */
70
+ export const CLOUD_JEV_INITIAL_MODE = "shadow" as const;
71
+
72
+ export type CloudJevConfigWrite =
73
+ | { status: "written"; path: string; mode: typeof CLOUD_JEV_INITIAL_MODE; baseUrl: string }
74
+ /** A `jev.json` already exists and was left exactly as it was. */
75
+ | {
76
+ status: "kept";
77
+ path: string;
78
+ /** The provider it names, when it can be read. */
79
+ provider: string | null;
80
+ /**
81
+ * Set when it names the Cloud provider on ANOTHER origin than the one just
82
+ * connected to: it stays off (`the key is only sent where it was issued`)
83
+ * until pointed at this one, which is worth one line at connect time.
84
+ */
85
+ otherOrigin?: string;
86
+ /** Set when the kept file leaves Jev off here: refused, or switched off. */
87
+ jevOff?: { why: "refused"; problem: string; fix?: string } | { why: "off" };
88
+ }
89
+ | { status: "error"; path: string; problem: string };
90
+
91
+ /**
92
+ * Create `jev.json` for the Cloud provider unless one exists. Never throws and
93
+ * never replaces a file.
94
+ */
95
+ export function writeCloudJevConfigIfAbsent(cloudBase: string): CloudJevConfigWrite {
96
+ const path = jevConfigPath();
97
+ let baseUrl: string;
98
+ try {
99
+ baseUrl = jevCloudBaseUrl(cloudBase);
100
+ } catch {
101
+ return { status: "error", path, problem: "the FailproofAI Cloud URL is not a URL" };
102
+ }
103
+ if (existsSync(path)) return kept(path, cloudBase);
104
+
105
+ const body = `${JSON.stringify({ provider: JEV_CLOUD_PROVIDER, baseUrl, mode: CLOUD_JEV_INITIAL_MODE }, null, 2)}\n`;
106
+ const dir = dirname(path);
107
+ const tmp = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
108
+ try {
109
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
110
+ writeFileSync(tmp, body, { mode: 0o600, flag: "wx" });
111
+ chmodSync(tmp, 0o600);
112
+ try {
113
+ linkSync(tmp, path);
114
+ } catch (err) {
115
+ if ((err as NodeJS.ErrnoException).code === "EEXIST") return kept(path, cloudBase);
116
+ throw err;
117
+ }
118
+ } catch (err) {
119
+ return { status: "error", path, problem: `could not write ${path} (${(err as NodeJS.ErrnoException).code ?? "error"})` };
120
+ } finally {
121
+ try {
122
+ rmSync(tmp, { force: true });
123
+ } catch {
124
+ // A stray temp file holds no key; nothing to do.
125
+ }
126
+ }
127
+ tightenDir(dir);
128
+ return { status: "written", path, mode: CLOUD_JEV_INITIAL_MODE, baseUrl };
129
+ }
130
+
131
+ /**
132
+ * What `writeCloudJevConfigIfAbsent` would report for a `jev.json` that is
133
+ * already there — without writing anything when there is none. For a connect
134
+ * that must not switch Jev on (`--no-transcripts`) but still says what an
135
+ * existing file does. Null when there is no file.
136
+ */
137
+ export function existingJevConfig(cloudBase: string): CloudJevConfigWrite | null {
138
+ const path = jevConfigPath();
139
+ return existsSync(path) ? kept(path, cloudBase) : null;
140
+ }
141
+
142
+ /**
143
+ * The mode Jev runs in through FailproofAI Cloud right now, or null when it
144
+ * does not: no `jev.json`, another provider, switched off, or no usable key.
145
+ * Asked the way a hook asks (`inspectJevConfig`), so "on" here means a tool
146
+ * call's next Jev request really goes to FailproofAI Cloud.
147
+ */
148
+ export function cloudJevRunningMode(): "shadow" | "enforce" | null {
149
+ try {
150
+ const r = inspectJevConfig();
151
+ if (r.status !== "ok" || r.config.provider !== JEV_CLOUD_PROVIDER) return null;
152
+ return r.config.mode === "off" ? null : (r.config.mode ?? DEFAULT_JEV_MODE);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ function kept(path: string, cloudBase: string): CloudJevConfigWrite {
159
+ const raw = readJevConfigFileForUpdate()?.raw ?? null;
160
+ const provider = typeof raw?.provider === "string" ? raw.provider : null;
161
+ let otherOrigin: string | undefined;
162
+ if (provider === JEV_CLOUD_PROVIDER && typeof raw?.baseUrl === "string") {
163
+ try {
164
+ const theirs = new URL(raw.baseUrl).origin;
165
+ if (theirs !== new URL(cloudBase).origin) otherOrigin = theirs;
166
+ } catch {
167
+ // Unreadable: the loader will say so; nothing to add here.
168
+ }
169
+ }
170
+ // Not inspected (that would add the origin mismatch as a second refusal), but
171
+ // a stored `off` survives `jev setup --provider failproofai`, so say it.
172
+ if (otherOrigin) return { status: "kept", path, provider, otherOrigin, ...(raw?.mode === "off" ? { jevOff: { why: "off" as const } } : {}) };
173
+ // Asked the way a hook asks, after the new Jev key was stored, so "off" here
174
+ // is what the next tool call sees.
175
+ let jevOff: Extract<CloudJevConfigWrite, { status: "kept" }>["jevOff"];
176
+ try {
177
+ const r = inspectJevConfig();
178
+ if (r.status === "refused") jevOff = { why: "refused", problem: r.problem, ...(r.fix ? { fix: r.fix } : {}) };
179
+ else if (r.status === "off") jevOff = { why: "off" };
180
+ } catch {
181
+ // Nothing to add; the kept line stands on its own.
182
+ }
183
+ return { status: "kept", path, provider, ...(jevOff ? { jevOff } : {}) };
184
+ }
185
+
186
+ /**
187
+ * The directory's group/other WRITE bits, taken off exactly as `jev setup`
188
+ * takes them off: a directory others can write into defeats the file's 0600.
189
+ */
190
+ function tightenDir(dir: string): void {
191
+ if (process.platform === "win32") return;
192
+ try {
193
+ const before = statSync(dir).mode & 0o777;
194
+ if ((before & 0o022) !== 0) chmodSync(dir, before & ~0o022);
195
+ } catch {
196
+ // Not fatal: the loader refuses a directory it will not read from.
197
+ }
198
+ }
199
+
200
+ export type CloudJevConfigRemoval =
201
+ | { status: "removed"; path: string }
202
+ | { status: "absent"; path: string }
203
+ /** The Cloud's `jev.json`, switched off: left in place so the opt-out outlives a reconnect. */
204
+ | { status: "kept-off"; path: string }
205
+ /** A `jev.json` that is not the Cloud's — BYOK, or unreadable — left in place. */
206
+ | { status: "kept"; path: string; provider: string | null }
207
+ /**
208
+ * Not the Cloud's, and another `jev.json` was written in its place while it
209
+ * was being checked. Neither is deleted: the new one stays at `path`, the one
210
+ * that was there is kept at `setAside`.
211
+ */
212
+ | { status: "set-aside"; path: string; provider: string | null; setAside: string }
213
+ | {
214
+ status: "error";
215
+ path: string;
216
+ problem: string;
217
+ /** Where a file that is not the Cloud's was left, when it could not be put back at `path`. */
218
+ setAside?: string;
219
+ };
220
+
221
+ /**
222
+ * Delete `jev.json` iff it names the FailproofAI Cloud provider and is not
223
+ * switched off (`mode: "off"`). Never throws,
224
+ * and never deletes a file it has not judged.
225
+ *
226
+ * # Moved aside, then judged
227
+ *
228
+ * Checking the file's provider and then unlinking the PATH is a race: `jev
229
+ * setup` (or the dashboard's save) replaces jev.json by atomic rename, and a
230
+ * BYOK file renamed into place between the check and the unlink is the file
231
+ * the unlink removes. So the file is first RENAMED to a name only this call
232
+ * knows — atomic, and whatever lands at the path afterwards is untouched by
233
+ * anything below — and it is that file, which nobody else can now replace,
234
+ * whose provider decides. The Cloud's is deleted. Anything else is linked back
235
+ * to the path (same inode, bytes and mode) with a no-clobber link — or, where
236
+ * the filesystem has no hard links, put back by the fallbacks in `putBack` —
237
+ * so a file written at the path meanwhile is never overwritten either; when one
238
+ * was, the original is left beside it under the set-aside name rather than
239
+ * deleted, and when nothing can put it back, the result says where it is.
240
+ */
241
+ export function removeCloudJevConfig(): CloudJevConfigRemoval {
242
+ const path = jevConfigPath();
243
+ let st: Stats;
244
+ try {
245
+ st = lstatSync(path);
246
+ } catch (err) {
247
+ const code = (err as NodeJS.ErrnoException).code;
248
+ if (code === "ENOENT" || code === "ENOTDIR") return { status: "absent", path };
249
+ return { status: "error", path, problem: `could not read ${path} (${code ?? "error"})` };
250
+ }
251
+ // Only a regular file can be the one `config --token` wrote. A symlink, a
252
+ // directory or a FIFO in its place is somebody else's, and is not touched.
253
+ if (!st.isFile()) return { status: "kept", path, provider: null };
254
+
255
+ const aside = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.disconnecting`;
256
+ try {
257
+ renameSync(path, aside);
258
+ } catch (err) {
259
+ const code = (err as NodeJS.ErrnoException).code;
260
+ if (code === "ENOENT") return { status: "absent", path };
261
+ return { status: "error", path, problem: `could not remove ${path} (${code ?? "error"})` };
262
+ }
263
+
264
+ const { provider, off } = fieldsOf(aside);
265
+ if (provider === JEV_CLOUD_PROVIDER && !off) {
266
+ try {
267
+ unlinkSync(aside);
268
+ return { status: "removed", path };
269
+ } catch (err) {
270
+ return { status: "error", path, problem: `could not remove ${aside} (${(err as NodeJS.ErrnoException).code ?? "error"})` };
271
+ }
272
+ }
273
+
274
+ // Not the Cloud's: back where it was, and never over a file written since.
275
+ const back = putBack(aside, path, st.mode & 0o777);
276
+ if (back === "restored") return provider === JEV_CLOUD_PROVIDER ? { status: "kept-off", path } : { status: "kept", path, provider };
277
+ if (back === "occupied") return { status: "set-aside", path, provider, setAside: aside };
278
+ return {
279
+ status: "error",
280
+ path,
281
+ problem: `${path} is not FailproofAI Cloud's and was kept at ${aside}; putting it back failed (${back.code})`,
282
+ setAside: aside,
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Put a set-aside file back at `path`, never over a file that is there now:
288
+ * `restored`, `occupied` (another file is at `path`, and `aside` is left as it
289
+ * is), or the error that stopped every attempt.
290
+ *
291
+ * 1. A hard link: atomic, the same inode, bytes and mode, and it fails with
292
+ * EEXIST when anything is at the path.
293
+ * 2. Some filesystems have no hard links (FAT and exFAT, some network and FUSE
294
+ * mounts answer EPERM or ENOTSUP), so next a COPY created exclusively (`wx`,
295
+ * O_EXCL): just as no-clobber, with the file's own mode (less the umask,
296
+ * so never looser). A hook reading the file mid-copy sees a truncated one
297
+ * and refuses it — Jev off for that call, the regex verdict stands — never
298
+ * another config.
299
+ * 3. When no copy can be created either (no room for one, say), a rename —
300
+ * which replaces what it finds, so only while the path is still empty. The
301
+ * moment between that check and the rename is the one place a write landing
302
+ * at the path could be lost, and it is reached only when neither no-clobber
303
+ * way works.
304
+ *
305
+ * Until one works, the file sits at `aside`, and the caller says where.
306
+ */
307
+ function putBack(aside: string, path: string, mode: number): "restored" | "occupied" | { code: string } {
308
+ const codeOf = (err: unknown) => (err as NodeJS.ErrnoException).code ?? "error";
309
+ try {
310
+ linkSync(aside, path);
311
+ discard(aside);
312
+ return "restored";
313
+ } catch (err) {
314
+ if (codeOf(err) === "EEXIST") return "occupied";
315
+ }
316
+ let created = false;
317
+ try {
318
+ const bytes = readFileSync(aside);
319
+ const fd = openSync(path, "wx", mode);
320
+ created = true;
321
+ try {
322
+ writeFileSync(fd, bytes);
323
+ } finally {
324
+ closeSync(fd);
325
+ }
326
+ discard(aside);
327
+ return "restored";
328
+ } catch (err) {
329
+ if (codeOf(err) === "EEXIST") return "occupied";
330
+ // Created, then the write failed: a truncated file is at the path now, and
331
+ // the original is still at `aside`. Not renamed over — the path is not
332
+ // empty, and whose file is there by now is not knowable.
333
+ if (created) return { code: codeOf(err) };
334
+ }
335
+ try {
336
+ lstatSync(path);
337
+ return "occupied";
338
+ } catch (err) {
339
+ if (codeOf(err) !== "ENOENT") return { code: codeOf(err) };
340
+ }
341
+ try {
342
+ renameSync(aside, path);
343
+ return "restored";
344
+ } catch (err) {
345
+ return { code: codeOf(err) };
346
+ }
347
+ }
348
+
349
+ /** Drop the set-aside name once the file is back. A leftover is 0600 in an owner-only directory, and holds nothing the file at the path does not. */
350
+ function discard(aside: string): void {
351
+ try {
352
+ unlinkSync(aside);
353
+ } catch {
354
+ // Harmless; see above.
355
+ }
356
+ }
357
+
358
+ /** The `provider` a config file names, and whether it is switched off, read without following links or blocking on a FIFO. */
359
+ function fieldsOf(file: string): { provider: string | null; off: boolean } {
360
+ const none = { provider: null, off: false };
361
+ let fd: number;
362
+ try {
363
+ fd = openSync(file, fsConstants.O_RDONLY | (fsConstants.O_NONBLOCK ?? 0) | (fsConstants.O_NOFOLLOW ?? 0));
364
+ } catch {
365
+ return none;
366
+ }
367
+ try {
368
+ const st = fstatSync(fd);
369
+ if (!st.isFile() || st.size > MAX_JEV_CONFIG_BYTES) return none;
370
+ const buf = Buffer.alloc(st.size);
371
+ let off = 0;
372
+ while (off < buf.length) {
373
+ const n = readSync(fd, buf, off, buf.length - off, off);
374
+ if (n === 0) break;
375
+ off += n;
376
+ }
377
+ const parsed: unknown = JSON.parse(buf.subarray(0, off).toString("utf8"));
378
+ const obj = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
379
+ return { provider: typeof obj.provider === "string" ? obj.provider : null, off: obj.mode === "off" };
380
+ } catch {
381
+ return none;
382
+ } finally {
383
+ try {
384
+ closeSync(fd);
385
+ } catch {
386
+ // Nothing useful to do.
387
+ }
388
+ }
389
+ }
390
+
391
+ /** The loader's own bound on a config file (`jev-config.ts`); nothing legitimate is near it. */
392
+ const MAX_JEV_CONFIG_BYTES = 64 * 1024;
@@ -191,7 +191,13 @@ export async function createEsmShim(
191
191
  const shimCode = [
192
192
  `import _cjs from '${distUrl}';`,
193
193
  `export const customPolicies = _cjs.customPolicies;`,
194
+ // Every name a policy file may import has to be listed HERE as well as in
195
+ // `src/index.ts`: this shim is an ESM module, so importing a name it does not
196
+ // declare is a link-time error, not an undefined at runtime. A pack entry
197
+ // calling `semanticPolicies.add` would fail to load entirely.
198
+ `export const semanticPolicies = _cjs.semanticPolicies;`,
194
199
  `export const getCustomHooks = _cjs.getCustomHooks;`,
200
+ `export const getSemanticRegistrations = _cjs.getSemanticRegistrations;`,
195
201
  `export const clearCustomHooks = _cjs.clearCustomHooks;`,
196
202
  `export const allow = _cjs.allow;`,
197
203
  `export const deny = _cjs.deny;`,
@@ -19,6 +19,7 @@ import { configuredCustomPolicyPaths, readMergedHooksConfig, readScopedHooksConf
19
19
  import type { HooksConfig, ConventionPolicyRecord } from "./policy-types";
20
20
  import { BUILTIN_POLICIES } from "./builtin-policies";
21
21
  import { loadCustomHooks, discoverPolicyFiles } from "./custom-hooks-loader";
22
+ import { getSemanticRegistrations } from "./custom-hooks-registry";
22
23
  import { trackHookEvent } from "./hook-telemetry";
23
24
  import { getInstanceId, hashToId } from "../../lib/telemetry-id";
24
25
  import { CliError } from "../cli-error";
@@ -589,6 +590,13 @@ async function installHooksImpl(
589
590
  console.error(`Error: ${msg}`);
590
591
  process.exit(1);
591
592
  }
593
+ const semanticCount = getSemanticRegistrations().length;
594
+ if (semanticCount > 0) {
595
+ console.error(
596
+ `Note: ${path} declares ${semanticCount} Jev check(s) with semanticPolicies.add. They take effect only in a ` +
597
+ "pack published with `failproofai publish`, and are never asked from a policy file.",
598
+ );
599
+ }
592
600
  if (validatedHooks.length === 0) {
593
601
  try {
594
602
  await trackHookEvent(getInstanceId(), "custom_policy_validation_failed", {
@@ -1108,17 +1116,29 @@ export async function listHooks(cwd?: string): Promise<void> {
1108
1116
  // `packCount` counts ENABLED policies, so a pack installed with everything
1109
1117
  // switched off reaches zero the same way an empty machine does — and the
1110
1118
  // advice for the two could not be more different. Telling somebody who has
1111
- // just unticked all 38 to `policies add FailproofAI/policies` sends them to
1119
+ // just unticked all 39 to `policies add FailproofAI/policies` sends them to
1112
1120
  // install what they already have, and doing it would change nothing: the
1113
1121
  // selection is what is empty, not the shelf.
1114
- const packsInstalled = (() => {
1122
+ //
1123
+ // Nor is it "nothing" while a refused pack fails closed (the warning above
1124
+ // says what is denied), or while a pack's Jev checks are live with no regex
1125
+ // policy on.
1126
+ const { packsInstalled, jevChecks, failClosed } = (() => {
1115
1127
  try {
1116
- return readInstalledPacks().packs.length;
1128
+ const { packs, errors } = readInstalledPacks();
1129
+ return {
1130
+ packsInstalled: packs.length,
1131
+ jevChecks: packs.reduce((n, p) => n + (p.effect === "observe" ? 0 : (p.semantic?.length ?? 0)), 0),
1132
+ // The refusals `missingGuards` denies over (pack-failclosed.ts).
1133
+ failClosed: errors.some((e) => e.effect !== "observe" && !e.semanticOnly),
1134
+ };
1117
1135
  } catch {
1118
- return 0;
1136
+ return { packsInstalled: 0, jevChecks: 0, failClosed: false };
1119
1137
  }
1120
1138
  })();
1121
- if (packCount === 0 && packsInstalled === 0) {
1139
+ if (failClosed) {
1140
+ // Said by the pack section's warning, where the refused pack is named.
1141
+ } else if (packCount === 0 && packsInstalled === 0) {
1122
1142
  footer.unshift(
1123
1143
  nextStep(
1124
1144
  `failproofai policies add ${CORE_SOURCE}`,
@@ -1128,6 +1148,14 @@ export async function listHooks(cwd?: string): Promise<void> {
1128
1148
  note("Someone else's: failproofai policies add <owner>/<repo>", opts),
1129
1149
  note("Look first: failproofai policies show <owner>/<repo>", opts),
1130
1150
  );
1151
+ } else if (packCount === 0 && jevChecks > 0) {
1152
+ footer.unshift(
1153
+ note(
1154
+ `No regex policy is on; ${jevChecks} Jev check${jevChecks === 1 ? "" : "s"} from ` +
1155
+ `${packsInstalled === 1 ? "the pack" : "the packs"} above apply where Jev is configured (failproofai jev status).`,
1156
+ opts,
1157
+ ),
1158
+ );
1131
1159
  } else if (packCount === 0) {
1132
1160
  footer.unshift(
1133
1161
  nextStep(
@@ -1292,7 +1320,9 @@ export async function listHooks(cwd?: string): Promise<void> {
1292
1320
  const { packs, errors } = readInstalledPacks();
1293
1321
  for (const pack of packs) {
1294
1322
  const taken = pack.enabled ?? pack.policies.map((p) => p.name);
1295
- groups.push(rule(`Pack — ${pack.id}@${pack.version}`, opts));
1323
+ // The source too: the id is whatever the manifest claims, the source is
1324
+ // the repository this CLI actually fetched it from.
1325
+ groups.push(rule(`Pack — ${pack.id}@${pack.version} · ${pack.source}`, opts));
1296
1326
  groups.push(
1297
1327
  table(
1298
1328
  {
@@ -1319,7 +1349,15 @@ export async function listHooks(cwd?: string): Promise<void> {
1319
1349
  if (errors.length > 0) {
1320
1350
  groups.push(
1321
1351
  warning(
1322
- errors.map((err) => `pack ${err.id ?? "(unnamed)"} will not load: ${err.reason}`),
1352
+ errors.flatMap((err) => [
1353
+ `pack ${err.id ?? "(unnamed)"} will not load: ${err.reason}`,
1354
+ ...(err.effect !== "observe" && !err.semanticOnly
1355
+ ? [
1356
+ "Until it loads, the tool calls its policies cover are DENIED. Fix it, or remove it with: " +
1357
+ `failproofai policies remove ${err.id ?? "<id>"}`,
1358
+ ]
1359
+ : []),
1360
+ ]),
1323
1361
  opts,
1324
1362
  ),
1325
1363
  );