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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/agent-binding.d.ts +8 -0
  2. package/dist/agent-binding.d.ts.map +1 -1
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +7 -1
  5. package/dist/app.js.map +1 -1
  6. package/dist/deploy-versions.d.ts +2 -0
  7. package/dist/deploy-versions.d.ts.map +1 -1
  8. package/dist/deploy-versions.js +1 -1
  9. package/dist/deploy-versions.js.map +1 -1
  10. package/dist/derive-agent-version.d.ts +9 -1
  11. package/dist/derive-agent-version.d.ts.map +1 -1
  12. package/dist/derive-agent-version.js +11 -1
  13. package/dist/derive-agent-version.js.map +1 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +1 -0
  16. package/dist/errors.js.map +1 -1
  17. package/dist/eval-run-binding.d.ts +7 -0
  18. package/dist/eval-run-binding.d.ts.map +1 -1
  19. package/dist/eval-run-binding.js.map +1 -1
  20. package/dist/eval-run-dispatcher.d.ts +3 -0
  21. package/dist/eval-run-dispatcher.d.ts.map +1 -1
  22. package/dist/eval-run-dispatcher.js.map +1 -1
  23. package/dist/flow-binding.d.ts +8 -0
  24. package/dist/flow-binding.d.ts.map +1 -1
  25. package/dist/guardrail-binding.d.ts +8 -0
  26. package/dist/guardrail-binding.d.ts.map +1 -1
  27. package/dist/handler-binding.d.ts +3 -0
  28. package/dist/handler-binding.d.ts.map +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/judged-dispatcher.d.ts +4 -0
  33. package/dist/judged-dispatcher.d.ts.map +1 -1
  34. package/dist/judged-dispatcher.js +14 -3
  35. package/dist/judged-dispatcher.js.map +1 -1
  36. package/dist/openapi/operations.d.ts.map +1 -1
  37. package/dist/openapi/operations.js +21 -6
  38. package/dist/openapi/operations.js.map +1 -1
  39. package/dist/openapi/schemas.d.ts +6 -0
  40. package/dist/openapi/schemas.d.ts.map +1 -1
  41. package/dist/openapi/schemas.js +341 -2
  42. package/dist/openapi/schemas.js.map +1 -1
  43. package/dist/registry-read-only.d.ts +32 -0
  44. package/dist/registry-read-only.d.ts.map +1 -0
  45. package/dist/registry-read-only.js +22 -0
  46. package/dist/registry-read-only.js.map +1 -0
  47. package/dist/routes/agents.d.ts.map +1 -1
  48. package/dist/routes/agents.js +7 -1
  49. package/dist/routes/agents.js.map +1 -1
  50. package/dist/routes/blocks.d.ts.map +1 -1
  51. package/dist/routes/blocks.js +37 -12
  52. package/dist/routes/blocks.js.map +1 -1
  53. package/dist/routes/deployments.d.ts.map +1 -1
  54. package/dist/routes/deployments.js +16 -0
  55. package/dist/routes/deployments.js.map +1 -1
  56. package/dist/routes/eval-comparison.d.ts +3 -2
  57. package/dist/routes/eval-comparison.d.ts.map +1 -1
  58. package/dist/routes/eval-comparison.js +39 -3
  59. package/dist/routes/eval-comparison.js.map +1 -1
  60. package/dist/routes/eval-runs.d.ts +7 -1
  61. package/dist/routes/eval-runs.d.ts.map +1 -1
  62. package/dist/routes/eval-runs.js +34 -3
  63. package/dist/routes/eval-runs.js.map +1 -1
  64. package/dist/routes/eval-versions.d.ts +25 -0
  65. package/dist/routes/eval-versions.d.ts.map +1 -0
  66. package/dist/routes/eval-versions.js +66 -0
  67. package/dist/routes/eval-versions.js.map +1 -0
  68. package/dist/routes/flows.d.ts.map +1 -1
  69. package/dist/routes/flows.js +4 -0
  70. package/dist/routes/flows.js.map +1 -1
  71. package/dist/routes/guardrails.d.ts.map +1 -1
  72. package/dist/routes/guardrails.js +4 -0
  73. package/dist/routes/guardrails.js.map +1 -1
  74. package/dist/routes/hierarchy-errors.d.ts +10 -0
  75. package/dist/routes/hierarchy-errors.d.ts.map +1 -1
  76. package/dist/routes/hierarchy-errors.js +8 -0
  77. package/dist/routes/hierarchy-errors.js.map +1 -1
  78. package/dist/routes/projects.d.ts.map +1 -1
  79. package/dist/routes/projects.js +13 -7
  80. package/dist/routes/projects.js.map +1 -1
  81. package/dist/routes/runs.js +1 -0
  82. package/dist/routes/runs.js.map +1 -1
  83. package/dist/routes/teams.d.ts.map +1 -1
  84. package/dist/routes/teams.js +12 -6
  85. package/dist/routes/teams.js.map +1 -1
  86. package/dist/routes/tools.d.ts.map +1 -1
  87. package/dist/routes/tools.js +4 -0
  88. package/dist/routes/tools.js.map +1 -1
  89. package/dist/tool-binding.d.ts +8 -0
  90. package/dist/tool-binding.d.ts.map +1 -1
  91. package/openapi.json +729 -17
  92. package/package.json +21 -21
  93. package/src/agent-binding.ts +9 -0
  94. package/src/app.ts +10 -1
  95. package/src/deploy-versions.ts +1 -1
  96. package/src/derive-agent-version.ts +12 -1
  97. package/src/errors.ts +1 -0
  98. package/src/eval-run-binding.ts +7 -0
  99. package/src/eval-run-dispatcher.ts +3 -0
  100. package/src/flow-binding.ts +9 -0
  101. package/src/guardrail-binding.ts +9 -0
  102. package/src/handler-binding.ts +3 -0
  103. package/src/index.ts +1 -0
  104. package/src/judged-dispatcher.ts +27 -4
  105. package/src/openapi/operations.ts +56 -6
  106. package/src/openapi/schemas.ts +361 -2
  107. package/src/registry-read-only.ts +43 -0
  108. package/src/routes/agents.ts +10 -1
  109. package/src/routes/blocks.ts +39 -14
  110. package/src/routes/deployments.ts +25 -0
  111. package/src/routes/eval-comparison.ts +37 -3
  112. package/src/routes/eval-runs.ts +46 -3
  113. package/src/routes/eval-versions.ts +110 -0
  114. package/src/routes/flows.ts +7 -0
  115. package/src/routes/guardrails.ts +7 -0
  116. package/src/routes/hierarchy-errors.ts +9 -0
  117. package/src/routes/projects.ts +18 -7
  118. package/src/routes/runs.ts +1 -0
  119. package/src/routes/teams.ts +14 -6
  120. package/src/routes/tools.ts +7 -0
  121. package/src/tool-binding.ts +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/api",
3
- "version": "0.1.4-rc.0",
3
+ "version": "0.1.4-rc.1",
4
4
  "description": "REST + SSE HTTP surface for Kindgi™. createApp assembles a Hono app serving the /v1/* REST API (bearer or session-token auth; OpenAPI 3.1 document at /v1/openapi.json) and an optional S3-compatible /s3/* surface (SigV4), over caller-plugged bindings for the runtime (runs, agents, flows, HITL, supervisor) and for storage. Route conventions: docs/API-ROUTE-CONVENTIONS.md.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,30 +32,30 @@
32
32
  "openapi.json"
33
33
  ],
34
34
  "dependencies": {
35
- "@kindgi/agents": "0.1.4-rc.0",
36
- "@kindgi/audit-events": "0.1.4-rc.0",
37
- "@kindgi/compliance": "0.1.4-rc.0",
38
- "@kindgi/authz": "0.1.4-rc.0",
39
- "@kindgi/blob-binding": "0.1.4-rc.0",
40
- "@kindgi/capabilities": "0.1.4-rc.0",
41
- "@kindgi/crypto": "0.1.4-rc.0",
42
- "@kindgi/flow": "0.1.4-rc.0",
43
- "@kindgi/guardrails": "0.1.4-rc.0",
44
- "@kindgi/memory": "0.1.4-rc.0",
45
- "@kindgi/platform": "0.1.4-rc.0",
46
- "@kindgi/provenance": "0.1.4-rc.0",
47
- "@kindgi/runtime": "0.1.4-rc.0",
48
- "@kindgi/policy-contract": "0.1.4-rc.0",
49
- "@kindgi/schema": "0.1.4-rc.0",
50
- "@kindgi/tools": "0.1.4-rc.0",
51
- "@kindgi/types": "0.1.4-rc.0",
35
+ "@kindgi/agents": "0.1.4-rc.1",
36
+ "@kindgi/audit-events": "0.1.4-rc.1",
37
+ "@kindgi/compliance": "0.1.4-rc.1",
38
+ "@kindgi/authz": "0.1.4-rc.1",
39
+ "@kindgi/blob-binding": "0.1.4-rc.1",
40
+ "@kindgi/capabilities": "0.1.4-rc.1",
41
+ "@kindgi/crypto": "0.1.4-rc.1",
42
+ "@kindgi/flow": "0.1.4-rc.1",
43
+ "@kindgi/guardrails": "0.1.4-rc.1",
44
+ "@kindgi/memory": "0.1.4-rc.1",
45
+ "@kindgi/platform": "0.1.4-rc.1",
46
+ "@kindgi/provenance": "0.1.4-rc.1",
47
+ "@kindgi/runtime": "0.1.4-rc.1",
48
+ "@kindgi/policy-contract": "0.1.4-rc.1",
49
+ "@kindgi/schema": "0.1.4-rc.1",
50
+ "@kindgi/tools": "0.1.4-rc.1",
51
+ "@kindgi/types": "0.1.4-rc.1",
52
52
  "@scalar/hono-api-reference": "^0.12.2",
53
53
  "hono": "^4.6.14"
54
54
  },
55
55
  "devDependencies": {
56
- "@kindgi/audit-events-inmemory": "0.1.4-rc.0",
57
- "@kindgi/specs": "0.1.4-rc.0",
58
- "@kindgi/testing": "0.1.4-rc.0",
56
+ "@kindgi/audit-events-inmemory": "0.1.4-rc.1",
57
+ "@kindgi/specs": "0.1.4-rc.1",
58
+ "@kindgi/testing": "0.1.4-rc.1",
59
59
  "@types/aws4": "^1.11.6",
60
60
  "@types/node": "^22.10.5",
61
61
  "aws4": "^1.13.2",
@@ -6,6 +6,8 @@ import type { TupleEnqueueHook } from '@kindgi/authz';
6
6
  import type { Scope } from '@kindgi/platform';
7
7
  import type { Cursor, ProjectId, Semver, TenantId } from '@kindgi/types';
8
8
 
9
+ import type { RegistryReadOnly } from './registry-read-only.js';
10
+
9
11
  /**
10
12
  * Caller-plugged surface for the agent catalog. Same shape as
11
13
  * `TokenAdmin` / `ReviewerBinding` / `RunHandlerBinding`: the API
@@ -22,6 +24,13 @@ import type { Cursor, ProjectId, Semver, TenantId } from '@kindgi/types';
22
24
  * cursor round-trips as a string; it never inspects the payload.
23
25
  */
24
26
  export interface AgentRegistryBinding {
27
+ /**
28
+ * Set when this registry takes no writes (under `kindgi dev`, the
29
+ * pack's files are the source of its agents): every write is refused
30
+ * with `409 registry-read-only` and this reason, before the binding is
31
+ * called. See `RegistryReadOnly`.
32
+ */
33
+ readonly readOnly?: RegistryReadOnly;
25
34
  /**
26
35
  * Cursor-paginated list of agents (latest version per id, sorted by
27
36
  * agent id ascending). Optional `nameFilter` is a prefix match on the
package/src/app.ts CHANGED
@@ -1289,7 +1289,16 @@ export function createApp(input: CreateAppInput): Hono<AppEnv> {
1289
1289
  // `evalSuiteRegistry` binding: the dispatcher itself surfaces
1290
1290
  // `suite-not-found` when the caller-plugged binding can't resolve
1291
1291
  // the suite id, so the API layer stays consumer-neutral.
1292
- const evalRuns = evalRunsRouters(input.evalRunBinding);
1292
+ const evalRuns = evalRunsRouters(
1293
+ input.evalRunBinding,
1294
+ input.flowRegistry !== undefined
1295
+ ? {
1296
+ flows: input.flowRegistry,
1297
+ ...(input.agentRegistry !== undefined && { agents: input.agentRegistry }),
1298
+ ...(input.toolRegistry !== undefined && { tools: input.toolRegistry }),
1299
+ }
1300
+ : undefined,
1301
+ );
1293
1302
  v1.route('/eval-suites', evalRuns.start);
1294
1303
  v1.route('/eval-runs', evalRuns.readback);
1295
1304
  }
@@ -151,7 +151,7 @@ async function registerNextFree<T extends PinnedDefinition>(
151
151
  }
152
152
 
153
153
  /** A definition: everything but its version and what the runtime sets. */
154
- function definitionKey(definition: PinnedDefinition): string {
154
+ export function definitionKey(definition: PinnedDefinition): string {
155
155
  const { version: _v, pins: _p, pinsDigest: _d, derivedFrom: _f, ...rest } = definition;
156
156
  return canonicalize(rest);
157
157
  }
@@ -16,6 +16,7 @@ import type { ProjectId, Semver, TenantId } from '@kindgi/types';
16
16
  import type { AgentRegistryBinding, AgentVersionRecord } from './agent-binding.js';
17
17
  import { activeAgentVersions } from './agent-pins.js';
18
18
  import type { BlockRegistryBinding } from './block-binding.js';
19
+ import { definitionKey } from './deploy-versions.js';
19
20
 
20
21
  /** Which data-block pins to swap, by kind then block id → exact version. */
21
22
  export interface PinSwaps {
@@ -48,6 +49,8 @@ export interface SwapIssue {
48
49
 
49
50
  export type DeriveAgentVersionOutcome =
50
51
  | { readonly kind: 'ok'; readonly agent: Agent }
52
+ /** An active version already has this definition and these pins: that one, unchanged. */
53
+ | { readonly kind: 'reused'; readonly agent: Agent }
51
54
  | { readonly kind: 'not-found' }
52
55
  | { readonly kind: 'unpinned' }
53
56
  | { readonly kind: 'invalid'; readonly issues: readonly SwapIssue[] }
@@ -63,7 +66,10 @@ const MAX_TRIES = 100;
63
66
  * reaching an agent without a code change. The new version is the old
64
67
  * one's bag with those pins swapped (`derivedFrom: { version, reason:
65
68
  * 'edited', label, by }`), numbered the next free patch after the
66
- * agent's highest version (versions never change).
69
+ * agent's highest version (versions never change). If an active version
70
+ * already holds that definition and those pins (the same swap derived
71
+ * before, or a deploy that registered it), that version is returned
72
+ * unchanged (`reused`) rather than a duplicate.
67
73
  *
68
74
  * Only data-block pins swap: a swap must name a block the version
69
75
  * already references (adding one is a code change), at a published,
@@ -114,6 +120,11 @@ async function publishNextFree(
114
120
  ): Promise<DeriveAgentVersionOutcome> {
115
121
  const { agents, tenantId, agentId } = input;
116
122
  const active = await activeAgentVersions(agents, tenantId, agentId);
123
+ // The same swap again (or a deploy that registered it) finds the
124
+ // version that already holds it: versions are never duplicated.
125
+ const key = definitionKey(derived);
126
+ const same = active.find((a) => a.pinsDigest === derived.pinsDigest && definitionKey(a) === key);
127
+ if (same !== undefined) return { kind: 'reused', agent: same };
117
128
  const highest =
118
129
  latestVersion(active.map((a) => a.version as unknown as string)) ??
119
130
  (derived.version as unknown as string);
package/src/errors.ts CHANGED
@@ -67,6 +67,7 @@ export const ERROR_CODE_TO_STATUS: Readonly<Record<string, number>> = {
67
67
  'duplicate-edge-id': 409,
68
68
  'agent-version-mismatch': 409,
69
69
  'agent-already-registered': 409,
70
+ 'registry-read-only': 409,
70
71
  'agent-gone': 410,
71
72
  'flow-gone': 410,
72
73
  'policy-gone': 410,
@@ -2,6 +2,7 @@
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
3
 
4
4
  import type { AgentId } from '@kindgi/agents';
5
+ import type { FlowVersionOverrides } from '@kindgi/flow';
5
6
  import type { Scope } from '@kindgi/platform';
6
7
  import type { Cursor, FlowId, ProjectId, RunId, Semver, TenantId, Timestamp } from '@kindgi/types';
7
8
 
@@ -145,6 +146,12 @@ export interface EvalComparison {
145
146
  readonly repetitions: number;
146
147
  /** How many ranked items `weightedPrecisionAtK` looks at (1–100). */
147
148
  readonly k: number;
149
+ /**
150
+ * For a flow candidate: agents and tools its replays run at other exact
151
+ * versions than the flow version's pins ("this flow, with `acme.scorer`
152
+ * at 0.4.0"), without publishing a new flow version.
153
+ */
154
+ readonly versions?: FlowVersionOverrides;
148
155
  }
149
156
 
150
157
  export interface EvalRunStartInput {
@@ -4,6 +4,7 @@
4
4
  import { randomUUID } from 'node:crypto';
5
5
 
6
6
  import type { ReplayTurnReport } from '@kindgi/agents';
7
+ import type { FlowVersionOverrides } from '@kindgi/flow';
7
8
  import type { RunReplayRef } from '@kindgi/runtime';
8
9
  import type { ProjectId, RunId, TenantId, Timestamp } from '@kindgi/types';
9
10
 
@@ -63,6 +64,8 @@ export interface EvalRunSubjectInvokeInput {
63
64
  readonly replay?: RunReplayRef;
64
65
  /** The conversation before the past turn, oldest first: a replayed turn starts from it. */
65
66
  readonly history?: readonly unknown[];
67
+ /** A flow target's agents and tools at other exact versions (`EvalComparison.versions`). */
68
+ readonly versions?: FlowVersionOverrides;
66
69
  }
67
70
 
68
71
  export interface EvalRunSubjectInvokeOutcome {
@@ -6,6 +6,8 @@ import type { Flow } from '@kindgi/flow';
6
6
  import type { Scope } from '@kindgi/platform';
7
7
  import type { Cursor, FlowId, ProjectId, TenantId } from '@kindgi/types';
8
8
 
9
+ import type { RegistryReadOnly } from './registry-read-only.js';
10
+
9
11
  /**
10
12
  * Caller-plugged surface for the flow catalog. Mirrors
11
13
  * `AgentRegistryBinding` 1:1 — the API package does NOT own registry
@@ -26,6 +28,13 @@ import type { Cursor, FlowId, ProjectId, TenantId } from '@kindgi/types';
26
28
  * cursor round-trips as a string; it never inspects the payload.
27
29
  */
28
30
  export interface FlowRegistryBinding {
31
+ /**
32
+ * Set when this registry takes no writes (under `kindgi dev`, the
33
+ * pack's files are the source of its flows): every write is refused
34
+ * with `409 registry-read-only` and this reason, before the binding is
35
+ * called. See `RegistryReadOnly`.
36
+ */
37
+ readonly readOnly?: RegistryReadOnly;
29
38
  /**
30
39
  * Cursor-paginated list of flows (latest version per id, sorted by
31
40
  * flow id ascending). Optional `nameFilter` is a prefix match on the
@@ -6,6 +6,8 @@ import type { Guardrail } from '@kindgi/guardrails';
6
6
  import type { Scope } from '@kindgi/platform';
7
7
  import type { Cursor, GuardrailId, ProjectId, TenantId } from '@kindgi/types';
8
8
 
9
+ import type { RegistryReadOnly } from './registry-read-only.js';
10
+
9
11
  /**
10
12
  * Caller-plugged surface for the guardrail catalog. Same shape as
11
13
  * `AgentRegistryBinding` / `ToolRegistryBinding`: the API package does
@@ -27,6 +29,13 @@ import type { Cursor, GuardrailId, ProjectId, TenantId } from '@kindgi/types';
27
29
  * inspects the payload.
28
30
  */
29
31
  export interface GuardrailRegistryBinding {
32
+ /**
33
+ * Set when this registry takes no writes (under `kindgi dev`, the
34
+ * pack's files are the source of its guardrails): every write is refused
35
+ * with `409 registry-read-only` and this reason, before the binding is
36
+ * called. See `RegistryReadOnly`.
37
+ */
38
+ readonly readOnly?: RegistryReadOnly;
30
39
  /**
31
40
  * Cursor-paginated list of guardrails sorted by id ascending.
32
41
  * Optional `nameFilter` is a prefix match on the guardrail id —
@@ -2,6 +2,7 @@
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
3
 
4
4
  import type { AgentId } from '@kindgi/agents';
5
+ import type { FlowVersionOverrides } from '@kindgi/flow';
5
6
  import type { FlowId, ProjectId, RunId, Semver, TenantId } from '@kindgi/types';
6
7
 
7
8
  /**
@@ -107,6 +108,8 @@ export interface InvokeFlowBindingInput {
107
108
  * can't run in the background may treat `false` like `true`.
108
109
  */
109
110
  readonly wait?: boolean;
111
+ /** Agents and tools to run at other exact versions than the flow version's pins (`RunFlowInput.versions`). */
112
+ readonly versions?: FlowVersionOverrides;
110
113
  }
111
114
 
112
115
  export type RunHandlerOutcome =
package/src/index.ts CHANGED
@@ -239,6 +239,7 @@ export type {
239
239
  AgentUnregisterInput,
240
240
  AgentUnregisterOutcome,
241
241
  } from './agent-binding.js';
242
+ export type { RegistryReadOnly } from './registry-read-only.js';
242
243
  export type {
243
244
  FlowGetInput,
244
245
  FlowGetVersionInput,
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import type { ReplayTurnReport } from '@kindgi/agents';
19
+ import type { FlowVersionOverrides } from '@kindgi/flow';
19
20
  import type { RunId } from '@kindgi/types';
20
21
 
21
22
  import type { EvalCaseStoreBinding, JudgedEvalCase } from './eval-case-binding.js';
@@ -81,7 +82,13 @@ export type RecordedVersion =
81
82
  /** What ran on the cases: an agent version, or a flow version. */
82
83
  export type ComparisonCandidate =
83
84
  | { readonly kind: 'agent'; readonly agentId: string; readonly version: string }
84
- | { readonly kind: 'flow'; readonly flowId: string; readonly version: string };
85
+ | {
86
+ readonly kind: 'flow';
87
+ readonly flowId: string;
88
+ readonly version: string;
89
+ /** Agents and tools its replays ran at other versions than the flow version's pins. */
90
+ readonly versions?: FlowVersionOverrides;
91
+ };
85
92
 
86
93
  /** What a comparison eval run concluded: what a promotion gate reads. */
87
94
  export interface JudgedComparisonSummary {
@@ -150,6 +157,9 @@ export interface JudgedDispatcherOptions {
150
157
  /** Cases read per page. */
151
158
  const CASE_PAGE = 100;
152
159
 
160
+ export const VERSIONS_NEED_A_FLOW =
161
+ '`versions` runs a flow with some of its agents or tools at other versions: it needs `flowRef`.';
162
+
153
163
  function validateComparison(
154
164
  suite: { readonly spec: Readonly<Record<string, unknown>> },
155
165
  target: AgentRef | FlowRef,
@@ -168,6 +178,9 @@ function validateComparison(
168
178
  return { kind: 'err', message: 'The test set has no cases.' };
169
179
  }
170
180
  const c = comparison ?? DEFAULT_COMPARISON;
181
+ if (c.versions !== undefined && 'agentId' in target) {
182
+ return { kind: 'err', message: VERSIONS_NEED_A_FLOW };
183
+ }
171
184
  if (c.baseline !== 'recorded') {
172
185
  return { kind: 'err', message: "Only `baseline: 'recorded'` runs today." };
173
186
  }
@@ -337,6 +350,8 @@ async function invokeCase(
337
350
  evalRunId: ctx.runId as unknown as string,
338
351
  },
339
352
  ...(judgedCase.subject.kind === 'agent' && { history: judgedCase.context?.history ?? [] }),
353
+ ...(!('agentId' in ctx.target) &&
354
+ ctx.comparison?.versions !== undefined && { versions: ctx.comparison.versions }),
340
355
  });
341
356
  } catch (cause) {
342
357
  return { error: cause instanceof Error ? cause.message : String(cause) };
@@ -467,7 +482,7 @@ function summarize(
467
482
  : 'failed',
468
483
  completedAt: new Date().toISOString(),
469
484
  suite: { id: ctx.suite.id, version: ctx.suite.version },
470
- candidate: candidateOf(ctx.target),
485
+ candidate: candidateOf(ctx.target, ctx.comparison?.versions),
471
486
  baseline: {
472
487
  kind: 'recorded',
473
488
  versions: [...versions.values()].map(
@@ -500,8 +515,16 @@ function summarize(
500
515
  };
501
516
  }
502
517
 
503
- function candidateOf(target: AgentRef | FlowRef): ComparisonCandidate {
518
+ function candidateOf(
519
+ target: AgentRef | FlowRef,
520
+ versions: FlowVersionOverrides | undefined,
521
+ ): ComparisonCandidate {
504
522
  return 'agentId' in target
505
523
  ? { kind: 'agent', agentId: target.agentId as unknown as string, version: target.version ?? '' }
506
- : { kind: 'flow', flowId: target.flowId as unknown as string, version: target.version ?? '' };
524
+ : {
525
+ kind: 'flow',
526
+ flowId: target.flowId as unknown as string,
527
+ version: target.version ?? '',
528
+ ...(versions !== undefined && { versions }),
529
+ };
507
530
  }
@@ -1686,8 +1686,16 @@ export const OPERATIONS: readonly OperationSpec[] = [
1686
1686
  parameters: [AgentIdPathParam, IdempotencyKeyParam],
1687
1687
  requestBody: { required: true, schema: ref('DeriveAgentVersionBody') },
1688
1688
  responses: {
1689
+ '200': {
1690
+ description:
1691
+ 'An active version already holds this definition and these pins (the same swap derived before, or a deploy that registered it): that version, unchanged.',
1692
+ schema: ref('Agent'),
1693
+ },
1689
1694
  '201': { description: 'The derived agent version.', schema: ref('Agent') },
1690
1695
  ...CommonMutationErrors,
1696
+ '409': ErrorResponse(
1697
+ "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
+ ),
1691
1699
  '400': ErrorResponse(
1692
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`).",
1693
1701
  ),
@@ -1725,7 +1733,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1725
1733
  '201': { description: 'Agent published.', schema: ref('PublishAgentResult') },
1726
1734
  ...CommonMutationErrors,
1727
1735
  '400': ErrorResponse('Validation failed (see `details.issues`).'),
1728
- '409': ErrorResponse('Agent already registered at that (id, version).'),
1736
+ '409': ErrorResponse(
1737
+ "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
+ ),
1729
1739
  },
1730
1740
  },
1731
1741
  {
@@ -1740,6 +1750,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1740
1750
  responses: {
1741
1751
  '200': { description: 'Unregistered.', schema: ref('UnregisterAgentResult') },
1742
1752
  ...CommonMutationErrors,
1753
+ '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.",
1755
+ ),
1743
1756
  '404': ErrorResponse('No agent at that (id, version) under this tenant.'),
1744
1757
  },
1745
1758
  },
@@ -1757,6 +1770,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1757
1770
  responses: {
1758
1771
  '200': { description: 'Reinstated.', schema: ref('ReinstateAgentVersionResult') },
1759
1772
  ...CommonMutationErrors,
1773
+ '409': ErrorResponse(
1774
+ "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.",
1775
+ ),
1760
1776
  '404': ErrorResponse('No agent at that (id, version) under this tenant.'),
1761
1777
  },
1762
1778
  },
@@ -1848,7 +1864,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1848
1864
  '201': { description: 'Flow published.', schema: ref('PublishFlowResult') },
1849
1865
  ...CommonMutationErrors,
1850
1866
  '400': ErrorResponse('Validation failed (see `details.issues`).'),
1851
- '409': ErrorResponse('Flow already registered at that (id, version).'),
1867
+ '409': ErrorResponse(
1868
+ "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
+ ),
1852
1870
  },
1853
1871
  },
1854
1872
  {
@@ -1863,6 +1881,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1863
1881
  responses: {
1864
1882
  '200': { description: 'Unregistered.', schema: ref('UnregisterFlowResult') },
1865
1883
  ...CommonMutationErrors,
1884
+ '409': ErrorResponse(
1885
+ "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.",
1886
+ ),
1866
1887
  '404': ErrorResponse('No flow at that (id, version) under this tenant.'),
1867
1888
  },
1868
1889
  },
@@ -1880,6 +1901,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1880
1901
  responses: {
1881
1902
  '200': { description: 'Reinstated.', schema: ref('ReinstateFlowVersionResult') },
1882
1903
  ...CommonMutationErrors,
1904
+ '409': ErrorResponse(
1905
+ "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.",
1906
+ ),
1883
1907
  '404': ErrorResponse('No flow at that (id, version) under this tenant.'),
1884
1908
  },
1885
1909
  },
@@ -1975,7 +1999,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1975
1999
  '201': { description: 'Tool registered.', schema: ref('RegisterToolResult') },
1976
2000
  ...CommonMutationErrors,
1977
2001
  '400': ErrorResponse('Validation failed (see `details.issues`).'),
1978
- '409': ErrorResponse('Tool already registered at that id.'),
2002
+ '409': ErrorResponse(
2003
+ "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
+ ),
1979
2005
  },
1980
2006
  },
1981
2007
  {
@@ -1990,6 +2016,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1990
2016
  responses: {
1991
2017
  '200': { description: 'Unregistered.', schema: ref('UnregisterToolResult') },
1992
2018
  ...CommonMutationErrors,
2019
+ '409': ErrorResponse(
2020
+ "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.",
2021
+ ),
1993
2022
  '404': ErrorResponse('No tool at that (id, version) under this tenant.'),
1994
2023
  },
1995
2024
  },
@@ -2007,6 +2036,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2007
2036
  responses: {
2008
2037
  '200': { description: 'Reinstated.', schema: ref('ReinstateToolVersionResult') },
2009
2038
  ...CommonMutationErrors,
2039
+ '409': ErrorResponse(
2040
+ "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.",
2041
+ ),
2010
2042
  '404': ErrorResponse('No tool at that (id, version) under this tenant.'),
2011
2043
  },
2012
2044
  },
@@ -2067,7 +2099,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2067
2099
  '201': { description: 'Guardrail registered.', schema: ref('RegisterGuardrailResult') },
2068
2100
  ...CommonMutationErrors,
2069
2101
  '400': ErrorResponse('Validation failed (see `details.issues`).'),
2070
- '409': ErrorResponse('Guardrail already registered at that id.'),
2102
+ '409': ErrorResponse(
2103
+ "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
+ ),
2071
2105
  },
2072
2106
  },
2073
2107
  {
@@ -2082,6 +2116,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2082
2116
  responses: {
2083
2117
  '200': { description: 'Unregistered.', schema: ref('UnregisterGuardrailResult') },
2084
2118
  ...CommonMutationErrors,
2119
+ '409': ErrorResponse(
2120
+ "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.",
2121
+ ),
2085
2122
  '404': ErrorResponse('No guardrail with that id under this tenant.'),
2086
2123
  },
2087
2124
  },
@@ -4131,6 +4168,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4131
4168
  schema: ref('DeploymentRecord'),
4132
4169
  },
4133
4170
  ...CommonMutationErrors,
4171
+ '409': ErrorResponse(
4172
+ "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.",
4173
+ ),
4134
4174
  '400': ErrorResponse(
4135
4175
  'Signature invalid, image unverifiable, or deployment-validation-failed with per-primitive `details[]`.',
4136
4176
  ),
@@ -4632,6 +4672,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4632
4672
  responses: {
4633
4673
  '201': { description: 'Team created.', schema: ref('CreateResourceResult') },
4634
4674
  ...CommonMutationErrors,
4675
+ '404': ErrorResponse(
4676
+ 'org-not-found: `orgId` names no org of the tenant (it never existed, or it was deleted).',
4677
+ ),
4635
4678
  },
4636
4679
  },
4637
4680
  {
@@ -4679,7 +4722,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4679
4722
  responses: {
4680
4723
  '204': { description: 'Updated. No body.' },
4681
4724
  ...CommonMutationErrors,
4682
- '404': ErrorResponse('No team with that id under this tenant.'),
4725
+ '404': ErrorResponse(
4726
+ 'team-not-found: no team with that id under this tenant; or org-not-found: `orgId` names no org of the tenant (it never existed, or it was deleted).',
4727
+ ),
4683
4728
  },
4684
4729
  },
4685
4730
  {
@@ -4893,6 +4938,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4893
4938
  responses: {
4894
4939
  '201': { description: 'Project created.', schema: ref('CreateResourceResult') },
4895
4940
  ...CommonMutationErrors,
4941
+ '404': ErrorResponse(
4942
+ 'org-not-found: `orgId` names no org of the tenant (it never existed, or it was deleted).',
4943
+ ),
4896
4944
  },
4897
4945
  },
4898
4946
  {
@@ -4942,7 +4990,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4942
4990
  responses: {
4943
4991
  '204': { description: 'Updated. No body.' },
4944
4992
  ...CommonMutationErrors,
4945
- '404': ErrorResponse('No project with that id under this tenant.'),
4993
+ '404': ErrorResponse(
4994
+ 'project-not-found: no project with that id under this tenant; or org-not-found: `orgId` names no org of the tenant (it never existed, or it was deleted).',
4995
+ ),
4946
4996
  },
4947
4997
  },
4948
4998
  {