failproofai 1.0.7 → 1.0.8-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +8 -7
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +9 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -10
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +8 -7
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -6
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +7 -6
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +52 -8
  63. package/.next/standalone/.next/server/app/settings/page.js +12 -7
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  70. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  71. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  74. package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +4 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +4 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bd3mje._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +4 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +4 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0o4xkpl._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/{_1-i_gzc._.js → _1-7sqrb._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
  110. package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  113. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  117. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
  121. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  123. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  131. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  132. package/.next/standalone/.next/server/pages/404.html +1 -1
  133. package/.next/standalone/.next/server/pages/500.html +1 -1
  134. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/server-reference-manifest.json +67 -23
  136. package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
  137. package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0ahfwmbkpgfw4.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
  139. package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
  140. package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 1v_tp3hm8wyhe.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
  142. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
  143. package/.next/standalone/.next/static/chunks/2vo7qbdbvnc0o.js +69 -0
  144. package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 2z7i-yg59w4pn.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
  146. package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 3bgot5v6f6s3c.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +6 -0
  148. package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
  149. package/.next/standalone/app/actions/get-jev-config.ts +604 -0
  150. package/.next/standalone/app/actions/pack-actions.ts +12 -0
  151. package/.next/standalone/app/actions/update-jev-config.ts +569 -0
  152. package/.next/standalone/app/components/jev-notices.tsx +96 -0
  153. package/.next/standalone/app/policies/hooks-client.tsx +14 -3
  154. package/.next/standalone/app/settings/jev-panel.tsx +630 -0
  155. package/.next/standalone/app/settings/page.tsx +20 -1
  156. package/.next/standalone/app/settings/settings-client.tsx +27 -1
  157. package/.next/standalone/app/settings/settings.css +79 -0
  158. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  159. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  160. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  161. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  162. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  163. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  164. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  165. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  166. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  167. package/.next/standalone/package.json +10 -10
  168. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  169. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  170. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  171. package/.next/standalone/server.js +1 -1
  172. package/README.md +3 -2
  173. package/bin/failproofai.mjs +148 -4
  174. package/dist/cli.mjs +18537 -9492
  175. package/dist/index.js +19 -1
  176. package/dist/worker.mjs +10421 -3770
  177. package/package.json +10 -10
  178. package/pi-extension/index.ts +11 -0
  179. package/scripts/build-policy-pack.mjs +53 -2
  180. package/src/audit/features.ts +3 -2
  181. package/src/hooks/builtin-policies.ts +195 -8
  182. package/src/hooks/cloud-connection.ts +190 -1
  183. package/src/hooks/cloud-enrollment-cli.ts +57 -8
  184. package/src/hooks/cloud-introspect.ts +6 -0
  185. package/src/hooks/cloud-managed-policies.ts +22 -0
  186. package/src/hooks/configure-wizard.ts +26 -6
  187. package/src/hooks/custom-hooks-loader.ts +98 -10
  188. package/src/hooks/custom-hooks-registry.ts +45 -1
  189. package/src/hooks/effective-reviewers.ts +243 -0
  190. package/src/hooks/first-run-gate.ts +5 -0
  191. package/src/hooks/flush-cli.ts +35 -8
  192. package/src/hooks/fp-config.ts +266 -1
  193. package/src/hooks/fp-home.ts +23 -0
  194. package/src/hooks/fp-reset.ts +22 -4
  195. package/src/hooks/handler.ts +303 -7
  196. package/src/hooks/hook-activity-store.ts +118 -6
  197. package/src/hooks/hook-telemetry.ts +41 -0
  198. package/src/hooks/jev-activity.ts +385 -0
  199. package/src/hooks/jev-cli.ts +2172 -0
  200. package/src/hooks/jev-cloud-connection.ts +392 -0
  201. package/src/hooks/loader-utils.ts +6 -0
  202. package/src/hooks/manager.ts +45 -7
  203. package/src/hooks/pack-cli.ts +568 -37
  204. package/src/hooks/pack-failclosed.ts +3 -0
  205. package/src/hooks/pack-manifest.ts +489 -7
  206. package/src/hooks/pack-store.ts +194 -11
  207. package/src/hooks/policy-authority.ts +386 -0
  208. package/src/hooks/policy-catalog.ts +206 -0
  209. package/src/hooks/policy-evaluator.ts +963 -796
  210. package/src/hooks/policy-registry.ts +27 -1
  211. package/src/hooks/policy-reviewability.ts +258 -0
  212. package/src/hooks/policy-types.ts +128 -0
  213. package/src/hooks/semantic/combine.ts +581 -0
  214. package/src/hooks/semantic/compile.ts +176 -0
  215. package/src/hooks/semantic/decide.ts +408 -0
  216. package/src/hooks/semantic/envelope.ts +1255 -0
  217. package/src/hooks/semantic/evaluator.ts +556 -0
  218. package/src/hooks/semantic/facts.ts +298 -0
  219. package/src/hooks/semantic/intent.ts +1190 -0
  220. package/src/hooks/semantic/jev-client.ts +1155 -0
  221. package/src/hooks/semantic/jev-config.ts +1142 -0
  222. package/src/hooks/semantic/jev-review.ts +389 -0
  223. package/src/hooks/semantic/jev-stats.ts +289 -0
  224. package/src/hooks/semantic/jev-throttle.ts +427 -0
  225. package/src/hooks/semantic/pack-policies.ts +294 -0
  226. package/src/hooks/semantic/policies.ts +597 -0
  227. package/src/hooks/semantic/precondition-names.ts +60 -0
  228. package/src/hooks/semantic/preconditions.ts +58 -0
  229. package/src/hooks/semantic/redact.ts +2937 -0
  230. package/src/hooks/semantic/session-root.ts +116 -0
  231. package/src/hooks/semantic/types.ts +156 -0
  232. package/src/hooks/semver-precedence.ts +128 -0
  233. package/src/hooks/tui.ts +4 -0
  234. package/src/hooks/types.ts +1 -1
  235. package/src/hooks/worker-server.ts +121 -27
  236. package/src/index.ts +6 -0
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
  239. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
  240. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
  241. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +0 -4
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
  243. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +0 -4
  244. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
  246. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +0 -4
  247. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +0 -4
  248. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +0 -4
  249. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
  250. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
  253. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
  254. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +0 -4
  255. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
  256. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
  257. package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
  258. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
  259. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
  260. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  261. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
  262. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
  263. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
  264. package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
  265. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
  266. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
  267. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
  268. package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
  269. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
  270. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
  271. /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
@@ -0,0 +1,604 @@
1
+ "use server";
2
+
3
+ /**
4
+ * Read side of the /settings "Jev" section. Writes live in
5
+ * `update-jev-config.ts`, mirroring the get-scheduled-audit /
6
+ * update-scheduled-audit split next door.
7
+ *
8
+ * ## CLI ⟷ dashboard parity
9
+ *
10
+ * Every field comes from the same `~/.failproofai/jev.json` the hooks read, via
11
+ * the same `inspectJevConfig()` that backs `failproofai jev status` — so the
12
+ * two surfaces cannot disagree about what is configured, and neither can
13
+ * disagree with what a hook will actually do on the next tool call. There is no
14
+ * dashboard-side copy of this state.
15
+ *
16
+ * ## The token never comes back
17
+ *
18
+ * This is the rule the whole module is built around: nothing returned here
19
+ * carries the API key or any piece of it, not even from a file the loader
20
+ * refused. The view has a `token` field and it holds a SOURCE — whether a key is
21
+ * there and where it came from, which is what the panel has to say. A GET that
22
+ * returns the key, or four characters of it, hands that to any page which can
23
+ * reach this origin, and the dashboard has no authentication.
24
+ *
25
+ * So the routing fields of a refused file are copied out one by one
26
+ * (`routingFromRaw`) rather than spread: a spread is how `apiKey` gets carried
27
+ * along by accident the next time someone adds a field.
28
+ *
29
+ * ## …and neither does the model
30
+ *
31
+ * `model` is the one routing field that holds a free string a person types next
32
+ * to their key, and `--model <key>` is one slip away from `--token <key>`. The
33
+ * loader refuses such a file (`validateModel` knows the shape), and a refused
34
+ * file's routing is exactly what this module hands back so the owner can see
35
+ * and repair it — which would have carried the probable key into the browser
36
+ * while the very same response said the key never comes back. So the model is
37
+ * treated as the token is, rather than masked: `modelView()` returns the stored
38
+ * string only where it cannot be a credential, and otherwise reports presence
39
+ * alone. A mask would be worse on both counts — it still hands over a piece of
40
+ * a key, and the last four characters of a model id tell nobody anything.
41
+ *
42
+ * It is a shape test, so it is not a proof: a short secret with no known prefix
43
+ * can still look like a model id, and the honest bound is "the file the loader
44
+ * would refuse cannot be echoed". Anything the loader ACCEPTS as a model is a
45
+ * value `jev setup` would also have printed. See `JevModelView`.
46
+ */
47
+
48
+ import { readCredentials, readJevCloudCredential } from "@/src/hooks/fp-config";
49
+ import {
50
+ CLOUDFLARE_ACCOUNT_ID_RE,
51
+ DEFAULT_JEV_MODE,
52
+ JEV_API_KEY_ENV,
53
+ JEV_CLOUD_PROVIDER,
54
+ JEV_PROVIDER_KINDS,
55
+ baseUrlWithoutQuery,
56
+ inspectJevConfig,
57
+ looksLikeCredential,
58
+ readJevConfigForUpdate,
59
+ type JevConfig,
60
+ type JevProviderKind,
61
+ } from "@/src/hooks/semantic/jev-config";
62
+ import { displayEndpoint, jevRoute } from "@/src/hooks/semantic/jev-client";
63
+ import { jevStats } from "@/src/hooks/semantic/jev-stats";
64
+ import {
65
+ reviewableProblem,
66
+ reviewableSummary,
67
+ surveyReviewableCoverage,
68
+ } from "@/src/hooks/policy-reviewability";
69
+
70
+ /** The loader's answers, unchanged — see `JevConfigInspection`. */
71
+ export type JevSettingsStatus = "absent" | "ok" | "key-missing" | "refused" | "off" | "not-connected" | "key-lacks-jev";
72
+
73
+ /** What Jev has been doing lately, from `jevStats()`. Not a new pipeline. */
74
+ export interface JevSettingsStats {
75
+ windowMs: number;
76
+ /** Gate evaluations Jev was consulted on: answered or fell back. */
77
+ total: number;
78
+ answered: number;
79
+ fallbacks: number;
80
+ /** Share of `total` that fell back to the regex engine, 0..1. */
81
+ fallbackRate: number;
82
+ }
83
+
84
+ /**
85
+ * Presence of a stored key, and nothing else about it.
86
+ *
87
+ * It used to carry the last four characters, so the panel could say "configured,
88
+ * ending 1eb9". That is a recognisable fragment of a live credential rendered
89
+ * into a web page on an origin with no authentication, and it buys the reader
90
+ * nothing they could not get by re-pasting the key: the panel already says
91
+ * whether one is stored, where it came from, and that leaving the field blank
92
+ * keeps it. So the fragment is not shown, and — the part that matters — it is
93
+ * not computed and not sent.
94
+ */
95
+ export interface JevTokenPresence {
96
+ /** `cloud`: the key is this machine's FailproofAI Cloud key, held in `credentials.json`. */
97
+ source: "file" | "env" | "cloud";
98
+ }
99
+
100
+ /**
101
+ * What the panel may say about the stored model id.
102
+ *
103
+ * - `default` — nothing is stored, so the provider's own default is used.
104
+ * - `id` — a stored value that is a model id, shown as itself.
105
+ * - `withheld` — something is stored that does not look like a model id, so it
106
+ * is reported the way a token is: present, never quoted.
107
+ *
108
+ * The decision is `looksLikeCredential()`, the loader's own predicate for "this
109
+ * is a key, not a model id" — the one that refuses the file in the first place,
110
+ * not a second rule invented here. It has two deliberate carve-outs so it does
111
+ * not refuse real ids (a `/`, which every gateway-qualified id has, and the
112
+ * letters "jev"), and a base64 key can contain a `/`, so length is checked too:
113
+ * every id Jev is addressed by is short — `jev-1.13.0`, `typesafe/jev-1.13`,
114
+ * `@cf/typesafe/jev-1.13` — and nothing longer is worth showing at the risk of
115
+ * it being a key.
116
+ */
117
+ export type JevModelView =
118
+ | { kind: "default" }
119
+ | { kind: "id"; id: string }
120
+ | { kind: "withheld" };
121
+
122
+ /**
123
+ * How much of this machine's enabled policy set Jev is allowed to CLEAR.
124
+ *
125
+ * The panel says whether Jev is on; this is what says whether the half of it
126
+ * that clears a verdict can fire at all. A pack published before this release
127
+ * declares no `authority`, every policy in it is therefore `hard`, and a
128
+ * machine in that state looks perfect from every other field on the panel — the
129
+ * endpoint answers, the fallback rate is fine, and no deny is ever cleared. See
130
+ * `src/hooks/policy-reviewability.ts` for what is counted.
131
+ *
132
+ * The two sentences are computed there rather than here, so this panel and
133
+ * `failproofai jev status` say the same thing in the same words — the same rule
134
+ * the rest of this module follows for everything it reports.
135
+ */
136
+ export interface JevReviewabilityView {
137
+ /** Enabled policies whose authority could be read without running code. */
138
+ enabled: number;
139
+ /** Of those, the ones Jev may clear. */
140
+ reviewable: number;
141
+ /** The count, as one line. */
142
+ summary: string;
143
+ /** Why nothing can be cleared, and what fixes it; null when something can. */
144
+ problem: string | null;
145
+ }
146
+
147
+ /**
148
+ * This machine's FailproofAI Cloud connection, as far as Jev is concerned —
149
+ * read from `credentials.json` on every render, with NO network call and
150
+ * nothing secret in it: the org as the server named it at connect time, the
151
+ * host, and whether the key carries Jev. Every token in that file is read and
152
+ * dropped inside `cloudConnectionView`; none of it, nor any piece of it, is in
153
+ * this object.
154
+ */
155
+ export interface JevCloudConnectionView {
156
+ /** A Cloud credential (policy or reporting) is stored on this machine. */
157
+ connected: boolean;
158
+ /** "Acme Inc (acme)" — recorded at connect time; null when it was not. */
159
+ org: string | null;
160
+ /** The Cloud host, e.g. `app.befailproof.ai`. */
161
+ host: string | null;
162
+ /**
163
+ * Whether the key this machine connected with carries `jev:evaluate` — which
164
+ * is exactly whether connect stored a `jev` slot. `refused`: a slot may be
165
+ * there, but `credentials.json` is not owner-only, so Jev will not read it.
166
+ */
167
+ jev: "yes" | "no" | "refused";
168
+ }
169
+
170
+ export interface JevSettingsView {
171
+ status: JevSettingsStatus;
172
+ /** True only when a hook running right now would consult Jev. */
173
+ on: boolean;
174
+ /** See `JevCloudConnectionView`. Present on every status. */
175
+ cloud: JevCloudConnectionView;
176
+ /**
177
+ * `~/.failproofai/jev.json`, whether or not it exists, and its permission
178
+ * bits as `0600` when it has some.
179
+ *
180
+ * Both are part of this inspection because `failproofai jev status` prints
181
+ * them, and this module is that command's answer read from the same
182
+ * `inspectJevConfig`. The PANEL does not draw them: a config path and a mode
183
+ * are not something anybody acts on from a browser, and the one case where
184
+ * the bits decide anything — a file the loader refuses for being
185
+ * group-writable — already names the file in `problem` and the `chmod` in
186
+ * `fix`. See `jev-panel.tsx`.
187
+ */
188
+ path: string;
189
+ permissions: string | null;
190
+ provider: JevProviderKind | null;
191
+ /**
192
+ * The stored base URL, or "" for the provider's own API. Form value, and
193
+ * NEVER its query string — see `baseUrlView` below for why, and
194
+ * `baseUrlQueryWithheld` for what the panel says about the part it did not
195
+ * get.
196
+ */
197
+ baseUrl: string;
198
+ /**
199
+ * True when the stored base URL carries a query string that `baseUrl` above
200
+ * does not.
201
+ *
202
+ * The panel needs it to avoid lying twice over: the field would otherwise
203
+ * show a URL that is not the stored one, and an untouched save would look
204
+ * like it kept a query string the person never saw. `update-jev-config.ts`
205
+ * keeps a stored query that the loader accepts when the field comes back
206
+ * unchanged, and this is how the panel can say so.
207
+ */
208
+ baseUrlQueryWithheld: boolean;
209
+ /** Cloudflare only; "" otherwise. Form value. */
210
+ accountId: string;
211
+ /**
212
+ * The stored model, for DISPLAY only — never a form value, and never the
213
+ * stored string when that string could be a key. The form does not offer this
214
+ * field and the save path leaves it alone (see `update-jev-config.ts`), so
215
+ * nothing needs it back to round-trip it.
216
+ */
217
+ model: JevModelView;
218
+ /**
219
+ * Where requests actually go, query string elided. Null when unroutable.
220
+ *
221
+ * The computed route, which is not what any form field holds — `baseUrl` may
222
+ * be empty for the provider's own API, and Cloudflare's route is built from
223
+ * `accountId`. `jev status` prints it for that reason. The panel does not: for
224
+ * Cloudflare the route IS `/accounts/<id>/ai/run`, so a read-only row above
225
+ * the form repeated the endpoint field and the account id field at once,
226
+ * putting a per-account address on the page twice.
227
+ */
228
+ endpoint: string | null;
229
+ mode: NonNullable<JevConfig["mode"]>;
230
+ timeoutMs: number | null;
231
+ token: JevTokenPresence | null;
232
+ /** The loader's own words for why this file is refused or incomplete. */
233
+ problem: string | null;
234
+ /** The command that fixes it, when there is one. */
235
+ fix: string | null;
236
+ stats: JevSettingsStats | null;
237
+ /** What Jev may clear here. Null when Jev is off, or when it could not be read. */
238
+ reviewable: JevReviewabilityView | null;
239
+ }
240
+
241
+ /**
242
+ * Fills the key slot when a route is computed for DISPLAY only. The key does
243
+ * not decide where requests go, but `jevRoute` validates the whole config
244
+ * before answering, so a config whose key lives in the environment — or one the
245
+ * loader refused — needs something in the field to get an endpoint out. Never
246
+ * written, never sent.
247
+ */
248
+ const KEY_STAND_IN = "display-only";
249
+
250
+ /** Longer than any model id a Jev route knows, and well inside a pasted key's length. */
251
+ const MAX_SHOWABLE_MODEL_LENGTH = 40;
252
+
253
+ /** The stored model as the panel may see it — see `JevModelView` for the rule. */
254
+ function modelView(stored: string): JevModelView {
255
+ if (!stored) return { kind: "default" };
256
+ if (stored.length > MAX_SHOWABLE_MODEL_LENGTH || looksLikeCredential(stored)) {
257
+ return { kind: "withheld" };
258
+ }
259
+ return { kind: "id", id: stored };
260
+ }
261
+
262
+ /**
263
+ * A stored base URL as the browser may see it: never its query string.
264
+ *
265
+ * `validateBaseUrl` now refuses a credential-bearing query parameter, so a file
266
+ * written by THIS build cannot carry one. A file written by an older one can,
267
+ * and that file is precisely the one this module hands back for repair — the
268
+ * refused branch below copies the raw routing fields out on purpose. Only the
269
+ * derived `endpoint` was elided (`displayEndpoint`), so the base URL carried
270
+ * the secret through a response whose whole documented rule is that the
271
+ * credential never comes back.
272
+ *
273
+ * Every branch goes through this one function rather than reading `baseUrl`
274
+ * itself, for the reason `routingFromRaw` copies field by field: the next field
275
+ * added should not be able to quietly reintroduce it.
276
+ */
277
+ function baseUrlView(stored: string | undefined): { baseUrl: string; baseUrlQueryWithheld: boolean } {
278
+ if (!stored) return { baseUrl: "", baseUrlQueryWithheld: false };
279
+ const { url, hadQuery } = baseUrlWithoutQuery(stored);
280
+ return { baseUrl: url, baseUrlQueryWithheld: hadQuery };
281
+ }
282
+
283
+ function octal(mode: number | null): string | null {
284
+ return mode === null ? null : mode.toString(8).padStart(4, "0");
285
+ }
286
+
287
+ function asString(v: unknown): string {
288
+ return typeof v === "string" ? v : "";
289
+ }
290
+
291
+ /**
292
+ * The routing fields of a raw `jev.json`, for a file the loader refused — so
293
+ * the panel can show what it names and the form can offer it back for repair.
294
+ *
295
+ * Copied field by field, deliberately. `apiKey` is the reason: a spread of the
296
+ * parsed object would carry it into a value this module hands to the browser,
297
+ * and it would keep doing so silently as fields are added.
298
+ */
299
+ function routingFromRaw(raw: Record<string, unknown> | null): {
300
+ provider: JevProviderKind | null;
301
+ baseUrl: string;
302
+ accountId: string;
303
+ model: string;
304
+ mode: NonNullable<JevConfig["mode"]>;
305
+ } {
306
+ const providerRaw = asString(raw?.provider);
307
+ const provider = (JEV_PROVIDER_KINDS as readonly string[]).includes(providerRaw)
308
+ ? (providerRaw as JevProviderKind)
309
+ : null;
310
+ const modeRaw = raw?.mode;
311
+ return {
312
+ provider,
313
+ baseUrl: asString(raw?.baseUrl),
314
+ // An account id is 32 hex characters; anything else in that slot of a
315
+ // refused file may be a pasted key, and is not shown.
316
+ accountId: CLOUDFLARE_ACCOUNT_ID_RE.test(asString(raw?.accountId)) ? asString(raw?.accountId) : "",
317
+ model: asString(raw?.model),
318
+ mode: modeRaw === "off" || modeRaw === "shadow" || modeRaw === "enforce" ? modeRaw : DEFAULT_JEV_MODE,
319
+ };
320
+ }
321
+
322
+ /**
323
+ * Whether a switched-off file still has a key to switch back on with — as a
324
+ * SOURCE, like every other token field here. The routing the loader reports
325
+ * for an `off` file deliberately carries no key, so this is read for presence
326
+ * only: the raw file's `apiKey` is tested and dropped, and the Cloud
327
+ * credential's status is read and its key never touched.
328
+ */
329
+ function offTokenPresence(provider: JevProviderKind): JevTokenPresence | null {
330
+ if (provider === JEV_CLOUD_PROVIDER) {
331
+ try {
332
+ return readJevCloudCredential().status === "ok" ? { source: "cloud" } : null;
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+ const raw = readJevConfigForUpdate();
338
+ return typeof raw?.apiKey === "string" && raw.apiKey !== "" ? { source: "file" } : null;
339
+ }
340
+
341
+ /**
342
+ * `jevRoute` for display, swallowing the throw a file that names no usable
343
+ * route produces.
344
+ *
345
+ * A FailproofAI Cloud config is only routed against the origin of the
346
+ * credential it was validated with (`validateLoadedJevConfig`). The loaded
347
+ * config carries it; a display stand-in built here — for a file that is off,
348
+ * refused, or has no credential behind it — gets the file's own origin, like
349
+ * the stand-in key beside it. Nothing built here is ever sent.
350
+ */
351
+ function endpointFor(cfg: JevConfig): string | null {
352
+ try {
353
+ return displayEndpoint(jevRoute(displayOnlyCloudOrigin(cfg)).endpoint);
354
+ } catch {
355
+ return null;
356
+ }
357
+ }
358
+
359
+ function displayOnlyCloudOrigin(cfg: JevConfig): JevConfig {
360
+ if (cfg.provider !== JEV_CLOUD_PROVIDER || cfg.credentialOrigin || typeof cfg.baseUrl !== "string") return cfg;
361
+ try {
362
+ return { ...cfg, credentialOrigin: new URL(cfg.baseUrl).origin };
363
+ } catch {
364
+ return cfg;
365
+ }
366
+ }
367
+
368
+ /**
369
+ * The fallback rate and the counts behind it, for the one line on the panel
370
+ * that says whether Jev is working. Read only when Jev is on, or may be for
371
+ * hooks whose environment has the key: on a machine with no config the numbers
372
+ * are all zero and the line would be noise.
373
+ *
374
+ * `jevStats()` never throws and derives from the activity store the activity
375
+ * tab already reads, so this adds no pipeline and no persisted state.
376
+ */
377
+ async function statsOrNull(on: boolean): Promise<JevSettingsStats | null> {
378
+ if (!on) return null;
379
+ try {
380
+ const s = await jevStats();
381
+ return {
382
+ windowMs: s.windowMs,
383
+ total: s.total,
384
+ answered: s.answered,
385
+ fallbacks: s.fallbacks,
386
+ fallbackRate: s.fallbackRate,
387
+ };
388
+ } catch {
389
+ // The panel renders without the line rather than without the panel.
390
+ return null;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * The authority counts, for a machine where a clear could actually happen.
396
+ *
397
+ * Off means the regex policies decide whatever any policy's authority says, so
398
+ * there is nothing to report and the panel shows no row. Never throws, for the
399
+ * same reason `statsOrNull` does not: the panel renders without the line rather
400
+ * than without the panel.
401
+ */
402
+ function reviewableOrNull(on: boolean): JevReviewabilityView | null {
403
+ if (!on) return null;
404
+ try {
405
+ // The launch directory, as every other dashboard read resolves project
406
+ // config: the standalone server chdirs into the package, and `jev status`
407
+ // counts the project it is run in.
408
+ const coverage = surveyReviewableCoverage(process.env.FAILPROOFAI_LAUNCH_CWD || process.cwd());
409
+ return {
410
+ enabled: coverage.enabled,
411
+ reviewable: coverage.reviewable,
412
+ summary: reviewableSummary(coverage),
413
+ problem: reviewableProblem(coverage),
414
+ };
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+
420
+ /**
421
+ * The Cloud connection row, from the local files only. `readCredentials`
422
+ * returns every token on the machine; this function takes the three
423
+ * non-secret facts out and lets the rest go, so the object it returns — and
424
+ * therefore the page — never holds a key. Never throws.
425
+ */
426
+ function cloudConnectionView(): JevCloudConnectionView {
427
+ let connected = false;
428
+ let org: string | null = null;
429
+ let host: string | null = null;
430
+ try {
431
+ const creds = readCredentials();
432
+ const url = creds.cloud?.url ?? creds.ingest?.url ?? null;
433
+ connected = url !== null;
434
+ if (url !== null) {
435
+ try {
436
+ host = new URL(url).host;
437
+ } catch {
438
+ host = null;
439
+ }
440
+ }
441
+ const o = creds.org;
442
+ org = o ? (o.name && o.slug ? `${o.name} (${o.slug})` : (o.name ?? o.slug ?? o.id ?? null)) : null;
443
+ } catch {
444
+ // An unreadable file is "not connected" here, as it is to `config --status`.
445
+ }
446
+ let jev: JevCloudConnectionView["jev"] = "no";
447
+ try {
448
+ const slot = readJevCloudCredential().status;
449
+ jev = slot === "ok" ? "yes" : slot === "refused" ? "refused" : "no";
450
+ } catch {
451
+ jev = "no";
452
+ }
453
+ return { connected, org, host, jev };
454
+ }
455
+
456
+ /**
457
+ * Everything /settings shows about Jev, and nothing it must not.
458
+ *
459
+ * Never throws: every branch of `inspectJevConfig` is a state the panel can
460
+ * draw, and a settings page whose job is to say whether a security feature is
461
+ * on must not be replaced by an error boundary when the answer is "badly".
462
+ */
463
+ export async function getJevSettingsAction(): Promise<JevSettingsView> {
464
+ const inspection = inspectJevConfig();
465
+ const base = {
466
+ cloud: cloudConnectionView(),
467
+ path: inspection.path,
468
+ permissions: null as string | null,
469
+ provider: null as JevProviderKind | null,
470
+ baseUrl: "",
471
+ baseUrlQueryWithheld: false,
472
+ accountId: "",
473
+ model: { kind: "default" } as JevModelView,
474
+ endpoint: null as string | null,
475
+ mode: DEFAULT_JEV_MODE,
476
+ timeoutMs: null as number | null,
477
+ token: null as JevTokenPresence | null,
478
+ problem: null as string | null,
479
+ fix: null as string | null,
480
+ reviewable: null as JevReviewabilityView | null,
481
+ };
482
+
483
+ if (inspection.status === "ok") {
484
+ const cfg = inspection.config;
485
+ return {
486
+ ...base,
487
+ status: "ok",
488
+ on: true,
489
+ permissions: octal(inspection.mode),
490
+ provider: cfg.provider,
491
+ ...baseUrlView(cfg.baseUrl),
492
+ accountId: cfg.accountId ?? "",
493
+ // A loadable config's model already passed `validateModel`, so this can
494
+ // only be an id — run through the same gate anyway, because the gate is
495
+ // what keeps that true if the loader's rules ever loosen.
496
+ model: modelView(cfg.model ?? ""),
497
+ endpoint: endpointFor(cfg),
498
+ mode: cfg.mode ?? DEFAULT_JEV_MODE,
499
+ timeoutMs: cfg.timeoutMs ?? null,
500
+ // The key is read here and nothing derived from it leaves the function —
501
+ // not a mask, not a fragment, not its length.
502
+ token: { source: inspection.keySource },
503
+ stats: await statsOrNull(true),
504
+ reviewable: reviewableOrNull(true),
505
+ };
506
+ }
507
+
508
+ if (inspection.status === "key-missing") {
509
+ const r = inspection.routing;
510
+ return {
511
+ ...base,
512
+ status: "key-missing",
513
+ on: false,
514
+ permissions: octal(inspection.mode),
515
+ provider: r.provider,
516
+ ...baseUrlView(r.baseUrl),
517
+ accountId: r.accountId ?? "",
518
+ model: modelView(r.model ?? ""),
519
+ endpoint: endpointFor({ ...r, apiKey: KEY_STAND_IN }),
520
+ mode: r.mode ?? DEFAULT_JEV_MODE,
521
+ timeoutMs: r.timeoutMs ?? null,
522
+ // The file names the environment as the source and the variable is not
523
+ // set here, so there is nothing present to hint at.
524
+ token: null,
525
+ problem: inspection.problem,
526
+ fix: `set ${JEV_API_KEY_ENV}, or save a token here`,
527
+ // Off in THIS process's environment only; hooks that have the variable
528
+ // still consult Jev, and the activity store records what they did.
529
+ stats: await statsOrNull(true),
530
+ };
531
+ }
532
+
533
+ if (inspection.status === "off" || inspection.status === "not-connected" || inspection.status === "key-lacks-jev") {
534
+ // Configured, and not running: switched off by its owner, or a FailproofAI
535
+ // Cloud file on a machine with no Cloud key — not connected at all, or
536
+ // connected with a key that has no Jev. Routing only, as for
537
+ // `key-missing` — the loader's routing object carries no key to leak.
538
+ const r = inspection.routing;
539
+ return {
540
+ ...base,
541
+ status: inspection.status,
542
+ on: false,
543
+ permissions: octal(inspection.mode),
544
+ provider: r.provider,
545
+ ...baseUrlView(r.baseUrl),
546
+ accountId: r.accountId ?? "",
547
+ model: modelView(r.model ?? ""),
548
+ endpoint: endpointFor({ ...r, apiKey: KEY_STAND_IN }),
549
+ mode: r.mode ?? DEFAULT_JEV_MODE,
550
+ timeoutMs: r.timeoutMs ?? null,
551
+ token: inspection.status === "off" ? offTokenPresence(r.provider) : null,
552
+ problem: inspection.status === "off" ? null : inspection.problem,
553
+ fix:
554
+ inspection.status === "not-connected"
555
+ ? "connect this machine with a key that carries jev:evaluate: failproofai config --token <key>"
556
+ : inspection.status === "key-lacks-jev"
557
+ ? "reconnect this machine with a key that carries jev:evaluate: failproofai config --token <key>"
558
+ : null,
559
+ stats: null,
560
+ };
561
+ }
562
+
563
+ if (inspection.status === "refused") {
564
+ // Show what the file names so the owner can see whether the endpoint is one
565
+ // they chose — the same question `jev status` puts to them. Routing only.
566
+ const r = routingFromRaw(readJevConfigForUpdate());
567
+ return {
568
+ ...base,
569
+ status: "refused",
570
+ on: false,
571
+ permissions: octal(inspection.mode),
572
+ provider: r.provider,
573
+ ...baseUrlView(r.baseUrl),
574
+ accountId: r.accountId,
575
+ // This is the branch the rule exists for: a file whose `model` slot holds
576
+ // a pasted key IS a refused file, and these fields are copied raw.
577
+ model: modelView(r.model),
578
+ // Shown for the same reason `jev status` shows it: the owner needs to see
579
+ // whether the endpoint a file they may not have written names is one they
580
+ // chose. Null when the routing fields do not make a usable route.
581
+ //
582
+ // The model is left out of this probe deliberately. It does not appear in
583
+ // any endpoint — `jevRoute` puts it in the request body, not the URL — but
584
+ // `jevRoute` validates the whole config first, so a refused model would
585
+ // hide the one field the owner is being asked to check.
586
+ endpoint:
587
+ r.provider === null
588
+ ? null
589
+ : endpointFor({
590
+ provider: r.provider,
591
+ apiKey: KEY_STAND_IN,
592
+ ...(r.baseUrl ? { baseUrl: r.baseUrl } : {}),
593
+ ...(r.accountId ? { accountId: r.accountId } : {}),
594
+ mode: r.mode,
595
+ }),
596
+ mode: r.mode,
597
+ problem: inspection.problem,
598
+ fix: inspection.fix ?? null,
599
+ stats: null,
600
+ };
601
+ }
602
+
603
+ return { ...base, status: "absent", on: false, stats: null };
604
+ }
@@ -139,6 +139,16 @@ export interface PackPreviewResult {
139
139
  source?: string;
140
140
  effect?: "enforce" | "observe";
141
141
  policies?: Array<{ name: string; description: string; category: string; defaultEnabled: boolean }>;
142
+ /**
143
+ * Names of the pack's Jev checks, not a policy list: they are not selectable,
144
+ * and they REPLACE the ones this build ships rather than adding to them.
145
+ *
146
+ * Carried because the preview is what somebody consents to. A pack whose
147
+ * semantic half is invisible here is half a pack agreed to unseen.
148
+ */
149
+ semantic?: string[];
150
+ /** The minimum CLI the pack declares, when it declares one. */
151
+ minCliVersion?: string;
142
152
  error?: string;
143
153
  }
144
154
 
@@ -167,6 +177,8 @@ export async function previewPackWebAction(source: string): Promise<PackPreviewR
167
177
  category: p.category,
168
178
  defaultEnabled: p.defaultEnabled,
169
179
  })),
180
+ ...(preview.semantic.length > 0 ? { semantic: preview.semantic.map((s) => s.name) } : {}),
181
+ ...(preview.minCliVersion ? { minCliVersion: preview.minCliVersion } : {}),
170
182
  };
171
183
  } catch (err) {
172
184
  return { ok: false, error: err instanceof Error ? err.message : String(err) };