failproofai 1.0.12-beta.1 → 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 (236) 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 +2 -2
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  60. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__167mipqmjprq5._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0b78b0j8iyv6l._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_1a3tubds7s7ow._.js → _0ouu886szh6yj._.js} +4 -4
  75. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_1d98fyfrcaqhy._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.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 +2 -2
  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_1i8oid1gc7s1f.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  90. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  91. package/.next/standalone/.next/server/pages/404.html +1 -1
  92. package/.next/standalone/.next/server/pages/500.html +1 -1
  93. package/.next/standalone/.next/server/preview-props.json +3 -3
  94. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  96. package/.next/standalone/.next/static/chunks/{1b32-afqacaen.js → 0el9n3h9_zp5m.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
  98. package/.next/standalone/.next/static/chunks/{42e8zab314654.js → 0umzct31duk5g.js} +1 -1
  99. package/.next/standalone/.next/static/chunks/{24w3x2da9sk49.js → 0wobqgosirs8p.js} +1 -1
  100. package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
  101. package/.next/standalone/.next/static/chunks/{2-tf6_7u623kd.js → 1kduj74-715jh.js} +1 -1
  102. package/.next/standalone/.next/static/chunks/2701amhrbsbot.js +1 -0
  103. package/.next/standalone/.next/static/chunks/{18433qz-8njv3.js → 27cavoc8paelg.js} +1 -1
  104. package/.next/standalone/.next/static/chunks/{0_zfad4co-t2z.js → 2ve23a7keupjw.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/3bv6xaoitjpnh.js +6 -0
  106. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  107. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  108. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +28 -18
  109. package/.next/standalone/fp-cloud-cli/README.md +127 -9
  110. package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
  111. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +199 -34
  112. package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
  113. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
  114. package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
  115. package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
  116. package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
  117. package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
  118. package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
  119. package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
  120. package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
  121. package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
  122. package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
  123. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +12 -216
  124. package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
  125. package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
  126. package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
  127. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
  128. package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
  129. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
  130. package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
  131. package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
  132. package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
  133. package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
  134. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +7 -53
  135. package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
  136. package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
  137. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -152
  138. package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
  139. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
  140. package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
  141. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +173 -22
  142. package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
  143. package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
  144. package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
  145. package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
  146. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +32 -197
  147. package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
  148. package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
  149. package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
  150. package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
  151. package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
  152. package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
  153. package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
  154. package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
  155. package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
  156. package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
  157. package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
  158. package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
  159. package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
  160. package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
  161. package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
  162. package/.next/standalone/hermes-plugin/__init__.py +11 -0
  163. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  164. package/.next/standalone/package.json +11 -10
  165. package/.next/standalone/sdk/python/uv.lock +31 -8
  166. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
  167. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
  168. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
  169. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  170. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
  171. package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
  172. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
  173. package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
  174. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
  175. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
  176. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
  177. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
  178. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
  179. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
  180. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
  181. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
  182. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
  183. package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
  184. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
  185. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
  186. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
  187. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
  188. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
  189. package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
  190. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
  191. package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
  192. package/.next/standalone/server.js +1 -1
  193. package/dist/cli.mjs +1760 -638
  194. package/dist/worker.mjs +426 -76
  195. package/hermes-plugin/__init__.py +11 -0
  196. package/hermes-plugin/plugin.yaml +1 -1
  197. package/package.json +11 -10
  198. package/src/audit/open-browser.ts +24 -7
  199. package/src/hooks/agent-roster.ts +9 -1
  200. package/src/hooks/agent-skill.ts +206 -0
  201. package/src/hooks/cloud-connection.ts +27 -51
  202. package/src/hooks/cloud-introspect.ts +5 -2
  203. package/src/hooks/cloud-login.ts +248 -0
  204. package/src/hooks/cloud-managed-policies.ts +54 -4
  205. package/src/hooks/codex-system-hooks.ts +10 -4
  206. package/src/hooks/config-status.ts +9 -3
  207. package/src/hooks/configure-wizard.ts +356 -32
  208. package/src/hooks/delivery-health.ts +47 -2
  209. package/src/hooks/effective-reviewers.ts +14 -0
  210. package/src/hooks/fp-config.ts +19 -0
  211. package/src/hooks/fp-home.ts +1 -1
  212. package/src/hooks/handler.ts +43 -5
  213. package/src/hooks/hook-activity-store.ts +7 -1
  214. package/src/hooks/integrations.ts +18 -3
  215. package/src/hooks/jev-activity.ts +117 -1
  216. package/src/hooks/jev-cli.ts +7 -9
  217. package/src/hooks/manager.ts +80 -46
  218. package/src/hooks/policy-evaluator.ts +17 -0
  219. package/src/hooks/policy-reviewability.ts +4 -2
  220. package/src/hooks/semantic/cloud-jev.ts +219 -28
  221. package/src/hooks/semantic/combine.ts +26 -1
  222. package/src/hooks/semantic/intent.ts +127 -24
  223. package/src/hooks/semantic/jev-client.ts +1 -1
  224. package/src/hooks/semantic/jev-config.ts +6 -27
  225. package/src/hooks/semantic/jev-review.ts +111 -4
  226. package/src/hooks/tui.ts +280 -0
  227. package/src/hooks/uninstall-cli.ts +12 -1
  228. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0588st-tpiuia._.js +0 -3
  229. package/.next/standalone/.next/server/chunks/ssr/_128ebxr83nk6g._.js +0 -3
  230. package/.next/standalone/.next/static/chunks/0jutamwqz8pg5.js +0 -1
  231. package/.next/standalone/.next/static/chunks/114hbolvrv94e.js +0 -1
  232. package/.next/standalone/.next/static/chunks/1p-hsi58i-6h0.js +0 -6
  233. package/.next/standalone/.next/static/chunks/3-pku9qwv1yfu.js +0 -1
  234. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
  235. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
  236. /package/.next/standalone/.next/static/{xq8UgF5yFxty1VWxY2hyn → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
@@ -0,0 +1,266 @@
1
+ """Evaluator API bodies: validate data, never execute customer Python locally."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import json
7
+ import math
8
+ import re
9
+ from typing import Any, Optional
10
+
11
+ from . import _click_compat as click
12
+ from .commands._write import read_text_arg
13
+
14
+ FIELDS = frozenset({
15
+ "eval_key", "display_name", "eval_version", "result_kind", "labels",
16
+ "condition_source", "evaluator_source", "timeout_seconds",
17
+ })
18
+
19
+
20
+ def fail(message: str) -> None:
21
+ raise click.UsageError(message)
22
+
23
+
24
+ def text(value: Any, name: str) -> str:
25
+ if not isinstance(value, str) or not value.strip():
26
+ fail(f"{name} must be a non-empty string")
27
+ return value
28
+
29
+
30
+ def threshold(value: Any) -> None:
31
+ if value is not None and (
32
+ isinstance(value, bool) or not isinstance(value, (int, float))
33
+ or not math.isfinite(value) or not 0 <= value <= 1
34
+ ):
35
+ fail("threshold must be between 0 and 1")
36
+
37
+
38
+ def validate_labels(labels: Any) -> None:
39
+ if not isinstance(labels, list) or len(labels) > 20 or any(
40
+ not isinstance(v, str) or not v.strip() or len(v.encode()) > 64
41
+ or any(ord(c) < 32 or 127 <= ord(c) <= 159 for c in v) for v in labels
42
+ ):
43
+ fail("labels must contain at most 20 non-empty strings, up to 64 bytes each, without control characters")
44
+
45
+
46
+ def source_kind(body: dict) -> str:
47
+ source = body.get("evaluator_source", "")
48
+ try:
49
+ envelope = json.loads(source)
50
+ except (ValueError, TypeError):
51
+ return "code"
52
+ return envelope.get("kind", "code") if isinstance(envelope, dict) else "code"
53
+
54
+
55
+ def binary_judge_step(step: str) -> bool:
56
+ """Mirror the server's literal scoring check, plus natural-language pairs.
57
+
58
+ The six score phrases match evaluator_definitions.rs::steps_are_binary_scored.
59
+ Whitespace normalization and equivalent directives catch binary rubrics without
60
+ rejecting ordinary continuous wording such as "score between 0 and 1".
61
+ """
62
+ lowered = " ".join(step.lower().split())
63
+ if any(phrase in lowered for phrase in (
64
+ "score 0", "score 1", "score of 0", "score of 1", "score: 0", "score: 1",
65
+ )):
66
+ return True
67
+ return bool(re.search(
68
+ r"\bbinary\b"
69
+ r"|\bscore(?:\s+of)?\s*[:=]?\s*(?:0|1|zero|one)\b"
70
+ r"|\b(?:yes\s*(?:/|or|and|versus|vs\.?|-)\s*no"
71
+ r"|no\s*(?:/|or|and|versus|vs\.?|-)\s*yes"
72
+ r"|true\s*(?:/|or|and|versus|vs\.?|-)\s*false"
73
+ r"|false\s*(?:/|or|and|versus|vs\.?|-)\s*true"
74
+ r"|pass\s*(?:/|or|and|versus|vs\.?|-)\s*fail"
75
+ r"|fail\s*(?:/|or|and|versus|vs\.?|-)\s*pass)\b"
76
+ r"|\b(?:give|assign|award|return|output)\s+"
77
+ r"(?:(?:a\s+)?(?:score|value)(?:\s+of)?\s*[:=]?\s*)?"
78
+ r"(?:0(?:\.0+)?|1(?:\.0+)?|zero|one|true|false|yes|no|pass|fail)"
79
+ r"\s+(?:if|when|otherwise|for)\b",
80
+ lowered,
81
+ ))
82
+
83
+
84
+ def validate(body: dict, *, complete: bool = True) -> dict:
85
+ unknown = set(body) - FIELDS
86
+ if unknown:
87
+ fail(f"Unknown spec field(s): {', '.join(sorted(unknown))}; use the evaluator API body")
88
+ if complete:
89
+ key = text(body.get("eval_key"), "eval_key")
90
+ if not re.fullmatch(r"[a-z][a-z0-9_]*", key) or len(key.encode()) > 128:
91
+ fail("eval_key must match ^[a-z][a-z0-9_]*$")
92
+ name = text(body.get("display_name"), "display_name")
93
+ version = text(body.get("eval_version"), "eval_version")
94
+ if len(name.encode()) > 128 or len(version.encode()) > 128 or any(c.isspace() for c in version):
95
+ fail("display_name and eval_version are limited to 128 bytes; versions cannot contain whitespace")
96
+ elif body.get("eval_key") is not None and not re.fullmatch(r"[a-z][a-z0-9_]*", str(body["eval_key"])):
97
+ fail("eval_key must match ^[a-z][a-z0-9_]*$")
98
+ result_kind = body.get("result_kind", "score")
99
+ if result_kind not in ("score", "metric", "assertion"):
100
+ fail("result_kind must be score, metric or assertion")
101
+ labels = body.get("labels", [])
102
+ validate_labels(labels)
103
+ timeout = body.get("timeout_seconds")
104
+ if timeout is not None and (
105
+ isinstance(timeout, bool) or not isinstance(timeout, (int, float))
106
+ or not math.isfinite(timeout) or not 0.001 <= timeout <= 3600
107
+ ):
108
+ fail("timeout_seconds must be finite and between 0.001 and 3600")
109
+ condition = body.get("condition_source")
110
+ if condition is not None:
111
+ text(condition, "condition_source")
112
+ if len(condition.encode()) > 16 * 1024:
113
+ fail("condition_source is limited to 16 KiB")
114
+ expression(condition, "condition_source")
115
+ source = text(body.get("evaluator_source"), "evaluator_source")
116
+ if len(source.encode()) > 128 * 1024:
117
+ fail("evaluator_source is limited to 128 KiB")
118
+ kind = source_kind(body)
119
+ if kind == "code":
120
+ expression(source, "evaluator_source")
121
+ return body
122
+ envelope = json.loads(source)
123
+ if kind not in ("judge", "jev"):
124
+ fail("evaluator_source kind must be judge or jev")
125
+ if result_kind != "score":
126
+ fail("judge/Jev evaluators require result_kind score")
127
+ threshold(envelope.get("threshold"))
128
+ if kind == "judge":
129
+ text(envelope.get("criteria"), "judge criteria")
130
+ steps = envelope.get("steps")
131
+ if steps is not None:
132
+ if not isinstance(steps, list):
133
+ fail("judge steps must be a list")
134
+ for step in steps:
135
+ text(step, "judge step")
136
+ if binary_judge_step(step):
137
+ fail("binary judge steps are not supported; use graded evaluation steps")
138
+ else:
139
+ text(envelope.get("instructions"), "Jev instructions")
140
+ criteria = envelope.get("criteria")
141
+ if envelope.get("type") == "noul":
142
+ if not isinstance(criteria, dict) or set(criteria) != {"true", "false"}:
143
+ fail("Jev noul criteria must contain true and false")
144
+ text(criteria["true"], "true criterion")
145
+ text(criteria["false"], "false criterion")
146
+ if criteria["true"].strip() == criteria["false"].strip():
147
+ fail("Jev true and false criteria must be distinct")
148
+ elif envelope.get("type") == "score":
149
+ if not isinstance(criteria, list) or not 3 <= len(criteria) <= 5:
150
+ fail("Jev score needs 3–5 distinct levels, ordered worst to best")
151
+ for level in criteria:
152
+ text(level, "Jev level")
153
+ if len({v.strip().lower() for v in criteria}) != len(criteria):
154
+ fail("Jev score levels must be distinct")
155
+ else:
156
+ fail("Jev type must be noul or score")
157
+ return body
158
+
159
+
160
+ def from_draft(data: dict, requested_kind: str) -> dict:
161
+ """Convert the dashboard's EvalDraft (UI fields) to the immutable API body."""
162
+ draft = data.get("spec", data)
163
+ if isinstance(draft, str):
164
+ try:
165
+ draft = json.loads(draft)
166
+ except ValueError:
167
+ fail("Cloud draft was not an evaluator JSON spec")
168
+ if not isinstance(draft, dict):
169
+ fail("Cloud draft was not an evaluator JSON spec")
170
+ body = {k: v for k, v in draft.items() if k in FIELDS}
171
+ if not body.get("condition_source"):
172
+ body.pop("condition_source", None)
173
+ body.setdefault("eval_version", "1")
174
+ kind = draft.get("kind", source_kind(body))
175
+ if kind != requested_kind:
176
+ fail(f"Cloud drafted {kind}, not requested {requested_kind}; retry with a more specific intent")
177
+ if kind == "judge" and source_kind(body) != "judge":
178
+ body["evaluator_source"] = json.dumps({
179
+ "kind": "judge", "criteria": draft.get("criteria"),
180
+ "threshold": draft.get("threshold"), "steps": draft.get("steps"),
181
+ })
182
+ elif kind == "jev" and source_kind(body) != "jev":
183
+ body["evaluator_source"] = json.dumps({
184
+ "kind": "jev", "type": draft.get("jevType"), "instructions": draft.get("instructions"),
185
+ "criteria": draft.get("jevCriteria"), "threshold": draft.get("threshold"),
186
+ })
187
+ return validate(body)
188
+
189
+
190
+ def expression(source: str, name: str) -> None:
191
+ try:
192
+ ast.parse(source.strip(), mode="eval")
193
+ except SyntaxError as exc:
194
+ fail(f"{name} must be a restricted Python expression: {exc.msg}")
195
+
196
+
197
+ def build(
198
+ *, key=None, name=None, version=None, label=None, condition=None,
199
+ code=None, result_kind=None, timeout=None, judge=False, criteria=None,
200
+ step=None, threshold_value=None, jev=None, instructions=None,
201
+ true=None, false=None, level=None, file=None, complete=True,
202
+ ) -> dict:
203
+ kinds = sum((bool(code), bool(judge), bool(jev), bool(file)))
204
+ if kinds != 1:
205
+ fail("Choose exactly one source: --code, --judge, --jev or --file")
206
+ source_flags = (criteria, step, threshold_value, instructions, true, false, level)
207
+ if file:
208
+ if any(v is not None for v in (key, name, version, label, condition, result_kind, timeout, *source_flags)):
209
+ fail("Pass either --file or discrete spec flags, not both")
210
+ try:
211
+ body = json.loads(read_text_arg(file))
212
+ except ValueError as exc:
213
+ fail(f"--file is not valid JSON: {exc}")
214
+ if not isinstance(body, dict):
215
+ fail("--file must contain a JSON object")
216
+ return validate(body, complete=complete)
217
+ if code:
218
+ if any(v is not None for v in source_flags):
219
+ fail("Judge/Jev flags cannot be used with --code")
220
+ source = read_text_arg(code, flag="--code")
221
+ elif judge:
222
+ if any(v is not None for v in (instructions, true, false, level)):
223
+ fail("Jev flags cannot be used with --judge")
224
+ source = json.dumps({"kind": "judge", "criteria": criteria, "threshold": threshold_value, "steps": step})
225
+ else:
226
+ if criteria is not None or step is not None:
227
+ fail("Judge flags cannot be used with --jev")
228
+ if jev == "noul" and level is not None or jev == "score" and (true is not None or false is not None):
229
+ fail("Use --true/--false for Jev noul; --level for Jev score")
230
+ source = json.dumps({
231
+ "kind": "jev", "type": jev, "instructions": instructions,
232
+ "criteria": {"true": true, "false": false} if jev == "noul" else level,
233
+ "threshold": threshold_value,
234
+ })
235
+ body = {
236
+ "result_kind": result_kind or "score", "labels": label or [],
237
+ "evaluator_source": source,
238
+ }
239
+ if key is not None:
240
+ body["eval_key"] = key
241
+ if name is not None:
242
+ body["display_name"] = name
243
+ if version is not None:
244
+ body["eval_version"] = version
245
+ if condition is not None:
246
+ body["condition_source"] = read_text_arg(condition[1:], flag="--condition") if condition.startswith("@") else condition
247
+ if timeout is not None:
248
+ body["timeout_seconds"] = timeout
249
+ if complete:
250
+ body.setdefault("eval_version", "1")
251
+ return validate(body, complete=complete)
252
+
253
+
254
+ def dry_run_body(body: dict, sessions: Optional[list]) -> dict:
255
+ if len(sessions or []) > 10:
256
+ fail("dry-run accepts at most 10 sessions")
257
+ if any(not isinstance(s, str) or not s.strip() or len(s.encode()) > 256 for s in (sessions or [])):
258
+ fail("dry-run session ids must be non-empty, up to 256 bytes")
259
+ if source_kind(body) == "judge":
260
+ fail("Cloud refuses judge dry-runs; create/deploy without --dry-run-on and inspect evaluation results instead")
261
+ return {
262
+ **{k: v for k, v in body.items() if k in (
263
+ "evaluator_source", "condition_source", "timeout_seconds", "result_kind", "eval_key",
264
+ )},
265
+ "session_ids": sessions or [],
266
+ }