@kindgi/api 0.1.4-rc.1 → 0.1.4-rc.3

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 (220) hide show
  1. package/dist/agent-binding.d.ts +12 -2
  2. package/dist/agent-binding.d.ts.map +1 -1
  3. package/dist/agent-pins.d.ts.map +1 -1
  4. package/dist/agent-pins.js +4 -1
  5. package/dist/agent-pins.js.map +1 -1
  6. package/dist/app.d.ts +8 -0
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +23 -4
  9. package/dist/app.js.map +1 -1
  10. package/dist/deploy-versions.d.ts +8 -5
  11. package/dist/deploy-versions.d.ts.map +1 -1
  12. package/dist/deploy-versions.js +3 -9
  13. package/dist/deploy-versions.js.map +1 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +25 -0
  16. package/dist/errors.js.map +1 -1
  17. package/dist/eval-case-binding.d.ts +10 -0
  18. package/dist/eval-case-binding.d.ts.map +1 -1
  19. package/dist/eval-run-binding.d.ts +14 -3
  20. package/dist/eval-run-binding.d.ts.map +1 -1
  21. package/dist/eval-run-binding.js.map +1 -1
  22. package/dist/flow-pins.d.ts +20 -7
  23. package/dist/flow-pins.d.ts.map +1 -1
  24. package/dist/flow-pins.js +36 -11
  25. package/dist/flow-pins.js.map +1 -1
  26. package/dist/gate-policy-binding.d.ts +164 -0
  27. package/dist/gate-policy-binding.d.ts.map +1 -0
  28. package/dist/gate-policy-binding.js +12 -0
  29. package/dist/gate-policy-binding.js.map +1 -0
  30. package/dist/gate.d.ts +56 -0
  31. package/dist/gate.d.ts.map +1 -0
  32. package/dist/gate.js +359 -0
  33. package/dist/gate.js.map +1 -0
  34. package/dist/handler-binding.d.ts +14 -2
  35. package/dist/handler-binding.d.ts.map +1 -1
  36. package/dist/hitl-binding.d.ts +5 -0
  37. package/dist/hitl-binding.d.ts.map +1 -1
  38. package/dist/index.d.ts +10 -4
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +3 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/judged-dispatcher.d.ts +16 -1
  43. package/dist/judged-dispatcher.d.ts.map +1 -1
  44. package/dist/judged-dispatcher.js +45 -6
  45. package/dist/judged-dispatcher.js.map +1 -1
  46. package/dist/judgment-binding.d.ts +38 -0
  47. package/dist/judgment-binding.d.ts.map +1 -1
  48. package/dist/judgment-binding.js +19 -0
  49. package/dist/judgment-binding.js.map +1 -1
  50. package/dist/live-version-binding.d.ts +206 -0
  51. package/dist/live-version-binding.d.ts.map +1 -0
  52. package/dist/live-version-binding.js +4 -0
  53. package/dist/live-version-binding.js.map +1 -0
  54. package/dist/middleware/authorize.d.ts.map +1 -1
  55. package/dist/middleware/authorize.js +13 -2
  56. package/dist/middleware/authorize.js.map +1 -1
  57. package/dist/middleware/project-ref.d.ts +25 -0
  58. package/dist/middleware/project-ref.d.ts.map +1 -0
  59. package/dist/middleware/project-ref.js +72 -0
  60. package/dist/middleware/project-ref.js.map +1 -0
  61. package/dist/openapi/generate.d.ts.map +1 -1
  62. package/dist/openapi/generate.js +4 -0
  63. package/dist/openapi/generate.js.map +1 -1
  64. package/dist/openapi/operations.d.ts +6 -0
  65. package/dist/openapi/operations.d.ts.map +1 -1
  66. package/dist/openapi/operations.js +512 -22
  67. package/dist/openapi/operations.js.map +1 -1
  68. package/dist/openapi/schemas.d.ts +37 -0
  69. package/dist/openapi/schemas.d.ts.map +1 -1
  70. package/dist/openapi/schemas.js +709 -5
  71. package/dist/openapi/schemas.js.map +1 -1
  72. package/dist/publish-refused.d.ts +18 -0
  73. package/dist/publish-refused.d.ts.map +1 -0
  74. package/dist/publish-refused.js +22 -0
  75. package/dist/publish-refused.js.map +1 -0
  76. package/dist/retention-binding.d.ts +29 -0
  77. package/dist/retention-binding.d.ts.map +1 -1
  78. package/dist/routes/agent-releases.d.ts +40 -0
  79. package/dist/routes/agent-releases.d.ts.map +1 -0
  80. package/dist/routes/agent-releases.js +462 -0
  81. package/dist/routes/agent-releases.js.map +1 -0
  82. package/dist/routes/agents.d.ts +7 -1
  83. package/dist/routes/agents.d.ts.map +1 -1
  84. package/dist/routes/agents.js +31 -3
  85. package/dist/routes/agents.js.map +1 -1
  86. package/dist/routes/approvals.d.ts.map +1 -1
  87. package/dist/routes/approvals.js +40 -9
  88. package/dist/routes/approvals.js.map +1 -1
  89. package/dist/routes/audit.d.ts.map +1 -1
  90. package/dist/routes/audit.js +7 -0
  91. package/dist/routes/audit.js.map +1 -1
  92. package/dist/routes/auth.js +1 -1
  93. package/dist/routes/auth.js.map +1 -1
  94. package/dist/routes/conversations.d.ts +0 -7
  95. package/dist/routes/conversations.d.ts.map +1 -1
  96. package/dist/routes/conversations.js +19 -3
  97. package/dist/routes/conversations.js.map +1 -1
  98. package/dist/routes/deployments.d.ts +6 -0
  99. package/dist/routes/deployments.d.ts.map +1 -1
  100. package/dist/routes/deployments.js +33 -5
  101. package/dist/routes/deployments.js.map +1 -1
  102. package/dist/routes/env.d.ts.map +1 -1
  103. package/dist/routes/env.js +1 -0
  104. package/dist/routes/env.js.map +1 -1
  105. package/dist/routes/eval-comparison.d.ts +2 -1
  106. package/dist/routes/eval-comparison.d.ts.map +1 -1
  107. package/dist/routes/eval-comparison.js +22 -9
  108. package/dist/routes/eval-comparison.js.map +1 -1
  109. package/dist/routes/flows.d.ts +14 -7
  110. package/dist/routes/flows.d.ts.map +1 -1
  111. package/dist/routes/flows.js +10 -8
  112. package/dist/routes/flows.js.map +1 -1
  113. package/dist/routes/gate-policies.d.ts +19 -0
  114. package/dist/routes/gate-policies.d.ts.map +1 -0
  115. package/dist/routes/gate-policies.js +191 -0
  116. package/dist/routes/gate-policies.js.map +1 -0
  117. package/dist/routes/gate-policy-spec.d.ts +17 -0
  118. package/dist/routes/gate-policy-spec.d.ts.map +1 -0
  119. package/dist/routes/gate-policy-spec.js +192 -0
  120. package/dist/routes/gate-policy-spec.js.map +1 -0
  121. package/dist/routes/gate-policy-wire.d.ts +3 -0
  122. package/dist/routes/gate-policy-wire.d.ts.map +1 -0
  123. package/dist/routes/gate-policy-wire.js +18 -0
  124. package/dist/routes/gate-policy-wire.js.map +1 -0
  125. package/dist/routes/hierarchy-errors.d.ts +11 -0
  126. package/dist/routes/hierarchy-errors.d.ts.map +1 -1
  127. package/dist/routes/hierarchy-errors.js +13 -0
  128. package/dist/routes/hierarchy-errors.js.map +1 -1
  129. package/dist/routes/judged-suites.js +7 -0
  130. package/dist/routes/judged-suites.js.map +1 -1
  131. package/dist/routes/judgments.d.ts +4 -1
  132. package/dist/routes/judgments.d.ts.map +1 -1
  133. package/dist/routes/judgments.js +92 -19
  134. package/dist/routes/judgments.js.map +1 -1
  135. package/dist/routes/live-scope-wire.d.ts +32 -0
  136. package/dist/routes/live-scope-wire.d.ts.map +1 -0
  137. package/dist/routes/live-scope-wire.js +71 -0
  138. package/dist/routes/live-scope-wire.js.map +1 -0
  139. package/dist/routes/policies.d.ts +6 -3
  140. package/dist/routes/policies.d.ts.map +1 -1
  141. package/dist/routes/policies.js +55 -3
  142. package/dist/routes/policies.js.map +1 -1
  143. package/dist/routes/projects.d.ts.map +1 -1
  144. package/dist/routes/projects.js +47 -3
  145. package/dist/routes/projects.js.map +1 -1
  146. package/dist/routes/retention.d.ts.map +1 -1
  147. package/dist/routes/retention.js +18 -2
  148. package/dist/routes/retention.js.map +1 -1
  149. package/dist/routes/reviewers.d.ts +8 -1
  150. package/dist/routes/reviewers.d.ts.map +1 -1
  151. package/dist/routes/reviewers.js +20 -3
  152. package/dist/routes/reviewers.js.map +1 -1
  153. package/dist/routes/runs.d.ts.map +1 -1
  154. package/dist/routes/runs.js +22 -13
  155. package/dist/routes/runs.js.map +1 -1
  156. package/dist/routes/secrets.d.ts.map +1 -1
  157. package/dist/routes/secrets.js +2 -0
  158. package/dist/routes/secrets.js.map +1 -1
  159. package/dist/routes/segments.d.ts +17 -0
  160. package/dist/routes/segments.d.ts.map +1 -0
  161. package/dist/routes/segments.js +68 -0
  162. package/dist/routes/segments.js.map +1 -0
  163. package/dist/routes/teams.d.ts.map +1 -1
  164. package/dist/routes/teams.js +42 -3
  165. package/dist/routes/teams.js.map +1 -1
  166. package/dist/routes/uuid-param.d.ts +11 -0
  167. package/dist/routes/uuid-param.d.ts.map +1 -0
  168. package/dist/routes/uuid-param.js +19 -0
  169. package/dist/routes/uuid-param.js.map +1 -0
  170. package/openapi.json +7008 -3624
  171. package/package.json +21 -21
  172. package/src/agent-binding.ts +12 -2
  173. package/src/agent-pins.ts +3 -1
  174. package/src/app.ts +37 -3
  175. package/src/deploy-versions.ts +11 -14
  176. package/src/errors.ts +25 -0
  177. package/src/eval-case-binding.ts +7 -0
  178. package/src/eval-run-binding.ts +24 -3
  179. package/src/flow-pins.ts +49 -9
  180. package/src/gate-policy-binding.ts +171 -0
  181. package/src/gate.ts +467 -0
  182. package/src/handler-binding.ts +14 -2
  183. package/src/hitl-binding.ts +5 -0
  184. package/src/index.ts +46 -1
  185. package/src/judged-dispatcher.ts +74 -6
  186. package/src/judgment-binding.ts +62 -0
  187. package/src/live-version-binding.ts +237 -0
  188. package/src/middleware/authorize.ts +18 -2
  189. package/src/middleware/project-ref.ts +88 -0
  190. package/src/openapi/generate.ts +5 -0
  191. package/src/openapi/operations.ts +619 -22
  192. package/src/openapi/schemas.ts +788 -5
  193. package/src/publish-refused.ts +31 -0
  194. package/src/retention-binding.ts +30 -0
  195. package/src/routes/agent-releases.ts +581 -0
  196. package/src/routes/agents.ts +43 -2
  197. package/src/routes/approvals.ts +58 -8
  198. package/src/routes/audit.ts +10 -0
  199. package/src/routes/auth.ts +1 -1
  200. package/src/routes/conversations.ts +31 -3
  201. package/src/routes/deployments.ts +50 -5
  202. package/src/routes/env.ts +1 -0
  203. package/src/routes/eval-comparison.ts +24 -10
  204. package/src/routes/flows.ts +27 -11
  205. package/src/routes/gate-policies.ts +226 -0
  206. package/src/routes/gate-policy-spec.ts +229 -0
  207. package/src/routes/gate-policy-wire.ts +20 -0
  208. package/src/routes/hierarchy-errors.ts +14 -0
  209. package/src/routes/judged-suites.ts +5 -0
  210. package/src/routes/judgments.ts +107 -23
  211. package/src/routes/live-scope-wire.ts +90 -0
  212. package/src/routes/policies.ts +61 -3
  213. package/src/routes/projects.ts +54 -2
  214. package/src/routes/retention.ts +21 -2
  215. package/src/routes/reviewers.ts +23 -4
  216. package/src/routes/runs.ts +33 -20
  217. package/src/routes/secrets.ts +2 -0
  218. package/src/routes/segments.ts +75 -0
  219. package/src/routes/teams.ts +53 -3
  220. package/src/routes/uuid-param.ts +28 -0
@@ -31,6 +31,12 @@ export interface ParameterSpec {
31
31
  readonly required?: boolean;
32
32
  readonly description?: string;
33
33
  readonly schema: JsonSchema;
34
+ /**
35
+ * A segment path, one `key:value` per repeated value, coarse to fine.
36
+ * Emitted as `x-kindgi-segment-path: true`; a generated client takes
37
+ * `{key, value}` steps and writes the values itself.
38
+ */
39
+ readonly segmentPath?: boolean;
34
40
  }
35
41
 
36
42
  export interface ResponseSpec {
@@ -158,6 +164,15 @@ const RunReplaysQueryParam: ParameterSpec = {
158
164
  schema: { type: 'string', enum: ['exclude', 'include', 'only'], default: 'exclude' },
159
165
  };
160
166
 
167
+ const ConversationReplaysQueryParam: ParameterSpec = {
168
+ name: 'replays',
169
+ in: 'query',
170
+ required: false,
171
+ description:
172
+ "Replay conversations (opened by a comparison's replay turn; `metadata.replayOf` names the run it replays). `exclude` (default) leaves them out; `include` lists them with the others; `only` lists just them.",
173
+ schema: { type: 'string', enum: ['exclude', 'include', 'only'], default: 'exclude' },
174
+ };
175
+
161
176
  const RunEvalRunIdQueryParam: ParameterSpec = {
162
177
  name: 'evalRunId',
163
178
  in: 'query',
@@ -167,6 +182,39 @@ const RunEvalRunIdQueryParam: ParameterSpec = {
167
182
  schema: { type: 'string', minLength: 1 },
168
183
  };
169
184
 
185
+ const LiveProjectQueryParam: ParameterSpec = {
186
+ name: 'projectId',
187
+ in: 'query',
188
+ required: false,
189
+ description: "The run's project; omit → only the agent's tenant-wide pin applies.",
190
+ schema: { type: 'string', format: 'uuid' },
191
+ };
192
+
193
+ const SegmentQueryParam: ParameterSpec = {
194
+ name: 'segment',
195
+ in: 'query',
196
+ required: false,
197
+ description: 'One step of the segment path, `key:value`; repeat it in order, coarse to fine.',
198
+ schema: { type: 'array', items: { type: 'string' } },
199
+ segmentPath: true,
200
+ };
201
+
202
+ const PromotionScopeKindQueryParam: ParameterSpec = {
203
+ name: 'scopeKind',
204
+ in: 'query',
205
+ required: false,
206
+ description:
207
+ 'Only one scope: `tenant`, `org` or `project` (with `scopeId`), or `segment` (with `scopeId` and `segment`).',
208
+ schema: { type: 'string', enum: ['tenant', 'org', 'project', 'segment'] },
209
+ };
210
+
211
+ const PromotionIdPathParam: ParameterSpec = {
212
+ name: 'promotionId',
213
+ in: 'path',
214
+ required: true,
215
+ schema: { type: 'string', format: 'uuid' },
216
+ };
217
+
170
218
  const RunIncludeQueryParam: ParameterSpec = {
171
219
  name: 'include',
172
220
  in: 'query',
@@ -201,6 +249,22 @@ const LastEventIdParam: ParameterSpec = {
201
249
  schema: { type: 'string' },
202
250
  };
203
251
 
252
+ const GatePolicyIdPathParam: ParameterSpec = {
253
+ name: 'policyId',
254
+ in: 'path',
255
+ required: true,
256
+ description: 'The gate policy id.',
257
+ schema: { type: 'string' },
258
+ };
259
+
260
+ const GatePolicyVersionPathParam: ParameterSpec = {
261
+ name: 'version',
262
+ in: 'path',
263
+ required: true,
264
+ description: 'A semver version of the gate policy.',
265
+ schema: { type: 'string' },
266
+ };
267
+
204
268
  const AgentIdPathParam: ParameterSpec = {
205
269
  name: 'agentId',
206
270
  in: 'path',
@@ -321,6 +385,15 @@ const ApprovalRequiredRoleQueryParam: ParameterSpec = {
321
385
  schema: { $ref: '#/components/schemas/ReviewerRole' },
322
386
  };
323
387
 
388
+ const WaitTokenIdQueryParam: ParameterSpec = {
389
+ name: 'waitTokenId',
390
+ in: 'query',
391
+ required: false,
392
+ description:
393
+ "Only approvals linked to one of these run waits (an approval's `waitTokenId`; a run's journal names its open waits in `wait.suspended` entries). Repeat it for several, at most 50.",
394
+ schema: { type: 'array', items: { type: 'string', minLength: 1 }, maxItems: 50 },
395
+ };
396
+
324
397
  const CreatedAfterQueryParam: ParameterSpec = {
325
398
  name: 'createdAfter',
326
399
  in: 'query',
@@ -367,6 +440,38 @@ const SupervisorIdQueryParam: ParameterSpec = {
367
440
  schema: { type: 'string' },
368
441
  };
369
442
 
443
+ const ObservationAgentVersionQueryParam: ParameterSpec = {
444
+ name: 'agentVersion',
445
+ in: 'query',
446
+ required: false,
447
+ description: 'Only the observations of this agent version (with `agentId`).',
448
+ schema: { type: 'string' },
449
+ };
450
+
451
+ const ObservationConversationIdQueryParam: ParameterSpec = {
452
+ name: 'conversationId',
453
+ in: 'query',
454
+ required: false,
455
+ description: 'Only the observations of turns in this conversation.',
456
+ schema: { type: 'string' },
457
+ };
458
+
459
+ const ObservationSinceQueryParam: ParameterSpec = {
460
+ name: 'since',
461
+ in: 'query',
462
+ required: false,
463
+ description: 'Only the observations at or after this time (ISO 8601).',
464
+ schema: { type: 'string', format: 'date-time' },
465
+ };
466
+
467
+ const ObservationUntilQueryParam: ParameterSpec = {
468
+ name: 'until',
469
+ in: 'query',
470
+ required: false,
471
+ description: 'Only the observations at or before this time (ISO 8601).',
472
+ schema: { type: 'string', format: 'date-time' },
473
+ };
474
+
370
475
  const FactIdPathParam: ParameterSpec = {
371
476
  name: 'factId',
372
477
  in: 'path',
@@ -456,7 +561,7 @@ const ProvenanceAgentIdQueryParam: ParameterSpec = {
456
561
  in: 'query',
457
562
  required: false,
458
563
  description:
459
- 'Filter records to those with at least one DAG node whose `actor = <agentId>` (agent-driven turns tag their nodes with the agent id).',
564
+ "Only the records of this agent's turns, at any version: the agent the record's run names, as `GET /v1/runs?agentId=` matches it. Turns that ran before Kindgi 0.1.3 don't name their agent and aren't matched.",
460
565
  schema: { type: 'string' },
461
566
  };
462
567
 
@@ -769,7 +874,7 @@ const PolicyKindFilterQueryParam: ParameterSpec = {
769
874
  in: 'query',
770
875
  required: false,
771
876
  description:
772
- 'Filter to policies of a single kind. Values: `access-control | model-routing | adapter-allowlist | rate-limit | retention | compliance`.',
877
+ 'Filter to policies of a single kind. Values: `access-control | model-routing | adapter-allowlist | rate-limit | retention | compliance | tool-errors | hitl`.',
773
878
  schema: { $ref: '#/components/schemas/PolicyKind' },
774
879
  };
775
880
 
@@ -781,6 +886,32 @@ const PolicyNameFilterQueryParam: ParameterSpec = {
781
886
  schema: { type: 'string' },
782
887
  };
783
888
 
889
+ // Admin plane — retention.
890
+
891
+ const RetentionDomainQueryParam: ParameterSpec = {
892
+ name: 'domain',
893
+ in: 'query',
894
+ required: false,
895
+ description: 'Only this domain. Absent: every domain.',
896
+ schema: { $ref: '#/components/schemas/RetentionDomain' },
897
+ };
898
+
899
+ const RetentionPastGraceOnlyQueryParam: ParameterSpec = {
900
+ name: 'pastGraceOnly',
901
+ in: 'query',
902
+ required: false,
903
+ description: '`true`: only rows past their grace, the ones a sweep would purge now.',
904
+ schema: { type: 'boolean', default: false },
905
+ };
906
+
907
+ const RetentionDomainPathParam: ParameterSpec = {
908
+ name: 'domain',
909
+ in: 'path',
910
+ required: true,
911
+ description: 'The domain to sweep (not `*`).',
912
+ schema: { $ref: '#/components/schemas/RetentionDomain' },
913
+ };
914
+
784
915
  // Admin plane — eval suites.
785
916
 
786
917
  const EvalSuiteIdPathParam: ParameterSpec = {
@@ -1115,8 +1246,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
1115
1246
  schema: ref('Run'),
1116
1247
  },
1117
1248
  ...CommonMutationErrors,
1118
- '404': ErrorResponse('Agent or flow not found.'),
1249
+ '404': ErrorResponse(
1250
+ 'Agent or flow not found; or `projectId` names no project of this tenant (`project-not-found`).',
1251
+ ),
1119
1252
  '422': ErrorResponse('Guardrail violation or budget exceeded.'),
1253
+ '400': ErrorResponse(
1254
+ "Malformed request body, or the body's `projectId` isn't a project id (a UUID).",
1255
+ ),
1120
1256
  },
1121
1257
  },
1122
1258
  {
@@ -1386,6 +1522,12 @@ export const OPERATIONS: readonly OperationSpec[] = [
1386
1522
  '201': { description: 'Token minted.', schema: ref('MintTokenResult') },
1387
1523
  ...CommonMutationErrors,
1388
1524
  '403': ErrorResponse('Not a tenant admin, or a capability the caller does not hold.'),
1525
+ '400': ErrorResponse(
1526
+ "Malformed request body, or the body's `projectId` isn't a project id (a UUID).",
1527
+ ),
1528
+ '404': ErrorResponse(
1529
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
1530
+ ),
1389
1531
  },
1390
1532
  },
1391
1533
  {
@@ -1479,6 +1621,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1479
1621
  ApprovalStatusQueryParam,
1480
1622
  ApprovalRequiredRoleQueryParam,
1481
1623
  CreatedAfterQueryParam,
1624
+ WaitTokenIdQueryParam,
1482
1625
  ],
1483
1626
  responses: {
1484
1627
  '200': { description: 'Page of approvals.', schema: ref('ApprovalCollectionPage') },
@@ -1576,6 +1719,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1576
1719
  responses: {
1577
1720
  '201': { description: 'Reviewer registered.', schema: ref('Reviewer') },
1578
1721
  ...CommonMutationErrors,
1722
+ '403': ErrorResponse(
1723
+ 'With authorization enforced, the caller is not an admin of the tenant.',
1724
+ ),
1579
1725
  },
1580
1726
  },
1581
1727
  {
@@ -1593,6 +1739,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1593
1739
  '200': { description: 'Unregistered.', schema: ref('UnregisterReviewerResult') },
1594
1740
  ...CommonMutationErrors,
1595
1741
  '404': ErrorResponse('No reviewer with that id under this tenant.'),
1742
+ '403': ErrorResponse(
1743
+ 'With authorization enforced, the caller is not an admin of the tenant.',
1744
+ ),
1596
1745
  },
1597
1746
  },
1598
1747
  {
@@ -1697,9 +1846,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
1697
1846
  "Idempotency-Key was reused with a different body, or resource-state conflict. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
1698
1847
  ),
1699
1848
  '400': ErrorResponse(
1700
- "`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`).",
1849
+ "`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`); or `projectId` isn't a project id (a UUID).",
1850
+ ),
1851
+ '404': ErrorResponse(
1852
+ '`agent-not-found`: no agent at `from`; or `projectId` names no project of this tenant (`project-not-found`).',
1701
1853
  ),
1702
- '404': ErrorResponse('`agent-not-found`: no agent at `from`.'),
1703
1854
  },
1704
1855
  },
1705
1856
  {
@@ -1732,10 +1883,15 @@ export const OPERATIONS: readonly OperationSpec[] = [
1732
1883
  responses: {
1733
1884
  '201': { description: 'Agent published.', schema: ref('PublishAgentResult') },
1734
1885
  ...CommonMutationErrors,
1735
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
1886
+ '400': ErrorResponse(
1887
+ "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
1888
+ ),
1736
1889
  '409': ErrorResponse(
1737
1890
  "Agent already registered at that (id, version). Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
1738
1891
  ),
1892
+ '404': ErrorResponse(
1893
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
1894
+ ),
1739
1895
  },
1740
1896
  },
1741
1897
  {
@@ -1751,7 +1907,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1751
1907
  '200': { description: 'Unregistered.', schema: ref('UnregisterAgentResult') },
1752
1908
  ...CommonMutationErrors,
1753
1909
  '409': ErrorResponse(
1754
- "Idempotency-Key was reused with a different body, or resource-state conflict. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
1910
+ "Idempotency-Key was reused with a different body, or resource-state conflict. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead. Or `agent-version-live`: the version is the live version of the scopes in `details.scopes`; roll back, unpin, or promote another version there first.",
1755
1911
  ),
1756
1912
  '404': ErrorResponse('No agent at that (id, version) under this tenant.'),
1757
1913
  },
@@ -1776,6 +1932,322 @@ export const OPERATIONS: readonly OperationSpec[] = [
1776
1932
  '404': ErrorResponse('No agent at that (id, version) under this tenant.'),
1777
1933
  },
1778
1934
  },
1935
+ {
1936
+ method: 'get',
1937
+ honoPath: '/v1/agents/:agentId/live',
1938
+ openapiPath: '/v1/agents/{agentId}/live',
1939
+ operationId: 'agents.live.resolve',
1940
+ summary: 'The version a run would use',
1941
+ description:
1942
+ "Resolves the version a run of this agent would use for a project and segment path: the most specific live version (segment path, project, org, tenant), else the latest registered. A run that names its version, or a follow-up turn in a conversation, isn't resolved this way.",
1943
+ tags: ['agents'],
1944
+ security: 'bearer',
1945
+ parameters: [AgentIdPathParam, LiveProjectQueryParam, SegmentQueryParam],
1946
+ responses: {
1947
+ '200': { description: 'The resolved version.', schema: ref('LiveVersionResolution') },
1948
+ ...CommonAuthErrors,
1949
+ '400': ErrorResponse('A malformed project id or segment, or a segment without a project.'),
1950
+ '404': ErrorResponse('No agent with that id.'),
1951
+ },
1952
+ },
1953
+ {
1954
+ method: 'get',
1955
+ honoPath: '/v1/agents/:agentId/live-versions',
1956
+ openapiPath: '/v1/agents/{agentId}/live-versions',
1957
+ operationId: 'agents.live.list',
1958
+ summary: "List an agent's live versions",
1959
+ description: 'Every scope with a live version pinned, and the promotion that set it.',
1960
+ tags: ['agents'],
1961
+ security: 'bearer',
1962
+ parameters: [AgentIdPathParam],
1963
+ responses: {
1964
+ '200': { description: 'The pins.', schema: ref('LivePinList') },
1965
+ ...CommonAuthErrors,
1966
+ },
1967
+ },
1968
+ {
1969
+ method: 'post',
1970
+ honoPath: '/v1/agents/:agentId/promotions',
1971
+ openapiPath: '/v1/agents/{agentId}/promotions',
1972
+ operationId: 'agents.promotions.create',
1973
+ summary: 'Make a version live for a scope',
1974
+ description:
1975
+ "Pins `version` live for `scope`: runs in that scope that don't name a version use it, from the next run. Open conversations keep their version. The version must be registered and active. Every promotion is recorded, with who asked and why. Needs `promote` on the agent.\n\nWith a gate policy for the scope (`GET …/gate-policy`), the promotion is checked first against the comparison named by `evalRunId`: `201` promoted; `202` the gate passed and the policy wants a reviewer's approval (a HITL approval, subject `agent-promotion`; the live version moves once it's approved, if nothing changed meanwhile); `422 gate-failed` with `details.promotionId`, `details.policy` and every check in `details.checks`. A refused promotion is recorded too.",
1976
+ tags: ['agents'],
1977
+ security: 'bearer',
1978
+ parameters: [AgentIdPathParam, IdempotencyKeyParam],
1979
+ requestBody: { required: true, schema: ref('PromoteBody') },
1980
+ responses: {
1981
+ '201': { description: 'Promoted.', schema: ref('Promotion') },
1982
+ '202': {
1983
+ description: 'The gate passed; the promotion waits for an approval (`approvalId`).',
1984
+ schema: ref('Promotion'),
1985
+ },
1986
+ ...CommonMutationErrors,
1987
+ '404': ErrorResponse(
1988
+ 'The version is not registered, or was unregistered; or the eval run is not found.',
1989
+ ),
1990
+ '409': ErrorResponse(
1991
+ "`promotion-superseded`: the scope's live version changed while the gate ran; check again.",
1992
+ ),
1993
+ '422': ErrorResponse(
1994
+ '`gate-failed`: the gate refused it. `details.checks` has every check; the refusal is recorded (`details.promotionId`).',
1995
+ ),
1996
+ '501': ErrorResponse(
1997
+ "`promotion-gate-unsupported`: a gate policy applies, and the deployment can't record a gated promotion.",
1998
+ ),
1999
+ },
2000
+ },
2001
+ {
2002
+ method: 'post',
2003
+ honoPath: '/v1/agents/:agentId/promotions/check',
2004
+ openapiPath: '/v1/agents/{agentId}/promotions/check',
2005
+ operationId: 'agents.promotions.check',
2006
+ summary: 'Check a promotion against its gate',
2007
+ description:
2008
+ 'What `POST …/promotions` with the same body would do, with nothing recorded: `would-promote`, `needs-approval` (with the approval it needs) or `gate-failed`, with every check. For a pipeline: evaluate, check, promote. Needs `read` on the agent.',
2009
+ tags: ['agents'],
2010
+ security: 'bearer',
2011
+ parameters: [AgentIdPathParam],
2012
+ requestBody: { required: true, schema: ref('PromoteBody') },
2013
+ responses: {
2014
+ '200': { description: "The gate's answer.", schema: ref('PromotionCheck') },
2015
+ ...CommonAuthErrors,
2016
+ '400': ErrorResponse('Malformed body, or the eval run is not a finished comparison.'),
2017
+ '404': ErrorResponse('The version, the agent or the eval run is not found.'),
2018
+ },
2019
+ },
2020
+ {
2021
+ method: 'get',
2022
+ honoPath: '/v1/agents/:agentId/gate-policy',
2023
+ openapiPath: '/v1/agents/{agentId}/gate-policy',
2024
+ operationId: 'agents.gatePolicy.resolve',
2025
+ summary: 'The gate policy for a scope',
2026
+ description:
2027
+ "The gate policy a promotion of the agent for the scope would be checked against: the most specific scope with an active policy (a segment path's longer prefixes first, then its project, the project's org, the tenant), at its latest active version. `policy: null` when none applies.",
2028
+ tags: ['agents'],
2029
+ security: 'bearer',
2030
+ parameters: [
2031
+ AgentIdPathParam,
2032
+ PromotionScopeKindQueryParam,
2033
+ ScopeIdQueryParam,
2034
+ SegmentQueryParam,
2035
+ ],
2036
+ responses: {
2037
+ '200': { description: 'The policy, or null.', schema: ref('GatePolicyResolution') },
2038
+ ...CommonAuthErrors,
2039
+ '400': ErrorResponse('Missing or malformed scope.'),
2040
+ },
2041
+ },
2042
+ {
2043
+ method: 'get',
2044
+ honoPath: '/v1/gate-policies',
2045
+ openapiPath: '/v1/gate-policies',
2046
+ operationId: 'gatePolicies.list',
2047
+ summary: 'List gate policies',
2048
+ description:
2049
+ "Each gate policy's latest active version. `agentId` narrows it to one agent's; `scopeKind` (with `scopeId` and `segment`) to one scope's.",
2050
+ tags: ['gate-policies'],
2051
+ security: 'bearer',
2052
+ parameters: [
2053
+ LimitQueryParam,
2054
+ CursorQueryParam,
2055
+ {
2056
+ name: 'agentId',
2057
+ in: 'query',
2058
+ required: false,
2059
+ description: 'Only the policies gating this agent.',
2060
+ schema: { type: 'string' },
2061
+ },
2062
+ PromotionScopeKindQueryParam,
2063
+ ScopeIdQueryParam,
2064
+ SegmentQueryParam,
2065
+ ],
2066
+ responses: {
2067
+ '200': { description: 'Page of gate policies.', schema: ref('GatePolicyPage') },
2068
+ ...CommonAuthErrors,
2069
+ '400': ErrorResponse('Malformed scope or cursor.'),
2070
+ },
2071
+ },
2072
+ {
2073
+ method: 'post',
2074
+ honoPath: '/v1/gate-policies',
2075
+ openapiPath: '/v1/gate-policies',
2076
+ operationId: 'gatePolicies.publish',
2077
+ summary: 'Publish a gate policy',
2078
+ description:
2079
+ "Registers a gate policy, or a new version of one: what a promotion of `agentId` for `scope` must show. One policy per agent and scope: another policy id for a scope that has one is refused with `409 gate-policy-scope-taken` (`details.heldBy` names it; publish a new version of that one instead), and a new version can't change the agent or scope (`409 gate-policy-scope-changed`). The `spec` is checked strictly: an unknown key is refused (`400 validation-failed`, `details.issues`). Needs `admin` on the tenant: whoever may promote can't loosen their own gate.",
2080
+ tags: ['gate-policies'],
2081
+ security: 'bearer',
2082
+ parameters: [IdempotencyKeyParam],
2083
+ requestBody: { required: true, schema: ref('PublishGatePolicyBody') },
2084
+ responses: {
2085
+ '201': { description: 'Gate policy published.', schema: ref('GatePolicy') },
2086
+ ...CommonMutationErrors,
2087
+ '400': ErrorResponse('Validation failed (see `details.issues`).'),
2088
+ '409': ErrorResponse(
2089
+ '`gate-policy-already-registered`: that (id, version) exists. `gate-policy-scope-taken`: another policy gates the agent for the scope (`details.heldBy`). `gate-policy-scope-changed`: the version would change the agent or scope. `gate-policy-scope-unpinned`: nothing covering the scope is pinned, so a published version would go live there ungated; pin a version for the scope, or one above it, first.',
2090
+ ),
2091
+ },
2092
+ },
2093
+ {
2094
+ method: 'get',
2095
+ honoPath: '/v1/gate-policies/:policyId',
2096
+ openapiPath: '/v1/gate-policies/{policyId}',
2097
+ operationId: 'gatePolicies.get',
2098
+ summary: 'Get a gate policy',
2099
+ description: "The policy's latest active version.",
2100
+ tags: ['gate-policies'],
2101
+ security: 'bearer',
2102
+ parameters: [GatePolicyIdPathParam],
2103
+ responses: {
2104
+ '200': { description: 'The gate policy.', schema: ref('GatePolicy') },
2105
+ ...CommonAuthErrors,
2106
+ '404': ErrorResponse('No active gate policy with that id.'),
2107
+ },
2108
+ },
2109
+ {
2110
+ method: 'get',
2111
+ honoPath: '/v1/gate-policies/:policyId/versions',
2112
+ openapiPath: '/v1/gate-policies/{policyId}/versions',
2113
+ operationId: 'gatePolicies.versions.list',
2114
+ summary: "List a gate policy's versions",
2115
+ description: 'Every version, oldest first, unregistered ones too (with `unregisteredAt`).',
2116
+ tags: ['gate-policies'],
2117
+ security: 'bearer',
2118
+ parameters: [GatePolicyIdPathParam],
2119
+ responses: {
2120
+ '200': { description: 'The versions.', schema: ref('GatePolicyPage') },
2121
+ ...CommonAuthErrors,
2122
+ '404': ErrorResponse('No gate policy with that id.'),
2123
+ },
2124
+ },
2125
+ {
2126
+ method: 'get',
2127
+ honoPath: '/v1/gate-policies/:policyId/versions/:version',
2128
+ openapiPath: '/v1/gate-policies/{policyId}/versions/{version}',
2129
+ operationId: 'gatePolicies.versions.get',
2130
+ summary: 'Get a gate policy version',
2131
+ tags: ['gate-policies'],
2132
+ security: 'bearer',
2133
+ parameters: [GatePolicyIdPathParam, GatePolicyVersionPathParam],
2134
+ responses: {
2135
+ '200': { description: 'The version.', schema: ref('GatePolicy') },
2136
+ ...CommonAuthErrors,
2137
+ '404': ErrorResponse('No such version.'),
2138
+ },
2139
+ },
2140
+ {
2141
+ method: 'post',
2142
+ honoPath: '/v1/gate-policies/:policyId/versions/:version/unregister',
2143
+ openapiPath: '/v1/gate-policies/{policyId}/versions/{version}/unregister',
2144
+ operationId: 'gatePolicies.versions.unregister',
2145
+ summary: 'Unregister a gate policy version',
2146
+ description:
2147
+ "The version stops applying; the policy's latest remaining active version applies, or, with none, the scope above's policy. Promotions keep the version they were checked against. Needs `admin` on the tenant.",
2148
+ tags: ['gate-policies'],
2149
+ security: 'bearer',
2150
+ parameters: [GatePolicyIdPathParam, GatePolicyVersionPathParam],
2151
+ responses: {
2152
+ '200': { description: 'The unregistered version.', schema: ref('GatePolicy') },
2153
+ ...CommonMutationErrors,
2154
+ '404': ErrorResponse('No such version.'),
2155
+ },
2156
+ },
2157
+ {
2158
+ method: 'post',
2159
+ honoPath: '/v1/gate-policies/:policyId/versions/:version/reinstate',
2160
+ openapiPath: '/v1/gate-policies/{policyId}/versions/{version}/reinstate',
2161
+ operationId: 'gatePolicies.versions.reinstate',
2162
+ summary: 'Reinstate a gate policy version',
2163
+ description: 'Needs `admin` on the tenant.',
2164
+ tags: ['gate-policies'],
2165
+ security: 'bearer',
2166
+ parameters: [GatePolicyIdPathParam, GatePolicyVersionPathParam],
2167
+ responses: {
2168
+ '200': { description: 'The reinstated version.', schema: ref('GatePolicy') },
2169
+ ...CommonMutationErrors,
2170
+ '404': ErrorResponse('No such version.'),
2171
+ },
2172
+ },
2173
+ {
2174
+ method: 'get',
2175
+ honoPath: '/v1/agents/:agentId/promotions',
2176
+ openapiPath: '/v1/agents/{agentId}/promotions',
2177
+ operationId: 'agents.promotions.list',
2178
+ summary: "List an agent's promotions",
2179
+ description:
2180
+ 'The history of live-version changes (promotions, rollbacks, unpins), newest first. `scopeKind` (`tenant`, `org`, `project`, `segment`) with `scopeId` and `segment` narrows it to one scope.',
2181
+ tags: ['agents'],
2182
+ security: 'bearer',
2183
+ parameters: [
2184
+ AgentIdPathParam,
2185
+ LimitQueryParam,
2186
+ CursorQueryParam,
2187
+ PromotionScopeKindQueryParam,
2188
+ ScopeIdQueryParam,
2189
+ SegmentQueryParam,
2190
+ ],
2191
+ responses: {
2192
+ '200': { description: 'Page of promotions.', schema: ref('PromotionPage') },
2193
+ ...CommonAuthErrors,
2194
+ '400': ErrorResponse('Malformed scope or cursor.'),
2195
+ },
2196
+ },
2197
+ {
2198
+ method: 'get',
2199
+ honoPath: '/v1/agents/:agentId/promotions/:promotionId',
2200
+ openapiPath: '/v1/agents/{agentId}/promotions/{promotionId}',
2201
+ operationId: 'agents.promotions.get',
2202
+ summary: 'Get a promotion',
2203
+ tags: ['agents'],
2204
+ security: 'bearer',
2205
+ parameters: [AgentIdPathParam, PromotionIdPathParam],
2206
+ responses: {
2207
+ '200': { description: 'The promotion.', schema: ref('Promotion') },
2208
+ ...CommonAuthErrors,
2209
+ '404': ErrorResponse('No such promotion for this agent.'),
2210
+ },
2211
+ },
2212
+ {
2213
+ method: 'post',
2214
+ honoPath: '/v1/agents/:agentId/live/rollback',
2215
+ openapiPath: '/v1/agents/{agentId}/live/rollback',
2216
+ operationId: 'agents.live.rollback',
2217
+ summary: 'Roll a scope back to its previous live version',
2218
+ description:
2219
+ "Back to the scope's previous live version, or `toVersion`. Recorded like a promotion. Needs `promote` on the agent.",
2220
+ tags: ['agents'],
2221
+ security: 'bearer',
2222
+ parameters: [AgentIdPathParam, IdempotencyKeyParam],
2223
+ requestBody: { required: true, schema: ref('RollbackBody') },
2224
+ responses: {
2225
+ '200': { description: 'Rolled back.', schema: ref('Promotion') },
2226
+ ...CommonMutationErrors,
2227
+ '404': ErrorResponse('`toVersion` is not registered, or was unregistered.'),
2228
+ '409': ErrorResponse('The scope has no pin, or no earlier version to go back to.'),
2229
+ },
2230
+ },
2231
+ {
2232
+ method: 'post',
2233
+ honoPath: '/v1/agents/:agentId/live/unpin',
2234
+ openapiPath: '/v1/agents/{agentId}/live/unpin',
2235
+ operationId: 'agents.live.unpin',
2236
+ summary: "Remove a scope's live version",
2237
+ description:
2238
+ "Removes the scope's own pin: its runs use the next scope up (and the latest when nothing is pinned). Recorded like a promotion. Needs `promote` on the agent.",
2239
+ tags: ['agents'],
2240
+ security: 'bearer',
2241
+ parameters: [AgentIdPathParam, IdempotencyKeyParam],
2242
+ requestBody: { required: true, schema: ref('UnpinBody') },
2243
+ responses: {
2244
+ '200': { description: 'Unpinned.', schema: ref('Promotion') },
2245
+ ...CommonMutationErrors,
2246
+ '409': ErrorResponse(
2247
+ '`not-pinned`: the scope has no pin of its own. `gate-policy-needs-pin`: unpinning would leave a scope a gate policy applies to on the latest version, where publishing goes live ungated.',
2248
+ ),
2249
+ },
2250
+ },
1779
2251
 
1780
2252
  // ---------- flows ----------
1781
2253
  {
@@ -1863,10 +2335,15 @@ export const OPERATIONS: readonly OperationSpec[] = [
1863
2335
  responses: {
1864
2336
  '201': { description: 'Flow published.', schema: ref('PublishFlowResult') },
1865
2337
  ...CommonMutationErrors,
1866
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
2338
+ '400': ErrorResponse(
2339
+ "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
2340
+ ),
1867
2341
  '409': ErrorResponse(
1868
2342
  "Flow already registered at that (id, version). Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
1869
2343
  ),
2344
+ '404': ErrorResponse(
2345
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
2346
+ ),
1870
2347
  },
1871
2348
  },
1872
2349
  {
@@ -1998,10 +2475,15 @@ export const OPERATIONS: readonly OperationSpec[] = [
1998
2475
  responses: {
1999
2476
  '201': { description: 'Tool registered.', schema: ref('RegisterToolResult') },
2000
2477
  ...CommonMutationErrors,
2001
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
2478
+ '400': ErrorResponse(
2479
+ "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
2480
+ ),
2002
2481
  '409': ErrorResponse(
2003
2482
  "Tool already registered at that id. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
2004
2483
  ),
2484
+ '404': ErrorResponse(
2485
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
2486
+ ),
2005
2487
  },
2006
2488
  },
2007
2489
  {
@@ -2098,10 +2580,15 @@ export const OPERATIONS: readonly OperationSpec[] = [
2098
2580
  responses: {
2099
2581
  '201': { description: 'Guardrail registered.', schema: ref('RegisterGuardrailResult') },
2100
2582
  ...CommonMutationErrors,
2101
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
2583
+ '400': ErrorResponse(
2584
+ "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
2585
+ ),
2102
2586
  '409': ErrorResponse(
2103
2587
  "Guardrail already registered at that id. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead.",
2104
2588
  ),
2589
+ '404': ErrorResponse(
2590
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
2591
+ ),
2105
2592
  },
2106
2593
  },
2107
2594
  {
@@ -2131,7 +2618,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2131
2618
  operationId: 'conversations.list',
2132
2619
  summary: 'List conversations',
2133
2620
  description:
2134
- "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.",
2621
+ "Cursor-paginated. Fixed sort: `openedAt desc, id desc`. Filters: `?agentId=`, `?status=open|closed`, `?replays=exclude|include|only` (default `exclude`: a comparison's replay conversations are left out), 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.",
2135
2622
  tags: ['conversations'],
2136
2623
  security: 'bearer',
2137
2624
  parameters: [
@@ -2141,6 +2628,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2141
2628
  ScopeIdQueryParam,
2142
2629
  AgentIdQueryParam,
2143
2630
  ConversationStatusQueryParam,
2631
+ ConversationReplaysQueryParam,
2144
2632
  ],
2145
2633
  responses: {
2146
2634
  '200': {
@@ -2163,6 +2651,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2163
2651
  responses: {
2164
2652
  '200': { description: 'Conversation.', schema: ref('Conversation') },
2165
2653
  ...CommonAuthErrors,
2654
+ '400': ErrorResponse('`conversationId` is not a conversation id (a UUID).'),
2166
2655
  '404': ErrorResponse('No conversation with that id under this tenant.'),
2167
2656
  },
2168
2657
  },
@@ -2200,6 +2689,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2200
2689
  schema: ref('Conversation'),
2201
2690
  },
2202
2691
  ...CommonMutationErrors,
2692
+ '400': ErrorResponse('`conversationId` is not a conversation id (a UUID).'),
2203
2693
  '404': ErrorResponse('No conversation with that id under this tenant.'),
2204
2694
  },
2205
2695
  },
@@ -2220,7 +2710,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2220
2710
  schema: ref('ConversationMessageCollectionPage'),
2221
2711
  },
2222
2712
  ...CommonAuthErrors,
2223
- '400': ErrorResponse('Malformed cursor.'),
2713
+ '400': ErrorResponse(
2714
+ 'Malformed cursor, or `conversationId` is not a conversation id (a UUID).',
2715
+ ),
2224
2716
  '404': ErrorResponse('No conversation with that id under this tenant.'),
2225
2717
  },
2226
2718
  },
@@ -2712,7 +3204,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
2712
3204
  CursorQueryParam,
2713
3205
  ObservationStatusQueryParam,
2714
3206
  AgentIdQueryParam,
3207
+ ObservationAgentVersionQueryParam,
2715
3208
  SupervisorIdQueryParam,
3209
+ ObservationConversationIdQueryParam,
3210
+ ObservationSinceQueryParam,
3211
+ ObservationUntilQueryParam,
2716
3212
  ],
2717
3213
  responses: {
2718
3214
  '200': {
@@ -2872,7 +3368,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2872
3368
  '400': ErrorResponse(
2873
3369
  'Malformed body, or `item-not-found` (the pointer resolves to nothing in the output), or `judge-class-not-applicable`.',
2874
3370
  ),
2875
- '403': ErrorResponse('`permission-denied`: not allowed to judge this run.'),
3371
+ '403': ErrorResponse(
3372
+ '`permission-denied`: not allowed to judge this run. `judge-class-not-allowed`: the judge class is restricted (`assertableBy`) and the caller may not assert it.',
3373
+ ),
2876
3374
  '404': ErrorResponse('`run-not-found`.'),
2877
3375
  '409': ErrorResponse('`run-not-finished`: the run has no output to judge yet.'),
2878
3376
  },
@@ -3441,7 +3939,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
3441
3939
  operationId: 'policies.publish',
3442
3940
  summary: 'Publish a policy',
3443
3941
  description:
3444
- "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).",
3942
+ "Body is a full `Policy` — the server validates top-level shape (id, tenantId, semver version, kind ∈ closed enum, spec is an object), and `spec` for `tool-errors`, `hitl` and `retention` (a retention policy with an unknown domain or `mode: archive` is refused with `400 validation-failed`, naming the field). Other kinds' specs are their runtime consumer's to validate. Re-publishing an existing `(policyId, version)` returns `409 policy-already-registered`. A tenant has one retention policy per domain, plus one for `*`: a second policy id for a covered domain is refused with `409 policy-scope-taken` (`details.heldBy` names the policy that covers it; publish a new version of that one instead), and a new version can't move a policy to another domain (`409 policy-scope-changed`). 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).",
3445
3943
  tags: ['policies'],
3446
3944
  security: 'bearer',
3447
3945
  parameters: [IdempotencyKeyParam],
@@ -3452,7 +3950,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3452
3950
  '400': ErrorResponse(
3453
3951
  'Validation failed (see `details.issues`), or `kind-not-applied`: no runtime consumer applies that kind yet.',
3454
3952
  ),
3455
- '409': ErrorResponse('Policy already registered at that (id, version).'),
3953
+ '409': ErrorResponse(
3954
+ '`policy-already-registered`: that (id, version) exists. `policy-scope-taken`: another policy covers the retention domain (`details.heldBy`). `policy-scope-changed`: the version would move the policy to another domain.',
3955
+ ),
3456
3956
  },
3457
3957
  },
3458
3958
  {
@@ -3485,6 +3985,70 @@ export const OPERATIONS: readonly OperationSpec[] = [
3485
3985
  '200': { description: 'Reinstated.', schema: ref('ReinstatePolicyVersionResult') },
3486
3986
  ...CommonMutationErrors,
3487
3987
  '404': ErrorResponse('No policy at that (id, version) under this tenant.'),
3988
+ '409': ErrorResponse(
3989
+ "`policy-scope-taken`: another policy now covers the version's retention domain (`details.heldBy`); unregister it first. `policy-scope-changed`: the version covers a different domain from the policy's other versions.",
3990
+ ),
3991
+ },
3992
+ },
3993
+
3994
+ // ---------- retention (admin plane) ----------
3995
+ {
3996
+ method: 'get',
3997
+ honoPath: '/v1/retention/scheduled',
3998
+ openapiPath: '/v1/retention/scheduled',
3999
+ operationId: 'retention.scheduled',
4000
+ summary: 'List deleted rows scheduled for purging',
4001
+ description:
4002
+ "Tombstoned rows in every domain a retention policy covers, with when each is purged (`purgeAt`) and the policy that decides it. `domainsMissingAdapter` names the covered domains this deployment can't purge; `unpolicedDomains` the ones no policy covers, whose tombstones are kept; `conflicts` the domains two policies cover (stored before one policy per domain was enforced). `limit` caps the rows **per domain**; `hasMore` says some domain has more than it returned, and `nextCursor` (when the runtime can continue) is the `cursor` for the next page. Requires `admin` on the tenant.",
4003
+ tags: ['retention'],
4004
+ security: 'bearer',
4005
+ parameters: [
4006
+ RetentionDomainQueryParam,
4007
+ RetentionPastGraceOnlyQueryParam,
4008
+ LimitQueryParam,
4009
+ CursorQueryParam,
4010
+ ],
4011
+ responses: {
4012
+ '200': { description: 'Scheduled rows.', schema: ref('RetentionScheduledPage') },
4013
+ ...CommonAuthErrors,
4014
+ '403': ErrorResponse('Not a tenant admin.'),
4015
+ '400': ErrorResponse('Unknown `domain`.'),
4016
+ },
4017
+ },
4018
+ {
4019
+ method: 'post',
4020
+ honoPath: '/v1/retention/sweep',
4021
+ openapiPath: '/v1/retention/sweep',
4022
+ operationId: 'retention.sweep',
4023
+ summary: 'Purge the deleted rows past their grace',
4024
+ description:
4025
+ "Purges, for good, every tombstoned row past the grace of the retention policy that covers its domain (or only `domain`'s), up to `maxPerDomain` per domain; `remaining` counts what is left for the next call. Nothing sweeps on its own: call this (or `POST /v1/retention/sweep/{domain}`) from a schedule. A hold (`graceSeconds: -1`) keeps its domain's rows. Idempotent: a second call purges nothing new. Requires `admin` on the tenant.",
4026
+ tags: ['retention'],
4027
+ security: 'bearer',
4028
+ requestBody: { required: false, schema: ref('RetentionSweepBody') },
4029
+ responses: {
4030
+ '200': { description: 'What was purged, per domain.', schema: ref('RetentionSweepResult') },
4031
+ ...CommonMutationErrors,
4032
+ '403': ErrorResponse('Not a tenant admin.'),
4033
+ '400': ErrorResponse('Unknown `domain`, or `maxPerDomain` outside 1..10000.'),
4034
+ },
4035
+ },
4036
+ {
4037
+ method: 'post',
4038
+ honoPath: '/v1/retention/sweep/:domain',
4039
+ openapiPath: '/v1/retention/sweep/{domain}',
4040
+ operationId: 'retention.sweepDomain',
4041
+ summary: "Purge one domain's deleted rows past their grace",
4042
+ description: 'As `POST /v1/retention/sweep`, for one domain. Requires `admin` on the tenant.',
4043
+ tags: ['retention'],
4044
+ security: 'bearer',
4045
+ parameters: [RetentionDomainPathParam],
4046
+ requestBody: { required: false, schema: ref('RetentionSweepDomainBody') },
4047
+ responses: {
4048
+ '200': { description: 'What was purged.', schema: ref('RetentionSweepResult') },
4049
+ ...CommonMutationErrors,
4050
+ '403': ErrorResponse('Not a tenant admin.'),
4051
+ '400': ErrorResponse('Unknown `domain` (or `*`), or `maxPerDomain` outside 1..10000.'),
3488
4052
  },
3489
4053
  },
3490
4054
 
@@ -3578,8 +4142,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
3578
4142
  responses: {
3579
4143
  '201': { description: 'Eval suite published.', schema: ref('PublishEvalSuiteResult') },
3580
4144
  ...CommonMutationErrors,
3581
- '400': ErrorResponse('Validation failed (see `details.issues`).'),
4145
+ '400': ErrorResponse(
4146
+ "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
4147
+ ),
3582
4148
  '409': ErrorResponse('Eval suite already registered at that (id, version).'),
4149
+ '404': ErrorResponse(
4150
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
4151
+ ),
3583
4152
  },
3584
4153
  },
3585
4154
  {
@@ -3597,10 +4166,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
3597
4166
  responses: {
3598
4167
  '201': { description: 'Version published.', schema: ref('BuildJudgedSuiteResult') },
3599
4168
  ...CommonMutationErrors,
3600
- '400': ErrorResponse('Malformed body.'),
4169
+ '400': ErrorResponse("Malformed body; or `projectId` isn't a project id (a UUID)."),
3601
4170
  '403': ErrorResponse('`permission-denied`.'),
3602
4171
  '409': ErrorResponse('Eval suite already registered at that (id, version).'),
3603
4172
  '501': ErrorResponse('`test-sets-not-supported`: this deployment cannot build test sets.'),
4173
+ '404': ErrorResponse(
4174
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
4175
+ ),
3604
4176
  },
3605
4177
  },
3606
4178
  {
@@ -3750,10 +4322,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
3750
4322
  '201': { description: 'Published.', schema: ref('PublishBlockResult') },
3751
4323
  ...CommonMutationErrors,
3752
4324
  '400': ErrorResponse(
3753
- '`validation-failed` (see `details.issues`), or an unknown `projectId`.',
4325
+ "`validation-failed` (see `details.issues`), or an unknown `projectId`; or `projectId` isn't a project id (a UUID).",
3754
4326
  ),
3755
4327
  '403': ErrorResponse('`permission-denied`: no `write` on the project.'),
3756
4328
  '409': ErrorResponse('`block-already-registered` or `block-project-mismatch`.'),
4329
+ '404': ErrorResponse(
4330
+ "The body's `projectId` names no project of this tenant (`project-not-found`).",
4331
+ ),
3757
4332
  },
3758
4333
  },
3759
4334
  {
@@ -3807,9 +4382,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
3807
4382
  '201': { description: 'Eval run started.', schema: ref('StartEvalRunResult') },
3808
4383
  ...CommonMutationErrors,
3809
4384
  '400': ErrorResponse(
3810
- 'Malformed body (both / neither of `agentRef` / `flowRef`, dispatcher-input-invalid).',
4385
+ "Malformed body (both / neither of `agentRef` / `flowRef`, dispatcher-input-invalid); or `projectId` isn't a project id (a UUID).",
4386
+ ),
4387
+ '404': ErrorResponse(
4388
+ 'No eval suite with that id under this tenant; or `projectId` names no project of this tenant (`project-not-found`).',
3811
4389
  ),
3812
- '404': ErrorResponse('No eval suite with that id under this tenant.'),
3813
4390
  '422': ErrorResponse('No dispatcher registered for the suite kind.'),
3814
4391
  },
3815
4392
  },
@@ -4392,7 +4969,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4392
4969
  operationId: 'audit.authz.list',
4393
4970
  summary: 'List authz decision audit events',
4394
4971
  description:
4395
- 'Cursor-paginated read of `authz-decision` audit events for the tenant. Filters (all AND-composed): `?actorSubject=` / `?onBehalfOf=` / `?action=` / `?resource=` / `?outcome=` / `?runId=` / `?from=` / `?to=`. Admin@tenant only. Only mounted when `CreateAppInput.auditEvents` is wired.',
4972
+ 'Cursor-paginated read of `authz-decision` audit events for the tenant. Filters (all AND-composed): `?actorSubject=` / `?onBehalfOf=` / `?action=` / `?resource=` / `?outcome=` / `?runId=` / `?from=` / `?to=`. Oldest first; `?order=desc` for newest first. Admin@tenant only. Only mounted when `CreateAppInput.auditEvents` is wired.',
4396
4973
  tags: ['audit'],
4397
4974
  security: 'bearer',
4398
4975
  parameters: [
@@ -4454,6 +5031,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
4454
5031
  description: 'ISO 8601 upper bound (inclusive) on `timestamp`.',
4455
5032
  schema: { type: 'string', format: 'date-time' },
4456
5033
  },
5034
+ {
5035
+ name: 'order',
5036
+ in: 'query',
5037
+ required: false,
5038
+ description:
5039
+ '`asc` (the default): oldest first. `desc`: newest first. `nextCursor` continues in the same order.',
5040
+ schema: { type: 'string', enum: ['asc', 'desc'] },
5041
+ },
4457
5042
  ],
4458
5043
  responses: {
4459
5044
  '200': {
@@ -4500,7 +5085,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4500
5085
  },
4501
5086
  },
4502
5087
  ...CommonAuthErrors,
4503
- '400': ErrorResponse('Malformed cursor, `from`, `to`, or `outcome`.'),
5088
+ '400': ErrorResponse('Malformed cursor, `from`, `to`, `outcome`, or `order`.'),
4504
5089
  },
4505
5090
  },
4506
5091
 
@@ -4835,6 +5420,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4835
5420
  responses: {
4836
5421
  '204': { description: 'Removed (or already absent). No body.' },
4837
5422
  ...CommonAuthErrors,
5423
+ '501': ErrorResponse(
5424
+ "With authorization enforced, a runtime whose tenant-hierarchy binding can't change the membership together with its authorization tuple refuses with `authz-membership-unsupported`; nothing is changed.",
5425
+ ),
4838
5426
  },
4839
5427
  },
4840
5428
  {
@@ -4869,6 +5457,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4869
5457
  '404': ErrorResponse(
4870
5458
  'No membership for that (team, user) pair, or no such team under this tenant.',
4871
5459
  ),
5460
+ '501': ErrorResponse(
5461
+ "With authorization enforced, a runtime whose tenant-hierarchy binding can't change the membership together with its authorization tuple refuses with `authz-membership-unsupported`; nothing is changed.",
5462
+ ),
4872
5463
  },
4873
5464
  },
4874
5465
 
@@ -5106,6 +5697,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
5106
5697
  responses: {
5107
5698
  '204': { description: 'Removed (or already absent). No body.' },
5108
5699
  ...CommonAuthErrors,
5700
+ '501': ErrorResponse(
5701
+ "With authorization enforced, a runtime whose tenant-hierarchy binding can't change the membership together with its authorization tuple refuses with `authz-membership-unsupported`; nothing is changed.",
5702
+ ),
5109
5703
  },
5110
5704
  },
5111
5705
  {
@@ -5140,6 +5734,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
5140
5734
  '404': ErrorResponse(
5141
5735
  'No membership for that (project, user) pair, or no such project under this tenant.',
5142
5736
  ),
5737
+ '501': ErrorResponse(
5738
+ "With authorization enforced, a runtime whose tenant-hierarchy binding can't change the membership together with its authorization tuple refuses with `authz-membership-unsupported`; nothing is changed.",
5739
+ ),
5143
5740
  },
5144
5741
  },
5145
5742