@kindgi/api 0.0.0-bootstrap.0 → 0.1.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 (505) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +216 -1
  3. package/dist/adapter-binding.d.ts +177 -0
  4. package/dist/adapter-binding.d.ts.map +1 -0
  5. package/dist/adapter-binding.js +12 -0
  6. package/dist/adapter-binding.js.map +1 -0
  7. package/dist/agent-binding.d.ts +207 -0
  8. package/dist/agent-binding.d.ts.map +1 -0
  9. package/dist/agent-binding.js +4 -0
  10. package/dist/agent-binding.js.map +1 -0
  11. package/dist/app.d.ts +770 -0
  12. package/dist/app.d.ts.map +1 -0
  13. package/dist/app.js +396 -0
  14. package/dist/app.js.map +1 -0
  15. package/dist/capability-binding.d.ts +76 -0
  16. package/dist/capability-binding.d.ts.map +1 -0
  17. package/dist/capability-binding.js +4 -0
  18. package/dist/capability-binding.js.map +1 -0
  19. package/dist/cost-binding.d.ts +175 -0
  20. package/dist/cost-binding.d.ts.map +1 -0
  21. package/dist/cost-binding.js +13 -0
  22. package/dist/cost-binding.js.map +1 -0
  23. package/dist/deployment-binding.d.ts +201 -0
  24. package/dist/deployment-binding.d.ts.map +1 -0
  25. package/dist/deployment-binding.js +4 -0
  26. package/dist/deployment-binding.js.map +1 -0
  27. package/dist/env-binding.d.ts +132 -0
  28. package/dist/env-binding.d.ts.map +1 -0
  29. package/dist/env-binding.js +4 -0
  30. package/dist/env-binding.js.map +1 -0
  31. package/dist/errors.d.ts +37 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +264 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/eval-run-binding.d.ts +196 -0
  36. package/dist/eval-run-binding.d.ts.map +1 -0
  37. package/dist/eval-run-binding.js +16 -0
  38. package/dist/eval-run-binding.js.map +1 -0
  39. package/dist/eval-run-dispatcher.d.ts +131 -0
  40. package/dist/eval-run-dispatcher.d.ts.map +1 -0
  41. package/dist/eval-run-dispatcher.js +368 -0
  42. package/dist/eval-run-dispatcher.js.map +1 -0
  43. package/dist/eval-suite-binding.d.ts +227 -0
  44. package/dist/eval-suite-binding.d.ts.map +1 -0
  45. package/dist/eval-suite-binding.js +24 -0
  46. package/dist/eval-suite-binding.js.map +1 -0
  47. package/dist/event-bus-binding.d.ts +107 -0
  48. package/dist/event-bus-binding.d.ts.map +1 -0
  49. package/dist/event-bus-binding.js +5 -0
  50. package/dist/event-bus-binding.js.map +1 -0
  51. package/dist/flow-binding.d.ts +194 -0
  52. package/dist/flow-binding.d.ts.map +1 -0
  53. package/dist/flow-binding.js +4 -0
  54. package/dist/flow-binding.js.map +1 -0
  55. package/dist/guardrail-binding.d.ts +138 -0
  56. package/dist/guardrail-binding.d.ts.map +1 -0
  57. package/dist/guardrail-binding.js +4 -0
  58. package/dist/guardrail-binding.js.map +1 -0
  59. package/dist/handler-binding.d.ts +126 -0
  60. package/dist/handler-binding.d.ts.map +1 -0
  61. package/dist/handler-binding.js +4 -0
  62. package/dist/handler-binding.js.map +1 -0
  63. package/dist/hitl-binding.d.ts +115 -0
  64. package/dist/hitl-binding.d.ts.map +1 -0
  65. package/dist/hitl-binding.js +4 -0
  66. package/dist/hitl-binding.js.map +1 -0
  67. package/dist/identity-directory-binding.d.ts +116 -0
  68. package/dist/identity-directory-binding.d.ts.map +1 -0
  69. package/dist/identity-directory-binding.js +4 -0
  70. package/dist/identity-directory-binding.js.map +1 -0
  71. package/dist/identity-provider-binding.d.ts +184 -0
  72. package/dist/identity-provider-binding.d.ts.map +1 -0
  73. package/dist/identity-provider-binding.js +4 -0
  74. package/dist/identity-provider-binding.js.map +1 -0
  75. package/dist/image-registry-binding.d.ts +165 -0
  76. package/dist/image-registry-binding.d.ts.map +1 -0
  77. package/dist/image-registry-binding.js +4 -0
  78. package/dist/image-registry-binding.js.map +1 -0
  79. package/dist/index.d.ts +72 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +26 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/key-management-binding.d.ts +101 -0
  84. package/dist/key-management-binding.d.ts.map +1 -0
  85. package/dist/key-management-binding.js +4 -0
  86. package/dist/key-management-binding.js.map +1 -0
  87. package/dist/mcp-endpoint-binding.d.ts +312 -0
  88. package/dist/mcp-endpoint-binding.d.ts.map +1 -0
  89. package/dist/mcp-endpoint-binding.js +12 -0
  90. package/dist/mcp-endpoint-binding.js.map +1 -0
  91. package/dist/memory-binding.d.ts +209 -0
  92. package/dist/memory-binding.d.ts.map +1 -0
  93. package/dist/memory-binding.js +4 -0
  94. package/dist/memory-binding.js.map +1 -0
  95. package/dist/middleware/auth.d.ts +168 -0
  96. package/dist/middleware/auth.d.ts.map +1 -0
  97. package/dist/middleware/auth.js +247 -0
  98. package/dist/middleware/auth.js.map +1 -0
  99. package/dist/middleware/authorize.d.ts +26 -0
  100. package/dist/middleware/authorize.d.ts.map +1 -0
  101. package/dist/middleware/authorize.js +65 -0
  102. package/dist/middleware/authorize.js.map +1 -0
  103. package/dist/middleware/error-mapper.d.ts +13 -0
  104. package/dist/middleware/error-mapper.d.ts.map +1 -0
  105. package/dist/middleware/error-mapper.js +32 -0
  106. package/dist/middleware/error-mapper.js.map +1 -0
  107. package/dist/middleware/idempotency.d.ts +43 -0
  108. package/dist/middleware/idempotency.d.ts.map +1 -0
  109. package/dist/middleware/idempotency.js +118 -0
  110. package/dist/middleware/idempotency.js.map +1 -0
  111. package/dist/middleware/principal.d.ts +4 -0
  112. package/dist/middleware/principal.d.ts.map +1 -0
  113. package/dist/middleware/principal.js +42 -0
  114. package/dist/middleware/principal.js.map +1 -0
  115. package/dist/middleware/public-run-routes.d.ts +16 -0
  116. package/dist/middleware/public-run-routes.d.ts.map +1 -0
  117. package/dist/middleware/public-run-routes.js +43 -0
  118. package/dist/middleware/public-run-routes.js.map +1 -0
  119. package/dist/middleware/request-id.d.ts +14 -0
  120. package/dist/middleware/request-id.d.ts.map +1 -0
  121. package/dist/middleware/request-id.js +24 -0
  122. package/dist/middleware/request-id.js.map +1 -0
  123. package/dist/middleware/sigv4-verify.d.ts +64 -0
  124. package/dist/middleware/sigv4-verify.d.ts.map +1 -0
  125. package/dist/middleware/sigv4-verify.js +286 -0
  126. package/dist/middleware/sigv4-verify.js.map +1 -0
  127. package/dist/middleware/sigv4.d.ts +25 -0
  128. package/dist/middleware/sigv4.d.ts.map +1 -0
  129. package/dist/middleware/sigv4.js +120 -0
  130. package/dist/middleware/sigv4.js.map +1 -0
  131. package/dist/openapi/generate.d.ts +15 -0
  132. package/dist/openapi/generate.d.ts.map +1 -0
  133. package/dist/openapi/generate.js +241 -0
  134. package/dist/openapi/generate.js.map +1 -0
  135. package/dist/openapi/operations.d.ts +70 -0
  136. package/dist/openapi/operations.d.ts.map +1 -0
  137. package/dist/openapi/operations.js +5051 -0
  138. package/dist/openapi/operations.js.map +1 -0
  139. package/dist/openapi/schemas.d.ts +590 -0
  140. package/dist/openapi/schemas.d.ts.map +1 -0
  141. package/dist/openapi/schemas.js +5820 -0
  142. package/dist/openapi/schemas.js.map +1 -0
  143. package/dist/provenance-binding.d.ts +116 -0
  144. package/dist/provenance-binding.d.ts.map +1 -0
  145. package/dist/provenance-binding.js +31 -0
  146. package/dist/provenance-binding.js.map +1 -0
  147. package/dist/provider-binding.d.ts +174 -0
  148. package/dist/provider-binding.d.ts.map +1 -0
  149. package/dist/provider-binding.js +4 -0
  150. package/dist/provider-binding.js.map +1 -0
  151. package/dist/public-run-token.d.ts +97 -0
  152. package/dist/public-run-token.d.ts.map +1 -0
  153. package/dist/public-run-token.js +167 -0
  154. package/dist/public-run-token.js.map +1 -0
  155. package/dist/retention-binding.d.ts +69 -0
  156. package/dist/retention-binding.d.ts.map +1 -0
  157. package/dist/retention-binding.js +4 -0
  158. package/dist/retention-binding.js.map +1 -0
  159. package/dist/reviewer-binding.d.ts +84 -0
  160. package/dist/reviewer-binding.d.ts.map +1 -0
  161. package/dist/reviewer-binding.js +4 -0
  162. package/dist/reviewer-binding.js.map +1 -0
  163. package/dist/rotation-status-store.d.ts +96 -0
  164. package/dist/rotation-status-store.d.ts.map +1 -0
  165. package/dist/rotation-status-store.js +106 -0
  166. package/dist/rotation-status-store.js.map +1 -0
  167. package/dist/routes/adapters.d.ts +29 -0
  168. package/dist/routes/adapters.d.ts.map +1 -0
  169. package/dist/routes/adapters.js +230 -0
  170. package/dist/routes/adapters.js.map +1 -0
  171. package/dist/routes/agents.d.ts +17 -0
  172. package/dist/routes/agents.d.ts.map +1 -0
  173. package/dist/routes/agents.js +342 -0
  174. package/dist/routes/agents.js.map +1 -0
  175. package/dist/routes/approvals.d.ts +37 -0
  176. package/dist/routes/approvals.d.ts.map +1 -0
  177. package/dist/routes/approvals.js +551 -0
  178. package/dist/routes/approvals.js.map +1 -0
  179. package/dist/routes/artifacts.d.ts +25 -0
  180. package/dist/routes/artifacts.d.ts.map +1 -0
  181. package/dist/routes/artifacts.js +265 -0
  182. package/dist/routes/artifacts.js.map +1 -0
  183. package/dist/routes/audit.d.ts +6 -0
  184. package/dist/routes/audit.d.ts.map +1 -0
  185. package/dist/routes/audit.js +162 -0
  186. package/dist/routes/audit.js.map +1 -0
  187. package/dist/routes/auth.d.ts +47 -0
  188. package/dist/routes/auth.d.ts.map +1 -0
  189. package/dist/routes/auth.js +630 -0
  190. package/dist/routes/auth.js.map +1 -0
  191. package/dist/routes/capabilities.d.ts +14 -0
  192. package/dist/routes/capabilities.d.ts.map +1 -0
  193. package/dist/routes/capabilities.js +62 -0
  194. package/dist/routes/capabilities.js.map +1 -0
  195. package/dist/routes/compliance.d.ts +37 -0
  196. package/dist/routes/compliance.d.ts.map +1 -0
  197. package/dist/routes/compliance.js +321 -0
  198. package/dist/routes/compliance.js.map +1 -0
  199. package/dist/routes/conversations.d.ts +13 -0
  200. package/dist/routes/conversations.d.ts.map +1 -0
  201. package/dist/routes/conversations.js +295 -0
  202. package/dist/routes/conversations.js.map +1 -0
  203. package/dist/routes/cost.d.ts +24 -0
  204. package/dist/routes/cost.d.ts.map +1 -0
  205. package/dist/routes/cost.js +243 -0
  206. package/dist/routes/cost.js.map +1 -0
  207. package/dist/routes/deployments.d.ts +122 -0
  208. package/dist/routes/deployments.d.ts.map +1 -0
  209. package/dist/routes/deployments.js +1036 -0
  210. package/dist/routes/deployments.js.map +1 -0
  211. package/dist/routes/env.d.ts +62 -0
  212. package/dist/routes/env.d.ts.map +1 -0
  213. package/dist/routes/env.js +311 -0
  214. package/dist/routes/env.js.map +1 -0
  215. package/dist/routes/eval-runs.d.ts +28 -0
  216. package/dist/routes/eval-runs.d.ts.map +1 -0
  217. package/dist/routes/eval-runs.js +479 -0
  218. package/dist/routes/eval-runs.js.map +1 -0
  219. package/dist/routes/eval-suites.d.ts +24 -0
  220. package/dist/routes/eval-suites.d.ts.map +1 -0
  221. package/dist/routes/eval-suites.js +381 -0
  222. package/dist/routes/eval-suites.js.map +1 -0
  223. package/dist/routes/event-triggers.d.ts +12 -0
  224. package/dist/routes/event-triggers.d.ts.map +1 -0
  225. package/dist/routes/event-triggers.js +218 -0
  226. package/dist/routes/event-triggers.js.map +1 -0
  227. package/dist/routes/flows.d.ts +23 -0
  228. package/dist/routes/flows.d.ts.map +1 -0
  229. package/dist/routes/flows.js +344 -0
  230. package/dist/routes/flows.js.map +1 -0
  231. package/dist/routes/guardrails.d.ts +33 -0
  232. package/dist/routes/guardrails.d.ts.map +1 -0
  233. package/dist/routes/guardrails.js +236 -0
  234. package/dist/routes/guardrails.js.map +1 -0
  235. package/dist/routes/identity.d.ts +39 -0
  236. package/dist/routes/identity.d.ts.map +1 -0
  237. package/dist/routes/identity.js +138 -0
  238. package/dist/routes/identity.js.map +1 -0
  239. package/dist/routes/mcp.d.ts +23 -0
  240. package/dist/routes/mcp.d.ts.map +1 -0
  241. package/dist/routes/mcp.js +727 -0
  242. package/dist/routes/mcp.js.map +1 -0
  243. package/dist/routes/memory.d.ts +16 -0
  244. package/dist/routes/memory.d.ts.map +1 -0
  245. package/dist/routes/memory.js +353 -0
  246. package/dist/routes/memory.js.map +1 -0
  247. package/dist/routes/observations.d.ts +19 -0
  248. package/dist/routes/observations.d.ts.map +1 -0
  249. package/dist/routes/observations.js +135 -0
  250. package/dist/routes/observations.js.map +1 -0
  251. package/dist/routes/orgs.d.ts +24 -0
  252. package/dist/routes/orgs.d.ts.map +1 -0
  253. package/dist/routes/orgs.js +208 -0
  254. package/dist/routes/orgs.js.map +1 -0
  255. package/dist/routes/pagination.d.ts +18 -0
  256. package/dist/routes/pagination.d.ts.map +1 -0
  257. package/dist/routes/pagination.js +35 -0
  258. package/dist/routes/pagination.js.map +1 -0
  259. package/dist/routes/policies.d.ts +23 -0
  260. package/dist/routes/policies.d.ts.map +1 -0
  261. package/dist/routes/policies.js +314 -0
  262. package/dist/routes/policies.js.map +1 -0
  263. package/dist/routes/projects.d.ts +45 -0
  264. package/dist/routes/projects.d.ts.map +1 -0
  265. package/dist/routes/projects.js +485 -0
  266. package/dist/routes/projects.js.map +1 -0
  267. package/dist/routes/proposals.d.ts +24 -0
  268. package/dist/routes/proposals.d.ts.map +1 -0
  269. package/dist/routes/proposals.js +581 -0
  270. package/dist/routes/proposals.js.map +1 -0
  271. package/dist/routes/provenance.d.ts +34 -0
  272. package/dist/routes/provenance.d.ts.map +1 -0
  273. package/dist/routes/provenance.js +274 -0
  274. package/dist/routes/provenance.js.map +1 -0
  275. package/dist/routes/providers.d.ts +32 -0
  276. package/dist/routes/providers.d.ts.map +1 -0
  277. package/dist/routes/providers.js +490 -0
  278. package/dist/routes/providers.js.map +1 -0
  279. package/dist/routes/public-run-tokens.d.ts +17 -0
  280. package/dist/routes/public-run-tokens.d.ts.map +1 -0
  281. package/dist/routes/public-run-tokens.js +69 -0
  282. package/dist/routes/public-run-tokens.js.map +1 -0
  283. package/dist/routes/retention.d.ts +19 -0
  284. package/dist/routes/retention.d.ts.map +1 -0
  285. package/dist/routes/retention.js +152 -0
  286. package/dist/routes/retention.js.map +1 -0
  287. package/dist/routes/reviewers.d.ts +5 -0
  288. package/dist/routes/reviewers.d.ts.map +1 -0
  289. package/dist/routes/reviewers.js +161 -0
  290. package/dist/routes/reviewers.js.map +1 -0
  291. package/dist/routes/runs.d.ts +34 -0
  292. package/dist/routes/runs.d.ts.map +1 -0
  293. package/dist/routes/runs.js +773 -0
  294. package/dist/routes/runs.js.map +1 -0
  295. package/dist/routes/s3.d.ts +31 -0
  296. package/dist/routes/s3.d.ts.map +1 -0
  297. package/dist/routes/s3.js +433 -0
  298. package/dist/routes/s3.js.map +1 -0
  299. package/dist/routes/schedules.d.ts +20 -0
  300. package/dist/routes/schedules.d.ts.map +1 -0
  301. package/dist/routes/schedules.js +234 -0
  302. package/dist/routes/schedules.js.map +1 -0
  303. package/dist/routes/scope-params.d.ts +57 -0
  304. package/dist/routes/scope-params.d.ts.map +1 -0
  305. package/dist/routes/scope-params.js +105 -0
  306. package/dist/routes/scope-params.js.map +1 -0
  307. package/dist/routes/secret-ref.d.ts +20 -0
  308. package/dist/routes/secret-ref.d.ts.map +1 -0
  309. package/dist/routes/secret-ref.js +42 -0
  310. package/dist/routes/secret-ref.js.map +1 -0
  311. package/dist/routes/secrets.d.ts +53 -0
  312. package/dist/routes/secrets.d.ts.map +1 -0
  313. package/dist/routes/secrets.js +715 -0
  314. package/dist/routes/secrets.js.map +1 -0
  315. package/dist/routes/signing-keys.d.ts +23 -0
  316. package/dist/routes/signing-keys.d.ts.map +1 -0
  317. package/dist/routes/signing-keys.js +212 -0
  318. package/dist/routes/signing-keys.js.map +1 -0
  319. package/dist/routes/sse.d.ts +58 -0
  320. package/dist/routes/sse.d.ts.map +1 -0
  321. package/dist/routes/sse.js +92 -0
  322. package/dist/routes/sse.js.map +1 -0
  323. package/dist/routes/teams.d.ts +30 -0
  324. package/dist/routes/teams.d.ts.map +1 -0
  325. package/dist/routes/teams.js +443 -0
  326. package/dist/routes/teams.js.map +1 -0
  327. package/dist/routes/tenant.d.ts +57 -0
  328. package/dist/routes/tenant.d.ts.map +1 -0
  329. package/dist/routes/tenant.js +352 -0
  330. package/dist/routes/tenant.js.map +1 -0
  331. package/dist/routes/tokens.d.ts +14 -0
  332. package/dist/routes/tokens.d.ts.map +1 -0
  333. package/dist/routes/tokens.js +231 -0
  334. package/dist/routes/tokens.js.map +1 -0
  335. package/dist/routes/tools.d.ts +33 -0
  336. package/dist/routes/tools.d.ts.map +1 -0
  337. package/dist/routes/tools.js +380 -0
  338. package/dist/routes/tools.js.map +1 -0
  339. package/dist/routes/webhook-endpoints.d.ts +10 -0
  340. package/dist/routes/webhook-endpoints.d.ts.map +1 -0
  341. package/dist/routes/webhook-endpoints.js +407 -0
  342. package/dist/routes/webhook-endpoints.js.map +1 -0
  343. package/dist/routes/webhooks.d.ts +21 -0
  344. package/dist/routes/webhooks.d.ts.map +1 -0
  345. package/dist/routes/webhooks.js +228 -0
  346. package/dist/routes/webhooks.js.map +1 -0
  347. package/dist/s3-credential-binding.d.ts +54 -0
  348. package/dist/s3-credential-binding.d.ts.map +1 -0
  349. package/dist/s3-credential-binding.js +4 -0
  350. package/dist/s3-credential-binding.js.map +1 -0
  351. package/dist/secrets-binding.d.ts +281 -0
  352. package/dist/secrets-binding.d.ts.map +1 -0
  353. package/dist/secrets-binding.js +10 -0
  354. package/dist/secrets-binding.js.map +1 -0
  355. package/dist/secrets-provider-binding.d.ts +164 -0
  356. package/dist/secrets-provider-binding.d.ts.map +1 -0
  357. package/dist/secrets-provider-binding.js +4 -0
  358. package/dist/secrets-provider-binding.js.map +1 -0
  359. package/dist/session-store-binding.d.ts +148 -0
  360. package/dist/session-store-binding.d.ts.map +1 -0
  361. package/dist/session-store-binding.js +4 -0
  362. package/dist/session-store-binding.js.map +1 -0
  363. package/dist/signing-key-binding.d.ts +210 -0
  364. package/dist/signing-key-binding.d.ts.map +1 -0
  365. package/dist/signing-key-binding.js +4 -0
  366. package/dist/signing-key-binding.js.map +1 -0
  367. package/dist/state-store-binding.d.ts +53 -0
  368. package/dist/state-store-binding.d.ts.map +1 -0
  369. package/dist/state-store-binding.js +38 -0
  370. package/dist/state-store-binding.js.map +1 -0
  371. package/dist/supervisor-binding.d.ts +526 -0
  372. package/dist/supervisor-binding.d.ts.map +1 -0
  373. package/dist/supervisor-binding.js +4 -0
  374. package/dist/supervisor-binding.js.map +1 -0
  375. package/dist/tenant-host-access.d.ts +41 -0
  376. package/dist/tenant-host-access.d.ts.map +1 -0
  377. package/dist/tenant-host-access.js +42 -0
  378. package/dist/tenant-host-access.js.map +1 -0
  379. package/dist/token-admin.d.ts +99 -0
  380. package/dist/token-admin.d.ts.map +1 -0
  381. package/dist/token-admin.js +4 -0
  382. package/dist/token-admin.js.map +1 -0
  383. package/dist/tool-binding.d.ts +247 -0
  384. package/dist/tool-binding.d.ts.map +1 -0
  385. package/dist/tool-binding.js +4 -0
  386. package/dist/tool-binding.js.map +1 -0
  387. package/dist/trigger-binding.d.ts +13 -0
  388. package/dist/trigger-binding.d.ts.map +1 -0
  389. package/dist/trigger-binding.js +4 -0
  390. package/dist/trigger-binding.js.map +1 -0
  391. package/dist/types.d.ts +71 -0
  392. package/dist/types.d.ts.map +1 -0
  393. package/dist/types.js +4 -0
  394. package/dist/types.js.map +1 -0
  395. package/dist/webhook-endpoint-binding.d.ts +230 -0
  396. package/dist/webhook-endpoint-binding.d.ts.map +1 -0
  397. package/dist/webhook-endpoint-binding.js +12 -0
  398. package/dist/webhook-endpoint-binding.js.map +1 -0
  399. package/dist/xml.d.ts +79 -0
  400. package/dist/xml.d.ts.map +1 -0
  401. package/dist/xml.js +115 -0
  402. package/dist/xml.js.map +1 -0
  403. package/openapi.json +29659 -0
  404. package/package.json +76 -4
  405. package/scripts/emit-openapi.mjs +44 -0
  406. package/src/adapter-binding.ts +204 -0
  407. package/src/agent-binding.ts +231 -0
  408. package/src/app.ts +1285 -0
  409. package/src/capability-binding.ts +83 -0
  410. package/src/cost-binding.ts +213 -0
  411. package/src/deployment-binding.ts +225 -0
  412. package/src/env-binding.ts +140 -0
  413. package/src/errors.ts +291 -0
  414. package/src/eval-run-binding.ts +209 -0
  415. package/src/eval-run-dispatcher.ts +497 -0
  416. package/src/eval-suite-binding.ts +257 -0
  417. package/src/event-bus-binding.ts +127 -0
  418. package/src/flow-binding.ts +213 -0
  419. package/src/guardrail-binding.ts +151 -0
  420. package/src/handler-binding.ts +132 -0
  421. package/src/hitl-binding.ts +172 -0
  422. package/src/identity-directory-binding.ts +128 -0
  423. package/src/identity-provider-binding.ts +203 -0
  424. package/src/image-registry-binding.ts +193 -0
  425. package/src/index.ts +606 -0
  426. package/src/key-management-binding.ts +91 -0
  427. package/src/mcp-endpoint-binding.ts +311 -0
  428. package/src/memory-binding.ts +239 -0
  429. package/src/middleware/auth.ts +437 -0
  430. package/src/middleware/authorize.ts +125 -0
  431. package/src/middleware/error-mapper.ts +37 -0
  432. package/src/middleware/idempotency.ts +160 -0
  433. package/src/middleware/principal.ts +64 -0
  434. package/src/middleware/public-run-routes.ts +50 -0
  435. package/src/middleware/request-id.ts +28 -0
  436. package/src/middleware/sigv4-verify.ts +374 -0
  437. package/src/middleware/sigv4.ts +165 -0
  438. package/src/openapi/generate.ts +307 -0
  439. package/src/openapi/operations.ts +5445 -0
  440. package/src/openapi/schemas.ts +6363 -0
  441. package/src/provenance-binding.ts +169 -0
  442. package/src/provider-binding.ts +196 -0
  443. package/src/public-run-token.ts +263 -0
  444. package/src/retention-binding.ts +78 -0
  445. package/src/reviewer-binding.ts +99 -0
  446. package/src/rotation-status-store.ts +209 -0
  447. package/src/routes/adapters.ts +319 -0
  448. package/src/routes/agents.ts +424 -0
  449. package/src/routes/approvals.ts +759 -0
  450. package/src/routes/artifacts.ts +317 -0
  451. package/src/routes/audit.ts +219 -0
  452. package/src/routes/auth.ts +806 -0
  453. package/src/routes/capabilities.ts +79 -0
  454. package/src/routes/compliance.ts +482 -0
  455. package/src/routes/conversations.ts +369 -0
  456. package/src/routes/cost.ts +327 -0
  457. package/src/routes/deployments.ts +1493 -0
  458. package/src/routes/env.ts +429 -0
  459. package/src/routes/eval-runs.ts +631 -0
  460. package/src/routes/eval-suites.ts +490 -0
  461. package/src/routes/event-triggers.ts +283 -0
  462. package/src/routes/flows.ts +423 -0
  463. package/src/routes/guardrails.ts +320 -0
  464. package/src/routes/identity.ts +203 -0
  465. package/src/routes/mcp.ts +940 -0
  466. package/src/routes/memory.ts +430 -0
  467. package/src/routes/observations.ts +175 -0
  468. package/src/routes/orgs.ts +273 -0
  469. package/src/routes/pagination.ts +51 -0
  470. package/src/routes/policies.ts +413 -0
  471. package/src/routes/projects.ts +634 -0
  472. package/src/routes/proposals.ts +719 -0
  473. package/src/routes/provenance.ts +406 -0
  474. package/src/routes/providers.ts +624 -0
  475. package/src/routes/public-run-tokens.ts +104 -0
  476. package/src/routes/retention.ts +193 -0
  477. package/src/routes/reviewers.ts +209 -0
  478. package/src/routes/runs.ts +981 -0
  479. package/src/routes/s3.ts +519 -0
  480. package/src/routes/schedules.ts +300 -0
  481. package/src/routes/scope-params.ts +133 -0
  482. package/src/routes/secret-ref.ts +52 -0
  483. package/src/routes/secrets.ts +975 -0
  484. package/src/routes/signing-keys.ts +259 -0
  485. package/src/routes/sse.ts +148 -0
  486. package/src/routes/teams.ts +584 -0
  487. package/src/routes/tenant.ts +530 -0
  488. package/src/routes/tokens.ts +285 -0
  489. package/src/routes/tools.ts +491 -0
  490. package/src/routes/webhook-endpoints.ts +534 -0
  491. package/src/routes/webhooks.ts +296 -0
  492. package/src/s3-credential-binding.ts +58 -0
  493. package/src/secrets-binding.ts +308 -0
  494. package/src/secrets-provider-binding.ts +167 -0
  495. package/src/session-store-binding.ts +163 -0
  496. package/src/signing-key-binding.ts +235 -0
  497. package/src/state-store-binding.ts +84 -0
  498. package/src/supervisor-binding.ts +554 -0
  499. package/src/tenant-host-access.ts +62 -0
  500. package/src/token-admin.ts +104 -0
  501. package/src/tool-binding.ts +274 -0
  502. package/src/trigger-binding.ts +38 -0
  503. package/src/types.ts +74 -0
  504. package/src/webhook-endpoint-binding.ts +246 -0
  505. package/src/xml.ts +196 -0
@@ -0,0 +1,1493 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import { createHash } from 'node:crypto';
5
+
6
+ import { Hono } from 'hono';
7
+
8
+ import type { AgentId } from '@kindgi/agents';
9
+ import { tuplesForCreate } from '@kindgi/authz';
10
+ import { parsePublicKeyPem, verifyEd25519 } from '@kindgi/crypto';
11
+ import { type Guardrail, validateGuardrailSpec } from '@kindgi/guardrails';
12
+ import type { ProjectBinding, Scope } from '@kindgi/platform';
13
+ import { validateToolManifest } from '@kindgi/tools';
14
+ import type {
15
+ Cursor,
16
+ FlowId,
17
+ GuardrailId,
18
+ ProjectId,
19
+ SigningKeyId,
20
+ TenantId,
21
+ ToolId,
22
+ } from '@kindgi/types';
23
+
24
+ import type { AgentRegistryBinding } from '../agent-binding.js';
25
+ import type {
26
+ Deployment,
27
+ DeploymentBinding,
28
+ DeploymentPrimitiveCounts,
29
+ DeploymentRegisterOutcome,
30
+ } from '../deployment-binding.js';
31
+ import { statusFor, toWireError } from '../errors.js';
32
+ import type { FlowRegistryBinding } from '../flow-binding.js';
33
+ import type { GuardrailRegistryBinding } from '../guardrail-binding.js';
34
+ import type { ImageRegistryBinding } from '../image-registry-binding.js';
35
+ import type { SecretBinding } from '../secrets-binding.js';
36
+ import type { SigningKeyBinding as SigningKeyRegistryBinding } from '../signing-key-binding.js';
37
+ import type { ToolRegistryBinding } from '../tool-binding.js';
38
+ import type { AppEnv } from '../types.js';
39
+ import { hasCapability, requireEnvName } from './env.js';
40
+ import type { GuardrailWriteHook } from './guardrails.js';
41
+ import { clampLimit } from './pagination.js';
42
+ import type { ToolWriteHook } from './tools.js';
43
+
44
+ /**
45
+ * Deployments resource routes — the landing surface for signed pack
46
+ * deploys:
47
+ *
48
+ * - `POST /v1/deployments` — signed deployment (atomic).
49
+ * - `GET /v1/deployments` — cursor-paginated list.
50
+ * - `GET /v1/deployments/:deploymentId` — single record.
51
+ * - `POST /v1/deployments/:deploymentId/secrets` — deployment-scoped secret sync.
52
+ *
53
+ * ### Atomic transaction (POST)
54
+ *
55
+ * The route runs four verification steps IN ORDER (1–4 below) before any
56
+ * registry write, then does upserts + records deployment (5–6) as a single
57
+ * all-or-nothing unit. Any failure past the first upsert rolls back the successfully-
58
+ * inserted primitives so the tenant's catalog never observes a partial
59
+ * deploy.
60
+ *
61
+ * 1. Body shape (id/version/signature fields present + typed).
62
+ * 2. Signature — canonicalise `{imageDigest, artifactVersion,
63
+ * indexHash, tenantId, publishedAt}` as sorted-key JSON (UTF-8,
64
+ * no whitespace); verify with Ed25519 against `signerPublicKey`;
65
+ * cross-check `(signerKeyId, publicKey)` on the tenant's trust
66
+ * list via `SigningKeyRegistryBinding.isTrusted`.
67
+ * 3. Image — probe via `ImageRegistryBinding.head`, verify digest in
68
+ * the image ref matches the descriptor; extract `/app/index.json`,
69
+ * verify its sha256 matches the signed `indexHash` and its
70
+ * `artifactVersion` is the signed one.
71
+ * 4. Manifest validation — the image's `/app/index.json` (not a copy
72
+ * from the request: only the image's bytes are signed). Each
73
+ * `tools[]`, `guardrails[]`, `agents[]`, `flows[]` runs through its
74
+ * validator. A tool's `modulePath` and a
75
+ * guardrail's `checkModulePath` become `codeArtifactRef: { kind:
76
+ * 'oci', imageRef, modulePath, artifactVersion }`, which is how the
77
+ * runtime knows the code runs in the pack service; a guardrail's
78
+ * `checkId` becomes its `check`. Agents and flows are declarative:
79
+ * their `modulePath` is dropped.
80
+ * 5. Upserts — tools + guardrails + agents + flows. Track which were
81
+ * `ok` (newly registered) vs `already-registered` so rollback only
82
+ * unwinds our own writes.
83
+ * 6. Deployment record via `DeploymentBinding.register`. On failure,
84
+ * rollback all `ok` upserts and return 500.
85
+ *
86
+ * ### Idempotency
87
+ *
88
+ * Two layers:
89
+ * - `Idempotency-Key` middleware caches the full HTTP response — a
90
+ * retry with the same key + same body replays the original 201.
91
+ * - Digest idempotency handled inside `DeploymentBinding.register`:
92
+ * the same `imageDigest` re-submitted under the same tenant returns
93
+ * `{ kind: 'already-registered', deployment }` and the route surfaces
94
+ * it as `200` (NOT `201`), same `deploymentId` + `artifactVersion`.
95
+ *
96
+ * ### Immutable records
97
+ *
98
+ * No PATCH, no DELETE. Each record lists exactly what it shipped
99
+ * (`contents`: ids and versions). Versions are immutable and an image
100
+ * digest deploys once, so the latest deployment is the live one, and a
101
+ * rollback rolls forward: deploy the earlier code again, under new
102
+ * versions. (Re-activating an earlier record instead would need every
103
+ * resolver to resolve only within the active deployment's versions.)
104
+ *
105
+ * The route is register-only + read-only.
106
+ */
107
+ export interface DeploymentsRouterBindings {
108
+ readonly deploymentRegistry: DeploymentBinding;
109
+ readonly signingKeyRegistry: SigningKeyRegistryBinding;
110
+ readonly imageRegistry: ImageRegistryBinding;
111
+ readonly toolRegistry?: ToolRegistryBinding;
112
+ readonly guardrailRegistry?: GuardrailRegistryBinding;
113
+ readonly agentRegistry?: AgentRegistryBinding;
114
+ readonly flowRegistry?: FlowRegistryBinding;
115
+ /**
116
+ * REQUIRED at runtime for `POST /v1/deployments/:deploymentId/secrets`.
117
+ * Optional at the type level so app compositions without a secrets
118
+ * binding keep booting — the sync route is always mounted and answers
119
+ * `500` when this is not wired; the register / list / get routes remain
120
+ * functional without it.
121
+ */
122
+ readonly secretsBinding?: SecretBinding;
123
+ /**
124
+ * OPTIONAL at the type level, but REQUIRED at runtime any time
125
+ * `validated.agents.length > 0` OR `validated.flows.length > 0`
126
+ * OR `validated.tools.length > 0` OR `validated.guardrails.length > 0`.
127
+ * The agent / flow / tool / guardrail register loops resolve the
128
+ * tenant's Default project via `projectBinding.getDefault(tenantId)`
129
+ * and thread the resolved id through
130
+ * `AgentRegistryBinding.publish(...)` /
131
+ * `FlowRegistryBinding.publish(...)` /
132
+ * `ToolRegistryBinding.register(...)` /
133
+ * `GuardrailRegistryBinding.register(...)` (content-scoped writes
134
+ * always carry a project; deploys target the Default project).
135
+ * A deployment that carries any of those primitives without this
136
+ * binding wired fails fast at the loop entrypoint with a clear 500
137
+ * message. All four register loops resolve the project the same way.
138
+ */
139
+ readonly projectBinding?: ProjectBinding;
140
+ /**
141
+ * Called for each tool and guardrail a new deployment registered, as
142
+ * `POST /v1/tools` and `POST /v1/guardrails` call them, so a runtime
143
+ * that caches the catalogs (its tool and guardrail bridges) sees the
144
+ * deployment at once rather than at restart. Advisory: a failing hook
145
+ * doesn't fail the deployment.
146
+ */
147
+ readonly onToolWrite?: ToolWriteHook;
148
+ readonly onGuardrailWrite?: GuardrailWriteHook;
149
+ }
150
+
151
+ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<AppEnv> {
152
+ const r = new Hono<AppEnv>();
153
+
154
+ // ---------- GET / (list, cursor-paginated) ----------
155
+ r.get('/', async (c) => {
156
+ const requestId = c.get('requestId');
157
+ const tenantId = c.get('tenantId') as TenantId;
158
+ const limit = clampLimit(c.req.query('limit'));
159
+
160
+ const cursorRaw = c.req.query('cursor');
161
+ const imageRefPrefixRaw = c.req.query('imageRefPrefix');
162
+ const signerKeyIdRaw = c.req.query('signerKeyId');
163
+
164
+ try {
165
+ const page = await bindings.deploymentRegistry.list({
166
+ tenantId,
167
+ limit,
168
+ ...(cursorRaw !== undefined && cursorRaw.length > 0 && { cursor: cursorRaw as Cursor }),
169
+ ...(imageRefPrefixRaw !== undefined &&
170
+ imageRefPrefixRaw.length > 0 && {
171
+ imageRefPrefix: imageRefPrefixRaw,
172
+ }),
173
+ ...(signerKeyIdRaw !== undefined &&
174
+ signerKeyIdRaw.length > 0 && {
175
+ signerKeyId: signerKeyIdRaw as SigningKeyId,
176
+ }),
177
+ });
178
+ return c.json({
179
+ data: page.data.map(serializeDeployment),
180
+ hasMore: page.nextCursor !== undefined,
181
+ ...(page.nextCursor !== undefined && {
182
+ nextCursor: page.nextCursor as unknown as string,
183
+ }),
184
+ });
185
+ } catch (cause) {
186
+ c.status(statusFor('internal-server-error') as never);
187
+ return c.json(
188
+ toWireError(
189
+ {
190
+ code: 'internal-server-error',
191
+ message: `Deployment list failed: ${errMessage(cause)}`,
192
+ },
193
+ requestId,
194
+ ),
195
+ );
196
+ }
197
+ });
198
+
199
+ // ---------- GET /:deploymentId ----------
200
+ r.get('/:deploymentId', async (c) => {
201
+ const requestId = c.get('requestId');
202
+ const tenantId = c.get('tenantId') as TenantId;
203
+ const deploymentId = c.req.param('deploymentId');
204
+
205
+ const record = await bindings.deploymentRegistry.get({ tenantId, deploymentId });
206
+ if (record === null) {
207
+ c.status(statusFor('deployment-not-found') as never);
208
+ return c.json(
209
+ toWireError(
210
+ {
211
+ code: 'deployment-not-found',
212
+ message: `No deployment with id "${deploymentId}" under this tenant`,
213
+ deploymentId,
214
+ },
215
+ requestId,
216
+ ),
217
+ );
218
+ }
219
+ return c.json(serializeDeployment(record));
220
+ });
221
+
222
+ // ---------- POST / (register a signed deployment) ----------
223
+ r.post('/', async (c) => {
224
+ const requestId = c.get('requestId');
225
+ const tenantId = c.get('tenantId') as TenantId;
226
+
227
+ let body: unknown;
228
+ try {
229
+ body = await c.req.json();
230
+ } catch {
231
+ c.status(statusFor('bad-input') as never);
232
+ return c.json(
233
+ toWireError({ code: 'bad-input', message: 'Request body must be valid JSON' }, requestId),
234
+ );
235
+ }
236
+ const parsed = parseWireBody(body);
237
+ if (parsed.kind === 'err') {
238
+ c.status(statusFor('bad-input') as never);
239
+ return c.json(
240
+ toWireError(
241
+ {
242
+ code: 'bad-input',
243
+ message: parsed.error.message,
244
+ issues: parsed.error.issues as unknown as Record<string, unknown>[],
245
+ },
246
+ requestId,
247
+ ),
248
+ );
249
+ }
250
+ const wire = parsed.value;
251
+
252
+ // Extract image digest from imageRef. Must be `<host>/<repo>@sha256:<hex>`.
253
+ const digestFromRef = extractImageDigest(wire.imageRef);
254
+ if (digestFromRef === null) {
255
+ c.status(statusFor('bad-input') as never);
256
+ return c.json(
257
+ toWireError(
258
+ {
259
+ code: 'bad-input',
260
+ message: 'imageRef must be digest-pinned: "<host>/<repo>@sha256:<64-hex>"',
261
+ },
262
+ requestId,
263
+ ),
264
+ );
265
+ }
266
+ const imageDigest = digestFromRef;
267
+
268
+ // ---- Signature verification ----
269
+ const publicKeyBytes = parsePublicKeyPem(wire.signerPublicKey);
270
+ if (publicKeyBytes.kind === 'err') {
271
+ c.status(statusFor('bad-input') as never);
272
+ return c.json(
273
+ toWireError(
274
+ {
275
+ code: 'bad-input',
276
+ message: `signerPublicKey PEM invalid: ${publicKeyBytes.error.message}`,
277
+ },
278
+ requestId,
279
+ ),
280
+ );
281
+ }
282
+ const publicKeyBase64 = Buffer.from(publicKeyBytes.value).toString('base64');
283
+ const trusted = await bindings.signingKeyRegistry.isTrusted({
284
+ tenantId,
285
+ keyId: wire.signerKeyId,
286
+ publicKey: publicKeyBase64,
287
+ });
288
+ if (trusted.kind === 'err') {
289
+ c.status(statusFor('internal-server-error') as never);
290
+ return c.json(
291
+ toWireError(
292
+ {
293
+ code: 'internal-server-error',
294
+ message: `Trust check failed: ${trusted.error.message}`,
295
+ },
296
+ requestId,
297
+ ),
298
+ );
299
+ }
300
+ if (!trusted.value) {
301
+ c.status(statusFor('signer-not-trusted') as never);
302
+ return c.json(
303
+ toWireError(
304
+ {
305
+ code: 'signer-not-trusted',
306
+ message: `Signer key "${wire.signerKeyId as unknown as string}" is not on this tenant's trust list`,
307
+ signerKeyId: wire.signerKeyId as unknown as string,
308
+ },
309
+ requestId,
310
+ ),
311
+ );
312
+ }
313
+
314
+ const canonicalEnvelope = canonicalizeEnvelope({
315
+ imageDigest,
316
+ artifactVersion: wire.artifactVersion,
317
+ indexHash: wire.indexHash,
318
+ tenantId: tenantId as unknown as string,
319
+ publishedAt: wire.publishedAt,
320
+ });
321
+ let signatureBytes: Buffer;
322
+ try {
323
+ signatureBytes = Buffer.from(wire.signature, 'base64');
324
+ } catch {
325
+ c.status(statusFor('signature-invalid') as never);
326
+ return c.json(
327
+ toWireError(
328
+ { code: 'signature-invalid', message: 'signature is not valid base64' },
329
+ requestId,
330
+ ),
331
+ );
332
+ }
333
+ const verification = verifyEd25519(
334
+ publicKeyBytes.value,
335
+ canonicalEnvelope,
336
+ new Uint8Array(signatureBytes),
337
+ );
338
+ if (verification.kind === 'err') {
339
+ c.status(statusFor('signature-invalid') as never);
340
+ return c.json(
341
+ toWireError(
342
+ {
343
+ code: 'signature-invalid',
344
+ message: `Signature verification rejected: ${verification.error.message}`,
345
+ },
346
+ requestId,
347
+ ),
348
+ );
349
+ }
350
+ if (!verification.value) {
351
+ c.status(statusFor('signature-invalid') as never);
352
+ return c.json(
353
+ toWireError(
354
+ {
355
+ code: 'signature-invalid',
356
+ message:
357
+ 'Ed25519 signature does not match canonical envelope { imageDigest, artifactVersion, indexHash, tenantId, publishedAt }',
358
+ },
359
+ requestId,
360
+ ),
361
+ );
362
+ }
363
+
364
+ // ---- Image pullability + index integrity ----
365
+ const head = await bindings.imageRegistry.head({ tenantId, imageRef: wire.imageRef });
366
+ if (head.kind === 'err') {
367
+ c.status(statusFor('image-unverifiable') as never);
368
+ return c.json(
369
+ toWireError(
370
+ {
371
+ code: 'image-unverifiable',
372
+ message: `Image head probe failed: ${head.error.message}`,
373
+ imageRef: wire.imageRef,
374
+ cause: head.error.code,
375
+ },
376
+ requestId,
377
+ ),
378
+ );
379
+ }
380
+ if (head.value.digest !== imageDigest) {
381
+ c.status(statusFor('image-unverifiable') as never);
382
+ return c.json(
383
+ toWireError(
384
+ {
385
+ code: 'image-unverifiable',
386
+ message: `Registry digest ${head.value.digest} does not match imageRef digest ${imageDigest}`,
387
+ imageRef: wire.imageRef,
388
+ },
389
+ requestId,
390
+ ),
391
+ );
392
+ }
393
+
394
+ const indexFile = await bindings.imageRegistry.extractFile({
395
+ tenantId,
396
+ imageRef: wire.imageRef,
397
+ path: '/app/index.json',
398
+ });
399
+ if (indexFile.kind === 'err') {
400
+ c.status(statusFor('image-unverifiable') as never);
401
+ return c.json(
402
+ toWireError(
403
+ {
404
+ code: 'image-unverifiable',
405
+ message: `Extract /app/index.json failed: ${indexFile.error.message}`,
406
+ imageRef: wire.imageRef,
407
+ cause: indexFile.error.code,
408
+ },
409
+ requestId,
410
+ ),
411
+ );
412
+ }
413
+ const normalisedIndexHash = normaliseSha256Hex(indexFile.value.sha256);
414
+ const normalisedWireIndexHash = normaliseSha256Hex(wire.indexHash);
415
+ if (normalisedIndexHash !== normalisedWireIndexHash) {
416
+ c.status(statusFor('image-unverifiable') as never);
417
+ return c.json(
418
+ toWireError(
419
+ {
420
+ code: 'image-unverifiable',
421
+ message: `Image /app/index.json sha256 (${normalisedIndexHash}) does not match signed indexHash (${normalisedWireIndexHash})`,
422
+ imageRef: wire.imageRef,
423
+ },
424
+ requestId,
425
+ ),
426
+ );
427
+ }
428
+
429
+ // What registers is the image's own index, the bytes the signature
430
+ // covers (through indexHash), never a copy from the request.
431
+ const imageIndex = parseImageIndex(indexFile.value.bytes);
432
+ if (imageIndex === undefined) {
433
+ c.status(statusFor('image-unverifiable') as never);
434
+ return c.json(
435
+ toWireError(
436
+ {
437
+ code: 'image-unverifiable',
438
+ message: 'Image /app/index.json is not a JSON object',
439
+ imageRef: wire.imageRef,
440
+ },
441
+ requestId,
442
+ ),
443
+ );
444
+ }
445
+ if (imageIndex.artifactVersion !== wire.artifactVersion) {
446
+ c.status(statusFor('image-unverifiable') as never);
447
+ return c.json(
448
+ toWireError(
449
+ {
450
+ code: 'image-unverifiable',
451
+ message: `Image /app/index.json is artifact ${JSON.stringify(imageIndex.artifactVersion)}, not the signed ${wire.artifactVersion}`,
452
+ imageRef: wire.imageRef,
453
+ },
454
+ requestId,
455
+ ),
456
+ );
457
+ }
458
+
459
+ // ---- Manifest validation ----
460
+ const validation = validatePrimitives(imageIndex, {
461
+ imageRef: wire.imageRef,
462
+ artifactVersion: wire.artifactVersion,
463
+ });
464
+ if (validation.kind === 'err') {
465
+ c.status(statusFor('deployment-validation-failed') as never);
466
+ return c.json(
467
+ toWireError(
468
+ {
469
+ code: 'deployment-validation-failed',
470
+ message: `Deployment manifest validation failed (${validation.error.details.length} issue${validation.error.details.length === 1 ? '' : 's'})`,
471
+ issues: validation.error.details as unknown as Record<string, unknown>[],
472
+ },
473
+ requestId,
474
+ ),
475
+ );
476
+ }
477
+ const validated = validation.value;
478
+
479
+ // ---- Registry upserts (with rollback tracking) ----
480
+ //
481
+ // Agent-registry, flow-registry, tool-registry, and
482
+ // guardrail-registry writes are all project-scoped. All four loops resolve
483
+ // the tenant's Default project via `projectBinding.getDefault(
484
+ // tenantId)` once per request and thread the resolved id into
485
+ // `AgentRegistryBinding.publish(...)` /
486
+ // `FlowRegistryBinding.publish(...)` /
487
+ // `ToolRegistryBinding.register(...)` /
488
+ // `GuardrailRegistryBinding.register(...)`.
489
+ //
490
+ // Fail-fast: if a deployment brings any of those primitives but
491
+ // no `projectBinding` was wired, we surface `500 internal-server-
492
+ // error` with an operator-actionable message BEFORE any registry
493
+ // writes so rollback stays empty.
494
+ const needsProjectBinding =
495
+ validated.agents.length > 0 ||
496
+ validated.flows.length > 0 ||
497
+ validated.tools.length > 0 ||
498
+ validated.guardrails.length > 0;
499
+ if (needsProjectBinding && bindings.projectBinding === undefined) {
500
+ c.status(statusFor('internal-server-error') as never);
501
+ return c.json(
502
+ toWireError(
503
+ {
504
+ code: 'internal-server-error',
505
+ message:
506
+ 'agent/flow/tool/guardrail publish requires projectBinding; deployment blocked. Wire projectBinding into DeploymentsRouterBindings at app-creation time.',
507
+ },
508
+ requestId,
509
+ ),
510
+ );
511
+ }
512
+ let defaultProjectId: import('@kindgi/types').ProjectId | undefined;
513
+ if (needsProjectBinding && bindings.projectBinding !== undefined) {
514
+ const defaultProject = await bindings.projectBinding.getDefault(tenantId);
515
+ if (defaultProject === undefined) {
516
+ c.status(statusFor('internal-server-error') as never);
517
+ return c.json(
518
+ toWireError(
519
+ {
520
+ code: 'internal-server-error',
521
+ message:
522
+ 'no Default project exists for this tenant; deployment blocked. Every tenant should auto-seed a Default project at creation.',
523
+ },
524
+ requestId,
525
+ ),
526
+ );
527
+ }
528
+ defaultProjectId = defaultProject.id;
529
+ }
530
+
531
+ const rolled: RollbackAction[] = [];
532
+ try {
533
+ if (bindings.toolRegistry !== undefined && defaultProjectId !== undefined) {
534
+ for (const tool of validated.tools) {
535
+ const projectIdForTool = defaultProjectId;
536
+ const outcome = await bindings.toolRegistry.publish({
537
+ tenantId,
538
+ projectId: projectIdForTool,
539
+ tool,
540
+ enqueueTuples: (toolId) =>
541
+ tuplesForCreate({
542
+ kind: 'tool',
543
+ id: toolId as ToolId,
544
+ tenantId,
545
+ projectId: projectIdForTool,
546
+ }),
547
+ });
548
+ if (outcome.kind === 'ok') {
549
+ const toolId = outcome.toolId;
550
+ const version = outcome.version;
551
+ rolled.push(async () => {
552
+ await bindings.toolRegistry?.unregister({ tenantId, toolId, version });
553
+ });
554
+ }
555
+ }
556
+ }
557
+ if (bindings.guardrailRegistry !== undefined && defaultProjectId !== undefined) {
558
+ for (const guardrail of validated.guardrails) {
559
+ const projectIdForGuardrail = defaultProjectId;
560
+ const outcome = await bindings.guardrailRegistry.register({
561
+ tenantId,
562
+ projectId: projectIdForGuardrail,
563
+ guardrail,
564
+ enqueueTuples: (guardrailId) =>
565
+ tuplesForCreate({
566
+ kind: 'guardrail',
567
+ id: guardrailId as GuardrailId,
568
+ tenantId,
569
+ projectId: projectIdForGuardrail,
570
+ }),
571
+ });
572
+ if (outcome.kind === 'ok') {
573
+ const guardrailId = outcome.guardrailId;
574
+ rolled.push(async () => {
575
+ await bindings.guardrailRegistry?.unregister({ tenantId, guardrailId });
576
+ });
577
+ }
578
+ }
579
+ }
580
+ if (bindings.agentRegistry !== undefined && defaultProjectId !== undefined) {
581
+ for (const agent of validated.agents) {
582
+ const projectIdForAgent = defaultProjectId;
583
+ const outcome = await bindings.agentRegistry.publish({
584
+ tenantId,
585
+ projectId: projectIdForAgent,
586
+ agent,
587
+ // Signed deploys have no per-request principal — pass parent-
588
+ // only tuples (no owner grant). Tenant admins keep access via
589
+ // `admin from parent` cascade.
590
+ enqueueTuples: (agentId) =>
591
+ tuplesForCreate({
592
+ kind: 'agent',
593
+ id: agentId as AgentId,
594
+ tenantId,
595
+ projectId: projectIdForAgent,
596
+ }),
597
+ });
598
+ if (outcome.kind === 'ok') {
599
+ const agentId = outcome.agentId;
600
+ const version = outcome.version;
601
+ rolled.push(async () => {
602
+ await bindings.agentRegistry?.unregister({ tenantId, agentId, version });
603
+ });
604
+ }
605
+ }
606
+ }
607
+ if (bindings.flowRegistry !== undefined && defaultProjectId !== undefined) {
608
+ for (const flow of validated.flows) {
609
+ const projectIdForGraph = defaultProjectId;
610
+ const outcome = await bindings.flowRegistry.publish({
611
+ tenantId,
612
+ projectId: projectIdForGraph,
613
+ flow,
614
+ enqueueTuples: (flowId) =>
615
+ tuplesForCreate({
616
+ kind: 'flow',
617
+ id: flowId as FlowId,
618
+ tenantId,
619
+ projectId: projectIdForGraph,
620
+ }),
621
+ });
622
+ if (outcome.kind === 'ok') {
623
+ const flowId = outcome.flowId;
624
+ const version = outcome.version;
625
+ rolled.push(async () => {
626
+ await bindings.flowRegistry?.unregister({ tenantId, flowId, version });
627
+ });
628
+ }
629
+ }
630
+ }
631
+ } catch (cause) {
632
+ await rollback(rolled);
633
+ c.status(statusFor('internal-server-error') as never);
634
+ return c.json(
635
+ toWireError(
636
+ {
637
+ code: 'internal-server-error',
638
+ message: `Registry upsert failed: ${errMessage(cause)}`,
639
+ },
640
+ requestId,
641
+ ),
642
+ );
643
+ }
644
+
645
+ // ---- Deployment record ----
646
+ const primitives: DeploymentPrimitiveCounts = {
647
+ tools: validated.tools.length,
648
+ guardrails: validated.guardrails.length,
649
+ agents: validated.agents.length,
650
+ flows: validated.flows.length,
651
+ };
652
+
653
+ let outcome: DeploymentRegisterOutcome;
654
+ try {
655
+ outcome = await bindings.deploymentRegistry.register({
656
+ tenantId,
657
+ imageRef: wire.imageRef,
658
+ imageDigest,
659
+ artifactVersion: wire.artifactVersion,
660
+ indexHash: normalisedWireIndexHash,
661
+ signerKeyId: wire.signerKeyId,
662
+ signerPublicKey: publicKeyBase64,
663
+ signature: wire.signature,
664
+ publishedAt: wire.publishedAt,
665
+ primitives,
666
+ contents: {
667
+ tools: validated.tools.map((t) => ({
668
+ id: t.id as unknown as string,
669
+ version: t.version,
670
+ })),
671
+ guardrails: validated.guardrails.map((g) => ({ id: g.id as unknown as string })),
672
+ agents: validated.agents.map((a) => ({
673
+ id: a.id as unknown as string,
674
+ version: a.version as unknown as string,
675
+ })),
676
+ flows: validated.flows.map((f) => ({
677
+ id: f.id as unknown as string,
678
+ version: f.version as unknown as string,
679
+ })),
680
+ },
681
+ });
682
+ } catch (cause) {
683
+ await rollback(rolled);
684
+ c.status(statusFor('internal-server-error') as never);
685
+ return c.json(
686
+ toWireError(
687
+ {
688
+ code: 'internal-server-error',
689
+ message: `Deployment record failed: ${errMessage(cause)}`,
690
+ },
691
+ requestId,
692
+ ),
693
+ );
694
+ }
695
+
696
+ if (outcome.kind === 'error') {
697
+ await rollback(rolled);
698
+ c.status(statusFor('internal-server-error') as never);
699
+ return c.json(
700
+ toWireError(
701
+ {
702
+ code: 'internal-server-error',
703
+ message: `Deployment record failed: ${outcome.message}`,
704
+ cause: outcome.code,
705
+ },
706
+ requestId,
707
+ ),
708
+ );
709
+ }
710
+ if (outcome.kind === 'already-registered') {
711
+ // Digest-based idempotency: same digest already recorded. Roll back
712
+ // any freshly-inserted primitives (the primitive itself is fine —
713
+ // the previous deploy landed it — but re-inserting is idempotent
714
+ // at the primitive layer too, so `ok` outcomes only happen when
715
+ // the primitive was DIFFERENT this run, which is an inconsistent
716
+ // wire body).
717
+ await rollback(rolled);
718
+ c.status(200);
719
+ return c.json(serializeDeployment(outcome.deployment));
720
+ }
721
+
722
+ await notifyWrites(tenantId, validated, bindings);
723
+ c.status(201);
724
+ return c.json(serializeDeployment(outcome.deployment));
725
+ });
726
+
727
+ // ---------- POST /:deploymentId/secrets ----------
728
+ //
729
+ // Deployment-scoped bulk secret sync.
730
+ //
731
+ // Body shape:
732
+ // {
733
+ // envName: EnvName;
734
+ // secrets: Array<{ name, ref } | { name, value }>;
735
+ // }
736
+ //
737
+ // - `{ name, ref }` entries → validated via `SecretBinding.get`. No
738
+ // bytes cross the wire. Missing → 404 `secret-not-found` (matches
739
+ // the framework-wide `secret-not-found` → 404 mapping in
740
+ // `errors.ts`). The `ref` value is echoed
741
+ // verbatim in the response (opaque reference the deployment
742
+ // runtime resolves at dispatch).
743
+ // - `{ name, value }` entries → written via `SecretBinding.set` with
744
+ // `writeMode: 'add-version'` + `tags.deployment = deploymentId`. The
745
+ // response returns the just-minted reference (framework-generated
746
+ // `secret:` scheme so the developer can round-trip).
747
+ //
748
+ // Scope is derived from the target deployment record, NOT accepted from
749
+ // the wire (CRITICAL). The `Deployment` shape carries no `projectId`, so
750
+ // the scope is `{ kind: 'tenant', tenantId }`; `deriveScopeFromDeployment`
751
+ // switches to a project scope if a record does carry one.
752
+ //
753
+ // Capability: `secrets:write` (fail-closed).
754
+ // Idempotency-Key: honoured by the middleware layer.
755
+ r.post('/:deploymentId/secrets', async (c) => {
756
+ const requestId = c.get('requestId');
757
+ const tenantId = c.get('tenantId') as TenantId;
758
+ const deploymentId = c.req.param('deploymentId');
759
+
760
+ if (bindings.secretsBinding === undefined) {
761
+ c.status(statusFor('internal-server-error') as never);
762
+ return c.json(
763
+ toWireError(
764
+ {
765
+ code: 'internal-server-error',
766
+ message:
767
+ 'secretsBinding is not wired; POST /v1/deployments/:deploymentId/secrets is unavailable. ' +
768
+ 'Wire secretsBinding into DeploymentsRouterBindings.',
769
+ },
770
+ requestId,
771
+ ),
772
+ );
773
+ }
774
+
775
+ if (!hasCapability(c, 'secrets:write')) {
776
+ c.status(statusFor('permission-denied') as never);
777
+ return c.json(
778
+ toWireError(
779
+ {
780
+ code: 'permission-denied',
781
+ message:
782
+ 'Bearer token is missing the `secrets:write` capability required for deployment secret sync.',
783
+ },
784
+ requestId,
785
+ ),
786
+ );
787
+ }
788
+
789
+ // Resolve the target deployment first — its `(tenantId, projectId?)`
790
+ // is what determines the write scope.
791
+ const deployment = await bindings.deploymentRegistry.get({ tenantId, deploymentId });
792
+ if (deployment === null) {
793
+ c.status(statusFor('deployment-not-found') as never);
794
+ return c.json(
795
+ toWireError(
796
+ {
797
+ code: 'deployment-not-found',
798
+ message: `No deployment with id "${deploymentId}" under this tenant`,
799
+ deploymentId,
800
+ },
801
+ requestId,
802
+ ),
803
+ );
804
+ }
805
+ const scope = deriveScopeFromDeployment(deployment);
806
+
807
+ let body: unknown;
808
+ try {
809
+ body = await c.req.json();
810
+ } catch {
811
+ c.status(statusFor('bad-input') as never);
812
+ return c.json(
813
+ toWireError({ code: 'bad-input', message: 'Request body must be valid JSON' }, requestId),
814
+ );
815
+ }
816
+ if (body === null || typeof body !== 'object' || Array.isArray(body)) {
817
+ c.status(statusFor('bad-input') as never);
818
+ return c.json(
819
+ toWireError(
820
+ { code: 'bad-input', message: 'Request body must be a JSON object' },
821
+ requestId,
822
+ ),
823
+ );
824
+ }
825
+ const b = body as Record<string, unknown>;
826
+
827
+ const envNameResult = requireEnvName(typeof b.envName === 'string' ? b.envName : undefined);
828
+ if (envNameResult.kind === 'err') {
829
+ c.status(statusFor(envNameResult.code) as never);
830
+ return c.json(
831
+ toWireError({ code: envNameResult.code, message: envNameResult.message }, requestId),
832
+ );
833
+ }
834
+ const envName = envNameResult.envName;
835
+
836
+ if (!Array.isArray(b.secrets)) {
837
+ c.status(statusFor('bad-input') as never);
838
+ return c.json(
839
+ toWireError(
840
+ {
841
+ code: 'bad-input',
842
+ message: '`secrets` must be an array of `{ name, ref }` or `{ name, value }` entries',
843
+ },
844
+ requestId,
845
+ ),
846
+ );
847
+ }
848
+ // Empty array is legal (a no-op sync). Every real entry must be an
849
+ // object with a non-empty `name` and exactly one of `ref` / `value`.
850
+ const parsed: ParsedSyncEntry[] = [];
851
+ for (let i = 0; i < b.secrets.length; i++) {
852
+ const raw = b.secrets[i];
853
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
854
+ c.status(statusFor('bad-input') as never);
855
+ return c.json(
856
+ toWireError(
857
+ {
858
+ code: 'bad-input',
859
+ message: `secrets[${i}] must be an object`,
860
+ },
861
+ requestId,
862
+ ),
863
+ );
864
+ }
865
+ const e = raw as Record<string, unknown>;
866
+ if (typeof e.name !== 'string' || e.name.length === 0) {
867
+ c.status(statusFor('bad-input') as never);
868
+ return c.json(
869
+ toWireError(
870
+ {
871
+ code: 'bad-input',
872
+ message: `secrets[${i}].name is required and must be a non-empty string`,
873
+ },
874
+ requestId,
875
+ ),
876
+ );
877
+ }
878
+ const hasRef = typeof e.ref === 'string';
879
+ const hasValue = typeof e.value === 'string';
880
+ if (hasRef === hasValue) {
881
+ c.status(statusFor('bad-input') as never);
882
+ return c.json(
883
+ toWireError(
884
+ {
885
+ code: 'bad-input',
886
+ message: `secrets[${i}] must set exactly one of \`ref\` or \`value\``,
887
+ },
888
+ requestId,
889
+ ),
890
+ );
891
+ }
892
+ if (hasRef) {
893
+ parsed.push({ kind: 'ref', name: e.name, ref: e.ref as string });
894
+ } else {
895
+ parsed.push({ kind: 'value', name: e.name, value: e.value as string });
896
+ }
897
+ }
898
+
899
+ let resolvedCount = 0;
900
+ let addedCount = 0;
901
+ const references: Array<{ name: string; ref: string; version: number }> = [];
902
+ for (const entry of parsed) {
903
+ if (entry.kind === 'ref') {
904
+ const rec = await bindings.secretsBinding.get({ scope, envName, name: entry.name });
905
+ if (rec === null || rec.revokedAt !== undefined) {
906
+ c.status(statusFor('secret-not-found') as never);
907
+ return c.json(
908
+ toWireError(
909
+ {
910
+ code: 'secret-not-found',
911
+ message: `secrets[${entry.name}]: no secret at (${scope.kind}, ${envName as unknown as string}, ${entry.name}) — write with { name, value } first or fix the ref.`,
912
+ name: entry.name,
913
+ },
914
+ requestId,
915
+ ),
916
+ );
917
+ }
918
+ resolvedCount += 1;
919
+ references.push({ name: entry.name, ref: entry.ref, version: rec.currentVersion });
920
+ } else {
921
+ const outcome = await bindings.secretsBinding.set({
922
+ scope,
923
+ envName,
924
+ name: entry.name,
925
+ value: entry.value,
926
+ writeMode: 'add-version',
927
+ tags: { deployment: deploymentId },
928
+ // Only fires on create-new; here we're adding a version → no-op.
929
+ enqueueTuples: () => [],
930
+ });
931
+ if (outcome.kind !== 'ok') {
932
+ // add-version should never trigger `already-exists`; treat any
933
+ // non-ok as a hard failure (binding-side conflict, encryption
934
+ // failure, etc.). Fail the whole request (atomic model).
935
+ const code =
936
+ outcome.kind === 'version-conflict'
937
+ ? 'secret-write-conflict'
938
+ : outcome.kind === 'already-exists'
939
+ ? 'secret-write-conflict'
940
+ : outcome.code;
941
+ const message =
942
+ outcome.kind === 'version-conflict'
943
+ ? `secrets[${entry.name}]: version conflict (stored=${outcome.currentVersion}); retry.`
944
+ : outcome.kind === 'already-exists'
945
+ ? `secrets[${entry.name}]: already-exists surface on add-version (adapter bug); investigate.`
946
+ : `secrets[${entry.name}]: ${outcome.message}`;
947
+ c.status(statusFor(code) as never);
948
+ return c.json(toWireError({ code, message }, requestId));
949
+ }
950
+ addedCount += 1;
951
+ // Reference format: `secret:<envName>/<name>#<version>`. Opaque
952
+ // to the wire — the deployment runtime resolves via SecretBinding
953
+ // at dispatch time.
954
+ const ref = `secret:${envName as unknown as string}/${entry.name}#${outcome.versionId}`;
955
+ references.push({ name: entry.name, ref, version: outcome.versionId });
956
+ }
957
+ }
958
+
959
+ return c.json({
960
+ resolved: resolvedCount,
961
+ added: addedCount,
962
+ references,
963
+ });
964
+ });
965
+
966
+ return r;
967
+ }
968
+
969
+ // ------------------------------------------------------------
970
+ // Scope derivation for deployment secret sync
971
+ // ------------------------------------------------------------
972
+
973
+ /**
974
+ * Derive the write scope for a deployment secret-sync request from
975
+ * the deployment record itself. Never accepts a caller-supplied scope
976
+ * override on the wire (CRITICAL — scope MUST be inferred
977
+ * server-side to prevent tenant/project confusion attacks).
978
+ *
979
+ * The `Deployment` type carries no `projectId`, so this normally returns
980
+ * `{ kind: 'tenant', tenantId }`. If a binding's record does carry a
981
+ * non-empty `projectId`, the helper returns a project scope instead. The
982
+ * field is read dynamically because the type does not declare it.
983
+ */
984
+ function deriveScopeFromDeployment(d: Deployment): Scope {
985
+ const maybeProjectId = (d as Deployment & { readonly projectId?: unknown }).projectId;
986
+ if (typeof maybeProjectId === 'string' && maybeProjectId.length > 0) {
987
+ return {
988
+ kind: 'project',
989
+ tenantId: d.tenantId,
990
+ projectId: maybeProjectId as unknown as ProjectId,
991
+ } as Scope;
992
+ }
993
+ return { kind: 'tenant', tenantId: d.tenantId } as Scope;
994
+ }
995
+
996
+ type ParsedSyncEntry =
997
+ | { readonly kind: 'ref'; readonly name: string; readonly ref: string }
998
+ | { readonly kind: 'value'; readonly name: string; readonly value: string };
999
+
1000
+ // ------------------------------------------------------------
1001
+ // Wire body validation
1002
+ // ------------------------------------------------------------
1003
+
1004
+ interface WireBody {
1005
+ readonly imageRef: string;
1006
+ readonly artifactVersion: string;
1007
+ readonly indexHash: string;
1008
+ readonly signerKeyId: SigningKeyId;
1009
+ readonly signerPublicKey: string;
1010
+ readonly signature: string;
1011
+ readonly publishedAt: string;
1012
+ }
1013
+
1014
+ interface Issue {
1015
+ readonly path: string;
1016
+ readonly message: string;
1017
+ }
1018
+
1019
+ type ParseResult =
1020
+ | { readonly kind: 'ok'; readonly value: WireBody }
1021
+ | {
1022
+ readonly kind: 'err';
1023
+ readonly error: { readonly message: string; readonly issues: readonly Issue[] };
1024
+ };
1025
+
1026
+ const ARTIFACT_VERSION_RE = /^\d{8}\.\d+$/;
1027
+ const SHA256_HEX_RE = /^[0-9a-f]{64}$/i;
1028
+ const SHA256_PREFIX_RE = /^sha256:[0-9a-f]{64}$/i;
1029
+
1030
+ function parseWireBody(body: unknown): ParseResult {
1031
+ if (body === null || typeof body !== 'object' || Array.isArray(body)) {
1032
+ return {
1033
+ kind: 'err',
1034
+ error: {
1035
+ message: 'Request body must be a JSON object',
1036
+ issues: [{ path: '', message: 'must be an object' }],
1037
+ },
1038
+ };
1039
+ }
1040
+ const b = body as Record<string, unknown>;
1041
+ const issues: Issue[] = [];
1042
+ const requireString = (field: string): string | undefined => {
1043
+ const raw = b[field];
1044
+ if (typeof raw !== 'string' || raw.length === 0) {
1045
+ issues.push({ path: field, message: `${field} must be a non-empty string` });
1046
+ return undefined;
1047
+ }
1048
+ return raw;
1049
+ };
1050
+ const imageRef = requireString('imageRef');
1051
+ const artifactVersion = requireString('artifactVersion');
1052
+ const indexHash = requireString('indexHash');
1053
+ const signerKeyId = requireString('signerKeyId');
1054
+ const signerPublicKey = requireString('signerPublicKey');
1055
+ const signature = requireString('signature');
1056
+ const publishedAt = requireString('publishedAt');
1057
+
1058
+ if (artifactVersion !== undefined && !ARTIFACT_VERSION_RE.test(artifactVersion)) {
1059
+ issues.push({
1060
+ path: 'artifactVersion',
1061
+ message: 'artifactVersion must match YYYYMMDD.N',
1062
+ });
1063
+ }
1064
+ if (
1065
+ indexHash !== undefined &&
1066
+ !SHA256_PREFIX_RE.test(indexHash) &&
1067
+ !SHA256_HEX_RE.test(indexHash)
1068
+ ) {
1069
+ issues.push({
1070
+ path: 'indexHash',
1071
+ message: 'indexHash must be sha256:<64-hex> (or bare 64-hex)',
1072
+ });
1073
+ }
1074
+ if (publishedAt !== undefined && Number.isNaN(Date.parse(publishedAt))) {
1075
+ issues.push({ path: 'publishedAt', message: 'publishedAt must be ISO-8601' });
1076
+ }
1077
+
1078
+ if (issues.length > 0) {
1079
+ return {
1080
+ kind: 'err',
1081
+ error: { message: issues[0]?.message ?? 'body validation failed', issues },
1082
+ };
1083
+ }
1084
+
1085
+ return {
1086
+ kind: 'ok',
1087
+ value: {
1088
+ imageRef: imageRef as string,
1089
+ artifactVersion: artifactVersion as string,
1090
+ indexHash: indexHash as string,
1091
+ signerKeyId: signerKeyId as unknown as SigningKeyId,
1092
+ signerPublicKey: signerPublicKey as string,
1093
+ signature: signature as string,
1094
+ publishedAt: publishedAt as string,
1095
+ },
1096
+ };
1097
+ }
1098
+
1099
+ /** The image's `/app/index.json` as an object, or `undefined` when it isn't one. */
1100
+ function parseImageIndex(bytes: Uint8Array): Readonly<Record<string, unknown>> | undefined {
1101
+ let parsed: unknown;
1102
+ try {
1103
+ parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
1104
+ } catch {
1105
+ return undefined;
1106
+ }
1107
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
1108
+ ? (parsed as Readonly<Record<string, unknown>>)
1109
+ : undefined;
1110
+ }
1111
+
1112
+ function extractImageDigest(imageRef: string): string | null {
1113
+ const at = imageRef.lastIndexOf('@');
1114
+ if (at < 0) return null;
1115
+ const digest = imageRef.slice(at + 1);
1116
+ if (!SHA256_PREFIX_RE.test(digest)) return null;
1117
+ return digest.toLowerCase();
1118
+ }
1119
+
1120
+ function normaliseSha256Hex(raw: string): string {
1121
+ const lower = raw.toLowerCase();
1122
+ return lower.startsWith('sha256:') ? lower : `sha256:${lower}`;
1123
+ }
1124
+
1125
+ // ------------------------------------------------------------
1126
+ // Canonical envelope
1127
+ // ------------------------------------------------------------
1128
+
1129
+ interface EnvelopeFields {
1130
+ readonly imageDigest: string;
1131
+ readonly artifactVersion: string;
1132
+ readonly indexHash: string;
1133
+ readonly tenantId: string;
1134
+ readonly publishedAt: string;
1135
+ }
1136
+
1137
+ /**
1138
+ * Canonical form of the signed envelope: JSON object with sorted keys,
1139
+ * no whitespace, UTF-8. Deterministic by construction — the same field
1140
+ * values always produce byte-identical output.
1141
+ */
1142
+ function canonicalizeEnvelope(fields: EnvelopeFields): Uint8Array {
1143
+ const ordered: Record<string, string> = {
1144
+ artifactVersion: fields.artifactVersion,
1145
+ imageDigest: normaliseSha256Hex(fields.imageDigest),
1146
+ indexHash: normaliseSha256Hex(fields.indexHash),
1147
+ publishedAt: fields.publishedAt,
1148
+ tenantId: fields.tenantId,
1149
+ };
1150
+ const json = JSON.stringify(ordered);
1151
+ return new TextEncoder().encode(json);
1152
+ }
1153
+
1154
+ // ------------------------------------------------------------
1155
+ // Primitive validation
1156
+ // ------------------------------------------------------------
1157
+
1158
+ interface ValidationDetail {
1159
+ readonly primitive: 'tool' | 'guardrail' | 'agent' | 'flow';
1160
+ readonly index: number;
1161
+ readonly id?: string;
1162
+ readonly path: string;
1163
+ readonly message: string;
1164
+ }
1165
+
1166
+ interface ValidatedPrimitives {
1167
+ readonly tools: readonly import('@kindgi/tools').ToolManifest[];
1168
+ readonly guardrails: readonly Guardrail[];
1169
+ readonly agents: readonly import('@kindgi/agents').Agent[];
1170
+ readonly flows: readonly import('@kindgi/flow').Flow[];
1171
+ }
1172
+
1173
+ type ValidateResult =
1174
+ | { readonly kind: 'ok'; readonly value: ValidatedPrimitives }
1175
+ | { readonly kind: 'err'; readonly error: { readonly details: readonly ValidationDetail[] } };
1176
+
1177
+ /** The image a deployment's code lives in, for each primitive's `codeArtifactRef`. */
1178
+ interface DeployedImage {
1179
+ readonly imageRef: string;
1180
+ readonly artifactVersion: string;
1181
+ }
1182
+
1183
+ /** The pointer to a tool's or guardrail's module inside the deployed image. */
1184
+ function ociCodeArtifactRef(image: DeployedImage, modulePath: unknown) {
1185
+ return typeof modulePath === 'string' && modulePath.length > 0
1186
+ ? {
1187
+ codeArtifactRef: {
1188
+ kind: 'oci' as const,
1189
+ imageRef: image.imageRef,
1190
+ modulePath,
1191
+ artifactVersion: image.artifactVersion,
1192
+ },
1193
+ }
1194
+ : {};
1195
+ }
1196
+
1197
+ /**
1198
+ * Validate the index's primitives as the registries store them. Tools
1199
+ * and guardrails keep where their code is (`codeArtifactRef`, an `oci`
1200
+ * pointer into the deployed image) in place of the index's module path;
1201
+ * a guardrail's `checkId` becomes its `check`, as `kindgi dev`'s disk
1202
+ * registry maps it.
1203
+ */
1204
+ function validatePrimitives(
1205
+ index: Readonly<Record<string, unknown>>,
1206
+ image: DeployedImage,
1207
+ ): ValidateResult {
1208
+ const details: ValidationDetail[] = [];
1209
+
1210
+ const tools = readArray(index.tools);
1211
+ const guardrails = readArray(index.guardrails);
1212
+ const agents = readArray(index.agents);
1213
+ const flows = readArray(index.flows);
1214
+
1215
+ const validatedTools: import('@kindgi/tools').ToolManifest[] = [];
1216
+ tools.forEach((raw, i) => {
1217
+ if (raw === null || typeof raw !== 'object') {
1218
+ details.push({ primitive: 'tool', index: i, path: '', message: 'must be an object' });
1219
+ return;
1220
+ }
1221
+ const { modulePath, ...manifest } = raw as Record<string, unknown>;
1222
+ const r = validateToolManifest({ ...manifest, ...ociCodeArtifactRef(image, modulePath) });
1223
+ if (r.kind === 'err') {
1224
+ const err = r.error as {
1225
+ message: string;
1226
+ issues?: readonly { path: string; message: string }[];
1227
+ };
1228
+ const rawId = (raw as { id?: unknown }).id;
1229
+ const idBase: Partial<ValidationDetail> = typeof rawId === 'string' ? { id: rawId } : {};
1230
+ if (err.issues && err.issues.length > 0) {
1231
+ for (const issue of err.issues) {
1232
+ details.push({
1233
+ primitive: 'tool',
1234
+ index: i,
1235
+ ...idBase,
1236
+ path: issue.path,
1237
+ message: issue.message,
1238
+ });
1239
+ }
1240
+ } else {
1241
+ details.push({ primitive: 'tool', index: i, ...idBase, path: '', message: err.message });
1242
+ }
1243
+ return;
1244
+ }
1245
+ validatedTools.push(r.value);
1246
+ });
1247
+
1248
+ const validatedGuardrails: Guardrail[] = [];
1249
+ guardrails.forEach((raw, i) => {
1250
+ if (raw === null || typeof raw !== 'object') {
1251
+ details.push({ primitive: 'guardrail', index: i, path: '', message: 'must be an object' });
1252
+ return;
1253
+ }
1254
+ const {
1255
+ checkModulePath,
1256
+ checkId,
1257
+ configSchema: _configSchema,
1258
+ ...rest
1259
+ } = raw as Record<string, unknown>;
1260
+ const r = validateGuardrailSpec({
1261
+ ...rest,
1262
+ check: checkId ?? rest.check ?? rest.id,
1263
+ ...ociCodeArtifactRef(image, checkModulePath),
1264
+ });
1265
+ if (r.kind === 'err') {
1266
+ const err = r.error as {
1267
+ message: string;
1268
+ issues?: readonly { path: string; message: string }[];
1269
+ };
1270
+ const rawId = (raw as { id?: unknown }).id;
1271
+ const idBase: Partial<ValidationDetail> = typeof rawId === 'string' ? { id: rawId } : {};
1272
+ if (err.issues && err.issues.length > 0) {
1273
+ for (const issue of err.issues) {
1274
+ details.push({
1275
+ primitive: 'guardrail',
1276
+ index: i,
1277
+ ...idBase,
1278
+ path: issue.path,
1279
+ message: issue.message,
1280
+ });
1281
+ }
1282
+ } else {
1283
+ details.push({
1284
+ primitive: 'guardrail',
1285
+ index: i,
1286
+ ...idBase,
1287
+ path: '',
1288
+ message: err.message,
1289
+ });
1290
+ }
1291
+ return;
1292
+ }
1293
+ validatedGuardrails.push(r.value);
1294
+ });
1295
+
1296
+ const validatedAgents: import('@kindgi/agents').Agent[] = [];
1297
+ agents.forEach((raw, i) => {
1298
+ if (raw === null || typeof raw !== 'object') {
1299
+ details.push({ primitive: 'agent', index: i, path: '', message: 'must be an object' });
1300
+ return;
1301
+ }
1302
+ const { modulePath: _mp, ...spec } = raw as Record<string, unknown>;
1303
+ const shapeIssues = validateAgentIndexShape(spec);
1304
+ if (shapeIssues.length > 0) {
1305
+ const rawId = (spec as { id?: unknown }).id;
1306
+ const idBase: Partial<ValidationDetail> = typeof rawId === 'string' ? { id: rawId } : {};
1307
+ for (const issue of shapeIssues) {
1308
+ details.push({ primitive: 'agent', index: i, ...idBase, ...issue });
1309
+ }
1310
+ return;
1311
+ }
1312
+ validatedAgents.push(spec as unknown as import('@kindgi/agents').Agent);
1313
+ });
1314
+
1315
+ const validatedGraphs: import('@kindgi/flow').Flow[] = [];
1316
+ flows.forEach((raw, i) => {
1317
+ if (raw === null || typeof raw !== 'object') {
1318
+ details.push({ primitive: 'flow', index: i, path: '', message: 'must be an object' });
1319
+ return;
1320
+ }
1321
+ const { modulePath: _mp, kernelPayloadVersion: _kpv, ...spec } = raw as Record<string, unknown>;
1322
+ const shapeIssues = validateFlowIndexShape(spec);
1323
+ if (shapeIssues.length > 0) {
1324
+ const rawId = (spec as { id?: unknown }).id;
1325
+ const idBase: Partial<ValidationDetail> = typeof rawId === 'string' ? { id: rawId } : {};
1326
+ for (const issue of shapeIssues) {
1327
+ details.push({ primitive: 'flow', index: i, ...idBase, ...issue });
1328
+ }
1329
+ return;
1330
+ }
1331
+ validatedGraphs.push(spec as unknown as import('@kindgi/flow').Flow);
1332
+ });
1333
+
1334
+ if (details.length > 0) {
1335
+ return { kind: 'err', error: { details } };
1336
+ }
1337
+ return {
1338
+ kind: 'ok',
1339
+ value: {
1340
+ tools: validatedTools,
1341
+ guardrails: validatedGuardrails,
1342
+ agents: validatedAgents,
1343
+ flows: validatedGraphs,
1344
+ },
1345
+ };
1346
+ }
1347
+
1348
+ function readArray(value: unknown): readonly unknown[] {
1349
+ return Array.isArray(value) ? (value as readonly unknown[]) : [];
1350
+ }
1351
+
1352
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
1353
+
1354
+ /**
1355
+ * Minimal shape check on an `IndexedAgent` after `modulePath` is
1356
+ * stripped. Deeper validation (RetrievalIntent shape, budget arithmetic,
1357
+ * conversation policy tri-state) is left to the runtime at dispatch —
1358
+ * the deploy route only validates that the wire is well-shaped enough to
1359
+ * persist. The alternative is running `defineAgent`, but that requires
1360
+ * promoting `capabilities`/`tools` to their branded shapes; the pattern
1361
+ * across the API package is to keep transport shape checks lightweight
1362
+ * and leave semantic checks to the runtime.
1363
+ */
1364
+ function validateAgentIndexShape(
1365
+ spec: Record<string, unknown>,
1366
+ ): readonly { path: string; message: string }[] {
1367
+ const out: { path: string; message: string }[] = [];
1368
+ if (typeof spec.id !== 'string' || (spec.id as string).length === 0) {
1369
+ out.push({ path: '/id', message: 'agent.id must be a non-empty string' });
1370
+ }
1371
+ if (typeof spec.version !== 'string' || !SEMVER_RE.test(spec.version as string)) {
1372
+ out.push({ path: '/version', message: 'agent.version must be semver' });
1373
+ }
1374
+ if (typeof spec.name !== 'string' || (spec.name as string).length === 0) {
1375
+ out.push({ path: '/name', message: 'agent.name must be a non-empty string' });
1376
+ }
1377
+ if (typeof spec.instructions !== 'string' || (spec.instructions as string).length === 0) {
1378
+ out.push({ path: '/instructions', message: 'agent.instructions must be a non-empty string' });
1379
+ }
1380
+ if (!Array.isArray(spec.capabilities)) {
1381
+ out.push({ path: '/capabilities', message: 'agent.capabilities must be an array' });
1382
+ }
1383
+ if (!Array.isArray(spec.tools)) {
1384
+ out.push({ path: '/tools', message: 'agent.tools must be an array of tool ids' });
1385
+ }
1386
+ return out;
1387
+ }
1388
+
1389
+ function validateFlowIndexShape(
1390
+ spec: Record<string, unknown>,
1391
+ ): readonly { path: string; message: string }[] {
1392
+ const out: { path: string; message: string }[] = [];
1393
+ if (typeof spec.id !== 'string' || (spec.id as string).length === 0) {
1394
+ out.push({ path: '/id', message: 'flow.id must be a non-empty string' });
1395
+ }
1396
+ if (typeof spec.version !== 'string' || !SEMVER_RE.test(spec.version as string)) {
1397
+ out.push({ path: '/version', message: 'flow.version must be semver' });
1398
+ }
1399
+ if (!Array.isArray(spec.nodes)) {
1400
+ out.push({ path: '/nodes', message: 'flow.nodes must be an array' });
1401
+ }
1402
+ if (!Array.isArray(spec.edges)) {
1403
+ out.push({ path: '/edges', message: 'flow.edges must be an array' });
1404
+ }
1405
+ return out;
1406
+ }
1407
+
1408
+ // ------------------------------------------------------------
1409
+ // Rollback / serialisation
1410
+ // ------------------------------------------------------------
1411
+
1412
+ type RollbackAction = () => Promise<void>;
1413
+
1414
+ async function rollback(actions: readonly RollbackAction[]): Promise<void> {
1415
+ // Reverse order — last-in, first-out — so registrations are undone in
1416
+ // the mirror sequence of their creation.
1417
+ for (let i = actions.length - 1; i >= 0; i--) {
1418
+ const action = actions[i];
1419
+ if (action === undefined) continue;
1420
+ try {
1421
+ await action();
1422
+ } catch {
1423
+ // Swallow: rollback is best-effort. Real production adapters
1424
+ // record a rollback-failure event so operators can reconcile.
1425
+ }
1426
+ }
1427
+ }
1428
+
1429
+ function serializeDeployment(d: Deployment): Record<string, unknown> {
1430
+ return {
1431
+ deploymentId: d.deploymentId,
1432
+ tenantId: d.tenantId as unknown as string,
1433
+ imageRef: d.imageRef,
1434
+ imageDigest: d.imageDigest,
1435
+ artifactVersion: d.artifactVersion,
1436
+ indexHash: d.indexHash,
1437
+ signerKeyId: d.signerKeyId as unknown as string,
1438
+ signerPublicKey: d.signerPublicKey,
1439
+ signature: d.signature,
1440
+ publishedAt: d.publishedAt,
1441
+ activatedAt: d.activatedAt,
1442
+ primitives: {
1443
+ tools: d.primitives.tools,
1444
+ guardrails: d.primitives.guardrails,
1445
+ agents: d.primitives.agents,
1446
+ flows: d.primitives.flows,
1447
+ },
1448
+ contents: d.contents,
1449
+ };
1450
+ }
1451
+
1452
+ /** Tell the catalog caches about what a new deployment registered (advisory). */
1453
+ async function notifyWrites(
1454
+ tenantId: TenantId,
1455
+ validated: ValidatedPrimitives,
1456
+ bindings: DeploymentsRouterBindings,
1457
+ ): Promise<void> {
1458
+ for (const tool of validated.tools) {
1459
+ try {
1460
+ await bindings.onToolWrite?.({
1461
+ tenantId,
1462
+ toolId: tool.id as ToolId,
1463
+ version: tool.version,
1464
+ kind: 'publish',
1465
+ });
1466
+ } catch {
1467
+ // Advisory, as on `POST /v1/tools`.
1468
+ }
1469
+ }
1470
+ for (const guardrail of validated.guardrails) {
1471
+ try {
1472
+ await bindings.onGuardrailWrite?.({
1473
+ tenantId,
1474
+ guardrailId: guardrail.id as GuardrailId,
1475
+ kind: 'register',
1476
+ });
1477
+ } catch {
1478
+ // Advisory, as on `POST /v1/guardrails`.
1479
+ }
1480
+ }
1481
+ }
1482
+
1483
+ function errMessage(cause: unknown): string {
1484
+ return cause instanceof Error ? cause.message : String(cause);
1485
+ }
1486
+
1487
+ // Formats a sha256 digest in the canonical `sha256:<hex>` shape. Not used
1488
+ // by the router or elsewhere in this package; exported from this module
1489
+ // only.
1490
+ export function sha256HexPrefixed(bytes: Uint8Array): string {
1491
+ const hex = createHash('sha256').update(bytes).digest('hex');
1492
+ return `sha256:${hex}`;
1493
+ }