@kindgi/api 0.1.2 → 0.1.4-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/agent-binding.d.ts +18 -4
  2. package/dist/agent-binding.d.ts.map +1 -1
  3. package/dist/agent-pins.d.ts +48 -0
  4. package/dist/agent-pins.d.ts.map +1 -0
  5. package/dist/agent-pins.js +102 -0
  6. package/dist/agent-pins.js.map +1 -0
  7. package/dist/app.d.ts +22 -0
  8. package/dist/app.d.ts.map +1 -1
  9. package/dist/app.js +22 -3
  10. package/dist/app.js.map +1 -1
  11. package/dist/block-binding.d.ts +132 -0
  12. package/dist/block-binding.d.ts.map +1 -0
  13. package/dist/block-binding.js +4 -0
  14. package/dist/block-binding.js.map +1 -0
  15. package/dist/block-pins.d.ts +22 -0
  16. package/dist/block-pins.d.ts.map +1 -0
  17. package/dist/block-pins.js +112 -0
  18. package/dist/block-pins.js.map +1 -0
  19. package/dist/cost-binding.d.ts +100 -5
  20. package/dist/cost-binding.d.ts.map +1 -1
  21. package/dist/cost-binding.js +10 -0
  22. package/dist/cost-binding.js.map +1 -1
  23. package/dist/deploy-versions.d.ts +58 -0
  24. package/dist/deploy-versions.d.ts.map +1 -0
  25. package/dist/deploy-versions.js +91 -0
  26. package/dist/deploy-versions.js.map +1 -0
  27. package/dist/deployment-binding.d.ts +24 -3
  28. package/dist/deployment-binding.d.ts.map +1 -1
  29. package/dist/derive-agent-version.d.ts +69 -0
  30. package/dist/derive-agent-version.d.ts.map +1 -0
  31. package/dist/derive-agent-version.js +139 -0
  32. package/dist/derive-agent-version.js.map +1 -0
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +17 -0
  35. package/dist/errors.js.map +1 -1
  36. package/dist/eval-case-binding.d.ts +65 -0
  37. package/dist/eval-case-binding.d.ts.map +1 -0
  38. package/dist/eval-case-binding.js +4 -0
  39. package/dist/eval-case-binding.js.map +1 -0
  40. package/dist/eval-run-binding.d.ts +30 -0
  41. package/dist/eval-run-binding.d.ts.map +1 -1
  42. package/dist/eval-run-dispatcher.d.ts +38 -3
  43. package/dist/eval-run-dispatcher.d.ts.map +1 -1
  44. package/dist/eval-run-dispatcher.js +21 -15
  45. package/dist/eval-run-dispatcher.js.map +1 -1
  46. package/dist/eval-suite-binding.d.ts +1 -1
  47. package/dist/eval-suite-binding.d.ts.map +1 -1
  48. package/dist/eval-suite-binding.js +2 -0
  49. package/dist/eval-suite-binding.js.map +1 -1
  50. package/dist/flow-binding.d.ts +10 -4
  51. package/dist/flow-binding.d.ts.map +1 -1
  52. package/dist/flow-pins.d.ts +36 -0
  53. package/dist/flow-pins.d.ts.map +1 -0
  54. package/dist/flow-pins.js +81 -0
  55. package/dist/flow-pins.js.map +1 -0
  56. package/dist/hitl-binding.d.ts +20 -5
  57. package/dist/hitl-binding.d.ts.map +1 -1
  58. package/dist/index.d.ts +19 -8
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +8 -2
  61. package/dist/index.js.map +1 -1
  62. package/dist/judged-dispatcher.d.ts +134 -0
  63. package/dist/judged-dispatcher.d.ts.map +1 -0
  64. package/dist/judged-dispatcher.js +297 -0
  65. package/dist/judged-dispatcher.js.map +1 -0
  66. package/dist/judged-items.d.ts +86 -0
  67. package/dist/judged-items.d.ts.map +1 -0
  68. package/dist/judged-items.js +184 -0
  69. package/dist/judged-items.js.map +1 -0
  70. package/dist/judgment-binding.d.ts +316 -0
  71. package/dist/judgment-binding.d.ts.map +1 -0
  72. package/dist/judgment-binding.js +19 -0
  73. package/dist/judgment-binding.js.map +1 -0
  74. package/dist/middleware/auth.d.ts +3 -2
  75. package/dist/middleware/auth.d.ts.map +1 -1
  76. package/dist/middleware/auth.js.map +1 -1
  77. package/dist/middleware/idempotency.d.ts +5 -1
  78. package/dist/middleware/idempotency.d.ts.map +1 -1
  79. package/dist/middleware/idempotency.js +8 -1
  80. package/dist/middleware/idempotency.js.map +1 -1
  81. package/dist/openapi/generate.d.ts.map +1 -1
  82. package/dist/openapi/generate.js +4 -1
  83. package/dist/openapi/generate.js.map +1 -1
  84. package/dist/openapi/operations.d.ts.map +1 -1
  85. package/dist/openapi/operations.js +543 -24
  86. package/dist/openapi/operations.js.map +1 -1
  87. package/dist/openapi/schemas.d.ts +58 -0
  88. package/dist/openapi/schemas.d.ts.map +1 -1
  89. package/dist/openapi/schemas.js +1058 -27
  90. package/dist/openapi/schemas.js.map +1 -1
  91. package/dist/provenance-binding.d.ts +27 -1
  92. package/dist/provenance-binding.d.ts.map +1 -1
  93. package/dist/provenance-binding.js.map +1 -1
  94. package/dist/provider-binding.d.ts +12 -7
  95. package/dist/provider-binding.d.ts.map +1 -1
  96. package/dist/reviewer-binding.d.ts +10 -2
  97. package/dist/reviewer-binding.d.ts.map +1 -1
  98. package/dist/reviewer-role.d.ts +13 -0
  99. package/dist/reviewer-role.d.ts.map +1 -0
  100. package/dist/reviewer-role.js +27 -0
  101. package/dist/reviewer-role.js.map +1 -0
  102. package/dist/routes/agents.d.ts +9 -1
  103. package/dist/routes/agents.d.ts.map +1 -1
  104. package/dist/routes/agents.js +175 -11
  105. package/dist/routes/agents.js.map +1 -1
  106. package/dist/routes/approvals.d.ts +5 -4
  107. package/dist/routes/approvals.d.ts.map +1 -1
  108. package/dist/routes/approvals.js +52 -16
  109. package/dist/routes/approvals.js.map +1 -1
  110. package/dist/routes/blocks.d.ts +19 -0
  111. package/dist/routes/blocks.d.ts.map +1 -0
  112. package/dist/routes/blocks.js +281 -0
  113. package/dist/routes/blocks.js.map +1 -0
  114. package/dist/routes/conversations.d.ts +7 -1
  115. package/dist/routes/conversations.d.ts.map +1 -1
  116. package/dist/routes/conversations.js +41 -1
  117. package/dist/routes/conversations.js.map +1 -1
  118. package/dist/routes/cost.d.ts.map +1 -1
  119. package/dist/routes/cost.js +142 -11
  120. package/dist/routes/cost.js.map +1 -1
  121. package/dist/routes/deployments.d.ts +3 -0
  122. package/dist/routes/deployments.d.ts.map +1 -1
  123. package/dist/routes/deployments.js +208 -55
  124. package/dist/routes/deployments.js.map +1 -1
  125. package/dist/routes/eval-comparison.d.ts +14 -0
  126. package/dist/routes/eval-comparison.d.ts.map +1 -0
  127. package/dist/routes/eval-comparison.js +87 -0
  128. package/dist/routes/eval-comparison.js.map +1 -0
  129. package/dist/routes/eval-runs.d.ts.map +1 -1
  130. package/dist/routes/eval-runs.js +8 -0
  131. package/dist/routes/eval-runs.js.map +1 -1
  132. package/dist/routes/flows.d.ts +13 -1
  133. package/dist/routes/flows.d.ts.map +1 -1
  134. package/dist/routes/flows.js +44 -3
  135. package/dist/routes/flows.js.map +1 -1
  136. package/dist/routes/hierarchy-errors.d.ts +35 -0
  137. package/dist/routes/hierarchy-errors.d.ts.map +1 -0
  138. package/dist/routes/hierarchy-errors.js +39 -0
  139. package/dist/routes/hierarchy-errors.js.map +1 -0
  140. package/dist/routes/identity.d.ts +7 -0
  141. package/dist/routes/identity.d.ts.map +1 -1
  142. package/dist/routes/identity.js +3 -2
  143. package/dist/routes/identity.js.map +1 -1
  144. package/dist/routes/judged-suites.d.ts +20 -0
  145. package/dist/routes/judged-suites.d.ts.map +1 -0
  146. package/dist/routes/judged-suites.js +272 -0
  147. package/dist/routes/judged-suites.js.map +1 -0
  148. package/dist/routes/judgment-context.d.ts +22 -0
  149. package/dist/routes/judgment-context.d.ts.map +1 -0
  150. package/dist/routes/judgment-context.js +88 -0
  151. package/dist/routes/judgment-context.js.map +1 -0
  152. package/dist/routes/judgment-flow-context.d.ts +32 -0
  153. package/dist/routes/judgment-flow-context.d.ts.map +1 -0
  154. package/dist/routes/judgment-flow-context.js +195 -0
  155. package/dist/routes/judgment-flow-context.js.map +1 -0
  156. package/dist/routes/judgments.d.ts +41 -0
  157. package/dist/routes/judgments.d.ts.map +1 -0
  158. package/dist/routes/judgments.js +566 -0
  159. package/dist/routes/judgments.js.map +1 -0
  160. package/dist/routes/orgs.d.ts +5 -2
  161. package/dist/routes/orgs.d.ts.map +1 -1
  162. package/dist/routes/orgs.js +38 -22
  163. package/dist/routes/orgs.js.map +1 -1
  164. package/dist/routes/policies.d.ts.map +1 -1
  165. package/dist/routes/policies.js +12 -1
  166. package/dist/routes/policies.js.map +1 -1
  167. package/dist/routes/projects.d.ts +10 -2
  168. package/dist/routes/projects.d.ts.map +1 -1
  169. package/dist/routes/projects.js +87 -79
  170. package/dist/routes/projects.js.map +1 -1
  171. package/dist/routes/provenance.d.ts.map +1 -1
  172. package/dist/routes/provenance.js +32 -1
  173. package/dist/routes/provenance.js.map +1 -1
  174. package/dist/routes/providers.d.ts.map +1 -1
  175. package/dist/routes/providers.js +6 -1
  176. package/dist/routes/providers.js.map +1 -1
  177. package/dist/routes/runs.d.ts +0 -7
  178. package/dist/routes/runs.d.ts.map +1 -1
  179. package/dist/routes/runs.js +65 -20
  180. package/dist/routes/runs.js.map +1 -1
  181. package/dist/routes/scope-params.d.ts +16 -1
  182. package/dist/routes/scope-params.d.ts.map +1 -1
  183. package/dist/routes/scope-params.js +28 -0
  184. package/dist/routes/scope-params.js.map +1 -1
  185. package/dist/routes/teams.d.ts +6 -2
  186. package/dist/routes/teams.d.ts.map +1 -1
  187. package/dist/routes/teams.js +77 -73
  188. package/dist/routes/teams.js.map +1 -1
  189. package/dist/types.d.ts +4 -3
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/webhook-endpoint-binding.d.ts +11 -0
  192. package/dist/webhook-endpoint-binding.d.ts.map +1 -1
  193. package/dist/webhook-endpoint-binding.js.map +1 -1
  194. package/openapi.json +13316 -9299
  195. package/package.json +21 -21
  196. package/src/agent-binding.ts +19 -4
  197. package/src/agent-pins.ts +147 -0
  198. package/src/app.ts +76 -3
  199. package/src/block-binding.ts +137 -0
  200. package/src/block-pins.ts +148 -0
  201. package/src/cost-binding.ts +116 -5
  202. package/src/deploy-versions.ts +157 -0
  203. package/src/deployment-binding.ts +27 -3
  204. package/src/derive-agent-version.ts +206 -0
  205. package/src/errors.ts +17 -0
  206. package/src/eval-case-binding.ts +71 -0
  207. package/src/eval-run-binding.ts +33 -0
  208. package/src/eval-run-dispatcher.ts +57 -16
  209. package/src/eval-suite-binding.ts +2 -0
  210. package/src/flow-binding.ts +11 -4
  211. package/src/flow-pins.ts +113 -0
  212. package/src/hitl-binding.ts +20 -4
  213. package/src/index.ts +88 -2
  214. package/src/judged-dispatcher.ts +507 -0
  215. package/src/judged-items.ts +263 -0
  216. package/src/judgment-binding.ts +349 -0
  217. package/src/middleware/auth.ts +3 -2
  218. package/src/middleware/idempotency.ts +7 -1
  219. package/src/openapi/generate.ts +7 -1
  220. package/src/openapi/operations.ts +615 -24
  221. package/src/openapi/schemas.ts +1157 -22
  222. package/src/provenance-binding.ts +42 -1
  223. package/src/provider-binding.ts +12 -7
  224. package/src/reviewer-binding.ts +10 -2
  225. package/src/reviewer-role.ts +35 -0
  226. package/src/routes/agents.ts +243 -19
  227. package/src/routes/approvals.ts +70 -19
  228. package/src/routes/blocks.ts +362 -0
  229. package/src/routes/conversations.ts +57 -1
  230. package/src/routes/cost.ts +159 -21
  231. package/src/routes/deployments.ts +266 -56
  232. package/src/routes/eval-comparison.ts +101 -0
  233. package/src/routes/eval-runs.ts +11 -0
  234. package/src/routes/flows.ts +63 -5
  235. package/src/routes/hierarchy-errors.ts +51 -0
  236. package/src/routes/identity.ts +10 -2
  237. package/src/routes/judged-suites.ts +363 -0
  238. package/src/routes/judgment-context.ts +128 -0
  239. package/src/routes/judgment-flow-context.ts +245 -0
  240. package/src/routes/judgments.ts +743 -0
  241. package/src/routes/orgs.ts +44 -27
  242. package/src/routes/policies.ts +19 -0
  243. package/src/routes/projects.ts +106 -95
  244. package/src/routes/provenance.ts +48 -1
  245. package/src/routes/providers.ts +5 -0
  246. package/src/routes/runs.ts +79 -22
  247. package/src/routes/scope-params.ts +35 -1
  248. package/src/routes/teams.ts +96 -90
  249. package/src/types.ts +4 -3
  250. package/src/webhook-endpoint-binding.ts +11 -0
@@ -140,6 +140,33 @@ const TopLevelQueryParam: ParameterSpec = {
140
140
  schema: { type: 'boolean' },
141
141
  };
142
142
 
143
+ const RunAgentIdQueryParam: ParameterSpec = {
144
+ name: 'agentId',
145
+ in: 'query',
146
+ required: false,
147
+ description:
148
+ "Only this agent's turns, at any version. Turns that ran before Kindgi 0.1.3 don't name their agent and aren't matched.",
149
+ schema: { type: 'string', minLength: 1 },
150
+ };
151
+
152
+ const RunReplaysQueryParam: ParameterSpec = {
153
+ name: 'replays',
154
+ in: 'query',
155
+ required: false,
156
+ description:
157
+ 'Replay runs (an eval run re-running a past run). `exclude` (default) leaves them out; `include` lists them with the other runs; `only` lists just them.',
158
+ schema: { type: 'string', enum: ['exclude', 'include', 'only'], default: 'exclude' },
159
+ };
160
+
161
+ const RunEvalRunIdQueryParam: ParameterSpec = {
162
+ name: 'evalRunId',
163
+ in: 'query',
164
+ required: false,
165
+ description:
166
+ 'Only the replay runs of this eval run. Implies replays are included; cannot be combined with `replays=exclude`.',
167
+ schema: { type: 'string', minLength: 1 },
168
+ };
169
+
143
170
  const RunIncludeQueryParam: ParameterSpec = {
144
171
  name: 'include',
145
172
  in: 'query',
@@ -532,7 +559,7 @@ const CostToQueryParam: ParameterSpec = {
532
559
  in: 'query',
533
560
  required: false,
534
561
  description:
535
- 'ISO 8601 timestamp; records with `occurredAt <= to`. Required on `/v1/cost/aggregate` (or both endpoints omitted for default last-30-days window).',
562
+ 'ISO 8601 timestamp; records with `occurredAt < to` (exclusive). Required on `/v1/cost/aggregate` (or both endpoints omitted for default last-30-days window).',
536
563
  schema: { type: 'string', format: 'date-time' },
537
564
  };
538
565
 
@@ -541,10 +568,62 @@ const CostGroupByQueryParam: ParameterSpec = {
541
568
  in: 'query',
542
569
  required: true,
543
570
  description:
544
- 'Comma-separated list of dimensions to aggregate over. Each value must be one of `agentId | runId | category | providerId | day | month | tenant | conversationId`. Duplicates collapse.',
571
+ "Comma-separated list of dimensions to aggregate over. Each value must be one of `agentId | runId | category | providerId | day | month | tenant | conversationId | model | servedModel | projectId | orgId | rootRunId | flowId`. `model` is the model actually called; `servedModel` the exact version the vendor reported; `orgId` the org of the record's project. Duplicates collapse.",
572
+ schema: { type: 'string' },
573
+ };
574
+
575
+ const CostAggregateLimitQueryParam: ParameterSpec = {
576
+ name: 'limit',
577
+ in: 'query',
578
+ required: false,
579
+ description:
580
+ 'The most groups to return: the most expensive ones (`groups` is ordered by `totalUsd`, highest first). 1 to 10000, default 1000. When there were more, `truncated` is `true` and `totalGroups` says how many; the totals still cover every record.',
581
+ schema: { type: 'integer', minimum: 1, maximum: 10000, default: 1000 },
582
+ };
583
+
584
+ const CostModelQueryParam: ParameterSpec = {
585
+ name: 'model',
586
+ in: 'query',
587
+ required: false,
588
+ description: 'Filter model calls to this model, the one actually called (exact match).',
545
589
  schema: { type: 'string' },
546
590
  };
547
591
 
592
+ const CostServedModelQueryParam: ParameterSpec = {
593
+ name: 'servedModel',
594
+ in: 'query',
595
+ required: false,
596
+ description: 'Filter model calls to the exact model version the vendor reported (exact match).',
597
+ schema: { type: 'string' },
598
+ };
599
+
600
+ const CostRootRunIdQueryParam: ParameterSpec = {
601
+ name: 'rootRunId',
602
+ in: 'query',
603
+ required: false,
604
+ description:
605
+ 'Every record of the run tree whose root is this run: a flow run and the agent turns and sub-flows it started.',
606
+ schema: { type: 'string', format: 'uuid' },
607
+ };
608
+
609
+ const CostIncludeDescendantsQueryParam: ParameterSpec = {
610
+ name: 'includeDescendants',
611
+ in: 'query',
612
+ required: false,
613
+ description:
614
+ "With `runId`: the run's records and those of every run it started, at any depth. `true` or `false` (default).",
615
+ schema: { type: 'boolean', default: false },
616
+ };
617
+
618
+ const CostIncludeQueryParam: ParameterSpec = {
619
+ name: 'include',
620
+ in: 'query',
621
+ required: false,
622
+ description:
623
+ "Extra fields, comma-separated. `rawUsage`: each model call's usage object exactly as the vendor reported it.",
624
+ schema: { type: 'string', enum: ['rawUsage'] },
625
+ };
626
+
548
627
  const ProviderIdPathParam: ParameterSpec = {
549
628
  name: 'providerId',
550
629
  in: 'path',
@@ -725,7 +804,7 @@ const EvalSuiteKindFilterQueryParam: ParameterSpec = {
725
804
  in: 'query',
726
805
  required: false,
727
806
  description:
728
- 'Filter to eval suites of a single kind. Values: `accuracy | pairwise | regression | human-review | benchmark | custom`.',
807
+ 'Filter to eval suites of a single kind. Values: `accuracy | pairwise | regression | human-review | benchmark | custom | judged`.',
729
808
  schema: { $ref: '#/components/schemas/EvalKind' },
730
809
  };
731
810
 
@@ -737,6 +816,38 @@ const EvalSuiteNameFilterQueryParam: ParameterSpec = {
737
816
  schema: { type: 'string' },
738
817
  };
739
818
 
819
+ const BlockIdPathParam: ParameterSpec = {
820
+ name: 'blockId',
821
+ in: 'path',
822
+ required: true,
823
+ description: 'Block id: dotted lowercase (e.g. `acme.intake-prompt`).',
824
+ schema: { type: 'string', minLength: 1 },
825
+ };
826
+
827
+ const BlockVersionPathParam: ParameterSpec = {
828
+ name: 'version',
829
+ in: 'path',
830
+ required: true,
831
+ description: 'Exact block version (`major.minor.patch`).',
832
+ schema: { type: 'string', minLength: 1 },
833
+ };
834
+
835
+ const BlockKindFilterQueryParam: ParameterSpec = {
836
+ name: 'kind',
837
+ in: 'query',
838
+ required: false,
839
+ description: 'Only blocks of this kind: `prompt` or `settings`.',
840
+ schema: { $ref: '#/components/schemas/BlockKind' },
841
+ };
842
+
843
+ const BlockNameFilterQueryParam: ParameterSpec = {
844
+ name: 'name',
845
+ in: 'query',
846
+ required: false,
847
+ description: 'Prefix match on the block id.',
848
+ schema: { type: 'string' },
849
+ };
850
+
740
851
  // Admin plane — eval-run data plane.
741
852
 
742
853
  const EvalRunIdPathParam: ParameterSpec = {
@@ -858,6 +969,75 @@ const SecretNamePathParam: ParameterSpec = {
858
969
  schema: { type: 'string', minLength: 1 },
859
970
  };
860
971
 
972
+ // ---------------- judgments + judge classes: parameters ----------------
973
+
974
+ const JudgmentIdPathParam: ParameterSpec = {
975
+ name: 'judgmentId',
976
+ in: 'path',
977
+ required: true,
978
+ description: 'Judgment id.',
979
+ schema: { type: 'string', minLength: 1 },
980
+ };
981
+
982
+ const JudgeClassIdPathParam: ParameterSpec = {
983
+ name: 'judgeClassId',
984
+ in: 'path',
985
+ required: true,
986
+ description: 'Judge class id.',
987
+ schema: { type: 'string', minLength: 1 },
988
+ };
989
+
990
+ const judgmentQuery = (name: string, description: string, schema: JsonSchema): ParameterSpec => ({
991
+ name,
992
+ in: 'query',
993
+ required: false,
994
+ description,
995
+ schema,
996
+ });
997
+
998
+ const JudgmentListQueryParams: readonly ParameterSpec[] = [
999
+ judgmentQuery('runId', 'Only judgments of this run.', { type: 'string', minLength: 1 }),
1000
+ judgmentQuery('agentId', 'Only judgments of runs of this agent.', {
1001
+ type: 'string',
1002
+ minLength: 1,
1003
+ }),
1004
+ judgmentQuery('agentVersion', 'Only judgments of runs of this agent version. Needs `agentId`.', {
1005
+ type: 'string',
1006
+ minLength: 1,
1007
+ }),
1008
+ judgmentQuery('flowId', 'Only judgments of runs of this flow.', { type: 'string', minLength: 1 }),
1009
+ judgmentQuery('verdict', 'Only judgments with this verdict.', {
1010
+ type: 'string',
1011
+ enum: ['yes', 'no'],
1012
+ }),
1013
+ judgmentQuery('judgeClassId', 'Only judgments recorded under this judge class.', {
1014
+ type: 'string',
1015
+ minLength: 1,
1016
+ }),
1017
+ judgmentQuery('participantId', "Only judgments made for this app end user's opaque id.", {
1018
+ type: 'string',
1019
+ minLength: 1,
1020
+ }),
1021
+ ];
1022
+
1023
+ const JudgeClassScopeKindQueryParam: ParameterSpec = judgmentQuery(
1024
+ 'scopeKind',
1025
+ 'Only classes of this scope kind. `project` and `agent` need `projectId`; `agent` also needs `agentId`.',
1026
+ { type: 'string', enum: ['tenant', 'project', 'agent'] },
1027
+ );
1028
+
1029
+ const JudgeClassProjectIdQueryParam: ParameterSpec = judgmentQuery(
1030
+ 'projectId',
1031
+ 'Project of the scope, for `scopeKind=project|agent`.',
1032
+ { type: 'string', minLength: 1 },
1033
+ );
1034
+
1035
+ const JudgeClassAgentIdQueryParam: ParameterSpec = judgmentQuery(
1036
+ 'agentId',
1037
+ 'Agent of the scope, for `scopeKind=agent`.',
1038
+ { type: 'string', minLength: 1 },
1039
+ );
1040
+
861
1041
  // ---------------- shared responses ----------------
862
1042
 
863
1043
  const ErrorResponse = (description: string): ResponseSpec => ({
@@ -951,14 +1131,19 @@ export const OPERATIONS: readonly OperationSpec[] = [
951
1131
  parameters: [
952
1132
  LimitQueryParam,
953
1133
  CursorQueryParam,
1134
+ ScopeKindQueryParam,
1135
+ ScopeIdQueryParam,
954
1136
  ParentRunIdQueryParam,
955
1137
  TopLevelQueryParam,
1138
+ RunAgentIdQueryParam,
1139
+ RunReplaysQueryParam,
1140
+ RunEvalRunIdQueryParam,
956
1141
  RunIncludeQueryParam,
957
1142
  ],
958
1143
  responses: {
959
1144
  '200': { description: 'Page of runs.', schema: ref('RunCollectionPage') },
960
1145
  ...CommonAuthErrors,
961
- '400': ErrorResponse('Malformed cursor or filter.'),
1146
+ '400': ErrorResponse('Malformed cursor, filter or scope.'),
962
1147
  },
963
1148
  },
964
1149
  {
@@ -971,6 +1156,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
971
1156
  security: 'bearer',
972
1157
  parameters: [RunIdPathParam],
973
1158
  responses: {
1159
+ '400': ErrorResponse('`runId` is not a run id (a UUID).'),
974
1160
  '200': { description: 'Run row.', schema: ref('Run') },
975
1161
  ...CommonAuthErrors,
976
1162
  '404': ErrorResponse('No run with that id under this tenant.'),
@@ -1022,6 +1208,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1022
1208
  security: 'bearer',
1023
1209
  parameters: [RunIdPathParam, JournalSinceQueryParam],
1024
1210
  responses: {
1211
+ '400': ErrorResponse('`runId` is not a run id (a UUID).'),
1025
1212
  '200': { description: 'Journal page.', schema: ref('RunJournalPage') },
1026
1213
  ...CommonAuthErrors,
1027
1214
  '404': ErrorResponse('No run with that id under this tenant.'),
@@ -1039,6 +1226,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1039
1226
  security: 'bearer',
1040
1227
  parameters: [RunIdPathParam, LastEventIdParam],
1041
1228
  responses: {
1229
+ '400': ErrorResponse('`runId` is not a run id (a UUID).'),
1042
1230
  '200': {
1043
1231
  description: 'text/event-stream. Each frame is one RunEvent.',
1044
1232
  contentType: 'text/event-stream',
@@ -1060,6 +1248,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1060
1248
  security: 'bearer-or-public-run',
1061
1249
  parameters: [RunIdPathParam],
1062
1250
  responses: {
1251
+ '400': ErrorResponse('`runId` is not a run id (a UUID).'),
1063
1252
  '200': { description: "The run's progress.", schema: ref('RunProgress') },
1064
1253
  ...CommonAuthErrors,
1065
1254
  '404': ErrorResponse('No run with that id that the token may read.'),
@@ -1077,6 +1266,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1077
1266
  security: 'bearer-or-public-run',
1078
1267
  parameters: [RunIdPathParam, LastEventIdParam],
1079
1268
  responses: {
1269
+ '400': ErrorResponse('`runId` is not a run id (a UUID).'),
1080
1270
  '200': {
1081
1271
  description: 'text/event-stream. Each frame is one RunProgressEvent.',
1082
1272
  contentType: 'text/event-stream',
@@ -1278,12 +1468,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
1278
1468
  operationId: 'approvals.list',
1279
1469
  summary: 'List approvals visible to the caller',
1280
1470
  description:
1281
- 'Requires the token to carry a `reviewerRole`. Role-scoped: reviewers only see approvals whose `requiredRole` rank ≤ their rank (standard < senior < admin).',
1471
+ "Requires a reviewer: a token that carries a `reviewerRole`, or whose user is a registered reviewer (the roster gives the role). Role-scoped: reviewers only see approvals whose `requiredRole` rank ≤ their rank (standard < senior < admin). `scopeKind`/`scopeId` narrow to one project's approvals, or every project's in an org; approvals from before Kindgi 0.1.3 have no project and are listed only without a scope.",
1282
1472
  tags: ['approvals'],
1283
1473
  security: 'bearer',
1284
1474
  parameters: [
1285
1475
  LimitQueryParam,
1286
1476
  CursorQueryParam,
1477
+ ScopeKindQueryParam,
1478
+ ScopeIdQueryParam,
1287
1479
  ApprovalStatusQueryParam,
1288
1480
  ApprovalRequiredRoleQueryParam,
1289
1481
  CreatedAfterQueryParam,
@@ -1291,8 +1483,8 @@ export const OPERATIONS: readonly OperationSpec[] = [
1291
1483
  responses: {
1292
1484
  '200': { description: 'Page of approvals.', schema: ref('ApprovalCollectionPage') },
1293
1485
  ...CommonAuthErrors,
1294
- '400': ErrorResponse('Malformed query parameter.'),
1295
- '403': ErrorResponse('Token lacks a reviewer role or asked for a tier above the caller.'),
1486
+ '400': ErrorResponse('Malformed query parameter or scope.'),
1487
+ '403': ErrorResponse("The caller isn't a reviewer, or asked for a tier above its own."),
1296
1488
  },
1297
1489
  },
1298
1490
  {
@@ -1302,14 +1494,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
1302
1494
  operationId: 'approvals.get',
1303
1495
  summary: 'Fetch a single approval',
1304
1496
  description:
1305
- 'Returns 404 for ids that exist but require a higher role than the caller (avoids cross-tier existence leaks — see API-ROUTE-CONVENTIONS.md §2.4).',
1497
+ 'Returns 404 for ids that exist but require a higher role than the caller (avoids cross-tier existence leaks — see API-ROUTE-CONVENTIONS.md §2.4). A decided approval carries its `decision`: what the reviewer decided, why, and who (`decidedBy`, `user:<userId>`).',
1306
1498
  tags: ['approvals'],
1307
1499
  security: 'bearer',
1308
1500
  parameters: [ApprovalIdPathParam],
1309
1501
  responses: {
1310
1502
  '200': { description: 'Approval.', schema: ref('Approval') },
1311
1503
  ...CommonAuthErrors,
1312
- '403': ErrorResponse('Token lacks a reviewer role.'),
1504
+ '403': ErrorResponse("The caller isn't a reviewer."),
1313
1505
  '404': ErrorResponse('No approval with that id — or the caller cannot see it.'),
1314
1506
  },
1315
1507
  },
@@ -1320,7 +1512,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1320
1512
  operationId: 'approvals.complete',
1321
1513
  summary: 'Submit a decision on an approval',
1322
1514
  description:
1323
- 'Records the decision (`HitlBinding.submitReview`). When the approval carries a `waitTokenId` and the decision is `approve` or `reject`, also completes the waitpoint so the suspended run resumes.',
1515
+ "Records the decision (`HitlBinding.submitReview`). When the approval carries a `waitTokenId` and the decision is `approve` or `reject`, also completes the waitpoint so the suspended run resumes, with `{ decided, rationale?, decidedBy, approvalId }`: the run's journal records who decided which approval (`decidedBy` is `user:<userId>`).",
1324
1516
  tags: ['approvals'],
1325
1517
  security: 'bearer',
1326
1518
  parameters: [ApprovalIdPathParam, IdempotencyKeyParam],
@@ -1418,7 +1610,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1418
1610
  responses: {
1419
1611
  '200': { description: 'Signed audit bundle.', schema: ref('ExportAuditBundleResult') },
1420
1612
  ...CommonMutationErrors,
1421
- '403': ErrorResponse('Token lacks a reviewer role.'),
1613
+ '403': ErrorResponse("The caller isn't a reviewer."),
1422
1614
  '404': ErrorResponse(
1423
1615
  'Approval not found, signing key id unknown, or signing not configured on this deployment.',
1424
1616
  ),
@@ -1481,6 +1673,27 @@ export const OPERATIONS: readonly OperationSpec[] = [
1481
1673
  '404': ErrorResponse('No agent with that id under this tenant.'),
1482
1674
  },
1483
1675
  },
1676
+ {
1677
+ method: 'post',
1678
+ honoPath: '/v1/agents/:agentId/versions',
1679
+ openapiPath: '/v1/agents/{agentId}/versions',
1680
+ operationId: 'agents.deriveVersion',
1681
+ summary: 'Derive an agent version with data-block pins swapped',
1682
+ description:
1683
+ "An expert's edit, without a code change: a new agent version that is `from`'s bag with some prompt or settings pins swapped (`derivedFrom: { version, reason: 'edited', label, by }`), numbered the next free patch after the agent's highest version. Only blocks `from` already references swap, to a published, active version of the right kind (model settings for the model-settings block); tool pins come from code. Needs `publish` on the agent.",
1684
+ tags: ['agents'],
1685
+ security: 'bearer',
1686
+ parameters: [AgentIdPathParam, IdempotencyKeyParam],
1687
+ requestBody: { required: true, schema: ref('DeriveAgentVersionBody') },
1688
+ responses: {
1689
+ '201': { description: 'The derived agent version.', schema: ref('Agent') },
1690
+ ...CommonMutationErrors,
1691
+ '400': ErrorResponse(
1692
+ "`validation-failed`: `from` has no pins, a swap names a block it doesn't reference, or a version that isn't published, active or the right kind (see `details.issues`).",
1693
+ ),
1694
+ '404': ErrorResponse('`agent-not-found`: no agent at `from`.'),
1695
+ },
1696
+ },
1484
1697
  {
1485
1698
  method: 'get',
1486
1699
  honoPath: '/v1/agents/:agentId/versions/:version',
@@ -1881,12 +2094,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
1881
2094
  operationId: 'conversations.list',
1882
2095
  summary: 'List conversations',
1883
2096
  description:
1884
- 'Cursor-paginated. Fixed sort: `openedAt desc, id desc`. Filters: `?agentId=`, `?status=open|closed`.',
2097
+ "Cursor-paginated. Fixed sort: `openedAt desc, id desc`. Filters: `?agentId=`, `?status=open|closed`, and `scopeKind`/`scopeId` for one project's conversations, or every project's in an org. Conversations from before Kindgi 0.1.3 have no project and are listed only without a scope.",
1885
2098
  tags: ['conversations'],
1886
2099
  security: 'bearer',
1887
2100
  parameters: [
1888
2101
  LimitQueryParam,
1889
2102
  CursorQueryParam,
2103
+ ScopeKindQueryParam,
2104
+ ScopeIdQueryParam,
1890
2105
  AgentIdQueryParam,
1891
2106
  ConversationStatusQueryParam,
1892
2107
  ],
@@ -1896,7 +2111,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1896
2111
  schema: ref('ConversationCollectionPage'),
1897
2112
  },
1898
2113
  ...CommonAuthErrors,
1899
- '400': ErrorResponse('Malformed query parameter.'),
2114
+ '400': ErrorResponse('Malformed query parameter or scope.'),
1900
2115
  },
1901
2116
  },
1902
2117
  {
@@ -1921,7 +2136,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1921
2136
  operationId: 'conversations.open',
1922
2137
  summary: 'Open a conversation',
1923
2138
  description:
1924
- 'Pins `(agentId, agentVersion)` at open time. `title` defaults to `"Untitled conversation"` when omitted; `scope` accepts arbitrary JSON.',
2139
+ 'Pins `(agentId, agentVersion)` at open time. `title` defaults to `"Untitled conversation"` when omitted; `projectId` puts it in a project (lists filter by it; omitted, the Default project); `scope` accepts arbitrary JSON.',
1925
2140
  tags: ['conversations'],
1926
2141
  security: 'bearer',
1927
2142
  parameters: [IdempotencyKeyParam],
@@ -2243,12 +2458,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
2243
2458
  operationId: 'provenance.list',
2244
2459
  summary: 'List provenance records',
2245
2460
  description:
2246
- 'Cursor-paginated. Metadata rows only — clients fetch the DAG payload via `GET /v1/provenance/{runId}`. Fixed sort: `createdAt desc, id desc`. Filters: `?runId=`, `?agentId=`, `?createdAfter=`.',
2461
+ "Cursor-paginated. Metadata rows only — clients fetch the DAG payload via `GET /v1/provenance/{runId}`. Fixed sort: `createdAt desc, id desc`. Filters: `?runId=`, `?agentId=`, `?createdAfter=`, and `scopeKind`/`scopeId` for one project's records, or every project's in an org (records from before Kindgi 0.1.3 have no project and are listed only without a scope).",
2247
2462
  tags: ['provenance'],
2248
2463
  security: 'bearer',
2249
2464
  parameters: [
2250
2465
  LimitQueryParam,
2251
2466
  CursorQueryParam,
2467
+ ScopeKindQueryParam,
2468
+ ScopeIdQueryParam,
2252
2469
  ProvenanceRunIdQueryParam,
2253
2470
  ProvenanceAgentIdQueryParam,
2254
2471
  ProvenanceCreatedAfterQueryParam,
@@ -2256,7 +2473,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2256
2473
  responses: {
2257
2474
  '200': { description: 'Page of records.', schema: ref('ProvenanceCollectionPage') },
2258
2475
  ...CommonAuthErrors,
2259
- '400': ErrorResponse('Malformed cursor or `createdAfter` timestamp.'),
2476
+ '400': ErrorResponse('Malformed cursor, `createdAfter` timestamp or scope.'),
2260
2477
  },
2261
2478
  },
2262
2479
  {
@@ -2569,7 +2786,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2569
2786
  operationId: 'providers.register',
2570
2787
  summary: 'Register a model provider',
2571
2788
  description:
2572
- 'Body is a full `ProviderMetadata`. Server validates shape: provider-level `id` + `region` non-empty; `models[]` non-empty with unique `name` per entry; per-model `contextWindow` positive integer; per-model `features` against the closed enum; per-model `cost` non-negative; optional per-model `p95LatencyMs` / `maxOutputTokens` well-shaped — same rules as `@kindgi/capabilities.createProviderRegistry`. Secrets (API keys, endpoints) are NOT part of the wire shape; deployments store them inside the binding.',
2789
+ 'Body is a full `ProviderMetadata`. Server validates shape: provider-level `id` + `region` non-empty; `models[]` non-empty with unique `name` per entry; per-model `contextWindow` positive integer; per-model `features` against the closed enum; per-model `cost` non-negative; optional per-model `p95LatencyMs` / `maxOutputTokens` well-shaped; optional `labels` within their limits — same rules as `@kindgi/capabilities.createProviderRegistry`. Secrets (API keys, endpoints) are NOT part of the wire shape; deployments store them inside the binding.',
2573
2790
  tags: ['providers'],
2574
2791
  security: 'bearer',
2575
2792
  parameters: [IdempotencyKeyParam],
@@ -2587,13 +2804,193 @@ export const OPERATIONS: readonly OperationSpec[] = [
2587
2804
  openapiPath: '/v1/providers/{providerId}/unregister',
2588
2805
  operationId: 'providers.unregister',
2589
2806
  summary: 'Unregister a model provider',
2807
+ description:
2808
+ 'A tombstone, not an erase: from then on the provider is gone from list, get and capabilities, and the router never picks it. Its id is free to register again. A retention policy on the `provider` domain purges the row.',
2590
2809
  tags: ['providers'],
2591
2810
  security: 'bearer',
2592
2811
  parameters: [ProviderIdPathParam, IdempotencyKeyParam],
2593
2812
  responses: {
2594
2813
  '200': { description: 'Unregistered.', schema: ref('UnregisterProviderResult') },
2595
2814
  ...CommonMutationErrors,
2596
- '404': ErrorResponse('No provider with that id under this tenant.'),
2815
+ '404': ErrorResponse('No provider with that id under this tenant, or already unregistered.'),
2816
+ },
2817
+ },
2818
+
2819
+ // ---------- judgments ----------
2820
+ {
2821
+ method: 'post',
2822
+ honoPath: '/v1/judgments',
2823
+ openapiPath: '/v1/judgments',
2824
+ operationId: 'judgments.create',
2825
+ summary: "Judge an item of a run's output",
2826
+ description:
2827
+ "Records yes or no, with an optional reason, about one item of a finished run's output, optionally under a judge class that applies to the run's project or agent (unclassified judgments count with weight 1). `item.pointer` (a JSON Pointer) must resolve in the run's output; its value is kept as `itemValue`. The first judgment of a run also stores a copy of the run's input and output. `assertedBy` is the authenticated caller, never the body. Judging again as the same caller for the same run, item key and `participantId` supersedes the earlier judgment. Needs `judge` on the run.",
2828
+ tags: ['judgments'],
2829
+ security: 'bearer',
2830
+ parameters: [IdempotencyKeyParam],
2831
+ requestBody: { required: true, schema: ref('CreateJudgmentBody') },
2832
+ responses: {
2833
+ '201': { description: 'Judgment recorded.', schema: ref('Judgment') },
2834
+ ...CommonMutationErrors,
2835
+ '400': ErrorResponse(
2836
+ 'Malformed body, or `item-not-found` (the pointer resolves to nothing in the output), or `judge-class-not-applicable`.',
2837
+ ),
2838
+ '403': ErrorResponse('`permission-denied`: not allowed to judge this run.'),
2839
+ '404': ErrorResponse('`run-not-found`.'),
2840
+ '409': ErrorResponse('`run-not-finished`: the run has no output to judge yet.'),
2841
+ },
2842
+ },
2843
+ {
2844
+ method: 'get',
2845
+ honoPath: '/v1/judgments',
2846
+ openapiPath: '/v1/judgments',
2847
+ operationId: 'judgments.list',
2848
+ summary: 'List judgments',
2849
+ description:
2850
+ 'Live judgments (not removed or superseded), newest first, cursor-paginated. Filter by run, agent (and version), flow, verdict, judge class or participant; `?scopeKind + ?scopeId` narrow to a project.',
2851
+ tags: ['judgments'],
2852
+ security: 'bearer',
2853
+ parameters: [
2854
+ LimitQueryParam,
2855
+ CursorQueryParam,
2856
+ ...JudgmentListQueryParams,
2857
+ ScopeKindQueryParam,
2858
+ ScopeIdQueryParam,
2859
+ ],
2860
+ responses: {
2861
+ '200': { description: 'Page of judgments.', schema: ref('JudgmentCollectionPage') },
2862
+ ...CommonAuthErrors,
2863
+ '400': ErrorResponse('Malformed query parameter.'),
2864
+ },
2865
+ },
2866
+ {
2867
+ method: 'get',
2868
+ honoPath: '/v1/judgments/:judgmentId',
2869
+ openapiPath: '/v1/judgments/{judgmentId}',
2870
+ operationId: 'judgments.get',
2871
+ summary: 'Fetch a judgment with its copies',
2872
+ description:
2873
+ "Returns the judgment (live or not) with the stored copy of the run's input and output and, when the judgment pointed at an item, its value.",
2874
+ tags: ['judgments'],
2875
+ security: 'bearer',
2876
+ parameters: [JudgmentIdPathParam],
2877
+ responses: {
2878
+ '200': { description: 'Judgment with copies.', schema: ref('JudgmentWithCopies') },
2879
+ ...CommonAuthErrors,
2880
+ '404': ErrorResponse('No judgment with that id under this tenant.'),
2881
+ },
2882
+ },
2883
+ {
2884
+ method: 'post',
2885
+ honoPath: '/v1/judgments/:judgmentId/unregister',
2886
+ openapiPath: '/v1/judgments/{judgmentId}/unregister',
2887
+ operationId: 'judgments.unregister',
2888
+ summary: 'Remove a judgment',
2889
+ description:
2890
+ 'Soft delete: the judgment stops listing; retention policy decides when it is purged.',
2891
+ tags: ['judgments'],
2892
+ security: 'bearer',
2893
+ parameters: [JudgmentIdPathParam, IdempotencyKeyParam],
2894
+ responses: {
2895
+ '200': { description: 'Removed.', schema: ref('UnregisterJudgmentResult') },
2896
+ ...CommonMutationErrors,
2897
+ '403': ErrorResponse('`permission-denied`.'),
2898
+ '404': ErrorResponse('No live judgment with that id under this tenant.'),
2899
+ },
2900
+ },
2901
+
2902
+ // ---------- judge classes ----------
2903
+ {
2904
+ method: 'post',
2905
+ honoPath: '/v1/judge-classes',
2906
+ openapiPath: '/v1/judge-classes',
2907
+ operationId: 'judgeClasses.create',
2908
+ summary: 'Create a judge class',
2909
+ description:
2910
+ 'A named kind of judge with a weight, scoped to the tenant, a project, or an agent in a project. Names are unique among the live classes of a scope. Needs `admin` on the tenant (tenant scope) or the project.',
2911
+ tags: ['judge-classes'],
2912
+ security: 'bearer',
2913
+ parameters: [IdempotencyKeyParam],
2914
+ requestBody: { required: true, schema: ref('CreateJudgeClassBody') },
2915
+ responses: {
2916
+ '201': { description: 'Judge class created.', schema: ref('JudgeClass') },
2917
+ ...CommonMutationErrors,
2918
+ '403': ErrorResponse('`permission-denied`.'),
2919
+ '409': ErrorResponse('`judge-class-name-taken`, or an idempotency conflict.'),
2920
+ },
2921
+ },
2922
+ {
2923
+ method: 'get',
2924
+ honoPath: '/v1/judge-classes',
2925
+ openapiPath: '/v1/judge-classes',
2926
+ operationId: 'judgeClasses.list',
2927
+ summary: 'List judge classes',
2928
+ description:
2929
+ 'Live classes, newest first, cursor-paginated. `?scopeKind=tenant|project|agent` (with `projectId` / `agentId`) narrows to one scope.',
2930
+ tags: ['judge-classes'],
2931
+ security: 'bearer',
2932
+ parameters: [
2933
+ LimitQueryParam,
2934
+ CursorQueryParam,
2935
+ JudgeClassScopeKindQueryParam,
2936
+ JudgeClassProjectIdQueryParam,
2937
+ JudgeClassAgentIdQueryParam,
2938
+ ],
2939
+ responses: {
2940
+ '200': { description: 'Page of judge classes.', schema: ref('JudgeClassCollectionPage') },
2941
+ ...CommonAuthErrors,
2942
+ '400': ErrorResponse('Malformed scope parameters.'),
2943
+ },
2944
+ },
2945
+ {
2946
+ method: 'get',
2947
+ honoPath: '/v1/judge-classes/:judgeClassId',
2948
+ openapiPath: '/v1/judge-classes/{judgeClassId}',
2949
+ operationId: 'judgeClasses.get',
2950
+ summary: 'Fetch a judge class',
2951
+ description:
2952
+ 'Also returns a retired class (`unregisteredAt` set): judgments keep naming theirs.',
2953
+ tags: ['judge-classes'],
2954
+ security: 'bearer',
2955
+ parameters: [JudgeClassIdPathParam],
2956
+ responses: {
2957
+ '200': { description: 'Judge class.', schema: ref('JudgeClass') },
2958
+ ...CommonAuthErrors,
2959
+ '404': ErrorResponse('No judge class with that id under this tenant.'),
2960
+ },
2961
+ },
2962
+ {
2963
+ method: 'patch',
2964
+ honoPath: '/v1/judge-classes/:judgeClassId',
2965
+ openapiPath: '/v1/judge-classes/{judgeClassId}',
2966
+ operationId: 'judgeClasses.update',
2967
+ summary: "Change a judge class's weight or description",
2968
+ tags: ['judge-classes'],
2969
+ security: 'bearer',
2970
+ parameters: [JudgeClassIdPathParam, IdempotencyKeyParam],
2971
+ requestBody: { required: true, schema: ref('UpdateJudgeClassBody') },
2972
+ responses: {
2973
+ '200': { description: 'Updated judge class.', schema: ref('JudgeClass') },
2974
+ ...CommonMutationErrors,
2975
+ '403': ErrorResponse('`permission-denied`.'),
2976
+ '404': ErrorResponse('No live judge class with that id under this tenant.'),
2977
+ },
2978
+ },
2979
+ {
2980
+ method: 'post',
2981
+ honoPath: '/v1/judge-classes/:judgeClassId/unregister',
2982
+ openapiPath: '/v1/judge-classes/{judgeClassId}/unregister',
2983
+ operationId: 'judgeClasses.unregister',
2984
+ summary: 'Retire a judge class',
2985
+ description: 'No new judgments may name it; existing judgments keep it.',
2986
+ tags: ['judge-classes'],
2987
+ security: 'bearer',
2988
+ parameters: [JudgeClassIdPathParam, IdempotencyKeyParam],
2989
+ responses: {
2990
+ '200': { description: 'Retired.', schema: ref('UnregisterJudgeClassResult') },
2991
+ ...CommonMutationErrors,
2992
+ '403': ErrorResponse('`permission-denied`.'),
2993
+ '404': ErrorResponse('No live judge class with that id under this tenant.'),
2597
2994
  },
2598
2995
  },
2599
2996
 
@@ -2764,7 +3161,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2764
3161
  operationId: 'cost.records.list',
2765
3162
  summary: 'List cost records',
2766
3163
  description:
2767
- 'Cursor-paginated. Filters (all AND): `runId`, `agentId`, `conversationId`, `category`, `providerId`, `from`, `to`. Sort order is fixed: `occurredAt desc, id desc`. Records represent one accounted resource event each — LLM inference, tool invocation, storage write, sandbox execution, etc.',
3164
+ "Cursor-paginated. Filters (all AND): `runId` (with `includeDescendants`, its whole subtree), `rootRunId`, `agentId`, `conversationId`, `category`, `providerId`, `model`, `servedModel`, `from`, `to`. Sort order is fixed: `occurredAt desc, id desc`. Records represent one accounted resource event each — a model call (`category` `llm.inference`: one record per call, with its model, usage and what the vendor said about it), tool invocation, storage write, sandbox execution, etc. `include=rawUsage` adds each model call's usage as the vendor reported it.",
2768
3165
  tags: ['cost'],
2769
3166
  security: 'bearer',
2770
3167
  parameters: [
@@ -2775,11 +3172,16 @@ export const OPERATIONS: readonly OperationSpec[] = [
2775
3172
  CostConversationIdQueryParam,
2776
3173
  CostCategoryQueryParam,
2777
3174
  CostProviderIdQueryParam,
3175
+ CostModelQueryParam,
3176
+ CostServedModelQueryParam,
3177
+ CostRootRunIdQueryParam,
3178
+ CostIncludeDescendantsQueryParam,
2778
3179
  CostFromQueryParam,
2779
3180
  CostToQueryParam,
2780
3181
  ScopeKindQueryParam,
2781
3182
  ScopeIdQueryParam,
2782
3183
  InheritQueryParam,
3184
+ CostIncludeQueryParam,
2783
3185
  ],
2784
3186
  responses: {
2785
3187
  '200': { description: 'Page of cost records.', schema: ref('CostRecordCollectionPage') },
@@ -2795,7 +3197,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2795
3197
  summary: 'Fetch a cost record',
2796
3198
  tags: ['cost'],
2797
3199
  security: 'bearer',
2798
- parameters: [CostRecordIdPathParam],
3200
+ parameters: [CostRecordIdPathParam, CostIncludeQueryParam],
2799
3201
  responses: {
2800
3202
  '200': { description: 'Cost record.', schema: ref('CostRecord') },
2801
3203
  ...CommonAuthErrors,
@@ -2809,11 +3211,12 @@ export const OPERATIONS: readonly OperationSpec[] = [
2809
3211
  operationId: 'cost.aggregate',
2810
3212
  summary: 'Aggregate cost across a time window',
2811
3213
  description:
2812
- 'Primary consumer path for dashboards. `groupBy` is required (comma-separated dimensions from the closed set); time range is required (both `from` and `to`, or both omitted for the default last-30-days window echoed back in `timeRange`). Filters compose on top of the time window. `?scopeKind + ?scopeId + ?inherit` narrow the aggregate to a specific scope — reconciles byte-for-byte with the same scoped `/records` list.',
3214
+ "Primary consumer path for dashboards. `groupBy` is required (comma-separated dimensions from the closed set); time range is required (both `from` and `to`, or both omitted for the default last-30-days window echoed back in `timeRange`). Filters compose on top of the time window. `?scopeKind + ?scopeId` narrow the aggregate to a scope: `org` covers every project in the org, so one call sums an org's spend. Each group, and the total, carries its cost and its token sums (`tokens`). `groups` is ordered by `totalUsd`, highest first (ties by key), and capped at `limit` (default 1000): `truncated` and `totalGroups` say when there were more, and the totals still cover every record. For every record, page through `/v1/cost/records`. `inherit` has no effect on cost records, which always belong to a project.",
2813
3215
  tags: ['cost'],
2814
3216
  security: 'bearer',
2815
3217
  parameters: [
2816
3218
  CostGroupByQueryParam,
3219
+ CostAggregateLimitQueryParam,
2817
3220
  CostFromQueryParam,
2818
3221
  CostToQueryParam,
2819
3222
  CostCategoryQueryParam,
@@ -2821,6 +3224,10 @@ export const OPERATIONS: readonly OperationSpec[] = [
2821
3224
  CostAgentIdQueryParam,
2822
3225
  CostRunIdQueryParam,
2823
3226
  CostConversationIdQueryParam,
3227
+ CostModelQueryParam,
3228
+ CostServedModelQueryParam,
3229
+ CostRootRunIdQueryParam,
3230
+ CostIncludeDescendantsQueryParam,
2824
3231
  ScopeKindQueryParam,
2825
3232
  ScopeIdQueryParam,
2826
3233
  InheritQueryParam,
@@ -2997,7 +3404,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2997
3404
  operationId: 'policies.publish',
2998
3405
  summary: 'Publish a policy',
2999
3406
  description:
3000
- "Body is a full `Policy` — the server validates top-level shape (id, tenantId, semver version, kind ∈ closed enum, spec is an object). Deeper `spec` validation is the runtime consumer's responsibility per kind. Re-publishing an existing `(policyId, version)` returns `409 policy-already-registered`. Idempotency-Key applies (retries with the same key replay the original 201).",
3407
+ "Body is a full `Policy` — the server validates top-level shape (id, tenantId, semver version, kind ∈ closed enum, spec is an object). Deeper `spec` validation is the runtime consumer's responsibility per kind. Re-publishing an existing `(policyId, version)` returns `409 policy-already-registered`. A known kind that no runtime consumer applies yet (`access-control`, `adapter-allowlist`, `rate-limit`, `compliance`) is refused with `400 kind-not-applied` (`details.appliedKinds` lists the ones that are): publishing it would change nothing. Idempotency-Key applies (retries with the same key replay the original 201).",
3001
3408
  tags: ['policies'],
3002
3409
  security: 'bearer',
3003
3410
  parameters: [IdempotencyKeyParam],
@@ -3005,7 +3412,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3005
3412
  responses: {
3006
3413
  '201': { description: 'Policy published.', schema: ref('PublishPolicyResult') },
3007
3414
  ...CommonMutationErrors,
3008
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
3415
+ '400': ErrorResponse(
3416
+ 'Validation failed (see `details.issues`), or `kind-not-applied`: no runtime consumer applies that kind yet.',
3417
+ ),
3009
3418
  '409': ErrorResponse('Policy already registered at that (id, version).'),
3010
3419
  },
3011
3420
  },
@@ -3136,6 +3545,48 @@ export const OPERATIONS: readonly OperationSpec[] = [
3136
3545
  '409': ErrorResponse('Eval suite already registered at that (id, version).'),
3137
3546
  },
3138
3547
  },
3548
+ {
3549
+ method: 'post',
3550
+ honoPath: '/v1/eval-suites/:suiteId/versions/from-judgments',
3551
+ openapiPath: '/v1/eval-suites/{suiteId}/versions/from-judgments',
3552
+ operationId: 'evalSuites.buildFromJudgments',
3553
+ summary: 'Build a test set from judgments',
3554
+ description:
3555
+ "Publishes a `judged` eval suite version whose cases are copies of judged runs of one agent (optionally one version) or flow, newest first, at most 1000. Each case holds the run's input, what the turn read (`context`), the judged output, and each item's judgments summed up: yes and no counts, the weight behind yes and behind all judgments (an unclassified judgment counts 1), and the reasons. `judgeClassIds` counts only judgments of those classes; `minJudgments` leaves out runs with fewer. Needs `admin` on the project.",
3556
+ tags: ['eval-suites'],
3557
+ security: 'bearer',
3558
+ parameters: [EvalSuiteIdPathParam, IdempotencyKeyParam],
3559
+ requestBody: { required: true, schema: ref('BuildJudgedSuiteBody') },
3560
+ responses: {
3561
+ '201': { description: 'Version published.', schema: ref('BuildJudgedSuiteResult') },
3562
+ ...CommonMutationErrors,
3563
+ '400': ErrorResponse('Malformed body.'),
3564
+ '403': ErrorResponse('`permission-denied`.'),
3565
+ '409': ErrorResponse('Eval suite already registered at that (id, version).'),
3566
+ '501': ErrorResponse('`test-sets-not-supported`: this deployment cannot build test sets.'),
3567
+ },
3568
+ },
3569
+ {
3570
+ method: 'get',
3571
+ honoPath: '/v1/eval-suites/:suiteId/versions/:version/cases',
3572
+ openapiPath: '/v1/eval-suites/{suiteId}/versions/{version}/cases',
3573
+ operationId: 'evalSuites.listCases',
3574
+ summary: 'List the cases of a judged eval suite version',
3575
+ description: 'Cursor-paginated, in the order the cases were stored (newest judged run first).',
3576
+ tags: ['eval-suites'],
3577
+ security: 'bearer',
3578
+ parameters: [
3579
+ EvalSuiteIdPathParam,
3580
+ EvalSuiteVersionPathParam,
3581
+ LimitQueryParam,
3582
+ CursorQueryParam,
3583
+ ],
3584
+ responses: {
3585
+ '200': { description: 'Page of cases.', schema: ref('JudgedEvalCaseCollectionPage') },
3586
+ ...CommonAuthErrors,
3587
+ '404': ErrorResponse('No eval suite with that id.'),
3588
+ },
3589
+ },
3139
3590
  {
3140
3591
  method: 'post',
3141
3592
  honoPath: '/v1/eval-suites/:suiteId/versions/:version/unregister',
@@ -3170,6 +3621,139 @@ export const OPERATIONS: readonly OperationSpec[] = [
3170
3621
  },
3171
3622
 
3172
3623
  // ---------- eval runs (data plane) ----------
3624
+ {
3625
+ method: 'get',
3626
+ honoPath: '/v1/blocks',
3627
+ openapiPath: '/v1/blocks',
3628
+ operationId: 'blocks.list',
3629
+ summary: 'List data blocks (latest version of each)',
3630
+ description:
3631
+ 'Cursor-paginated. Only the blocks of projects the caller can read. `?kind=` narrows to prompts or settings, `?name=` is a prefix match on the id, and `?scopeKind=` + `?scopeId=` narrow to a project or an org, as the other lists do.',
3632
+ tags: ['blocks'],
3633
+ security: 'bearer',
3634
+ parameters: [
3635
+ LimitQueryParam,
3636
+ CursorQueryParam,
3637
+ BlockKindFilterQueryParam,
3638
+ BlockNameFilterQueryParam,
3639
+ ScopeKindQueryParam,
3640
+ ScopeIdQueryParam,
3641
+ ],
3642
+ responses: {
3643
+ '200': { description: 'Page of blocks.', schema: ref('BlockCollectionPage') },
3644
+ ...CommonAuthErrors,
3645
+ '400': ErrorResponse(
3646
+ 'Malformed query parameter: an unknown `kind`, or `scope-invalid` for a malformed scope.',
3647
+ ),
3648
+ },
3649
+ },
3650
+ {
3651
+ method: 'get',
3652
+ honoPath: '/v1/blocks/:blockId',
3653
+ openapiPath: '/v1/blocks/{blockId}',
3654
+ operationId: 'blocks.get',
3655
+ summary: 'Fetch a data block (latest version)',
3656
+ tags: ['blocks'],
3657
+ security: 'bearer',
3658
+ parameters: [BlockIdPathParam],
3659
+ responses: {
3660
+ '200': { description: 'Latest active version.', schema: ref('Block') },
3661
+ ...CommonAuthErrors,
3662
+ '404': ErrorResponse(
3663
+ "`block-not-found`: no such block, or one in a project the caller can't read.",
3664
+ ),
3665
+ },
3666
+ },
3667
+ {
3668
+ method: 'get',
3669
+ honoPath: '/v1/blocks/:blockId/versions',
3670
+ openapiPath: '/v1/blocks/{blockId}/versions',
3671
+ operationId: 'blocks.versions.list',
3672
+ summary: 'List versions of a data block',
3673
+ description:
3674
+ 'Newest published first. `?includeTombstoned=true` includes unregistered versions, each with `unregisteredAt`.',
3675
+ tags: ['blocks'],
3676
+ security: 'bearer',
3677
+ parameters: [BlockIdPathParam, LimitQueryParam, CursorQueryParam, IncludeTombstonedQueryParam],
3678
+ responses: {
3679
+ '200': { description: 'Page of versions.', schema: ref('BlockCollectionPage') },
3680
+ ...CommonAuthErrors,
3681
+ '404': ErrorResponse('`block-not-found`.'),
3682
+ },
3683
+ },
3684
+ {
3685
+ method: 'get',
3686
+ honoPath: '/v1/blocks/:blockId/versions/:version',
3687
+ openapiPath: '/v1/blocks/{blockId}/versions/{version}',
3688
+ operationId: 'blocks.versions.get',
3689
+ summary: 'Fetch a specific data block version',
3690
+ description: 'An unregistered version is returned too, with `unregisteredAt`.',
3691
+ tags: ['blocks'],
3692
+ security: 'bearer',
3693
+ parameters: [BlockIdPathParam, BlockVersionPathParam],
3694
+ responses: {
3695
+ '200': { description: 'The block at that version.', schema: ref('Block') },
3696
+ ...CommonAuthErrors,
3697
+ '404': ErrorResponse('`block-not-found`.'),
3698
+ },
3699
+ },
3700
+ {
3701
+ method: 'post',
3702
+ honoPath: '/v1/blocks',
3703
+ openapiPath: '/v1/blocks',
3704
+ operationId: 'blocks.publish',
3705
+ summary: 'Publish a data block version',
3706
+ description:
3707
+ "Needs `write` on the project. A prompt's template must parse as Liquid; a settings block's `values` must satisfy its `schema` and the latest version's. A taken version is `409 block-already-registered` (versions never change); a block keeps its kind, and its versions stay in its first version's project (`409 block-project-mismatch`). Idempotency-Key applies.",
3708
+ tags: ['blocks'],
3709
+ security: 'bearer',
3710
+ parameters: [IdempotencyKeyParam],
3711
+ requestBody: { required: true, schema: ref('PublishBlockBody') },
3712
+ responses: {
3713
+ '201': { description: 'Published.', schema: ref('PublishBlockResult') },
3714
+ ...CommonMutationErrors,
3715
+ '400': ErrorResponse(
3716
+ '`validation-failed` (see `details.issues`), or an unknown `projectId`.',
3717
+ ),
3718
+ '403': ErrorResponse('`permission-denied`: no `write` on the project.'),
3719
+ '409': ErrorResponse('`block-already-registered` or `block-project-mismatch`.'),
3720
+ },
3721
+ },
3722
+ {
3723
+ method: 'post',
3724
+ honoPath: '/v1/blocks/:blockId/versions/:version/unregister',
3725
+ openapiPath: '/v1/blocks/{blockId}/versions/{version}/unregister',
3726
+ operationId: 'blocks.versions.unregister',
3727
+ summary: 'Unregister a data block version',
3728
+ description:
3729
+ 'Soft: no range picks it any more, but the agent versions that pin it keep running it, and GET still reads it. Needs `write` on the project.',
3730
+ tags: ['blocks'],
3731
+ security: 'bearer',
3732
+ parameters: [BlockIdPathParam, BlockVersionPathParam, IdempotencyKeyParam],
3733
+ responses: {
3734
+ '200': { description: 'Unregistered.', schema: ref('UnregisterBlockResult') },
3735
+ ...CommonMutationErrors,
3736
+ '403': ErrorResponse('`permission-denied`: no `write` on the project.'),
3737
+ '404': ErrorResponse('`block-not-found`, or already unregistered.'),
3738
+ },
3739
+ },
3740
+ {
3741
+ method: 'post',
3742
+ honoPath: '/v1/blocks/:blockId/versions/:version/reinstate',
3743
+ openapiPath: '/v1/blocks/{blockId}/versions/{version}/reinstate',
3744
+ operationId: 'blocks.versions.reinstate',
3745
+ summary: 'Reinstate an unregistered data block version',
3746
+ description: 'Unchanged, as published. Idempotent. Needs `write` on the project.',
3747
+ tags: ['blocks'],
3748
+ security: 'bearer',
3749
+ parameters: [BlockIdPathParam, BlockVersionPathParam, IdempotencyKeyParam],
3750
+ responses: {
3751
+ '200': { description: 'Reinstated.', schema: ref('ReinstateBlockResult') },
3752
+ ...CommonMutationErrors,
3753
+ '403': ErrorResponse('`permission-denied`: no `write` on the project.'),
3754
+ '404': ErrorResponse('`block-not-found`.'),
3755
+ },
3756
+ },
3173
3757
  {
3174
3758
  method: 'post',
3175
3759
  honoPath: '/v1/eval-suites/:suiteId/runs',
@@ -3978,7 +4562,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
3978
4562
  operationId: 'orgs.delete',
3979
4563
  summary: 'Delete an org (idempotent)',
3980
4564
  description:
3981
- 'Idempotent — deleting an unknown or already-deleted org returns 204 per the binding contract.',
4565
+ "A tombstone, not an erase: from then on the org is gone from get and list, and its slug is free for a new org. Its projects and teams stay, without an org; when one of those projects has the slug of a project that has none, nothing is deleted: `409 slug-conflict` names the slugs (rename or move those projects first). In the Kindgi runtime, the org's own secrets and secret mappings are deleted with it, for good, and its own environments and MCP endpoints are unregistered. A retention policy on the `org` domain purges the org's row. Idempotent: deleting an unknown or already-deleted org returns 204.",
3982
4566
  tags: ['orgs'],
3983
4567
  security: 'bearer',
3984
4568
  parameters: [
@@ -3994,6 +4578,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3994
4578
  responses: {
3995
4579
  '204': { description: 'Deleted (or already absent). No body.' },
3996
4580
  ...CommonAuthErrors,
4581
+ '409': ErrorResponse(
4582
+ "slug-conflict: the org's projects would leave it with slugs that projects without an org already have.",
4583
+ ),
3997
4584
  },
3998
4585
  },
3999
4586
 
@@ -4297,6 +4884,8 @@ export const OPERATIONS: readonly OperationSpec[] = [
4297
4884
  openapiPath: '/v1/projects',
4298
4885
  operationId: 'projects.create',
4299
4886
  summary: 'Create a project',
4887
+ description:
4888
+ "A project's slug is unique within its org, and a project without an org's among the tenant's projects without one: two orgs may each have a project with the same slug. A taken slug answers `409 slug-conflict`; a second Default, `409 project-default-already-exists`.",
4300
4889
  tags: ['projects'],
4301
4890
  security: 'bearer',
4302
4891
  parameters: [IdempotencyKeyParam],
@@ -4335,6 +4924,8 @@ export const OPERATIONS: readonly OperationSpec[] = [
4335
4924
  openapiPath: '/v1/projects/{projectId}',
4336
4925
  operationId: 'projects.update',
4337
4926
  summary: 'Partially update a project',
4927
+ description:
4928
+ 'A new `slug`, or a move to another org (`orgId`, or `null` for none), answers `409 slug-conflict` when the slug is taken where the project ends up.',
4338
4929
  tags: ['projects'],
4339
4930
  security: 'bearer',
4340
4931
  parameters: [