failproofai 1.0.11 → 1.0.12-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +23 -23
  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/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +3 -3
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/invite/route.js +5 -6
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js +24 -24
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/status/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +4 -5
  32. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +6 -7
  34. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route.js +6 -7
  36. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route.js +5 -6
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -2
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  42. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/audit/page.js +4 -4
  44. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/index.html +1 -1
  47. package/.next/standalone/.next/server/app/index.rsc +3 -3
  48. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  49. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  50. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  51. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  52. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  53. package/.next/standalone/.next/server/app/page.js +2 -2
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  57. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  58. package/.next/standalone/.next/server/app/policies/page.js +7 -7
  59. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  62. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page.js +7 -7
  64. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +8 -8
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  75. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  79. package/.next/standalone/.next/server/app/settings/page.js +10 -10
  80. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  82. package/.next/standalone/.next/server/chunks/{[externals]__0qkp92tjfvw8r._.js → [externals]__0vt9i6fqinjne._.js} +1 -1
  83. package/.next/standalone/.next/server/chunks/[externals]__1x24dzcbbq6-g._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02vous7mpi2xm._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03i5o4a840guz._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0__a_je8fsn65._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_hsnj_a2mvl_._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1izwqj_ij_9x_._.js → [root-of-the-server]__0f_4g-vazrvxv._.js} +1 -1
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l-oi4gdtmlo5._.js +3 -0
  90. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0swiyzbmvja2d._.js → [root-of-the-server]__0t9ufnv5hi2hq._.js} +5 -5
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wg9dk8x8bnau._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12kq3x-h7t7i1._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a5zkndjwlc13._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1r7kbfkiw2xmu._.js → [root-of-the-server]__1aaw59g-5uty7._.js} +12 -12
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hqpg6vgmfexv._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/_09dz7xva4ce_2._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/_0hf5q4xzyg40j._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/_19011-u44143j._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/_1vslkoe35wok6._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/{_1ixl-kxq_ctsa._.js → _1y590mjzpkbos._.js} +2 -2
  104. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +5 -0
  106. package/.next/standalone/.next/server/chunks/src_hooks_builtin-policies_ts_1yq5ybgx1v98e._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3h94wnn._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/src_hooks_daemon-client_ts_0il_yu1zc0291._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn_4twyh._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01j58mdcdw0vn._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +3 -0
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_a8z2ldp2v3n._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0vcyntk4snf0f._.js → [root-of-the-server]__0b78b0j8iyv6l._.js} +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ffu2-38y03ic._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g8b0kyorya-t._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1_xbg2kx30wp-._.js → [root-of-the-server]__0naevg4bcoh54._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__075uro8t92s0_._.js → [root-of-the-server]__0pwj2h2efe5f-._.js} +3 -3
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0zn1d4blhd3bu._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17r4zrwanhp6r._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1lnfv02ucky7m._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mc9-zlnh656_._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1s2-ck3k3rsoo._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1l1qgu5mfapkh._.js → [root-of-the-server]__1vx7o8ukefudh._.js} +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/_1_ozgifqv1ssi._.js +1 -1
  137. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifpkowwub._.js → _1a3tubds7s7ow._.js} +10 -9
  138. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  140. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1f_ymmaonkksc.js → node_modules_html-to-image_es_index_1i8oid1gc7s1f.js} +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9ddabn3_._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +5 -0
  149. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +5 -0
  150. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4l1tzrm._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/ssr/src_hooks_daemon-client_ts_0k41_i7jg6ndv._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  153. package/.next/standalone/.next/server/middleware-build-manifest.js +23 -23
  154. package/.next/standalone/.next/server/middleware.js +2 -2
  155. package/.next/standalone/.next/server/pages/404.html +1 -1
  156. package/.next/standalone/.next/server/pages/500.html +1 -1
  157. package/.next/standalone/.next/server/preview-props.json +3 -3
  158. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  159. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  160. package/.next/standalone/.next/static/chunks/{0frxv4_m-m2u5.js → 063al_q720fkj.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/0_zfad4co-t2z.js +1 -0
  162. package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +1 -0
  163. package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +1 -0
  164. package/.next/standalone/.next/static/chunks/{37stoe-_yd852.js → 18433qz-8njv3.js} +1 -1
  165. package/.next/standalone/.next/static/chunks/{1kwj8uqxuv85t.js → 1b32-afqacaen.js} +1 -1
  166. package/.next/standalone/.next/static/chunks/{44a7k2_62qc-9.js → 1p-hsi58i-6h0.js} +2 -2
  167. package/.next/standalone/.next/static/chunks/{302llgh97ce6w.js → 2-tf6_7u623kd.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{1_c0dc7zqj1n7.js → 24w3x2da9sk49.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/2tjfp9dirxnku.js +1 -0
  170. package/.next/standalone/.next/static/chunks/{16z6_d7sp61jm.js → 3-pku9qwv1yfu.js} +1 -1
  171. package/.next/standalone/.next/static/chunks/{15rm15u5a6dua.js → 42e8zab314654.js} +1 -1
  172. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +41 -0
  173. package/.next/standalone/fp-cloud-cli/README.md +42 -1
  174. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +117 -12
  175. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +285 -19
  176. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +84 -18
  177. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +170 -6
  178. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +89 -6
  179. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +259 -21
  180. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +8 -5
  181. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +2 -2
  182. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +17 -9
  183. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +692 -0
  184. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +46 -0
  185. package/.next/standalone/fp-cloud-cli/tests/test_output.py +23 -0
  186. package/.next/standalone/fp-cloud-cli/uv.lock +6 -6
  187. package/.next/standalone/hermes-plugin/README.md +6 -4
  188. package/.next/standalone/hermes-plugin/__init__.py +16 -0
  189. package/.next/standalone/hermes-plugin/client.py +5 -1
  190. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  191. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  192. package/.next/standalone/node_modules/@opentelemetry/api/build/src/api/context.js +72 -0
  193. package/.next/standalone/node_modules/@opentelemetry/api/build/src/api/diag.js +84 -0
  194. package/.next/standalone/node_modules/@opentelemetry/api/build/src/api/metrics.js +50 -0
  195. package/.next/standalone/node_modules/@opentelemetry/api/build/src/api/propagation.js +80 -0
  196. package/.next/standalone/node_modules/@opentelemetry/api/build/src/api/trace.js +70 -0
  197. package/.next/standalone/node_modules/@opentelemetry/api/build/src/baggage/context-helpers.js +52 -0
  198. package/.next/standalone/node_modules/@opentelemetry/api/build/src/baggage/internal/baggage-impl.js +44 -0
  199. package/.next/standalone/node_modules/@opentelemetry/api/build/src/baggage/internal/symbol.js +12 -0
  200. package/.next/standalone/node_modules/@opentelemetry/api/build/src/baggage/utils.js +41 -0
  201. package/.next/standalone/node_modules/@opentelemetry/api/build/src/context/NoopContextManager.js +27 -0
  202. package/.next/standalone/node_modules/@opentelemetry/api/build/src/context/context.js +52 -0
  203. package/.next/standalone/node_modules/@opentelemetry/api/build/src/context-api.js +16 -0
  204. package/.next/standalone/node_modules/@opentelemetry/api/build/src/diag/ComponentLogger.js +47 -0
  205. package/.next/standalone/node_modules/@opentelemetry/api/build/src/diag/consoleLogger.js +73 -0
  206. package/.next/standalone/node_modules/@opentelemetry/api/build/src/diag/internal/logLevelLogger.js +34 -0
  207. package/.next/standalone/node_modules/@opentelemetry/api/build/src/diag/types.js +33 -0
  208. package/.next/standalone/node_modules/@opentelemetry/api/build/src/diag-api.js +20 -0
  209. package/.next/standalone/node_modules/@opentelemetry/api/build/src/index.js +71 -0
  210. package/.next/standalone/node_modules/@opentelemetry/api/build/src/internal/global-utils.js +60 -0
  211. package/.next/standalone/node_modules/@opentelemetry/api/build/src/internal/semver.js +111 -0
  212. package/.next/standalone/node_modules/@opentelemetry/api/build/src/metrics/Metric.js +18 -0
  213. package/.next/standalone/node_modules/@opentelemetry/api/build/src/metrics/NoopMeter.js +118 -0
  214. package/.next/standalone/node_modules/@opentelemetry/api/build/src/metrics/NoopMeterProvider.js +20 -0
  215. package/.next/standalone/node_modules/@opentelemetry/api/build/src/metrics-api.js +17 -0
  216. package/.next/standalone/node_modules/@opentelemetry/api/build/src/propagation/NoopTextMapPropagator.js +23 -0
  217. package/.next/standalone/node_modules/@opentelemetry/api/build/src/propagation/TextMapPropagator.js +36 -0
  218. package/.next/standalone/node_modules/@opentelemetry/api/build/src/propagation-api.js +17 -0
  219. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/NonRecordingSpan.js +58 -0
  220. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/NoopTracer.js +68 -0
  221. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/NoopTracerProvider.js +21 -0
  222. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/ProxyTracer.js +46 -0
  223. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/ProxyTracerProvider.js +46 -0
  224. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/SamplingResult.js +33 -0
  225. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/context-utils.js +71 -0
  226. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/internal/tracestate-impl.js +94 -0
  227. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/internal/tracestate-validators.js +35 -0
  228. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/internal/utils.js +16 -0
  229. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/invalid-span-constants.js +25 -0
  230. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/span_kind.js +38 -0
  231. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/spancontext-utils.js +66 -0
  232. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/status.js +29 -0
  233. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace/trace_flags.js +18 -0
  234. package/.next/standalone/node_modules/@opentelemetry/api/build/src/trace-api.js +17 -0
  235. package/.next/standalone/node_modules/@opentelemetry/api/build/src/version.js +10 -0
  236. package/.next/standalone/node_modules/@opentelemetry/api/package.json +96 -0
  237. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  238. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +2 -2
  239. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +2 -2
  240. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +1 -1
  241. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  242. package/.next/standalone/node_modules/next/dist/server/app-render/async-local-storage.js +1 -1
  243. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  244. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  245. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  246. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  247. package/.next/standalone/node_modules/next/dist/server/lib/generate-agent-files.js +3 -3
  248. package/.next/standalone/node_modules/next/dist/server/lib/router-server.js +1 -1
  249. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  250. package/.next/standalone/node_modules/next/dist/server/lib/trace/tracer.js +1 -1
  251. package/.next/standalone/node_modules/next/dist/server/typescript/rules/config.js +2 -2
  252. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +1 -1
  253. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  254. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  255. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  256. package/.next/standalone/node_modules/next/package.json +11 -11
  257. package/.next/standalone/package.json +11 -11
  258. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  259. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  260. package/.next/standalone/sdk/python/pyproject.toml +19 -0
  261. package/.next/standalone/sdk/python/uv.lock +520 -301
  262. package/.next/standalone/sdk/typescript/CHANGELOG.md +13 -0
  263. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +4 -13
  264. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +2 -1
  265. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +4 -13
  266. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +3 -0
  267. package/.next/standalone/sdk/typescript/package-lock.json +178 -162
  268. package/.next/standalone/sdk/typescript/package.json +1 -1
  269. package/.next/standalone/sdk/typescript/src/version.ts +1 -1
  270. package/.next/standalone/server.js +1 -1
  271. package/bin/failproofai.mjs +17 -1
  272. package/dist/cli.mjs +2866 -1124
  273. package/dist/worker.mjs +1450 -145
  274. package/hermes-plugin/README.md +6 -4
  275. package/hermes-plugin/__init__.py +16 -0
  276. package/hermes-plugin/client.py +5 -1
  277. package/hermes-plugin/plugin.yaml +1 -1
  278. package/package.json +11 -11
  279. package/src/hooks/agent-roster.ts +172 -0
  280. package/src/hooks/agent-targets.ts +60 -0
  281. package/src/hooks/cloud-connection.ts +97 -27
  282. package/src/hooks/cloud-enrollment-cli.ts +7 -4
  283. package/src/hooks/cloud-managed-policies.ts +380 -20
  284. package/src/hooks/cloud-policy-errors.ts +481 -0
  285. package/src/hooks/config-status.ts +1 -1
  286. package/src/hooks/custom-hooks-loader.ts +26 -3
  287. package/src/hooks/daemon-client.ts +9 -4
  288. package/src/hooks/daemon-service.ts +2 -1
  289. package/src/hooks/effective-reviewers.ts +22 -1
  290. package/src/hooks/fp-home.ts +6 -0
  291. package/src/hooks/handler.ts +235 -18
  292. package/src/hooks/health-probe.ts +15 -0
  293. package/src/hooks/integrations.ts +18 -11
  294. package/src/hooks/jev-cli.ts +197 -0
  295. package/src/hooks/jev-cloud-connection.ts +92 -0
  296. package/src/hooks/manager.ts +122 -16
  297. package/src/hooks/pack-failclosed.ts +2 -0
  298. package/src/hooks/pack-manifest.ts +8 -1
  299. package/src/hooks/policy-evaluator.ts +5 -1
  300. package/src/hooks/policy-registry.ts +3 -2
  301. package/src/hooks/policy-reviewability.ts +21 -5
  302. package/src/hooks/semantic/cloud-jev-health.ts +227 -0
  303. package/src/hooks/semantic/cloud-jev.ts +677 -0
  304. package/src/hooks/semantic/jev-client.ts +18 -1
  305. package/src/hooks/semantic/jev-config.ts +173 -3
  306. package/src/hooks/semantic/jev-review.ts +129 -10
  307. package/src/hooks/semantic/pack-policies.ts +2 -1
  308. package/src/hooks/semantic/types.ts +8 -0
  309. package/src/hooks/session-pause-cli.ts +1 -1
  310. package/src/hooks/worker-server.ts +5 -1
  311. package/.next/standalone/.next/server/chunks/[externals]__0hjaw70fchx66._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07l68zv0hqdiu._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0car70pylmvor._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0u3i025_setk_._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10a2f6s636yc7._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qt5u58xrcuk6._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_077n_4ip5__fu._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0mqvvxcn6v0op._.js +0 -3
  319. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_0pb7ds5g0vzgn._.js +0 -3
  320. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1gasm5xkeqmm3._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_1q1heqmkhh8l4._.js +0 -3
  322. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0uhs2t1b._.js +0 -5
  323. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7cyqlb2._.js +0 -3
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0888k4s6spfmh._.js +0 -3
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_bflu046ep5j._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_lbstb5x2t15._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0fdwgktjfhedo._.js +0 -3
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s4deza787asx._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__18qe-hu0gwqm4._.js +0 -3
  330. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_p_9xe520fex._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fmhm8_s9i4re._.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1g76awtnmimd1._.js +0 -3
  333. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pczevlpk5lxh._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/ssr/_0dc-of_bn0cim._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo55j16x._.js +0 -5
  336. package/.next/standalone/.next/static/chunks/0m-tv1y6ghhtk.js +0 -1
  337. package/.next/standalone/.next/static/chunks/0ol2gj4ntfn56.js +0 -1
  338. package/.next/standalone/.next/static/chunks/3hj5_7i3nw4zj.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3iyoapeoq_hup.js +0 -1
  340. /package/.next/standalone/.next/static/{kEFP3ybXwZdriNEQmWwlH → xq8UgF5yFxty1VWxY2hyn}/_buildManifest.js +0 -0
  341. /package/.next/standalone/.next/static/{kEFP3ybXwZdriNEQmWwlH → xq8UgF5yFxty1VWxY2hyn}/_clientMiddlewareManifest.js +0 -0
  342. /package/.next/standalone/.next/static/{kEFP3ybXwZdriNEQmWwlH → xq8UgF5yFxty1VWxY2hyn}/_ssgManifest.js +0 -0
@@ -0,0 +1,692 @@
1
+ """FailproofAI Cloud Jev / regex / both policies in `fp` (CONTRACT C4, C6).
2
+
3
+ `fp policies publish --kind regex|jev|both [--source] [--semantic]`, list/show
4
+ carrying the kind and the Jev check names, `fp fleet deploy --jev-mode`, and
5
+ machines showing the policy errors they report.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+
12
+ import httpx
13
+ import pytest
14
+ import respx
15
+
16
+ from fp_cli import client
17
+ from fp_cli.app import app
18
+ from fp_cli.enforcement import (
19
+ RefUsageError,
20
+ parse_semantic,
21
+ plan_deploy,
22
+ resolve_kind,
23
+ version_kinds,
24
+ )
25
+ from fp_cli.models import Deployment, Machine, PolicyRef, PolicyVersion
26
+
27
+ BASE = "http://dash.test"
28
+ FIXTURES = Path(__file__).resolve().parents[2] / "__tests__" / "fixtures" / "cloud-jev"
29
+ VALID = json.loads((FIXTURES / "semantic-valid.json").read_text())
30
+
31
+ DECL = {
32
+ "name": "acme-prod-db",
33
+ "title": "Tried to write to the production database",
34
+ "appliesTo": ["shell"],
35
+ "mode": "deny",
36
+ "userCanOverride": True,
37
+ "probes": [{"id": "writes", "instructions": "It writes.", "criteria": {"true": "yes", "false": "no"}}],
38
+ "guidance": "Ask first.",
39
+ }
40
+
41
+ SERVER_BOTH = {
42
+ "id": "no-prod-db", "version": 3, "description": "prod db guard", "sha256": "a" * 64,
43
+ "source": "export default 1;", "createdAt": "2026-09-30T00:00:00Z", "createdBy": "me@test",
44
+ "disabled": False, "archived": False, "kind": "both", "semantic": [DECL],
45
+ "semanticSha256": "b" * 64, "authority": "reviewable", "reviewedBy": ["acme-prod-db"],
46
+ }
47
+ SERVER_JEV = {
48
+ "id": "secrets", "version": 1, "description": "", "sha256": "", "source": None,
49
+ "createdAt": "2026-09-30T00:00:00Z", "createdBy": "me@test", "disabled": False,
50
+ "archived": False, "kind": "jev", "semantic": [dict(DECL, name="acme-secrets")],
51
+ "semanticSha256": "c" * 64,
52
+ }
53
+ SERVER_REGEX_OLD = {
54
+ "id": "no-force-push", "version": 2, "description": "", "sha256": "d" * 64,
55
+ "source": "export default 2;", "createdAt": "2026-09-01T00:00:00Z", "createdBy": None,
56
+ "disabled": False, "archived": False,
57
+ }
58
+
59
+
60
+ # ── models ────────────────────────────────────────────────────────────────────
61
+
62
+
63
+ def test_a_policy_version_carries_its_kind_and_jev_checks():
64
+ both = PolicyVersion.from_dict(SERVER_BOTH)
65
+ assert (both.kind, both.jev_names, both.authority, both.reviewed_by) == (
66
+ "both", ["acme-prod-db"], "reviewable", ["acme-prod-db"])
67
+ out = both.to_dict()
68
+ assert out["kind"] == "both" and out["semantic"] == [DECL]
69
+ assert out["semanticSha256"] == "b" * 64 and out["reviewedBy"] == ["acme-prod-db"]
70
+
71
+
72
+ def test_a_version_from_a_server_that_predates_kinds_is_regex():
73
+ old = PolicyVersion.from_dict(SERVER_REGEX_OLD)
74
+ assert old.kind == "regex" and old.jev_names == [] and old.semantic is None
75
+ out = old.to_dict()
76
+ assert out["kind"] == "regex"
77
+ assert "semantic" not in out and "reviewedBy" not in out and "authority" not in out
78
+
79
+
80
+ def test_machines_carry_policy_errors_and_jev_mode_only_when_reported():
81
+ reported = Machine.from_dict({
82
+ "machineId": "m1", "policyErrors": [{"id": "p", "version": 1, "kind": "jev", "message": "bad"}],
83
+ "policyErrorsAt": "2026-09-30T01:00:00Z", "jevMode": "observe",
84
+ })
85
+ assert reported.policy_errors == [{"id": "p", "version": 1, "kind": "jev", "message": "bad"}]
86
+ assert reported.jev_mode == "observe"
87
+ d = reported.to_dict()
88
+ assert d["policyErrors"][0]["message"] == "bad" and d["jevMode"] == "observe"
89
+ assert d["policyErrorsAt"] == "2026-09-30T01:00:00Z"
90
+
91
+ quiet = Machine.from_dict({"machineId": "m2"})
92
+ assert quiet.policy_errors is None and quiet.jev_mode is None
93
+ assert not {"policyErrors", "policyErrorsAt", "jevMode"} & set(quiet.to_dict())
94
+
95
+
96
+ def test_a_deployment_carries_its_jev_mode():
97
+ dep = Deployment.from_dict({"machineId": "m1", "deployment": 4, "policies": [], "jevMode": "enforce"})
98
+ assert dep.jev_mode == "enforce" and dep.to_dict()["jevMode"] == "enforce"
99
+ assert "jevMode" not in Deployment.from_dict({"machineId": "m1", "deployment": 4}).to_dict()
100
+
101
+
102
+ # ── kind + semantic (pure) ────────────────────────────────────────────────────
103
+
104
+
105
+ @pytest.mark.parametrize("kind,src,sem,expected", [
106
+ (None, True, False, "regex"),
107
+ (None, False, True, "jev"),
108
+ (None, True, True, "both"),
109
+ ("regex", True, False, "regex"),
110
+ ("jev", False, True, "jev"),
111
+ ("both", True, True, "both"),
112
+ ])
113
+ def test_the_kind_follows_from_what_was_given(kind, src, sem, expected):
114
+ assert resolve_kind(kind, has_source=src, has_semantic=sem) == expected
115
+
116
+
117
+ @pytest.mark.parametrize("kind,src,sem,needle", [
118
+ ("regex", True, True, "takes no --semantic"),
119
+ ("jev", True, True, "takes no JavaScript"),
120
+ ("jev", False, False, "needs --semantic"),
121
+ ("both", True, False, "needs --semantic"),
122
+ ("hybrid", True, False, "not one of"),
123
+ ])
124
+ def test_a_kind_that_contradicts_its_inputs_is_a_usage_error(kind, src, sem, needle):
125
+ with pytest.raises(RefUsageError, match=needle):
126
+ resolve_kind(kind, has_source=src, has_semantic=sem)
127
+
128
+
129
+ def test_semantic_accepts_the_contract_fixtures_and_a_wrapper():
130
+ assert len(VALID) == 16
131
+ assert parse_semantic(json.dumps(VALID)) == VALID
132
+ assert parse_semantic(json.dumps({"semantic": VALID[:2]})) == VALID[:2]
133
+
134
+
135
+ @pytest.mark.parametrize("text,needle", [
136
+ ("{not json", "not valid JSON"),
137
+ (json.dumps({"name": "x"}), "JSON array"),
138
+ ("[]", "no Jev declarations"),
139
+ (json.dumps([DECL] * 25), "at most 24"),
140
+ (json.dumps([{"title": "no name"}]), "#0 is not an object with a name"),
141
+ ])
142
+ def test_semantic_refuses_what_is_not_a_declaration_list(text, needle):
143
+ with pytest.raises(RefUsageError, match=needle):
144
+ parse_semantic(text)
145
+
146
+
147
+ # ── deploy plan: --jev-mode ───────────────────────────────────────────────────
148
+
149
+
150
+ def _plan(**kw):
151
+ base = dict(current=[PolicyRef("a", 1)], base=5, latest={"a": 1, "j": 2})
152
+ base.update(kw)
153
+ return plan_deploy("m1", **base)
154
+
155
+
156
+ def test_jev_mode_alone_is_a_change_and_is_carried_in_the_plan():
157
+ plan = _plan(jev_mode="observe")
158
+ assert not plan.is_noop and plan.jev_mode_changes
159
+ assert plan.to_dict()["jevMode"] == {"from": "local", "to": "observe", "changed": True}
160
+
161
+
162
+ def test_the_same_jev_mode_again_is_a_noop():
163
+ assert _plan(jev_mode="observe", current_jev_mode="observe").is_noop
164
+ assert _plan(jev_mode="local").is_noop # already local
165
+
166
+
167
+ def test_local_hands_the_mode_back_to_the_machine():
168
+ plan = _plan(jev_mode="local", current_jev_mode="enforce")
169
+ assert plan.jev_mode_changes and plan.jev_mode_after is None
170
+ assert plan.to_dict()["jevMode"] == {"from": "enforce", "to": "local", "changed": True}
171
+
172
+
173
+ def test_no_jev_mode_says_nothing_and_changes_nothing():
174
+ plan = _plan(add=["a"], current_jev_mode="enforce")
175
+ assert plan.is_noop and "jevMode" not in plan.to_dict()
176
+
177
+
178
+ def test_an_unknown_jev_mode_is_a_usage_error():
179
+ with pytest.raises(RefUsageError, match="not one of off, observe, enforce, local"):
180
+ _plan(jev_mode="shadow")
181
+
182
+
183
+ def test_observe_on_a_jev_policy_is_refused_with_the_mode_that_does_it():
184
+ kinds = version_kinds([PolicyVersion.from_dict(dict(SERVER_JEV, id="j", version=2))])
185
+ with pytest.raises(RefUsageError, match="use --jev-mode observe"):
186
+ _plan(add=["j:observe"], kinds=kinds)
187
+ # enforce is fine, and so is observe on a `both` policy (it applies to the JS half).
188
+ assert _plan(add=["j"], kinds=kinds).added[0].effect == "enforce"
189
+ both = version_kinds([PolicyVersion.from_dict(dict(SERVER_BOTH, id="j", version=2))])
190
+ assert _plan(add=["j:observe"], kinds=both).added[0].effect == "observe"
191
+
192
+
193
+ # ── the wire ──────────────────────────────────────────────────────────────────
194
+
195
+
196
+ def test_publish_sends_kind_and_semantic_and_no_source_for_jev(monkeypatch):
197
+ sent = []
198
+ monkeypatch.setattr(client, "_post_json", lambda ctx, path, body=None: sent.append(body) or {})
199
+ client.publish_policy(None, "secrets", None, "d", kind="jev", semantic=[DECL])
200
+ assert sent[-1] == {"id": "secrets", "description": "d", "kind": "jev", "semantic": [DECL]}
201
+
202
+ client.publish_policy(None, "p", "export default 1;", "")
203
+ assert sent[-1] == {"id": "p", "description": "", "source": "export default 1;"}
204
+
205
+
206
+ def test_publish_still_refuses_jev_fields_in_the_javascript(monkeypatch):
207
+ from fp_cli.errors import ApiError
208
+
209
+ monkeypatch.setattr(client, "_post_json", lambda *a, **k: pytest.fail("reached the server"))
210
+ src = 'semanticPolicies.add({ name: "x" });'
211
+ with pytest.raises(ApiError) as exc:
212
+ client.publish_policy(None, "p", src, "", kind="both", semantic=[DECL])
213
+ assert "--kind both --semantic" in (exc.value.hint or "")
214
+
215
+
216
+ def test_deploy_sends_jev_mode_only_when_given(monkeypatch):
217
+ bodies = []
218
+ monkeypatch.setattr(client, "_request_json",
219
+ lambda ctx, method, path, json_body=None: bodies.append(json_body) or {})
220
+ client.deploy_policies(None, "m1", [PolicyRef("a", 1)])
221
+ client.deploy_policies(None, "m1", [PolicyRef("a", 1)], jev_mode="local")
222
+ assert "jevMode" not in bodies[0]
223
+ assert bodies[1]["jevMode"] == "local"
224
+
225
+
226
+ # ── the commands, over HTTP ───────────────────────────────────────────────────
227
+
228
+
229
+ @respx.mock
230
+ def test_publish_a_jev_policy_from_a_semantic_file(logged_in, runner, tmp_path):
231
+ checks = tmp_path / "checks.json"
232
+ checks.write_text(json.dumps([DECL]))
233
+ route = respx.post(f"{BASE}/api/enforcement/policies").mock(
234
+ return_value=httpx.Response(200, json=dict(SERVER_JEV, id="prod-db-intent", semantic=[DECL])))
235
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[]))
236
+ result = runner.invoke(app, ["--json", "policies", "publish", "prod-db-intent", "--semantic", str(checks)])
237
+ assert result.exit_code == 0, result.output
238
+ body = json.loads(route.calls.last.request.content)
239
+ assert body == {"id": "prod-db-intent", "description": "", "kind": "jev", "semantic": [DECL]}
240
+ out = json.loads(result.stdout)
241
+ assert out["kind"] == "jev" and out["syntax"]["checked"] is False
242
+
243
+
244
+ @respx.mock
245
+ def test_publish_both_sends_the_source_and_the_declarations(logged_in, runner, tmp_path):
246
+ rule = tmp_path / "rule.mjs"
247
+ rule.write_text('import { customPolicies, allow } from "failproofai";\n')
248
+ checks = tmp_path / "checks.json"
249
+ checks.write_text(json.dumps([DECL]))
250
+ route = respx.post(f"{BASE}/api/enforcement/policies").mock(
251
+ return_value=httpx.Response(200, json=SERVER_BOTH))
252
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[]))
253
+ result = runner.invoke(app, ["policies", "publish", "no-prod-db", "--kind", "both",
254
+ "--source", str(rule), "--semantic", str(checks), "--no-verify"])
255
+ assert result.exit_code == 0, result.output
256
+ body = json.loads(route.calls.last.request.content)
257
+ assert body["kind"] == "both" and body["semantic"] == [DECL] and "customPolicies" in body["source"]
258
+ assert "acme-prod-db" in result.stdout and "both" in result.stdout
259
+
260
+
261
+ @respx.mock
262
+ def test_publish_jev_with_source_is_a_usage_error_before_anything_is_sent(logged_in, runner, tmp_path):
263
+ rule = tmp_path / "rule.mjs"
264
+ rule.write_text("export default 1;\n")
265
+ checks = tmp_path / "checks.json"
266
+ checks.write_text(json.dumps([DECL]))
267
+ result = runner.invoke(app, ["policies", "publish", "x", "--kind", "jev",
268
+ "--source", str(rule), "--semantic", str(checks)])
269
+ assert result.exit_code == 2
270
+ assert not respx.calls
271
+
272
+
273
+ @respx.mock
274
+ def test_list_and_show_print_the_kind_and_the_jev_names(logged_in, runner):
275
+ respx.get(f"{BASE}/api/enforcement/policies").mock(
276
+ return_value=httpx.Response(200, json=[SERVER_BOTH, SERVER_JEV, SERVER_REGEX_OLD]))
277
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[]))
278
+ listed = runner.invoke(app, ["policies", "list"])
279
+ assert listed.exit_code == 0, listed.output
280
+ cells = [ln.strip().strip("│").strip() for ln in listed.stdout.splitlines()]
281
+ rows = {c.split()[0]: c for c in cells if c[:1].isalpha()}
282
+ assert "both" in rows["no-prod-db"] and "acme-prod-db" in rows["no-prod-db"]
283
+ assert "jev" in rows["secrets"] and "acme-secrets" in rows["secrets"]
284
+ assert "regex" in rows["no-force-push"]
285
+
286
+ shown = runner.invoke(app, ["policies", "show", "secrets"])
287
+ assert shown.exit_code == 0, shown.output
288
+ assert "jev" in shown.stdout and "acme-secrets" in shown.stdout
289
+ # The declarations themselves, printed where the JavaScript source is.
290
+ assert '"name": "acme-secrets"' in shown.stdout + (shown.stderr or "")
291
+
292
+ js = json.loads(runner.invoke(app, ["--json", "policies", "show", "no-prod-db"]).stdout)
293
+ assert js["kind"] == "both" and js["reviewedBy"] == ["acme-prod-db"] and js["semantic"] == [DECL]
294
+
295
+
296
+ def test_a_version_carries_its_jev_question_chars_and_regex_has_none():
297
+ jev = PolicyVersion.from_dict(dict(SERVER_JEV, jevChars=412))
298
+ assert jev.jev_chars == 412 and jev.to_dict()["jevChars"] == 412
299
+ regex = PolicyVersion.from_dict(dict(SERVER_REGEX_OLD, jevChars=None))
300
+ assert regex.jev_chars is None and "jevChars" not in regex.to_dict()
301
+
302
+
303
+ @respx.mock
304
+ def test_list_and_show_print_the_jev_question_chars(logged_in, runner):
305
+ respx.get(f"{BASE}/api/enforcement/policies").mock(return_value=httpx.Response(
306
+ 200, json=[dict(SERVER_JEV, jevChars=18653), dict(SERVER_REGEX_OLD, jevChars=None)]))
307
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[]))
308
+ listed = runner.invoke(app, ["policies", "list"], env={"COLUMNS": "200"})
309
+ assert listed.exit_code == 0, listed.output
310
+ rows = {c.split()[0]: c for c in (ln.strip().strip("│").strip() for ln in listed.stdout.splitlines())
311
+ if c[:1].isalpha()}
312
+ assert "18,653 chars" in rows["secrets"]
313
+ assert "chars" not in rows["no-force-push"]
314
+
315
+ shown = runner.invoke(app, ["policies", "show", "secrets"], env={"COLUMNS": "200"})
316
+ assert shown.exit_code == 0, shown.output
317
+ assert "18,653 question chars" in shown.stdout
318
+
319
+
320
+ @respx.mock
321
+ def test_a_jev_budget_refusal_names_each_policys_share_largest_first(logged_in, runner):
322
+ # The server's deploy 422 (CONTRACT C9.2): the message has the total, the
323
+ # body lists every contributing version — which is what says what to drop.
324
+ _fleet_routes([])
325
+ refusal = {
326
+ "error": "the Jev checks of this deployment are over the machine's question budget "
327
+ "(37756 of 27591 characters)",
328
+ "code": "jev_budget_exceeded", "used": 37756, "budget": 27591,
329
+ "policies": [{"id": "e2e-prod-db", "version": 1, "chars": 415},
330
+ {"id": "e2e-big", "version": 1, "chars": 18653},
331
+ {"id": "e2e-big2", "version": 1, "chars": 18688}],
332
+ }
333
+ respx.put(f"{BASE}/api/enforcement/deployments/m1").mock(return_value=httpx.Response(422, json=refusal))
334
+ human = runner.invoke(app, ["fleet", "deploy", "m1", "--add", "secrets", "--yes"], env={"COLUMNS": "400"})
335
+ assert human.exit_code == 1
336
+ text = " ".join((human.output + (human.stderr or "")).split())
337
+ assert "37756 of 27591" in text
338
+ assert "e2e-big2@1 18,688 · e2e-big@1 18,653 · e2e-prod-db@1 415" in text
339
+
340
+ js = runner.invoke(app, ["--json", "fleet", "deploy", "m1", "--add", "secrets", "--yes"])
341
+ out = json.loads(js.stdout)
342
+ assert out["status"] == 422
343
+ assert out["hint"].startswith("Jev question characters per policy: e2e-big2@1 18,688")
344
+
345
+ # Any other refusal carries no such line.
346
+ respx.put(f"{BASE}/api/enforcement/deployments/m1").mock(return_value=httpx.Response(
347
+ 422, json={"error": "bad", "code": "jev_effect_unsupported"}))
348
+ other = json.loads(runner.invoke(app, ["--json", "fleet", "deploy", "m1", "--add", "secrets", "--yes"]).stdout)
349
+ assert "hint" not in other
350
+
351
+
352
+ def _fleet_routes(current_policies, jev_mode=None, errors=None):
353
+ machine = {"machineId": "m1", "deployment": 5, "appliedDeployment": 5, "deployed": True}
354
+ if errors is not None:
355
+ machine["policyErrors"] = errors
356
+ if jev_mode:
357
+ machine["jevMode"] = jev_mode
358
+ dep = {"machineId": "m1", "deployment": 5, "policies": current_policies}
359
+ if jev_mode:
360
+ dep["jevMode"] = jev_mode
361
+ respx.get(f"{BASE}/api/enforcement/machines").mock(return_value=httpx.Response(200, json=[machine]))
362
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[dep]))
363
+ respx.get(f"{BASE}/api/enforcement/policies").mock(
364
+ return_value=httpx.Response(200, json=[SERVER_BOTH, SERVER_JEV, SERVER_REGEX_OLD]))
365
+
366
+
367
+ @respx.mock
368
+ def test_fleet_deploy_jev_mode_alone_changes_only_the_mode_through_its_own_route(logged_in, runner):
369
+ # CONTRACT C9.1: a mode change sent as a deploy rewrote the set the CLI had
370
+ # read — which leaves out disabled policies' assignments, so they were lost.
371
+ _fleet_routes([{"id": "no-force-push", "version": 2, "effect": "enforce"}])
372
+ deploy = respx.put(f"{BASE}/api/enforcement/deployments/m1")
373
+ mode = respx.put(f"{BASE}/api/enforcement/deployments/m1/jev-mode").mock(return_value=httpx.Response(
374
+ 200, json={"machineId": "m1", "deployment": 6, "jevMode": "observe", "changed": True,
375
+ "policies": [{"id": "no-force-push", "version": 2, "effect": "enforce"}]}))
376
+ result = runner.invoke(app, ["--json", "fleet", "deploy", "m1", "--jev-mode", "observe", "--yes"])
377
+ assert result.exit_code == 0, result.output
378
+ assert not deploy.called
379
+ assert json.loads(mode.calls.last.request.content) == {"jevMode": "observe"}
380
+ out = json.loads(result.stdout)
381
+ assert out["applied"] is True and out["jevModeOnly"] is True
382
+ assert out["plan"]["jevMode"]["to"] == "observe" and out["deployment"]["deployment"] == 6
383
+
384
+
385
+ @respx.mock
386
+ def test_fleet_deploy_adds_a_jev_policy_and_leaves_the_mode_alone_without_the_flag(logged_in, runner):
387
+ _fleet_routes([], jev_mode="enforce")
388
+ put = respx.put(f"{BASE}/api/enforcement/deployments/m1").mock(return_value=httpx.Response(
389
+ 200, json={"machineId": "m1", "deployment": 6, "jevMode": "enforce",
390
+ "policies": [{"id": "secrets", "version": 1, "effect": "enforce"}]}))
391
+ result = runner.invoke(app, ["fleet", "deploy", "m1", "--add", "secrets", "--yes"])
392
+ assert result.exit_code == 0, result.output
393
+ assert json.loads(put.calls.last.request.content) == {
394
+ "policies": [{"id": "secrets", "version": 1, "effect": "enforce"}]}
395
+
396
+
397
+ @respx.mock
398
+ def test_fleet_deploy_refuses_observe_on_a_jev_policy_before_writing(logged_in, runner):
399
+ _fleet_routes([])
400
+ put = respx.put(f"{BASE}/api/enforcement/deployments/m1")
401
+ result = runner.invoke(app, ["fleet", "deploy", "m1", "--add", "secrets:observe", "--yes"])
402
+ assert result.exit_code == 2
403
+ assert "--jev-mode observe" in result.output + (result.stderr or "")
404
+ assert not put.called
405
+
406
+
407
+ @respx.mock
408
+ def test_fleet_show_and_list_show_the_jev_mode_and_the_reported_errors(logged_in, runner):
409
+ errors = [{"id": "secrets", "version": 1, "kind": "jev", "message": "not loaded: failed integrity verification"},
410
+ {"id": "jevMode", "version": None, "kind": "daemon", "message": "jev_unconfigured"}]
411
+ _fleet_routes([{"id": "secrets", "version": 1, "effect": "enforce"}], jev_mode="observe", errors=errors)
412
+ shown = runner.invoke(app, ["fleet", "show", "m1"])
413
+ assert shown.exit_code == 0, shown.output
414
+ assert "jev mode" in shown.stdout and "observe" in shown.stdout and "set by FailproofAI Cloud" in shown.stdout
415
+ assert "2 policy errors" in shown.stdout and "jev_unconfigured" in shown.stdout
416
+ assert "failed integrity verification" in shown.stdout
417
+
418
+ listed = runner.invoke(app, ["fleet", "list"])
419
+ assert listed.exit_code == 0, listed.output
420
+ assert "2 errors" in listed.stdout
421
+
422
+ js = json.loads(runner.invoke(app, ["--json", "fleet", "show", "m1"]).stdout)
423
+ assert js["machine"]["policyErrors"] == errors and js["machine"]["jevMode"] == "observe"
424
+ assert js["deployment"]["jevMode"] == "observe"
425
+
426
+
427
+ # ── Review fixes: the Jev mode route (C9.1), rollback and history (m6), help (m7) ──
428
+
429
+
430
+ def _fleet_many(deployments, machines=None):
431
+ """`deployments`: {machine_id: jev_mode or None}; machines default to the same ids."""
432
+ ids = machines if machines is not None else list(deployments)
433
+ respx.get(f"{BASE}/api/enforcement/machines").mock(return_value=httpx.Response(200, json=[
434
+ {"machineId": m, "deployment": 5, "appliedDeployment": 5, "deployed": m in deployments} for m in ids]))
435
+ deps = []
436
+ for m, mode in deployments.items():
437
+ d = {"machineId": m, "deployment": 5, "policies": [{"id": "no-force-push", "version": 2, "effect": "enforce"}]}
438
+ if mode:
439
+ d["jevMode"] = mode
440
+ deps.append(d)
441
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=deps))
442
+ respx.get(f"{BASE}/api/enforcement/policies").mock(
443
+ return_value=httpx.Response(200, json=[SERVER_BOTH, SERVER_JEV, SERVER_REGEX_OLD]))
444
+
445
+
446
+ def _mode_route(machine_id, mode, deployment=6, status=200):
447
+ body = ({"machineId": machine_id, "deployment": deployment, "jevMode": None if mode == "local" else mode,
448
+ "changed": True, "policies": []} if status == 200
449
+ else {"error": f"machine {machine_id} has no deployment", "code": "no_deployment"})
450
+ return respx.put(f"{BASE}/api/enforcement/deployments/{machine_id}/jev-mode").mock(
451
+ return_value=httpx.Response(status, json=body))
452
+
453
+
454
+ def test_set_jev_mode_sends_only_the_mode(monkeypatch):
455
+ seen = {}
456
+
457
+ def fake(ctx, method, path, json_body=None, **kw):
458
+ seen.update(method=method, path=path, body=json_body)
459
+ return {"machineId": "m1", "deployment": 9, "jevMode": "enforce", "changed": False, "policies": []}
460
+
461
+ monkeypatch.setattr(client, "_request_json", fake)
462
+ dep, changed = client.set_jev_mode(object(), "m1", "enforce")
463
+ assert seen == {"method": "PUT", "path": "/api/enforcement/deployments/m1/jev-mode", "body": {"jevMode": "enforce"}}
464
+ assert (dep.deployment, dep.jev_mode, changed) == (9, "enforce", False)
465
+
466
+
467
+ @respx.mock
468
+ def test_fleet_deploy_jev_mode_on_a_machine_with_no_deployment_deploys_its_first(logged_in, runner):
469
+ respx.get(f"{BASE}/api/enforcement/machines").mock(return_value=httpx.Response(
470
+ 200, json=[{"machineId": "m1", "deployment": None, "deployed": False}]))
471
+ respx.get(f"{BASE}/api/enforcement/deployments").mock(return_value=httpx.Response(200, json=[]))
472
+ respx.get(f"{BASE}/api/enforcement/policies").mock(return_value=httpx.Response(200, json=[SERVER_JEV]))
473
+ mode = respx.put(f"{BASE}/api/enforcement/deployments/m1/jev-mode")
474
+ deploy = respx.put(f"{BASE}/api/enforcement/deployments/m1").mock(return_value=httpx.Response(
475
+ 200, json={"machineId": "m1", "deployment": 1, "jevMode": "observe", "policies": []}))
476
+ result = runner.invoke(app, ["--json", "fleet", "deploy", "m1", "--jev-mode", "observe", "--yes"])
477
+ assert result.exit_code == 0, result.output
478
+ assert not mode.called
479
+ assert json.loads(deploy.calls.last.request.content) == {"policies": [], "jevMode": "observe"}
480
+
481
+
482
+ @respx.mock
483
+ def test_fleet_deploy_with_a_set_change_and_a_mode_still_deploys_both_together(logged_in, runner):
484
+ _fleet_routes([{"id": "no-force-push", "version": 2, "effect": "enforce"}])
485
+ mode = respx.put(f"{BASE}/api/enforcement/deployments/m1/jev-mode")
486
+ deploy = respx.put(f"{BASE}/api/enforcement/deployments/m1").mock(return_value=httpx.Response(
487
+ 200, json={"machineId": "m1", "deployment": 6, "jevMode": "enforce", "policies": []}))
488
+ result = runner.invoke(app, ["fleet", "deploy", "m1", "--add", "secrets", "--jev-mode", "enforce", "--yes"])
489
+ assert result.exit_code == 0, result.output
490
+ assert not mode.called
491
+ assert json.loads(deploy.calls.last.request.content)["jevMode"] == "enforce"
492
+
493
+
494
+ @respx.mock
495
+ def test_fleet_jev_mode_sets_named_machines_and_skips_one_already_there(logged_in, runner):
496
+ _fleet_many({"m1": None, "m2": "enforce", "m3": "observe"})
497
+ r1, r2, r3 = _mode_route("m1", "enforce"), _mode_route("m2", "enforce"), _mode_route("m3", "enforce", 7)
498
+ deploy = respx.put(f"{BASE}/api/enforcement/deployments/m1")
499
+ result = runner.invoke(app, ["--json", "fleet", "jev-mode", "m1", "m2", "m3", "enforce"])
500
+ assert result.exit_code == 0, result.output
501
+ assert r1.called and r3.called and not r2.called and not deploy.called
502
+ assert json.loads(r1.calls.last.request.content) == {"jevMode": "enforce"}
503
+ out = json.loads(result.stdout)
504
+ assert out["mode"] == "enforce" and out["applied"] is True
505
+ rows = {r["machineId"]: r for r in out["machines"]}
506
+ assert rows["m1"]["from"] == "local" and rows["m1"]["to"] == "enforce" and rows["m1"]["deployment"] == 6
507
+ assert rows["m2"]["changed"] is False
508
+ assert rows["m3"]["from"] == "observe" and rows["m3"]["deployment"] == 7
509
+
510
+
511
+ @respx.mock
512
+ def test_fleet_jev_mode_all_covers_every_machine_with_a_deployment_only(logged_in, runner):
513
+ _fleet_many({"m1": "enforce", "m2": "off"}, machines=["m1", "m2", "m-new"])
514
+ r1, r2 = _mode_route("m1", "local"), _mode_route("m2", "local")
515
+ fresh = respx.put(f"{BASE}/api/enforcement/deployments/m-new/jev-mode")
516
+ result = runner.invoke(app, ["fleet", "jev-mode", "--all", "local"])
517
+ assert result.exit_code == 0, result.output
518
+ assert r1.called and r2.called and not fresh.called
519
+ assert json.loads(r1.calls.last.request.content) == {"jevMode": "local"}
520
+ assert "enforce → local" in result.stdout and "off → local" in result.stdout
521
+
522
+
523
+ @respx.mock
524
+ def test_fleet_jev_mode_when_nothing_changes_writes_nothing(logged_in, runner):
525
+ _fleet_many({"m1": "observe"})
526
+ route = _mode_route("m1", "observe")
527
+ result = runner.invoke(app, ["fleet", "jev-mode", "m1", "observe"])
528
+ assert result.exit_code == 0, result.output
529
+ assert not route.called
530
+ assert "already has jev mode observe" in result.output + (result.stderr or "")
531
+
532
+
533
+ @pytest.mark.parametrize("argv, needle", [
534
+ (["fleet", "jev-mode"], "pass the mode"),
535
+ (["fleet", "jev-mode", "m1", "shadow"], "is not a Jev mode"),
536
+ (["fleet", "jev-mode", "observe"], "name at least one machine"),
537
+ (["fleet", "jev-mode", "--all", "m1", "observe"], "do not name machines"),
538
+ ])
539
+ def test_fleet_jev_mode_usage_errors_exit_2_before_any_request(logged_in, runner, argv, needle):
540
+ with respx.mock(assert_all_called=False) as mock:
541
+ result = runner.invoke(app, argv)
542
+ assert result.exit_code == 2, result.output
543
+ assert needle in result.output + (result.stderr or "")
544
+ assert not mock.calls
545
+
546
+
547
+ @respx.mock
548
+ def test_fleet_jev_mode_refuses_a_named_machine_with_no_deployment_before_writing(logged_in, runner):
549
+ _fleet_many({"m1": None}, machines=["m1", "m2"])
550
+ r1 = _mode_route("m1", "observe")
551
+ result = runner.invoke(app, ["fleet", "jev-mode", "m1", "m2", "observe"])
552
+ assert result.exit_code == 1, result.output
553
+ text = result.output + (result.stderr or "")
554
+ assert "m2 has no deployment" in text and "fp fleet deploy <machine> --jev-mode observe" in text
555
+ assert not r1.called
556
+
557
+
558
+ @respx.mock
559
+ def test_fleet_jev_mode_goes_on_past_a_machine_that_fails_and_names_it(logged_in, runner):
560
+ _fleet_many({"m1": None, "m2": None})
561
+ _mode_route("m1", "observe", status=404)
562
+ r2 = _mode_route("m2", "observe")
563
+ result = runner.invoke(app, ["--json", "fleet", "jev-mode", "m1", "m2", "observe"])
564
+ assert result.exit_code == 1, result.output
565
+ assert r2.called
566
+ # The result document, then the error document the exit code comes with.
567
+ out, end = json.JSONDecoder().raw_decode(result.stdout)
568
+ rows = {r["machineId"]: r for r in out["machines"]}
569
+ assert "error" in rows["m1"] and rows["m2"]["deployment"] == 6 and out["applied"] is False
570
+ assert "could not be changed on m1" in result.stdout[end:] + (result.stderr or "")
571
+
572
+
573
+ @respx.mock
574
+ def test_fleet_rollback_says_which_jev_mode_it_restores(logged_in, runner):
575
+ _fleet_many({"m1": "off"})
576
+ respx.get(f"{BASE}/api/enforcement/deployments/m1/history").mock(return_value=httpx.Response(200, json=[
577
+ {"deployment": 5, "policies": [], "updatedAt": "2026-09-30T02:00:00Z", "jevMode": "off"},
578
+ {"deployment": 3, "policies": [], "updatedAt": "2026-09-29T02:00:00Z", "jevMode": "enforce"},
579
+ ]))
580
+ respx.post(f"{BASE}/api/enforcement/deployments/m1/rollback").mock(return_value=httpx.Response(
581
+ 200, json={"machineId": "m1", "deployment": 6, "rolledBackFrom": 3, "policies": [], "jevMode": "enforce"}))
582
+ human = runner.invoke(app, ["fleet", "rollback", "m1", "3", "--yes"])
583
+ assert human.exit_code == 0, human.output
584
+ text = human.output + (human.stderr or "")
585
+ assert "jev mode" in text and "off → enforce" in text
586
+
587
+ js = runner.invoke(app, ["--json", "fleet", "rollback", "m1", "3", "--yes"])
588
+ assert js.exit_code == 0, js.output
589
+ out = json.loads(js.stdout)
590
+ assert out["jevMode"] == "enforce" and out["jevModeBefore"] == "off"
591
+
592
+
593
+ @respx.mock
594
+ def test_fleet_history_shows_each_generations_jev_mode_and_a_mode_only_change(logged_in, runner):
595
+ _fleet_many({"m1": "observe"})
596
+ pols = [{"id": "no-force-push", "version": 2, "effect": "enforce"}]
597
+ respx.get(f"{BASE}/api/enforcement/deployments/m1/history").mock(return_value=httpx.Response(200, json=[
598
+ {"deployment": 6, "policies": pols, "updatedAt": "2026-09-30T02:00:00Z", "jevMode": "observe"},
599
+ {"deployment": 5, "policies": pols, "updatedAt": "2026-09-30T01:00:00Z"},
600
+ ]))
601
+ result = runner.invoke(app, ["fleet", "history", "m1"])
602
+ assert result.exit_code == 0, result.output
603
+ assert "jev" in result.stdout
604
+ assert "local→observe" in result.stdout
605
+ lines = [ln for ln in result.stdout.splitlines() if "#6" in ln or "#5" in ln]
606
+ assert any("observe" in ln for ln in lines if "#6" in ln)
607
+ assert any("local" in ln for ln in lines if "#5" in ln)
608
+
609
+
610
+ def test_the_help_says_enforce_blocks_and_that_the_mode_is_machine_wide(runner):
611
+ for argv in (["fleet", "deploy", "--help"], ["fleet", "jev-mode", "--help"]):
612
+ result = runner.invoke(app, argv)
613
+ assert result.exit_code == 0, result.output
614
+ flat = " ".join(result.stdout.split())
615
+ assert "BLOCK" in flat, argv
616
+ assert "installed" in flat and "pack" in flat, argv
617
+
618
+
619
+ # ── CONTRACT C10.6: Jev checks run on FailproofAI Cloud (help text only) ──────
620
+
621
+
622
+ @pytest.mark.parametrize(
623
+ "argv",
624
+ [
625
+ ["fleet", "deploy", "--help"],
626
+ ["fleet", "jev-mode", "--help"],
627
+ ["policies", "publish", "--help"],
628
+ ],
629
+ )
630
+ def test_the_help_says_jev_checks_run_on_cloud_and_nothing_is_installed(runner, argv):
631
+ result = runner.invoke(app, argv)
632
+ assert result.exit_code == 0, result.output
633
+ flat = " ".join(result.stdout.split())
634
+ assert "Jev checks run on FailproofAI Cloud; nothing is installed on the machine" in flat, argv
635
+ # The on-machine delivery it replaced is not described any more.
636
+ assert "puts its Jev checks on the machine" not in flat, argv
637
+ assert "AgentEye" not in flat, argv
638
+
639
+
640
+ @pytest.mark.parametrize("argv", [["fleet", "show", "--help"], ["fleet", "jev-mode", "--help"]])
641
+ def test_the_help_names_the_reasons_a_machine_cannot_ask(runner, argv):
642
+ flat = " ".join(runner.invoke(app, argv).stdout.split())
643
+ assert "transcripts_disabled" in flat, argv
644
+ if argv[1] == "show":
645
+ assert "jev_unconfigured" in flat
646
+ assert "jev_budget" in flat
647
+ # Review M3 and the name-clash split: the machine's own reports.
648
+ for code in ("jev_rate_limited", "jev_unavailable", "jev_name_clash"):
649
+ assert code in flat
650
+ # A machine no longer parses Jev checks, so it cannot fail to.
651
+ assert "could not parse" not in flat
652
+
653
+
654
+ # ── e2e findings: exact-profile targets read as their label (B11) ─────────────
655
+
656
+ _AGT = "agt_" + "1f" * 16
657
+ _PROFILE_POLS = [{"id": "no-force-push", "version": 2, "effect": "enforce",
658
+ "agentTargets": [{"integration": "claude", "instanceId": _AGT}]}]
659
+
660
+
661
+ def _roster_route(agents, status=200):
662
+ return respx.get(f"{BASE}/api/enforcement/machines/m1/agents").mock(return_value=httpx.Response(
663
+ status, json={"machineId": "m1", "generation": 3, "agents": agents} if status == 200
664
+ else {"error": "not found"}))
665
+
666
+
667
+ @respx.mock
668
+ def test_fleet_show_and_history_name_an_exact_profile_by_its_label(logged_in, runner):
669
+ _fleet_routes(_PROFILE_POLS)
670
+ _roster_route([{"instanceId": _AGT, "integration": "claude", "profileLabel": ".claude-work",
671
+ "scope": "user", "hookInstalled": True}])
672
+ respx.get(f"{BASE}/api/enforcement/deployments/m1/history").mock(return_value=httpx.Response(200, json=[
673
+ {"deployment": 5, "policies": _PROFILE_POLS, "updatedAt": "2026-09-30T01:00:00Z"}]))
674
+
675
+ shown = runner.invoke(app, ["fleet", "show", "m1"])
676
+ assert shown.exit_code == 0, shown.output
677
+ # The label a person recognises, with the id a selector needs beside it.
678
+ assert f"claude/.claude-work ({_AGT})" in shown.stdout
679
+
680
+ history = runner.invoke(app, ["fleet", "history", "m1"])
681
+ assert history.exit_code == 0, history.output
682
+ assert "claude/.claude-work" in history.stdout
683
+ assert _AGT not in history.stdout
684
+
685
+
686
+ @respx.mock
687
+ def test_an_exact_profile_without_a_reported_label_keeps_the_bare_id(logged_in, runner):
688
+ _fleet_routes(_PROFILE_POLS)
689
+ _roster_route([], status=404)
690
+ shown = runner.invoke(app, ["fleet", "show", "m1"])
691
+ assert shown.exit_code == 0, shown.output
692
+ assert f"claude/{_AGT}" in shown.stdout