failproofai 1.0.12-beta.2 → 1.0.12

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 (196) 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.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +3 -3
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  54. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00l2u6j86cb8y._.js → [root-of-the-server]__167mipqmjprq5._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1fw0_nzkae5ao._.js → _1d98fyfrcaqhy._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1fv03uigni8s5.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  87. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  88. package/.next/standalone/.next/server/pages/404.html +1 -1
  89. package/.next/standalone/.next/server/pages/500.html +1 -1
  90. package/.next/standalone/.next/server/preview-props.json +3 -3
  91. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  93. package/.next/standalone/.next/static/chunks/{1c_jf6g5wnslk.js → 0el9n3h9_zp5m.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{2o6d37j425uci.js → 0umzct31duk5g.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{42xptz88mtq3x.js → 0wobqgosirs8p.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{0ay__-d2h1xb5.js → 1kduj74-715jh.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{40oworn_-x12p.js → 2701amhrbsbot.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/{0tbm3vjm62h-2.js → 27cavoc8paelg.js} +1 -1
  101. package/.next/standalone/.next/static/chunks/{1my3-5p47t7r-.js → 2ve23a7keupjw.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/{21hckpmsc4g70.js → 3bv6xaoitjpnh.js} +2 -2
  103. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +19 -0
  104. package/.next/standalone/fp-cloud-cli/README.md +120 -0
  105. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  106. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +198 -7
  107. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  108. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  109. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  110. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  111. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  112. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  128. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +187 -81
  131. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  132. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  133. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  134. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +168 -16
  135. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  136. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  137. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  138. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  139. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  140. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  141. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  142. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  143. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  145. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  147. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  148. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  149. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  150. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  151. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  153. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  154. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  155. package/.next/standalone/package.json +9 -9
  156. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  157. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  158. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  159. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  160. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  161. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  162. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  163. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  164. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  165. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  166. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  167. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  168. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  171. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  172. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  173. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  175. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  176. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  177. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  179. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  181. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  182. package/.next/standalone/server.js +1 -1
  183. package/dist/cli.mjs +85 -37
  184. package/dist/worker.mjs +73 -28
  185. package/hermes-plugin/plugin.yaml +1 -1
  186. package/package.json +9 -9
  187. package/src/hooks/agent-skill.ts +3 -0
  188. package/src/hooks/fp-home.ts +1 -1
  189. package/src/hooks/handler.ts +22 -3
  190. package/src/hooks/semantic/intent.ts +127 -24
  191. package/src/hooks/semantic/jev-review.ts +21 -2
  192. package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +0 -1
  193. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +0 -1
  194. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  195. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  196. /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -51,7 +51,7 @@ def test_print_table_writes_to_stdout(capsys):
51
51
  output.configure(no_color=True, quiet=False)
52
52
  output.print_table(["a", "b"], [["1", "2"]], title="Demo")
53
53
  out = capsys.readouterr().out
54
- assert "Demo" in out
54
+ assert "DEMO" in out
55
55
  assert "1" in out and "2" in out
56
56
 
57
57
 
@@ -91,7 +91,7 @@ def test_render_events_box_to_stdout(capsys):
91
91
  output.render_events([_event("2026-06-22T12:21:54Z")], order="desc")
92
92
  captured = capsys.readouterr()
93
93
  # The box (human data view) goes to STDOUT; the title carries count, direction and date.
94
- assert "events" in captured.out
94
+ assert "EVENTS" in captured.out
95
95
  assert "newest first" in captured.out
96
96
  assert "2026-06-22" in captured.out
97
97
  assert captured.err == ""
@@ -182,7 +182,7 @@ def test_status_color_enum():
182
182
 
183
183
 
184
184
  def test_short_session():
185
- assert output._short_session("sess-20260615-fcf97e01") == "sess-…fcf97e01"
185
+ assert output._short_session("sess-20260615-fcf97e01") == "sess-20260615-fcf97e01"
186
186
  assert output._short_session("sess-20260615-fcf97e01", full=True) == "sess-20260615-fcf97e01"
187
187
  assert output._short_session("nonnum-3-b9d9de") == "nonnum-3-b9d9de" # short → intact
188
188
  assert output._short_session("short") == "short"
@@ -208,9 +208,9 @@ def test_render_sessions_box_has_no_scores(capsys):
208
208
  _wide_stdout()
209
209
  output.render_sessions([_sample_eval()])
210
210
  out = capsys.readouterr().out
211
- assert "sessions" in out and "newest first" in out and "2026-06-22" in out
211
+ assert "SESSIONS" in out and "newest first" in out and "2026-06-22" in out
212
212
  assert "agent-codegen" in out and "done" in out
213
- assert "12:56:31" in out and "sess-…fcf97e01" in out
213
+ assert "12:56:31" in out and "sess-20260615-fcf97e01" in out
214
214
  assert "coherence" not in out and "scores" not in out # scores moved to evals
215
215
  output.configure(no_color=True, quiet=False)
216
216
 
@@ -219,8 +219,8 @@ def test_render_evals_box_has_scores(capsys):
219
219
  _wide_stdout()
220
220
  output.render_evals([_sample_eval()])
221
221
  out = capsys.readouterr().out
222
- assert "evals" in out and "scores" in out and "coherence" in out
223
- assert "agent-codegen" in out and "sess-…fcf97e01" in out
222
+ assert "EVALS" in out and "scores" in out and "coherence" in out
223
+ assert "agent-codegen" in out and "sess-20260615-fcf97e01" in out
224
224
  output.configure(no_color=True, quiet=False)
225
225
 
226
226
 
@@ -296,7 +296,7 @@ def test_render_sessions_expanded_lists_full_roster(capsys):
296
296
  assert "agent-testgen" in out and "9 ev" in out
297
297
  assert "●" not in out # uniform: no special "root" marker
298
298
  assert "├" in out and "└" in out # uniform tree glyphs, └ closes the list
299
- assert "sessions · 1" in out # panel count = sessions, not rows
299
+ assert "SESSIONS · 1" in out # count = sessions, not rows
300
300
  output.configure(no_color=True, quiet=False)
301
301
 
302
302
 
@@ -403,8 +403,8 @@ def test_render_errors_box(capsys):
403
403
  _wide_stdout()
404
404
  output.render_errors([_errevent("error", "RateLimitError: upstream timed out")])
405
405
  out = capsys.readouterr().out
406
- assert "errors" in out and "newest first" in out
407
- assert "agent-orderbot" in out and "sess-…4b90b240" in out # truncated session (shared last-8)
406
+ assert "ERRORS" in out and "newest first" in out
407
+ assert "agent-orderbot" in out and "sess-20260622-4b90b240" in out
408
408
  assert "RateLimitError: upstream timed out" in out # server-computed summary field
409
409
  assert "17:51:34" in out
410
410
  output.configure(no_color=True, quiet=False)
@@ -479,7 +479,7 @@ def test_render_org_perms(capsys):
479
479
  out = capsys.readouterr().out
480
480
  # header leads with the org name + slug + role; the count moved into the box title
481
481
  assert "Globex Corp" in out and "globex" in out and "role admin" in out
482
- assert "permissions · 5 · Globex Corp" in out
482
+ assert "PERMISSIONS · 5 · Globex Corp" in out
483
483
  assert "dashboards" in out and "keys" in out and "agent" in out
484
484
  output.configure(no_color=True, quiet=False)
485
485
 
@@ -549,7 +549,7 @@ def test_render_value_list_narrow_caps_columns(capsys):
549
549
 
550
550
 
551
551
  def test_short_id():
552
- assert output._short_id("1f58376d-7947-9826") == "1f58…9826"
552
+ assert output._short_id("1f58376d-7947-9826") == "1f58376d-7947-9826"
553
553
  assert output._short_id("short") == "short"
554
554
 
555
555
 
@@ -585,7 +585,7 @@ def test_render_keys_box_and_footer(capsys):
585
585
  def test_render_keys_show_id(capsys):
586
586
  _wide_stdout()
587
587
  output.render_keys([ApiKey(id="1f58376d-7947-9826", name="admin", created_at="2026-06-18T05:14:00Z")], show_id=True)
588
- assert "1f58…9826" in capsys.readouterr().out # short id column when --show-id
588
+ assert "1f58376d-7947-9826" in capsys.readouterr().out
589
589
  output.configure(no_color=True, quiet=False)
590
590
 
591
591
 
@@ -659,7 +659,7 @@ def test_render_users_box_and_footer(capsys):
659
659
  output.users_footer(users)
660
660
  cap = capsys.readouterr()
661
661
  out, err = cap.out, cap.err
662
- assert "users · 3" in out
662
+ assert "USERS · 3" in out
663
663
  for c in ("email", "access", "perms", "joined", "status"):
664
664
  assert c in out
665
665
  assert "root@test" in out and "admin" in out and "06-25" in out
@@ -695,7 +695,7 @@ def test_render_user_show_card_and_perms(capsys):
695
695
  out = capsys.readouterr().out # show is a read view → stdout
696
696
  assert "user" in out and "dev@test" in out and "protected" in out
697
697
  assert "access admin" in out and "3 permissions" in out and "active" in out
698
- assert "permissions · 3" in out and "keys" in out and "dashboards" in out
698
+ assert "PERMISSIONS · 3" in out and "keys" in out and "dashboards" in out
699
699
  output.configure(no_color=True, quiet=False)
700
700
 
701
701
 
@@ -706,7 +706,7 @@ def test_render_user_created_green_card(capsys):
706
706
  err = capsys.readouterr().err # write result → stderr chrome
707
707
  assert "user created" in err and "new@test" in err
708
708
  assert "access read-only" in err and "2 permissions" in err and "active" in err
709
- assert "permissions · 2 · read-only" in err # suffix shows the set
709
+ assert "PERMISSIONS · 2 · read-only" in err # suffix shows the set
710
710
  output.configure(no_color=True, quiet=False)
711
711
 
712
712
 
@@ -718,7 +718,7 @@ def test_render_user_updated_diff(capsys):
718
718
  err = capsys.readouterr().err
719
719
  assert "permissions updated · dev@test" in err
720
720
  assert "now 3" in err and "+1 added" in err and "−1 removed" in err
721
- assert "permissions · 3" in err # NEW set size (struck removal not counted)
721
+ assert "PERMISSIONS · 3" in err # NEW set size (struck removal not counted)
722
722
  assert "+create" in err and "-write" in err # added chip / removed ghost (no-color prefixes)
723
723
  assert "unchanged" in err # legend
724
724
  output.configure(no_color=True, quiet=False)
@@ -1146,7 +1146,7 @@ def test_render_settings_box(capsys):
1146
1146
  output.render_settings(rows)
1147
1147
  cap = capsys.readouterr()
1148
1148
  out, err = cap.out, cap.err
1149
- assert "settings · 3" in out # the count glows in the title
1149
+ assert "SETTINGS · 3" in out
1150
1150
  for c in ("key", "value", "type", "updated"):
1151
1151
  assert c in out
1152
1152
  assert "session_ttl_secs" in out and "86400" in out and "integer" in out
@@ -1231,10 +1231,10 @@ def test_render_incidents_box_and_footer(capsys):
1231
1231
  output.incidents_footer(incs)
1232
1232
  cap = capsys.readouterr()
1233
1233
  out, err = cap.out, cap.err
1234
- assert "issues · 3" in out
1234
+ assert "ISSUES · 3" in out
1235
1235
  for c in ("id", "alert", "severity", "state", "opened", "assignees"):
1236
1236
  assert c in out
1237
- assert "1f58…9826" in out # short id (the handle)
1237
+ assert "1f5803aaaaaabbbbcccc000000009826" in out
1238
1238
  assert "—" in out # manual incident (no alert_name) + no assignees
1239
1239
  assert "firing" in out and "acknowledged" in out and "resolved" in out
1240
1240
  assert "3 issues" in err and "1 firing" in err and "1 resolved" in err # footer distribution
@@ -1271,7 +1271,7 @@ def test_render_incident_show_sections(capsys):
1271
1271
  )
1272
1272
  output.render_incident_show(inc)
1273
1273
  out = capsys.readouterr().out
1274
- assert "p95 latency" in out and "1f58…9826" in out
1274
+ assert "p95 latency" in out and "1f5803aaaaaabbbbcccc000000009826" in out
1275
1275
  assert "acknowledged by ops@example.com" in out and "assigned to a@example.com" in out
1276
1276
  assert "breach" in out and "1240ms" in out
1277
1277
  assert "comments · 2" in out and "on it" in out and "(deleted)" in out
@@ -1357,7 +1357,7 @@ def test_confirm_incident_resolve_headline(capsys, monkeypatch):
1357
1357
  monkeypatch.setattr(output.typer, "confirm", lambda *a, **k: False)
1358
1358
  assert output.confirm_incident_resolve("1f5803aaaaaabbbbcccc000000009826", "p95 latency") is False
1359
1359
  err = capsys.readouterr().err
1360
- assert "resolve issue" in err and "1f58…9826" in err and "(p95 latency)" in err and "this closes it" in err
1360
+ assert "resolve issue" in err and "1f5803aaaaaabbbbcccc000000009826" in err and "(p95 latency)" in err and "this closes it" in err
1361
1361
 
1362
1362
 
1363
1363
  def test_incident_plain_feedback_lines(capsys):
@@ -1374,14 +1374,14 @@ def test_incident_plain_feedback_lines(capsys):
1374
1374
  output.incident_comment_not_found("c0ffee001111")
1375
1375
  output.incident_failed("a@x.com is not an operator")
1376
1376
  err = capsys.readouterr().err
1377
- assert "acknowledged issue 1f58…9826" in err
1378
- assert "resolved issue 1f58…9826" in err
1379
- assert "assigned 1f58…9826" in err and "a@example.com, b@example.com" in err
1380
- assert "cleared assignees on 1f58…9826" in err
1377
+ assert f"acknowledged issue {iid}" in err
1378
+ assert f"resolved issue {iid}" in err
1379
+ assert f"assigned {iid}" in err and "a@example.com, b@example.com" in err
1380
+ assert f"cleared assignees on {iid}" in err
1381
1381
  assert "subscribed you to issue" in err and "unsubscribed x@y.z from issue" in err
1382
1382
  assert "deleted comment" in err
1383
- assert "no issue 1f58…9826" in err and "fp issues list" in err
1384
- assert "no comment" in err and "c0ff…1111" in err
1383
+ assert f"no issue {iid}" in err and "fp issues list" in err
1384
+ assert "no comment" in err and "c0ffee001111" in err
1385
1385
  assert "a@x.com is not an operator" in err
1386
1386
 
1387
1387
 
@@ -1399,7 +1399,7 @@ def test_render_agent_health_configured(capsys):
1399
1399
  _wide_stdout()
1400
1400
  output.render_agent_health(configured=True, default_model="claude-x", model_count=3)
1401
1401
  out = capsys.readouterr().out # data view → stdout
1402
- assert "assistant" in out and "configured" in out
1402
+ assert "ASSISTANT" in out and "configured" in out
1403
1403
  assert "default model claude-x" in out and "3 models available" in out
1404
1404
  output.configure(no_color=True, quiet=False)
1405
1405
 
@@ -1417,7 +1417,7 @@ def test_render_agent_models_marks_default(capsys):
1417
1417
  _wide_stdout()
1418
1418
  output.render_agent_models(["m-default", "m-fast"], default_model="m-default")
1419
1419
  out = capsys.readouterr().out
1420
- assert "models · 2" in out and "m-default" in out and "m-fast" in out and "default" in out
1420
+ assert "MODELS · 2" in out and "m-default" in out and "m-fast" in out and "default" in out
1421
1421
  output.configure(no_color=True, quiet=False)
1422
1422
 
1423
1423
 
@@ -1425,7 +1425,7 @@ def test_render_agent_models_empty(capsys):
1425
1425
  _wide_stdout()
1426
1426
  output.render_agent_models([], default_model=None)
1427
1427
  out = capsys.readouterr().out
1428
- assert "models · 0" in out and "no models reported" in out
1428
+ assert "MODELS · 0" in out and "no models reported" in out
1429
1429
  output.configure(no_color=True, quiet=False)
1430
1430
 
1431
1431
 
@@ -1438,12 +1438,12 @@ def test_render_agent_chats_box(capsys):
1438
1438
  output.render_agent_chats(chats)
1439
1439
  cap = capsys.readouterr()
1440
1440
  out, err = cap.out, cap.err
1441
- assert "chats · 2" in out # the count glows in the title
1441
+ assert "CHATS · 2" in out
1442
1442
  for c in ("chat-id", "title", "messages", "updated"):
1443
1443
  assert c in out
1444
1444
  assert "older" in out and "newer" in out
1445
1445
  assert "07854990" in out and "17bf35c3" in out # short copy-friendly handle (first 8)
1446
- assert "dade" not in out # the rest of the id is not shown
1446
+ assert "07854990-dade-4dea-aaaa" in out
1447
1447
  assert out.index("newer") < out.index("older") # newest first
1448
1448
  assert "open one with" not in err # the footer hint was removed
1449
1449
  output.configure(no_color=True, quiet=False)
@@ -1492,7 +1492,7 @@ def test_render_agent_delete_preview_and_feedback(capsys):
1492
1492
  output.print_cancelled("nothing deleted") # boxed cancel, like the command uses
1493
1493
  err = capsys.readouterr().err
1494
1494
  assert "delete chat" in err and "perf review" in err and "4 messages" in err
1495
- assert "07854990" in err and "dade" not in err # short id in the preview
1495
+ assert "07854990-dade" in err # full id in the preview
1496
1496
  assert "deleted chat" in err and "perf review" in err # boxed deleted
1497
1497
  assert "cancelled" in err and "nothing deleted" in err # boxed cancel
1498
1498
  output.configure(no_color=True, quiet=False)
@@ -1517,7 +1517,7 @@ def test_agent_ask_chrome_lines(capsys):
1517
1517
  output.agent_unconfigured_note()
1518
1518
  err = capsys.readouterr().err
1519
1519
  assert "used tool: run_query" in err
1520
- assert "new chat" in err and 'fp agent ask --chat 07854990 "…"' in err # short id, positional msg
1520
+ assert "new chat" in err and 'fp agent ask --chat 07854990-dade-4dea "…"' in err
1521
1521
  assert "assistant error: boom" in err
1522
1522
  assert "chat not found" in err and "fp agent chats" in err # boxed not-found
1523
1523
  assert "isn't configured" in err and "fp agent health" in err
@@ -1642,7 +1642,7 @@ def test_policy_list_counts_policies_and_captions_versions(capsys):
1642
1642
  _wide_stdout()
1643
1643
  output.render_policies([pv("a", 1), pv("b", 2), pv("b", 1)])
1644
1644
  out = capsys.readouterr().out
1645
- assert "policies · 2 · 3 versions" in out
1645
+ assert "POLICIES · 2 · 3 versions" in out
1646
1646
  # newest version of each policy first, rather than server order
1647
1647
  b_rows = [ln for ln in out.splitlines() if "b" in ln and "v" in ln
1648
1648
  and ("v1" in ln or "v2" in ln)]
@@ -1657,7 +1657,7 @@ def test_policy_list_omits_the_caption_when_each_policy_has_one_version(capsys):
1657
1657
  source=None, created_at="", created_by=None,
1658
1658
  disabled=False, archived=False)])
1659
1659
  out = capsys.readouterr().out
1660
- assert "policies · 1" in out and "versions" not in out
1660
+ assert "POLICIES · 1" in out and "versions" not in out
1661
1661
 
1662
1662
 
1663
1663
  # ─────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,144 @@
1
+ import json
2
+ from io import StringIO
3
+
4
+ import pytest
5
+ import respx
6
+ from rich.console import Console
7
+
8
+ from fp_cli import output
9
+ from fp_cli.app import app
10
+
11
+ BASE = "http://dash.test"
12
+ SOURCE = 'EvalResult(score=Score(0.7), reasoning="Tool usage")'
13
+
14
+
15
+ @pytest.mark.parametrize("command", ["create", "deploy"])
16
+ @pytest.mark.parametrize("force", [False, True])
17
+ @pytest.mark.parametrize("json_mode", [False, True])
18
+ @pytest.mark.parametrize("width", [80, 140])
19
+ def test_failed_preview_stops_unless_force(logged_in, runner, tmp_path, monkeypatch, command, force, json_mode, width):
20
+ monkeypatch.setenv("COLUMNS", str(width))
21
+ file = tmp_path / "source.py"
22
+ file.write_text(SOURCE)
23
+ old = {"id": "old", "eval_key": "tool_usage", "eval_version": "1", "enabled": True}
24
+ preview = {
25
+ "runs": [
26
+ {"session_id": "run-check", "ok": True,
27
+ "result": {"score": {"value": 0.7}, "reasoning": "Seven tool calls"}},
28
+ {"session_id": "failed-check", "ok": False, "error_kind": "invalid_result",
29
+ "error_message": "Missing result score"},
30
+ ],
31
+ "summary": {"succeeded": 1, "failed": 1, "skipped": 0},
32
+ }
33
+ capture = StringIO()
34
+ original_configure = output.configure
35
+
36
+ def configure_capture(**kwargs):
37
+ original_configure(**kwargs)
38
+ if not json_mode:
39
+ output._stdout = Console(file=capture, width=width, no_color=True)
40
+
41
+ monkeypatch.setattr(output, "configure", configure_capture)
42
+ with respx.mock(assert_all_called=False) as mock:
43
+ mock.get(BASE + "/api/evaluator-definitions").respond(200, json=[old])
44
+ mock.post(BASE + "/api/evaluator-definitions/dry-run").respond(200, json=preview)
45
+ created = {
46
+ "id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True,
47
+ }
48
+ create = mock.post(BASE + "/api/evaluator-definitions")
49
+ patch = mock.patch(BASE + "/api/evaluator-definitions/old").respond(200, json={})
50
+
51
+ def preview_happened_first(request):
52
+ # Data must already be visible to the user when creation starts.
53
+ if not json_mode:
54
+ assert "Missing result score" in capture.getvalue()
55
+ assert "Seven tool calls" in capture.getvalue()
56
+ import httpx
57
+ return httpx.Response(201, json=created)
58
+
59
+ create.mock(side_effect=preview_happened_first)
60
+ flags = ["--key", "tool_usage", "--name", "Tool usage"] if command == "create" else ["tool_usage"]
61
+ result = runner.invoke(app, [
62
+ *(["--json"] if json_mode else []), "evaluators", command, *flags,
63
+ "--code", str(file), "--dry-run-on", "run-check", "--dry-run-on", "failed-check",
64
+ *(["--force"] if force else []),
65
+ ])
66
+ assert result.exit_code == (0 if force else 1), result.output
67
+ assert create.called is force
68
+ assert patch.called is (force and command == "deploy")
69
+ if json_mode:
70
+ payload = json.loads(result.stdout)
71
+ if force:
72
+ assert payload == (
73
+ {"created": {"id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True}, "disabled": ["old"]}
74
+ if command == "deploy" else {"id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True}
75
+ )
76
+ else:
77
+ assert payload["exit_code"] == 1
78
+ assert "Missing result score" in payload["error"]
79
+ else:
80
+ rendered = capture.getvalue()
81
+ assert "failed-check · failed" in rendered
82
+ assert "Missing result score" in rendered
83
+ if force:
84
+ assert "--force" in result.stderr
85
+ assert rendered.index("Missing result score") < rendered.index(
86
+ "deployed tool_usage" if command == "deploy" else "EVALUATOR CREATED")
87
+
88
+
89
+ @pytest.mark.parametrize("width", [80, 140])
90
+ def test_preview_skip_has_deliberate_outcome(logged_in, runner, tmp_path, monkeypatch, width):
91
+ monkeypatch.setenv("COLUMNS", str(width))
92
+ file = tmp_path / "source.py"
93
+ file.write_text(SOURCE)
94
+ with respx.mock as mock:
95
+ mock.post(BASE + "/api/evaluator-definitions/dry-run").respond(200, json={
96
+ "runs": [{"session_id": "run-check", "ok": True, "applicable": False, "result": None}],
97
+ "summary": {"succeeded": 0, "failed": 0, "skipped": 1},
98
+ })
99
+ result = runner.invoke(app, ["evaluators", "dry-run", "--code", str(file), "--session", "run-check"])
100
+ assert result.exit_code == 0, result.output
101
+ assert "○ run-check · skipped" in result.stdout
102
+
103
+
104
+ @pytest.mark.parametrize("key_mode", [False, True])
105
+ def test_deploy_file_without_key(logged_in, runner, tmp_path, key_mode):
106
+ file = tmp_path / "spec.json"
107
+ file.write_text(json.dumps({"display_name": "Tool usage", "evaluator_source": SOURCE, "eval_version": "2"}))
108
+ prefix = "/v1" if key_mode else "/api"
109
+ with respx.mock as mock:
110
+ mock.get(BASE + prefix + "/evaluator-definitions").respond(200, json=[])
111
+ create = mock.post(BASE + prefix + "/evaluator-definitions").respond(201, json={"id": "new"})
112
+ result = runner.invoke(app, [
113
+ "--json", *(["--api-key", "key"] if key_mode else []),
114
+ "evaluators", "deploy", "tool_usage", "--file", str(file),
115
+ ])
116
+ assert result.exit_code == 0, result.output
117
+ assert json.loads(create.calls[0].request.content)["eval_key"] == "tool_usage"
118
+ assert json.loads(result.stdout) == {"created": {"id": "new"}, "disabled": []}
119
+
120
+
121
+ @pytest.mark.parametrize("command", [["plan"], ["plan", "claim", "team_offer"]])
122
+ @pytest.mark.parametrize("json_mode", [False, True])
123
+ def test_plan_needs_signed_in_person_before_http(logged_in, runner, command, json_mode):
124
+ with respx.mock as mock:
125
+ result = runner.invoke(app, ["--api-key", "key", *(["--json"] if json_mode else []), *command])
126
+ assert not mock.calls
127
+ assert result.exit_code == 2
128
+ expected = f"`fp {' '.join(command[:2])}` needs a signed-in session: run `fp login`"
129
+ assert expected in result.output
130
+
131
+
132
+ @pytest.mark.parametrize("key_mode", [False, True])
133
+ @pytest.mark.parametrize("body,expected", [
134
+ ({}, "not available on this FailproofAI Cloud yet"),
135
+ ({"error": "unknown route"}, "not available on this FailproofAI Cloud yet"),
136
+ ({"error": "setting not found"}, "setting not found"),
137
+ ])
138
+ def test_setting_key_404_on_older_cloud(logged_in, runner, key_mode, body, expected):
139
+ prefix = "/v1" if key_mode else "/api"
140
+ with respx.mock as mock:
141
+ mock.put(BASE + prefix + "/settings/otel.ingest_enabled").respond(404, json=body)
142
+ result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), "otel", "enable"])
143
+ assert result.exit_code == 6, result.output
144
+ assert json.loads(result.stdout)["error"] == expected
@@ -0,0 +1,80 @@
1
+ import json
2
+ from io import StringIO
3
+
4
+ from rich.console import Console
5
+
6
+ from fp_cli import output, theme
7
+ from fp_cli.models import AgentEvent
8
+
9
+
10
+ def test_two_accent_palette():
11
+ assert theme.ACCENT == "#66d1b5"
12
+ assert theme.PINK == "#e4587d"
13
+ assert {theme.SUCCESS, theme.ERROR, theme.AMBER, theme.BLUE, theme.SCORE_HIGH} <= {theme.ACCENT, theme.PINK}
14
+
15
+
16
+ def test_permission_risk_order_is_independent_of_shared_colours():
17
+ assert theme.PERM_WRITE == theme.PERM_ACTION == theme.PERM_DANGER
18
+ assert sorted(["delete", "run", "write", "read"], key=output._perm_act_key) == [
19
+ "read", "write", "run", "delete",
20
+ ]
21
+
22
+
23
+ def test_open_table_and_header(capsys):
24
+ output.configure(no_color=True)
25
+ output.print_table(["Id", "URL"], [["run-001", "https://cloud.example.test"]], title="Resources")
26
+ text = capsys.readouterr().out
27
+ assert "failproof ai v" in text
28
+ assert "RESOURCES" in text
29
+ assert not any(c in text for c in "╭╮╰╯┌┐└┘─━│┃")
30
+
31
+
32
+ def test_legacy_panels_use_open_screen(capsys):
33
+ output.configure(no_color=True)
34
+ output.render_events([AgentEvent(id=1, session_id="run-001", agent_id="agent", event_type="tool_use", ts="2026-10-13T00:00:00Z")])
35
+ text = capsys.readouterr().out
36
+ assert "EVENTS" in text
37
+ assert "failproof ai v" in text
38
+ assert not any(c in text for c in "╭╮╰╯┌┐└┘─━│┃")
39
+
40
+
41
+ def test_narrow_ids_and_urls_are_not_cut(capsys):
42
+ output.configure(no_color=True)
43
+ output._stdout = Console(width=45, no_color=True)
44
+ id = "session-001-abcdefghijklmnopqrstuvwxyz-123456789"
45
+ url = "https://cloud.example.test/organizations/reliability/sessions/" + id
46
+ output.print_table(["Id", "URL"], [[id, url]])
47
+ compact = "".join(capsys.readouterr().out.split())
48
+ assert id in compact and url in compact
49
+ assert "…" not in compact
50
+
51
+
52
+ def test_json_bytes_unchanged(capsys):
53
+ output.configure(json=True)
54
+ data = {"session_id": "run-001", "scores": {"quality": 0.9}}
55
+ output.emit_json(data)
56
+ assert capsys.readouterr().out == json.dumps(data, indent=2, ensure_ascii=False) + "\n"
57
+
58
+
59
+ def test_generated_help_has_no_boxes(logged_in, runner):
60
+ from fp_cli.app import app
61
+ result = runner.invoke(app, ["evaluators", "create", "--help"])
62
+ assert result.exit_code == 0
63
+ assert "Code:" in result.stdout and "Judge:" in result.stdout and "Jev:" in result.stdout
64
+ assert not any(c in result.stdout for c in "╭╮╰╯┌┐└┘─━│┃")
65
+
66
+
67
+ def test_shared_key_value_labels_are_grey():
68
+ capture = StringIO()
69
+ output.configure()
70
+ output._stdout = Console(file=capture, width=140, force_terminal=True, color_system="truecolor")
71
+ output.print_table(["Field", "Value"], [["enabled", "true"]])
72
+ assert "\x1b[38;2;118;127;139menabled" in capture.getvalue()
73
+
74
+
75
+ def test_help_typeable_commands_are_pink():
76
+ capture = StringIO()
77
+ output.configure()
78
+ output._stdout = Console(file=capture, width=140, force_terminal=True, color_system="truecolor")
79
+ output.render_top_level_help()
80
+ assert "\x1b[38;2;228;88;125mfp login" in capture.getvalue()
@@ -0,0 +1,33 @@
1
+ import pytest
2
+ import respx
3
+
4
+ from fp_cli.app import app
5
+
6
+
7
+ @pytest.mark.parametrize("key_mode", [False, True])
8
+ @pytest.mark.parametrize("status", [200, 404])
9
+ def test_re_evaluate(logged_in, runner, key_mode, status):
10
+ prefix = "v1" if key_mode else "api"
11
+ with respx.mock:
12
+ route = respx.post(f"http://dash.test/{prefix}/sessions/run-001/re-evaluate").respond(
13
+ status, json={"queued": True} if status == 200 else {"error": "unknown route"},
14
+ )
15
+ result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), "sessions", "re-evaluate", "run-001"])
16
+ assert route.called
17
+ assert result.exit_code == (0 if status == 200 else 6), result.output
18
+ if status == 404:
19
+ assert "not available on this FailproofAI Cloud yet" in result.stdout
20
+
21
+
22
+ @pytest.mark.parametrize("key_mode", [False, True])
23
+ @pytest.mark.parametrize("error_body", [{}, {"error": "session not found"}])
24
+ def test_re_evaluate_missing_session(logged_in, runner, key_mode, error_body):
25
+ prefix = "v1" if key_mode else "api"
26
+ with respx.mock as mock:
27
+ mock.post(f"http://dash.test/{prefix}/sessions/missing/re-evaluate").respond(404, json=error_body)
28
+ result = runner.invoke(app, [
29
+ "--json", *(["--api-key", "key"] if key_mode else []), "sessions", "re-evaluate", "missing",
30
+ ])
31
+ assert result.exit_code == 6, result.output
32
+ assert "not found" in result.stdout
33
+ assert "not available on this FailproofAI Cloud yet" not in result.stdout