@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
@@ -140,6 +140,7 @@ describe("every static (kind, permission) the served RPCs ask about is a declare
140
140
  "memory",
141
141
  "oauth_app",
142
142
  "organization",
143
+ "platform_client",
143
144
  "plugin",
144
145
  "schedule",
145
146
  "session",
@@ -78,10 +78,16 @@ import { formatObjectRef } from "./tuples.js";
78
78
 
79
79
  /**
80
80
  * Kinds whose missing row is a denial, never `not-found` (arm 4): the
81
- * cloud's `PROBE_EXEMPT_KINDS` restricted to the kinds this edition
82
- * serves — `platform`, `identity_provider`, `platform_client` and
83
- * `invitation` are refused by arm 2 before any row is asked about. The
84
- * cloud's drift test pins the two sets equal over the open-source tier.
81
+ * cloud's `PROBE_EXEMPT_KINDS` (stigmer-cloud
82
+ * src/authorizer/fga-authorizer.ts) restricted to the kinds this edition
83
+ * serves — `platform`, `identity_provider` and `invitation` are refused by
84
+ * arm 2 before any row is asked about. The cloud exempts them because
85
+ * their rows live in its own tables, out of its probe's reach; matching
86
+ * the set here keeps the wire identical across editions (an unknown
87
+ * platform client's id is PERMISSION_DENIED with the annotation's copy in
88
+ * both). The cloud's model drift test pins every kind here inside its
89
+ * `PROBE_EXEMPT_KINDS`, which is why this set is exported; a kind that
90
+ * moves into open source joins this set in the same change.
85
91
  *
86
92
  * `iam_policy` is reachable as a target through the IamPolicy RPCs' own
87
93
  * `resource.kind` (a caller may name it); the grant scope refuses such a
@@ -91,6 +97,7 @@ import { formatObjectRef } from "./tuples.js";
91
97
  export const NOT_FOUND_EXEMPT_KINDS: ReadonlySet<ApiResourceKind> = new Set([
92
98
  ApiResourceKind.identity_account,
93
99
  ApiResourceKind.iam_policy,
100
+ ApiResourceKind.platform_client,
94
101
  ]);
95
102
 
96
103
  /** The Java RequestAuthorizationService's pre-check copy, byte for byte (the cloud renders the same). */
@@ -34,6 +34,7 @@ const DECLARED_KINDS = [
34
34
  ApiResourceKind.iam_policy,
35
35
  ApiResourceKind.api_key,
36
36
  ApiResourceKind.oauth_app,
37
+ ApiResourceKind.platform_client,
37
38
  ApiResourceKind.organization,
38
39
  ApiResourceKind.agent,
39
40
  ApiResourceKind.agent_channel,
@@ -94,6 +95,13 @@ const WIRE_PERMISSIONS_BY_TYPE: Readonly<
94
95
  "can_grant_access",
95
96
  "can_view_access",
96
97
  ],
98
+ platform_client: [
99
+ "can_view",
100
+ "can_edit",
101
+ "can_delete",
102
+ "can_grant_access",
103
+ "can_view_access",
104
+ ],
97
105
  organization: [
98
106
  "can_view",
99
107
  "can_edit",
@@ -262,7 +270,6 @@ describe("the built-in model's registry", () => {
262
270
  for (const unserved of [
263
271
  ApiResourceKind.platform,
264
272
  ApiResourceKind.identity_provider,
265
- ApiResourceKind.platform_client,
266
273
  ApiResourceKind.invitation,
267
274
  ]) {
268
275
  expect(declarationFor(unserved), kindEnumName(unserved)).toBeUndefined();
@@ -12,11 +12,11 @@
12
12
  * chain past the depth bound) and for the cloud's drift test, which
13
13
  * builds a model from the live `.fga` files and compares.
14
14
  *
15
- * Registered: every kind of the open-source tier — the twenty-three
16
- * `kind_meta.tier: open_source` members — and nothing else. The four
15
+ * Registered: every kind of the open-source tier — the twenty-four
16
+ * `kind_meta.tier: open_source` members — and nothing else. The three
17
17
  * files `fga.mod` lists that have no declaration here (`platform`,
18
- * `identity_provider`, `platform_client`, `invitation`) are kinds this
19
- * edition does not serve: a check that targets one is refused by the
18
+ * `identity_provider`, `invitation`) are kinds this edition does not
19
+ * serve: a check that targets one is refused by the
20
20
  * driver, and a tuple that names one (`identity_provider#platform_user`)
21
21
  * resolves to nobody. The registry test pins the list against the tier.
22
22
  */
@@ -38,6 +38,7 @@ import { mcpServerDeclaration } from "./mcp_server.js";
38
38
  import { memoryDeclaration } from "./memory.js";
39
39
  import { oauthAppDeclaration } from "./oauth_app.js";
40
40
  import { organizationDeclaration } from "./organization.js";
41
+ import { platformClientDeclaration } from "./platform_client.js";
41
42
  import { pluginDeclaration } from "./plugin.js";
42
43
  import type { KindDeclaration } from "./rewrite.js";
43
44
  import { scheduleDeclaration } from "./schedule.js";
@@ -77,6 +78,7 @@ export const builtInModel: Model = newModel([
77
78
  iamPolicyDeclaration,
78
79
  apiKeyDeclaration,
79
80
  oauthAppDeclaration,
81
+ platformClientDeclaration,
80
82
  organizationDeclaration,
81
83
  agentDeclaration,
82
84
  agentChannelDeclaration,
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Transcript of fga/model/iam/oauth_app.fga — the one organization-scoped
3
- * kind whose `owner` has NO admin arm while its `viewer` does: the file's
2
+ * Transcript of fga/model/iam/oauth_app.fga — an organization-scoped kind
3
+ * whose `owner` has NO admin arm while its `viewer` does (platform_client
4
+ * shares the shape, for the same reason): the file's
4
5
  * "administrative visibility" for the organization's admins over a
5
6
  * registration that holds vendor client credentials, with change and
6
7
  * deletion the creator's (or an explicit grant's) alone, and no public
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Transcript of fga/model/iam/platform_client.fga — relation for relation
3
+ * the oauth_app shape: organization-scoped, an `owner` with no admin arm
4
+ * and a `viewer` that has one. A platform client holds credential material,
5
+ * so the model is restricted: its creator (the owner) edits, deletes,
6
+ * rotates and grants; the organization's admins may view it; ordinary
7
+ * members have no access at all, which is why listByOrg narrows through
8
+ * the list read scope. There is no public level.
9
+ */
10
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
11
+ import { PlatformClientSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
12
+
13
+ import {
14
+ computed,
15
+ declareKind,
16
+ direct,
17
+ from,
18
+ objectOf,
19
+ union,
20
+ } from "./rewrite.js";
21
+
22
+ export const platformClientDeclaration = declareKind({
23
+ kind: ApiResourceKind.platform_client,
24
+ schema: PlatformClientSchema,
25
+ source: "fga/model/iam/platform_client.fga",
26
+ relations: [
27
+ ["organization", direct(objectOf("organization"))],
28
+ ["owner", direct(objectOf("identity_account"))],
29
+ [
30
+ "viewer",
31
+ union(
32
+ direct(objectOf("identity_account")),
33
+ computed("owner"),
34
+ from("admin", "organization"),
35
+ ),
36
+ ],
37
+ ["can_view", computed("viewer")],
38
+ ["can_edit", computed("owner")],
39
+ ["can_delete", computed("owner")],
40
+ ["can_grant_access", computed("owner")],
41
+ ["can_view_access", computed("viewer")],
42
+ ],
43
+ });
@@ -236,10 +236,11 @@ describe("the require-authentication posture on the wire", () => {
236
236
  );
237
237
  });
238
238
 
239
- it("is_public methods stay reachable tokenless (getServerInfo)", async () => {
239
+ it("is_public methods stay reachable tokenless (getServerInfo), and it reports the posture", async () => {
240
240
  const platform = createClient(PlatformQueryController, transportWith());
241
241
  const info = await platform.getServerInfo({});
242
242
  expect(info.edition).not.toBe("");
243
+ expect(info.authenticationRequired).toBe(true);
243
244
  });
244
245
 
245
246
  it("the gRPC health service stays reachable tokenless — a Kubernetes grpc probe survives the posture (stigmer#974)", async () => {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pins the list-indexed surface (list-indexes.ts). The table below is
3
+ * every declaration's keys against its revision: a store trusts a row's
4
+ * facts only under the revision they were derived with, so a change to a
5
+ * declaration's keys without a new revision would read rows written under
6
+ * the old keys as if they carried the new ones. Changing a declaration
7
+ * therefore fails here until its revision is bumped and this table is
8
+ * updated in the same change.
9
+ */
10
+ import { describe, expect, it } from "vitest";
11
+
12
+ import {
13
+ ListIndexRegistry,
14
+ listIndexFingerprint,
15
+ } from "../../store/list-index.js";
16
+ import { LIST_INDEXES } from "../list-indexes.js";
17
+
18
+ const PINNED: Readonly<
19
+ Record<string, { revision: number; fingerprint: string }>
20
+ > = {
21
+ agent_execution: {
22
+ revision: 1,
23
+ fingerprint: "agent_execution{session=field:spec.session_id}",
24
+ },
25
+ artifact: {
26
+ revision: 1,
27
+ fingerprint:
28
+ "artifact{agent_execution=field:spec.source.agent_execution_id,workflow_execution=field:spec.source.workflow_execution_id}",
29
+ },
30
+ session: {
31
+ revision: 1,
32
+ fingerprint:
33
+ "session{agent_instance=field:spec.agent_instance_id,channel=label:stigmer.ai/channel-id}",
34
+ },
35
+ workflow_execution: {
36
+ revision: 1,
37
+ fingerprint:
38
+ "workflow_execution{workflow=field:spec.workflow_id,workflow_instance=field:spec.workflow_instance_id}",
39
+ },
40
+ };
41
+
42
+ describe("the list-indexed surface", () => {
43
+ it("opens as one registry: no kind declared twice", () => {
44
+ expect(() => new ListIndexRegistry(LIST_INDEXES)).not.toThrow();
45
+ });
46
+
47
+ it("declares exactly the pinned kinds", () => {
48
+ const kinds = LIST_INDEXES.map(
49
+ (d) => listIndexFingerprint(d).split("{")[0],
50
+ );
51
+ expect(kinds.sort()).toEqual(Object.keys(PINNED).sort());
52
+ });
53
+
54
+ it.each(LIST_INDEXES.map((d) => [listIndexFingerprint(d), d] as const))(
55
+ "%s carries the revision its keys are pinned to",
56
+ (fingerprint, declaration) => {
57
+ const kind = fingerprint.split("{")[0] ?? "";
58
+ const pinned = PINNED[kind];
59
+ expect(
60
+ { revision: declaration.revision, fingerprint },
61
+ "a declaration's keys changed: bump its revision and update this table together",
62
+ ).toEqual(pinned);
63
+ },
64
+ );
65
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Pins the routes stage's refusal of a shadowing registration: Connect's
3
+ * router lets the last registration of a path win silently, so an
4
+ * extension that registers a service the core already serves — or that
5
+ * another extension already registered — must fail boot, naming itself and
6
+ * whoever serves the path. Distinct services pass untouched, in order.
7
+ */
8
+ import { createConnectRouter } from "@connectrpc/connect";
9
+ import { describe, expect, it } from "vitest";
10
+
11
+ import { OAuthAppQueryController } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/query_pb";
12
+ import { PlatformClientQueryController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/query_pb";
13
+ import { PlatformClientTokenController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
14
+
15
+ import { registerExtensionServicesUnshadowed } from "../route-shadowing.js";
16
+
17
+ describe("registerExtensionServicesUnshadowed", () => {
18
+ it("refuses an extension service the core already serves, naming the unit and the path", () => {
19
+ const router = createConnectRouter();
20
+ router.service(PlatformClientTokenController, {});
21
+ expect(() =>
22
+ registerExtensionServicesUnshadowed(router, [
23
+ {
24
+ unit: "cloud-iam",
25
+ register: (r) => void r.service(PlatformClientTokenController, {}),
26
+ },
27
+ ]),
28
+ ).toThrow(
29
+ /extension 'cloud-iam' registers \/ai\.stigmer\.iam\.platformclient\.v1\.PlatformClientTokenController\/mintUserToken, which the core already serves/,
30
+ );
31
+ });
32
+
33
+ it("refuses a second extension registering what a first one did", () => {
34
+ const router = createConnectRouter();
35
+ expect(() =>
36
+ registerExtensionServicesUnshadowed(router, [
37
+ {
38
+ unit: "first",
39
+ register: (r) => void r.service(OAuthAppQueryController, {}),
40
+ },
41
+ {
42
+ unit: "second",
43
+ register: (r) =>
44
+ void r.rpc(OAuthAppQueryController.method.get, () => {
45
+ throw new Error("never called");
46
+ }),
47
+ },
48
+ ]),
49
+ ).toThrow(
50
+ /extension 'second' registers .*OAuthAppQueryController\/get, which extension 'first' already serves/,
51
+ );
52
+ });
53
+
54
+ it("registers distinct services from every unit", () => {
55
+ const router = createConnectRouter();
56
+ router.service(OAuthAppQueryController, {});
57
+ registerExtensionServicesUnshadowed(router, [
58
+ {
59
+ unit: "billing",
60
+ register: (r) => void r.service(PlatformClientQueryController, {}),
61
+ },
62
+ ]);
63
+ expect(router.handlers.map((handler) => handler.requestPath)).toContain(
64
+ "/ai.stigmer.iam.platformclient.v1.PlatformClientQueryController/listByOrg",
65
+ );
66
+ });
67
+ });
@@ -108,6 +108,11 @@ import { registerExecutionContextServices } from "../domain/executioncontext/con
108
108
  import { registerGitHubServices } from "../domain/github/controller.js";
109
109
  import { registerMemoryServices } from "../domain/memory/controller.js";
110
110
  import { registerOAuthAppServices } from "../domain/oauthapp/controller.js";
111
+ import { registerPlatformClientServices } from "../domain/platformclient/controller.js";
112
+ import { newPlatformClientOriginGuard } from "../domain/platformclient/origin-guard.js";
113
+ import { newResourcePlatformClientStore } from "../domain/platformclient/resource-store.js";
114
+ import { registerPlatformClientTokenService } from "../domain/platformclient/token-controller.js";
115
+ import { newPlatformClientTokenVerifier } from "../domain/platformclient/verifier.js";
111
116
  import { registerOrganizationServices } from "../domain/organization/controller.js";
112
117
  import { registerMcpServerServices } from "../domain/mcpserver/controller.js";
113
118
  import { registerPlatformServices } from "../domain/platform/controller.js";
@@ -144,6 +149,7 @@ import {
144
149
  import type { SecretCodec } from "../encryption/codec.js";
145
150
  import { getOrCreateNamedKey } from "../encryption/key-manager.js";
146
151
  import { V1_VERSION } from "../encryption/v1-codec.js";
152
+ import { resolvePlatformTokenKeys } from "../platformtoken/key-ring.js";
147
153
  import { registerActivityServices } from "../query/activity/controller.js";
148
154
  import { ActivityHandler } from "../query/activity/handler.js";
149
155
  import { registerSearchServices } from "../query/search/controller.js";
@@ -203,9 +209,11 @@ import {
203
209
  } from "../transport/console/handler.js";
204
210
  import { createRegistryLanes } from "../transport/registry/lanes.js";
205
211
  import { createUnifiedPortServer } from "../transport/server.js";
212
+ import { registerExtensionServicesUnshadowed } from "./route-shadowing.js";
206
213
  import type { ServerConfig } from "./config.js";
207
214
  import { createInProcessClients } from "./inprocess.js";
208
215
  import type { InProcessClients } from "./inprocess.js";
216
+ import { LIST_INDEXES } from "./list-indexes.js";
209
217
  import type { Logger } from "./logger.js";
210
218
 
211
219
  export interface ComposedServer {
@@ -329,20 +337,22 @@ export async function composeServer(
329
337
  }
330
338
  // Stage: storage — the driver selection seam (DD-010): DATABASE_URL
331
339
  // present → Postgres (async connect + advisory-locked migrations), else
332
- // sqlite on DB_PATH (migrations v1–v7, incl. adopting a Go-created
340
+ // sqlite on DB_PATH (its whole chain, incl. adopting a Go-created
333
341
  // database — D2 §3 schema continuity). Postgres wins when both are set:
334
342
  // DB_PATH always has a default value, so no other precedence could ever
335
- // select Postgres (config.ts documents the contract). A failure here is
336
- // a loud boot throw, never a degraded server. The operator identity
337
- // (#400) is installed by main.ts — once per PROCESS, before any writer
338
- // exists — not here: composeServer is re-entrant for tests, the
339
- // identity seam deliberately is not.
343
+ // select Postgres (config.ts documents the contract). Either driver
344
+ // opens with the list indexes (list-indexes.ts) and reconciles them
345
+ // before it returns. A failure here is a loud boot throw, never a
346
+ // degraded server. The operator identity (#400) is installed by main.ts
347
+ // — once per PROCESS, before any writer exists — not here: composeServer
348
+ // is re-entrant for tests, the identity seam deliberately is not.
340
349
  let store: Store;
350
+ const storeOptions = { listIndexes: LIST_INDEXES };
341
351
  if (config.databaseUrl !== "") {
342
- store = await PostgresStore.open(config.databaseUrl, logger);
352
+ store = await PostgresStore.open(config.databaseUrl, logger, storeOptions);
343
353
  logger.info("storage driver selected", { driver: "postgres" });
344
354
  } else {
345
- store = SqliteStore.open(config.dbPath, logger);
355
+ store = SqliteStore.open(config.dbPath, logger, storeOptions);
346
356
  logger.info("storage driver selected", {
347
357
  driver: "sqlite",
348
358
  dbPath: config.dbPath,
@@ -413,6 +423,14 @@ export async function composeServer(
413
423
  // bindings are what the IamPolicy controller registers over (routes).
414
424
  const iamPolicies =
415
425
  extensions.drivers.iamPolicyStore ?? newResourceIamPolicyStore(store);
426
+ // The PlatformClient store PORT, bound the same way
427
+ // (`drivers.platformClientStore`; open source's adapter over `store`
428
+ // when absent): the chains, the mint and the verifier's liveness read all
429
+ // go through this one binding, so a composed driver is followed
430
+ // everywhere by construction.
431
+ const platformClients =
432
+ extensions.drivers.platformClientStore ??
433
+ newResourcePlatformClientStore(store);
416
434
  const iamPolicyGrantPath = newIamPolicyGrantPath({
417
435
  policies: iamPolicies,
418
436
  lifecycle: extensions.drivers.resourceAuthorizationLifecycle,
@@ -690,6 +708,26 @@ export async function composeServer(
690
708
  writeVersionValue === "" ? DEFAULT_WRITE_VERSION : writeVersionValue,
691
709
  registeredCodecs: [...secretCodecs.keys()].sort(),
692
710
  });
711
+ // The platform-token key ring joins the FATAL side of the asymmetry:
712
+ // under an authentication posture a server that cannot sign or verify
713
+ // its own tokens would refuse every PlatformClient-minted caller, and an
714
+ // explicitly configured key that does not load is configuration the
715
+ // operator meant (the ladder's rule). No posture, no ring
716
+ // (resolvePlatformTokenKeys carries the four arms).
717
+ const platformTokenKeys = resolvePlatformTokenKeys({
718
+ requireAuthentication,
719
+ supplied: extensions.drivers.platformTokenKeys,
720
+ edition: extensions.edition,
721
+ });
722
+ logger.info("platform-token keys resolved", {
723
+ source:
724
+ platformTokenKeys === undefined
725
+ ? "none (no authentication posture)"
726
+ : extensions.drivers.platformTokenKeys !== undefined
727
+ ? "extension"
728
+ : "open-source ladder",
729
+ signing: platformTokenKeys?.signer !== undefined,
730
+ });
693
731
  // Stage: temporal (#18). Construction is sync and connection-free —
694
732
  // initialConnect/startWorkers/startHealthMonitor run in start(), all
695
733
  // NON-fatal (Go server.go: the server boots and serves with the engine
@@ -985,7 +1023,10 @@ export async function composeServer(
985
1023
  reserve: (key, declaredSizeBytes) => {
986
1024
  const { ttlMs } = skillUploadSlots.reserve(key, declaredSizeBytes);
987
1025
  return {
988
- url: skillUploadUrl(config.skillTransferBaseUrl, uploadRefOf(key)),
1026
+ url: skillUploadUrl(
1027
+ config.skillTransferBaseUrl,
1028
+ uploadRefOf(key),
1029
+ ),
989
1030
  ttlMs,
990
1031
  };
991
1032
  },
@@ -1161,9 +1202,12 @@ export async function composeServer(
1161
1202
  case "configured":
1162
1203
  break;
1163
1204
  case "derived":
1164
- logger.info("STIGMER_OAUTH_REDIRECT_URI is not set — deriving the served console's callback for MCP server OAuth Connect", {
1165
- redirectUri: oauthRedirect.uri,
1166
- });
1205
+ logger.info(
1206
+ "STIGMER_OAUTH_REDIRECT_URI is not set — deriving the served console's callback for MCP server OAuth Connect",
1207
+ {
1208
+ redirectUri: oauthRedirect.uri,
1209
+ },
1210
+ );
1167
1211
  break;
1168
1212
  case "absent":
1169
1213
  logger.warn(
@@ -1175,7 +1219,8 @@ export async function composeServer(
1175
1219
  throw new Error(String(exhaustive));
1176
1220
  }
1177
1221
  }
1178
- const oauthRedirectUri = oauthRedirect.kind === "absent" ? "" : oauthRedirect.uri;
1222
+ const oauthRedirectUri =
1223
+ oauthRedirect.kind === "absent" ? "" : oauthRedirect.uri;
1179
1224
  // The ONE fetch the McpServer domain dials user-supplied URLs with: the
1180
1225
  // endpoint it probes at save time and the login server it reaches on
1181
1226
  // Sign in. Every hop is judged under the edition's egress policy
@@ -1284,6 +1329,34 @@ export async function composeServer(
1284
1329
  authorizer,
1285
1330
  authorizationLifecycle,
1286
1331
  });
1332
+ // PlatformClient, OAuthApp's inbound counterpart: served in every
1333
+ // composition over the store PORT bound above. The token service is
1334
+ // registered here too — mintGuestToken dispatches to the composed
1335
+ // capability — so nothing else may register it (route-shadowing.ts).
1336
+ // The mint writes end users through the identity-account create path
1337
+ // and the one grant path, as the platform client (mint.ts), and signs
1338
+ // with the ring the keys stage resolved (none without a posture: the
1339
+ // mint then refuses).
1340
+ registerPlatformClientServices(router, {
1341
+ clients: platformClients,
1342
+ store,
1343
+ logger,
1344
+ authorizer,
1345
+ authorizationLifecycle,
1346
+ listReadScope,
1347
+ });
1348
+ registerPlatformClientTokenService(router, {
1349
+ mint: {
1350
+ clients: platformClients,
1351
+ accounts: identityAccounts,
1352
+ createAccount: createIdentityAccount,
1353
+ grantPath: iamPolicyGrantPath,
1354
+ keys: platformTokenKeys,
1355
+ logger,
1356
+ now: () => new Date(),
1357
+ },
1358
+ guestTokenMinting: extensions.drivers.guestTokenMinting,
1359
+ });
1287
1360
  registerExecutionContextServices(router, {
1288
1361
  store,
1289
1362
  logger,
@@ -1549,6 +1622,7 @@ export async function composeServer(
1549
1622
  runnerAuthService: runnerCredentials,
1550
1623
  edition: extensions.edition,
1551
1624
  version: options.version ?? SERVER_VERSION,
1625
+ authenticationRequired: requireAuthentication,
1552
1626
  // The built-in `absent` answer installs here, at the consumer, when
1553
1627
  // no unit registered a license-status provider: open source and the
1554
1628
  // cloud hold no key, and only an Enterprise unit ever has one.
@@ -1559,10 +1633,9 @@ export async function composeServer(
1559
1633
  // Extension services register after the whole OSS set, inside the ONE
1560
1634
  // routes closure — so the serving router AND the in-process transport
1561
1635
  // see them by construction (blueprint §2a; DD-002's parity doctrine
1562
- // extends to extension services unchanged).
1563
- for (const registerExtensionServices of extensions.services) {
1564
- registerExtensionServices(router);
1565
- }
1636
+ // extends to extension services unchanged). A registration that would
1637
+ // shadow a route already served is a boot throw (route-shadowing.ts).
1638
+ registerExtensionServicesUnshadowed(router, extensions.services);
1566
1639
  };
1567
1640
  const inProcessWiring = createInProcessClients(routes, logger);
1568
1641
  inProcess = inProcessWiring.clients;
@@ -1610,6 +1683,18 @@ export async function composeServer(
1610
1683
  ...(requireAuthentication
1611
1684
  ? [newApiKeyIdentityVerifier({ store, accounts: identityAccounts })]
1612
1685
  : []),
1686
+ // The PlatformClient user-token lane rides the ring, which the keys
1687
+ // stage resolves only under an authentication posture. It sits before
1688
+ // `oidc` for the runner-subject verifier's reason (stigmer#1137): the
1689
+ // OIDC verifier would claim the platform token and throw on it.
1690
+ ...(platformTokenKeys !== undefined
1691
+ ? [
1692
+ newPlatformClientTokenVerifier({
1693
+ keys: platformTokenKeys,
1694
+ clients: platformClients,
1695
+ }),
1696
+ ]
1697
+ : []),
1613
1698
  ...(authorizationPosture === "built-in"
1614
1699
  ? [
1615
1700
  newRunnerSubjectIdentityVerifier({
@@ -1652,6 +1737,16 @@ export async function composeServer(
1652
1737
  ? [`extension '${extensions.requireAuthentication.declaredBy}'`]
1653
1738
  : []),
1654
1739
  ];
1740
+ // Caller guards: open source's own first, then the extensions' — the
1741
+ // rule the verifier list follows ("OSS entries first"). The origin guard
1742
+ // exists only where PlatformClient tokens can be presented, under the
1743
+ // posture that verifies them.
1744
+ const callerGuards = [
1745
+ ...(platformTokenKeys !== undefined
1746
+ ? [newPlatformClientOriginGuard({ clients: platformClients, logger })]
1747
+ : []),
1748
+ ...extensions.callerGuards,
1749
+ ];
1655
1750
  logger.info("authentication posture resolved", {
1656
1751
  posture: requireAuthentication ? "required" : "trusted-local",
1657
1752
  source: postureSources.length > 0 ? postureSources.join(" + ") : "none",
@@ -1680,7 +1775,7 @@ export async function composeServer(
1680
1775
  logger,
1681
1776
  createVerifierChainInterceptor(
1682
1777
  identityVerifiers,
1683
- extensions.callerGuards,
1778
+ callerGuards,
1684
1779
  logger,
1685
1780
  requireAuthentication,
1686
1781
  ),
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The whole list-indexed surface, one explicit list (the search
3
+ * registry's composition-root idiom, query/search/registry.ts; house rule:
4
+ * no import side effects). The store is opened with it
5
+ * (`StoreOpenOptions.listIndexes`), keeps a declared kind's facts on every
6
+ * write, and refuses a read through a declaration it was not opened with,
7
+ * so a lane that reads an index and this list cannot disagree silently.
8
+ *
9
+ * A kind joins when a lane needs to read one organization's or one
10
+ * parent's rows without decoding the whole kind; the kinds here were
11
+ * chosen on the hosted edition's row counts and sizes (2026-09-23: every
12
+ * other org-scoped kind held under a hundred rows and a hundred kilobytes).
13
+ */
14
+ import { agentExecutionListIndex } from "../domain/agentexecution/list-index.js";
15
+ import { artifactListIndex } from "../domain/artifact/list-index.js";
16
+ import { sessionListIndex } from "../domain/session/list-index.js";
17
+ import { workflowExecutionListIndex } from "../domain/workflowexecution/list-index.js";
18
+ import type { ListIndexDeclaration } from "../store/list-index.js";
19
+
20
+ export const LIST_INDEXES: ReadonlyArray<ListIndexDeclaration> = [
21
+ agentExecutionListIndex,
22
+ artifactListIndex,
23
+ sessionListIndex,
24
+ workflowExecutionListIndex,
25
+ ];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The routes stage's one refusal: an extension may not register a route
3
+ * the router already serves.
4
+ *
5
+ * Connect's router keeps every registration, and both the serving adapter
6
+ * and the in-process transport build their path table with a plain map
7
+ * set, so a second registration of `/<service>/<method>` silently wins.
8
+ * Extensions register after the whole core set (compose.ts), so a
9
+ * composition that re-registers a service the core has come to serve —
10
+ * the moment a cloud domain moves into open source and the cloud's pin
11
+ * advances without deleting its own handlers — would shadow the core with
12
+ * no error, and serve from whatever store its old handlers read. Refusing
13
+ * at boot turns that into the loudest possible failure; the fix is always
14
+ * the domain's driver point, never a second service.
15
+ *
16
+ * Checked per request path, because `router.service` registers every
17
+ * method of a descriptor (the unimplemented ones answering UNIMPLEMENTED)
18
+ * and `router.rpc` registers one; two extensions colliding with each
19
+ * other are refused the same way.
20
+ */
21
+ import type { ConnectRouter } from "@connectrpc/connect";
22
+
23
+ import type { ResolvedServiceRegistration } from "../extensions/registry.js";
24
+
25
+ /** Runs each extension registration, refusing any path already routed. */
26
+ export function registerExtensionServicesUnshadowed(
27
+ router: ConnectRouter,
28
+ registrations: ReadonlyArray<ResolvedServiceRegistration>,
29
+ ): void {
30
+ // Request path → who serves it, as the refusal names them.
31
+ const servedBy = new Map<string, string>(
32
+ router.handlers.map((handler) => [handler.requestPath, "the core"]),
33
+ );
34
+ for (const { unit, register } of registrations) {
35
+ const before = router.handlers.length;
36
+ register(router);
37
+ for (const handler of router.handlers.slice(before)) {
38
+ const server = servedBy.get(handler.requestPath);
39
+ if (server !== undefined) {
40
+ throw new Error(
41
+ `extension '${unit}' registers ${handler.requestPath}, which ${server} already serves — a route is never registered twice; an edition changes a core domain's behaviour through its driver points`,
42
+ );
43
+ }
44
+ servedBy.set(handler.requestPath, `extension '${unit}'`);
45
+ }
46
+ }
47
+ }