@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
@@ -25,7 +25,7 @@ const IdempotencyKeyParam = {
25
25
  name: 'Idempotency-Key',
26
26
  in: 'header',
27
27
  required: false,
28
- description: 'Caller-supplied idempotency key. Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1).',
28
+ description: "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).",
29
29
  schema: { type: 'string', minLength: 1 },
30
30
  };
31
31
  const RunIdPathParam = {
@@ -42,6 +42,13 @@ const TokenIdPathParam = {
42
42
  description: 'ApiTokenId — opaque branded string (a UUID).',
43
43
  schema: { type: 'string', format: 'uuid' },
44
44
  };
45
+ const ServiceAccountIdPathParam = {
46
+ name: 'serviceAccountId',
47
+ in: 'path',
48
+ required: true,
49
+ description: 'The service account id.',
50
+ schema: { type: 'string' },
51
+ };
45
52
  const SigningKeyIdPathParam = {
46
53
  name: 'keyId',
47
54
  in: 'path',
@@ -105,6 +112,13 @@ const RunEvalRunIdQueryParam = {
105
112
  description: 'Only the replay runs of this eval run. Implies replays are included; cannot be combined with `replays=exclude`.',
106
113
  schema: { type: 'string', minLength: 1 },
107
114
  };
115
+ const RunTriggerIdQueryParam = {
116
+ name: 'triggerId',
117
+ in: 'query',
118
+ required: false,
119
+ description: 'Only the runs this trigger started (`Run.trigger.triggerId`).',
120
+ schema: { type: 'string', format: 'uuid' },
121
+ };
108
122
  const LiveProjectQueryParam = {
109
123
  name: 'projectId',
110
124
  in: 'query',
@@ -355,11 +369,18 @@ const ObservationUntilQueryParam = {
355
369
  description: 'Only the observations at or before this time (ISO 8601).',
356
370
  schema: { type: 'string', format: 'date-time' },
357
371
  };
372
+ const ErasureIdPathParam = {
373
+ name: 'erasureId',
374
+ in: 'path',
375
+ required: true,
376
+ description: 'The erasure (a UUID).',
377
+ schema: { type: 'string', format: 'uuid' },
378
+ };
358
379
  const FactIdPathParam = {
359
380
  name: 'factId',
360
381
  in: 'path',
361
382
  required: true,
362
- description: 'FactId — opaque branded string.',
383
+ description: 'The fact id (kept across revisions).',
363
384
  schema: { type: 'string' },
364
385
  };
365
386
  const FactTypeQueryParam = {
@@ -376,6 +397,27 @@ const FactScopeQueryParam = {
376
397
  description: 'JSON-encoded partial scope object. Every provided key must match. Example: `%7B%22projectId%22%3A%22...%22%7D`.',
377
398
  schema: { type: 'string' },
378
399
  };
400
+ const FactAsOfQueryParam = {
401
+ name: 'asOf',
402
+ in: 'query',
403
+ required: false,
404
+ description: 'Read memory as it stood at this time (ISO 8601): the revision that was current then, including one since superseded or deleted.',
405
+ schema: { type: 'string', format: 'date-time' },
406
+ };
407
+ const FactVersionQueryParam = {
408
+ name: 'version',
409
+ in: 'query',
410
+ required: false,
411
+ description: 'A revision number: that revision, current or not.',
412
+ schema: { type: 'integer', minimum: 1 },
413
+ };
414
+ const FactExpectVersionQueryParam = {
415
+ name: 'expectVersion',
416
+ in: 'query',
417
+ required: false,
418
+ description: 'Only if the current revision is still this one; otherwise `409 fact-changed` with `currentVersion`.',
419
+ schema: { type: 'integer', minimum: 1 },
420
+ };
379
421
  // ---------------- scope triplet ----------------
380
422
  //
381
423
  // Shared parameters for the `?scopeKind + ?scopeId + ?inherit` filter.
@@ -404,18 +446,18 @@ const InheritQueryParam = {
404
446
  description: 'Default `true`. `false` = literal-at-this-scope only (admin/audit view). Load-bearing for policy/config-scoped resources (mcp-endpoints); documented no-op for content-scoped resources (agents/flows/tools/...).',
405
447
  schema: { type: 'boolean', default: true },
406
448
  };
407
- const SupervisorIdHeaderParam = {
408
- name: 'X-Supervisor-Id',
409
- in: 'header',
449
+ const ImprovementPassIdPathParam = {
450
+ name: 'passId',
451
+ in: 'path',
410
452
  required: true,
411
- description: '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.',
412
- schema: { type: 'string', minLength: 1 },
453
+ description: 'The improvement pass id (a UUID).',
454
+ schema: { type: 'string', format: 'uuid' },
413
455
  };
414
456
  const ProposalIdPathParam = {
415
457
  name: 'proposalId',
416
458
  in: 'path',
417
459
  required: true,
418
- description: 'FixProposalId — opaque branded string (a UUID).',
460
+ description: 'The proposal id (a UUID).',
419
461
  schema: { type: 'string', format: 'uuid' },
420
462
  };
421
463
  const ProvenanceRunIdQueryParam = {
@@ -443,14 +485,14 @@ const ProposalStatusQueryParam = {
443
485
  name: 'status',
444
486
  in: 'query',
445
487
  required: false,
446
- description: 'Filter by proposal status.',
488
+ description: 'Only proposals with this (derived) status.',
447
489
  schema: { $ref: '#/components/schemas/FixProposalStatus' },
448
490
  };
449
491
  const ProposalTierQueryParam = {
450
492
  name: 'tier',
451
493
  in: 'query',
452
494
  required: false,
453
- description: 'Filter by artifact tier.',
495
+ description: 'Only proposals of this tier.',
454
496
  schema: { $ref: '#/components/schemas/ProposalTier' },
455
497
  };
456
498
  // Admin plane — capabilities + providers.
@@ -995,6 +1037,7 @@ export const OPERATIONS = [
995
1037
  ...CommonMutationErrors,
996
1038
  '404': ErrorResponse('Agent or flow not found; or `projectId` names no project of this tenant (`project-not-found`).'),
997
1039
  '422': ErrorResponse('Guardrail violation or budget exceeded.'),
1040
+ '409': ErrorResponse("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."),
998
1041
  '400': ErrorResponse("Malformed request body, or the body's `projectId` isn't a project id (a UUID)."),
999
1042
  },
1000
1043
  },
@@ -1017,6 +1060,7 @@ export const OPERATIONS = [
1017
1060
  RunAgentIdQueryParam,
1018
1061
  RunReplaysQueryParam,
1019
1062
  RunEvalRunIdQueryParam,
1063
+ RunTriggerIdQueryParam,
1020
1064
  RunIncludeQueryParam,
1021
1065
  ],
1022
1066
  responses: {
@@ -1242,7 +1286,7 @@ export const OPERATIONS = [
1242
1286
  openapiPath: '/v1/tokens',
1243
1287
  operationId: 'tokens.mint',
1244
1288
  summary: 'Mint an API key',
1245
- description: '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`.',
1289
+ description: "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`.",
1246
1290
  tags: ['tokens'],
1247
1291
  security: 'bearer',
1248
1292
  parameters: [IdempotencyKeyParam],
@@ -1250,9 +1294,10 @@ export const OPERATIONS = [
1250
1294
  responses: {
1251
1295
  '201': { description: 'Token minted.', schema: ref('MintTokenResult') },
1252
1296
  ...CommonMutationErrors,
1253
- '403': ErrorResponse('Not a tenant admin, or a capability the caller does not hold.'),
1297
+ '409': ErrorResponse("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`)."),
1298
+ '403': ErrorResponse("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`)."),
1254
1299
  '400': ErrorResponse("Malformed request body, or the body's `projectId` isn't a project id (a UUID)."),
1255
- '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
1300
+ '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`), or `for` names no person or service account (`principal-not-found`)."),
1256
1301
  },
1257
1302
  },
1258
1303
  {
@@ -1261,15 +1306,25 @@ export const OPERATIONS = [
1261
1306
  openapiPath: '/v1/tokens',
1262
1307
  operationId: 'tokens.list',
1263
1308
  summary: 'List API keys',
1264
- description: 'Newest first. Never returns secrets. Tenant admins only.',
1309
+ description: "Newest first. Never returns secrets. A tenant admin sees every key (`?principal=` for one principal's); anyone else sees their own.",
1265
1310
  tags: ['tokens'],
1266
1311
  security: 'bearer',
1267
- parameters: [CursorQueryParam, LimitQueryParam],
1312
+ parameters: [
1313
+ CursorQueryParam,
1314
+ LimitQueryParam,
1315
+ {
1316
+ name: 'principal',
1317
+ in: 'query',
1318
+ required: false,
1319
+ description: "Tenant admins: only this principal's keys, `user:<id>` or `service-account:<id>`.",
1320
+ schema: { type: 'string' },
1321
+ },
1322
+ ],
1268
1323
  responses: {
1269
1324
  '200': { description: 'A page of keys.', schema: ref('ApiTokenPage') },
1270
1325
  ...CommonAuthErrors,
1271
- '400': ErrorResponse('Malformed cursor.'),
1272
- '403': ErrorResponse('Not a tenant admin.'),
1326
+ '400': ErrorResponse('Malformed cursor or `principal`.'),
1327
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1273
1328
  },
1274
1329
  },
1275
1330
  {
@@ -1278,15 +1333,15 @@ export const OPERATIONS = [
1278
1333
  openapiPath: '/v1/tokens/{tokenId}',
1279
1334
  operationId: 'tokens.get',
1280
1335
  summary: 'Read an API key',
1281
- description: 'Never returns the secret. Tenant admins only.',
1336
+ description: "Never returns the secret. A tenant admin reads any key; anyone else only their own (someone else's reads as missing).",
1282
1337
  tags: ['tokens'],
1283
1338
  security: 'bearer',
1284
1339
  parameters: [TokenIdPathParam],
1285
1340
  responses: {
1286
1341
  '200': { description: 'The key.', schema: ref('ApiToken') },
1287
1342
  ...CommonAuthErrors,
1288
- '403': ErrorResponse('Not a tenant admin.'),
1289
- '404': ErrorResponse('No token with that id under this tenant.'),
1343
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1344
+ '404': ErrorResponse('No token with that id that the caller may see.'),
1290
1345
  },
1291
1346
  },
1292
1347
  {
@@ -1295,15 +1350,132 @@ export const OPERATIONS = [
1295
1350
  openapiPath: '/v1/tokens/{tokenId}/revoke',
1296
1351
  operationId: 'tokens.revoke',
1297
1352
  summary: 'Revoke an API key',
1298
- description: 'Takes effect on the next request. Tenant admins only.',
1353
+ description: 'Takes effect on the next request. A tenant admin revokes any key; anyone else only their own.',
1299
1354
  tags: ['tokens'],
1300
1355
  security: 'bearer',
1301
1356
  parameters: [TokenIdPathParam, IdempotencyKeyParam],
1302
1357
  responses: {
1303
1358
  '200': { description: 'Revoked.', schema: ref('RevokeTokenResult') },
1304
1359
  ...CommonMutationErrors,
1360
+ '403': ErrorResponse('A caller with no keys of its own that is not a tenant admin.'),
1361
+ '404': ErrorResponse('No token with that id that the caller may see.'),
1362
+ },
1363
+ },
1364
+ // ---------- service accounts ----------
1365
+ {
1366
+ method: 'post',
1367
+ honoPath: '/v1/service-accounts',
1368
+ openapiPath: '/v1/service-accounts',
1369
+ operationId: 'serviceAccounts.create',
1370
+ summary: 'Create a service account',
1371
+ description: "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`.",
1372
+ tags: ['service-accounts'],
1373
+ security: 'bearer',
1374
+ parameters: [IdempotencyKeyParam],
1375
+ requestBody: { required: true, schema: ref('CreateServiceAccountBody') },
1376
+ responses: {
1377
+ '201': { description: 'Created.', schema: ref('ServiceAccount') },
1378
+ ...CommonMutationErrors,
1305
1379
  '403': ErrorResponse('Not a tenant admin.'),
1306
- '404': ErrorResponse('No token with that id under this tenant.'),
1380
+ '404': ErrorResponse('A grant names no project of this tenant (`project-not-found`).'),
1381
+ '409': ErrorResponse('An active service account has the name (`service-account-name-taken`), or an idempotency conflict.'),
1382
+ },
1383
+ },
1384
+ {
1385
+ method: 'get',
1386
+ honoPath: '/v1/service-accounts',
1387
+ openapiPath: '/v1/service-accounts',
1388
+ operationId: 'serviceAccounts.list',
1389
+ summary: 'List service accounts',
1390
+ description: 'Oldest first; active only unless `?includeUnregistered=true`. Tenant admins only.',
1391
+ tags: ['service-accounts'],
1392
+ security: 'bearer',
1393
+ parameters: [
1394
+ CursorQueryParam,
1395
+ LimitQueryParam,
1396
+ {
1397
+ name: 'includeUnregistered',
1398
+ in: 'query',
1399
+ required: false,
1400
+ description: '`true`: unregistered accounts too.',
1401
+ schema: { type: 'string', enum: ['true', 'false'] },
1402
+ },
1403
+ ],
1404
+ responses: {
1405
+ '200': { description: 'A page of service accounts.', schema: ref('ServiceAccountPage') },
1406
+ ...CommonAuthErrors,
1407
+ '403': ErrorResponse('Not a tenant admin.'),
1408
+ },
1409
+ },
1410
+ {
1411
+ method: 'get',
1412
+ honoPath: '/v1/service-accounts/:serviceAccountId',
1413
+ openapiPath: '/v1/service-accounts/{serviceAccountId}',
1414
+ operationId: 'serviceAccounts.get',
1415
+ summary: 'Read a service account',
1416
+ description: 'Unregistered ones too. Tenant admins only.',
1417
+ tags: ['service-accounts'],
1418
+ security: 'bearer',
1419
+ parameters: [ServiceAccountIdPathParam],
1420
+ responses: {
1421
+ '200': { description: 'The service account.', schema: ref('ServiceAccount') },
1422
+ ...CommonAuthErrors,
1423
+ '403': ErrorResponse('Not a tenant admin.'),
1424
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1425
+ },
1426
+ },
1427
+ {
1428
+ method: 'post',
1429
+ honoPath: '/v1/service-accounts/:serviceAccountId/grant',
1430
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/grant',
1431
+ operationId: 'serviceAccounts.grant',
1432
+ summary: 'Grant a service account',
1433
+ description: "Tenant admin, or a role on a project (replacing the account's role there). Written before the call answers. Tenant admins only.",
1434
+ tags: ['service-accounts'],
1435
+ security: 'bearer',
1436
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1437
+ requestBody: { required: true, schema: ref('ServiceAccountGrantBody') },
1438
+ responses: {
1439
+ '200': { description: 'The account, with its grants.', schema: ref('ServiceAccount') },
1440
+ ...CommonMutationErrors,
1441
+ '403': ErrorResponse('Not a tenant admin.'),
1442
+ '404': ErrorResponse('No such service account (`service-account-not-found`), or no such project (`project-not-found`).'),
1443
+ '409': ErrorResponse('The account is unregistered (`service-account-unregistered`), or an idempotency conflict.'),
1444
+ },
1445
+ },
1446
+ {
1447
+ method: 'post',
1448
+ honoPath: '/v1/service-accounts/:serviceAccountId/ungrant',
1449
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/ungrant',
1450
+ operationId: 'serviceAccounts.ungrant',
1451
+ summary: 'Remove a grant from a service account',
1452
+ description: 'A no-op when the account does not hold it. Tenant admins only.',
1453
+ tags: ['service-accounts'],
1454
+ security: 'bearer',
1455
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1456
+ requestBody: { required: true, schema: ref('ServiceAccountUngrantBody') },
1457
+ responses: {
1458
+ '200': { description: 'The account, with its grants.', schema: ref('ServiceAccount') },
1459
+ ...CommonMutationErrors,
1460
+ '403': ErrorResponse('Not a tenant admin.'),
1461
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1462
+ },
1463
+ },
1464
+ {
1465
+ method: 'post',
1466
+ honoPath: '/v1/service-accounts/:serviceAccountId/unregister',
1467
+ openapiPath: '/v1/service-accounts/{serviceAccountId}/unregister',
1468
+ operationId: 'serviceAccounts.unregister',
1469
+ summary: 'Unregister a service account',
1470
+ description: 'A tombstone: its grants go and its keys stop working; it stays readable. Idempotent. Tenant admins only.',
1471
+ tags: ['service-accounts'],
1472
+ security: 'bearer',
1473
+ parameters: [ServiceAccountIdPathParam, IdempotencyKeyParam],
1474
+ responses: {
1475
+ '200': { description: 'The unregistered account.', schema: ref('ServiceAccount') },
1476
+ ...CommonMutationErrors,
1477
+ '403': ErrorResponse('Not a tenant admin.'),
1478
+ '404': ErrorResponse('No such service account (`service-account-not-found`).'),
1307
1479
  },
1308
1480
  },
1309
1481
  {
@@ -1320,6 +1492,7 @@ export const OPERATIONS = [
1320
1492
  responses: {
1321
1493
  '201': { description: 'Token minted.', schema: ref('MintPublicRunTokenResult') },
1322
1494
  ...CommonMutationErrors,
1495
+ '409': ErrorResponse("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`)."),
1323
1496
  '400': ErrorResponse('Malformed body, or `expiresInSeconds` above the deployment maximum.'),
1324
1497
  '403': ErrorResponse('The caller may not read one of the runs (`permission-denied`).'),
1325
1498
  '404': ErrorResponse('A run does not exist under this tenant (`run-not-found`).'),
@@ -1462,11 +1635,11 @@ export const OPERATIONS = [
1462
1635
  openapiPath: '/v1/approvals/{approvalId}/audit-bundle',
1463
1636
  operationId: 'approvals.auditBundle',
1464
1637
  summary: 'Export a signed audit bundle for a decided approval',
1465
- description: "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`.",
1638
+ description: "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`.",
1466
1639
  tags: ['approvals'],
1467
1640
  security: 'bearer',
1468
1641
  parameters: [ApprovalIdPathParam, IdempotencyKeyParam],
1469
- requestBody: { required: true, schema: ref('ExportAuditBundleBody') },
1642
+ requestBody: { required: false, schema: ref('ExportAuditBundleBody') },
1470
1643
  responses: {
1471
1644
  '200': { description: 'Signed audit bundle.', schema: ref('ExportAuditBundleResult') },
1472
1645
  ...CommonMutationErrors,
@@ -1547,7 +1720,7 @@ export const OPERATIONS = [
1547
1720
  },
1548
1721
  '201': { description: 'The derived agent version.', schema: ref('Agent') },
1549
1722
  ...CommonMutationErrors,
1550
- '409': ErrorResponse("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."),
1723
+ '409': ErrorResponse("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."),
1551
1724
  '400': ErrorResponse("`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)."),
1552
1725
  '404': ErrorResponse('`agent-not-found`: no agent at `from`; or `projectId` names no project of this tenant (`project-not-found`).'),
1553
1726
  },
@@ -1582,7 +1755,7 @@ export const OPERATIONS = [
1582
1755
  '201': { description: 'Agent published.', schema: ref('PublishAgentResult') },
1583
1756
  ...CommonMutationErrors,
1584
1757
  '400': ErrorResponse("Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID)."),
1585
- '409': ErrorResponse("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."),
1758
+ '409': ErrorResponse("`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."),
1586
1759
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
1587
1760
  },
1588
1761
  },
@@ -1999,7 +2172,7 @@ export const OPERATIONS = [
1999
2172
  '201': { description: 'Flow published.', schema: ref('PublishFlowResult') },
2000
2173
  ...CommonMutationErrors,
2001
2174
  '400': ErrorResponse("Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID)."),
2002
- '409': ErrorResponse("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."),
2175
+ '409': ErrorResponse("`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."),
2003
2176
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
2004
2177
  },
2005
2178
  },
@@ -2123,7 +2296,7 @@ export const OPERATIONS = [
2123
2296
  '201': { description: 'Tool registered.', schema: ref('RegisterToolResult') },
2124
2297
  ...CommonMutationErrors,
2125
2298
  '400': ErrorResponse("Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID)."),
2126
- '409': ErrorResponse("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."),
2299
+ '409': ErrorResponse("`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."),
2127
2300
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
2128
2301
  },
2129
2302
  },
@@ -2216,6 +2389,7 @@ export const OPERATIONS = [
2216
2389
  '400': ErrorResponse("Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID)."),
2217
2390
  '409': ErrorResponse("Guardrail already registered at that id. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack's files are the source); the message says what to do instead."),
2218
2391
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
2392
+ '422': ErrorResponse("`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."),
2219
2393
  },
2220
2394
  },
2221
2395
  {
@@ -2314,6 +2488,27 @@ export const OPERATIONS = [
2314
2488
  '404': ErrorResponse('No conversation with that id under this tenant.'),
2315
2489
  },
2316
2490
  },
2491
+ {
2492
+ method: 'post',
2493
+ honoPath: '/v1/conversations/:conversationId/unregister',
2494
+ openapiPath: '/v1/conversations/{conversationId}/unregister',
2495
+ operationId: 'conversations.unregister',
2496
+ summary: 'Unregister a conversation',
2497
+ description: "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.",
2498
+ tags: ['conversations'],
2499
+ security: 'bearer',
2500
+ parameters: [ConversationIdPathParam, IdempotencyKeyParam],
2501
+ responses: {
2502
+ '200': {
2503
+ description: 'The conversation, with `unregisteredAt`.',
2504
+ schema: ref('Conversation'),
2505
+ },
2506
+ ...CommonMutationErrors,
2507
+ '400': ErrorResponse('`conversationId` is not a conversation id (a UUID).'),
2508
+ '404': ErrorResponse('No conversation with that id under this tenant, or it is unregistered already.'),
2509
+ '501': ErrorResponse("`conversation-unregister-unsupported`: this runtime can't unregister conversations."),
2510
+ },
2511
+ },
2317
2512
  {
2318
2513
  method: 'get',
2319
2514
  honoPath: '/v1/conversations/:conversationId/messages',
@@ -2341,7 +2536,7 @@ export const OPERATIONS = [
2341
2536
  openapiPath: '/v1/memory/facts',
2342
2537
  operationId: 'memory.listFacts',
2343
2538
  summary: 'List facts',
2344
- description: '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.',
2539
+ description: "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.",
2345
2540
  tags: ['memory'],
2346
2541
  security: 'bearer',
2347
2542
  parameters: [
@@ -2352,11 +2547,12 @@ export const OPERATIONS = [
2352
2547
  ScopeKindQueryParam,
2353
2548
  ScopeIdQueryParam,
2354
2549
  InheritQueryParam,
2550
+ FactAsOfQueryParam,
2355
2551
  ],
2356
2552
  responses: {
2357
2553
  '200': { description: 'Page of facts.', schema: ref('FactCollectionPage') },
2358
2554
  ...CommonAuthErrors,
2359
- '400': ErrorResponse('Malformed query parameter (e.g. `scope` not valid JSON).'),
2555
+ '400': ErrorResponse('Malformed query parameter (e.g. `scope` not valid JSON, `asOf` not a time).'),
2360
2556
  },
2361
2557
  },
2362
2558
  {
@@ -2365,13 +2561,32 @@ export const OPERATIONS = [
2365
2561
  openapiPath: '/v1/memory/facts/{factId}',
2366
2562
  operationId: 'memory.getFact',
2367
2563
  summary: 'Fetch a fact',
2564
+ description: 'Its current revision; `?version=` reads one revision, `?asOf=` the revision current at that time. A fact the caller may not see is not found.',
2368
2565
  tags: ['memory'],
2369
2566
  security: 'bearer',
2370
- parameters: [FactIdPathParam],
2567
+ parameters: [FactIdPathParam, FactVersionQueryParam, FactAsOfQueryParam],
2371
2568
  responses: {
2372
2569
  '200': { description: 'Fact.', schema: ref('Fact') },
2373
2570
  ...CommonAuthErrors,
2374
- '404': ErrorResponse('No fact with that id under this tenant.'),
2571
+ '400': ErrorResponse('`version` is not a revision number, or `asOf` not a time.'),
2572
+ '404': ErrorResponse('No fact with that id the caller may see (or no such revision).'),
2573
+ },
2574
+ },
2575
+ {
2576
+ method: 'get',
2577
+ honoPath: '/v1/memory/facts/:factId/revisions',
2578
+ openapiPath: '/v1/memory/facts/{factId}/revisions',
2579
+ operationId: 'memory.listFactRevisions',
2580
+ summary: "List a fact's revisions",
2581
+ description: 'Every revision of the fact, newest first, superseded and deleted ones included: who changed it, when and why (`invalidatedBy`, `invalidatedAt`, `invalidationReason`).',
2582
+ tags: ['memory'],
2583
+ security: 'bearer',
2584
+ parameters: [FactIdPathParam],
2585
+ responses: {
2586
+ '200': { description: 'The revisions.', schema: ref('FactRevisionList') },
2587
+ ...CommonAuthErrors,
2588
+ '404': ErrorResponse('No fact with that id the caller may see.'),
2589
+ '501': ErrorResponse("`memory-operation-unsupported`: this runtime's memory doesn't keep fact history."),
2375
2590
  },
2376
2591
  },
2377
2592
  {
@@ -2380,7 +2595,7 @@ export const OPERATIONS = [
2380
2595
  openapiPath: '/v1/memory/facts',
2381
2596
  operationId: 'memory.writeFact',
2382
2597
  summary: 'Write a fact',
2383
- description: '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`.',
2598
+ description: "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`).",
2384
2599
  tags: ['memory'],
2385
2600
  security: 'bearer',
2386
2601
  parameters: [IdempotencyKeyParam],
@@ -2389,6 +2604,8 @@ export const OPERATIONS = [
2389
2604
  '201': { description: 'Fact written.', schema: ref('Fact') },
2390
2605
  ...CommonMutationErrors,
2391
2606
  '400': ErrorResponse('Malformed body, or the fact type requires semantic indexing and no embedding provider is bound.'),
2607
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
2608
+ '409': ErrorResponse("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."),
2392
2609
  },
2393
2610
  },
2394
2611
  {
@@ -2396,18 +2613,58 @@ export const OPERATIONS = [
2396
2613
  honoPath: '/v1/memory/facts/:factId/supersede',
2397
2614
  openapiPath: '/v1/memory/facts/{factId}/supersede',
2398
2615
  operationId: 'memory.supersedeFact',
2399
- summary: 'Mark a fact as superseded',
2400
- description: 'Soft-delete via supersession — the historical row is retained until retention sweeps remove it. Idempotent: superseding an already-superseded fact returns `200 { superseded: true }`.',
2616
+ summary: "Change a fact's content",
2617
+ description: '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.',
2401
2618
  tags: ['memory'],
2402
2619
  security: 'bearer',
2403
2620
  parameters: [FactIdPathParam, IdempotencyKeyParam],
2621
+ requestBody: { required: true, schema: ref('SupersedeFactBody') },
2404
2622
  responses: {
2405
- '200': {
2406
- description: 'Superseded (or already superseded).',
2407
- schema: ref('SupersedeFactResult'),
2408
- },
2623
+ '200': { description: 'The new revision.', schema: ref('Fact') },
2409
2624
  ...CommonMutationErrors,
2410
- '404': ErrorResponse('No fact with that id under this tenant.'),
2625
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
2626
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
2627
+ '409': ErrorResponse('`fact-changed`: the current revision is not `expectVersion` (`details.currentVersion`); `legal-hold`: the fact is under legal hold.'),
2628
+ },
2629
+ },
2630
+ {
2631
+ method: 'delete',
2632
+ honoPath: '/v1/memory/facts/:factId',
2633
+ openapiPath: '/v1/memory/facts/{factId}',
2634
+ operationId: 'memory.deleteFact',
2635
+ summary: 'Delete a fact',
2636
+ description: '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.',
2637
+ tags: ['memory'],
2638
+ security: 'bearer',
2639
+ parameters: [FactIdPathParam, FactExpectVersionQueryParam],
2640
+ responses: {
2641
+ '200': { description: 'The closed revision.', schema: ref('Fact') },
2642
+ ...CommonAuthErrors,
2643
+ '400': ErrorResponse('`expectVersion` is not a revision number.'),
2644
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
2645
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
2646
+ '409': ErrorResponse('`fact-changed`: the current revision is not `expectVersion`; `legal-hold`: the fact is under legal hold.'),
2647
+ '501': ErrorResponse("`memory-operation-unsupported`: this runtime's memory can't delete facts."),
2648
+ },
2649
+ },
2650
+ {
2651
+ method: 'post',
2652
+ honoPath: '/v1/memory/facts/:factId/verify',
2653
+ openapiPath: '/v1/memory/facts/{factId}/verify',
2654
+ operationId: 'memory.verifyFact',
2655
+ summary: 'Mark a fact verified',
2656
+ description: 'A person who may write in its scope checked it: the next revision has `trust: verified`, `verifiedBy` and `verifiedAt`, and the same content.',
2657
+ tags: ['memory'],
2658
+ security: 'bearer',
2659
+ parameters: [FactIdPathParam, IdempotencyKeyParam],
2660
+ requestBody: { required: false, schema: ref('VerifyFactBody') },
2661
+ responses: {
2662
+ '200': { description: 'The verified revision.', schema: ref('Fact') },
2663
+ ...CommonMutationErrors,
2664
+ '403': ErrorResponse("`permission-denied`: the caller may not write in the fact's scope."),
2665
+ '404': ErrorResponse('No fact with that id the caller may see, or it was deleted.'),
2666
+ '409': ErrorResponse('`fact-changed`: the current revision is not `expectVersion`; `legal-hold`: the fact is under legal hold.'),
2667
+ '501': ErrorResponse("`memory-operation-unsupported`: this runtime's memory can't verify facts."),
2411
2668
  },
2412
2669
  },
2413
2670
  {
@@ -2416,7 +2673,7 @@ export const OPERATIONS = [
2416
2673
  openapiPath: '/v1/memory/retrieve',
2417
2674
  operationId: 'memory.retrieve',
2418
2675
  summary: 'Retrieve facts by intent',
2419
- description: 'Cross-history retrieval. Body is a `RetrieveIntent` shape mirroring the agent-side declarative retrieval. Semantic modes require an embedding provider bound on the deployment.',
2676
+ description: '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`.',
2420
2677
  tags: ['memory'],
2421
2678
  security: 'bearer',
2422
2679
  parameters: [IdempotencyKeyParam],
@@ -2424,34 +2681,159 @@ export const OPERATIONS = [
2424
2681
  responses: {
2425
2682
  '200': { description: 'Retrieval results.', schema: ref('RetrieveMemoryResult') },
2426
2683
  ...CommonMutationErrors,
2427
- '400': ErrorResponse('Malformed intent, or semantic mode requested and no embedding provider is bound.'),
2684
+ '400': ErrorResponse('Malformed intent.'),
2685
+ '422': ErrorResponse('`semantic-unavailable`: `semantic` or `both` asked to search by meaning, and the deployment has no embeddings (`KINDGI_MEMORY_EMBEDDINGS`).'),
2686
+ '501': ErrorResponse("`memory-operation-unsupported`: this runtime's memory can't retrieve by intent."),
2687
+ },
2688
+ },
2689
+ // ---------- memory erasures (a person's words) ----------
2690
+ {
2691
+ method: 'post',
2692
+ honoPath: '/v1/memory/erasures',
2693
+ openapiPath: '/v1/memory/erasures',
2694
+ operationId: 'memory.createErasure',
2695
+ summary: "Erase a person's words",
2696
+ description: "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.",
2697
+ tags: ['memory'],
2698
+ security: 'bearer',
2699
+ parameters: [IdempotencyKeyParam],
2700
+ requestBody: { required: true, schema: ref('CreateMemoryErasureBody') },
2701
+ responses: {
2702
+ '202': { description: 'Started.', schema: ref('MemoryErasureCreated') },
2703
+ ...CommonMutationErrors,
2704
+ '400': ErrorResponse("Not exactly one of `factId`, `subject` or `conversationId`; or a `subject` of kind `user` (erasing a Kindgi user isn't offered)."),
2705
+ '403': ErrorResponse('Not a tenant admin.'),
2706
+ '409': ErrorResponse('`legal-hold`: a fact it reaches is under legal hold (`details.factIds`); nothing started.'),
2428
2707
  },
2429
2708
  },
2430
- // ---------- proposals (supervisor fix lifecycle) ----------
2709
+ {
2710
+ method: 'get',
2711
+ honoPath: '/v1/memory/erasures',
2712
+ openapiPath: '/v1/memory/erasures',
2713
+ operationId: 'memory.listErasures',
2714
+ summary: 'List erasures',
2715
+ description: '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.',
2716
+ tags: ['memory'],
2717
+ security: 'bearer',
2718
+ parameters: [LimitQueryParam, CursorQueryParam],
2719
+ responses: {
2720
+ '200': { description: 'Page of erasures.', schema: ref('MemoryErasurePage') },
2721
+ ...CommonAuthErrors,
2722
+ '403': ErrorResponse('Not a tenant admin.'),
2723
+ '400': ErrorResponse('`cursor` is not one this list issued.'),
2724
+ },
2725
+ },
2726
+ {
2727
+ method: 'get',
2728
+ honoPath: '/v1/memory/erasures/export',
2729
+ openapiPath: '/v1/memory/erasures/export',
2730
+ operationId: 'memory.exportErasures',
2731
+ summary: 'Export the erasure ledger',
2732
+ description: "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.",
2733
+ tags: ['memory'],
2734
+ security: 'bearer',
2735
+ responses: {
2736
+ '200': { description: 'The ledger.', schema: ref('MemoryErasureLedger') },
2737
+ ...CommonAuthErrors,
2738
+ '403': ErrorResponse('Not a tenant admin.'),
2739
+ },
2740
+ },
2741
+ {
2742
+ method: 'post',
2743
+ honoPath: '/v1/memory/erasures/replay',
2744
+ openapiPath: '/v1/memory/erasures/replay',
2745
+ operationId: 'memory.replayErasures',
2746
+ summary: 'Replay erasures after a backup restore',
2747
+ description: "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.",
2748
+ tags: ['memory'],
2749
+ security: 'bearer',
2750
+ parameters: [IdempotencyKeyParam],
2751
+ requestBody: { required: true, schema: ref('ReplayMemoryErasuresBody') },
2752
+ responses: {
2753
+ '200': { description: 'What was replayed.', schema: ref('ReplayMemoryErasuresResult') },
2754
+ ...CommonMutationErrors,
2755
+ '400': ErrorResponse('Not `{erasures: [...]}` as the export gave them.'),
2756
+ '403': ErrorResponse('Not a tenant admin.'),
2757
+ },
2758
+ },
2759
+ {
2760
+ method: 'post',
2761
+ honoPath: '/v1/memory/erasures/:erasureId/resume',
2762
+ openapiPath: '/v1/memory/erasures/{erasureId}/resume',
2763
+ operationId: 'memory.resumeErasure',
2764
+ summary: 'Resume an erasure',
2765
+ description: "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.",
2766
+ tags: ['memory'],
2767
+ security: 'bearer',
2768
+ parameters: [ErasureIdPathParam, IdempotencyKeyParam],
2769
+ requestBody: { required: false, schema: ref('ResumeMemoryErasureBody') },
2770
+ responses: {
2771
+ '200': { description: 'The erasure.', schema: ref('MemoryErasure') },
2772
+ ...CommonMutationErrors,
2773
+ '400': ErrorResponse('The body is `{force?: boolean}`.'),
2774
+ '403': ErrorResponse('Not a tenant admin.'),
2775
+ '404': ErrorResponse('No such erasure in this tenant.'),
2776
+ },
2777
+ },
2778
+ {
2779
+ method: 'get',
2780
+ honoPath: '/v1/memory/erasures/:erasureId',
2781
+ openapiPath: '/v1/memory/erasures/{erasureId}',
2782
+ operationId: 'memory.getErasure',
2783
+ summary: 'Get an erasure',
2784
+ description: '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.',
2785
+ tags: ['memory'],
2786
+ security: 'bearer',
2787
+ parameters: [ErasureIdPathParam],
2788
+ responses: {
2789
+ '200': { description: 'The erasure.', schema: ref('MemoryErasure') },
2790
+ ...CommonAuthErrors,
2791
+ '403': ErrorResponse('Not a tenant admin.'),
2792
+ '404': ErrorResponse('No such erasure in this tenant.'),
2793
+ },
2794
+ },
2795
+ // ---------- improvement proposals ----------
2431
2796
  {
2432
2797
  method: 'get',
2433
2798
  honoPath: '/v1/proposals',
2434
2799
  openapiPath: '/v1/proposals',
2435
2800
  operationId: 'proposals.list',
2436
- summary: 'List supervisor fix proposals',
2437
- description: 'Cursor-paginated list scoped to `(tenantId, supervisorId)`. Filters: `?status=`, `?agentId=`, `?tier=`. Sort order is binding-defined (typically `createdAt desc, id desc`).',
2801
+ summary: 'List improvement proposals',
2802
+ description: '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).',
2438
2803
  tags: ['proposals'],
2439
2804
  security: 'bearer',
2440
2805
  parameters: [
2441
- SupervisorIdHeaderParam,
2442
2806
  LimitQueryParam,
2443
2807
  CursorQueryParam,
2444
2808
  ProposalStatusQueryParam,
2445
2809
  AgentIdQueryParam,
2446
2810
  ProposalTierQueryParam,
2447
- ScopeKindQueryParam,
2811
+ PromotionScopeKindQueryParam,
2448
2812
  ScopeIdQueryParam,
2449
- InheritQueryParam,
2813
+ SegmentQueryParam,
2450
2814
  ],
2451
2815
  responses: {
2452
2816
  '200': { description: 'Page of proposals.', schema: ref('FixProposalCollectionPage') },
2453
2817
  ...CommonAuthErrors,
2454
- '400': ErrorResponse('Missing `X-Supervisor-Id` header or malformed query parameter.'),
2818
+ '400': ErrorResponse('A malformed query parameter.'),
2819
+ },
2820
+ },
2821
+ {
2822
+ method: 'post',
2823
+ honoPath: '/v1/proposals/improve',
2824
+ openapiPath: '/v1/proposals/improve',
2825
+ operationId: 'proposals.improve',
2826
+ summary: 'Start an improvement pass',
2827
+ description: "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`.",
2828
+ tags: ['proposals'],
2829
+ security: 'bearer',
2830
+ parameters: [IdempotencyKeyParam],
2831
+ requestBody: { required: true, schema: ref('ImproveBody') },
2832
+ responses: {
2833
+ '202': { description: 'The pass, running.', schema: ref('ImprovementPass') },
2834
+ ...CommonMutationErrors,
2835
+ '404': ErrorResponse('`fromVersion` (or the version serving the scope) is not an active version.'),
2836
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2455
2837
  },
2456
2838
  },
2457
2839
  {
@@ -2459,122 +2841,162 @@ export const OPERATIONS = [
2459
2841
  honoPath: '/v1/proposals/:proposalId',
2460
2842
  openapiPath: '/v1/proposals/{proposalId}',
2461
2843
  operationId: 'proposals.get',
2462
- summary: 'Fetch a fix proposal',
2844
+ summary: 'Fetch an improvement proposal',
2845
+ description: 'Needs `read` on its agent.',
2463
2846
  tags: ['proposals'],
2464
2847
  security: 'bearer',
2465
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam],
2848
+ parameters: [ProposalIdPathParam],
2466
2849
  responses: {
2467
- '200': { description: 'Proposal.', schema: ref('FixProposal') },
2850
+ '200': { description: 'The proposal.', schema: ref('FixProposal') },
2468
2851
  ...CommonAuthErrors,
2469
- '400': ErrorResponse('Missing `X-Supervisor-Id` header.'),
2470
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2852
+ '404': ErrorResponse('No such proposal (or none the caller can read).'),
2471
2853
  },
2472
2854
  },
2473
2855
  {
2474
2856
  method: 'post',
2475
2857
  honoPath: '/v1/proposals',
2476
2858
  openapiPath: '/v1/proposals',
2477
- operationId: 'proposals.draft',
2478
- summary: 'Draft a fix proposal',
2479
- description: '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`.',
2859
+ operationId: 'proposals.create',
2860
+ summary: 'Propose new content for a data block',
2861
+ description: '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.',
2480
2862
  tags: ['proposals'],
2481
2863
  security: 'bearer',
2482
- parameters: [SupervisorIdHeaderParam, IdempotencyKeyParam],
2483
- requestBody: { required: true, schema: ref('DraftProposalBody') },
2864
+ parameters: [IdempotencyKeyParam],
2865
+ requestBody: { required: true, schema: ref('CreateProposalBody') },
2484
2866
  responses: {
2485
- '201': { description: 'Proposal drafted (or deduped).', schema: ref('FixProposal') },
2867
+ '201': { description: 'The proposal, a `draft`.', schema: ref('FixProposal') },
2868
+ '200': { description: 'The same proposal, made before.', schema: ref('FixProposal') },
2486
2869
  ...CommonMutationErrors,
2870
+ '404': ErrorResponse("`fromVersion` isn't an active version of the agent (`agent-version-not-found`)."),
2487
2871
  },
2488
2872
  },
2489
2873
  {
2490
2874
  method: 'post',
2491
- honoPath: '/v1/proposals/:proposalId/dry-run',
2492
- openapiPath: '/v1/proposals/{proposalId}/dry-run',
2493
- operationId: 'proposals.dryRun',
2494
- summary: 'Dry-run a proposal against an eval dataset',
2495
- description: '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`.',
2875
+ honoPath: '/v1/proposals/:proposalId/evaluate',
2876
+ openapiPath: '/v1/proposals/{proposalId}/evaluate',
2877
+ operationId: 'proposals.evaluate',
2878
+ summary: 'Compare a proposal on a test set',
2879
+ description: "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`.",
2496
2880
  tags: ['proposals'],
2497
2881
  security: 'bearer',
2498
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2499
- requestBody: { required: true, schema: ref('DryRunProposalBody') },
2882
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
2883
+ requestBody: { required: true, schema: ref('EvaluateProposalBody') },
2500
2884
  responses: {
2501
- '200': { description: 'Dry-run completed.', schema: ref('DryRunProposalResult') },
2885
+ '202': { description: 'The proposal, `evaluating`.', schema: ref('FixProposal') },
2502
2886
  ...CommonMutationErrors,
2503
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2504
- '422': ErrorResponse('Baseline mismatch, apply-change failure, or runtime dry-run error.'),
2887
+ '404': ErrorResponse('No such proposal, or no such test set.'),
2888
+ '409': ErrorResponse("`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`)."),
2505
2889
  },
2506
2890
  },
2507
2891
  {
2508
2892
  method: 'post',
2509
- honoPath: '/v1/proposals/:proposalId/submit-review',
2510
- openapiPath: '/v1/proposals/{proposalId}/submit-review',
2511
- operationId: 'proposals.submitReview',
2512
- summary: 'Submit a dry-run-passed proposal for HITL review',
2513
- description: '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).',
2893
+ honoPath: '/v1/proposals/:proposalId/request',
2894
+ openapiPath: '/v1/proposals/{proposalId}/request',
2895
+ operationId: 'proposals.request',
2896
+ summary: "Request a proposal's promotion for its scope",
2897
+ description: "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`.",
2514
2898
  tags: ['proposals'],
2515
2899
  security: 'bearer',
2516
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2517
- requestBody: { required: false, schema: ref('SubmitReviewProposalBody') },
2900
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
2901
+ requestBody: { required: false, schema: ref('ProposalReasonBody') },
2518
2902
  responses: {
2519
- '200': {
2520
- description: 'Review enqueued.',
2521
- schema: ref('SubmitReviewProposalResult'),
2903
+ '201': { description: 'Promoted: the proposal, `promoted`.', schema: ref('FixProposal') },
2904
+ '202': {
2905
+ description: 'The gate passed and an approval is open: the proposal, `in-review`.',
2906
+ schema: ref('FixProposal'),
2522
2907
  },
2523
2908
  ...CommonMutationErrors,
2524
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2525
- '422': ErrorResponse('Ground-layer guardrail violation.'),
2909
+ '404': ErrorResponse('No such proposal.'),
2910
+ '409': ErrorResponse("The proposal's status doesn't allow it."),
2911
+ '422': ErrorResponse('`gate-failed`: the gate refused it (the error carries the checks and `proposalId`).'),
2526
2912
  },
2527
2913
  },
2528
2914
  {
2529
2915
  method: 'post',
2530
- honoPath: '/v1/proposals/:proposalId/apply',
2531
- openapiPath: '/v1/proposals/{proposalId}/apply',
2532
- operationId: 'proposals.apply',
2533
- summary: 'Apply an approved proposal',
2534
- description: '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`.',
2916
+ honoPath: '/v1/proposals/:proposalId/rollback',
2917
+ openapiPath: '/v1/proposals/{proposalId}/rollback',
2918
+ operationId: 'proposals.rollback',
2919
+ summary: 'Roll back a promoted proposal',
2920
+ description: "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.",
2535
2921
  tags: ['proposals'],
2536
2922
  security: 'bearer',
2537
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2538
- requestBody: { required: false, schema: ref('ApplyProposalBody') },
2923
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
2924
+ requestBody: { required: false, schema: ref('ProposalReasonBody') },
2539
2925
  responses: {
2540
- '200': { description: 'Proposal applied.', schema: ref('ApplyProposalResult') },
2926
+ '200': { description: 'The proposal, `rolled-back`.', schema: ref('FixProposal') },
2541
2927
  ...CommonMutationErrors,
2542
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2543
- '422': ErrorResponse('Baseline agent not in registry, invalid new version, or apply-change failure.'),
2928
+ '404': ErrorResponse('No such proposal.'),
2929
+ '409': ErrorResponse("It isn't promoted, or its version doesn't serve the scope anymore."),
2544
2930
  },
2545
2931
  },
2546
2932
  {
2547
2933
  method: 'post',
2548
- honoPath: '/v1/proposals/:proposalId/rollback',
2549
- openapiPath: '/v1/proposals/{proposalId}/rollback',
2550
- operationId: 'proposals.rollback',
2551
- summary: 'Roll back an applied proposal',
2552
- description: 'Unregisters the applied version from the agent registry and transitions the proposal to `rolled-back`. Legal only from `applied`.',
2934
+ honoPath: '/v1/proposals/:proposalId/withdraw',
2935
+ openapiPath: '/v1/proposals/{proposalId}/withdraw',
2936
+ operationId: 'proposals.withdraw',
2937
+ summary: 'Withdraw a proposal',
2938
+ description: "Closes it. Not while it's in review (decide its approval instead), nor once promoted, rejected or rolled back. Needs `publish` on the agent.",
2553
2939
  tags: ['proposals'],
2554
2940
  security: 'bearer',
2555
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2556
- requestBody: { required: true, schema: ref('RollbackProposalBody') },
2941
+ parameters: [ProposalIdPathParam, IdempotencyKeyParam],
2942
+ requestBody: { required: true, schema: ref('WithdrawProposalBody') },
2557
2943
  responses: {
2558
- '200': { description: 'Proposal rolled back.', schema: ref('RollbackProposalResult') },
2944
+ '200': { description: 'The proposal, `withdrawn`.', schema: ref('FixProposal') },
2559
2945
  ...CommonMutationErrors,
2560
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2946
+ '404': ErrorResponse('No such proposal.'),
2947
+ '409': ErrorResponse("The proposal's status doesn't allow it."),
2948
+ },
2949
+ },
2950
+ // ---------- improvement passes ----------
2951
+ {
2952
+ method: 'get',
2953
+ honoPath: '/v1/improvement-passes',
2954
+ openapiPath: '/v1/improvement-passes',
2955
+ operationId: 'improvementPasses.list',
2956
+ summary: 'List improvement passes',
2957
+ description: 'Newest first, only the passes of agents the caller can read. `?agentId=` narrows them.',
2958
+ tags: ['proposals'],
2959
+ security: 'bearer',
2960
+ parameters: [LimitQueryParam, CursorQueryParam, AgentIdQueryParam],
2961
+ responses: {
2962
+ '200': { description: 'Page of passes.', schema: ref('ImprovementPassCollectionPage') },
2963
+ ...CommonAuthErrors,
2964
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2965
+ },
2966
+ },
2967
+ {
2968
+ method: 'get',
2969
+ honoPath: '/v1/improvement-passes/:passId',
2970
+ openapiPath: '/v1/improvement-passes/{passId}',
2971
+ operationId: 'improvementPasses.get',
2972
+ summary: 'Fetch an improvement pass',
2973
+ description: 'Its status, the candidates it compared and what they cost, and once it ends, what it found. Needs `read` on its agent.',
2974
+ tags: ['proposals'],
2975
+ security: 'bearer',
2976
+ parameters: [ImprovementPassIdPathParam],
2977
+ responses: {
2978
+ '200': { description: 'The pass.', schema: ref('ImprovementPass') },
2979
+ ...CommonAuthErrors,
2980
+ '404': ErrorResponse('No such pass (or none the caller can read).'),
2981
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2561
2982
  },
2562
2983
  },
2563
2984
  {
2564
2985
  method: 'post',
2565
- honoPath: '/v1/proposals/:proposalId/withdraw',
2566
- openapiPath: '/v1/proposals/{proposalId}/withdraw',
2567
- operationId: 'proposals.withdraw',
2568
- summary: 'Withdraw a non-terminal proposal',
2569
- description: '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`.',
2986
+ honoPath: '/v1/improvement-passes/:passId/cancel',
2987
+ openapiPath: '/v1/improvement-passes/{passId}/cancel',
2988
+ operationId: 'improvementPasses.cancel',
2989
+ summary: 'Cancel an improvement pass',
2990
+ description: 'A running pass stops and ends `cancelled`, writing no proposal. Needs `publish` on its agent.',
2570
2991
  tags: ['proposals'],
2571
2992
  security: 'bearer',
2572
- parameters: [SupervisorIdHeaderParam, ProposalIdPathParam, IdempotencyKeyParam],
2573
- requestBody: { required: true, schema: ref('WithdrawProposalBody') },
2993
+ parameters: [ImprovementPassIdPathParam, IdempotencyKeyParam],
2574
2994
  responses: {
2575
- '200': { description: 'Proposal withdrawn.', schema: ref('FixProposal') },
2995
+ '200': { description: 'The pass, cancelled.', schema: ref('ImprovementPass') },
2576
2996
  ...CommonMutationErrors,
2577
- '404': ErrorResponse('No proposal visible under this supervisor with that id.'),
2997
+ '404': ErrorResponse('No such pass.'),
2998
+ '409': ErrorResponse('`improvement-pass-finished`: it has ended already.'),
2999
+ '501': ErrorResponse('`improve-unsupported`: this runtime runs no improvement passes.'),
2578
3000
  },
2579
3001
  },
2580
3002
  // ---------- provenance ----------
@@ -2618,17 +3040,31 @@ export const OPERATIONS = [
2618
3040
  '404': ErrorResponse('No provenance record for that run under this tenant.'),
2619
3041
  },
2620
3042
  },
3043
+ {
3044
+ method: 'get',
3045
+ honoPath: '/v1/export-signing-keys',
3046
+ openapiPath: '/v1/export-signing-keys',
3047
+ operationId: 'exportSigningKeys.list',
3048
+ summary: 'List the keys this deployment signs exports with',
3049
+ description: "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.",
3050
+ tags: ['export-signing-keys'],
3051
+ security: 'bearer',
3052
+ responses: {
3053
+ '200': { description: 'The keys.', schema: ref('ExportSigningKeyList') },
3054
+ ...CommonAuthErrors,
3055
+ },
3056
+ },
2621
3057
  {
2622
3058
  method: 'post',
2623
3059
  honoPath: '/v1/provenance/:runId/export',
2624
3060
  openapiPath: '/v1/provenance/{runId}/export',
2625
3061
  operationId: 'provenance.export',
2626
3062
  summary: 'Export a signed provenance bundle for a run',
2627
- description: "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`.",
3063
+ description: "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`.",
2628
3064
  tags: ['provenance'],
2629
3065
  security: 'bearer',
2630
3066
  parameters: [RunIdPathParam, IdempotencyKeyParam],
2631
- requestBody: { required: true, schema: ref('ExportProvenanceBody') },
3067
+ requestBody: { required: false, schema: ref('ExportProvenanceBody') },
2632
3068
  responses: {
2633
3069
  '200': { description: 'Signed bundle.', schema: ref('ExportProvenanceResult') },
2634
3070
  ...CommonMutationErrors,
@@ -2642,7 +3078,7 @@ export const OPERATIONS = [
2642
3078
  openapiPath: '/v1/artifacts',
2643
3079
  operationId: 'artifacts.list',
2644
3080
  summary: 'List artifact metadata',
2645
- description: '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`).',
3081
+ description: '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.',
2646
3082
  tags: ['artifacts'],
2647
3083
  security: 'bearer',
2648
3084
  parameters: [
@@ -2662,6 +3098,13 @@ export const OPERATIONS = [
2662
3098
  description: 'Filter by exact content-type match.',
2663
3099
  schema: { type: 'string' },
2664
3100
  },
3101
+ {
3102
+ name: 'projectId',
3103
+ in: 'query',
3104
+ required: false,
3105
+ description: 'Filter to the artifacts of one project.',
3106
+ schema: { type: 'string' },
3107
+ },
2665
3108
  ],
2666
3109
  responses: {
2667
3110
  '200': { description: 'Page of blob metadata.', schema: ref('ArtifactCollectionPage') },
@@ -2675,7 +3118,7 @@ export const OPERATIONS = [
2675
3118
  openapiPath: '/v1/artifacts',
2676
3119
  operationId: 'artifacts.upload',
2677
3120
  summary: 'Upload an artifact',
2678
- description: '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.',
3121
+ description: "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`.",
2679
3122
  tags: ['artifacts'],
2680
3123
  security: 'bearer',
2681
3124
  parameters: [IdempotencyKeyParam],
@@ -2691,7 +3134,10 @@ export const OPERATIONS = [
2691
3134
  responses: {
2692
3135
  '201': { description: 'Upload accepted; metadata returned.', schema: ref('BlobMeta') },
2693
3136
  ...CommonMutationErrors,
2694
- '400': ErrorResponse('Malformed multipart body, bad tag JSON, hash mismatch, or declared size mismatch.'),
3137
+ '400': ErrorResponse("Malformed multipart body, bad tag JSON, hash mismatch, declared size mismatch, or a `projectId` that isn't the owner run's."),
3138
+ '403': ErrorResponse('No `write` on the project (`permission-denied`).'),
3139
+ '404': ErrorResponse('No such owner run (`run-not-found`).'),
3140
+ '413': ErrorResponse('Over the upload cap (`artifact-too-large`; `details.maxBytes` says how much).'),
2695
3141
  },
2696
3142
  },
2697
3143
  {
@@ -2719,7 +3165,7 @@ export const OPERATIONS = [
2719
3165
  schema: { type: 'string', format: 'binary' },
2720
3166
  },
2721
3167
  ...CommonAuthErrors,
2722
- '404': ErrorResponse('No blob with that id under this tenant.'),
3168
+ '404': ErrorResponse("No blob with that id under this tenant, or one in a project the caller can't read."),
2723
3169
  },
2724
3170
  },
2725
3171
  {
@@ -2770,6 +3216,8 @@ export const OPERATIONS = [
2770
3216
  responses: {
2771
3217
  '200': { description: 'Delete acknowledged.', schema: ref('DeleteArtifactResult') },
2772
3218
  ...CommonMutationErrors,
3219
+ '403': ErrorResponse('No `write` on its project (`permission-denied`).'),
3220
+ '404': ErrorResponse("An artifact in a project the caller can't read (`blob-not-found`)."),
2773
3221
  },
2774
3222
  },
2775
3223
  // ---------- observations (supervisor) ----------
@@ -2889,13 +3337,29 @@ export const OPERATIONS = [
2889
3337
  '404': ErrorResponse('No provider with that id under this tenant.'),
2890
3338
  },
2891
3339
  },
3340
+ {
3341
+ method: 'get',
3342
+ honoPath: '/v1/providers/:providerId/check',
3343
+ openapiPath: '/v1/providers/{providerId}/check',
3344
+ operationId: 'providers.check',
3345
+ summary: "Check a provider's registration",
3346
+ description: "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.",
3347
+ tags: ['providers'],
3348
+ security: 'bearer',
3349
+ parameters: [ProviderIdPathParam],
3350
+ responses: {
3351
+ '200': { description: 'The check.', schema: ref('ProviderCheckResult') },
3352
+ ...CommonAuthErrors,
3353
+ '404': ErrorResponse('No provider with that id under this tenant.'),
3354
+ },
3355
+ },
2892
3356
  {
2893
3357
  method: 'post',
2894
3358
  honoPath: '/v1/providers',
2895
3359
  openapiPath: '/v1/providers',
2896
3360
  operationId: 'providers.register',
2897
3361
  summary: 'Register a model provider',
2898
- description: '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.',
3362
+ description: '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`.',
2899
3363
  tags: ['providers'],
2900
3364
  security: 'bearer',
2901
3365
  parameters: [IdempotencyKeyParam],
@@ -2905,6 +3369,7 @@ export const OPERATIONS = [
2905
3369
  ...CommonMutationErrors,
2906
3370
  '400': ErrorResponse('Validation failed (see `details.reason`).'),
2907
3371
  '409': ErrorResponse('Provider already registered at that id.'),
3372
+ '422': ErrorResponse("`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."),
2908
3373
  },
2909
3374
  },
2910
3375
  {
@@ -2942,6 +3407,7 @@ export const OPERATIONS = [
2942
3407
  '403': ErrorResponse('`permission-denied`: not allowed to judge this run. `judge-class-not-allowed`: the judge class is restricted (`assertableBy`) and the caller may not assert it.'),
2943
3408
  '404': ErrorResponse('`run-not-found`.'),
2944
3409
  '409': ErrorResponse('`run-not-finished`: the run has no output to judge yet.'),
3410
+ '410': ErrorResponse("`run-erased`: an erasure cleared the run's content (a person's words were removed); there's nothing to judge."),
2945
3411
  },
2946
3412
  },
2947
3413
  {
@@ -3665,7 +4131,7 @@ export const OPERATIONS = [
3665
4131
  '201': { description: 'Eval suite published.', schema: ref('PublishEvalSuiteResult') },
3666
4132
  ...CommonMutationErrors,
3667
4133
  '400': ErrorResponse("Validation failed (see `details.issues`); or `projectId` isn't a project id (a UUID)."),
3668
- '409': ErrorResponse('Eval suite already registered at that (id, version).'),
4134
+ '409': ErrorResponse("`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)."),
3669
4135
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
3670
4136
  },
3671
4137
  },
@@ -3675,7 +4141,7 @@ export const OPERATIONS = [
3675
4141
  openapiPath: '/v1/eval-suites/{suiteId}/versions/from-judgments',
3676
4142
  operationId: 'evalSuites.buildFromJudgments',
3677
4143
  summary: 'Build a test set from judgments',
3678
- description: "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.",
4144
+ description: "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.",
3679
4145
  tags: ['eval-suites'],
3680
4146
  security: 'bearer',
3681
4147
  parameters: [EvalSuiteIdPathParam, IdempotencyKeyParam],
@@ -3685,7 +4151,7 @@ export const OPERATIONS = [
3685
4151
  ...CommonMutationErrors,
3686
4152
  '400': ErrorResponse("Malformed body; or `projectId` isn't a project id (a UUID)."),
3687
4153
  '403': ErrorResponse('`permission-denied`.'),
3688
- '409': ErrorResponse('Eval suite already registered at that (id, version).'),
4154
+ '409': ErrorResponse("`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)."),
3689
4155
  '501': ErrorResponse('`test-sets-not-supported`: this deployment cannot build test sets.'),
3690
4156
  '404': ErrorResponse("The body's `projectId` names no project of this tenant (`project-not-found`)."),
3691
4157
  },
@@ -3972,7 +4438,7 @@ export const OPERATIONS = [
3972
4438
  openapiPath: '/v1/auth/providers',
3973
4439
  operationId: 'auth.providers.list',
3974
4440
  summary: 'List identity providers configured for the tenant',
3975
- description: '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.',
4441
+ description: "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.",
3976
4442
  tags: ['auth'],
3977
4443
  security: 'bearer',
3978
4444
  responses: {
@@ -3983,23 +4449,150 @@ export const OPERATIONS = [
3983
4449
  ...CommonAuthErrors,
3984
4450
  },
3985
4451
  },
4452
+ {
4453
+ method: 'get',
4454
+ honoPath: '/v1/auth/sign-in-options',
4455
+ openapiPath: '/v1/auth/sign-in-options',
4456
+ operationId: 'auth.signInOptions',
4457
+ summary: 'How a person can sign in',
4458
+ description: '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`).',
4459
+ tags: ['auth'],
4460
+ security: 'public',
4461
+ parameters: [
4462
+ {
4463
+ name: 'email',
4464
+ in: 'query',
4465
+ required: false,
4466
+ schema: { type: 'string', minLength: 3 },
4467
+ description: 'The email the person typed; only its domain is used.',
4468
+ },
4469
+ ],
4470
+ responses: {
4471
+ '200': { description: 'The ways to sign in (possibly none).', schema: ref('SignInOptions') },
4472
+ '400': ErrorResponse('`email` is not an email address.'),
4473
+ '429': ErrorResponse('Too many lookups from this client.'),
4474
+ },
4475
+ },
4476
+ {
4477
+ method: 'post',
4478
+ honoPath: '/v1/auth/token-sign-in',
4479
+ openapiPath: '/v1/auth/token-sign-in',
4480
+ operationId: 'auth.tokenSignIn',
4481
+ summary: 'Sign in to the console with an API token',
4482
+ description: "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.",
4483
+ tags: ['auth'],
4484
+ security: 'bearer',
4485
+ responses: {
4486
+ '200': {
4487
+ description: 'Signed in: the session cookie is set.',
4488
+ schema: ref('TokenSignInResult'),
4489
+ },
4490
+ ...CommonAuthErrors,
4491
+ '400': ErrorResponse('Signed in by a session, not an API token (`token-sign-in-needs-an-api-token`).'),
4492
+ '403': ErrorResponse("Not allowed here (`token-sign-in-off`), or not this key (`token-sign-in-not-allowed`): a service account's, or a narrowed one."),
4493
+ '409': ErrorResponse("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."),
4494
+ },
4495
+ },
3986
4496
  {
3987
4497
  method: 'post',
3988
4498
  honoPath: '/v1/auth/providers',
3989
4499
  openapiPath: '/v1/auth/providers',
3990
4500
  operationId: 'auth.providers.register',
3991
- summary: 'Register a new OAuth/OIDC identity provider',
3992
- description: 'Unique per tenant on `providerId`: re-registering a known provider returns `409 identity-provider-already-registered` — unregister it first, then register again.',
4501
+ summary: 'Register an identity provider (OIDC, SAML or OAuth 2.0)',
4502
+ description: '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.',
3993
4503
  tags: ['auth'],
3994
4504
  security: 'bearer',
3995
4505
  parameters: [IdempotencyKeyParam],
3996
- requestBody: { required: true, schema: ref('IdentityProviderConfig') },
4506
+ requestBody: { required: true, schema: ref('RegisterIdentityProviderBody') },
3997
4507
  responses: {
3998
4508
  '201': {
3999
4509
  description: 'Provider registered.',
4000
4510
  schema: ref('RegisterIdentityProviderResult'),
4001
4511
  },
4002
4512
  ...CommonMutationErrors,
4513
+ '422': ErrorResponse('The deployment could not use the configuration (`identity-provider-invalid`).'),
4514
+ },
4515
+ },
4516
+ {
4517
+ method: 'get',
4518
+ honoPath: '/v1/auth/providers/:providerId',
4519
+ openapiPath: '/v1/auth/providers/{providerId}',
4520
+ operationId: 'auth.providers.get',
4521
+ summary: 'Get one identity provider',
4522
+ description: 'The provider as stored, with `signIn` when the deployment sets it. Secrets appear only as references.',
4523
+ tags: ['auth'],
4524
+ security: 'bearer',
4525
+ parameters: [
4526
+ {
4527
+ name: 'providerId',
4528
+ in: 'path',
4529
+ required: true,
4530
+ schema: { type: 'string', minLength: 1 },
4531
+ },
4532
+ ],
4533
+ responses: {
4534
+ '200': { description: 'The provider.', schema: ref('GetIdentityProviderResult') },
4535
+ ...CommonAuthErrors,
4536
+ '404': ErrorResponse('No identity provider registered with that id under this tenant.'),
4537
+ },
4538
+ },
4539
+ {
4540
+ method: 'get',
4541
+ honoPath: '/v1/auth/providers/:providerId/sign-in',
4542
+ openapiPath: '/v1/auth/providers/{providerId}/sign-in',
4543
+ operationId: 'auth.providers.signIn',
4544
+ summary: 'What to give the identity provider, before or after registering',
4545
+ description: "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.",
4546
+ tags: ['auth'],
4547
+ security: 'bearer',
4548
+ parameters: [
4549
+ {
4550
+ name: 'providerId',
4551
+ in: 'path',
4552
+ required: true,
4553
+ schema: { type: 'string', minLength: 1 },
4554
+ },
4555
+ {
4556
+ name: 'kind',
4557
+ in: 'query',
4558
+ required: false,
4559
+ schema: { $ref: '#/components/schemas/IdentityProviderKind' },
4560
+ description: "The provider's kind; default: the registered provider's.",
4561
+ },
4562
+ ],
4563
+ responses: {
4564
+ '200': {
4565
+ description: 'What to give the identity provider.',
4566
+ schema: ref('IdentityProviderSignInUrls'),
4567
+ },
4568
+ ...CommonAuthErrors,
4569
+ '400': ErrorResponse("`kind` missing for a provider that isn't registered, or a kind this deployment doesn't sign in with (`bad-input`)."),
4570
+ },
4571
+ },
4572
+ {
4573
+ method: 'patch',
4574
+ honoPath: '/v1/auth/providers/:providerId',
4575
+ openapiPath: '/v1/auth/providers/{providerId}',
4576
+ operationId: 'auth.providers.update',
4577
+ summary: 'Change an identity provider, keeping its sign-in URLs',
4578
+ description: "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.",
4579
+ tags: ['auth'],
4580
+ security: 'bearer',
4581
+ parameters: [
4582
+ {
4583
+ name: 'providerId',
4584
+ in: 'path',
4585
+ required: true,
4586
+ schema: { type: 'string', minLength: 1 },
4587
+ },
4588
+ IdempotencyKeyParam,
4589
+ ],
4590
+ requestBody: { required: true, schema: ref('UpdateIdentityProviderBody') },
4591
+ responses: {
4592
+ '200': { description: 'Updated.', schema: ref('UpdateIdentityProviderResult') },
4593
+ ...CommonMutationErrors,
4594
+ '404': ErrorResponse('No identity provider registered with that id under this tenant.'),
4595
+ '422': ErrorResponse('The deployment could not use the configuration (`identity-provider-invalid`).'),
4003
4596
  },
4004
4597
  },
4005
4598
  {
@@ -4086,14 +4679,15 @@ export const OPERATIONS = [
4086
4679
  openapiPath: '/v1/auth/refresh',
4087
4680
  operationId: 'auth.refresh',
4088
4681
  summary: 'Refresh the current session token',
4089
- description: '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.',
4682
+ description: '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.',
4090
4683
  tags: ['auth'],
4091
4684
  security: 'bearer',
4092
4685
  parameters: [IdempotencyKeyParam],
4093
4686
  responses: {
4094
4687
  '200': { description: 'New session token.', schema: ref('RefreshResult') },
4095
4688
  ...CommonMutationErrors,
4096
- '400': ErrorResponse('Caller presented a bearer token; refresh is session-only.'),
4689
+ '409': ErrorResponse("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`)."),
4690
+ '400': ErrorResponse('Caller presented a bearer token (refresh is session-only), or a browser session (cookie).'),
4097
4691
  '404': ErrorResponse('Session no longer exists.'),
4098
4692
  '422': ErrorResponse('Refresh with the provider failed.'),
4099
4693
  },
@@ -4104,7 +4698,7 @@ export const OPERATIONS = [
4104
4698
  openapiPath: '/v1/auth/logout',
4105
4699
  operationId: 'auth.logout',
4106
4700
  summary: 'Revoke the current session',
4107
- description: 'Requires a session token (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`. Idempotent — revoking an already-revoked session returns `{ revoked: false }`.',
4701
+ description: '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`).',
4108
4702
  tags: ['auth'],
4109
4703
  security: 'bearer',
4110
4704
  parameters: [IdempotencyKeyParam],
@@ -4121,7 +4715,7 @@ export const OPERATIONS = [
4121
4715
  openapiPath: '/v1/identity/users',
4122
4716
  operationId: 'identity.users.list',
4123
4717
  summary: 'List users in the tenant',
4124
- description: '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.',
4718
+ description: '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`).',
4125
4719
  tags: ['identity'],
4126
4720
  security: 'bearer',
4127
4721
  parameters: [
@@ -4134,10 +4728,36 @@ export const OPERATIONS = [
4134
4728
  description: 'Prefix match on `displayName`.',
4135
4729
  schema: { type: 'string' },
4136
4730
  },
4731
+ {
4732
+ name: 'includeUnregistered',
4733
+ in: 'query',
4734
+ required: false,
4735
+ description: 'With `true`, people who were removed (`unregisteredAt`) too.',
4736
+ schema: { type: 'boolean' },
4737
+ },
4137
4738
  ],
4138
4739
  responses: {
4139
4740
  '200': { description: 'Page of users.', schema: ref('UserCollectionPage') },
4140
4741
  ...CommonAuthErrors,
4742
+ '403': ErrorResponse('Not a tenant admin.'),
4743
+ },
4744
+ },
4745
+ {
4746
+ method: 'post',
4747
+ honoPath: '/v1/identity/users',
4748
+ openapiPath: '/v1/identity/users',
4749
+ operationId: 'identity.users.create',
4750
+ summary: 'Add a person',
4751
+ description: "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.",
4752
+ tags: ['identity'],
4753
+ security: 'bearer',
4754
+ parameters: [IdempotencyKeyParam],
4755
+ requestBody: { required: true, schema: ref('CreateUserBody') },
4756
+ responses: {
4757
+ '201': { description: 'The new person.', schema: ref('UserRecord') },
4758
+ ...CommonMutationErrors,
4759
+ '403': ErrorResponse('Not a tenant admin.'),
4760
+ '409': ErrorResponse('Another person of the tenant has the email (`identity-user-email-taken`), or an idempotency conflict.'),
4141
4761
  },
4142
4762
  },
4143
4763
  {
@@ -4146,6 +4766,7 @@ export const OPERATIONS = [
4146
4766
  openapiPath: '/v1/identity/users/{userId}',
4147
4767
  operationId: 'identity.users.get',
4148
4768
  summary: 'Get a user by id',
4769
+ description: 'A tenant admin, or the person themselves.',
4149
4770
  tags: ['identity'],
4150
4771
  security: 'bearer',
4151
4772
  parameters: [
@@ -4154,6 +4775,7 @@ export const OPERATIONS = [
4154
4775
  responses: {
4155
4776
  '200': { description: 'User record.', schema: ref('UserRecord') },
4156
4777
  ...CommonAuthErrors,
4778
+ '403': ErrorResponse("Someone else's record, and not a tenant admin."),
4157
4779
  '404': ErrorResponse('No user with that id under this tenant.'),
4158
4780
  },
4159
4781
  },
@@ -4163,7 +4785,7 @@ export const OPERATIONS = [
4163
4785
  openapiPath: '/v1/identity/users/{userId}/sessions',
4164
4786
  operationId: 'identity.users.listSessions',
4165
4787
  summary: 'List active sessions for a user',
4166
- description: '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").',
4788
+ description: '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").',
4167
4789
  tags: ['identity'],
4168
4790
  security: 'bearer',
4169
4791
  parameters: [
@@ -4172,6 +4794,31 @@ export const OPERATIONS = [
4172
4794
  responses: {
4173
4795
  '200': { description: 'Page of sessions.', schema: ref('IdentitySessionCollectionPage') },
4174
4796
  ...CommonAuthErrors,
4797
+ '403': ErrorResponse("Someone else's sessions, and not a tenant admin."),
4798
+ },
4799
+ },
4800
+ {
4801
+ method: 'post',
4802
+ honoPath: '/v1/identity/users/:userId/unregister',
4803
+ openapiPath: '/v1/identity/users/{userId}/unregister',
4804
+ operationId: 'identity.users.unregister',
4805
+ summary: 'Remove a person',
4806
+ description: "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.",
4807
+ tags: ['identity'],
4808
+ security: 'bearer',
4809
+ parameters: [
4810
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
4811
+ IdempotencyKeyParam,
4812
+ ],
4813
+ responses: {
4814
+ '200': {
4815
+ description: 'The removed person, and what removing them took away.',
4816
+ schema: ref('UnregisterUserResult'),
4817
+ },
4818
+ ...CommonMutationErrors,
4819
+ '403': ErrorResponse('Not a tenant admin.'),
4820
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
4821
+ '409': ErrorResponse('Yourself or the seed user (`identity-user-unregister-refused`, `details.reason`), the only tenant admin (`last-tenant-admin`), or an idempotency conflict.'),
4175
4822
  },
4176
4823
  },
4177
4824
  {
@@ -4180,7 +4827,7 @@ export const OPERATIONS = [
4180
4827
  openapiPath: '/v1/identity/users/{userId}/revoke-sessions',
4181
4828
  operationId: 'identity.users.revokeSessions',
4182
4829
  summary: 'Revoke every active session for a user',
4183
- description: 'Admin op — idempotent. Under the hood, deployments typically delegate to `SessionStoreBinding.revokeAllForUser`. Returns `{ revokedCount: 0 }` when the user was already fully signed out.',
4830
+ description: "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.",
4184
4831
  tags: ['identity'],
4185
4832
  security: 'bearer',
4186
4833
  parameters: [
@@ -4190,9 +4837,76 @@ export const OPERATIONS = [
4190
4837
  responses: {
4191
4838
  '200': { description: 'Revocation outcome.', schema: ref('RevokeSessionsResult') },
4192
4839
  ...CommonMutationErrors,
4840
+ '403': ErrorResponse("Another person's sessions, and not a tenant admin."),
4193
4841
  '500': ErrorResponse('Session revocation failed inside the caller-plugged binding.'),
4194
4842
  },
4195
4843
  },
4844
+ {
4845
+ method: 'get',
4846
+ honoPath: '/v1/identity/users/:userId/grants',
4847
+ openapiPath: '/v1/identity/users/{userId}/grants',
4848
+ operationId: 'identity.users.grants',
4849
+ summary: "Read a person's grants",
4850
+ description: "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.",
4851
+ tags: ['identity'],
4852
+ security: 'bearer',
4853
+ parameters: [
4854
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
4855
+ ],
4856
+ responses: {
4857
+ '200': { description: "The person's grants.", schema: ref('PersonGrants') },
4858
+ ...CommonAuthErrors,
4859
+ '403': ErrorResponse("Another person's grants, and not a tenant admin."),
4860
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
4861
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
4862
+ },
4863
+ },
4864
+ {
4865
+ method: 'post',
4866
+ honoPath: '/v1/identity/users/:userId/grant',
4867
+ openapiPath: '/v1/identity/users/{userId}/grant',
4868
+ operationId: 'identity.users.grant',
4869
+ summary: 'Make a person a tenant admin',
4870
+ description: "Written before the call answers, so the person's next request holds it. A no-op when held. Tenant admins only.",
4871
+ tags: ['identity'],
4872
+ security: 'bearer',
4873
+ parameters: [
4874
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
4875
+ IdempotencyKeyParam,
4876
+ ],
4877
+ requestBody: { required: true, schema: ref('PersonGrantBody') },
4878
+ responses: {
4879
+ '200': { description: "The person's grants, after.", schema: ref('PersonGrants') },
4880
+ ...CommonMutationErrors,
4881
+ '403': ErrorResponse('Not a tenant admin.'),
4882
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
4883
+ '409': ErrorResponse('The person was removed from the tenant (`identity-user-unregistered`), or an idempotency conflict.'),
4884
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
4885
+ },
4886
+ },
4887
+ {
4888
+ method: 'post',
4889
+ honoPath: '/v1/identity/users/:userId/ungrant',
4890
+ openapiPath: '/v1/identity/users/{userId}/ungrant',
4891
+ operationId: 'identity.users.ungrant',
4892
+ summary: 'Remove tenant admin from a person',
4893
+ description: '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.',
4894
+ tags: ['identity'],
4895
+ security: 'bearer',
4896
+ parameters: [
4897
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string', minLength: 1 } },
4898
+ IdempotencyKeyParam,
4899
+ ],
4900
+ requestBody: { required: true, schema: ref('PersonGrantBody') },
4901
+ responses: {
4902
+ '200': { description: "The person's grants, after.", schema: ref('PersonGrants') },
4903
+ ...CommonMutationErrors,
4904
+ '403': ErrorResponse('Not a tenant admin.'),
4905
+ '404': ErrorResponse('No user with that id under this tenant (`identity-user-not-found`).'),
4906
+ '409': ErrorResponse('The only person who is a tenant admin (`last-tenant-admin`), the seed user (`seed-user-admin`), or an idempotency conflict.'),
4907
+ '501': ErrorResponse('The runtime has no authorization store (`person-grants-unsupported`).'),
4908
+ },
4909
+ },
4196
4910
  {
4197
4911
  method: 'get',
4198
4912
  honoPath: '/v1/identity/whoami',
@@ -4226,7 +4940,7 @@ export const OPERATIONS = [
4226
4940
  schema: ref('DeploymentRecord'),
4227
4941
  },
4228
4942
  ...CommonMutationErrors,
4229
- '409': ErrorResponse("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."),
4943
+ '409': ErrorResponse("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."),
4230
4944
  '400': ErrorResponse('Signature invalid, image unverifiable, or deployment-validation-failed with per-primitive `details[]`.'),
4231
4945
  '403': ErrorResponse("Signer key not on the tenant's trust list."),
4232
4946
  },
@@ -4415,11 +5129,11 @@ export const OPERATIONS = [
4415
5129
  openapiPath: '/v1/compliance/evidence/export',
4416
5130
  operationId: 'compliance.evidence.export',
4417
5131
  summary: 'Export a signed compliance-evidence bundle',
4418
- description: "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`.",
5132
+ description: "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`.",
4419
5133
  tags: ['compliance'],
4420
5134
  security: 'bearer',
4421
5135
  parameters: [IdempotencyKeyParam],
4422
- requestBody: { required: true, schema: ref('ExportComplianceEvidenceBody') },
5136
+ requestBody: { required: false, schema: ref('ExportComplianceEvidenceBody') },
4423
5137
  responses: {
4424
5138
  '200': {
4425
5139
  description: 'Signed evidence bundle.',
@@ -4916,12 +5630,13 @@ export const OPERATIONS = [
4916
5630
  openapiPath: '/v1/projects/default',
4917
5631
  operationId: 'projects.getDefault',
4918
5632
  summary: "Fetch the tenant's Default project",
4919
- description: 'Returns the row where `Project.isDefault = true` (exactly one per tenant). Returns 404 `project-not-found` when no Default has been provisioned.',
5633
+ description: '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.',
4920
5634
  tags: ['projects'],
4921
5635
  security: 'bearer',
4922
5636
  responses: {
4923
5637
  '200': { description: 'Default project.', schema: ref('Project') },
4924
5638
  ...CommonAuthErrors,
5639
+ '403': ErrorResponse("The caller can't read the Default project."),
4925
5640
  '404': ErrorResponse('Tenant has no Default project.'),
4926
5641
  },
4927
5642
  },
@@ -5082,7 +5797,7 @@ export const OPERATIONS = [
5082
5797
  openapiPath: '/v1/projects/{projectId}/memberships',
5083
5798
  operationId: 'projects.memberships.add',
5084
5799
  summary: 'Add a user directly to a project',
5085
- description: 'Idempotent on `(projectId, userId)` — re-adding an existing member with a different role does NOT overwrite; use PATCH for role changes.',
5800
+ description: '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.',
5086
5801
  tags: ['projects'],
5087
5802
  security: 'bearer',
5088
5803
  parameters: [
@@ -5102,7 +5817,7 @@ export const OPERATIONS = [
5102
5817
  schema: ref('AddProjectMembershipResult'),
5103
5818
  },
5104
5819
  ...CommonMutationErrors,
5105
- '404': ErrorResponse('No project with that id under this tenant.'),
5820
+ '404': ErrorResponse('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`).'),
5106
5821
  },
5107
5822
  },
5108
5823
  {
@@ -5294,7 +6009,7 @@ export const OPERATIONS = [
5294
6009
  openapiPath: '/v1/env/{name}',
5295
6010
  operationId: 'env.put',
5296
6011
  summary: 'Upsert an env entry',
5297
- description: 'Requires the `env:write` capability. Every write bumps `revision`; optional `ifRevision` guards against concurrent updates (409 `env-write-conflict`).',
6012
+ description: "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`.",
5298
6013
  tags: ['env'],
5299
6014
  security: 'bearer',
5300
6015
  parameters: [
@@ -5632,7 +6347,16 @@ export const OPERATIONS = [
5632
6347
  summary: 'Fetch a cron schedule',
5633
6348
  tags: ['schedules'],
5634
6349
  security: 'bearer',
5635
- parameters: [TriggerIdPathParam],
6350
+ parameters: [
6351
+ TriggerIdPathParam,
6352
+ {
6353
+ name: 'upcoming',
6354
+ in: 'query',
6355
+ required: false,
6356
+ description: 'Include the next N occurrences (`upcoming`), 1 to 20.',
6357
+ schema: { type: 'integer', minimum: 1, maximum: 20 },
6358
+ },
6359
+ ],
5636
6360
  responses: {
5637
6361
  '200': { description: 'Schedule record.', schema: ref('ScheduleRecord') },
5638
6362
  ...CommonAuthErrors,
@@ -5703,6 +6427,60 @@ export const OPERATIONS = [
5703
6427
  ...CommonMutationErrors,
5704
6428
  },
5705
6429
  },
6430
+ {
6431
+ method: 'get',
6432
+ honoPath: '/v1/schedules/:triggerId/fires',
6433
+ openapiPath: '/v1/schedules/{triggerId}/fires',
6434
+ operationId: 'schedules.fires',
6435
+ summary: "A schedule's fire history",
6436
+ description: '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.',
6437
+ tags: ['schedules'],
6438
+ security: 'bearer',
6439
+ parameters: [TriggerIdPathParam, LimitQueryParam, CursorQueryParam],
6440
+ responses: {
6441
+ '200': { description: 'Page of fires.', schema: ref('ScheduleFirePage') },
6442
+ ...CommonAuthErrors,
6443
+ '404': ErrorResponse('No cron trigger with that id.'),
6444
+ '501': ErrorResponse('`trigger-operation-unsupported`: this deployment keeps no fire history.'),
6445
+ },
6446
+ },
6447
+ {
6448
+ method: 'post',
6449
+ honoPath: '/v1/schedules/:triggerId/run-now',
6450
+ openapiPath: '/v1/schedules/{triggerId}/run-now',
6451
+ operationId: 'schedules.runNow',
6452
+ summary: 'Run a schedule now',
6453
+ description: "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.",
6454
+ tags: ['schedules'],
6455
+ security: 'bearer',
6456
+ parameters: [TriggerIdPathParam, IdempotencyKeyParam],
6457
+ responses: {
6458
+ '202': {
6459
+ description: 'The fire; its run starts in the background.',
6460
+ schema: ref('ScheduleFire'),
6461
+ },
6462
+ ...CommonMutationErrors,
6463
+ '404': ErrorResponse('No cron trigger with that id.'),
6464
+ '501': ErrorResponse('`trigger-operation-unsupported`: this deployment has no run-now.'),
6465
+ },
6466
+ },
6467
+ {
6468
+ method: 'post',
6469
+ honoPath: '/v1/schedules/:triggerId/owner',
6470
+ openapiPath: '/v1/schedules/{triggerId}/owner',
6471
+ operationId: 'schedules.takeOwnership',
6472
+ summary: 'Take over a schedule',
6473
+ description: "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.",
6474
+ tags: ['schedules'],
6475
+ security: 'bearer',
6476
+ parameters: [TriggerIdPathParam, IdempotencyKeyParam],
6477
+ responses: {
6478
+ '200': { description: 'The schedule, with its new owner.', schema: ref('ScheduleRecord') },
6479
+ ...CommonMutationErrors,
6480
+ '404': ErrorResponse('No cron trigger with that id.'),
6481
+ '501': ErrorResponse("`trigger-operation-unsupported`: this deployment can't change a schedule's owner."),
6482
+ },
6483
+ },
5706
6484
  // ---------- event-triggers (trigger surface) ----------
5707
6485
  {
5708
6486
  method: 'post',
@@ -5945,12 +6723,13 @@ export const OPERATIONS = [
5945
6723
  openapiPath: '/v1/webhook-endpoints/generate-secret',
5946
6724
  operationId: 'webhookEndpoints.generateSecret',
5947
6725
  summary: 'Generate a webhook signing secret',
5948
- description: '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.',
6726
+ description: '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.',
5949
6727
  tags: ['webhook-endpoints'],
5950
6728
  security: 'bearer',
5951
6729
  responses: {
5952
6730
  '200': { description: 'A new secret.', schema: ref('GeneratedWebhookSecret') },
5953
6731
  ...CommonAuthErrors,
6732
+ '409': ErrorResponse("A retry with the Idempotency-Key of a request that succeeded: the secret isn't kept (`idempotency-key-replay-withheld`)."),
5954
6733
  },
5955
6734
  },
5956
6735
  {