failproofai 1.0.7-beta.2 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +7 -8
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js +5 -7
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js +6 -6
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +11 -13
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js +7 -8
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +7 -7
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  59. package/.next/standalone/.next/server/app/projects/page.js +6 -7
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -41
  63. package/.next/standalone/.next/server/app/settings/page.js +9 -12
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/{[externals]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
  67. package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
  71. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
  72. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
  76. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
  98. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-i_gzc._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _166t73i._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
  104. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  107. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
  113. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +23 -56
  121. package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 0__8a7m868fvf.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 0o6qlkgubtoex.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 13i7-9is-vhys.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
  126. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  127. package/.next/standalone/.next/static/chunks/{2_pltstd8-xgs.js → 2klitrtzpaoe0.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 2mdh397ghgnvv.js} +1 -1
  129. package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
  130. package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
  131. package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +69 -0
  132. package/.next/standalone/.next/static/chunks/{1qd741hzlmjbo.js → 43ufqrz8qo3h-.js} +1 -1
  133. package/.next/standalone/SECURITY.md +53 -0
  134. package/.next/standalone/app/actions/pack-actions.ts +0 -12
  135. package/.next/standalone/app/policies/hooks-client.tsx +0 -9
  136. package/.next/standalone/app/settings/page.tsx +1 -20
  137. package/.next/standalone/app/settings/settings-client.tsx +1 -27
  138. package/.next/standalone/app/settings/settings.css +0 -79
  139. package/.next/standalone/package.json +10 -10
  140. package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
  141. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
  142. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
  143. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
  144. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
  145. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
  146. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
  147. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
  148. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
  150. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
  151. package/.next/standalone/server.js +1 -1
  152. package/README.md +2 -2
  153. package/bin/failproofai.mjs +2 -115
  154. package/dist/cli.mjs +6543 -13872
  155. package/dist/index.js +1 -19
  156. package/dist/worker.mjs +2022 -8055
  157. package/package.json +10 -10
  158. package/pi-extension/index.ts +0 -11
  159. package/scripts/build-policy-pack.mjs +2 -53
  160. package/src/audit/features.ts +2 -3
  161. package/src/hooks/builtin-policies.ts +8 -177
  162. package/src/hooks/cloud-enrollment-cli.ts +1 -1
  163. package/src/hooks/cloud-managed-policies.ts +0 -22
  164. package/src/hooks/custom-hooks-loader.ts +7 -45
  165. package/src/hooks/custom-hooks-registry.ts +1 -45
  166. package/src/hooks/first-run-gate.ts +0 -5
  167. package/src/hooks/fp-home.ts +0 -23
  168. package/src/hooks/handler.ts +6 -265
  169. package/src/hooks/hook-activity-store.ts +1 -105
  170. package/src/hooks/hook-telemetry.ts +0 -41
  171. package/src/hooks/loader-utils.ts +0 -6
  172. package/src/hooks/manager.ts +1 -1
  173. package/src/hooks/pack-cli.ts +19 -412
  174. package/src/hooks/pack-manifest.ts +7 -479
  175. package/src/hooks/pack-store.ts +11 -157
  176. package/src/hooks/policy-catalog.ts +0 -204
  177. package/src/hooks/policy-evaluator.ts +796 -940
  178. package/src/hooks/policy-registry.ts +0 -25
  179. package/src/hooks/policy-types.ts +0 -126
  180. package/src/hooks/types.ts +1 -1
  181. package/src/hooks/worker-server.ts +26 -119
  182. package/src/index.ts +0 -6
  183. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  184. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  185. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
  186. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
  187. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
  189. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
  190. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  191. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
  192. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
  193. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  194. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  195. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
  196. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  197. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  198. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
  199. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
  200. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
  201. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  202. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +0 -151
  210. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  211. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  213. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  215. package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +0 -2
  216. package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
  217. package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
  218. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
  219. package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +0 -6
  220. package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +0 -69
  221. package/.next/standalone/PROBE-FOLLOWUP.md +0 -186
  222. package/.next/standalone/app/actions/get-jev-config.ts +0 -424
  223. package/.next/standalone/app/actions/update-jev-config.ts +0 -420
  224. package/.next/standalone/app/components/jev-notices.tsx +0 -96
  225. package/.next/standalone/app/settings/jev-panel.tsx +0 -473
  226. package/src/hooks/effective-reviewers.ts +0 -79
  227. package/src/hooks/jev-activity.ts +0 -385
  228. package/src/hooks/jev-cli.ts +0 -1249
  229. package/src/hooks/policy-authority.ts +0 -333
  230. package/src/hooks/policy-reviewability.ts +0 -229
  231. package/src/hooks/semantic/combine.ts +0 -541
  232. package/src/hooks/semantic/compile.ts +0 -176
  233. package/src/hooks/semantic/decide.ts +0 -392
  234. package/src/hooks/semantic/envelope.ts +0 -1296
  235. package/src/hooks/semantic/evaluator.ts +0 -547
  236. package/src/hooks/semantic/facts.ts +0 -292
  237. package/src/hooks/semantic/intent.ts +0 -1190
  238. package/src/hooks/semantic/jev-client.ts +0 -643
  239. package/src/hooks/semantic/jev-config.ts +0 -594
  240. package/src/hooks/semantic/jev-review.ts +0 -374
  241. package/src/hooks/semantic/jev-stats.ts +0 -289
  242. package/src/hooks/semantic/jev-throttle.ts +0 -421
  243. package/src/hooks/semantic/pack-policies.ts +0 -251
  244. package/src/hooks/semantic/policies.ts +0 -596
  245. package/src/hooks/semantic/precondition-names.ts +0 -60
  246. package/src/hooks/semantic/preconditions.ts +0 -58
  247. package/src/hooks/semantic/redact.ts +0 -2910
  248. package/src/hooks/semantic/types.ts +0 -145
  249. package/src/hooks/semver-precedence.ts +0 -128
  250. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
  251. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
  252. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
@@ -1,186 +0,0 @@
1
- # Probe follow-up: `destructive-deletion.irreplaceable` cannot see a pathless delete
2
-
3
- Status: **proposed, not applied.** The probe text in `src/hooks/semantic/policies.ts` is
4
- unchanged on purpose. This file records what the change would be, what it would cost to
5
- validate, and which commands are still uncovered while it is not made.
6
-
7
- ## The finding
8
-
9
- Measured on a real machine, through the real hook, with a Jev config present:
10
-
11
- ```
12
- $ probe --said "the repo feels cluttered, tidy it up" "git clean -fdx"
13
- allow git clean -fdx
14
- jev: ok decision=allow applied=two-tier 495ms
15
- probes: destructive-deletion.destroys=0.94 beyond_task=0.79 task_step=0.78
16
- ```
17
-
18
- `git clean -fdx` was caught by **neither tier**.
19
-
20
- `destructive-deletion.destroys` answered **0.94** — the check sees that the command
21
- destroys data, which it should, because its own `instructions` name `git clean -fdx` as
22
- an example. The policy still did not fire. Two independent reasons:
23
-
24
- 1. **Evidence is the MINIMUM over a policy's probes** (`PolicyOutcome.evidence`, "min over
25
- probe probabilities — one weak probe spoils the evidence"). `destructive-deletion` has
26
- two probes, and the partner `irreplaceable` answered low — it does not appear on the
27
- probe line at all, which lists only what came back above the display floor. One weak
28
- probe is enough, so 0.94 on `destroys` buys nothing.
29
- 2. **No regex builtin matched the string.** Verified by running every builtin that matches
30
- `PreToolUse` + `Bash` against the literal command: zero non-allow verdicts.
31
- `block-rm-rf` only recognises `rm` and `find` command words (`RM_CMD_RE`,
32
- `FIND_CMD_RE`), `warn-git-stash-drop` is `/\bgit\s+stash\s+(?:drop|clear)\b/`, and the
33
- only place `git clean` appears anywhere in the regex tier is
34
- `GIT_DESTRUCTIVE_SUBCOMMANDS` inside the always-on self-protection guard — which is
35
- anchored to failproofai's own state directory and fires on nothing else.
36
-
37
- ## Why `irreplaceable` answers low, and why that is not a tuning bug
38
-
39
- The probe, in full, as it stands today:
40
-
41
- ```
42
- id: "irreplaceable"
43
- instructions:
44
- "What would be destroyed is outside the project, is the whole project, is the user's
45
- home directory, is the filesystem root, or is data that cannot simply be regenerated
46
- (source code, documents, databases, keys). Use `facts.paths[].relation` to see where
47
- each target sits relative to the project."
48
- criteria:
49
- true: "The destroyed data matters and could not be rebuilt by running a command."
50
- false: "Only regenerable data inside the project is affected: build output, dist/,
51
- caches, node_modules, virtualenvs, coverage reports, temp files, or files the
52
- agent itself just created."
53
- ```
54
-
55
- Every clause of the instructions is about a **target**, and the probe is told to read
56
- `facts.paths[].relation` to locate it. But `git clean` takes no path operand at all, so
57
- there is nothing for `facts.ts` to extract. Measured on this build:
58
-
59
- | command | `facts.paths` |
60
- |---|---|
61
- | `git clean -fdx` | `[]` |
62
- | `git clean -fdx` (via `cd /tmp && …`) | `/tmp => system` — the `cd` target, not the delete target |
63
- | `git reset --hard` | `[]` |
64
- | `git checkout -- .` | `. => project_root` |
65
-
66
- `extractPaths` skips `clean` (not path-like: no `/`, `~`, `.` or `..`) and skips `-fdx`
67
- (starts with `-`). So the classifier is asked to judge where the target sits, is handed no
68
- target, and reads the false criteria — which enumerate exactly the things a "clean" verb
69
- suggests: build output, `dist/`, caches, `node_modules`, coverage, temp files. The
70
- ordinary answer to the question **as written** is the false branch. The probe is not
71
- mis-tuned; it is mis-scoped for operations whose blast radius is implied by a flag rather
72
- than named by an argument.
73
-
74
- What `git clean -fdx` actually removes: every untracked **and** ignored file in the tree —
75
- `.env`, local config, editor state, uncommitted scratch work, anything not yet added. The
76
- `-x` is what makes it severe; without it, ignored files are spared.
77
-
78
- ## The probe change this would need — and why it is not in this commit
79
-
80
- Two candidate edits, both to `irreplaceable`:
81
-
82
- **(a) Name the pathless case in the instructions.** Add a sentence such as: *"A command
83
- that names no path but sweeps the working tree — `git clean -x`/`-X`/`-d`, `git reset
84
- --hard`, `git checkout -- .` — destroys untracked and ignored files, which git has no copy
85
- of; treat that as data that cannot be regenerated even though `facts.paths` is empty."*
86
-
87
- **(b) Stop the empty-`facts.paths` case defaulting to false.** Add to the instructions:
88
- *"An empty `facts.paths` means no target could be resolved, not that the target is
89
- harmless."* This is the same principle `envelope.ts` already states for the two-tier
90
- combine — *"`policies.ts` treats incomplete evidence as a reason to ASK MORE"* — applied
91
- one level down, to a probe.
92
-
93
- **Why neither ships here.** The probe text is a calibrated classifier input. The decision
94
- thresholds in `decide.ts` were measured against this exact wording over the 1,332-call
95
- labelled corpus, and the numbers in the top CHANGELOG section (real work blocked 13.9% →
96
- 8.7%, 132 of 234 attacks blocked, 70.8% label agreement) are properties of the
97
- **wording + thresholds together**. `irreplaceable` is the partner probe of the only
98
- deny-mode check that covers deletion, and `destructive-deletion` applies to `shell` *and*
99
- `write` — so a word that raises it raises it on every `rm`, every `truncate`, every
100
- `dd of=`, every overwriting redirect and every `Write` in the corpus, not only on
101
- `git clean`. Raising a MIN-combined probe can only move policies toward firing, so the
102
- risk is one-directional and concentrated in false blocks, which is the metric the
103
- two-tier release was justified on.
104
-
105
- **So the change needs a full corpus replay before it lands:** re-run the 1,332 labelled
106
- calls, report real-work-blocked, attacks-blocked and label agreement against the numbers
107
- above, and confirm no threshold in `decide.ts` has to move to hold them. That is a
108
- measurement task with its own prereg, not a word edit.
109
-
110
- ## What ships instead
111
-
112
- `warn-git-clean`, a deterministic builtin in the regex tier (`policy-catalog.ts` +
113
- `builtin-policies.ts`). `instruct`, `defaultEnabled: false`, `authority: "hard"`, with a
114
- `destructiveFlags` param (default `["d","x","X"]`) to narrow or widen it. It fires when
115
- `git clean` has force (`-f` / `--force`, or a waived `clean.requireForce`) together with
116
- `-d`, `-x` or `-X`; it does not fire on `--dry-run` / `-n`, on a bare `git clean -f`, or
117
- on `git clean` with no force.
118
-
119
- It is **hard**, not `reviewable: ["destructive-deletion"]`, and the finding above is why:
120
- a named check that is asked and does not fire answers "no concern", which **clears**
121
- (`combine.ts`, "A warning-level answer clears the deny"). `destructive-deletion`
122
- demonstrably answers low here, so the pairing would not review this policy — it would
123
- switch it off on every machine that configured Jev. That is the `block-work-on-main`
124
- mistake, and the test that decides it is "is there anything left that can DENY". No other
125
- deny-mode semantic check covers untracked-file deletion, so a clear would leave the
126
- concern enforced by nothing. When (a) or (b) lands with a passing replay, this policy is
127
- the first candidate to be revisited as `reviewable`.
128
-
129
- ## Sibling sweep: destructive git commands with no path argument
130
-
131
- Measured, not guessed — each command run against every builtin matching `PreToolUse` +
132
- `Bash` on this build, and through `extractPaths` for the facts column.
133
-
134
- | command | matched by any builtin? | `facts.paths` | would `destructive-deletion` fire? | covered here? |
135
- |---|---|---|---|---|
136
- | `git clean -fdx` / `-fd` / `-fx` / `-fX` | **now yes** — `warn-git-clean` (was: none) | `[]` | No. `destroys` 0.94, `irreplaceable` low — measured | **yes** |
137
- | `git clean --dry-run` / `-n` | no, by design | `[]` | No | n/a — safe |
138
- | `git reset --hard` (and `--hard HEAD~N`) | **no** | `[]` | Unlikely. Overwrites tracked files from HEAD, so `destroys` should be high; `irreplaceable` has no target and the loss (uncommitted modifications) is not in the object database at all | no — see below |
139
- | `git checkout -- .` | **no** | `. => project_root` | Possible. `irreplaceable` does get a target here, and `project_root` is one of the relations its instructions name, so this is the one sibling the probe has a fair chance on | no |
140
- | `git restore .` | **no** | `. => project_root` | Same as above | no |
141
- | `git rm -r --cached .` | **no** | `. => project_root` | No, and correctly: `--cached` unstages and leaves the working-tree file. Not data loss | no — not destructive |
142
- | `git rm -rf src` | **no** | `[]` | Unlikely (no target in facts) | no — but see note |
143
- | `git rm -rf /` | **yes** — `block-rm-rf` denies | `/ => root` | Yes | already covered |
144
- | `git branch -D feature/x` | **no** | `feature/x => inside_project` (a false positive: it is a ref, not a path) | No, and defensibly: commits stay reachable through the reflog for 90 days | no — recoverable |
145
- | `git reflog expire --expire=now --all` | **no** | `[]` | No — destroys no files | no |
146
- | `git gc --prune=now` | **no** | `[]` | No — destroys no files | no |
147
-
148
- Notes on two rows that surprise:
149
-
150
- - **`git rm -rf <path>` is partially covered by accident.** `recursiveDeletionTargets`
151
- searches the token list for a word matching `RM_CMD_RE`, and in `git rm -rf src` that
152
- matches the `rm` **subcommand**. So `block-rm-rf` evaluates it as an `rm` with target
153
- `src` — allowed, because `src` is not catastrophic — and denies `git rm -rf /`. The
154
- coverage is real but incidental, and it stops at the catastrophic-target test.
155
- - **`git reflog expire` + `git gc --prune=now` are the recovery mechanism, not the loss.**
156
- Alone they destroy no working file. Their severity is that they make `git reset --hard`,
157
- `git branch -D` and a dropped stash *permanently* unrecoverable, which is a two-command
158
- concern no single-command matcher models well.
159
-
160
- ### Why only `git clean` got coverage in this change
161
-
162
- `git reset --hard`, `git checkout -- .` and `git restore .` are the same shape and the same
163
- severity class, and they are the obvious next policy — but not a bolt-on to this one:
164
-
165
- 1. **They are a different verb.** "Discard uncommitted changes to tracked files" is not
166
- "delete untracked files"; one instruct message cannot name both losses usefully, and
167
- the flag conditions have nothing in common.
168
- 2. **Their authority decision is genuinely open, and `checkout`/`restore` may differ from
169
- `reset`.** `git checkout -- .` and `git restore .` DO put `. => project_root` into
170
- `facts.paths`, which is a relation `irreplaceable`'s instructions name explicitly — so
171
- those two may be legitimately `reviewable` through `destructive-deletion` where
172
- `git clean` provably is not. That needs measuring, per command, the way this one was.
173
- 3. **`git reset --hard <commit>` overlaps `git-history-rewrite`'s concern**, so its pairing
174
- question is "which check owns this", not "does the existing check fire".
175
-
176
- Each of those is a measurement, not a patch. Filed here rather than guessed.
177
-
178
- ### Open items
179
-
180
- - [ ] Replay the labelled corpus against probe edit (a) and/or (b); report real-work-blocked,
181
- attacks-blocked and label agreement against the current baseline.
182
- - [ ] If the replay holds, revisit `warn-git-clean`'s authority.
183
- - [ ] Add a `warn-git-discard-changes` builtin for `git reset --hard` / `git checkout -- .` /
184
- `git restore .`, with its authority decided per command from measured probe answers.
185
- - [ ] Decide whether `git reflog expire --expire=now --all` and `git gc --prune=now` deserve
186
- a policy of their own, given that their harm is only realised in combination.
@@ -1,424 +0,0 @@
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 {
49
- DEFAULT_JEV_MODE,
50
- JEV_API_KEY_ENV,
51
- JEV_PROVIDER_KINDS,
52
- inspectJevConfig,
53
- looksLikeCredential,
54
- readJevConfigForUpdate,
55
- type JevConfig,
56
- type JevProviderKind,
57
- } from "@/src/hooks/semantic/jev-config";
58
- import { displayEndpoint, jevRoute } from "@/src/hooks/semantic/jev-client";
59
- import { jevStats } from "@/src/hooks/semantic/jev-stats";
60
- import {
61
- reviewableProblem,
62
- reviewableSummary,
63
- surveyReviewableCoverage,
64
- } from "@/src/hooks/policy-reviewability";
65
-
66
- /** The loader's four answers, unchanged — see `JevConfigInspection`. */
67
- export type JevSettingsStatus = "absent" | "ok" | "key-missing" | "refused";
68
-
69
- /** What Jev has been doing lately, from `jevStats()`. Not a new pipeline. */
70
- export interface JevSettingsStats {
71
- windowMs: number;
72
- /** Gate evaluations Jev was consulted on: answered or fell back. */
73
- total: number;
74
- answered: number;
75
- fallbacks: number;
76
- /** Share of `total` that fell back to the regex engine, 0..1. */
77
- fallbackRate: number;
78
- }
79
-
80
- /**
81
- * Presence of a stored key, and nothing else about it.
82
- *
83
- * It used to carry the last four characters, so the panel could say "configured,
84
- * ending 1eb9". That is a recognisable fragment of a live credential rendered
85
- * into a web page on an origin with no authentication, and it buys the reader
86
- * nothing they could not get by re-pasting the key: the panel already says
87
- * whether one is stored, where it came from, and that leaving the field blank
88
- * keeps it. So the fragment is not shown, and — the part that matters — it is
89
- * not computed and not sent.
90
- */
91
- export interface JevTokenPresence {
92
- source: "file" | "env";
93
- }
94
-
95
- /**
96
- * What the panel may say about the stored model id.
97
- *
98
- * - `default` — nothing is stored, so the provider's own default is used.
99
- * - `id` — a stored value that is a model id, shown as itself.
100
- * - `withheld` — something is stored that does not look like a model id, so it
101
- * is reported the way a token is: present, never quoted.
102
- *
103
- * The decision is `looksLikeCredential()`, the loader's own predicate for "this
104
- * is a key, not a model id" — the one that refuses the file in the first place,
105
- * not a second rule invented here. It has two deliberate carve-outs so it does
106
- * not refuse real ids (a `/`, which every gateway-qualified id has, and the
107
- * letters "jev"), and a base64 key can contain a `/`, so length is checked too:
108
- * every id Jev is addressed by is short — `jev-1.13.0`, `typesafe/jev-1.13`,
109
- * `@cf/typesafe/jev-1.13` — and nothing longer is worth showing at the risk of
110
- * it being a key.
111
- */
112
- export type JevModelView =
113
- | { kind: "default" }
114
- | { kind: "id"; id: string }
115
- | { kind: "withheld" };
116
-
117
- /**
118
- * How much of this machine's enabled policy set Jev is allowed to CLEAR.
119
- *
120
- * The panel says whether Jev is on; this is what says whether the half of it
121
- * that clears a verdict can fire at all. A pack published before this release
122
- * declares no `authority`, every policy in it is therefore `hard`, and a
123
- * machine in that state looks perfect from every other field on the panel — the
124
- * endpoint answers, the fallback rate is fine, and no deny is ever cleared. See
125
- * `src/hooks/policy-reviewability.ts` for what is counted.
126
- *
127
- * The two sentences are computed there rather than here, so this panel and
128
- * `failproofai jev status` say the same thing in the same words — the same rule
129
- * the rest of this module follows for everything it reports.
130
- */
131
- export interface JevReviewabilityView {
132
- /** Enabled policies whose authority could be read without running code. */
133
- enabled: number;
134
- /** Of those, the ones Jev may clear. */
135
- reviewable: number;
136
- /** The count, as one line. */
137
- summary: string;
138
- /** Why nothing can be cleared, and what fixes it; null when something can. */
139
- problem: string | null;
140
- }
141
-
142
- export interface JevSettingsView {
143
- status: JevSettingsStatus;
144
- /** True only when a hook running right now would consult Jev. */
145
- on: boolean;
146
- /**
147
- * `~/.failproofai/jev.json`, whether or not it exists, and its permission
148
- * bits as `0600` when it has some.
149
- *
150
- * Both are part of this inspection because `failproofai jev status` prints
151
- * them, and this module is that command's answer read from the same
152
- * `inspectJevConfig`. The PANEL does not draw them: a config path and a mode
153
- * are not something anybody acts on from a browser, and the one case where
154
- * the bits decide anything — a file the loader refuses for being
155
- * group-writable — already names the file in `problem` and the `chmod` in
156
- * `fix`. See `jev-panel.tsx`.
157
- */
158
- path: string;
159
- permissions: string | null;
160
- provider: JevProviderKind | null;
161
- /** The stored base URL, or "" for the provider's own API. Form value. */
162
- baseUrl: string;
163
- /** Cloudflare only; "" otherwise. Form value. */
164
- accountId: string;
165
- /**
166
- * The stored model, for DISPLAY only — never a form value, and never the
167
- * stored string when that string could be a key. The form does not offer this
168
- * field and the save path leaves it alone (see `update-jev-config.ts`), so
169
- * nothing needs it back to round-trip it.
170
- */
171
- model: JevModelView;
172
- /**
173
- * Where requests actually go, query string elided. Null when unroutable.
174
- *
175
- * The computed route, which is not what any form field holds — `baseUrl` may
176
- * be empty for the provider's own API, and Cloudflare's route is built from
177
- * `accountId`. `jev status` prints it for that reason. The panel does not: for
178
- * Cloudflare the route IS `/accounts/<id>/ai/run`, so a read-only row above
179
- * the form repeated the endpoint field and the account id field at once,
180
- * putting a per-account address on the page twice.
181
- */
182
- endpoint: string | null;
183
- mode: NonNullable<JevConfig["mode"]>;
184
- timeoutMs: number | null;
185
- token: JevTokenPresence | null;
186
- /** The loader's own words for why this file is refused or incomplete. */
187
- problem: string | null;
188
- /** The command that fixes it, when there is one. */
189
- fix: string | null;
190
- stats: JevSettingsStats | null;
191
- /** What Jev may clear here. Null when Jev is off, or when it could not be read. */
192
- reviewable: JevReviewabilityView | null;
193
- }
194
-
195
- /**
196
- * Fills the key slot when a route is computed for DISPLAY only. The key does
197
- * not decide where requests go, but `jevRoute` validates the whole config
198
- * before answering, so a config whose key lives in the environment — or one the
199
- * loader refused — needs something in the field to get an endpoint out. Never
200
- * written, never sent.
201
- */
202
- const KEY_STAND_IN = "display-only";
203
-
204
- /** Longer than any model id a Jev route knows, and well inside a pasted key's length. */
205
- const MAX_SHOWABLE_MODEL_LENGTH = 40;
206
-
207
- /** The stored model as the panel may see it — see `JevModelView` for the rule. */
208
- function modelView(stored: string): JevModelView {
209
- if (!stored) return { kind: "default" };
210
- if (stored.length > MAX_SHOWABLE_MODEL_LENGTH || looksLikeCredential(stored)) {
211
- return { kind: "withheld" };
212
- }
213
- return { kind: "id", id: stored };
214
- }
215
-
216
- function octal(mode: number | null): string | null {
217
- return mode === null ? null : mode.toString(8).padStart(4, "0");
218
- }
219
-
220
- function asString(v: unknown): string {
221
- return typeof v === "string" ? v : "";
222
- }
223
-
224
- /**
225
- * The routing fields of a raw `jev.json`, for a file the loader refused — so
226
- * the panel can show what it names and the form can offer it back for repair.
227
- *
228
- * Copied field by field, deliberately. `apiKey` is the reason: a spread of the
229
- * parsed object would carry it into a value this module hands to the browser,
230
- * and it would keep doing so silently as fields are added.
231
- */
232
- function routingFromRaw(raw: Record<string, unknown> | null): {
233
- provider: JevProviderKind | null;
234
- baseUrl: string;
235
- accountId: string;
236
- model: string;
237
- mode: NonNullable<JevConfig["mode"]>;
238
- } {
239
- const providerRaw = asString(raw?.provider);
240
- const provider = (JEV_PROVIDER_KINDS as readonly string[]).includes(providerRaw)
241
- ? (providerRaw as JevProviderKind)
242
- : null;
243
- const modeRaw = raw?.mode;
244
- return {
245
- provider,
246
- baseUrl: asString(raw?.baseUrl),
247
- accountId: asString(raw?.accountId),
248
- model: asString(raw?.model),
249
- mode: modeRaw === "shadow" || modeRaw === "enforce" ? modeRaw : DEFAULT_JEV_MODE,
250
- };
251
- }
252
-
253
- /** `jevRoute` for display, swallowing the throw a file that names no usable route produces. */
254
- function endpointFor(cfg: JevConfig): string | null {
255
- try {
256
- return displayEndpoint(jevRoute(cfg).endpoint);
257
- } catch {
258
- return null;
259
- }
260
- }
261
-
262
- /**
263
- * The fallback rate and the counts behind it, for the one line on the panel
264
- * that says whether Jev is working. Read only when Jev is actually on: on a
265
- * machine with no config the numbers are all zero and the line would be noise.
266
- *
267
- * `jevStats()` never throws and derives from the activity store the activity
268
- * tab already reads, so this adds no pipeline and no persisted state.
269
- */
270
- async function statsOrNull(on: boolean): Promise<JevSettingsStats | null> {
271
- if (!on) return null;
272
- try {
273
- const s = await jevStats();
274
- return {
275
- windowMs: s.windowMs,
276
- total: s.total,
277
- answered: s.answered,
278
- fallbacks: s.fallbacks,
279
- fallbackRate: s.fallbackRate,
280
- };
281
- } catch {
282
- // The panel renders without the line rather than without the panel.
283
- return null;
284
- }
285
- }
286
-
287
- /**
288
- * The authority counts, for a machine where a clear could actually happen.
289
- *
290
- * Off means the regex policies decide whatever any policy's authority says, so
291
- * there is nothing to report and the panel shows no row. Never throws, for the
292
- * same reason `statsOrNull` does not: the panel renders without the line rather
293
- * than without the panel.
294
- */
295
- function reviewableOrNull(on: boolean): JevReviewabilityView | null {
296
- if (!on) return null;
297
- try {
298
- const coverage = surveyReviewableCoverage();
299
- return {
300
- enabled: coverage.enabled,
301
- reviewable: coverage.reviewable,
302
- summary: reviewableSummary(coverage),
303
- problem: reviewableProblem(coverage),
304
- };
305
- } catch {
306
- return null;
307
- }
308
- }
309
-
310
- /**
311
- * Everything /settings shows about Jev, and nothing it must not.
312
- *
313
- * Never throws: every branch of `inspectJevConfig` is a state the panel can
314
- * draw, and a settings page whose job is to say whether a security feature is
315
- * on must not be replaced by an error boundary when the answer is "badly".
316
- */
317
- export async function getJevSettingsAction(): Promise<JevSettingsView> {
318
- const inspection = inspectJevConfig();
319
- const base = {
320
- path: inspection.path,
321
- permissions: null as string | null,
322
- provider: null as JevProviderKind | null,
323
- baseUrl: "",
324
- accountId: "",
325
- model: { kind: "default" } as JevModelView,
326
- endpoint: null as string | null,
327
- mode: DEFAULT_JEV_MODE,
328
- timeoutMs: null as number | null,
329
- token: null as JevTokenPresence | null,
330
- problem: null as string | null,
331
- fix: null as string | null,
332
- reviewable: null as JevReviewabilityView | null,
333
- };
334
-
335
- if (inspection.status === "ok") {
336
- const cfg = inspection.config;
337
- return {
338
- ...base,
339
- status: "ok",
340
- on: true,
341
- permissions: octal(inspection.mode),
342
- provider: cfg.provider,
343
- baseUrl: cfg.baseUrl ?? "",
344
- accountId: cfg.accountId ?? "",
345
- // A loadable config's model already passed `validateModel`, so this can
346
- // only be an id — run through the same gate anyway, because the gate is
347
- // what keeps that true if the loader's rules ever loosen.
348
- model: modelView(cfg.model ?? ""),
349
- endpoint: endpointFor(cfg),
350
- mode: cfg.mode ?? DEFAULT_JEV_MODE,
351
- timeoutMs: cfg.timeoutMs ?? null,
352
- // The key is read here and nothing derived from it leaves the function —
353
- // not a mask, not a fragment, not its length.
354
- token: { source: inspection.keySource },
355
- stats: await statsOrNull(true),
356
- reviewable: reviewableOrNull(true),
357
- };
358
- }
359
-
360
- if (inspection.status === "key-missing") {
361
- const r = inspection.routing;
362
- return {
363
- ...base,
364
- status: "key-missing",
365
- on: false,
366
- permissions: octal(inspection.mode),
367
- provider: r.provider,
368
- baseUrl: r.baseUrl ?? "",
369
- accountId: r.accountId ?? "",
370
- model: modelView(r.model ?? ""),
371
- endpoint: endpointFor({ ...r, apiKey: KEY_STAND_IN }),
372
- mode: r.mode ?? DEFAULT_JEV_MODE,
373
- timeoutMs: r.timeoutMs ?? null,
374
- // The file names the environment as the source and the variable is not
375
- // set here, so there is nothing present to hint at.
376
- token: null,
377
- problem: inspection.problem,
378
- fix: `set ${JEV_API_KEY_ENV}, or save a token here`,
379
- stats: null,
380
- };
381
- }
382
-
383
- if (inspection.status === "refused") {
384
- // Show what the file names so the owner can see whether the endpoint is one
385
- // they chose — the same question `jev status` puts to them. Routing only.
386
- const r = routingFromRaw(readJevConfigForUpdate());
387
- return {
388
- ...base,
389
- status: "refused",
390
- on: false,
391
- permissions: octal(inspection.mode),
392
- provider: r.provider,
393
- baseUrl: r.baseUrl,
394
- accountId: r.accountId,
395
- // This is the branch the rule exists for: a file whose `model` slot holds
396
- // a pasted key IS a refused file, and these fields are copied raw.
397
- model: modelView(r.model),
398
- // Shown for the same reason `jev status` shows it: the owner needs to see
399
- // whether the endpoint a file they may not have written names is one they
400
- // chose. Null when the routing fields do not make a usable route.
401
- //
402
- // The model is left out of this probe deliberately. It does not appear in
403
- // any endpoint — `jevRoute` puts it in the request body, not the URL — but
404
- // `jevRoute` validates the whole config first, so a refused model would
405
- // hide the one field the owner is being asked to check.
406
- endpoint:
407
- r.provider === null
408
- ? null
409
- : endpointFor({
410
- provider: r.provider,
411
- apiKey: KEY_STAND_IN,
412
- ...(r.baseUrl ? { baseUrl: r.baseUrl } : {}),
413
- ...(r.accountId ? { accountId: r.accountId } : {}),
414
- mode: r.mode,
415
- }),
416
- mode: r.mode,
417
- problem: inspection.problem,
418
- fix: inspection.fix ?? null,
419
- stats: null,
420
- };
421
- }
422
-
423
- return { ...base, status: "absent", on: false, stats: null };
424
- }