@stigmer/server 3.24.0 → 3.26.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 (341) hide show
  1. package/dist/authorization/authorizer.d.ts +10 -4
  2. package/dist/authorization/authorizer.d.ts.map +1 -1
  3. package/dist/authorization/authorizer.js +11 -4
  4. package/dist/authorization/authorizer.js.map +1 -1
  5. package/dist/authorization/model/index.d.ts +4 -4
  6. package/dist/authorization/model/index.d.ts.map +1 -1
  7. package/dist/authorization/model/index.js +2 -0
  8. package/dist/authorization/model/index.js.map +1 -1
  9. package/dist/authorization/model/oauth_app.d.ts.map +1 -1
  10. package/dist/authorization/model/oauth_app.js +3 -2
  11. package/dist/authorization/model/oauth_app.js.map +1 -1
  12. package/dist/authorization/model/platform_client.d.ts +2 -0
  13. package/dist/authorization/model/platform_client.d.ts.map +1 -0
  14. package/dist/authorization/model/platform_client.js +31 -0
  15. package/dist/authorization/model/platform_client.js.map +1 -0
  16. package/dist/boot/compose.d.ts.map +1 -1
  17. package/dist/boot/compose.js +99 -13
  18. package/dist/boot/compose.js.map +1 -1
  19. package/dist/boot/list-indexes.d.ts +3 -0
  20. package/dist/boot/list-indexes.d.ts.map +1 -0
  21. package/dist/boot/list-indexes.js +24 -0
  22. package/dist/boot/list-indexes.js.map +1 -0
  23. package/dist/boot/route-shadowing.d.ts +25 -0
  24. package/dist/boot/route-shadowing.d.ts.map +1 -0
  25. package/dist/boot/route-shadowing.js +17 -0
  26. package/dist/boot/route-shadowing.js.map +1 -0
  27. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  28. package/dist/domain/agentexecution/controller.js +6 -8
  29. package/dist/domain/agentexecution/controller.js.map +1 -1
  30. package/dist/domain/agentexecution/list-index.d.ts +2 -0
  31. package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
  32. package/dist/domain/agentexecution/list-index.js +23 -0
  33. package/dist/domain/agentexecution/list-index.js.map +1 -0
  34. package/dist/domain/agentexecution/steps.d.ts +17 -33
  35. package/dist/domain/agentexecution/steps.d.ts.map +1 -1
  36. package/dist/domain/agentexecution/steps.js +94 -95
  37. package/dist/domain/agentexecution/steps.js.map +1 -1
  38. package/dist/domain/agentexecution/usage.d.ts.map +1 -1
  39. package/dist/domain/agentexecution/usage.js +37 -14
  40. package/dist/domain/agentexecution/usage.js.map +1 -1
  41. package/dist/domain/artifact/controller.d.ts.map +1 -1
  42. package/dist/domain/artifact/controller.js +27 -15
  43. package/dist/domain/artifact/controller.js.map +1 -1
  44. package/dist/domain/artifact/list-index.d.ts +2 -0
  45. package/dist/domain/artifact/list-index.d.ts.map +1 -0
  46. package/dist/domain/artifact/list-index.js +21 -0
  47. package/dist/domain/artifact/list-index.js.map +1 -0
  48. package/dist/domain/iampolicy/membership.d.ts +6 -1
  49. package/dist/domain/iampolicy/membership.d.ts.map +1 -1
  50. package/dist/domain/iampolicy/membership.js +8 -2
  51. package/dist/domain/iampolicy/membership.js.map +1 -1
  52. package/dist/domain/identityaccount/constants.d.ts +13 -0
  53. package/dist/domain/identityaccount/constants.d.ts.map +1 -1
  54. package/dist/domain/identityaccount/constants.js +17 -0
  55. package/dist/domain/identityaccount/constants.js.map +1 -1
  56. package/dist/domain/identityaccount/controller.d.ts +3 -1
  57. package/dist/domain/identityaccount/controller.d.ts.map +1 -1
  58. package/dist/domain/identityaccount/controller.js +15 -6
  59. package/dist/domain/identityaccount/controller.js.map +1 -1
  60. package/dist/domain/identityaccount/operator.d.ts.map +1 -1
  61. package/dist/domain/identityaccount/operator.js +1 -0
  62. package/dist/domain/identityaccount/operator.js.map +1 -1
  63. package/dist/domain/identityaccount/provisioning.d.ts +18 -0
  64. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
  65. package/dist/domain/identityaccount/provisioning.js +1 -0
  66. package/dist/domain/identityaccount/provisioning.js.map +1 -1
  67. package/dist/domain/identityaccount/resource-store.js +9 -5
  68. package/dist/domain/identityaccount/resource-store.js.map +1 -1
  69. package/dist/domain/identityaccount/steps.d.ts +11 -2
  70. package/dist/domain/identityaccount/steps.d.ts.map +1 -1
  71. package/dist/domain/identityaccount/steps.js +42 -4
  72. package/dist/domain/identityaccount/steps.js.map +1 -1
  73. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
  74. package/dist/domain/identityaccount/store-contract.js +33 -0
  75. package/dist/domain/identityaccount/store-contract.js.map +1 -1
  76. package/dist/domain/identityaccount/store.d.ts +7 -5
  77. package/dist/domain/identityaccount/store.d.ts.map +1 -1
  78. package/dist/domain/identityaccount/store.js.map +1 -1
  79. package/dist/domain/platform/controller.d.ts +10 -0
  80. package/dist/domain/platform/controller.d.ts.map +1 -1
  81. package/dist/domain/platform/controller.js +2 -1
  82. package/dist/domain/platform/controller.js.map +1 -1
  83. package/dist/domain/platformclient/constants.d.ts +60 -0
  84. package/dist/domain/platformclient/constants.d.ts.map +1 -0
  85. package/dist/domain/platformclient/constants.js +78 -0
  86. package/dist/domain/platformclient/constants.js.map +1 -0
  87. package/dist/domain/platformclient/controller.d.ts +59 -0
  88. package/dist/domain/platformclient/controller.d.ts.map +1 -0
  89. package/dist/domain/platformclient/controller.js +191 -0
  90. package/dist/domain/platformclient/controller.js.map +1 -0
  91. package/dist/domain/platformclient/credentials.d.ts +13 -0
  92. package/dist/domain/platformclient/credentials.d.ts.map +1 -0
  93. package/dist/domain/platformclient/credentials.js +56 -0
  94. package/dist/domain/platformclient/credentials.js.map +1 -0
  95. package/dist/domain/platformclient/mint.d.ts +23 -0
  96. package/dist/domain/platformclient/mint.d.ts.map +1 -0
  97. package/dist/domain/platformclient/mint.js +254 -0
  98. package/dist/domain/platformclient/mint.js.map +1 -0
  99. package/dist/domain/platformclient/origin-guard.d.ts +9 -0
  100. package/dist/domain/platformclient/origin-guard.d.ts.map +1 -0
  101. package/dist/domain/platformclient/origin-guard.js +81 -0
  102. package/dist/domain/platformclient/origin-guard.js.map +1 -0
  103. package/dist/domain/platformclient/resource-store.d.ts +4 -0
  104. package/dist/domain/platformclient/resource-store.d.ts.map +1 -0
  105. package/dist/domain/platformclient/resource-store.js +117 -0
  106. package/dist/domain/platformclient/resource-store.js.map +1 -0
  107. package/dist/domain/platformclient/steps.d.ts +51 -0
  108. package/dist/domain/platformclient/steps.d.ts.map +1 -0
  109. package/dist/domain/platformclient/steps.js +339 -0
  110. package/dist/domain/platformclient/steps.js.map +1 -0
  111. package/dist/domain/platformclient/store-contract.d.ts +8 -0
  112. package/dist/domain/platformclient/store-contract.d.ts.map +1 -0
  113. package/dist/domain/platformclient/store-contract.js +208 -0
  114. package/dist/domain/platformclient/store-contract.js.map +1 -0
  115. package/dist/domain/platformclient/store.d.ts +58 -0
  116. package/dist/domain/platformclient/store.d.ts.map +1 -0
  117. package/dist/domain/platformclient/store.js +13 -0
  118. package/dist/domain/platformclient/store.js.map +1 -0
  119. package/dist/domain/platformclient/system-managed.d.ts +18 -0
  120. package/dist/domain/platformclient/system-managed.d.ts.map +1 -0
  121. package/dist/domain/platformclient/system-managed.js +80 -0
  122. package/dist/domain/platformclient/system-managed.js.map +1 -0
  123. package/dist/domain/platformclient/token-controller.d.ts +11 -0
  124. package/dist/domain/platformclient/token-controller.d.ts.map +1 -0
  125. package/dist/domain/platformclient/token-controller.js +32 -0
  126. package/dist/domain/platformclient/token-controller.js.map +1 -0
  127. package/dist/domain/platformclient/verifier.d.ts +10 -0
  128. package/dist/domain/platformclient/verifier.d.ts.map +1 -0
  129. package/dist/domain/platformclient/verifier.js +76 -0
  130. package/dist/domain/platformclient/verifier.js.map +1 -0
  131. package/dist/domain/session/list-index.d.ts +4 -0
  132. package/dist/domain/session/list-index.d.ts.map +1 -0
  133. package/dist/domain/session/list-index.js +26 -0
  134. package/dist/domain/session/list-index.js.map +1 -0
  135. package/dist/domain/session/steps.d.ts +7 -8
  136. package/dist/domain/session/steps.d.ts.map +1 -1
  137. package/dist/domain/session/steps.js +76 -78
  138. package/dist/domain/session/steps.js.map +1 -1
  139. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  140. package/dist/domain/workflowexecution/controller.js +53 -55
  141. package/dist/domain/workflowexecution/controller.js.map +1 -1
  142. package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
  143. package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
  144. package/dist/domain/workflowexecution/execution-filter.js +11 -5
  145. package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
  146. package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
  147. package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
  148. package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
  149. package/dist/domain/workflowexecution/list-index.d.ts +2 -0
  150. package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
  151. package/dist/domain/workflowexecution/list-index.js +21 -0
  152. package/dist/domain/workflowexecution/list-index.js.map +1 -0
  153. package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
  154. package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
  155. package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
  156. package/dist/domain/workflowexecution/queries.d.ts +30 -4
  157. package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
  158. package/dist/domain/workflowexecution/queries.js +76 -22
  159. package/dist/domain/workflowexecution/queries.js.map +1 -1
  160. package/dist/encryption/key-manager.d.ts +20 -3
  161. package/dist/encryption/key-manager.d.ts.map +1 -1
  162. package/dist/encryption/key-manager.js +64 -42
  163. package/dist/encryption/key-manager.js.map +1 -1
  164. package/dist/extensions/caller-guards.d.ts +8 -3
  165. package/dist/extensions/caller-guards.d.ts.map +1 -1
  166. package/dist/extensions/drivers.d.ts +37 -0
  167. package/dist/extensions/drivers.d.ts.map +1 -1
  168. package/dist/extensions/guest-token-minting.d.ts +26 -0
  169. package/dist/extensions/guest-token-minting.d.ts.map +1 -0
  170. package/dist/extensions/guest-token-minting.js +2 -0
  171. package/dist/extensions/guest-token-minting.js.map +1 -0
  172. package/dist/extensions/identity.d.ts +6 -4
  173. package/dist/extensions/identity.d.ts.map +1 -1
  174. package/dist/extensions/identity.js.map +1 -1
  175. package/dist/extensions/list-read-scope.d.ts +9 -12
  176. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  177. package/dist/extensions/list-read-scope.js.map +1 -1
  178. package/dist/extensions/registry.d.ts +30 -1
  179. package/dist/extensions/registry.d.ts.map +1 -1
  180. package/dist/extensions/registry.js +34 -1
  181. package/dist/extensions/registry.js.map +1 -1
  182. package/dist/index.d.ts +15 -2
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +6 -0
  185. package/dist/index.js.map +1 -1
  186. package/dist/pipeline/interceptors/auth.d.ts +14 -0
  187. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  188. package/dist/pipeline/interceptors/auth.js +24 -0
  189. package/dist/pipeline/interceptors/auth.js.map +1 -1
  190. package/dist/pipeline/steps/build-update-state.d.ts +11 -1
  191. package/dist/pipeline/steps/build-update-state.d.ts.map +1 -1
  192. package/dist/pipeline/steps/build-update-state.js +5 -2
  193. package/dist/pipeline/steps/build-update-state.js.map +1 -1
  194. package/dist/pipeline/steps/list-page.d.ts +63 -0
  195. package/dist/pipeline/steps/list-page.d.ts.map +1 -0
  196. package/dist/pipeline/steps/list-page.js +187 -0
  197. package/dist/pipeline/steps/list-page.js.map +1 -0
  198. package/dist/platformtoken/envelope.d.ts +100 -0
  199. package/dist/platformtoken/envelope.d.ts.map +1 -0
  200. package/dist/platformtoken/envelope.js +184 -0
  201. package/dist/platformtoken/envelope.js.map +1 -0
  202. package/dist/platformtoken/key-ring.d.ts +85 -0
  203. package/dist/platformtoken/key-ring.d.ts.map +1 -0
  204. package/dist/platformtoken/key-ring.js +164 -0
  205. package/dist/platformtoken/key-ring.js.map +1 -0
  206. package/dist/query/activity/handler.d.ts +6 -7
  207. package/dist/query/activity/handler.d.ts.map +1 -1
  208. package/dist/query/activity/handler.js +42 -59
  209. package/dist/query/activity/handler.js.map +1 -1
  210. package/dist/store/interface.d.ts +29 -0
  211. package/dist/store/interface.d.ts.map +1 -1
  212. package/dist/store/interface.js.map +1 -1
  213. package/dist/store/list-index.d.ts +123 -0
  214. package/dist/store/list-index.d.ts.map +1 -0
  215. package/dist/store/list-index.js +277 -0
  216. package/dist/store/list-index.js.map +1 -0
  217. package/dist/store/logger.d.ts +3 -0
  218. package/dist/store/logger.d.ts.map +1 -1
  219. package/dist/store/logger.js +5 -1
  220. package/dist/store/logger.js.map +1 -1
  221. package/dist/store/postgres/migrations.d.ts +10 -6
  222. package/dist/store/postgres/migrations.d.ts.map +1 -1
  223. package/dist/store/postgres/migrations.js +64 -6
  224. package/dist/store/postgres/migrations.js.map +1 -1
  225. package/dist/store/postgres/store.d.ts +26 -5
  226. package/dist/store/postgres/store.d.ts.map +1 -1
  227. package/dist/store/postgres/store.js +377 -22
  228. package/dist/store/postgres/store.js.map +1 -1
  229. package/dist/store/sqlite/migrations.d.ts +11 -5
  230. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  231. package/dist/store/sqlite/migrations.js +50 -4
  232. package/dist/store/sqlite/migrations.js.map +1 -1
  233. package/dist/store/sqlite/store.d.ts +18 -3
  234. package/dist/store/sqlite/store.d.ts.map +1 -1
  235. package/dist/store/sqlite/store.js +321 -15
  236. package/dist/store/sqlite/store.js.map +1 -1
  237. package/package.json +6 -6
  238. package/src/authorization/README.md +1 -1
  239. package/src/authorization/__tests__/authorizer.test.ts +6 -2
  240. package/src/authorization/__tests__/drivers.ts +6 -1
  241. package/src/authorization/__tests__/wire-permissions.test.ts +1 -0
  242. package/src/authorization/authorizer.ts +11 -4
  243. package/src/authorization/model/__tests__/registry.test.ts +8 -1
  244. package/src/authorization/model/index.ts +6 -4
  245. package/src/authorization/model/oauth_app.ts +3 -2
  246. package/src/authorization/model/platform_client.ts +43 -0
  247. package/src/boot/__tests__/auth-enabled-composition.test.ts +2 -1
  248. package/src/boot/__tests__/list-indexes.test.ts +65 -0
  249. package/src/boot/__tests__/route-shadowing.test.ts +67 -0
  250. package/src/boot/compose.ts +113 -18
  251. package/src/boot/list-indexes.ts +25 -0
  252. package/src/boot/route-shadowing.ts +47 -0
  253. package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
  254. package/src/domain/agentexecution/controller.ts +8 -10
  255. package/src/domain/agentexecution/list-index.ts +24 -0
  256. package/src/domain/agentexecution/steps.ts +118 -115
  257. package/src/domain/agentexecution/usage.ts +38 -17
  258. package/src/domain/artifact/controller.ts +28 -18
  259. package/src/domain/artifact/list-index.ts +22 -0
  260. package/src/domain/iampolicy/__tests__/membership.test.ts +7 -0
  261. package/src/domain/iampolicy/membership.ts +8 -2
  262. package/src/domain/identityaccount/__tests__/platform-client-accounts.test.ts +124 -0
  263. package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -0
  264. package/src/domain/identityaccount/constants.ts +20 -0
  265. package/src/domain/identityaccount/controller.ts +16 -4
  266. package/src/domain/identityaccount/operator.ts +1 -0
  267. package/src/domain/identityaccount/provisioning.ts +17 -0
  268. package/src/domain/identityaccount/resource-store.ts +9 -5
  269. package/src/domain/identityaccount/steps.ts +58 -3
  270. package/src/domain/identityaccount/store-contract.ts +51 -0
  271. package/src/domain/identityaccount/store.ts +7 -5
  272. package/src/domain/platform/__tests__/platform.test.ts +7 -1
  273. package/src/domain/platform/controller.ts +12 -1
  274. package/src/domain/platformclient/__tests__/controller.test.ts +397 -0
  275. package/src/domain/platformclient/__tests__/credentials.test.ts +46 -0
  276. package/src/domain/platformclient/__tests__/mint.test.ts +415 -0
  277. package/src/domain/platformclient/__tests__/resource-store.test.ts +140 -0
  278. package/src/domain/platformclient/__tests__/system-managed.test.ts +101 -0
  279. package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +207 -0
  280. package/src/domain/platformclient/constants.ts +120 -0
  281. package/src/domain/platformclient/controller.ts +449 -0
  282. package/src/domain/platformclient/credentials.ts +68 -0
  283. package/src/domain/platformclient/mint.ts +412 -0
  284. package/src/domain/platformclient/origin-guard.ts +117 -0
  285. package/src/domain/platformclient/resource-store.ts +150 -0
  286. package/src/domain/platformclient/steps.ts +472 -0
  287. package/src/domain/platformclient/store-contract.ts +318 -0
  288. package/src/domain/platformclient/store.ts +63 -0
  289. package/src/domain/platformclient/system-managed.ts +115 -0
  290. package/src/domain/platformclient/token-controller.ts +49 -0
  291. package/src/domain/platformclient/verifier.ts +106 -0
  292. package/src/domain/session/list-index.ts +28 -0
  293. package/src/domain/session/steps.ts +108 -123
  294. package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
  295. package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
  296. package/src/domain/workflowexecution/controller.ts +54 -82
  297. package/src/domain/workflowexecution/execution-filter.ts +20 -7
  298. package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
  299. package/src/domain/workflowexecution/list-index.ts +22 -0
  300. package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
  301. package/src/domain/workflowexecution/queries.ts +134 -23
  302. package/src/encryption/key-manager.ts +87 -44
  303. package/src/extensions/__tests__/extension-composition.test.ts +3 -2
  304. package/src/extensions/__tests__/platform-client-composed.test.ts +288 -0
  305. package/src/extensions/__tests__/registry.test.ts +4 -1
  306. package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
  307. package/src/extensions/caller-guards.ts +8 -3
  308. package/src/extensions/drivers.ts +37 -0
  309. package/src/extensions/guest-token-minting.ts +32 -0
  310. package/src/extensions/identity.ts +6 -4
  311. package/src/extensions/list-read-scope.ts +9 -12
  312. package/src/extensions/registry.ts +77 -3
  313. package/src/index.ts +65 -1
  314. package/src/pipeline/interceptors/auth.ts +27 -0
  315. package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
  316. package/src/pipeline/steps/__tests__/references.test.ts +2 -0
  317. package/src/pipeline/steps/build-update-state.ts +5 -2
  318. package/src/pipeline/steps/list-page.ts +284 -0
  319. package/src/platformtoken/__tests__/envelope.test.ts +277 -0
  320. package/src/platformtoken/__tests__/key-ring.test.ts +205 -0
  321. package/src/platformtoken/envelope.ts +276 -0
  322. package/src/platformtoken/key-ring.ts +266 -0
  323. package/src/query/activity/handler.ts +57 -78
  324. package/src/runnerauth/__tests__/runner-subject-composed.test.ts +9 -3
  325. package/src/store/README.md +51 -32
  326. package/src/store/__tests__/list-index.measure.test.ts +293 -0
  327. package/src/store/__tests__/list-index.test.ts +328 -0
  328. package/src/store/__tests__/store-contract.ts +346 -3
  329. package/src/store/interface.ts +39 -0
  330. package/src/store/list-index.ts +465 -0
  331. package/src/store/logger.ts +8 -1
  332. package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
  333. package/src/store/postgres/__tests__/migrations.test.ts +27 -19
  334. package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
  335. package/src/store/postgres/migrations.ts +69 -7
  336. package/src/store/postgres/store.ts +485 -29
  337. package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
  338. package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
  339. package/src/store/sqlite/__tests__/support.ts +12 -3
  340. package/src/store/sqlite/migrations.ts +57 -6
  341. package/src/store/sqlite/store.ts +462 -21
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The PlatformClient lane on a composed server in the authentication
3
+ * posture with the built-in Authorizer (a unit that vouches for tokens,
4
+ * declares the posture and registers no Authorizer — the open-source OIDC
5
+ * self-host's shape), with the key ring supplied so the run is hermetic:
6
+ * - the founder creates a client; an anonymous mint answers a token; the
7
+ * token authenticates as the end user's derived account, which holds the
8
+ * auto-grant role on the owning organization and sees that one alone;
9
+ * - the platform-client verifier sits ahead of the unit's verifier and
10
+ * claims only its lane: the founder's tokens still pass, and a TYPED
11
+ * platform token (a lane open source does not speak) is refused as
12
+ * unclaimed;
13
+ * - the origin guard refuses an unlisted Origin with the cloud's copy and
14
+ * passes a request without one;
15
+ * - deleting the client refuses its token on the next request with the
16
+ * cloud's liveness copy;
17
+ * - an outsider cannot read the client by reference.
18
+ */
19
+ import { generateKeyPairSync } from "node:crypto";
20
+ import { mkdtempSync, rmSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import path from "node:path";
23
+
24
+ import { Code, ConnectError, createClient } from "@connectrpc/connect";
25
+ import type { Interceptor, Transport } from "@connectrpc/connect";
26
+ import { createGrpcTransport } from "@connectrpc/connect-node";
27
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
28
+
29
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
30
+ import { IdentityAccountCommandController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/command_pb";
31
+ import { IdentityAccountQueryController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/query_pb";
32
+ import { PlatformClientCommandController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/command_pb";
33
+ import { PlatformClientQueryController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/query_pb";
34
+ import { PlatformClientTokenController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
35
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
36
+ import { OrganizationCommandController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/command_pb";
37
+ import { OrganizationQueryController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/query_pb";
38
+
39
+ import { loadConfig } from "../../boot/config.js";
40
+ import { composeServer } from "../../boot/compose.js";
41
+ import type { ComposedServer } from "../../boot/compose.js";
42
+ import { accountIdFor } from "../../domain/identityaccount/constants.js";
43
+ import {
44
+ DELETED_CLIENT_MESSAGE,
45
+ originRefusalMessage,
46
+ } from "../../domain/platformclient/constants.js";
47
+ import { signPlatformToken } from "../../platformtoken/envelope.js";
48
+ import { platformTokenKeyRingFromPem } from "../../platformtoken/key-ring.js";
49
+ import type { SigningPlatformTokenKeyRing } from "../../platformtoken/key-ring.js";
50
+ import type { ServerExtension } from "../registry.js";
51
+ import {
52
+ baseConfig,
53
+ bearer,
54
+ fakeJwt,
55
+ fakeVerifier,
56
+ silentLogger,
57
+ transportFor,
58
+ } from "./composed-support.js";
59
+
60
+ const FOUNDER = "fake|pc-founder";
61
+ const OUTSIDER = "fake|pc-outsider";
62
+ const ORG = "pc-org";
63
+
64
+ const { privateKey, publicKey } = generateKeyPairSync("rsa", {
65
+ modulusLength: 2048,
66
+ });
67
+ const built = platformTokenKeyRingFromPem({
68
+ privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
69
+ publicKeyPems: [publicKey.export({ format: "pem", type: "spki" }).toString()],
70
+ });
71
+ if (built.signer === undefined) throw new Error("fixture ring must sign");
72
+ const RING: SigningPlatformTokenKeyRing = { ...built, signer: built.signer };
73
+
74
+ /** Stamps the lowercase `origin` header a browser attaches to a cross-origin call. */
75
+ function browserOrigin(origin: string): Interceptor {
76
+ return (next) => (request) => {
77
+ request.header.set("origin", origin);
78
+ return next(request);
79
+ };
80
+ }
81
+
82
+ async function refusal(promise: Promise<unknown>): Promise<ConnectError> {
83
+ try {
84
+ await promise;
85
+ } catch (error) {
86
+ if (error instanceof ConnectError) return error;
87
+ throw error;
88
+ }
89
+ throw new Error("expected a ConnectError refusal");
90
+ }
91
+
92
+ describe("PlatformClient on a composed server (authentication posture, built-in Authorizer)", () => {
93
+ let dir: string;
94
+ let server: ComposedServer;
95
+ let port: number;
96
+
97
+ const asFounder = (): Transport =>
98
+ transportFor(port, fakeJwt(FOUNDER, "founder@example.com"));
99
+ const asOutsider = (): Transport =>
100
+ transportFor(port, fakeJwt(OUTSIDER, "outsider@example.com"));
101
+ const presenting = (token: string, origin?: string): Transport =>
102
+ createGrpcTransport({
103
+ baseUrl: `http://127.0.0.1:${port}`,
104
+ interceptors: [
105
+ bearer(token),
106
+ ...(origin !== undefined ? [browserOrigin(origin)] : []),
107
+ ],
108
+ });
109
+
110
+ beforeAll(async () => {
111
+ dir = mkdtempSync(path.join(tmpdir(), "platform-client-composed-"));
112
+ const unit: ServerExtension = {
113
+ name: "fake-oidc-only",
114
+ requireAuthentication: true,
115
+ identityVerifiers: [fakeVerifier],
116
+ drivers: { platformTokenKeys: RING },
117
+ };
118
+ server = await composeServer({
119
+ config: loadConfig(baseConfig(dir)),
120
+ logger: silentLogger,
121
+ extensions: [unit],
122
+ portOverride: 0,
123
+ host: "127.0.0.1",
124
+ });
125
+ port = await server.start();
126
+
127
+ await createClient(
128
+ IdentityAccountCommandController,
129
+ asFounder(),
130
+ ).provisionMyAccount({});
131
+ await createClient(OrganizationCommandController, asFounder()).create({
132
+ apiVersion: "tenancy.stigmer.ai/v1",
133
+ kind: "Organization",
134
+ metadata: { name: ORG, slug: ORG, org: "" },
135
+ spec: { description: ORG },
136
+ });
137
+ await createClient(
138
+ IdentityAccountCommandController,
139
+ asOutsider(),
140
+ ).provisionMyAccount({});
141
+ });
142
+
143
+ afterAll(async () => {
144
+ await server?.shutdown();
145
+ rmSync(dir, { recursive: true, force: true });
146
+ });
147
+
148
+ async function createPlatformClient(allowedOrigins: string[] = []) {
149
+ const created = await createClient(
150
+ PlatformClientCommandController,
151
+ asFounder(),
152
+ ).create({
153
+ apiVersion: "iam.stigmer.ai/v1",
154
+ kind: "PlatformClient",
155
+ metadata: {
156
+ name: `client-${Math.random().toString(36).slice(2, 8)}`,
157
+ org: ORG,
158
+ },
159
+ spec: {
160
+ autoProvisionAccounts: true,
161
+ autoGrantOnOrg: true,
162
+ autoGrantRole: IamRole.member,
163
+ allowedOrigins,
164
+ },
165
+ });
166
+ return {
167
+ id: created.platformClient?.metadata?.id ?? "",
168
+ slug: created.platformClient?.metadata?.slug ?? "",
169
+ clientId: created.platformClient?.spec?.clientId ?? "",
170
+ clientSecret: created.clientSecret,
171
+ };
172
+ }
173
+
174
+ async function mint(
175
+ client: { clientId: string; clientSecret: string },
176
+ userId: string,
177
+ ) {
178
+ const minted = await createClient(
179
+ PlatformClientTokenController,
180
+ transportFor(port),
181
+ ).mintUserToken({
182
+ clientId: client.clientId,
183
+ clientSecret: client.clientSecret,
184
+ userId,
185
+ userEmail: `${userId}@product.example`,
186
+ userName: "Product User",
187
+ });
188
+ return minted.accessToken;
189
+ }
190
+
191
+ it("mints for the end user's derived account, which sees its owning organization alone", async () => {
192
+ const client = await createPlatformClient();
193
+ const token = await mint(client, "user-1");
194
+
195
+ const me = await createClient(
196
+ IdentityAccountQueryController,
197
+ presenting(token),
198
+ ).whoAmI({});
199
+ expect(me.metadata?.id).toBe(accountIdFor(`stgm_pc|${ORG}|user-1`));
200
+
201
+ const mine = await createClient(
202
+ OrganizationQueryController,
203
+ presenting(token),
204
+ ).findMyOrganizations({});
205
+ expect(
206
+ mine.entries.map((organization) => organization.metadata?.slug),
207
+ ).toEqual([ORG]);
208
+ });
209
+
210
+ it("claims only its lane: the unit's tokens still pass, and a typed platform token is refused as unclaimed", async () => {
211
+ await createClient(
212
+ OrganizationQueryController,
213
+ asFounder(),
214
+ ).findMyOrganizations({});
215
+
216
+ const guest = signPlatformToken(RING, {
217
+ sub: "ida_guest",
218
+ token_type: "guest",
219
+ }).token;
220
+ const refused = await refusal(
221
+ createClient(
222
+ OrganizationQueryController,
223
+ presenting(guest),
224
+ ).findMyOrganizations({}),
225
+ );
226
+ expect(refused.code).toBe(Code.Unauthenticated);
227
+ });
228
+
229
+ it("refuses an unlisted Origin with the cloud's copy and passes a request without one", async () => {
230
+ const client = await createPlatformClient(["https://app.example"]);
231
+ const token = await mint(client, "user-2");
232
+
233
+ const denied = await refusal(
234
+ createClient(
235
+ OrganizationQueryController,
236
+ presenting(token, "https://evil.example"),
237
+ ).findMyOrganizations({}),
238
+ );
239
+ expect(denied.code).toBe(Code.PermissionDenied);
240
+ expect(denied.rawMessage).toBe(
241
+ originRefusalMessage("https://evil.example"),
242
+ );
243
+
244
+ await createClient(
245
+ OrganizationQueryController,
246
+ presenting(token),
247
+ ).findMyOrganizations({});
248
+ await createClient(
249
+ OrganizationQueryController,
250
+ presenting(token, "https://app.example"),
251
+ ).findMyOrganizations({});
252
+ });
253
+
254
+ it("refuses a deleted client's token on the next request with the liveness copy", async () => {
255
+ const client = await createPlatformClient();
256
+ const token = await mint(client, "user-3");
257
+ await createClient(
258
+ OrganizationQueryController,
259
+ presenting(token),
260
+ ).findMyOrganizations({});
261
+
262
+ await createClient(PlatformClientCommandController, asFounder()).delete({
263
+ resourceId: client.id,
264
+ });
265
+
266
+ const denied = await refusal(
267
+ createClient(
268
+ OrganizationQueryController,
269
+ presenting(token),
270
+ ).findMyOrganizations({}),
271
+ );
272
+ expect(denied.code).toBe(Code.Unauthenticated);
273
+ expect(denied.rawMessage).toBe(DELETED_CLIENT_MESSAGE);
274
+ });
275
+
276
+ it("an outsider cannot read the client by reference", async () => {
277
+ const client = await createPlatformClient();
278
+ const denied = await refusal(
279
+ createClient(PlatformClientQueryController, asOutsider()).getByReference({
280
+ org: ORG,
281
+ slug: client.slug,
282
+ kind: ApiResourceKind.platform_client,
283
+ }),
284
+ );
285
+ expect(denied.code).toBe(Code.PermissionDenied);
286
+ expect(denied.rawMessage).toBe("unauthorized to view platform client");
287
+ });
288
+ });
@@ -193,7 +193,10 @@ describe("resolveExtensions — merge semantics", () => {
193
193
  "g2",
194
194
  "g3",
195
195
  ]);
196
- expect(resolved.services).toEqual([registerA, registerB]);
196
+ expect(resolved.services).toEqual([
197
+ { unit: "alpha", register: registerA },
198
+ { unit: "beta", register: registerB },
199
+ ]);
197
200
  expect(resolved.workers).toEqual([workerFactory]);
198
201
  expect(resolved.statusObservers).toEqual([observerA]);
199
202
  expect(resolved.responseDecorators).toEqual([decoratorB]);
@@ -218,6 +218,14 @@ describe("tier truthfulness against the empty composition", () => {
218
218
  );
219
219
  expect(served.has("iampolicy")).toBe(true);
220
220
  });
221
+
222
+ it("platform_client is open_source and served in the empty composition — the mint refuses without a posture, the kind does not", () => {
223
+ const served = servedKindSegments(server.routes);
224
+ expect(getKindMeta(ApiResourceKind.platform_client).tier).toBe(
225
+ ResourceTier.open_source,
226
+ );
227
+ expect(served.has("platformclient")).toBe(true);
228
+ });
221
229
  });
222
230
 
223
231
  describe("the check bites (mutation proofs over the same function)", () => {
@@ -10,9 +10,14 @@
10
10
  * allowed_origins, oss#375). Java separates the two classes the same way.
11
11
  *
12
12
  * List point — guards concatenate in unit order and run in composed
13
- * order; the first throw wins. OSS composes zero guards, so with none
14
- * registered the serving chain behaves byte-identically to before the
15
- * point existed (the conformance rosters pin that).
13
+ * order; the first throw wins. Open source's own guards run first (the
14
+ * verifier list's "OSS entries first" rule): today the PlatformClient
15
+ * origin guard (domain/platformclient/origin-guard.ts), composed only
16
+ * under an authentication posture, where PlatformClient tokens exist.
17
+ * Liveness is not a guard in open source: the PlatformClient verifier
18
+ * refuses a deleted client's tokens itself, so verifier-only lanes (the
19
+ * extension HTTP edges) refuse them too. With no posture and no unit
20
+ * guards, the serving chain runs no guard at all.
16
21
  *
17
22
  * The in-process exemption is STRUCTURAL, not policed: guards are a
18
23
  * parameter of the serving chain's identity source only
@@ -35,6 +35,11 @@
35
35
  * it reaches a URL a user supplied: the MCP endpoint it probes at save
36
36
  * time and the login server it reaches on Sign in) — landed with the
37
37
  * save-time OAuth completion for URL-only MCP servers, 2026-09-19
38
+ * - platform-client store + platform-token keys + guest-token minting
39
+ * (PlatformClient served in every edition: the port the cloud's
40
+ * `cloud.iam_platform_client` driver fills, the RS256 key ring every
41
+ * self-signed token rides, and the one token method only an edition
42
+ * that hosts shared-agent pages serves) — landed 2026-09-23
38
43
  *
39
44
  * Merge rules (enforced by resolveExtensions, DD-006 §2b): the two
40
45
  * provider kinds are single-instance points — a second declaring unit is
@@ -50,12 +55,15 @@ import type { ArtifactStorageDriverFactory } from "../artifactstorage/artifact-s
50
55
  import type { ChannelRuntime } from "../domain/agentchannel/channel-runtime.js";
51
56
  import type { IdentityAccountStore } from "../domain/identityaccount/store.js";
52
57
  import type { IamPolicyStore } from "../domain/iampolicy/store.js";
58
+ import type { PlatformClientStore } from "../domain/platformclient/store.js";
53
59
  import type { SecretCodec } from "../encryption/codec.js";
54
60
  import type { ModelCatalogProvider } from "../domain/workflow/registry/model-catalog-provider.js";
55
61
  import type { VisitorErrorPolicy } from "../pipeline/interceptors/error-boundary.js";
62
+ import type { PlatformTokenKeyRing } from "../platformtoken/key-ring.js";
56
63
  import type { RunnerCredentialProvider } from "../runnerauth/runner-credential-provider.js";
57
64
  import type { SandboxProvisionerFactory } from "../sandbox/provisioner.js";
58
65
  import type { AuthorizationQueryEngine } from "./authorization-queries.js";
66
+ import type { GuestTokenMinting } from "./guest-token-minting.js";
59
67
  import type { IdentityFederation } from "./identity-federation.js";
60
68
  import type { LicenseStatusProvider } from "./license-status.js";
61
69
  import type { OutboundEgressPolicy } from "./outbound-egress.js";
@@ -247,4 +255,33 @@ export interface ExtensionDrivers {
247
255
  * (extensions/outbound-egress.ts carries the contract).
248
256
  */
249
257
  readonly outboundEgress?: OutboundEgressPolicy;
258
+ /**
259
+ * The platform-client store driver (single-instance point). The
260
+ * platform-client domain's persistence is a PORT like the
261
+ * identity-account domain's: when composed, every chain, the mint and
262
+ * the verifier's liveness read go through this driver (the cloud serves
263
+ * the domain over its own `cloud.iam_platform_client` table this way,
264
+ * legacy `pc_` ids included). When absent, the OSS adapter over the
265
+ * generic Store installs at the compose.ts consumption site.
266
+ */
267
+ readonly platformClientStore?: PlatformClientStore;
268
+ /**
269
+ * The platform-token key ring (single-instance point): the RS256 keys
270
+ * every token the server signs for itself rides
271
+ * (platformtoken/key-ring.ts). Consumed only under an authentication
272
+ * posture — nothing verifies a token on a server that trusts every
273
+ * request. When absent there, open source composes its own ring on the
274
+ * key-manager ladder; a composition declaring an edition other than
275
+ * open source must supply one (compose.ts refuses to boot without it,
276
+ * so a hosted edition never signs with a generated key).
277
+ */
278
+ readonly platformTokenKeys?: PlatformTokenKeyRing;
279
+ /**
280
+ * The guest-token capability (single-instance point): the one
281
+ * PlatformClientTokenController method only an edition hosting
282
+ * shared-agent pages serves. When absent, `mintGuestToken` refuses
283
+ * UNIMPLEMENTED with the edition sentence
284
+ * (extensions/guest-token-minting.ts carries the contract).
285
+ */
286
+ readonly guestTokenMinting?: GuestTokenMinting;
250
287
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The guest-token capability: `PlatformClientTokenController.mintGuestToken`,
3
+ * the credential-free mint for anonymous visitors of a shared agent's
4
+ * hosted page. Single instance, registered as `drivers.guestTokenMinting`
5
+ * (the identityFederation shape: a driver point whose absence is a
6
+ * refusal, never a silent no-op).
7
+ *
8
+ * Why a capability and not a second service: open source registers the
9
+ * whole PlatformClientTokenController, because `mintUserToken` is core,
10
+ * and the composition refuses an extension that registers a service the
11
+ * core already routes. Guest sharing — the hosted page, its rate limits,
12
+ * the share resolution, the org's system-managed client and guest
13
+ * account — is an edition's, so the one method that needs it is
14
+ * dispatched here.
15
+ *
16
+ * Absent, the controller answers UNIMPLEMENTED with the edition sentence
17
+ * (domain/platformclient/constants.ts). Present, the capability owns the
18
+ * whole arm: the request has passed the protovalidate interceptor, and
19
+ * `headers` carries the request's `origin` for the embed checks. It signs
20
+ * with the ring its own composition supplied (`signPlatformToken`).
21
+ */
22
+ import type {
23
+ MintGuestTokenRequest,
24
+ MintGuestTokenResponse,
25
+ } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
26
+
27
+ export interface GuestTokenMinting {
28
+ mintGuestToken(
29
+ request: MintGuestTokenRequest,
30
+ headers: Headers,
31
+ ): Promise<MintGuestTokenResponse>;
32
+ }
@@ -23,10 +23,12 @@
23
23
  * known classes from the type surface).
24
24
  *
25
25
  * `internal` is ratified contract with a structural guarantee: it is
26
- * minted ONLY by the in-process chain's identity interceptor
27
- * (boot/inprocess.ts). No IdentityVerifier may produce it — the serving
28
- * chain always overwrites the position-1 identity from the wire, so a
29
- * spoofed internal class cannot enter through a transport.
26
+ * minted ONLY by server code, in pipeline/interceptors/auth.ts — the
27
+ * in-process chain's identity interceptor (boot/inprocess.ts) and
28
+ * `serverActingFor`, the server acting for a principal it authenticated
29
+ * itself (the PlatformClient mint). No IdentityVerifier may produce it —
30
+ * the serving chain always overwrites the position-1 identity from the
31
+ * wire, so a spoofed internal class cannot enter through a transport.
30
32
  */
31
33
  export type CallerClass =
32
34
  | "user"
@@ -81,18 +81,15 @@
81
81
  * driver's cost is proportional to the candidates offered (the cloud
82
82
  * checks each one against its authorization engine), and the whole
83
83
  * kind across every tenant is what rolled back on 2026-09-14; the
84
- * request's org is tens of rows. Two idioms carry the rule and both
85
- * keep their reason: a lane whose Go/Java contract org-narrows in
86
- * BOTH editions (environment, memory, schedule, agent-instance,
87
- * agent-share and agent-channel lists; the getBy* parent filters)
88
- * applies its own filter ABOVE the helper call and passes `""`; a lane
89
- * whose contract is cross-org on OSS (`agentExecution.list`,
90
- * `workflowExecution.list`, `listPendingApprovals`) hands the request
91
- * org to the helper, which applies it before the scope ONLY when a
92
- * scope is composed — a scope-less server still returns the input
93
- * untouched with the org unconsulted (the first line above). Two lanes
94
- * have no org on the wire (`session.list`, `apiKey.findAll`) and offer
95
- * the kind until P1 entry 12 pages them.
84
+ * request's org is tens of rows. The lanes over the session, execution
85
+ * and artifact kinds narrow by the request's org or parent in the
86
+ * store's indexed read (store/list-index.ts; every posture honours the
87
+ * request) and page there (pipeline/steps/list-page.ts, one batch per
88
+ * scope call), passing `""`; the small org-scoped lists (environment,
89
+ * memory, schedule, agent-instance, agent-share and agent-channel; the
90
+ * getBy* parent filters) apply their own filter above the helper call
91
+ * and pass `""` too. `apiKey.findAll` has no org on the wire and offers
92
+ * the kind: a few rows per person.
96
93
  * - A KIND WHOSE AUTHORIZATION IS ITS PARENT'S CARRIES THAT PARENT ON
97
94
  * EVERY CANDIDATE (stigmer-cloud 20260913.04 T04, Q-LB-33..36). When
98
95
  * `kind_meta` declares PARENT scope with INHERITED owner
@@ -49,9 +49,12 @@ import { BUILT_IN_STORAGE_TYPES } from "../artifactstorage/artifact-storage.js";
49
49
  import type { ChannelRuntime } from "../domain/agentchannel/channel-runtime.js";
50
50
  import type { IdentityAccountStore } from "../domain/identityaccount/store.js";
51
51
  import type { IamPolicyStore } from "../domain/iampolicy/store.js";
52
+ import type { PlatformClientStore } from "../domain/platformclient/store.js";
52
53
  import type { SecretCodec } from "../encryption/codec.js";
53
54
  import { V1_VERSION } from "../encryption/v1-codec.js";
55
+ import type { PlatformTokenKeyRing } from "../platformtoken/key-ring.js";
54
56
  import type { AuthorizationQueryEngine } from "./authorization-queries.js";
57
+ import type { GuestTokenMinting } from "./guest-token-minting.js";
55
58
  import type { IdentityFederation } from "./identity-federation.js";
56
59
  import type { LicenseStatusProvider } from "./license-status.js";
57
60
  import type { OutboundEgressPolicy } from "./outbound-egress.js";
@@ -88,6 +91,17 @@ import type {
88
91
  */
89
92
  export type ExtensionServiceRegistration = (router: ConnectRouter) => void;
90
93
 
94
+ /**
95
+ * A service registration with the unit that contributed it, so the routes
96
+ * stage can name the unit when a registration shadows a core route
97
+ * (compose.ts refuses that at boot: Connect's router keys handlers by
98
+ * request path and the last registration wins, silently).
99
+ */
100
+ export interface ResolvedServiceRegistration {
101
+ readonly unit: string;
102
+ readonly register: ExtensionServiceRegistration;
103
+ }
104
+
91
105
  /**
92
106
  * One named extension unit — a package's whole contribution to the server
93
107
  * composition. All fields but `name` are optional; an omitted field is
@@ -191,7 +205,7 @@ export interface ResolvedExtensions {
191
205
  readonly statusObservers: ReadonlyArray<AgentExecutionStatusObserver>;
192
206
  readonly responseDecorators: ReadonlyArray<AgentExecutionResponseDecorator>;
193
207
  readonly drivers: ResolvedExtensionDrivers;
194
- readonly services: ReadonlyArray<ExtensionServiceRegistration>;
208
+ readonly services: ReadonlyArray<ResolvedServiceRegistration>;
195
209
  readonly workers: ReadonlyArray<WorkerFactory>;
196
210
  }
197
211
 
@@ -284,6 +298,22 @@ export interface ResolvedExtensionDrivers {
284
298
  * source's relaxed posture (only the link-local range refused).
285
299
  */
286
300
  readonly outboundEgress: OutboundEgressPolicy | undefined;
301
+ /**
302
+ * The platform-client store driver — undefined = compose.ts installs the
303
+ * OSS adapter over the generic Store.
304
+ */
305
+ readonly platformClientStore: PlatformClientStore | undefined;
306
+ /**
307
+ * The platform-token key ring — undefined = compose.ts composes open
308
+ * source's ring under an authentication posture, and refuses to boot a
309
+ * non-open-source edition that supplied none.
310
+ */
311
+ readonly platformTokenKeys: PlatformTokenKeyRing | undefined;
312
+ /**
313
+ * The guest-token capability — undefined = `mintGuestToken` refuses
314
+ * UNIMPLEMENTED with the edition sentence.
315
+ */
316
+ readonly guestTokenMinting: GuestTokenMinting | undefined;
287
317
  }
288
318
 
289
319
  /**
@@ -301,7 +331,7 @@ export function resolveExtensions(
301
331
  const gateSteps = new Map<string, ReadonlyArray<PipelineStep<DescMessage>>>();
302
332
  const statusObservers: AgentExecutionStatusObserver[] = [];
303
333
  const responseDecorators: AgentExecutionResponseDecorator[] = [];
304
- const services: ExtensionServiceRegistration[] = [];
334
+ const services: ResolvedServiceRegistration[] = [];
305
335
  const workers: WorkerFactory[] = [];
306
336
 
307
337
  let edition: ServerEdition | undefined;
@@ -341,6 +371,12 @@ export function resolveExtensions(
341
371
  let licenseStatusDeclaredBy: string | undefined;
342
372
  let outboundEgress: OutboundEgressPolicy | undefined;
343
373
  let outboundEgressDeclaredBy: string | undefined;
374
+ let platformClientStore: PlatformClientStore | undefined;
375
+ let platformClientStoreDeclaredBy: string | undefined;
376
+ let platformTokenKeys: PlatformTokenKeyRing | undefined;
377
+ let platformTokenKeysDeclaredBy: string | undefined;
378
+ let guestTokenMinting: GuestTokenMinting | undefined;
379
+ let guestTokenMintingDeclaredBy: string | undefined;
344
380
  const artifactStorageDrivers = new Map<
345
381
  string,
346
382
  ArtifactStorageDriverFactory
@@ -552,6 +588,36 @@ export function resolveExtensions(
552
588
  outboundEgressDeclaredBy = unit.name;
553
589
  }
554
590
 
591
+ if (unit.drivers?.platformClientStore !== undefined) {
592
+ if (platformClientStoreDeclaredBy !== undefined) {
593
+ throw new Error(
594
+ `extension '${unit.name}' registers a PlatformClientStore, but '${platformClientStoreDeclaredBy}' already did — exactly one may be composed`,
595
+ );
596
+ }
597
+ platformClientStore = unit.drivers.platformClientStore;
598
+ platformClientStoreDeclaredBy = unit.name;
599
+ }
600
+
601
+ if (unit.drivers?.platformTokenKeys !== undefined) {
602
+ if (platformTokenKeysDeclaredBy !== undefined) {
603
+ throw new Error(
604
+ `extension '${unit.name}' registers a PlatformTokenKeyRing, but '${platformTokenKeysDeclaredBy}' already did — exactly one may be composed`,
605
+ );
606
+ }
607
+ platformTokenKeys = unit.drivers.platformTokenKeys;
608
+ platformTokenKeysDeclaredBy = unit.name;
609
+ }
610
+
611
+ if (unit.drivers?.guestTokenMinting !== undefined) {
612
+ if (guestTokenMintingDeclaredBy !== undefined) {
613
+ throw new Error(
614
+ `extension '${unit.name}' registers a GuestTokenMinting capability, but '${guestTokenMintingDeclaredBy}' already did — exactly one may be composed`,
615
+ );
616
+ }
617
+ guestTokenMinting = unit.drivers.guestTokenMinting;
618
+ guestTokenMintingDeclaredBy = unit.name;
619
+ }
620
+
555
621
  if (unit.drivers?.artifactStorageDrivers !== undefined) {
556
622
  for (const [name, factory] of unit.drivers.artifactStorageDrivers) {
557
623
  if ((BUILT_IN_STORAGE_TYPES as ReadonlyArray<string>).includes(name)) {
@@ -643,7 +709,12 @@ export function resolveExtensions(
643
709
  responseDecorators.push(
644
710
  ...(unit.statusTransitionHooks?.responseDecorators ?? []),
645
711
  );
646
- services.push(...(unit.services ?? []));
712
+ services.push(
713
+ ...(unit.services ?? []).map((register) => ({
714
+ unit: unit.name,
715
+ register,
716
+ })),
717
+ );
647
718
  workers.push(...(unit.workers ?? []));
648
719
  }
649
720
 
@@ -679,6 +750,9 @@ export function resolveExtensions(
679
750
  authorizationQueries,
680
751
  licenseStatus,
681
752
  outboundEgress,
753
+ platformClientStore,
754
+ platformTokenKeys,
755
+ guestTokenMinting,
682
756
  },
683
757
  services,
684
758
  workers,