@kindgi/api 0.1.4-rc.5 → 0.1.5-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 (507) hide show
  1. package/dist/agent-binding.d.ts +13 -0
  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 +18 -2
  5. package/dist/agent-pins.js.map +1 -1
  6. package/dist/app.d.ts +110 -17
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +163 -35
  9. package/dist/app.js.map +1 -1
  10. package/dist/block-publish.d.ts +56 -0
  11. package/dist/block-publish.d.ts.map +1 -0
  12. package/dist/block-publish.js +132 -0
  13. package/dist/block-publish.js.map +1 -0
  14. package/dist/caller.d.ts +24 -0
  15. package/dist/caller.d.ts.map +1 -0
  16. package/dist/caller.js +47 -0
  17. package/dist/caller.js.map +1 -0
  18. package/dist/capability-binding.d.ts +11 -0
  19. package/dist/capability-binding.d.ts.map +1 -1
  20. package/dist/derive-agent-version.d.ts +7 -0
  21. package/dist/derive-agent-version.d.ts.map +1 -1
  22. package/dist/derive-agent-version.js +4 -0
  23. package/dist/derive-agent-version.js.map +1 -1
  24. package/dist/errors.d.ts.map +1 -1
  25. package/dist/errors.js +73 -7
  26. package/dist/errors.js.map +1 -1
  27. package/dist/eval-case-binding.d.ts +16 -4
  28. package/dist/eval-case-binding.d.ts.map +1 -1
  29. package/dist/eval-run-binding.d.ts +31 -0
  30. package/dist/eval-run-binding.d.ts.map +1 -1
  31. package/dist/eval-run-dispatcher.d.ts +7 -0
  32. package/dist/eval-run-dispatcher.d.ts.map +1 -1
  33. package/dist/eval-run-dispatcher.js.map +1 -1
  34. package/dist/eval-sample.d.ts +11 -0
  35. package/dist/eval-sample.d.ts.map +1 -0
  36. package/dist/eval-sample.js +34 -0
  37. package/dist/eval-sample.js.map +1 -0
  38. package/dist/eval-suite-binding.d.ts +13 -0
  39. package/dist/eval-suite-binding.d.ts.map +1 -1
  40. package/dist/eval-suite-binding.js.map +1 -1
  41. package/dist/flow-binding.d.ts +18 -0
  42. package/dist/flow-binding.d.ts.map +1 -1
  43. package/dist/flow-pins.d.ts.map +1 -1
  44. package/dist/flow-pins.js +18 -2
  45. package/dist/flow-pins.js.map +1 -1
  46. package/dist/gate.d.ts.map +1 -1
  47. package/dist/gate.js +25 -0
  48. package/dist/gate.js.map +1 -1
  49. package/dist/handler-binding.d.ts +32 -0
  50. package/dist/handler-binding.d.ts.map +1 -1
  51. package/dist/hitl-binding.d.ts +23 -0
  52. package/dist/hitl-binding.d.ts.map +1 -1
  53. package/dist/identity-directory-binding.d.ts +82 -4
  54. package/dist/identity-directory-binding.d.ts.map +1 -1
  55. package/dist/identity-provider-binding.d.ts +197 -17
  56. package/dist/identity-provider-binding.d.ts.map +1 -1
  57. package/dist/improvement-pass-binding.d.ts +196 -0
  58. package/dist/improvement-pass-binding.d.ts.map +1 -0
  59. package/dist/improvement-pass-binding.js +9 -0
  60. package/dist/improvement-pass-binding.js.map +1 -0
  61. package/dist/index.d.ts +35 -17
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +11 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/judged-dispatcher.d.ts +25 -4
  66. package/dist/judged-dispatcher.d.ts.map +1 -1
  67. package/dist/judged-dispatcher.js +43 -6
  68. package/dist/judged-dispatcher.js.map +1 -1
  69. package/dist/judgment-binding.d.ts +25 -1
  70. package/dist/judgment-binding.d.ts.map +1 -1
  71. package/dist/mcp-endpoint-binding.d.ts +7 -0
  72. package/dist/mcp-endpoint-binding.d.ts.map +1 -1
  73. package/dist/memory-binding.d.ts +124 -17
  74. package/dist/memory-binding.d.ts.map +1 -1
  75. package/dist/memory-erasure-binding.d.ts +145 -0
  76. package/dist/memory-erasure-binding.d.ts.map +1 -0
  77. package/dist/memory-erasure-binding.js +4 -0
  78. package/dist/memory-erasure-binding.js.map +1 -0
  79. package/dist/middleware/auth.d.ts +66 -13
  80. package/dist/middleware/auth.d.ts.map +1 -1
  81. package/dist/middleware/auth.js +150 -50
  82. package/dist/middleware/auth.js.map +1 -1
  83. package/dist/middleware/authorize.d.ts +6 -0
  84. package/dist/middleware/authorize.d.ts.map +1 -1
  85. package/dist/middleware/authorize.js +113 -4
  86. package/dist/middleware/authorize.js.map +1 -1
  87. package/dist/middleware/error-mapper.d.ts +5 -0
  88. package/dist/middleware/error-mapper.d.ts.map +1 -1
  89. package/dist/middleware/error-mapper.js +14 -1
  90. package/dist/middleware/error-mapper.js.map +1 -1
  91. package/dist/middleware/idempotency.d.ts +70 -5
  92. package/dist/middleware/idempotency.d.ts.map +1 -1
  93. package/dist/middleware/idempotency.js +174 -41
  94. package/dist/middleware/idempotency.js.map +1 -1
  95. package/dist/middleware/key-project.d.ts +17 -0
  96. package/dist/middleware/key-project.d.ts.map +1 -0
  97. package/dist/middleware/key-project.js +75 -0
  98. package/dist/middleware/key-project.js.map +1 -0
  99. package/dist/middleware/principal.d.ts.map +1 -1
  100. package/dist/middleware/principal.js +9 -5
  101. package/dist/middleware/principal.js.map +1 -1
  102. package/dist/middleware/project-ref.d.ts.map +1 -1
  103. package/dist/middleware/project-ref.js +3 -0
  104. package/dist/middleware/project-ref.js.map +1 -1
  105. package/dist/openapi/generate.d.ts.map +1 -1
  106. package/dist/openapi/generate.js +5 -2
  107. package/dist/openapi/generate.js.map +1 -1
  108. package/dist/openapi/operations.d.ts.map +1 -1
  109. package/dist/openapi/operations.js +918 -139
  110. package/dist/openapi/operations.js.map +1 -1
  111. package/dist/openapi/schemas.d.ts +92 -13
  112. package/dist/openapi/schemas.d.ts.map +1 -1
  113. package/dist/openapi/schemas.js +2122 -370
  114. package/dist/openapi/schemas.js.map +1 -1
  115. package/dist/person-grants-binding.d.ts +77 -0
  116. package/dist/person-grants-binding.d.ts.map +1 -0
  117. package/dist/person-grants-binding.js +4 -0
  118. package/dist/person-grants-binding.js.map +1 -0
  119. package/dist/proposal-service.d.ts +121 -0
  120. package/dist/proposal-service.d.ts.map +1 -0
  121. package/dist/proposal-service.js +447 -0
  122. package/dist/proposal-service.js.map +1 -0
  123. package/dist/proposal-status.d.ts +60 -0
  124. package/dist/proposal-status.d.ts.map +1 -0
  125. package/dist/proposal-status.js +109 -0
  126. package/dist/proposal-status.js.map +1 -0
  127. package/dist/provider-binding.d.ts +10 -0
  128. package/dist/provider-binding.d.ts.map +1 -1
  129. package/dist/publish-refused.d.ts +18 -3
  130. package/dist/publish-refused.d.ts.map +1 -1
  131. package/dist/publish-refused.js +18 -3
  132. package/dist/publish-refused.js.map +1 -1
  133. package/dist/routes/adapters.d.ts +2 -1
  134. package/dist/routes/adapters.d.ts.map +1 -1
  135. package/dist/routes/adapters.js +3 -1
  136. package/dist/routes/adapters.js.map +1 -1
  137. package/dist/routes/agent-releases.d.ts +55 -3
  138. package/dist/routes/agent-releases.d.ts.map +1 -1
  139. package/dist/routes/agent-releases.js +85 -55
  140. package/dist/routes/agent-releases.js.map +1 -1
  141. package/dist/routes/agents.d.ts +12 -1
  142. package/dist/routes/agents.d.ts.map +1 -1
  143. package/dist/routes/agents.js +100 -2
  144. package/dist/routes/agents.js.map +1 -1
  145. package/dist/routes/approvals.d.ts +15 -3
  146. package/dist/routes/approvals.d.ts.map +1 -1
  147. package/dist/routes/approvals.js +133 -122
  148. package/dist/routes/approvals.js.map +1 -1
  149. package/dist/routes/artifacts.d.ts +25 -3
  150. package/dist/routes/artifacts.d.ts.map +1 -1
  151. package/dist/routes/artifacts.js +116 -7
  152. package/dist/routes/artifacts.js.map +1 -1
  153. package/dist/routes/auth.d.ts +25 -1
  154. package/dist/routes/auth.d.ts.map +1 -1
  155. package/dist/routes/auth.js +655 -281
  156. package/dist/routes/auth.js.map +1 -1
  157. package/dist/routes/blocks.d.ts.map +1 -1
  158. package/dist/routes/blocks.js +2 -42
  159. package/dist/routes/blocks.js.map +1 -1
  160. package/dist/routes/capabilities.d.ts +2 -1
  161. package/dist/routes/capabilities.d.ts.map +1 -1
  162. package/dist/routes/capabilities.js +6 -1
  163. package/dist/routes/capabilities.js.map +1 -1
  164. package/dist/routes/compliance.d.ts +9 -10
  165. package/dist/routes/compliance.d.ts.map +1 -1
  166. package/dist/routes/compliance.js +42 -61
  167. package/dist/routes/compliance.js.map +1 -1
  168. package/dist/routes/conversations.d.ts +8 -1
  169. package/dist/routes/conversations.d.ts.map +1 -1
  170. package/dist/routes/conversations.js +87 -8
  171. package/dist/routes/conversations.js.map +1 -1
  172. package/dist/routes/cost.d.ts +7 -1
  173. package/dist/routes/cost.d.ts.map +1 -1
  174. package/dist/routes/cost.js +22 -3
  175. package/dist/routes/cost.js.map +1 -1
  176. package/dist/routes/denied.d.ts +11 -0
  177. package/dist/routes/denied.d.ts.map +1 -0
  178. package/dist/routes/denied.js +17 -0
  179. package/dist/routes/denied.js.map +1 -0
  180. package/dist/routes/deployments.d.ts +2 -1
  181. package/dist/routes/deployments.d.ts.map +1 -1
  182. package/dist/routes/deployments.js +44 -12
  183. package/dist/routes/deployments.js.map +1 -1
  184. package/dist/routes/eval-comparison.d.ts.map +1 -1
  185. package/dist/routes/eval-comparison.js +69 -1
  186. package/dist/routes/eval-comparison.js.map +1 -1
  187. package/dist/routes/eval-overrides.d.ts +18 -0
  188. package/dist/routes/eval-overrides.d.ts.map +1 -0
  189. package/dist/routes/eval-overrides.js +81 -0
  190. package/dist/routes/eval-overrides.js.map +1 -0
  191. package/dist/routes/eval-runs.d.ts +10 -1
  192. package/dist/routes/eval-runs.d.ts.map +1 -1
  193. package/dist/routes/eval-runs.js +63 -7
  194. package/dist/routes/eval-runs.js.map +1 -1
  195. package/dist/routes/eval-suites.d.ts.map +1 -1
  196. package/dist/routes/eval-suites.js +20 -1
  197. package/dist/routes/eval-suites.js.map +1 -1
  198. package/dist/routes/event-triggers.d.ts +5 -1
  199. package/dist/routes/event-triggers.d.ts.map +1 -1
  200. package/dist/routes/event-triggers.js +26 -3
  201. package/dist/routes/event-triggers.js.map +1 -1
  202. package/dist/routes/export-signing-keys.d.ts +12 -0
  203. package/dist/routes/export-signing-keys.d.ts.map +1 -0
  204. package/dist/routes/export-signing-keys.js +24 -0
  205. package/dist/routes/export-signing-keys.js.map +1 -0
  206. package/dist/routes/flows.d.ts.map +1 -1
  207. package/dist/routes/flows.js +9 -1
  208. package/dist/routes/flows.js.map +1 -1
  209. package/dist/routes/gate-policies.d.ts +4 -1
  210. package/dist/routes/gate-policies.d.ts.map +1 -1
  211. package/dist/routes/gate-policies.js +29 -5
  212. package/dist/routes/gate-policies.js.map +1 -1
  213. package/dist/routes/guardrails.d.ts +16 -14
  214. package/dist/routes/guardrails.d.ts.map +1 -1
  215. package/dist/routes/guardrails.js +39 -3
  216. package/dist/routes/guardrails.js.map +1 -1
  217. package/dist/routes/identity.d.ts +20 -4
  218. package/dist/routes/identity.d.ts.map +1 -1
  219. package/dist/routes/identity.js +246 -6
  220. package/dist/routes/identity.js.map +1 -1
  221. package/dist/routes/improvement-passes.d.ts +52 -0
  222. package/dist/routes/improvement-passes.d.ts.map +1 -0
  223. package/dist/routes/improvement-passes.js +449 -0
  224. package/dist/routes/improvement-passes.js.map +1 -0
  225. package/dist/routes/judged-suites.d.ts +50 -0
  226. package/dist/routes/judged-suites.d.ts.map +1 -1
  227. package/dist/routes/judged-suites.js +66 -27
  228. package/dist/routes/judged-suites.js.map +1 -1
  229. package/dist/routes/judgment-context.d.ts +18 -2
  230. package/dist/routes/judgment-context.d.ts.map +1 -1
  231. package/dist/routes/judgment-context.js +63 -5
  232. package/dist/routes/judgment-context.js.map +1 -1
  233. package/dist/routes/judgment-flow-context.d.ts.map +1 -1
  234. package/dist/routes/judgment-flow-context.js +14 -2
  235. package/dist/routes/judgment-flow-context.js.map +1 -1
  236. package/dist/routes/judgments.d.ts.map +1 -1
  237. package/dist/routes/judgments.js +8 -1
  238. package/dist/routes/judgments.js.map +1 -1
  239. package/dist/routes/mcp.d.ts.map +1 -1
  240. package/dist/routes/mcp.js +26 -1
  241. package/dist/routes/mcp.js.map +1 -1
  242. package/dist/routes/memory-access.d.ts +43 -0
  243. package/dist/routes/memory-access.d.ts.map +1 -0
  244. package/dist/routes/memory-access.js +129 -0
  245. package/dist/routes/memory-access.js.map +1 -0
  246. package/dist/routes/memory-erasures.d.ts +25 -0
  247. package/dist/routes/memory-erasures.d.ts.map +1 -0
  248. package/dist/routes/memory-erasures.js +214 -0
  249. package/dist/routes/memory-erasures.js.map +1 -0
  250. package/dist/routes/memory-parse.d.ts +38 -0
  251. package/dist/routes/memory-parse.d.ts.map +1 -0
  252. package/dist/routes/memory-parse.js +199 -0
  253. package/dist/routes/memory-parse.js.map +1 -0
  254. package/dist/routes/memory.d.ts +9 -4
  255. package/dist/routes/memory.d.ts.map +1 -1
  256. package/dist/routes/memory.js +226 -234
  257. package/dist/routes/memory.js.map +1 -1
  258. package/dist/routes/observations.d.ts +4 -1
  259. package/dist/routes/observations.d.ts.map +1 -1
  260. package/dist/routes/observations.js +8 -2
  261. package/dist/routes/observations.js.map +1 -1
  262. package/dist/routes/pagination.d.ts +7 -0
  263. package/dist/routes/pagination.d.ts.map +1 -1
  264. package/dist/routes/pagination.js +10 -0
  265. package/dist/routes/pagination.js.map +1 -1
  266. package/dist/routes/policies.d.ts +2 -1
  267. package/dist/routes/policies.d.ts.map +1 -1
  268. package/dist/routes/policies.js +3 -1
  269. package/dist/routes/policies.js.map +1 -1
  270. package/dist/routes/project-mismatch.d.ts +22 -0
  271. package/dist/routes/project-mismatch.d.ts.map +1 -0
  272. package/dist/routes/project-mismatch.js +34 -0
  273. package/dist/routes/project-mismatch.js.map +1 -0
  274. package/dist/routes/projects.d.ts +15 -4
  275. package/dist/routes/projects.d.ts.map +1 -1
  276. package/dist/routes/projects.js +100 -9
  277. package/dist/routes/projects.js.map +1 -1
  278. package/dist/routes/proposals.d.ts +25 -17
  279. package/dist/routes/proposals.d.ts.map +1 -1
  280. package/dist/routes/proposals.js +340 -507
  281. package/dist/routes/proposals.js.map +1 -1
  282. package/dist/routes/provenance.d.ts +17 -3
  283. package/dist/routes/provenance.d.ts.map +1 -1
  284. package/dist/routes/provenance.js +79 -103
  285. package/dist/routes/provenance.js.map +1 -1
  286. package/dist/routes/providers.d.ts +17 -1
  287. package/dist/routes/providers.d.ts.map +1 -1
  288. package/dist/routes/providers.js +188 -8
  289. package/dist/routes/providers.js.map +1 -1
  290. package/dist/routes/public-run-tokens.d.ts.map +1 -1
  291. package/dist/routes/public-run-tokens.js +3 -0
  292. package/dist/routes/public-run-tokens.js.map +1 -1
  293. package/dist/routes/runs.d.ts +10 -0
  294. package/dist/routes/runs.d.ts.map +1 -1
  295. package/dist/routes/runs.js +92 -42
  296. package/dist/routes/runs.js.map +1 -1
  297. package/dist/routes/schedules.d.ts +21 -13
  298. package/dist/routes/schedules.d.ts.map +1 -1
  299. package/dist/routes/schedules.js +416 -55
  300. package/dist/routes/schedules.js.map +1 -1
  301. package/dist/routes/segments.d.ts +2 -0
  302. package/dist/routes/segments.d.ts.map +1 -1
  303. package/dist/routes/segments.js +5 -0
  304. package/dist/routes/segments.js.map +1 -1
  305. package/dist/routes/service-accounts.d.ts +17 -0
  306. package/dist/routes/service-accounts.d.ts.map +1 -0
  307. package/dist/routes/service-accounts.js +212 -0
  308. package/dist/routes/service-accounts.js.map +1 -0
  309. package/dist/routes/sign-in-options.d.ts +50 -0
  310. package/dist/routes/sign-in-options.d.ts.map +1 -0
  311. package/dist/routes/sign-in-options.js +98 -0
  312. package/dist/routes/sign-in-options.js.map +1 -0
  313. package/dist/routes/signing-keys.d.ts +2 -1
  314. package/dist/routes/signing-keys.d.ts.map +1 -1
  315. package/dist/routes/signing-keys.js +3 -1
  316. package/dist/routes/signing-keys.js.map +1 -1
  317. package/dist/routes/tenant-access.d.ts +13 -0
  318. package/dist/routes/tenant-access.d.ts.map +1 -0
  319. package/dist/routes/tenant-access.js +26 -0
  320. package/dist/routes/tenant-access.js.map +1 -0
  321. package/dist/routes/tenant.d.ts.map +1 -1
  322. package/dist/routes/tenant.js +4 -0
  323. package/dist/routes/tenant.js.map +1 -1
  324. package/dist/routes/token-sign-in.d.ts +36 -0
  325. package/dist/routes/token-sign-in.d.ts.map +1 -0
  326. package/dist/routes/token-sign-in.js +101 -0
  327. package/dist/routes/token-sign-in.js.map +1 -0
  328. package/dist/routes/tokens.d.ts +15 -5
  329. package/dist/routes/tokens.d.ts.map +1 -1
  330. package/dist/routes/tokens.js +162 -43
  331. package/dist/routes/tokens.js.map +1 -1
  332. package/dist/routes/tools.d.ts.map +1 -1
  333. package/dist/routes/tools.js +9 -1
  334. package/dist/routes/tools.js.map +1 -1
  335. package/dist/routes/trigger-access.d.ts +29 -0
  336. package/dist/routes/trigger-access.d.ts.map +1 -0
  337. package/dist/routes/trigger-access.js +33 -0
  338. package/dist/routes/trigger-access.js.map +1 -0
  339. package/dist/routes/webhook-endpoints.d.ts +2 -1
  340. package/dist/routes/webhook-endpoints.d.ts.map +1 -1
  341. package/dist/routes/webhook-endpoints.js +11 -3
  342. package/dist/routes/webhook-endpoints.js.map +1 -1
  343. package/dist/routes/webhooks.d.ts +5 -1
  344. package/dist/routes/webhooks.d.ts.map +1 -1
  345. package/dist/routes/webhooks.js +25 -2
  346. package/dist/routes/webhooks.js.map +1 -1
  347. package/dist/run-failure.d.ts +21 -0
  348. package/dist/run-failure.d.ts.map +1 -0
  349. package/dist/run-failure.js +29 -0
  350. package/dist/run-failure.js.map +1 -0
  351. package/dist/service-account-binding.d.ts +108 -0
  352. package/dist/service-account-binding.d.ts.map +1 -0
  353. package/dist/service-account-binding.js +4 -0
  354. package/dist/service-account-binding.js.map +1 -0
  355. package/dist/session-store-binding.d.ts +61 -13
  356. package/dist/session-store-binding.d.ts.map +1 -1
  357. package/dist/signed-export.d.ts +89 -0
  358. package/dist/signed-export.d.ts.map +1 -0
  359. package/dist/signed-export.js +149 -0
  360. package/dist/signed-export.js.map +1 -0
  361. package/dist/supervisor-binding.d.ts +186 -413
  362. package/dist/supervisor-binding.d.ts.map +1 -1
  363. package/dist/testing/app-bindings.d.ts +33 -0
  364. package/dist/testing/app-bindings.d.ts.map +1 -0
  365. package/dist/testing/app-bindings.js +112 -0
  366. package/dist/testing/app-bindings.js.map +1 -0
  367. package/dist/testing/index.d.ts +11 -0
  368. package/dist/testing/index.d.ts.map +1 -0
  369. package/dist/testing/index.js +13 -0
  370. package/dist/testing/index.js.map +1 -0
  371. package/dist/testing/stub-binding.d.ts +34 -0
  372. package/dist/testing/stub-binding.d.ts.map +1 -0
  373. package/dist/testing/stub-binding.js +42 -0
  374. package/dist/testing/stub-binding.js.map +1 -0
  375. package/dist/testing/trigger-registry.d.ts +21 -0
  376. package/dist/testing/trigger-registry.d.ts.map +1 -0
  377. package/dist/testing/trigger-registry.js +206 -0
  378. package/dist/testing/trigger-registry.js.map +1 -0
  379. package/dist/token-admin.d.ts +46 -8
  380. package/dist/token-admin.d.ts.map +1 -1
  381. package/dist/token-admin.js.map +1 -1
  382. package/dist/tool-binding.d.ts +13 -0
  383. package/dist/tool-binding.d.ts.map +1 -1
  384. package/dist/trigger-binding.d.ts +2 -2
  385. package/dist/trigger-binding.d.ts.map +1 -1
  386. package/dist/trigger-binding.js +1 -1
  387. package/dist/trigger-binding.js.map +1 -1
  388. package/dist/types.d.ts +16 -0
  389. package/dist/types.d.ts.map +1 -1
  390. package/dist/webhook-endpoint-binding.d.ts +19 -3
  391. package/dist/webhook-endpoint-binding.d.ts.map +1 -1
  392. package/dist/webhook-endpoint-binding.js +1 -1
  393. package/dist/webhook-endpoint-binding.js.map +1 -1
  394. package/openapi.json +15286 -8653
  395. package/package.json +26 -22
  396. package/src/agent-binding.ts +14 -0
  397. package/src/agent-pins.ts +18 -2
  398. package/src/app.ts +366 -80
  399. package/src/block-publish.ts +181 -0
  400. package/src/caller.ts +62 -0
  401. package/src/capability-binding.ts +12 -0
  402. package/src/derive-agent-version.ts +9 -1
  403. package/src/errors.ts +73 -7
  404. package/src/eval-case-binding.ts +17 -1
  405. package/src/eval-run-binding.ts +31 -0
  406. package/src/eval-run-dispatcher.ts +7 -0
  407. package/src/eval-sample.ts +42 -0
  408. package/src/eval-suite-binding.ts +14 -0
  409. package/src/flow-binding.ts +19 -0
  410. package/src/flow-pins.ts +18 -2
  411. package/src/gate.ts +26 -0
  412. package/src/handler-binding.ts +36 -1
  413. package/src/hitl-binding.ts +24 -0
  414. package/src/identity-directory-binding.ts +85 -4
  415. package/src/identity-provider-binding.ts +216 -21
  416. package/src/improvement-pass-binding.ts +193 -0
  417. package/src/index.ts +153 -31
  418. package/src/judged-dispatcher.ts +78 -11
  419. package/src/judgment-binding.ts +25 -1
  420. package/src/mcp-endpoint-binding.ts +7 -0
  421. package/src/memory-binding.ts +121 -17
  422. package/src/memory-erasure-binding.ts +156 -0
  423. package/src/middleware/auth.ts +228 -62
  424. package/src/middleware/authorize.ts +139 -3
  425. package/src/middleware/error-mapper.ts +15 -1
  426. package/src/middleware/idempotency.ts +230 -43
  427. package/src/middleware/key-project.ts +86 -0
  428. package/src/middleware/principal.ts +9 -5
  429. package/src/middleware/project-ref.ts +3 -0
  430. package/src/openapi/generate.ts +11 -2
  431. package/src/openapi/operations.ts +1059 -142
  432. package/src/openapi/schemas.ts +2438 -485
  433. package/src/person-grants-binding.ts +76 -0
  434. package/src/proposal-service.ts +640 -0
  435. package/src/proposal-status.ts +176 -0
  436. package/src/provider-binding.ts +10 -0
  437. package/src/publish-refused.ts +38 -9
  438. package/src/routes/adapters.ts +4 -0
  439. package/src/routes/agent-releases.ts +121 -69
  440. package/src/routes/agents.ts +128 -1
  441. package/src/routes/approvals.ts +164 -154
  442. package/src/routes/artifacts.ts +181 -11
  443. package/src/routes/auth.ts +804 -336
  444. package/src/routes/blocks.ts +2 -53
  445. package/src/routes/capabilities.ts +10 -1
  446. package/src/routes/compliance.ts +60 -99
  447. package/src/routes/conversations.ts +101 -7
  448. package/src/routes/cost.ts +32 -3
  449. package/src/routes/denied.ts +28 -0
  450. package/src/routes/deployments.ts +62 -16
  451. package/src/routes/eval-comparison.ts +81 -2
  452. package/src/routes/eval-overrides.ts +108 -0
  453. package/src/routes/eval-runs.ts +86 -6
  454. package/src/routes/eval-suites.ts +25 -1
  455. package/src/routes/event-triggers.ts +27 -3
  456. package/src/routes/export-signing-keys.ts +31 -0
  457. package/src/routes/flows.ts +12 -1
  458. package/src/routes/gate-policies.ts +37 -6
  459. package/src/routes/guardrails.ts +61 -2
  460. package/src/routes/identity.ts +328 -10
  461. package/src/routes/improvement-passes.ts +541 -0
  462. package/src/routes/judged-suites.ts +113 -43
  463. package/src/routes/judgment-context.ts +76 -5
  464. package/src/routes/judgment-flow-context.ts +16 -2
  465. package/src/routes/judgments.ts +17 -3
  466. package/src/routes/mcp.ts +29 -1
  467. package/src/routes/memory-access.ts +179 -0
  468. package/src/routes/memory-erasures.ts +240 -0
  469. package/src/routes/memory-parse.ts +250 -0
  470. package/src/routes/memory.ts +244 -286
  471. package/src/routes/observations.ts +15 -2
  472. package/src/routes/pagination.ts +12 -0
  473. package/src/routes/policies.ts +7 -1
  474. package/src/routes/project-mismatch.ts +56 -0
  475. package/src/routes/projects.ts +107 -13
  476. package/src/routes/proposals.ts +370 -611
  477. package/src/routes/provenance.ts +99 -133
  478. package/src/routes/providers.ts +223 -8
  479. package/src/routes/public-run-tokens.ts +3 -0
  480. package/src/routes/runs.ts +119 -39
  481. package/src/routes/schedules.ts +472 -60
  482. package/src/routes/segments.ts +11 -0
  483. package/src/routes/service-accounts.ts +262 -0
  484. package/src/routes/sign-in-options.ts +168 -0
  485. package/src/routes/signing-keys.ts +7 -1
  486. package/src/routes/tenant-access.ts +37 -0
  487. package/src/routes/tenant.ts +6 -0
  488. package/src/routes/token-sign-in.ts +161 -0
  489. package/src/routes/tokens.ts +172 -46
  490. package/src/routes/tools.ts +12 -1
  491. package/src/routes/trigger-access.ts +72 -0
  492. package/src/routes/webhook-endpoints.ts +15 -3
  493. package/src/routes/webhooks.ts +26 -2
  494. package/src/run-failure.ts +42 -0
  495. package/src/service-account-binding.ts +113 -0
  496. package/src/session-store-binding.ts +65 -13
  497. package/src/signed-export.ts +250 -0
  498. package/src/supervisor-binding.ts +169 -409
  499. package/src/testing/app-bindings.ts +142 -0
  500. package/src/testing/index.ts +21 -0
  501. package/src/testing/stub-binding.ts +53 -0
  502. package/src/testing/trigger-registry.ts +259 -0
  503. package/src/token-admin.ts +40 -8
  504. package/src/tool-binding.ts +14 -0
  505. package/src/trigger-binding.ts +8 -1
  506. package/src/types.ts +16 -0
  507. package/src/webhook-endpoint-binding.ts +18 -3
@@ -86,7 +86,7 @@ const IdempotencyKeyParam: ParameterSpec = {
86
86
  in: 'header',
87
87
  required: false,
88
88
  description:
89
- 'Caller-supplied idempotency key. Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1).',
89
+ "Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request's answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn't kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request's status and when it succeeded (from 0.1.5).",
90
90
  schema: { type: 'string', minLength: 1 },
91
91
  };
92
92
 
@@ -106,6 +106,14 @@ const TokenIdPathParam: ParameterSpec = {
106
106
  schema: { type: 'string', format: 'uuid' },
107
107
  };
108
108
 
109
+ const ServiceAccountIdPathParam: ParameterSpec = {
110
+ name: 'serviceAccountId',
111
+ in: 'path',
112
+ required: true,
113
+ description: 'The service account id.',
114
+ schema: { type: 'string' },
115
+ };
116
+
109
117
  const SigningKeyIdPathParam: ParameterSpec = {
110
118
  name: 'keyId',
111
119
  in: 'path',
@@ -182,6 +190,14 @@ const RunEvalRunIdQueryParam: ParameterSpec = {
182
190
  schema: { type: 'string', minLength: 1 },
183
191
  };
184
192
 
193
+ const RunTriggerIdQueryParam: ParameterSpec = {
194
+ name: 'triggerId',
195
+ in: 'query',
196
+ required: false,
197
+ description: 'Only the runs this trigger started (`Run.trigger.triggerId`).',
198
+ schema: { type: 'string', format: 'uuid' },
199
+ };
200
+
185
201
  const LiveProjectQueryParam: ParameterSpec = {
186
202
  name: 'projectId',
187
203
  in: 'query',
@@ -472,11 +488,19 @@ const ObservationUntilQueryParam: ParameterSpec = {
472
488
  schema: { type: 'string', format: 'date-time' },
473
489
  };
474
490
 
491
+ const ErasureIdPathParam: ParameterSpec = {
492
+ name: 'erasureId',
493
+ in: 'path',
494
+ required: true,
495
+ description: 'The erasure (a UUID).',
496
+ schema: { type: 'string', format: 'uuid' },
497
+ };
498
+
475
499
  const FactIdPathParam: ParameterSpec = {
476
500
  name: 'factId',
477
501
  in: 'path',
478
502
  required: true,
479
- description: 'FactId — opaque branded string.',
503
+ description: 'The fact id (kept across revisions).',
480
504
  schema: { type: 'string' },
481
505
  };
482
506
 
@@ -497,6 +521,32 @@ const FactScopeQueryParam: ParameterSpec = {
497
521
  schema: { type: 'string' },
498
522
  };
499
523
 
524
+ const FactAsOfQueryParam: ParameterSpec = {
525
+ name: 'asOf',
526
+ in: 'query',
527
+ required: false,
528
+ description:
529
+ 'Read memory as it stood at this time (ISO 8601): the revision that was current then, including one since superseded or deleted.',
530
+ schema: { type: 'string', format: 'date-time' },
531
+ };
532
+
533
+ const FactVersionQueryParam: ParameterSpec = {
534
+ name: 'version',
535
+ in: 'query',
536
+ required: false,
537
+ description: 'A revision number: that revision, current or not.',
538
+ schema: { type: 'integer', minimum: 1 },
539
+ };
540
+
541
+ const FactExpectVersionQueryParam: ParameterSpec = {
542
+ name: 'expectVersion',
543
+ in: 'query',
544
+ required: false,
545
+ description:
546
+ 'Only if the current revision is still this one; otherwise `409 fact-changed` with `currentVersion`.',
547
+ schema: { type: 'integer', minimum: 1 },
548
+ };
549
+
500
550
  // ---------------- scope triplet ----------------
501
551
  //
502
552
  // Shared parameters for the `?scopeKind + ?scopeId + ?inherit` filter.
@@ -531,20 +581,19 @@ const InheritQueryParam: ParameterSpec = {
531
581
  schema: { type: 'boolean', default: true },
532
582
  };
533
583
 
534
- const SupervisorIdHeaderParam: ParameterSpec = {
535
- name: 'X-Supervisor-Id',
536
- in: 'header',
584
+ const ImprovementPassIdPathParam: ParameterSpec = {
585
+ name: 'passId',
586
+ in: 'path',
537
587
  required: true,
538
- description:
539
- 'SupervisorId scoping this request. Every /v1/proposals route requires this header — proposals are supervisor-owned, and the API does not derive supervisor scope from the token.',
540
- schema: { type: 'string', minLength: 1 },
588
+ description: 'The improvement pass id (a UUID).',
589
+ schema: { type: 'string', format: 'uuid' },
541
590
  };
542
591
 
543
592
  const ProposalIdPathParam: ParameterSpec = {
544
593
  name: 'proposalId',
545
594
  in: 'path',
546
595
  required: true,
547
- description: 'FixProposalId — opaque branded string (a UUID).',
596
+ description: 'The proposal id (a UUID).',
548
597
  schema: { type: 'string', format: 'uuid' },
549
598
  };
550
599
 
@@ -577,7 +626,7 @@ const ProposalStatusQueryParam: ParameterSpec = {
577
626
  name: 'status',
578
627
  in: 'query',
579
628
  required: false,
580
- description: 'Filter by proposal status.',
629
+ description: 'Only proposals with this (derived) status.',
581
630
  schema: { $ref: '#/components/schemas/FixProposalStatus' },
582
631
  };
583
632
 
@@ -585,7 +634,7 @@ const ProposalTierQueryParam: ParameterSpec = {
585
634
  name: 'tier',
586
635
  in: 'query',
587
636
  required: false,
588
- description: 'Filter by artifact tier.',
637
+ description: 'Only proposals of this tier.',
589
638
  schema: { $ref: '#/components/schemas/ProposalTier' },
590
639
  };
591
640
 
@@ -1250,6 +1299,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1250
1299
  'Agent or flow not found; or `projectId` names no project of this tenant (`project-not-found`).',
1251
1300
  ),
1252
1301
  '422': ErrorResponse('Guardrail violation or budget exceeded.'),
1302
+ '409': ErrorResponse(
1303
+ "Idempotency-Key was reused with a different body; or an erasure of the turn's person (its conversation or its `participantId`) is in progress (`erasure-in-progress`): no new turn starts for them until it completes.",
1304
+ ),
1253
1305
  '400': ErrorResponse(
1254
1306
  "Malformed request body, or the body's `projectId` isn't a project id (a UUID).",
1255
1307
  ),
@@ -1274,6 +1326,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1274
1326
  RunAgentIdQueryParam,
1275
1327
  RunReplaysQueryParam,
1276
1328
  RunEvalRunIdQueryParam,
1329
+ RunTriggerIdQueryParam,
1277
1330
  RunIncludeQueryParam,
1278
1331
  ],
1279
1332
  responses: {
@@ -1513,7 +1566,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1513
1566
  operationId: 'tokens.mint',
1514
1567
  summary: 'Mint an API key',
1515
1568
  description:
1516
- 'An API key is a service account in the tenant, with a `role` and explicit `capabilities`. Returns the plaintext token exactly once. Tenant admins only; a caller can only grant capabilities it holds. Only mounted when the deployment supplies a `TokenAdmin`.',
1569
+ "An API key acts for one principal (`for`: a person or a service account; default the caller), with that principal's grants. Its `role` is a ceiling under them and its `projectId` a limit. Returns the plaintext token exactly once. A person or a service account's key mints its own keys; only a tenant admin mints for someone else, or an `admin` key. A caller can only grant capabilities it holds, and a key limited to a project mints only keys limited to it. Only mounted when the deployment supplies a `TokenAdmin`.",
1517
1570
  tags: ['tokens'],
1518
1571
  security: 'bearer',
1519
1572
  parameters: [IdempotencyKeyParam],
@@ -1521,12 +1574,17 @@ export const OPERATIONS: readonly OperationSpec[] = [
1521
1574
  responses: {
1522
1575
  '201': { description: 'Token minted.', schema: ref('MintTokenResult') },
1523
1576
  ...CommonMutationErrors,
1524
- '403': ErrorResponse('Not a tenant admin, or a capability the caller does not hold.'),
1577
+ '409': ErrorResponse(
1578
+ "Idempotency-Key reused with a different body (`idempotency-key-body-mismatch`), or a retry of a request that succeeded: its answer carried a secret, which isn't kept (`idempotency-key-replay-withheld`, with its `status` and `at`).",
1579
+ ),
1580
+ '403': ErrorResponse(
1581
+ "Not a tenant admin where one is needed, or a capability the caller does not hold (`permission-denied`); an `admin` key for a principal that isn't a tenant admin (`role-exceeds-principal`); a key limited to a project minting for another (`key-project-mismatch`).",
1582
+ ),
1525
1583
  '400': ErrorResponse(
1526
1584
  "Malformed request body, or the body's `projectId` isn't a project id (a UUID).",
1527
1585
  ),
1528
1586
  '404': ErrorResponse(
1529
- "The body's `projectId` names no project of this tenant (`project-not-found`).",
1587
+ "The body's `projectId` names no project of this tenant (`project-not-found`), or `for` names no person or service account (`principal-not-found`).",
1530
1588
  ),
1531
1589
  },
1532
1590
  },
@@ -1536,15 +1594,27 @@ export const OPERATIONS: readonly OperationSpec[] = [
1536
1594
  openapiPath: '/v1/tokens',
1537
1595
  operationId: 'tokens.list',
1538
1596
  summary: 'List API keys',
1539
- description: 'Newest first. Never returns secrets. Tenant admins only.',
1597
+ description:
1598
+ "Newest first. Never returns secrets. A tenant admin sees every key (`?principal=` for one principal's); anyone else sees their own.",
1540
1599
  tags: ['tokens'],
1541
1600
  security: 'bearer',
1542
- parameters: [CursorQueryParam, LimitQueryParam],
1601
+ parameters: [
1602
+ CursorQueryParam,
1603
+ LimitQueryParam,
1604
+ {
1605
+ name: 'principal',
1606
+ in: 'query',
1607
+ required: false,
1608
+ description:
1609
+ "Tenant admins: only this principal's keys, `user:<id>` or `service-account:<id>`.",
1610
+ schema: { type: 'string' },
1611
+ },
1612
+ ],
1543
1613
  responses: {
1544
1614
  '200': { description: 'A page of keys.', schema: ref('ApiTokenPage') },
1545
1615
  ...CommonAuthErrors,
1546
- '400': ErrorResponse('Malformed cursor.'),
1547
- '403': ErrorResponse('Not a tenant admin.'),
1616
+ '400': ErrorResponse('Malformed cursor or `principal`.'),
1617
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1548
1618
  },
1549
1619
  },
1550
1620
  {
@@ -1553,15 +1623,16 @@ export const OPERATIONS: readonly OperationSpec[] = [
1553
1623
  openapiPath: '/v1/tokens/{tokenId}',
1554
1624
  operationId: 'tokens.get',
1555
1625
  summary: 'Read an API key',
1556
- description: 'Never returns the secret. Tenant admins only.',
1626
+ description:
1627
+ "Never returns the secret. A tenant admin reads any key; anyone else only their own (someone else's reads as missing).",
1557
1628
  tags: ['tokens'],
1558
1629
  security: 'bearer',
1559
1630
  parameters: [TokenIdPathParam],
1560
1631
  responses: {
1561
1632
  '200': { description: 'The key.', schema: ref('ApiToken') },
1562
1633
  ...CommonAuthErrors,
1563
- '403': ErrorResponse('Not a tenant admin.'),
1564
- '404': ErrorResponse('No token with that id under this tenant.'),
1634
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1635
+ '404': ErrorResponse('No token with that id that the caller may see.'),
1565
1636
  },
1566
1637
  },
1567
1638
  {
@@ -1570,15 +1641,143 @@ export const OPERATIONS: readonly OperationSpec[] = [
1570
1641
  openapiPath: '/v1/tokens/{tokenId}/revoke',
1571
1642
  operationId: 'tokens.revoke',
1572
1643
  summary: 'Revoke an API key',
1573
- description: 'Takes effect on the next request. Tenant admins only.',
1644
+ description:
1645
+ 'Takes effect on the next request. A tenant admin revokes any key; anyone else only their own.',
1574
1646
  tags: ['tokens'],
1575
1647
  security: 'bearer',
1576
1648
  parameters: [TokenIdPathParam, IdempotencyKeyParam],
1577
1649
  responses: {
1578
1650
  '200': { description: 'Revoked.', schema: ref('RevokeTokenResult') },
1579
1651
  ...CommonMutationErrors,
1652
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1653
+ '404': ErrorResponse('No token with that id that the caller may see.'),
1654
+ },
1655
+ },
1656
+ // ---------- service accounts ----------
1657
+ {
1658
+ method: 'post',
1659
+ honoPath: '/v1/service-accounts',
1660
+ openapiPath: '/v1/service-accounts',
1661
+ operationId: 'serviceAccounts.create',
1662
+ summary: 'Create a service account',
1663
+ description:
1664
+ "A named, non-human principal with its first grants, written before it is returned. It isn't a tenant member unless a grant makes it one (`{kind: 'tenant-member'}`: read the tenant's settings); give it only what its job needs. Mint its keys at `POST /v1/tokens` with `for`. Tenant admins only. Mounted when the deployment supplies a `ServiceAccountBinding`.",
1665
+ tags: ['service-accounts'],
1666
+ security: 'bearer',
1667
+ parameters: [IdempotencyKeyParam],
1668
+ requestBody: { required: true, schema: ref('CreateServiceAccountBody') },
1669
+ responses: {
1670
+ '201': { description: 'Created.', schema: ref('ServiceAccount') },
1671
+ ...CommonMutationErrors,
1580
1672
  '403': ErrorResponse('Not a tenant admin.'),
1581
- '404': ErrorResponse('No token with that id under this tenant.'),
1673
+ '404': ErrorResponse('A grant names no project of this tenant (`project-not-found`).'),
1674
+ '409': ErrorResponse(
1675
+ 'An active service account has the name (`service-account-name-taken`), or an idempotency conflict.',
1676
+ ),
1677
+ },
1678
+ },
1679
+ {
1680
+ method: 'get',
1681
+ honoPath: '/v1/service-accounts',
1682
+ openapiPath: '/v1/service-accounts',
1683
+ operationId: 'serviceAccounts.list',
1684
+ summary: 'List service accounts',
1685
+ description:
1686
+ 'Oldest first; active only unless `?includeUnregistered=true`. Tenant admins only.',
1687
+ tags: ['service-accounts'],
1688
+ security: 'bearer',
1689
+ parameters: [
1690
+ CursorQueryParam,
1691
+ LimitQueryParam,
1692
+ {
1693
+ name: 'includeUnregistered',
1694
+ in: 'query',
1695
+ required: false,
1696
+ description: '`true`: unregistered accounts too.',
1697
+ schema: { type: 'string', enum: ['true', 'false'] },
1698
+ },
1699
+ ],
1700
+ responses: {
1701
+ '200': { description: 'A page of service accounts.', schema: ref('ServiceAccountPage') },
1702
+ ...CommonAuthErrors,
1703
+ '403': ErrorResponse('Not a tenant admin.'),
1704
+ },
1705
+ },
1706
+ {
1707
+ method: 'get',
1708
+ honoPath: '/v1/service-accounts/:serviceAccountId',
1709
+ openapiPath: '/v1/service-accounts/{serviceAccountId}',
1710
+ operationId: 'serviceAccounts.get',
1711
+ summary: 'Read a service account',
1712
+ description: 'Unregistered ones too. Tenant admins only.',
1713
+ tags: ['service-accounts'],
1714
+ security: 'bearer',
1715
+ parameters: [ServiceAccountIdPathParam],
1716
+ responses: {
1717
+ '200': { description: 'The service account.', schema: ref('ServiceAccount') },
1718
+ ...CommonAuthErrors,
1719
+ '403': ErrorResponse('Not a tenant admin.'),
1720
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1721
+ },
1722
+ },
1723
+ {
1724
+ method: 'post',
1725
+ honoPath: '/v1/service-accounts/:serviceAccountId/grant',
1726
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/grant',
1727
+ operationId: 'serviceAccounts.grant',
1728
+ summary: 'Grant a service account',
1729
+ description:
1730
+ "Tenant admin, or a role on a project (replacing the account's role there). Written before the call answers. Tenant admins only.",
1731
+ tags: ['service-accounts'],
1732
+ security: 'bearer',
1733
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1734
+ requestBody: { required: true, schema: ref('ServiceAccountGrantBody') },
1735
+ responses: {
1736
+ '200': { description: 'The account, with its grants.', schema: ref('ServiceAccount') },
1737
+ ...CommonMutationErrors,
1738
+ '403': ErrorResponse('Not a tenant admin.'),
1739
+ '404': ErrorResponse(
1740
+ 'No such service account (`service-account-not-found`), or no such project (`project-not-found`).',
1741
+ ),
1742
+ '409': ErrorResponse(
1743
+ 'The account is unregistered (`service-account-unregistered`), or an idempotency conflict.',
1744
+ ),
1745
+ },
1746
+ },
1747
+ {
1748
+ method: 'post',
1749
+ honoPath: '/v1/service-accounts/:serviceAccountId/ungrant',
1750
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/ungrant',
1751
+ operationId: 'serviceAccounts.ungrant',
1752
+ summary: 'Remove a grant from a service account',
1753
+ description: 'A no-op when the account does not hold it. Tenant admins only.',
1754
+ tags: ['service-accounts'],
1755
+ security: 'bearer',
1756
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1757
+ requestBody: { required: true, schema: ref('ServiceAccountUngrantBody') },
1758
+ responses: {
1759
+ '200': { description: 'The account, with its grants.', schema: ref('ServiceAccount') },
1760
+ ...CommonMutationErrors,
1761
+ '403': ErrorResponse('Not a tenant admin.'),
1762
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1763
+ },
1764
+ },
1765
+ {
1766
+ method: 'post',
1767
+ honoPath: '/v1/service-accounts/:serviceAccountId/unregister',
1768
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/unregister',
1769
+ operationId: 'serviceAccounts.unregister',
1770
+ summary: 'Unregister a service account',
1771
+ description:
1772
+ 'A tombstone: its grants go and its keys stop working; it stays readable. Idempotent. Tenant admins only.',
1773
+ tags: ['service-accounts'],
1774
+ security: 'bearer',
1775
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1776
+ responses: {
1777
+ '200': { description: 'The unregistered account.', schema: ref('ServiceAccount') },
1778
+ ...CommonMutationErrors,
1779
+ '403': ErrorResponse('Not a tenant admin.'),
1780
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1582
1781
  },
1583
1782
  },
1584
1783
  {
@@ -1596,6 +1795,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
1596
1795
  responses: {
1597
1796
  '201': { description: 'Token minted.', schema: ref('MintPublicRunTokenResult') },
1598
1797
  ...CommonMutationErrors,
1798
+ '409': ErrorResponse(
1799
+ "Idempotency-Key reused with a different body (`idempotency-key-body-mismatch`), or a retry of a request that succeeded: its answer carried a secret, which isn't kept (`idempotency-key-replay-withheld`, with its `status` and `at`).",
1800
+ ),
1599
1801
  '400': ErrorResponse('Malformed body, or `expiresInSeconds` above the deployment maximum.'),
1600
1802
  '403': ErrorResponse('The caller may not read one of the runs (`permission-denied`).'),
1601
1803
  '404': ErrorResponse('A run does not exist under this tenant (`run-not-found`).'),
@@ -1751,11 +1953,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
1751
1953
  operationId: 'approvals.auditBundle',
1752
1954
  summary: 'Export a signed audit bundle for a decided approval',
1753
1955
  description:
1754
- "Canonicalizes the approval + decision + evidence as sorted-key JSON and signs with the deployment's Ed25519 key looked up by `signingKeyId`. Envelope shape mirrors `provenance.export` byte-for-byte so SDK clients can reuse a single `verifyEd25519` wrapper for both. Only meaningful post-decision — pending approvals return `409 approval-not-decided`.",
1956
+ "Signs the approval, its decision and its evidence with the deployment's export key, and records the export (an `export-signed` audit event). The same envelope as the other signed exports, so one verifier reads all three; check `publicKey` against `GET /v1/export-signing-keys`. For a decided approval only (approved, rejected, escalated, expired, withdrawn): a pending one is `409 approval-not-decided`. A deployment with no export key answers `404 signing-not-configured`.",
1755
1957
  tags: ['approvals'],
1756
1958
  security: 'bearer',
1757
1959
  parameters: [ApprovalIdPathParam, IdempotencyKeyParam],
1758
- requestBody: { required: true, schema: ref('ExportAuditBundleBody') },
1960
+ requestBody: { required: false, schema: ref('ExportAuditBundleBody') },
1759
1961
  responses: {
1760
1962
  '200': { description: 'Signed audit bundle.', schema: ref('ExportAuditBundleResult') },
1761
1963
  ...CommonMutationErrors,
@@ -1843,7 +2045,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1843
2045
  '201': { description: 'The derived agent version.', schema: ref('Agent') },
1844
2046
  ...CommonMutationErrors,
1845
2047
  '409': ErrorResponse(
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.",
2048
+ "Idempotency-Key was reused with a different body, or resource-state conflict. Or `agent-project-mismatch`: the agent belongs to another project than the body's `projectId` (agents never move; the message doesn't name the project). 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.",
1847
2049
  ),
1848
2050
  '400': ErrorResponse(
1849
2051
  "`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).",
@@ -1887,7 +2089,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
1887
2089
  "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
1888
2090
  ),
1889
2091
  '409': ErrorResponse(
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.",
2092
+ "`agent-already-registered`: that (id, version) is taken. Or `agent-project-mismatch`: the agent's versions live in another project (an agent belongs to the project its first version was published into and never moves; the message doesn't name the project). 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.",
1891
2093
  ),
1892
2094
  '404': ErrorResponse(
1893
2095
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
@@ -2344,7 +2546,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2344
2546
  "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
2345
2547
  ),
2346
2548
  '409': ErrorResponse(
2347
- "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.",
2549
+ "`flow-already-registered`: that (id, version) is taken. Or `flow-project-mismatch`: the flow's versions live in another project (a flow belongs to the project its first version was published into and never moves; the message doesn't name the project). 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.",
2348
2550
  ),
2349
2551
  '404': ErrorResponse(
2350
2552
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
@@ -2484,7 +2686,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2484
2686
  "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
2485
2687
  ),
2486
2688
  '409': ErrorResponse(
2487
- "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.",
2689
+ "`tool-already-registered`: that (id, version) is taken. Or `tool-project-mismatch`: the tool's versions live in another project (a tool belongs to the project its first version was published into and never moves; the message doesn't name the project). 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.",
2488
2690
  ),
2489
2691
  '404': ErrorResponse(
2490
2692
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
@@ -2594,6 +2796,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
2594
2796
  '404': ErrorResponse(
2595
2797
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
2596
2798
  ),
2799
+ '422': ErrorResponse(
2800
+ "`guardrail-config-invalid`: the guardrail's `config` breaks the `configSchema` of the pack check it names, which the pack service would refuse on every call. `details.issues` lists each problem, `{ path, message }` with `path` a JSON pointer into the guardrail (`/config/maxChars`); the message names the guardrail, the check and the setting. Checked when the check's deployment carries its schema.",
2801
+ ),
2597
2802
  },
2598
2803
  },
2599
2804
  {
@@ -2698,6 +2903,32 @@ export const OPERATIONS: readonly OperationSpec[] = [
2698
2903
  '404': ErrorResponse('No conversation with that id under this tenant.'),
2699
2904
  },
2700
2905
  },
2906
+ {
2907
+ method: 'post',
2908
+ honoPath: '/v1/conversations/:conversationId/unregister',
2909
+ openapiPath: '/v1/conversations/{conversationId}/unregister',
2910
+ operationId: 'conversations.unregister',
2911
+ summary: 'Unregister a conversation',
2912
+ description:
2913
+ "A tombstone: from now on no read, list or recall of earlier conversations returns it, and no message can be added. The retention sweep removes it after the tenant's grace.",
2914
+ tags: ['conversations'],
2915
+ security: 'bearer',
2916
+ parameters: [ConversationIdPathParam, IdempotencyKeyParam],
2917
+ responses: {
2918
+ '200': {
2919
+ description: 'The conversation, with `unregisteredAt`.',
2920
+ schema: ref('Conversation'),
2921
+ },
2922
+ ...CommonMutationErrors,
2923
+ '400': ErrorResponse('`conversationId` is not a conversation id (a UUID).'),
2924
+ '404': ErrorResponse(
2925
+ 'No conversation with that id under this tenant, or it is unregistered already.',
2926
+ ),
2927
+ '501': ErrorResponse(
2928
+ "`conversation-unregister-unsupported`: this runtime can't unregister conversations.",
2929
+ ),
2930
+ },
2931
+ },
2701
2932
  {
2702
2933
  method: 'get',
2703
2934
  honoPath: '/v1/conversations/:conversationId/messages',
@@ -2730,7 +2961,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2730
2961
  operationId: 'memory.listFacts',
2731
2962
  summary: 'List facts',
2732
2963
  description:
2733
- 'Cursor-paginated. Filters: `?type=` (exact match on `Fact.type`), `?scope=` (URL-encoded JSON partial memory `Scope`), `?scopeKind + ?scopeId + ?inherit` (discriminated `PlatformScope` triplet — threaded into the binding as `platformScope`; both fields coexist). Sort order is binding-defined.',
2964
+ "Cursor-paginated: the current revision of each fact the caller may see. That is tenant-wide facts, the projects they may read (with those projects' orgs), their own user facts, and every end user's and conversation's facts in the projects they may write; a tenant admin sees every fact. Filters: `?type=` (exact match on `Fact.type`), `?scope=` (URL-encoded JSON partial memory `Scope`), `?scopeKind + ?scopeId + ?inherit` (discriminated `PlatformScope` triplet — threaded into the binding as `platformScope`; both fields coexist), `?asOf=` (memory as it stood then). Sort order is binding-defined.",
2734
2965
  tags: ['memory'],
2735
2966
  security: 'bearer',
2736
2967
  parameters: [
@@ -2741,11 +2972,14 @@ export const OPERATIONS: readonly OperationSpec[] = [
2741
2972
  ScopeKindQueryParam,
2742
2973
  ScopeIdQueryParam,
2743
2974
  InheritQueryParam,
2975
+ FactAsOfQueryParam,
2744
2976
  ],
2745
2977
  responses: {
2746
2978
  '200': { description: 'Page of facts.', schema: ref('FactCollectionPage') },
2747
2979
  ...CommonAuthErrors,
2748
- '400': ErrorResponse('Malformed query parameter (e.g. `scope` not valid JSON).'),
2980
+ '400': ErrorResponse(
2981
+ 'Malformed query parameter (e.g. `scope` not valid JSON, `asOf` not a time).',
2982
+ ),
2749
2983
  },
2750
2984
  },
2751
2985
  {
@@ -2754,13 +2988,36 @@ export const OPERATIONS: readonly OperationSpec[] = [
2754
2988
  openapiPath: '/v1/memory/facts/{factId}',
2755
2989
  operationId: 'memory.getFact',
2756
2990
  summary: 'Fetch a fact',
2991
+ description:
2992
+ 'Its current revision; `?version=` reads one revision, `?asOf=` the revision current at that time. A fact the caller may not see is not found.',
2757
2993
  tags: ['memory'],
2758
2994
  security: 'bearer',
2759
- parameters: [FactIdPathParam],
2995
+ parameters: [FactIdPathParam, FactVersionQueryParam, FactAsOfQueryParam],
2760
2996
  responses: {
2761
2997
  '200': { description: 'Fact.', schema: ref('Fact') },
2762
2998
  ...CommonAuthErrors,
2763
- '404': ErrorResponse('No fact with that id under this tenant.'),
2999
+ '400': ErrorResponse('`version` is not a revision number, or `asOf` not a time.'),
3000
+ '404': ErrorResponse('No fact with that id the caller may see (or no such revision).'),
3001
+ },
3002
+ },
3003
+ {
3004
+ method: 'get',
3005
+ honoPath: '/v1/memory/facts/:factId/revisions',
3006
+ openapiPath: '/v1/memory/facts/{factId}/revisions',
3007
+ operationId: 'memory.listFactRevisions',
3008
+ summary: "List a fact's revisions",
3009
+ description:
3010
+ 'Every revision of the fact, newest first, superseded and deleted ones included: who changed it, when and why (`invalidatedBy`, `invalidatedAt`, `invalidationReason`).',
3011
+ tags: ['memory'],
3012
+ security: 'bearer',
3013
+ parameters: [FactIdPathParam],
3014
+ responses: {
3015
+ '200': { description: 'The revisions.', schema: ref('FactRevisionList') },
3016
+ ...CommonAuthErrors,
3017
+ '404': ErrorResponse('No fact with that id the caller may see.'),
3018
+ '501': ErrorResponse(
3019
+ "`memory-operation-unsupported`: this runtime's memory doesn't keep fact history.",
3020
+ ),
2764
3021
  },
2765
3022
  },
2766
3023
  {
@@ -2770,7 +3027,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2770
3027
  operationId: 'memory.writeFact',
2771
3028
  summary: 'Write a fact',
2772
3029
  description:
2773
- 'Persists a new fact. `type` selects the retrieval policy (which indexes populate). Semantic-indexed types require an embedding provider bound on the deployment; if unavailable, the route returns `400 bad-input`.',
3030
+ "Persists a new fact. `type` selects the retrieval policy (which indexes populate). Semantic-indexed types require an embedding provider bound on the deployment; if unavailable, the route returns `400 bad-input`. The caller needs write where the scope says: `write` on its project (an end user's or a conversation's fact included), `write` on its conversation outside a project, `admin` on its org for an org-wide fact, being that user for a user's fact, and tenant `admin` for a tenant-wide fact. The fact records who wrote it (`attributedTo`).",
2774
3031
  tags: ['memory'],
2775
3032
  security: 'bearer',
2776
3033
  parameters: [IdempotencyKeyParam],
@@ -2781,6 +3038,10 @@ export const OPERATIONS: readonly OperationSpec[] = [
2781
3038
  '400': ErrorResponse(
2782
3039
  'Malformed body, or the fact type requires semantic indexing and no embedding provider is bound.',
2783
3040
  ),
3041
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
3042
+ '409': ErrorResponse(
3043
+ "Idempotency-Key was reused with a different body; or an erasure of the fact's person (by scope or subject) or conversation is in progress (`erasure-in-progress`): nothing new is stored for them until it completes.",
3044
+ ),
2784
3045
  },
2785
3046
  },
2786
3047
  {
@@ -2788,19 +3049,71 @@ export const OPERATIONS: readonly OperationSpec[] = [
2788
3049
  honoPath: '/v1/memory/facts/:factId/supersede',
2789
3050
  openapiPath: '/v1/memory/facts/{factId}/supersede',
2790
3051
  operationId: 'memory.supersedeFact',
2791
- summary: 'Mark a fact as superseded',
3052
+ summary: "Change a fact's content",
2792
3053
  description:
2793
- 'Soft-delete via supersession — the historical row is retained until retention sweeps remove it. Idempotent: superseding an already-superseded fact returns `200 { superseded: true }`.',
3054
+ 'Writes the next revision (same fact id, `version` one more) and closes the current one; the old revision stays readable with `?version=`, `?asOf=` and in the history until the retention sweep removes it. `expectVersion` refuses if someone changed it first.',
2794
3055
  tags: ['memory'],
2795
3056
  security: 'bearer',
2796
3057
  parameters: [FactIdPathParam, IdempotencyKeyParam],
3058
+ requestBody: { required: true, schema: ref('SupersedeFactBody') },
2797
3059
  responses: {
2798
- '200': {
2799
- description: 'Superseded (or already superseded).',
2800
- schema: ref('SupersedeFactResult'),
2801
- },
3060
+ '200': { description: 'The new revision.', schema: ref('Fact') },
2802
3061
  ...CommonMutationErrors,
2803
- '404': ErrorResponse('No fact with that id under this tenant.'),
3062
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
3063
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
3064
+ '409': ErrorResponse(
3065
+ '`fact-changed`: the current revision is not `expectVersion` (`details.currentVersion`); `legal-hold`: the fact is under legal hold.',
3066
+ ),
3067
+ },
3068
+ },
3069
+ {
3070
+ method: 'delete',
3071
+ honoPath: '/v1/memory/facts/:factId',
3072
+ openapiPath: '/v1/memory/facts/{factId}',
3073
+ operationId: 'memory.deleteFact',
3074
+ summary: 'Delete a fact',
3075
+ description:
3076
+ 'Closes the current revision (`invalidationReason: deleted`): the fact is no longer listed, fetched or retrieved, and its history stays readable until the retention sweep removes it.',
3077
+ tags: ['memory'],
3078
+ security: 'bearer',
3079
+ parameters: [FactIdPathParam, FactExpectVersionQueryParam],
3080
+ responses: {
3081
+ '200': { description: 'The closed revision.', schema: ref('Fact') },
3082
+ ...CommonAuthErrors,
3083
+ '400': ErrorResponse('`expectVersion` is not a revision number.'),
3084
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
3085
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
3086
+ '409': ErrorResponse(
3087
+ '`fact-changed`: the current revision is not `expectVersion`; `legal-hold`: the fact is under legal hold.',
3088
+ ),
3089
+ '501': ErrorResponse(
3090
+ "`memory-operation-unsupported`: this runtime's memory can't delete facts.",
3091
+ ),
3092
+ },
3093
+ },
3094
+ {
3095
+ method: 'post',
3096
+ honoPath: '/v1/memory/facts/:factId/verify',
3097
+ openapiPath: '/v1/memory/facts/{factId}/verify',
3098
+ operationId: 'memory.verifyFact',
3099
+ summary: 'Mark a fact verified',
3100
+ description:
3101
+ 'A person who may write in its scope checked it: the next revision has `trust: verified`, `verifiedBy` and `verifiedAt`, and the same content.',
3102
+ tags: ['memory'],
3103
+ security: 'bearer',
3104
+ parameters: [FactIdPathParam, IdempotencyKeyParam],
3105
+ requestBody: { required: false, schema: ref('VerifyFactBody') },
3106
+ responses: {
3107
+ '200': { description: 'The verified revision.', schema: ref('Fact') },
3108
+ ...CommonMutationErrors,
3109
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
3110
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
3111
+ '409': ErrorResponse(
3112
+ '`fact-changed`: the current revision is not `expectVersion`; `legal-hold`: the fact is under legal hold.',
3113
+ ),
3114
+ '501': ErrorResponse(
3115
+ "`memory-operation-unsupported`: this runtime's memory can't verify facts.",
3116
+ ),
2804
3117
  },
2805
3118
  },
2806
3119
  {
@@ -2810,7 +3123,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
2810
3123
  operationId: 'memory.retrieve',
2811
3124
  summary: 'Retrieve facts by intent',
2812
3125
  description:
2813
- 'Cross-history retrieval. Body is a `RetrieveIntent` shape mirroring the agent-side declarative retrieval. Semantic modes require an embedding provider bound on the deployment.',
3126
+ 'Retrieval over the facts the caller may see (as for listing), current revisions only. `mode`: `list` (newest first, no query), `keyword` (full-text), `semantic` (by meaning), or `both` (the two searches fused by rank: reciprocal rank fusion). Searching by meaning needs embeddings on the deployment (`KINDGI_MEMORY_EMBEDDINGS`); without them `semantic` and `both` answer `422 semantic-unavailable`.',
2814
3127
  tags: ['memory'],
2815
3128
  security: 'bearer',
2816
3129
  parameters: [IdempotencyKeyParam],
@@ -2818,38 +3131,179 @@ export const OPERATIONS: readonly OperationSpec[] = [
2818
3131
  responses: {
2819
3132
  '200': { description: 'Retrieval results.', schema: ref('RetrieveMemoryResult') },
2820
3133
  ...CommonMutationErrors,
3134
+ '400': ErrorResponse('Malformed intent.'),
3135
+ '422': ErrorResponse(
3136
+ '`semantic-unavailable`: `semantic` or `both` asked to search by meaning, and the deployment has no embeddings (`KINDGI_MEMORY_EMBEDDINGS`).',
3137
+ ),
3138
+ '501': ErrorResponse(
3139
+ "`memory-operation-unsupported`: this runtime's memory can't retrieve by intent.",
3140
+ ),
3141
+ },
3142
+ },
3143
+
3144
+ // ---------- memory erasures (a person's words) ----------
3145
+ {
3146
+ method: 'post',
3147
+ honoPath: '/v1/memory/erasures',
3148
+ openapiPath: '/v1/memory/erasures',
3149
+ operationId: 'memory.createErasure',
3150
+ summary: "Erase a person's words",
3151
+ description:
3152
+ "Starts erasing, in the background, one fact (`factId`), a person (`subject`: an app's end user `participant`, or an `external` subject facts name) or one conversation (`conversationId`): their facts, conversations (messages, recall rows), the runs that served them (input, output, journal, snapshots) and the free text they left in provenance; facts written from them go to review. Answers `202` with the erasure; follow it with `GET /v1/memory/erasures/{erasureId}`. A completed erasure keeps no identifier, only a keyed hash for a replay after a backup restore; `warnings` says when this deployment can't keep one (`erasure-unmatchable`: no erasure ledger key, `KINDGI_ERASURE_LEDGER_KEY`). Requires `admin` on the tenant.",
3153
+ tags: ['memory'],
3154
+ security: 'bearer',
3155
+ parameters: [IdempotencyKeyParam],
3156
+ requestBody: { required: true, schema: ref('CreateMemoryErasureBody') },
3157
+ responses: {
3158
+ '202': { description: 'Started.', schema: ref('MemoryErasureCreated') },
3159
+ ...CommonMutationErrors,
2821
3160
  '400': ErrorResponse(
2822
- 'Malformed intent, or semantic mode requested and no embedding provider is bound.',
3161
+ "Not exactly one of `factId`, `subject` or `conversationId`; or a `subject` of kind `user` (erasing a Kindgi user isn't offered).",
3162
+ ),
3163
+ '403': ErrorResponse('Not a tenant admin.'),
3164
+ '409': ErrorResponse(
3165
+ '`legal-hold`: a fact it reaches is under legal hold (`details.factIds`); nothing started.',
2823
3166
  ),
2824
3167
  },
2825
3168
  },
3169
+ {
3170
+ method: 'get',
3171
+ honoPath: '/v1/memory/erasures',
3172
+ openapiPath: '/v1/memory/erasures',
3173
+ operationId: 'memory.listErasures',
3174
+ summary: 'List erasures',
3175
+ description:
3176
+ 'Cursor-paginated, newest first: each erasure, how far it got and what it cleared. A completed one shows no selector. Requires `admin` on the tenant.',
3177
+ tags: ['memory'],
3178
+ security: 'bearer',
3179
+ parameters: [LimitQueryParam, CursorQueryParam],
3180
+ responses: {
3181
+ '200': { description: 'Page of erasures.', schema: ref('MemoryErasurePage') },
3182
+ ...CommonAuthErrors,
3183
+ '403': ErrorResponse('Not a tenant admin.'),
3184
+ '400': ErrorResponse('`cursor` is not one this list issued.'),
3185
+ },
3186
+ },
3187
+ {
3188
+ method: 'get',
3189
+ honoPath: '/v1/memory/erasures/export',
3190
+ openapiPath: '/v1/memory/erasures/export',
3191
+ operationId: 'memory.exportErasures',
3192
+ summary: 'Export the erasure ledger',
3193
+ description:
3194
+ "The whole ledger, oldest first, content-free: each erasure's selector kind, the keyed hash of whom it erased, who asked and when. Keep it off-box: restoring a backup rolls the ledger back too, and `POST /v1/memory/erasures/replay` with it runs the erasures again. Requires `admin` on the tenant.",
3195
+ tags: ['memory'],
3196
+ security: 'bearer',
3197
+ responses: {
3198
+ '200': { description: 'The ledger.', schema: ref('MemoryErasureLedger') },
3199
+ ...CommonAuthErrors,
3200
+ '403': ErrorResponse('Not a tenant admin.'),
3201
+ },
3202
+ },
3203
+ {
3204
+ method: 'post',
3205
+ honoPath: '/v1/memory/erasures/replay',
3206
+ openapiPath: '/v1/memory/erasures/replay',
3207
+ operationId: 'memory.replayErasures',
3208
+ summary: 'Replay erasures after a backup restore',
3209
+ description:
3210
+ "Takes the ledger `GET /v1/memory/erasures/export` gave, puts back the rows the restore lost, and finds each erasure's person (or fact, or conversation) again by its keyed hash: those run again (`replayed`); ones nothing in the tenant matches are only restored (`restored`); ones with no keyed hash, or a key this deployment doesn't hold, are `unmatched`. Requires `admin` on the tenant.",
3211
+ tags: ['memory'],
3212
+ security: 'bearer',
3213
+ parameters: [IdempotencyKeyParam],
3214
+ requestBody: { required: true, schema: ref('ReplayMemoryErasuresBody') },
3215
+ responses: {
3216
+ '200': { description: 'What was replayed.', schema: ref('ReplayMemoryErasuresResult') },
3217
+ ...CommonMutationErrors,
3218
+ '400': ErrorResponse('Not `{erasures: [...]}` as the export gave them.'),
3219
+ '403': ErrorResponse('Not a tenant admin.'),
3220
+ },
3221
+ },
3222
+ {
3223
+ method: 'post',
3224
+ honoPath: '/v1/memory/erasures/:erasureId/resume',
3225
+ openapiPath: '/v1/memory/erasures/{erasureId}/resume',
3226
+ operationId: 'memory.resumeErasure',
3227
+ summary: 'Resume an erasure',
3228
+ description:
3229
+ "Tries an unfinished erasure again now. With `force: true`, an erasure `waiting-on-run` (a turn of the person's in a flow that serves other people) stops waiting: the run is cancelled and the erasure goes on; without it, it waits until its deadline (`waitingOn.until`). A finished erasure comes back as it is. Requires `admin` on the tenant.",
3230
+ tags: ['memory'],
3231
+ security: 'bearer',
3232
+ parameters: [ErasureIdPathParam, IdempotencyKeyParam],
3233
+ requestBody: { required: false, schema: ref('ResumeMemoryErasureBody') },
3234
+ responses: {
3235
+ '200': { description: 'The erasure.', schema: ref('MemoryErasure') },
3236
+ ...CommonMutationErrors,
3237
+ '400': ErrorResponse('The body is `{force?: boolean}`.'),
3238
+ '403': ErrorResponse('Not a tenant admin.'),
3239
+ '404': ErrorResponse('No such erasure in this tenant.'),
3240
+ },
3241
+ },
3242
+ {
3243
+ method: 'get',
3244
+ honoPath: '/v1/memory/erasures/:erasureId',
3245
+ openapiPath: '/v1/memory/erasures/{erasureId}',
3246
+ operationId: 'memory.getErasure',
3247
+ summary: 'Get an erasure',
3248
+ description:
3249
+ 'One erasure: its status (`pending`, `running`, `completed`, `failed`), phase, what each store cleared (`counts`), and `lastError` (a code, or `not-yet:<reason>` while it waits for a run to finish). Requires `admin` on the tenant.',
3250
+ tags: ['memory'],
3251
+ security: 'bearer',
3252
+ parameters: [ErasureIdPathParam],
3253
+ responses: {
3254
+ '200': { description: 'The erasure.', schema: ref('MemoryErasure') },
3255
+ ...CommonAuthErrors,
3256
+ '403': ErrorResponse('Not a tenant admin.'),
3257
+ '404': ErrorResponse('No such erasure in this tenant.'),
3258
+ },
3259
+ },
2826
3260
 
2827
- // ---------- proposals (supervisor fix lifecycle) ----------
3261
+ // ---------- improvement proposals ----------
2828
3262
  {
2829
3263
  method: 'get',
2830
3264
  honoPath: '/v1/proposals',
2831
3265
  openapiPath: '/v1/proposals',
2832
3266
  operationId: 'proposals.list',
2833
- summary: 'List supervisor fix proposals',
3267
+ summary: 'List improvement proposals',
2834
3268
  description:
2835
- 'Cursor-paginated list scoped to `(tenantId, supervisorId)`. Filters: `?status=`, `?agentId=`, `?tier=`. Sort order is binding-defined (typically `createdAt desc, id desc`).',
3269
+ 'Newest first, cursor-paginated, only the proposals of agents the caller can read. Filters: `?agentId=`, `?tier=`, `?status=` (statuses are derived, so a page filtered by status can hold fewer rows than `limit`), and the live scope a proposal is for (`scopeKind`, `scopeId`, `segment`, exactly as the promotions history takes it).',
2836
3270
  tags: ['proposals'],
2837
3271
  security: 'bearer',
2838
3272
  parameters: [
2839
- SupervisorIdHeaderParam,
2840
3273
  LimitQueryParam,
2841
3274
  CursorQueryParam,
2842
3275
  ProposalStatusQueryParam,
2843
3276
  AgentIdQueryParam,
2844
3277
  ProposalTierQueryParam,
2845
- ScopeKindQueryParam,
3278
+ PromotionScopeKindQueryParam,
2846
3279
  ScopeIdQueryParam,
2847
- InheritQueryParam,
3280
+ SegmentQueryParam,
2848
3281
  ],
2849
3282
  responses: {
2850
3283
  '200': { description: 'Page of proposals.', schema: ref('FixProposalCollectionPage') },
2851
3284
  ...CommonAuthErrors,
2852
- '400': ErrorResponse('Missing `X-Supervisor-Id` header or malformed query parameter.'),
3285
+ '400': ErrorResponse('A malformed query parameter.'),
3286
+ },
3287
+ },
3288
+ {
3289
+ method: 'post',
3290
+ honoPath: '/v1/proposals/improve',
3291
+ openapiPath: '/v1/proposals/improve',
3292
+ operationId: 'proposals.improve',
3293
+ summary: 'Start an improvement pass',
3294
+ description:
3295
+ "The runtime looks for better values for the version's tunable settings (keys its settings blocks' schemas mark `x-kindgi-tunable`) on the test set, within the budget, and writes its best candidate as an improvement proposal, which waits for a reviewer when requested. It answers at once with the pass, `running`. Checked first: the version is active and pins a settings block with tunable keys (`400 validation-failed`), the agent registry takes writes (`409 registry-read-only`), and the agent has a live version for the whole tenant (`409 proposal-needs-pin`). Needs `publish` on the agent. Without improvement passes in this runtime, `501 improve-unsupported`.",
3296
+ tags: ['proposals'],
3297
+ security: 'bearer',
3298
+ parameters: [IdempotencyKeyParam],
3299
+ requestBody: { required: true, schema: ref('ImproveBody') },
3300
+ responses: {
3301
+ '202': { description: 'The pass, running.', schema: ref('ImprovementPass') },
3302
+ ...CommonMutationErrors,
3303
+ '404': ErrorResponse(
3304
+ '`fromVersion` (or the version serving the scope) is not an active version.',
3305
+ ),
3306
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2853
3307
  },
2854
3308
  },
2855
3309
  {
@@ -2857,93 +3311,82 @@ export const OPERATIONS: readonly OperationSpec[] = [
2857
3311
  honoPath: '/v1/proposals/:proposalId',
2858
3312
  openapiPath: '/v1/proposals/{proposalId}',
2859
3313
  operationId: 'proposals.get',
2860
- summary: 'Fetch a fix proposal',
3314
+ summary: 'Fetch an improvement proposal',
3315
+ description: 'Needs `read` on its agent.',
2861
3316
  tags: ['proposals'],
2862
3317
  security: 'bearer',
2863
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam],
3318
+ parameters: [ProposalIdPathParam],
2864
3319
  responses: {
2865
- '200': { description: 'Proposal.', schema: ref('FixProposal') },
3320
+ '200': { description: 'The proposal.', schema: ref('FixProposal') },
2866
3321
  ...CommonAuthErrors,
2867
- '400': ErrorResponse('Missing `X-Supervisor-Id` header.'),
2868
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
3322
+ '404': ErrorResponse('No such proposal (or none the caller can read).'),
2869
3323
  },
2870
3324
  },
2871
3325
  {
2872
3326
  method: 'post',
2873
3327
  honoPath: '/v1/proposals',
2874
3328
  openapiPath: '/v1/proposals',
2875
- operationId: 'proposals.draft',
2876
- summary: 'Draft a fix proposal',
3329
+ operationId: 'proposals.create',
3330
+ summary: 'Propose new content for a data block',
2877
3331
  description:
2878
- 'Inserts a new proposal in `draft` state. Duplicate proposals (same `(supervisor, fingerprint)` non-terminal) short-circuit to the pre-existing row and mark the response with `X-Proposal-Deduped: true`.',
3332
+ 'A hand-written proposal: new settings values or a new prompt template for a block `fromVersion` pins, for a live scope. Checked as publishing that block version would be (its schema carries over), and refused when it equals the pinned content. The same change from the same version for the same scope is one proposal: answered `200` with `X-Proposal-Deduped: true`. Needs `publish` on the agent.',
2879
3333
  tags: ['proposals'],
2880
3334
  security: 'bearer',
2881
- parameters: [SupervisorIdHeaderParam, IdempotencyKeyParam],
2882
- requestBody: { required: true, schema: ref('DraftProposalBody') },
3335
+ parameters: [IdempotencyKeyParam],
3336
+ requestBody: { required: true, schema: ref('CreateProposalBody') },
2883
3337
  responses: {
2884
- '201': { description: 'Proposal drafted (or deduped).', schema: ref('FixProposal') },
3338
+ '201': { description: 'The proposal, a `draft`.', schema: ref('FixProposal') },
3339
+ '200': { description: 'The same proposal, made before.', schema: ref('FixProposal') },
2885
3340
  ...CommonMutationErrors,
3341
+ '404': ErrorResponse(
3342
+ "`fromVersion` isn't an active version of the agent (`agent-version-not-found`).",
3343
+ ),
2886
3344
  },
2887
3345
  },
2888
3346
  {
2889
3347
  method: 'post',
2890
- honoPath: '/v1/proposals/:proposalId/dry-run',
2891
- openapiPath: '/v1/proposals/{proposalId}/dry-run',
2892
- operationId: 'proposals.dryRun',
2893
- summary: 'Dry-run a proposal against an eval dataset',
3348
+ honoPath: '/v1/proposals/:proposalId/evaluate',
3349
+ openapiPath: '/v1/proposals/{proposalId}/evaluate',
3350
+ operationId: 'proposals.evaluate',
3351
+ summary: 'Compare a proposal on a test set',
2894
3352
  description:
2895
- 'Runs the candidate agent against the caller-supplied dataset + criterion. Transitions the proposal to `dry-run-passed` or `dry-run-failed`. Legal only from `draft` or `dry-run-failed`.',
3353
+ "The first evaluation publishes the block version and derives the agent version (`derivedFrom.proposalId`); they serve no scope until a promotion makes them live. Then a comparison eval run replays that version on the test set, against the recorded outputs (`baseline: 'recorded'`). Needs `publish` on the agent, and a live version of it for the whole tenant: an agent with none serves its latest version wherever nothing is pinned, so a new version would go live there at once (`409 proposal-needs-pin`). Allowed from `draft`, `evaluated`, `not-better`, `evaluation-failed`, `refused`, `superseded` and `expired`.",
2896
3354
  tags: ['proposals'],
2897
3355
  security: 'bearer',
2898
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2899
- requestBody: { required: true, schema: ref('DryRunProposalBody') },
3356
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
3357
+ requestBody: { required: true, schema: ref('EvaluateProposalBody') },
2900
3358
  responses: {
2901
- '200': { description: 'Dry-run completed.', schema: ref('DryRunProposalResult') },
3359
+ '202': { description: 'The proposal, `evaluating`.', schema: ref('FixProposal') },
2902
3360
  ...CommonMutationErrors,
2903
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2904
- '422': ErrorResponse('Baseline mismatch, apply-change failure, or runtime dry-run error.'),
3361
+ '404': ErrorResponse('No such proposal, or no such test set.'),
3362
+ '409': ErrorResponse(
3363
+ "`proposal-needs-pin`: the agent has no live version for the whole tenant. `proposal-invalid-state-transition`: the proposal's status doesn't allow it. `registry-read-only`: the agent registry takes no writes (under `kindgi dev`).",
3364
+ ),
2905
3365
  },
2906
3366
  },
2907
3367
  {
2908
3368
  method: 'post',
2909
- honoPath: '/v1/proposals/:proposalId/submit-review',
2910
- openapiPath: '/v1/proposals/{proposalId}/submit-review',
2911
- operationId: 'proposals.submitReview',
2912
- summary: 'Submit a dry-run-passed proposal for HITL review',
3369
+ honoPath: '/v1/proposals/:proposalId/request',
3370
+ openapiPath: '/v1/proposals/{proposalId}/request',
3371
+ operationId: 'proposals.request',
3372
+ summary: "Request a proposal's promotion for its scope",
2913
3373
  description:
2914
- 'Enqueues a HITL approval and transitions the proposal to `proposed-for-review`. Legal only from `dry-run-passed`. Body is optional; defaults auto-derive the reviewer role (meta-fixes → senior).',
3374
+ "A promotion of the candidate for the proposal's scope, with its evaluation's comparison, through the scope's gate: as `POST /v1/agents/{agentId}/promotions` answers. Needs `promote` on the agent. Allowed from `evaluated`, `not-better` (the gate decides), `refused`, `superseded` and `expired`.",
2915
3375
  tags: ['proposals'],
2916
3376
  security: 'bearer',
2917
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2918
- requestBody: { required: false, schema: ref('SubmitReviewProposalBody') },
3377
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
3378
+ requestBody: { required: false, schema: ref('ProposalReasonBody') },
2919
3379
  responses: {
2920
- '200': {
2921
- description: 'Review enqueued.',
2922
- schema: ref('SubmitReviewProposalResult'),
3380
+ '201': { description: 'Promoted: the proposal, `promoted`.', schema: ref('FixProposal') },
3381
+ '202': {
3382
+ description: 'The gate passed and an approval is open: the proposal, `in-review`.',
3383
+ schema: ref('FixProposal'),
2923
3384
  },
2924
3385
  ...CommonMutationErrors,
2925
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2926
- '422': ErrorResponse('Ground-layer guardrail violation.'),
2927
- },
2928
- },
2929
- {
2930
- method: 'post',
2931
- honoPath: '/v1/proposals/:proposalId/apply',
2932
- openapiPath: '/v1/proposals/{proposalId}/apply',
2933
- operationId: 'proposals.apply',
2934
- summary: 'Apply an approved proposal',
2935
- description:
2936
- 'Materializes the proposed change into a new agent version, registers it in the agent registry, and transitions the proposal to `applied`. Legal only from `approved`.',
2937
- tags: ['proposals'],
2938
- security: 'bearer',
2939
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2940
- requestBody: { required: false, schema: ref('ApplyProposalBody') },
2941
- responses: {
2942
- '200': { description: 'Proposal applied.', schema: ref('ApplyProposalResult') },
2943
- ...CommonMutationErrors,
2944
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
3386
+ '404': ErrorResponse('No such proposal.'),
3387
+ '409': ErrorResponse("The proposal's status doesn't allow it."),
2945
3388
  '422': ErrorResponse(
2946
- 'Baseline agent not in registry, invalid new version, or apply-change failure.',
3389
+ '`gate-failed`: the gate refused it (the error carries the checks and `proposalId`).',
2947
3390
  ),
2948
3391
  },
2949
3392
  },
@@ -2952,17 +3395,18 @@ export const OPERATIONS: readonly OperationSpec[] = [
2952
3395
  honoPath: '/v1/proposals/:proposalId/rollback',
2953
3396
  openapiPath: '/v1/proposals/{proposalId}/rollback',
2954
3397
  operationId: 'proposals.rollback',
2955
- summary: 'Roll back an applied proposal',
3398
+ summary: 'Roll back a promoted proposal',
2956
3399
  description:
2957
- 'Unregisters the applied version from the agent registry and transitions the proposal to `rolled-back`. Legal only from `applied`.',
3400
+ "The scope goes back to the version its own pin held before the proposal's promotion (or, with none, falls back to the scope above). Only while the proposal's version still serves the scope. Needs `promote` on the agent.",
2958
3401
  tags: ['proposals'],
2959
3402
  security: 'bearer',
2960
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2961
- requestBody: { required: true, schema: ref('RollbackProposalBody') },
3403
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
3404
+ requestBody: { required: false, schema: ref('ProposalReasonBody') },
2962
3405
  responses: {
2963
- '200': { description: 'Proposal rolled back.', schema: ref('RollbackProposalResult') },
3406
+ '200': { description: 'The proposal, `rolled-back`.', schema: ref('FixProposal') },
2964
3407
  ...CommonMutationErrors,
2965
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
3408
+ '404': ErrorResponse('No such proposal.'),
3409
+ '409': ErrorResponse("It isn't promoted, or its version doesn't serve the scope anymore."),
2966
3410
  },
2967
3411
  },
2968
3412
  {
@@ -2970,17 +3414,74 @@ export const OPERATIONS: readonly OperationSpec[] = [
2970
3414
  honoPath: '/v1/proposals/:proposalId/withdraw',
2971
3415
  openapiPath: '/v1/proposals/{proposalId}/withdraw',
2972
3416
  operationId: 'proposals.withdraw',
2973
- summary: 'Withdraw a non-terminal proposal',
3417
+ summary: 'Withdraw a proposal',
2974
3418
  description:
2975
- 'Transitions the proposal to `withdrawn`. Legal from any non-terminal state (`draft | dry-running | dry-run-passed | dry-run-failed | proposed-for-review`). Terminal states surface as `409 proposal-invalid-state-transition`.',
3419
+ "Closes it. Not while it's in review (decide its approval instead), nor once promoted, rejected or rolled back. Needs `publish` on the agent.",
2976
3420
  tags: ['proposals'],
2977
3421
  security: 'bearer',
2978
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
3422
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
2979
3423
  requestBody: { required: true, schema: ref('WithdrawProposalBody') },
2980
3424
  responses: {
2981
- '200': { description: 'Proposal withdrawn.', schema: ref('FixProposal') },
3425
+ '200': { description: 'The proposal, `withdrawn`.', schema: ref('FixProposal') },
2982
3426
  ...CommonMutationErrors,
2983
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
3427
+ '404': ErrorResponse('No such proposal.'),
3428
+ '409': ErrorResponse("The proposal's status doesn't allow it."),
3429
+ },
3430
+ },
3431
+
3432
+ // ---------- improvement passes ----------
3433
+ {
3434
+ method: 'get',
3435
+ honoPath: '/v1/improvement-passes',
3436
+ openapiPath: '/v1/improvement-passes',
3437
+ operationId: 'improvementPasses.list',
3438
+ summary: 'List improvement passes',
3439
+ description:
3440
+ 'Newest first, only the passes of agents the caller can read. `?agentId=` narrows them.',
3441
+ tags: ['proposals'],
3442
+ security: 'bearer',
3443
+ parameters: [LimitQueryParam, CursorQueryParam, AgentIdQueryParam],
3444
+ responses: {
3445
+ '200': { description: 'Page of passes.', schema: ref('ImprovementPassCollectionPage') },
3446
+ ...CommonAuthErrors,
3447
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
3448
+ },
3449
+ },
3450
+ {
3451
+ method: 'get',
3452
+ honoPath: '/v1/improvement-passes/:passId',
3453
+ openapiPath: '/v1/improvement-passes/{passId}',
3454
+ operationId: 'improvementPasses.get',
3455
+ summary: 'Fetch an improvement pass',
3456
+ description:
3457
+ 'Its status, the candidates it compared and what they cost, and once it ends, what it found. Needs `read` on its agent.',
3458
+ tags: ['proposals'],
3459
+ security: 'bearer',
3460
+ parameters: [ImprovementPassIdPathParam],
3461
+ responses: {
3462
+ '200': { description: 'The pass.', schema: ref('ImprovementPass') },
3463
+ ...CommonAuthErrors,
3464
+ '404': ErrorResponse('No such pass (or none the caller can read).'),
3465
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
3466
+ },
3467
+ },
3468
+ {
3469
+ method: 'post',
3470
+ honoPath: '/v1/improvement-passes/:passId/cancel',
3471
+ openapiPath: '/v1/improvement-passes/{passId}/cancel',
3472
+ operationId: 'improvementPasses.cancel',
3473
+ summary: 'Cancel an improvement pass',
3474
+ description:
3475
+ 'A running pass stops and ends `cancelled`, writing no proposal. Needs `publish` on its agent.',
3476
+ tags: ['proposals'],
3477
+ security: 'bearer',
3478
+ parameters: [ImprovementPassIdPathParam, IdempotencyKeyParam],
3479
+ responses: {
3480
+ '200': { description: 'The pass, cancelled.', schema: ref('ImprovementPass') },
3481
+ ...CommonMutationErrors,
3482
+ '404': ErrorResponse('No such pass.'),
3483
+ '409': ErrorResponse('`improvement-pass-finished`: it has ended already.'),
3484
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2984
3485
  },
2985
3486
  },
2986
3487
 
@@ -3027,6 +3528,21 @@ export const OPERATIONS: readonly OperationSpec[] = [
3027
3528
  '404': ErrorResponse('No provenance record for that run under this tenant.'),
3028
3529
  },
3029
3530
  },
3531
+ {
3532
+ method: 'get',
3533
+ honoPath: '/v1/export-signing-keys',
3534
+ openapiPath: '/v1/export-signing-keys',
3535
+ operationId: 'exportSigningKeys.list',
3536
+ summary: 'List the keys this deployment signs exports with',
3537
+ description:
3538
+ "The public keys of the deployment's export signing key, active first: what a verifier pins. An export's embedded `publicKey` only proves its bytes weren't changed; this list says who signed them. Empty when the deployment doesn't sign exports. Any authenticated caller may read it.",
3539
+ tags: ['export-signing-keys'],
3540
+ security: 'bearer',
3541
+ responses: {
3542
+ '200': { description: 'The keys.', schema: ref('ExportSigningKeyList') },
3543
+ ...CommonAuthErrors,
3544
+ },
3545
+ },
3030
3546
  {
3031
3547
  method: 'post',
3032
3548
  honoPath: '/v1/provenance/:runId/export',
@@ -3034,11 +3550,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
3034
3550
  operationId: 'provenance.export',
3035
3551
  summary: 'Export a signed provenance bundle for a run',
3036
3552
  description:
3037
- "Canonicalizes the record + optional messages as sorted-key JSON and signs with the deployment's Ed25519 key looked up by `signingKeyId`. Verification is a pure client-side operation: `verifyEd25519(publicKey, bundleBytes, signature)`. Deployments without a `signingKey` binding mounted return `404 signing-not-configured`.",
3553
+ "Signs the run's provenance (and, when asked, its messages) with the deployment's export key, and records the export (an `export-signed` audit event). The same envelope as the other signed exports; check `publicKey` against `GET /v1/export-signing-keys`. A deployment with no export key answers `404 signing-not-configured`.",
3038
3554
  tags: ['provenance'],
3039
3555
  security: 'bearer',
3040
3556
  parameters: [RunIdPathParam, IdempotencyKeyParam],
3041
- requestBody: { required: true, schema: ref('ExportProvenanceBody') },
3557
+ requestBody: { required: false, schema: ref('ExportProvenanceBody') },
3042
3558
  responses: {
3043
3559
  '200': { description: 'Signed bundle.', schema: ref('ExportProvenanceResult') },
3044
3560
  ...CommonMutationErrors,
@@ -3056,7 +3572,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
3056
3572
  operationId: 'artifacts.list',
3057
3573
  summary: 'List artifact metadata',
3058
3574
  description:
3059
- 'Cursor-paginated. Metadata rows only (no bytes). Filters: `?ownerRunId=`, `?contentType=`, `?tag.<key>=<value>` (repeatable — every provided tag must match as AND). Sort order is binding-defined (typically `createdAt desc, blobId desc`).',
3575
+ 'Cursor-paginated. Metadata rows only (no bytes). Filters: `?ownerRunId=`, `?projectId=`, `?contentType=`, `?tag.<key>=<value>` (repeatable — every provided tag must match as AND). Sort order is binding-defined (typically `createdAt desc, blobId desc`). With authorization on, only artifacts in projects the caller can read are listed.',
3060
3576
  tags: ['artifacts'],
3061
3577
  security: 'bearer',
3062
3578
  parameters: [
@@ -3076,6 +3592,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
3076
3592
  description: 'Filter by exact content-type match.',
3077
3593
  schema: { type: 'string' },
3078
3594
  },
3595
+ {
3596
+ name: 'projectId',
3597
+ in: 'query',
3598
+ required: false,
3599
+ description: 'Filter to the artifacts of one project.',
3600
+ schema: { type: 'string' },
3601
+ },
3079
3602
  ],
3080
3603
  responses: {
3081
3604
  '200': { description: 'Page of blob metadata.', schema: ref('ArtifactCollectionPage') },
@@ -3090,7 +3613,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
3090
3613
  operationId: 'artifacts.upload',
3091
3614
  summary: 'Upload an artifact',
3092
3615
  description:
3093
- 'Multipart upload. `file` part carries the bytes; other form fields carry metadata (`name`, `contentType`, `tags` (JSON), `ownerRunId`, `expectedHash`). Framework computes sha256 and returns it in `BlobMeta.hash`. If `expectedHash` was supplied and diverges, response is `400 blob-hash-mismatch`. Content-type sniffing is NOT performed server-side — the framework trusts the caller.',
3616
+ "Multipart upload. `file` part carries the bytes; other form fields carry metadata (`name`, `contentType`, `tags` (JSON), `ownerRunId`, `projectId`, `expectedHash`). Framework computes sha256 and returns it in `BlobMeta.hash`. If `expectedHash` was supplied and diverges, response is `400 blob-hash-mismatch`. Content-type sniffing is NOT performed server-side — the framework trusts the caller. The artifact belongs to its owner run's project, else `projectId`, else the tenant's default project; uploading needs `write` there. An upload over the runtime's cap (default 100 MB) is `413 artifact-too-large`.",
3094
3617
  tags: ['artifacts'],
3095
3618
  security: 'bearer',
3096
3619
  parameters: [IdempotencyKeyParam],
@@ -3107,7 +3630,12 @@ export const OPERATIONS: readonly OperationSpec[] = [
3107
3630
  '201': { description: 'Upload accepted; metadata returned.', schema: ref('BlobMeta') },
3108
3631
  ...CommonMutationErrors,
3109
3632
  '400': ErrorResponse(
3110
- 'Malformed multipart body, bad tag JSON, hash mismatch, or declared size mismatch.',
3633
+ "Malformed multipart body, bad tag JSON, hash mismatch, declared size mismatch, or a `projectId` that isn't the owner run's.",
3634
+ ),
3635
+ '403': ErrorResponse('No `write` on the project (`permission-denied`).'),
3636
+ '404': ErrorResponse('No such owner run (`run-not-found`).'),
3637
+ '413': ErrorResponse(
3638
+ 'Over the upload cap (`artifact-too-large`; `details.maxBytes` says how much).',
3111
3639
  ),
3112
3640
  },
3113
3641
  },
@@ -3137,7 +3665,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3137
3665
  schema: { type: 'string', format: 'binary' },
3138
3666
  },
3139
3667
  ...CommonAuthErrors,
3140
- '404': ErrorResponse('No blob with that id under this tenant.'),
3668
+ '404': ErrorResponse(
3669
+ "No blob with that id under this tenant, or one in a project the caller can't read.",
3670
+ ),
3141
3671
  },
3142
3672
  },
3143
3673
  {
@@ -3190,6 +3720,8 @@ export const OPERATIONS: readonly OperationSpec[] = [
3190
3720
  responses: {
3191
3721
  '200': { description: 'Delete acknowledged.', schema: ref('DeleteArtifactResult') },
3192
3722
  ...CommonMutationErrors,
3723
+ '403': ErrorResponse('No `write` on its project (`permission-denied`).'),
3724
+ '404': ErrorResponse("An artifact in a project the caller can't read (`blob-not-found`)."),
3193
3725
  },
3194
3726
  },
3195
3727
 
@@ -3317,6 +3849,23 @@ export const OPERATIONS: readonly OperationSpec[] = [
3317
3849
  '404': ErrorResponse('No provider with that id under this tenant.'),
3318
3850
  },
3319
3851
  },
3852
+ {
3853
+ method: 'get',
3854
+ honoPath: '/v1/providers/:providerId/check',
3855
+ openapiPath: '/v1/providers/{providerId}/check',
3856
+ operationId: 'providers.check',
3857
+ summary: "Check a provider's registration",
3858
+ description:
3859
+ "Runs the provider's adapter check over its stored registration (its `adapter_config`, its metadata, whether it names a `secret_ref`): the check `POST /v1/providers` runs before it stores one. Static: no network call, no secret read. `issues` lists what would keep the runtime from building the provider, each with a JSON-pointer `path`; an adapter this runtime doesn't have is one (`/adapter_id`). `checked` is false when this runtime has no check for the provider's adapter.",
3860
+ tags: ['providers'],
3861
+ security: 'bearer',
3862
+ parameters: [ProviderIdPathParam],
3863
+ responses: {
3864
+ '200': { description: 'The check.', schema: ref('ProviderCheckResult') },
3865
+ ...CommonAuthErrors,
3866
+ '404': ErrorResponse('No provider with that id under this tenant.'),
3867
+ },
3868
+ },
3320
3869
  {
3321
3870
  method: 'post',
3322
3871
  honoPath: '/v1/providers',
@@ -3324,7 +3873,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
3324
3873
  operationId: 'providers.register',
3325
3874
  summary: 'Register a model provider',
3326
3875
  description:
3327
- '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.',
3876
+ '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. When the runtime has the adapter the body names, that adapter checks the registration first (its `adapter_config`, the metadata and the presence of `secret_ref`; static: no network, no secret read): a problem refuses it with `422 provider-config-invalid`.',
3328
3877
  tags: ['providers'],
3329
3878
  security: 'bearer',
3330
3879
  parameters: [IdempotencyKeyParam],
@@ -3334,6 +3883,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3334
3883
  ...CommonMutationErrors,
3335
3884
  '400': ErrorResponse('Validation failed (see `details.reason`).'),
3336
3885
  '409': ErrorResponse('Provider already registered at that id.'),
3886
+ '422': ErrorResponse(
3887
+ "`provider-config-invalid`: a `send_traceparent` that isn't a boolean, or the provider's adapter refuses the registration (its `adapter_config`, its metadata, or a missing `secret_ref`); `details.issues` lists each (`path`, a JSON pointer, and `message`), the registration's own fields first, as other validation errors do. Nothing is stored.",
3888
+ ),
3337
3889
  },
3338
3890
  },
3339
3891
  {
@@ -3378,6 +3930,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
3378
3930
  ),
3379
3931
  '404': ErrorResponse('`run-not-found`.'),
3380
3932
  '409': ErrorResponse('`run-not-finished`: the run has no output to judge yet.'),
3933
+ '410': ErrorResponse(
3934
+ "`run-erased`: an erasure cleared the run's content (a person's words were removed); there's nothing to judge.",
3935
+ ),
3381
3936
  },
3382
3937
  },
3383
3938
  {
@@ -4150,7 +4705,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4150
4705
  '400': ErrorResponse(
4151
4706
  "Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID).",
4152
4707
  ),
4153
- '409': ErrorResponse('Eval suite already registered at that (id, version).'),
4708
+ '409': ErrorResponse(
4709
+ "`eval-suite-already-registered`: that (id, version) is taken. Or `eval-suite-project-mismatch`: the suite's versions live in another project (a suite belongs to the project its first version was published into and never moves; the message doesn't name the project).",
4710
+ ),
4154
4711
  '404': ErrorResponse(
4155
4712
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
4156
4713
  ),
@@ -4163,7 +4720,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4163
4720
  operationId: 'evalSuites.buildFromJudgments',
4164
4721
  summary: 'Build a test set from judgments',
4165
4722
  description:
4166
- "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.",
4723
+ "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; `segments` keeps only runs started in that segment path or below it. Needs `admin` on the project.",
4167
4724
  tags: ['eval-suites'],
4168
4725
  security: 'bearer',
4169
4726
  parameters: [EvalSuiteIdPathParam, IdempotencyKeyParam],
@@ -4173,7 +4730,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
4173
4730
  ...CommonMutationErrors,
4174
4731
  '400': ErrorResponse("Malformed body; or `projectId` isn't a project id (a UUID)."),
4175
4732
  '403': ErrorResponse('`permission-denied`.'),
4176
- '409': ErrorResponse('Eval suite already registered at that (id, version).'),
4733
+ '409': ErrorResponse(
4734
+ "`eval-suite-already-registered`: that (id, version) is taken. Or `eval-suite-project-mismatch`: the suite belongs to another project than the body's `projectId` (suites never move).",
4735
+ ),
4177
4736
  '501': ErrorResponse('`test-sets-not-supported`: this deployment cannot build test sets.'),
4178
4737
  '404': ErrorResponse(
4179
4738
  "The body's `projectId` names no project of this tenant (`project-not-found`).",
@@ -4485,7 +5044,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4485
5044
  operationId: 'auth.providers.list',
4486
5045
  summary: 'List identity providers configured for the tenant',
4487
5046
  description:
4488
- 'Returns the OAuth 2.0 / OIDC providers a caller can `login` through. `clientSecretRef` is a REFERENCE — the plaintext client secret is never on the wire.',
5047
+ "Returns the tenant's identity providers (OIDC, SAML, OAuth 2.0), each with `signIn` when the deployment sets it. Secrets appear only as REFERENCES (`clientSecretRef`, `spSigningKeyRef`…); a plaintext secret is never on the wire.",
4489
5048
  tags: ['auth'],
4490
5049
  security: 'bearer',
4491
5050
  responses: {
@@ -4496,24 +5055,168 @@ export const OPERATIONS: readonly OperationSpec[] = [
4496
5055
  ...CommonAuthErrors,
4497
5056
  },
4498
5057
  },
5058
+ {
5059
+ method: 'get',
5060
+ honoPath: '/v1/auth/sign-in-options',
5061
+ openapiPath: '/v1/auth/sign-in-options',
5062
+ operationId: 'auth.signInOptions',
5063
+ summary: 'How a person can sign in',
5064
+ description:
5065
+ 'Public: nobody is signed in yet. With `email`, the ways in for that email\'s domain: the identity providers of the one tenant the domain is verified for (an unverified domain offers none), then any the deployment offers everyone it has added (`owner: deployment`, "Continue with Google"); without, an empty list: sign-in is email first, so nothing is offered before an email. `methods` says which ways in the deployment allows: identity providers, an API token (`POST /v1/auth/token-sign-in`), and an emailed sign-in link (`emailLink`, with a captcha site key when it needs one); `identityProviders` and `apiToken` both `false` when nobody can sign in to the console. Always mounted. The answer depends only on the domain: two people at the same domain get the same answer, whether or not either has an account. Rate-limited per client (`429 rate-limit-exceeded`, with `Retry-After`).',
5066
+ tags: ['auth'],
5067
+ security: 'public',
5068
+ parameters: [
5069
+ {
5070
+ name: 'email',
5071
+ in: 'query',
5072
+ required: false,
5073
+ schema: { type: 'string', minLength: 3 },
5074
+ description: 'The email the person typed; only its domain is used.',
5075
+ },
5076
+ ],
5077
+ responses: {
5078
+ '200': { description: 'The ways to sign in (possibly none).', schema: ref('SignInOptions') },
5079
+ '400': ErrorResponse('`email` is not an email address.'),
5080
+ '429': ErrorResponse('Too many lookups from this client.'),
5081
+ },
5082
+ },
5083
+ {
5084
+ method: 'post',
5085
+ honoPath: '/v1/auth/token-sign-in',
5086
+ openapiPath: '/v1/auth/token-sign-in',
5087
+ operationId: 'auth.tokenSignIn',
5088
+ summary: 'Sign in to the console with an API token',
5089
+ description:
5090
+ "The API token in `Authorization` is exchanged once for a browser session in the session cookie (HttpOnly; the same as a sign-in with an identity provider), so the browser never keeps the token. Only a person's full key opens a session: a service account's key, or a narrowed one (a `member` role, or one project), is refused `403 token-sign-in-not-allowed`. The session ends after its lifetime, or when the key expires if sooner. `403 token-sign-in-off` when the deployment doesn't allow it (always mounted, so a console gets that answer); `400 token-sign-in-needs-an-api-token` when the request is already signed in by a session.",
5091
+ tags: ['auth'],
5092
+ security: 'bearer',
5093
+ responses: {
5094
+ '200': {
5095
+ description: 'Signed in: the session cookie is set.',
5096
+ schema: ref('TokenSignInResult'),
5097
+ },
5098
+ ...CommonAuthErrors,
5099
+ '400': ErrorResponse(
5100
+ 'Signed in by a session, not an API token (`token-sign-in-needs-an-api-token`).',
5101
+ ),
5102
+ '403': ErrorResponse(
5103
+ "Not allowed here (`token-sign-in-off`), or not this key (`token-sign-in-not-allowed`): a service account's, or a narrowed one.",
5104
+ ),
5105
+ '409': ErrorResponse(
5106
+ "A retry with the Idempotency-Key of a sign-in that succeeded: its session was in the cookie, which isn't kept (`idempotency-key-replay-withheld`). Sign in again without the key.",
5107
+ ),
5108
+ },
5109
+ },
4499
5110
  {
4500
5111
  method: 'post',
4501
5112
  honoPath: '/v1/auth/providers',
4502
5113
  openapiPath: '/v1/auth/providers',
4503
5114
  operationId: 'auth.providers.register',
4504
- summary: 'Register a new OAuth/OIDC identity provider',
5115
+ summary: 'Register an identity provider (OIDC, SAML or OAuth 2.0)',
4505
5116
  description:
4506
- 'Unique per tenant on `providerId`: re-registering a known provider returns `409 identity-provider-already-registered` — unregister it first, then register again.',
5117
+ 'Unique per tenant on `providerId`: re-registering a known provider returns `409 identity-provider-already-registered`; change it with `PATCH /v1/auth/providers/{providerId}`, which keeps its sign-in URLs. Secrets are given by reference (`clientSecretRef`, `spSigningKeyRef`…); a `clientSecret` (or a raw key) is refused with `400 invalid-provider-config`. The deployment may check the configuration (OIDC discovery, SAML metadata): `422 identity-provider-invalid` says what failed. The answer carries the stored provider when the deployment returns it, with `signIn`: what to give the identity provider.',
4507
5118
  tags: ['auth'],
4508
5119
  security: 'bearer',
4509
5120
  parameters: [IdempotencyKeyParam],
4510
- requestBody: { required: true, schema: ref('IdentityProviderConfig') },
5121
+ requestBody: { required: true, schema: ref('RegisterIdentityProviderBody') },
4511
5122
  responses: {
4512
5123
  '201': {
4513
5124
  description: 'Provider registered.',
4514
5125
  schema: ref('RegisterIdentityProviderResult'),
4515
5126
  },
4516
5127
  ...CommonMutationErrors,
5128
+ '422': ErrorResponse(
5129
+ 'The deployment could not use the configuration (`identity-provider-invalid`).',
5130
+ ),
5131
+ },
5132
+ },
5133
+ {
5134
+ method: 'get',
5135
+ honoPath: '/v1/auth/providers/:providerId',
5136
+ openapiPath: '/v1/auth/providers/{providerId}',
5137
+ operationId: 'auth.providers.get',
5138
+ summary: 'Get one identity provider',
5139
+ description:
5140
+ 'The provider as stored, with `signIn` when the deployment sets it. Secrets appear only as references.',
5141
+ tags: ['auth'],
5142
+ security: 'bearer',
5143
+ parameters: [
5144
+ {
5145
+ name: 'providerId',
5146
+ in: 'path',
5147
+ required: true,
5148
+ schema: { type: 'string', minLength: 1 },
5149
+ },
5150
+ ],
5151
+ responses: {
5152
+ '200': { description: 'The provider.', schema: ref('GetIdentityProviderResult') },
5153
+ ...CommonAuthErrors,
5154
+ '404': ErrorResponse('No identity provider registered with that id under this tenant.'),
5155
+ },
5156
+ },
5157
+ {
5158
+ method: 'get',
5159
+ honoPath: '/v1/auth/providers/:providerId/sign-in',
5160
+ openapiPath: '/v1/auth/providers/{providerId}/sign-in',
5161
+ operationId: 'auth.providers.signIn',
5162
+ summary: 'What to give the identity provider, before or after registering',
5163
+ description:
5164
+ "The redirect URI (OIDC) or the ACS URL, entity ID and metadata URL (SAML) a provider under this `providerId` gets: the same before it's registered, after, and after an unregister and a new registration. So an admin sets up the identity provider's side first, then registers with what it gives back. `kind` is required until the provider is registered. Not mounted when the deployment can't say.",
5165
+ tags: ['auth'],
5166
+ security: 'bearer',
5167
+ parameters: [
5168
+ {
5169
+ name: 'providerId',
5170
+ in: 'path',
5171
+ required: true,
5172
+ schema: { type: 'string', minLength: 1 },
5173
+ },
5174
+ {
5175
+ name: 'kind',
5176
+ in: 'query',
5177
+ required: false,
5178
+ schema: { $ref: '#/components/schemas/IdentityProviderKind' },
5179
+ description: "The provider's kind; default: the registered provider's.",
5180
+ },
5181
+ ],
5182
+ responses: {
5183
+ '200': {
5184
+ description: 'What to give the identity provider.',
5185
+ schema: ref('IdentityProviderSignInUrls'),
5186
+ },
5187
+ ...CommonAuthErrors,
5188
+ '400': ErrorResponse(
5189
+ "`kind` missing for a provider that isn't registered, or a kind this deployment doesn't sign in with (`bad-input`).",
5190
+ ),
5191
+ },
5192
+ },
5193
+ {
5194
+ method: 'patch',
5195
+ honoPath: '/v1/auth/providers/:providerId',
5196
+ openapiPath: '/v1/auth/providers/{providerId}',
5197
+ operationId: 'auth.providers.update',
5198
+ summary: 'Change an identity provider, keeping its sign-in URLs',
5199
+ description:
5200
+ "Merges the changes into the stored provider and checks the result as a registration is (`400 invalid-provider-config`; `422 identity-provider-invalid` when the deployment can't use it). The provider keeps its `signIn`, so nothing changes on the identity provider's side. Not mounted when the deployment can't update providers.",
5201
+ tags: ['auth'],
5202
+ security: 'bearer',
5203
+ parameters: [
5204
+ {
5205
+ name: 'providerId',
5206
+ in: 'path',
5207
+ required: true,
5208
+ schema: { type: 'string', minLength: 1 },
5209
+ },
5210
+ IdempotencyKeyParam,
5211
+ ],
5212
+ requestBody: { required: true, schema: ref('UpdateIdentityProviderBody') },
5213
+ responses: {
5214
+ '200': { description: 'Updated.', schema: ref('UpdateIdentityProviderResult') },
5215
+ ...CommonMutationErrors,
5216
+ '404': ErrorResponse('No identity provider registered with that id under this tenant.'),
5217
+ '422': ErrorResponse(
5218
+ 'The deployment could not use the configuration (`identity-provider-invalid`).',
5219
+ ),
4517
5220
  },
4518
5221
  },
4519
5222
  {
@@ -4603,14 +5306,19 @@ export const OPERATIONS: readonly OperationSpec[] = [
4603
5306
  operationId: 'auth.refresh',
4604
5307
  summary: 'Refresh the current session token',
4605
5308
  description:
4606
- 'Requires a session token (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`. When the deployment wired a `refreshToken` callback and the provider issued a refresh token, provider tokens rotate too; otherwise only the framework session token rotates. OAuth 2.1 BCP refresh-token rotation: the OLD session token is invalidated (marked rotated) — reusing it after refresh returns `401 refresh-token-invalid` so compliant clients can retry with the fresh token instead of prompting a re-auth.',
5309
+ 'Requires a session token (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`. When the deployment wired a `refreshToken` callback and the provider issued a refresh token, provider tokens rotate too; otherwise only the framework session token rotates. OAuth 2.1 BCP refresh-token rotation: the OLD session token is invalidated (marked rotated) — reusing it after refresh returns `401 refresh-token-invalid` so compliant clients can retry with the fresh token instead of prompting a re-auth. A browser session (the session cookie) is not refreshed: `400 cookie-session-not-refreshable`, so a new token never reaches page scripts; it ends at its TTL.',
4607
5310
  tags: ['auth'],
4608
5311
  security: 'bearer',
4609
5312
  parameters: [IdempotencyKeyParam],
4610
5313
  responses: {
4611
5314
  '200': { description: 'New session token.', schema: ref('RefreshResult') },
4612
5315
  ...CommonMutationErrors,
4613
- '400': ErrorResponse('Caller presented a bearer token; refresh is session-only.'),
5316
+ '409': ErrorResponse(
5317
+ "Idempotency-Key reused with a different body (`idempotency-key-body-mismatch`), or a retry of a request that succeeded: its answer carried a secret, which isn't kept (`idempotency-key-replay-withheld`, with its `status` and `at`).",
5318
+ ),
5319
+ '400': ErrorResponse(
5320
+ 'Caller presented a bearer token (refresh is session-only), or a browser session (cookie).',
5321
+ ),
4614
5322
  '404': ErrorResponse('Session no longer exists.'),
4615
5323
  '422': ErrorResponse('Refresh with the provider failed.'),
4616
5324
  },
@@ -4622,7 +5330,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4622
5330
  operationId: 'auth.logout',
4623
5331
  summary: 'Revoke the current session',
4624
5332
  description:
4625
- 'Requires a session token (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`. Idempotent — revoking an already-revoked session returns `{ revoked: false }`.',
5333
+ 'Requires a session token (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`. Idempotent — revoking an already-revoked session returns `{ revoked: false }`. A browser session (the session cookie) also gets its cookie cleared (`Set-Cookie` with `Max-Age=0`).',
4626
5334
  tags: ['auth'],
4627
5335
  security: 'bearer',
4628
5336
  parameters: [IdempotencyKeyParam],
@@ -4640,7 +5348,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4640
5348
  operationId: 'identity.users.list',
4641
5349
  summary: 'List users in the tenant',
4642
5350
  description:
4643
- 'Cursor-paginated list of tenant users (sort order is binding-defined). Optional `?query=` is a prefix match on `displayName` — the natural filter shape for a "search users" surface. `primaryEmail` may be redacted per tenant policy.',
5351
+ 'Cursor-paginated list of tenant users (sort order is binding-defined), for tenant admins only. Optional `?query=` is a prefix match on `displayName` — the natural filter shape for a "search users" surface. `primaryEmail` may be redacted per tenant policy. Anyone else adds a person to a project by their email or id (`POST /v1/projects/{projectId}/memberships`).',
4644
5352
  tags: ['identity'],
4645
5353
  security: 'bearer',
4646
5354
  parameters: [
@@ -4653,10 +5361,39 @@ export const OPERATIONS: readonly OperationSpec[] = [
4653
5361
  description: 'Prefix match on `displayName`.',
4654
5362
  schema: { type: 'string' },
4655
5363
  },
5364
+ {
5365
+ name: 'includeUnregistered',
5366
+ in: 'query',
5367
+ required: false,
5368
+ description: 'With `true`, people who were removed (`unregisteredAt`) too.',
5369
+ schema: { type: 'boolean' },
5370
+ },
4656
5371
  ],
4657
5372
  responses: {
4658
5373
  '200': { description: 'Page of users.', schema: ref('UserCollectionPage') },
4659
5374
  ...CommonAuthErrors,
5375
+ '403': ErrorResponse('Not a tenant admin.'),
5376
+ },
5377
+ },
5378
+ {
5379
+ method: 'post',
5380
+ honoPath: '/v1/identity/users',
5381
+ openapiPath: '/v1/identity/users',
5382
+ operationId: 'identity.users.create',
5383
+ summary: 'Add a person',
5384
+ description:
5385
+ "Adds a person to the tenant as a tenant member, written before it answers: they can read the tenant's settings (providers, policies, adapters, signing keys, deployments), not its projects. Give them a role to work (project or team membership, or tenant admin), then mint their first API key at `POST /v1/tokens` with `for`. Tenant admins only. Mounted when the identity directory can add people.",
5386
+ tags: ['identity'],
5387
+ security: 'bearer',
5388
+ parameters: [IdempotencyKeyParam],
5389
+ requestBody: { required: true, schema: ref('CreateUserBody') },
5390
+ responses: {
5391
+ '201': { description: 'The new person.', schema: ref('UserRecord') },
5392
+ ...CommonMutationErrors,
5393
+ '403': ErrorResponse('Not a tenant admin.'),
5394
+ '409': ErrorResponse(
5395
+ 'Another person of the tenant has the email (`identity-user-email-taken`), or an idempotency conflict.',
5396
+ ),
4660
5397
  },
4661
5398
  },
4662
5399
  {
@@ -4665,6 +5402,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4665
5402
  openapiPath: '/v1/identity/users/{userId}',
4666
5403
  operationId: 'identity.users.get',
4667
5404
  summary: 'Get a user by id',
5405
+ description: 'A tenant admin, or the person themselves.',
4668
5406
  tags: ['identity'],
4669
5407
  security: 'bearer',
4670
5408
  parameters: [
@@ -4673,6 +5411,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4673
5411
  responses: {
4674
5412
  '200': { description: 'User record.', schema: ref('UserRecord') },
4675
5413
  ...CommonAuthErrors,
5414
+ '403': ErrorResponse("Someone else's record, and not a tenant admin."),
4676
5415
  '404': ErrorResponse('No user with that id under this tenant.'),
4677
5416
  },
4678
5417
  },
@@ -4683,7 +5422,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4683
5422
  operationId: 'identity.users.listSessions',
4684
5423
  summary: 'List active sessions for a user',
4685
5424
  description:
4686
- 'Returns the wire-safe `IdentitySessionSummary` shape — provider access-token + refresh-token never cross the wire, even to admins. Unknown user id returns an empty list (call `GET /v1/identity/users/:userId` first to distinguish "no sessions" from "no user").',
5425
+ 'A tenant admin, or the person themselves. Returns the wire-safe `IdentitySessionSummary` shape — provider access-token + refresh-token never cross the wire, even to admins. Unknown user id returns an empty list (call `GET /v1/identity/users/:userId` first to distinguish "no sessions" from "no user").',
4687
5426
  tags: ['identity'],
4688
5427
  security: 'bearer',
4689
5428
  parameters: [
@@ -4692,6 +5431,34 @@ export const OPERATIONS: readonly OperationSpec[] = [
4692
5431
  responses: {
4693
5432
  '200': { description: 'Page of sessions.', schema: ref('IdentitySessionCollectionPage') },
4694
5433
  ...CommonAuthErrors,
5434
+ '403': ErrorResponse("Someone else's sessions, and not a tenant admin."),
5435
+ },
5436
+ },
5437
+ {
5438
+ method: 'post',
5439
+ honoPath: '/v1/identity/users/:userId/unregister',
5440
+ openapiPath: '/v1/identity/users/{userId}/unregister',
5441
+ operationId: 'identity.users.unregister',
5442
+ summary: 'Remove a person',
5443
+ description:
5444
+ "Removes a person from the tenant, in one step: they're marked removed (`unregisteredAt`; their record stays, so their history still says who they were), every API key and session of theirs is revoked, and every grant and membership they hold is taken away, all before it answers. Their keys get `401` at once. Their email is free again: adding it makes a new person. Removing someone already removed changes nothing. Refused for yourself and the deployment's seed user (`identity-user-unregister-refused`), and for the only tenant admin (`last-tenant-admin`). Tenant admins only. Mounted when the identity directory can remove people.",
5445
+ tags: ['identity'],
5446
+ security: 'bearer',
5447
+ parameters: [
5448
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
5449
+ IdempotencyKeyParam,
5450
+ ],
5451
+ responses: {
5452
+ '200': {
5453
+ description: 'The removed person, and what removing them took away.',
5454
+ schema: ref('UnregisterUserResult'),
5455
+ },
5456
+ ...CommonMutationErrors,
5457
+ '403': ErrorResponse('Not a tenant admin.'),
5458
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
5459
+ '409': ErrorResponse(
5460
+ 'Yourself or the seed user (`identity-user-unregister-refused`, `details.reason`), the only tenant admin (`last-tenant-admin`), or an idempotency conflict.',
5461
+ ),
4695
5462
  },
4696
5463
  },
4697
5464
  {
@@ -4701,7 +5468,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4701
5468
  operationId: 'identity.users.revokeSessions',
4702
5469
  summary: 'Revoke every active session for a user',
4703
5470
  description:
4704
- 'Admin op — idempotent. Under the hood, deployments typically delegate to `SessionStoreBinding.revokeAllForUser`. Returns `{ revokedCount: 0 }` when the user was already fully signed out.',
5471
+ "A tenant admin revokes anyone's sessions; anyone else only their own. Idempotent. Under the hood, deployments typically delegate to `SessionStoreBinding.revokeAllForUser`. Returns `{ revokedCount: 0 }` when the user was already fully signed out.",
4705
5472
  tags: ['identity'],
4706
5473
  security: 'bearer',
4707
5474
  parameters: [
@@ -4711,9 +5478,83 @@ export const OPERATIONS: readonly OperationSpec[] = [
4711
5478
  responses: {
4712
5479
  '200': { description: 'Revocation outcome.', schema: ref('RevokeSessionsResult') },
4713
5480
  ...CommonMutationErrors,
5481
+ '403': ErrorResponse("Another person's sessions, and not a tenant admin."),
4714
5482
  '500': ErrorResponse('Session revocation failed inside the caller-plugged binding.'),
4715
5483
  },
4716
5484
  },
5485
+ {
5486
+ method: 'get',
5487
+ honoPath: '/v1/identity/users/:userId/grants',
5488
+ openapiPath: '/v1/identity/users/{userId}/grants',
5489
+ operationId: 'identity.users.grants',
5490
+ summary: "Read a person's grants",
5491
+ description:
5492
+ "What the person may do, as granted directly: tenant admin, project and team roles, the reviewer roster. A tenant admin reads anyone's; anyone else only their own.",
5493
+ tags: ['identity'],
5494
+ security: 'bearer',
5495
+ parameters: [
5496
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
5497
+ ],
5498
+ responses: {
5499
+ '200': { description: "The person's grants.", schema: ref('PersonGrants') },
5500
+ ...CommonAuthErrors,
5501
+ '403': ErrorResponse("Another person's grants, and not a tenant admin."),
5502
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
5503
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
5504
+ },
5505
+ },
5506
+ {
5507
+ method: 'post',
5508
+ honoPath: '/v1/identity/users/:userId/grant',
5509
+ openapiPath: '/v1/identity/users/{userId}/grant',
5510
+ operationId: 'identity.users.grant',
5511
+ summary: 'Make a person a tenant admin',
5512
+ description:
5513
+ "Written before the call answers, so the person's next request holds it. A no-op when held. Tenant admins only.",
5514
+ tags: ['identity'],
5515
+ security: 'bearer',
5516
+ parameters: [
5517
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
5518
+ IdempotencyKeyParam,
5519
+ ],
5520
+ requestBody: { required: true, schema: ref('PersonGrantBody') },
5521
+ responses: {
5522
+ '200': { description: "The person's grants, after.", schema: ref('PersonGrants') },
5523
+ ...CommonMutationErrors,
5524
+ '403': ErrorResponse('Not a tenant admin.'),
5525
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
5526
+ '409': ErrorResponse(
5527
+ 'The person was removed from the tenant (`identity-user-unregistered`), or an idempotency conflict.',
5528
+ ),
5529
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
5530
+ },
5531
+ },
5532
+ {
5533
+ method: 'post',
5534
+ honoPath: '/v1/identity/users/:userId/ungrant',
5535
+ openapiPath: '/v1/identity/users/{userId}/ungrant',
5536
+ operationId: 'identity.users.ungrant',
5537
+ summary: 'Remove tenant admin from a person',
5538
+ description:
5539
+ 'A no-op when not held. Refused for the only person who is a tenant admin (`last-tenant-admin`: make someone else one first), and for the seed user, whom the runtime makes tenant admin at every boot (`seed-user-admin`: unset `KINDGI_SEED_USER_ID` and restart it first). Tenant admins only.',
5540
+ tags: ['identity'],
5541
+ security: 'bearer',
5542
+ parameters: [
5543
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
5544
+ IdempotencyKeyParam,
5545
+ ],
5546
+ requestBody: { required: true, schema: ref('PersonGrantBody') },
5547
+ responses: {
5548
+ '200': { description: "The person's grants, after.", schema: ref('PersonGrants') },
5549
+ ...CommonMutationErrors,
5550
+ '403': ErrorResponse('Not a tenant admin.'),
5551
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
5552
+ '409': ErrorResponse(
5553
+ 'The only person who is a tenant admin (`last-tenant-admin`), the seed user (`seed-user-admin`), or an idempotency conflict.',
5554
+ ),
5555
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
5556
+ },
5557
+ },
4717
5558
  {
4718
5559
  method: 'get',
4719
5560
  honoPath: '/v1/identity/whoami',
@@ -4751,7 +5592,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
4751
5592
  },
4752
5593
  ...CommonMutationErrors,
4753
5594
  '409': ErrorResponse(
4754
- "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.",
5595
+ "Idempotency-Key was reused with a different body, or resource-state conflict. Or `tool-project-mismatch`, `agent-project-mismatch` or `flow-project-mismatch`: one of the image's tools, agents or flows belongs to another project (`details.primitive`, `details.id`; the message doesn't name the project); nothing was deployed, even when it was unchanged. 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.",
4755
5596
  ),
4756
5597
  '400': ErrorResponse(
4757
5598
  'Signature invalid, image unverifiable, or deployment-validation-failed with per-primitive `details[]`.',
@@ -4951,11 +5792,11 @@ export const OPERATIONS: readonly OperationSpec[] = [
4951
5792
  operationId: 'compliance.evidence.export',
4952
5793
  summary: 'Export a signed compliance-evidence bundle',
4953
5794
  description:
4954
- "Canonicalizes the filtered records as sorted-key JSON and signs with the deployment's Ed25519 key looked up by `signingKeyId`. Verification is a pure client-side operation: `verifyEd25519(publicKey, bundleBytes, signature)`. Envelope shape matches `ExportProvenanceResult` + audit-bundle — verifiers reuse one wrapper across all three surfaces. Deployments without a `signingKey` binding mounted return `404 signing-not-configured`.",
5795
+ "Signs the evidence the filter matches (exportable kinds only) with the deployment's export key, and records the export (an `export-signed` audit event). The same envelope as the other signed exports; check `publicKey` against `GET /v1/export-signing-keys`. A deployment with no export key answers `404 signing-not-configured`.",
4955
5796
  tags: ['compliance'],
4956
5797
  security: 'bearer',
4957
5798
  parameters: [IdempotencyKeyParam],
4958
- requestBody: { required: true, schema: ref('ExportComplianceEvidenceBody') },
5799
+ requestBody: { required: false, schema: ref('ExportComplianceEvidenceBody') },
4959
5800
  responses: {
4960
5801
  '200': {
4961
5802
  description: 'Signed evidence bundle.',
@@ -5476,12 +6317,13 @@ export const OPERATIONS: readonly OperationSpec[] = [
5476
6317
  operationId: 'projects.getDefault',
5477
6318
  summary: "Fetch the tenant's Default project",
5478
6319
  description:
5479
- 'Returns the row where `Project.isDefault = true` (exactly one per tenant). Returns 404 `project-not-found` when no Default has been provisioned.',
6320
+ 'Returns the row where `Project.isDefault = true` (exactly one per tenant), to a caller who can read it, as `GET /v1/projects/{projectId}` checks. Returns 404 `project-not-found` when no Default has been provisioned.',
5480
6321
  tags: ['projects'],
5481
6322
  security: 'bearer',
5482
6323
  responses: {
5483
6324
  '200': { description: 'Default project.', schema: ref('Project') },
5484
6325
  ...CommonAuthErrors,
6326
+ '403': ErrorResponse("The caller can't read the Default project."),
5485
6327
  '404': ErrorResponse('Tenant has no Default project.'),
5486
6328
  },
5487
6329
  },
@@ -5651,7 +6493,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
5651
6493
  operationId: 'projects.memberships.add',
5652
6494
  summary: 'Add a user directly to a project',
5653
6495
  description:
5654
- 'Idempotent on `(projectId, userId)` — re-adding an existing member with a different role does NOT overwrite; use PATCH for role changes.',
6496
+ 'Names the person by exactly one of `userId` and `email` (matched as the runtime matches emails when it adds a person); someone who is not a person of this tenant, or was removed from it, is refused with 404 `identity-user-not-found`. Idempotent on `(projectId, userId)` — re-adding an existing member with a different role does NOT overwrite; use PATCH for role changes.',
5655
6497
  tags: ['projects'],
5656
6498
  security: 'bearer',
5657
6499
  parameters: [
@@ -5671,7 +6513,9 @@ export const OPERATIONS: readonly OperationSpec[] = [
5671
6513
  schema: ref('AddProjectMembershipResult'),
5672
6514
  },
5673
6515
  ...CommonMutationErrors,
5674
- '404': ErrorResponse('No project with that id under this tenant.'),
6516
+ '404': ErrorResponse(
6517
+ 'No project with that id under this tenant (`project-not-found`), or the person named is not a member of this tenant (`identity-user-not-found`).',
6518
+ ),
5675
6519
  },
5676
6520
  },
5677
6521
  {
@@ -5876,7 +6720,7 @@ export const OPERATIONS: readonly OperationSpec[] = [
5876
6720
  operationId: 'env.put',
5877
6721
  summary: 'Upsert an env entry',
5878
6722
  description:
5879
- 'Requires the `env:write` capability. Every write bumps `revision`; optional `ifRevision` guards against concurrent updates (409 `env-write-conflict`).',
6723
+ "Requires the `env:write` capability. Every write bumps `revision`; optional `ifRevision` guards against concurrent updates (409 `env-write-conflict`). Env values aren't secret: they're recorded with each run that uses them. A credential goes in `/v1/secrets`.",
5880
6724
  tags: ['env'],
5881
6725
  security: 'bearer',
5882
6726
  parameters: [
@@ -6229,7 +7073,16 @@ export const OPERATIONS: readonly OperationSpec[] = [
6229
7073
  summary: 'Fetch a cron schedule',
6230
7074
  tags: ['schedules'],
6231
7075
  security: 'bearer',
6232
- parameters: [TriggerIdPathParam],
7076
+ parameters: [
7077
+ TriggerIdPathParam,
7078
+ {
7079
+ name: 'upcoming',
7080
+ in: 'query',
7081
+ required: false,
7082
+ description: 'Include the next N occurrences (`upcoming`), 1 to 20.',
7083
+ schema: { type: 'integer', minimum: 1, maximum: 20 },
7084
+ },
7085
+ ],
6233
7086
  responses: {
6234
7087
  '200': { description: 'Schedule record.', schema: ref('ScheduleRecord') },
6235
7088
  ...CommonAuthErrors,
@@ -6304,6 +7157,67 @@ export const OPERATIONS: readonly OperationSpec[] = [
6304
7157
  ...CommonMutationErrors,
6305
7158
  },
6306
7159
  },
7160
+ {
7161
+ method: 'get',
7162
+ honoPath: '/v1/schedules/:triggerId/fires',
7163
+ openapiPath: '/v1/schedules/{triggerId}/fires',
7164
+ operationId: 'schedules.fires',
7165
+ summary: "A schedule's fire history",
7166
+ description:
7167
+ 'Newest first: each occurrence (and `run-now`) the schedule fired for, and what came of it: the run it started, or why it was skipped, refused or failed.',
7168
+ tags: ['schedules'],
7169
+ security: 'bearer',
7170
+ parameters: [TriggerIdPathParam, LimitQueryParam, CursorQueryParam],
7171
+ responses: {
7172
+ '200': { description: 'Page of fires.', schema: ref('ScheduleFirePage') },
7173
+ ...CommonAuthErrors,
7174
+ '404': ErrorResponse('No cron trigger with that id.'),
7175
+ '501': ErrorResponse(
7176
+ '`trigger-operation-unsupported`: this deployment keeps no fire history.',
7177
+ ),
7178
+ },
7179
+ },
7180
+ {
7181
+ method: 'post',
7182
+ honoPath: '/v1/schedules/:triggerId/run-now',
7183
+ openapiPath: '/v1/schedules/{triggerId}/run-now',
7184
+ operationId: 'schedules.runNow',
7185
+ summary: 'Run a schedule now',
7186
+ description:
7187
+ "One fire outside the schedule (`manual: true` in its history), starting one run as the schedule's owner. The schedule's next occurrence is unchanged.",
7188
+ tags: ['schedules'],
7189
+ security: 'bearer',
7190
+ parameters: [TriggerIdPathParam, IdempotencyKeyParam],
7191
+ responses: {
7192
+ '202': {
7193
+ description: 'The fire; its run starts in the background.',
7194
+ schema: ref('ScheduleFire'),
7195
+ },
7196
+ ...CommonMutationErrors,
7197
+ '404': ErrorResponse('No cron trigger with that id.'),
7198
+ '501': ErrorResponse('`trigger-operation-unsupported`: this deployment has no run-now.'),
7199
+ },
7200
+ },
7201
+ {
7202
+ method: 'post',
7203
+ honoPath: '/v1/schedules/:triggerId/owner',
7204
+ openapiPath: '/v1/schedules/{triggerId}/owner',
7205
+ operationId: 'schedules.takeOwnership',
7206
+ summary: 'Take over a schedule',
7207
+ description:
7208
+ "The caller becomes the schedule's owner, so its runs act as the caller from the next fire. Needs `admin` on the schedule's project and `execute` on what it runs. For a schedule whose owner left or lost access.",
7209
+ tags: ['schedules'],
7210
+ security: 'bearer',
7211
+ parameters: [TriggerIdPathParam, IdempotencyKeyParam],
7212
+ responses: {
7213
+ '200': { description: 'The schedule, with its new owner.', schema: ref('ScheduleRecord') },
7214
+ ...CommonMutationErrors,
7215
+ '404': ErrorResponse('No cron trigger with that id.'),
7216
+ '501': ErrorResponse(
7217
+ "`trigger-operation-unsupported`: this deployment can't change a schedule's owner.",
7218
+ ),
7219
+ },
7220
+ },
6307
7221
 
6308
7222
  // ---------- event-triggers (trigger surface) ----------
6309
7223
  {
@@ -6556,12 +7470,15 @@ export const OPERATIONS: readonly OperationSpec[] = [
6556
7470
  operationId: 'webhookEndpoints.generateSecret',
6557
7471
  summary: 'Generate a webhook signing secret',
6558
7472
  description:
6559
- 'Returns a new strong secret (`whsec_` + base64 of 32 random bytes). Nothing is stored: put it in your secrets, then register the endpoint with its name.',
7473
+ 'Returns a new strong secret (`whsec_` + base64 of 32 random bytes). Nothing is stored: put it in your secrets, then register the endpoint with its name. Sent with an `Idempotency-Key`, a retry gets `409 idempotency-key-replay-withheld`, not the secret again.',
6560
7474
  tags: ['webhook-endpoints'],
6561
7475
  security: 'bearer',
6562
7476
  responses: {
6563
7477
  '200': { description: 'A new secret.', schema: ref('GeneratedWebhookSecret') },
6564
7478
  ...CommonAuthErrors,
7479
+ '409': ErrorResponse(
7480
+ "A retry with the Idempotency-Key of a request that succeeded: the secret isn't kept (`idempotency-key-replay-withheld`).",
7481
+ ),
6565
7482
  },
6566
7483
  },
6567
7484
  {