@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,318 @@
1
+ /**
2
+ * The PlatformClientStore PORT-CONTRACT KIT: every behavior an
3
+ * implementation of store.ts must satisfy identically, as cases a driver's
4
+ * test iterates. Open source runs them over its own adapter
5
+ * (resource-store.ts) on sqlite and Postgres in
6
+ * __tests__/resource-store.test.ts; a composition runs the SAME cases over
7
+ * the store it registers as `drivers.platformClientStore` (the cloud's
8
+ * `cloud.iam_platform_client` store), so "the port holds" is one statement
9
+ * proven per driver, never restated per repository.
10
+ *
11
+ * Shape: declarations over the port-contract runner (store/port-contract.ts),
12
+ * which owns the fresh fixture per case, the cleanup, and the reason the
13
+ * kit returns cases instead of calling vitest's `describe`. The
14
+ * disconnected-store case matters most here: the verifier reads this port
15
+ * on every request a minted token bears, so an outage that read as "no
16
+ * client" would revoke every live token instead of failing loudly.
17
+ *
18
+ * The duplicate arms check `instanceof DuplicatePlatformClientError` on the
19
+ * exported class, because that is how the create chain's Persist step
20
+ * catches it: a driver throwing a lookalike fails here for the reason it
21
+ * would fail a create.
22
+ *
23
+ * Case names are the contract lines, in the port's words. The OSS test pins
24
+ * the full list, so a case cannot drop out of the kit unnoticed.
25
+ */
26
+ import assert from "node:assert/strict";
27
+
28
+ import { create, equals } from "@bufbuild/protobuf";
29
+
30
+ import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
31
+ import { PlatformClientSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
32
+
33
+ import { portContractCases } from "../../store/port-contract.js";
34
+ import type {
35
+ PortContractCase,
36
+ PortContractDeclaration,
37
+ PortContractFixture,
38
+ } from "../../store/port-contract.js";
39
+ import { DuplicatePlatformClientError } from "./store.js";
40
+ import type { PlatformClientStore } from "./store.js";
41
+
42
+ /** The runner's fixture over this port — the name a driver's test and the barrel know it by. */
43
+ export type PlatformClientStoreContractFixture =
44
+ PortContractFixture<PlatformClientStore>;
45
+
46
+ export type PlatformClientStoreContractCase = PortContractCase;
47
+
48
+ /** A client as the domain writes it; each fixture names its own id, org, slug and client_id. */
49
+ function platformClient(fields: {
50
+ id: string;
51
+ org: string;
52
+ slug: string;
53
+ clientId: string;
54
+ allowedOrigins?: string[];
55
+ }): PlatformClient {
56
+ return create(PlatformClientSchema, {
57
+ apiVersion: "iam.stigmer.ai/v1",
58
+ kind: "PlatformClient",
59
+ metadata: {
60
+ id: fields.id,
61
+ name: fields.slug,
62
+ slug: fields.slug,
63
+ org: fields.org,
64
+ },
65
+ spec: {
66
+ clientId: fields.clientId,
67
+ clientSecretHash: `hash-of-${fields.clientId}`,
68
+ secretFingerprint: "abcdef",
69
+ autoProvisionAccounts: true,
70
+ allowedOrigins: fields.allowedOrigins ?? [],
71
+ },
72
+ });
73
+ }
74
+
75
+ function assertSameClient(
76
+ actual: PlatformClient | undefined,
77
+ expected: PlatformClient,
78
+ message: string,
79
+ ): void {
80
+ assert.ok(actual !== undefined, `${message}: no client was answered`);
81
+ assert.ok(equals(PlatformClientSchema, actual, expected), message);
82
+ }
83
+
84
+ async function assertDuplicate(
85
+ write: Promise<void>,
86
+ message: string,
87
+ ): Promise<void> {
88
+ await assert.rejects(
89
+ write,
90
+ (error: unknown) => error instanceof DuplicatePlatformClientError,
91
+ message,
92
+ );
93
+ }
94
+
95
+ const ACME_DASHBOARD = platformClient({
96
+ id: "pcl_contract_acme_dashboard",
97
+ org: "acme",
98
+ slug: "dashboard",
99
+ clientId: "stgm_cid_contract_acme_dashboard",
100
+ });
101
+
102
+ const CASES: ReadonlyArray<PortContractDeclaration<PlatformClientStore>> = [
103
+ [
104
+ "save then findById, findByClientId and findByOrgAndSlug answer the saved client",
105
+ async ({ store }) => {
106
+ await store.save(ACME_DASHBOARD);
107
+ assertSameClient(
108
+ await store.findById("pcl_contract_acme_dashboard"),
109
+ ACME_DASHBOARD,
110
+ "findById must answer the saved row",
111
+ );
112
+ assertSameClient(
113
+ await store.findByClientId("stgm_cid_contract_acme_dashboard"),
114
+ ACME_DASHBOARD,
115
+ "findByClientId must answer the saved row",
116
+ );
117
+ assertSameClient(
118
+ await store.findByOrgAndSlug("acme", "dashboard"),
119
+ ACME_DASHBOARD,
120
+ "findByOrgAndSlug must answer the saved row",
121
+ );
122
+ },
123
+ ],
124
+ [
125
+ "lookups of a client that is not held answer undefined",
126
+ async ({ store }) => {
127
+ await store.save(ACME_DASHBOARD);
128
+ assert.equal(await store.findById("pcl_contract_nobody"), undefined);
129
+ assert.equal(
130
+ await store.findByClientId("stgm_cid_contract_nobody"),
131
+ undefined,
132
+ );
133
+ assert.equal(await store.findByOrgAndSlug("acme", "nobody"), undefined);
134
+ assert.equal(
135
+ await store.findByOrgAndSlug("globex", "dashboard"),
136
+ undefined,
137
+ "a slug is scoped to its organization: another org's lookup must miss",
138
+ );
139
+ },
140
+ ],
141
+ [
142
+ "save under a held id raises DuplicatePlatformClientError",
143
+ async ({ store }) => {
144
+ await store.save(ACME_DASHBOARD);
145
+ await assertDuplicate(
146
+ store.save(
147
+ platformClient({
148
+ id: "pcl_contract_acme_dashboard",
149
+ org: "acme",
150
+ slug: "other",
151
+ clientId: "stgm_cid_contract_other",
152
+ }),
153
+ ),
154
+ "a held id must be refused, never overwritten",
155
+ );
156
+ },
157
+ ],
158
+ [
159
+ "save under a held (org, slug) raises DuplicatePlatformClientError",
160
+ async ({ store }) => {
161
+ await store.save(ACME_DASHBOARD);
162
+ await assertDuplicate(
163
+ store.save(
164
+ platformClient({
165
+ id: "pcl_contract_acme_second",
166
+ org: "acme",
167
+ slug: "dashboard",
168
+ clientId: "stgm_cid_contract_acme_second",
169
+ }),
170
+ ),
171
+ "a held (org, slug) must be refused",
172
+ );
173
+ },
174
+ ],
175
+ [
176
+ "save under a held client_id raises DuplicatePlatformClientError",
177
+ async ({ store }) => {
178
+ await store.save(ACME_DASHBOARD);
179
+ await assertDuplicate(
180
+ store.save(
181
+ platformClient({
182
+ id: "pcl_contract_acme_third",
183
+ org: "acme",
184
+ slug: "third",
185
+ clientId: "stgm_cid_contract_acme_dashboard",
186
+ }),
187
+ ),
188
+ "a held client_id must be refused: the mint resolves a client by it",
189
+ );
190
+ },
191
+ ],
192
+ [
193
+ "the same slug in another organization is a different client",
194
+ async ({ store }) => {
195
+ await store.save(ACME_DASHBOARD);
196
+ const globex = platformClient({
197
+ id: "pcl_contract_globex_dashboard",
198
+ org: "globex",
199
+ slug: "dashboard",
200
+ clientId: "stgm_cid_contract_globex_dashboard",
201
+ });
202
+ await store.save(globex);
203
+ assertSameClient(
204
+ await store.findByOrgAndSlug("globex", "dashboard"),
205
+ globex,
206
+ "each organization answers its own client for the shared slug",
207
+ );
208
+ },
209
+ ],
210
+ [
211
+ "update replaces the row in place",
212
+ async ({ store }) => {
213
+ await store.save(ACME_DASHBOARD);
214
+ const edited = platformClient({
215
+ id: "pcl_contract_acme_dashboard",
216
+ org: "acme",
217
+ slug: "dashboard",
218
+ clientId: "stgm_cid_contract_acme_dashboard",
219
+ allowedOrigins: ["https://app.acme.example"],
220
+ });
221
+ await store.update(edited);
222
+ assertSameClient(
223
+ await store.findById("pcl_contract_acme_dashboard"),
224
+ edited,
225
+ "findById must answer the replaced row",
226
+ );
227
+ assertSameClient(
228
+ await store.findByClientId("stgm_cid_contract_acme_dashboard"),
229
+ edited,
230
+ "findByClientId must answer the replaced row",
231
+ );
232
+ },
233
+ ],
234
+ [
235
+ "update of an unknown id writes nothing",
236
+ async ({ store }) => {
237
+ await store.update(ACME_DASHBOARD);
238
+ assert.equal(
239
+ await store.findById("pcl_contract_acme_dashboard"),
240
+ undefined,
241
+ "update must replace, never create",
242
+ );
243
+ },
244
+ ],
245
+ [
246
+ "deleteById frees the id, the client_id and the slug; deleting an unknown id resolves",
247
+ async ({ store }) => {
248
+ await store.save(ACME_DASHBOARD);
249
+ await store.deleteById("pcl_contract_acme_dashboard");
250
+ assert.equal(
251
+ await store.findById("pcl_contract_acme_dashboard"),
252
+ undefined,
253
+ );
254
+ assert.equal(
255
+ await store.findByClientId("stgm_cid_contract_acme_dashboard"),
256
+ undefined,
257
+ "a deleted client's client_id must stop resolving: the mint and the verifier read it",
258
+ );
259
+ assert.equal(
260
+ await store.findByOrgAndSlug("acme", "dashboard"),
261
+ undefined,
262
+ );
263
+ await store.save(ACME_DASHBOARD);
264
+ await store.deleteById("pcl_contract_nobody");
265
+ },
266
+ ],
267
+ [
268
+ "findByOrg answers every client of the organization and no other",
269
+ async ({ store }) => {
270
+ await store.save(ACME_DASHBOARD);
271
+ await store.save(
272
+ platformClient({
273
+ id: "pcl_contract_acme_mobile",
274
+ org: "acme",
275
+ slug: "mobile",
276
+ clientId: "stgm_cid_contract_acme_mobile",
277
+ }),
278
+ );
279
+ await store.save(
280
+ platformClient({
281
+ id: "pcl_contract_globex_dashboard",
282
+ org: "globex",
283
+ slug: "dashboard",
284
+ clientId: "stgm_cid_contract_globex_dashboard",
285
+ }),
286
+ );
287
+ const ids = (await store.findByOrg("acme"))
288
+ .map((client) => client.metadata?.id ?? "")
289
+ .sort();
290
+ assert.deepEqual(ids, [
291
+ "pcl_contract_acme_dashboard",
292
+ "pcl_contract_acme_mobile",
293
+ ]);
294
+ assert.deepEqual(await store.findByOrg("initech"), []);
295
+ },
296
+ ],
297
+ [
298
+ "a disconnected store is an infrastructure fault, never 'not found'",
299
+ async (fixture) => {
300
+ await fixture.disconnect();
301
+ await assert.rejects(
302
+ fixture.store.findById("pcl_contract_acme_dashboard"),
303
+ "an id read on a disconnected store must reject: on the verifier's path 'not found' revokes every token",
304
+ );
305
+ await assert.rejects(
306
+ fixture.store.findByClientId("stgm_cid_contract_acme_dashboard"),
307
+ "a client_id read on a disconnected store must reject, never read as undefined",
308
+ );
309
+ },
310
+ ],
311
+ ];
312
+
313
+ /** The port's cases over `makeFixture`, one fresh fixture per case. */
314
+ export function platformClientStoreContract(
315
+ makeFixture: () => Promise<PlatformClientStoreContractFixture>,
316
+ ): ReadonlyArray<PlatformClientStoreContractCase> {
317
+ return portContractCases(CASES, makeFixture);
318
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The PlatformClientStore PORT: the storage contract the platform-client
3
+ * domain writes and reads through, so the cloud serves the same chains
4
+ * over its own `cloud.iam_platform_client` table by registering an
5
+ * implementation as `drivers.platformClientStore` (the identity-account
6
+ * and IamPolicy precedent: a port for an IAM kind whose rows an edition
7
+ * keeps in its own table). Open source's implementation is
8
+ * resource-store.ts over the generic Store.
9
+ *
10
+ * The methods are exactly the lookups the domain asks: by id (every
11
+ * chain, and the verifier's liveness read), by client_id (the mint), by
12
+ * org-scoped slug (getByReference, update addressed by slug, the
13
+ * duplicate check), by organization (listByOrg). A port carries nothing
14
+ * only one edition calls.
15
+ *
16
+ * Contract every implementation must satisfy — proven by the port-contract
17
+ * kit (store-contract.ts, exported), which the OSS adapter's test iterates
18
+ * on both drivers and a composition's driver test iterates too:
19
+ * - `save` is create-only in effect: a held id, a held (org, slug) or a
20
+ * held client_id raises DuplicatePlatformClientError, never a silent
21
+ * overwrite;
22
+ * - `update` replaces by id and never creates: an unknown id writes
23
+ * nothing;
24
+ * - `deleteById` of an unknown id resolves;
25
+ * - `findByOrg` answers every client of the organization and no other,
26
+ * in no promised order (the chain sorts);
27
+ * - a typed not-found reads as `undefined`; any other storage failure
28
+ * propagates as the infrastructure fault it is (the ratified
29
+ * store-fault mapping — an outage must never read as "no client", which
30
+ * on the verifier's path would revoke every live token).
31
+ */
32
+ import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
33
+
34
+ /**
35
+ * Raised by `save` when the id, the (org, slug) pair or the client_id is
36
+ * already held, so the create chain answers ALREADY_EXISTS. `cause`
37
+ * carries a driver's own error (the cloud's SQLSTATE 23505) for the logs;
38
+ * the message is the port's.
39
+ */
40
+ export class DuplicatePlatformClientError extends Error {
41
+ constructor(message: string, options?: { cause?: unknown }) {
42
+ super(message, options);
43
+ this.name = "DuplicatePlatformClientError";
44
+ }
45
+ }
46
+
47
+ export interface PlatformClientStore {
48
+ /** Insert; a held id, (org, slug) or client_id raises DuplicatePlatformClientError. */
49
+ save(client: PlatformClient): Promise<void>;
50
+ /** Full-row replace by id; an unknown id writes nothing. */
51
+ update(client: PlatformClient): Promise<void>;
52
+ /** Removes the row; no error when it does not exist. */
53
+ deleteById(id: string): Promise<void>;
54
+ findById(id: string): Promise<PlatformClient | undefined>;
55
+ /** The client holding this public identifier — the mint's lookup. */
56
+ findByClientId(clientId: string): Promise<PlatformClient | undefined>;
57
+ findByOrgAndSlug(
58
+ org: string,
59
+ slug: string,
60
+ ): Promise<PlatformClient | undefined>;
61
+ /** Every client of the organization, in no promised order. */
62
+ findByOrg(org: string): Promise<ReadonlyArray<PlatformClient>>;
63
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The one builder of a system-managed PlatformClient: the row an edition
3
+ * creates for itself, never through the create chain, so the tokens it
4
+ * signs on the platform's own behalf (a guest's, a schedule fire's) name a
5
+ * client that exists. Stigmer Cloud keeps one per organization under
6
+ * SYSTEM_SHARE_CLIENT_SLUG and persists it through its store driver.
7
+ *
8
+ * The domain already owns what such a client is — the create chain refuses
9
+ * its reserved slug (RefuseReservedSlug) and every mutation refuses its
10
+ * label (RefuseSystemManaged) — so the row is built here too, and the
11
+ * rules that make it safe hold by construction:
12
+ * - it lives only under a reserved slug, which no user can create, so a
13
+ * user client can never be mistaken for one;
14
+ * - it carries the reserved `stigmer.ai/system-managed` label, so update,
15
+ * delete and rotate refuse it;
16
+ * - its credentials are real (a `client_id` unique like any other, a
17
+ * hash, a fingerprint), but the secret is discarded here: nobody ever
18
+ * authenticates as the platform's own client;
19
+ * - its id is spelled like every other (`pcl_` + ULID, generateId);
20
+ * - its audit names no actor, because no caller made it: the platform did.
21
+ *
22
+ * Who decides when one exists, and where it is stored, is the edition's:
23
+ * this function writes nothing. It writes no authorization tuples either —
24
+ * nothing authorizes against a system-managed client, and with none it
25
+ * stays out of every list and reference read a person makes.
26
+ */
27
+ import { create } from "@bufbuild/protobuf";
28
+ import { timestampFromDate } from "@bufbuild/protobuf/wkt";
29
+
30
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
31
+ import {
32
+ ApiResourceAuditInfoSchema,
33
+ ApiResourceAuditSchema,
34
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
35
+ import { PlatformClientSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
36
+ import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
37
+
38
+ import {
39
+ RESERVED_LABEL_TRUE,
40
+ SYSTEM_MANAGED_LABEL,
41
+ } from "../../pipeline/apiresource-labels.js";
42
+ import {
43
+ defaultVisibilityFor,
44
+ getIdPrefix,
45
+ } from "../../pipeline/apiresource-meta.js";
46
+ import { generateId } from "../../pipeline/steps/defaults.js";
47
+ import {
48
+ PLATFORM_CLIENT_KIND_NAME,
49
+ RESERVED_PLATFORM_CLIENT_SLUGS,
50
+ } from "./constants.js";
51
+ import {
52
+ generateClientId,
53
+ generateClientSecret,
54
+ hashClientSecret,
55
+ secretFingerprint,
56
+ } from "./credentials.js";
57
+
58
+ export interface SystemManagedPlatformClientInput {
59
+ /** The owning organization. */
60
+ readonly org: string;
61
+ /** A reserved slug (RESERVED_PLATFORM_CLIENT_SLUGS); anything else throws. */
62
+ readonly slug: string;
63
+ /** The display name a console would show. */
64
+ readonly name: string;
65
+ /** The creation instant stamped on both audit slots; defaults to the wall clock. */
66
+ readonly now?: Date;
67
+ }
68
+
69
+ /**
70
+ * A new system-managed client, ready for a store's `save`. Throws on an
71
+ * empty organization or a slug that is not reserved — an edition's
72
+ * programming error, never a request's.
73
+ */
74
+ export function newSystemManagedPlatformClient(
75
+ input: SystemManagedPlatformClientInput,
76
+ ): PlatformClient {
77
+ if (input.org === "") {
78
+ throw new Error("a system-managed platform client needs an organization");
79
+ }
80
+ if (!RESERVED_PLATFORM_CLIENT_SLUGS.has(input.slug)) {
81
+ throw new Error(
82
+ `'${input.slug}' is not a reserved platform-client slug; a system-managed client lives only under one (${[...RESERVED_PLATFORM_CLIENT_SLUGS].join(", ")})`,
83
+ );
84
+ }
85
+ const secret = generateClientSecret();
86
+ const created = timestampFromDate(input.now ?? new Date());
87
+ const audit = create(ApiResourceAuditInfoSchema, {
88
+ createdAt: created,
89
+ updatedAt: created,
90
+ event: "created",
91
+ });
92
+ return create(PlatformClientSchema, {
93
+ apiVersion: "iam.stigmer.ai/v1",
94
+ kind: PLATFORM_CLIENT_KIND_NAME,
95
+ metadata: {
96
+ id: generateId(getIdPrefix(ApiResourceKind.platform_client)),
97
+ org: input.org,
98
+ name: input.name,
99
+ slug: input.slug,
100
+ labels: { [SYSTEM_MANAGED_LABEL]: RESERVED_LABEL_TRUE },
101
+ visibility: defaultVisibilityFor(ApiResourceKind.platform_client),
102
+ },
103
+ spec: {
104
+ clientId: generateClientId(),
105
+ clientSecretHash: hashClientSecret(secret),
106
+ secretFingerprint: secretFingerprint(secret),
107
+ },
108
+ status: {
109
+ audit: create(ApiResourceAuditSchema, {
110
+ specAudit: audit,
111
+ statusAudit: audit,
112
+ }),
113
+ },
114
+ });
115
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * PlatformClientTokenController — the two public token RPCs, registered in
3
+ * every composition so the service has one owner (the routes stage refuses
4
+ * an extension that registers it again, boot/route-shadowing.ts):
5
+ *
6
+ * - mintUserToken: the PlatformClient mint (mint.ts). Public on the wire
7
+ * — the client authenticates with its credentials in the body, not a
8
+ * bearer token — so there is no annotation to evaluate; the mint never
9
+ * reads the chain's caller.
10
+ * - mintGuestToken: the credential-free mint for anonymous visitors of a
11
+ * shared agent's hosted page, dispatched to the composed guest-token
12
+ * capability (extensions/guest-token-minting.ts). Without one — open
13
+ * source hosts no shared-agent pages — it answers UNIMPLEMENTED with
14
+ * the edition sentence, the identityFederation absent shape.
15
+ */
16
+ import { Code, ConnectError } from "@connectrpc/connect";
17
+ import type { ConnectRouter } from "@connectrpc/connect";
18
+
19
+ import { PlatformClientTokenController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
20
+
21
+ import type { GuestTokenMinting } from "../../extensions/guest-token-minting.js";
22
+ import { GUEST_MINT_UNIMPLEMENTED_MESSAGE } from "./constants.js";
23
+ import { mintUserToken } from "./mint.js";
24
+ import type { PlatformClientMintDeps } from "./mint.js";
25
+
26
+ export interface PlatformClientTokenControllerDeps {
27
+ readonly mint: PlatformClientMintDeps;
28
+ /** The composed capability; undefined = mintGuestToken refuses UNIMPLEMENTED. */
29
+ readonly guestTokenMinting: GuestTokenMinting | undefined;
30
+ }
31
+
32
+ /** Registers the token service on the router (routes stage). */
33
+ export function registerPlatformClientTokenService(
34
+ router: ConnectRouter,
35
+ deps: PlatformClientTokenControllerDeps,
36
+ ): void {
37
+ router.service(PlatformClientTokenController, {
38
+ mintUserToken: (request) => mintUserToken(deps.mint, request),
39
+ mintGuestToken: (request, ctx) => {
40
+ if (deps.guestTokenMinting === undefined) {
41
+ throw new ConnectError(
42
+ GUEST_MINT_UNIMPLEMENTED_MESSAGE,
43
+ Code.Unimplemented,
44
+ );
45
+ }
46
+ return deps.guestTokenMinting.mintGuestToken(request, ctx.requestHeader);
47
+ },
48
+ });
49
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The PlatformClient user-token verifier: open source's one platform-token
3
+ * lane. It claims a token with `iss: "stigmer"` and NO `token_type` — the
4
+ * user token the mint signs — and passes every other token on:
5
+ * - a token that is not a platform token is another verifier's (the
6
+ * OIDC verifier's, an API key's);
7
+ * - a typed platform token (guest, schedule, channel, the sandbox
8
+ * family) belongs to the edition that mints it; open source verifies
9
+ * only its own vocabulary, and on a server with no verifier for that
10
+ * lane the chain refuses it as unclaimed.
11
+ * A platform token whose signature, expiry, audience or subject fails is
12
+ * refused here with the envelope's pinned copy, whichever lane it names —
13
+ * the refusal the cloud's verifier has always answered for it.
14
+ *
15
+ * Liveness is the verifier's (not a guard's), so every lane that
16
+ * authenticates — the serving chain and the extension HTTP edges that run
17
+ * the verifier chain without guards — refuses a deleted client's tokens on
18
+ * the next request, with the cloud's copy. A store fault propagates as the
19
+ * infrastructure fault it is, never as a revocation.
20
+ *
21
+ * Composed between `apikey` and `oidc`, only under an authentication
22
+ * posture (compose.ts): the OIDC verifier claims every JWT-shaped token and
23
+ * throws on one it cannot verify, so a platform token must be claimed
24
+ * before it gets there (stigmer#1137's lesson).
25
+ *
26
+ * The identity is the account the token was minted for: `sub` IS the
27
+ * account id (the mint signs it), so no account read is needed; class
28
+ * `user`, issuer "stigmer", the token carried (the memory-capture gate and
29
+ * the origin guard read its `platform_client_id`), and the email and name
30
+ * the platform asserted, for display only.
31
+ */
32
+ import { Code, ConnectError } from "@connectrpc/connect";
33
+
34
+ import type {
35
+ CallerIdentity,
36
+ IdentityVerifier,
37
+ } from "../../extensions/identity.js";
38
+ import {
39
+ PLATFORM_TOKEN_ISSUER,
40
+ platformTokenRefusalError,
41
+ stringClaim,
42
+ verifyPlatformToken,
43
+ } from "../../platformtoken/envelope.js";
44
+ import type { PlatformTokenKeyRing } from "../../platformtoken/key-ring.js";
45
+ import {
46
+ DELETED_CLIENT_MESSAGE,
47
+ TOKEN_NAMES_NO_CLIENT_MESSAGE,
48
+ USER_TOKEN_CLAIMS,
49
+ } from "./constants.js";
50
+ import type { PlatformClientStore } from "./store.js";
51
+
52
+ export interface PlatformClientTokenVerifierDeps {
53
+ readonly keys: PlatformTokenKeyRing;
54
+ readonly clients: Pick<PlatformClientStore, "findById">;
55
+ readonly now?: () => Date;
56
+ }
57
+
58
+ export function newPlatformClientTokenVerifier(
59
+ deps: PlatformClientTokenVerifierDeps,
60
+ ): IdentityVerifier {
61
+ return {
62
+ name: "platform-client",
63
+ async verify(token: string): Promise<CallerIdentity | null> {
64
+ const result = verifyPlatformToken(deps.keys, token, deps.now?.());
65
+ switch (result.outcome) {
66
+ case "foreign":
67
+ return null;
68
+ case "refused":
69
+ throw platformTokenRefusalError(result.refusal);
70
+ case "verified":
71
+ break;
72
+ default: {
73
+ const exhaustive: never = result;
74
+ throw new Error(`unknown verification ${JSON.stringify(exhaustive)}`);
75
+ }
76
+ }
77
+ const verified = result.token;
78
+ if (verified.tokenType !== undefined) {
79
+ return null;
80
+ }
81
+ const platformClientId = stringClaim(
82
+ verified.payload,
83
+ USER_TOKEN_CLAIMS.platformClientId,
84
+ );
85
+ if (platformClientId === undefined) {
86
+ throw new ConnectError(
87
+ TOKEN_NAMES_NO_CLIENT_MESSAGE,
88
+ Code.Unauthenticated,
89
+ );
90
+ }
91
+ if ((await deps.clients.findById(platformClientId)) === undefined) {
92
+ throw new ConnectError(DELETED_CLIENT_MESSAGE, Code.Unauthenticated);
93
+ }
94
+ const email = stringClaim(verified.payload, USER_TOKEN_CLAIMS.email);
95
+ const displayName = stringClaim(verified.payload, USER_TOKEN_CLAIMS.name);
96
+ return {
97
+ identityId: verified.subject,
98
+ callerClass: "user",
99
+ issuer: PLATFORM_TOKEN_ISSUER,
100
+ rawToken: token,
101
+ ...(email !== undefined ? { email } : {}),
102
+ ...(displayName !== undefined ? { displayName } : {}),
103
+ };
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The session list index (store/list-index.ts): `agent_instance` is the
3
+ * key `listByAgentInstance` reads, `channel` the one `listByChannel` reads
4
+ * (the channel's id is stamped as a label by the channel lanes, not held
5
+ * in the spec). A session belongs to one organization, which is every
6
+ * declaration's first fact, so `session.list` narrows by it when the
7
+ * request names one.
8
+ *
9
+ * A change to `keys` bumps `revision` (boot/__tests__/list-indexes.test.ts
10
+ * pins the pair).
11
+ */
12
+ import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
13
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
14
+
15
+ import { declareListIndex, field, label } from "../../store/list-index.js";
16
+
17
+ /** The label the channel lanes stamp on every session they open. */
18
+ export const SESSION_CHANNEL_ID_LABEL = "stigmer.ai/channel-id";
19
+
20
+ export const sessionListIndex = declareListIndex({
21
+ kind: ApiResourceKind.session,
22
+ schema: SessionSchema,
23
+ revision: 1,
24
+ keys: {
25
+ agent_instance: field("spec.agent_instance_id"),
26
+ channel: label(SESSION_CHANNEL_ID_LABEL),
27
+ },
28
+ });