@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,449 @@
1
+ /**
2
+ * PlatformClient controller — the command and query sides, served by open
3
+ * source in every edition (the token side is token-controller.ts). A
4
+ * PlatformClient is an organization's inbound credential pair: its backend
5
+ * presents client_id + client_secret to mint short-lived user tokens for
6
+ * its own product's users (mint.ts). The inbound counterpart to OAuthApp,
7
+ * whose controller this one follows chain for chain.
8
+ *
9
+ * Every chain opens with Authorize and runs over the PlatformClientStore
10
+ * port (store.ts), so the cloud serves these same chains over its own
11
+ * table. What the domain adds to the OAuthApp shape:
12
+ * - the credential steps: generated on create and shown once in
13
+ * PlatformClientCreateResponse, kept from the row on update, replaced
14
+ * by rotateSecret (the client_id is permanent);
15
+ * - `system-share-client` refused on create, and the platform's
16
+ * system-managed clients refused on update, delete and rotate;
17
+ * - the reference rule on `environment_refs` (NormalizeReferences, then
18
+ * ValidateReferences) on create and update, which every chain that
19
+ * stores a spec reference runs; it reads the environments from the
20
+ * resource Store, not from the port, because a driver may keep client
21
+ * rows in a table of its own while environments stay in the Store;
22
+ * - the stored hash cleared on EVERY response, deletes and rotations
23
+ * included — the model promises it is never returned
24
+ * (fga/model/iam/platform_client.fga), and no reader needs it;
25
+ * - getByReference loads, then authorizes the loaded client exactly as
26
+ * `get` would (AuthorizeResolvedTarget), and listByOrg narrows through
27
+ * the list read scope, so a member who may not `get` a client does not
28
+ * see it listed either: the model gives organization members no access
29
+ * to a credential.
30
+ *
31
+ * There is no apply and no updateVisibility: clients are created through
32
+ * the console or the API and are always organization-private.
33
+ *
34
+ * Proven by platformclient.conformance.test.ts (local targets and the
35
+ * cloud) and __tests__/controller.test.ts.
36
+ */
37
+ import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
38
+ import { create } from "@bufbuild/protobuf";
39
+
40
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
41
+ import type {
42
+ ApiResourceDeleteInput,
43
+ ApiResourceId,
44
+ ApiResourceReference,
45
+ } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
46
+ import { PlatformClientSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
47
+ import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
48
+ import { PlatformClientCommandController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/command_pb";
49
+ import {
50
+ PlatformClientCreateResponseSchema,
51
+ PlatformClientsSchema,
52
+ } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/io_pb";
53
+ import type {
54
+ ListPlatformClientsByOrgInput,
55
+ PlatformClientCreateResponse,
56
+ PlatformClientId,
57
+ PlatformClients,
58
+ } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/io_pb";
59
+ import { PlatformClientQueryController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/query_pb";
60
+
61
+ import type { Logger } from "../../boot/logger.js";
62
+ import type { Authorizer } from "../../extensions/authorizer.js";
63
+ import type { ListReadScope } from "../../extensions/list-read-scope.js";
64
+ import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
65
+ import type { ResourceAuthorizationLifecycle } from "../../extensions/resource-authorization.js";
66
+ import { internalError } from "../../pipeline/errors.js";
67
+ import { apiResourceKindKey } from "../../pipeline/interceptors/apiresource.js";
68
+ import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
69
+ import { newPipeline } from "../../pipeline/pipeline.js";
70
+ import { RequestContext } from "../../pipeline/request-context.js";
71
+ import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
72
+ import {
73
+ loadedTargetAsMethod,
74
+ newAuthorizeResolvedTargetStep,
75
+ } from "../../pipeline/steps/authorize-resolved-target.js";
76
+ import {
77
+ newCleanupIamPoliciesStep,
78
+ newCreateAuthorizationTuplesStep,
79
+ } from "../../pipeline/steps/authorization-tuples.js";
80
+ import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
81
+ import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
82
+ import { RESOURCE_ID_KEY } from "../../pipeline/steps/delete.js";
83
+ import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
84
+ import { compareCreatedAtDesc } from "../../pipeline/steps/helpers.js";
85
+ import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
86
+ import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
87
+ import {
88
+ newNormalizeReferencesStep,
89
+ newValidateReferencesStep,
90
+ } from "../../pipeline/steps/references.js";
91
+ import { newResolveSlugStep } from "../../pipeline/steps/slug.js";
92
+ import { newValidateVisibilityStep } from "../../pipeline/steps/validate-visibility.js";
93
+ import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
94
+ import type { Store } from "../../store/interface.js";
95
+ import {
96
+ newCheckDuplicateStep,
97
+ newDeleteClientStep,
98
+ newGenerateClientCredentialsStep,
99
+ newLoadClientByReferenceStep,
100
+ newLoadExistingClientForDeleteStep,
101
+ newLoadExistingClientStep,
102
+ newLoadTargetClientStep,
103
+ newPersistNewClientStep,
104
+ newPersistTargetClientStep,
105
+ newPersistUpdatedClientStep,
106
+ newPreserveClientCredentialsStep,
107
+ newRefuseReservedSlugStep,
108
+ newRefuseSystemManagedStep,
109
+ newRotateClientCredentialsStep,
110
+ parkedClientSecret,
111
+ redactPlatformClient,
112
+ storedClientOf,
113
+ } from "./steps.js";
114
+ import type { PlatformClientStore } from "./store.js";
115
+
116
+ export interface PlatformClientControllerDeps {
117
+ /** The composed store — a driver's, or the OSS adapter over the generic Store. */
118
+ readonly clients: PlatformClientStore;
119
+ /** The resource store the reference rule reads the environments `environment_refs` names from. */
120
+ readonly store: Store;
121
+ readonly logger: Logger;
122
+ /** The composed authorization seam — the Authorize step at position 1 of every chain calls it. */
123
+ readonly authorizer: Authorizer;
124
+ /** The composed tuple-lifecycle driver — undefined = the shared steps no-op. */
125
+ readonly authorizationLifecycle: ResourceAuthorizationLifecycle | undefined;
126
+ /** The composed list read scope — listByOrg narrows through it; undefined = every client of the org. */
127
+ readonly listReadScope: ListReadScope | undefined;
128
+ }
129
+
130
+ /** Registers the command and query services on the router (routes stage). */
131
+ export function registerPlatformClientServices(
132
+ router: ConnectRouter,
133
+ deps: PlatformClientControllerDeps,
134
+ ): void {
135
+ router.service(PlatformClientCommandController, {
136
+ create: (client, ctx) => createClient(deps, client, ctx),
137
+ update: (client, ctx) => updateClient(deps, client, ctx),
138
+ delete: (input, ctx) => deleteClient(deps, input, ctx),
139
+ rotateSecret: (id, ctx) => rotateSecret(deps, id, ctx),
140
+ });
141
+ router.service(PlatformClientQueryController, {
142
+ get: (id, ctx) => get(deps, id, ctx),
143
+ getByReference: (ref, ctx) => getByReference(deps, ref, ctx),
144
+ listByOrg: (input, ctx) => listByOrg(deps, input, ctx),
145
+ });
146
+ }
147
+
148
+ function kindOf(ctx: HandlerContext): ApiResourceKind {
149
+ return ctx.values.get(apiResourceKindKey);
150
+ }
151
+
152
+ /**
153
+ * Create — OAuthApp's chain with the reserved-slug refusal after the slug
154
+ * resolves, the reference rule where every sibling chain runs it, and the
155
+ * credentials generated only once the row is known to be storable; the
156
+ * response carries the redacted client and the one look at its secret.
157
+ */
158
+ async function createClient(
159
+ deps: PlatformClientControllerDeps,
160
+ client: PlatformClient,
161
+ ctx: HandlerContext,
162
+ ): Promise<PlatformClientCreateResponse> {
163
+ const reqCtx = new RequestContext(
164
+ PlatformClientSchema,
165
+ client,
166
+ callerIdentityOf(ctx),
167
+ kindOf(ctx),
168
+ );
169
+ await newPipeline<typeof PlatformClientSchema>(
170
+ "platformclient-create",
171
+ deps.logger,
172
+ )
173
+ .addStep(
174
+ newAuthorizeStep(
175
+ PlatformClientCommandController.method.create,
176
+ deps.authorizer,
177
+ ),
178
+ )
179
+ .addStep(newResolveSlugStep())
180
+ .addStep(newValidateProtoStep())
181
+ .addStep(newValidateVisibilityStep())
182
+ .addStep(newRefuseReservedSlugStep())
183
+ .addStep(newCheckDuplicateStep(deps.clients))
184
+ .addStep(newBuildNewStateStep())
185
+ .addStep(newGuardReservedLabelsStep(deps.authorizer))
186
+ .addStep(newNormalizeReferencesStep())
187
+ .addStep(newValidateReferencesStep(deps.store))
188
+ .addStep(newGenerateClientCredentialsStep())
189
+ .addStep(newPersistNewClientStep(deps.clients))
190
+ .addStep(
191
+ newCreateAuthorizationTuplesStep(
192
+ deps.authorizationLifecycle,
193
+ deps.logger,
194
+ ),
195
+ )
196
+ .build()
197
+ .execute(reqCtx);
198
+ return create(PlatformClientCreateResponseSchema, {
199
+ platformClient: redactPlatformClient(reqCtx.newState),
200
+ clientSecret: parkedClientSecret(reqCtx),
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Update — full spec replacement from the request (BuildUpdateState),
206
+ * addressed by id or org-scoped slug; the platform's own clients refused
207
+ * before any state is built; the reference rule as on create; the
208
+ * credential fields kept from the row.
209
+ */
210
+ async function updateClient(
211
+ deps: PlatformClientControllerDeps,
212
+ client: PlatformClient,
213
+ ctx: HandlerContext,
214
+ ): Promise<PlatformClient> {
215
+ const reqCtx = new RequestContext(
216
+ PlatformClientSchema,
217
+ client,
218
+ callerIdentityOf(ctx),
219
+ kindOf(ctx),
220
+ );
221
+ await newPipeline<typeof PlatformClientSchema>(
222
+ "platformclient-update",
223
+ deps.logger,
224
+ )
225
+ .addStep(
226
+ newAuthorizeStep(
227
+ PlatformClientCommandController.method.update,
228
+ deps.authorizer,
229
+ ),
230
+ )
231
+ .addStep(newValidateProtoStep())
232
+ .addStep(newResolveSlugStep())
233
+ .addStep(newLoadExistingClientStep(deps.clients))
234
+ .addStep(
235
+ newRefuseSystemManagedStep<typeof PlatformClientSchema>(
236
+ "updated",
237
+ EXISTING_RESOURCE_KEY,
238
+ ),
239
+ )
240
+ .addStep(newBuildUpdateStateStep())
241
+ .addStep(newGuardReservedLabelsStep(deps.authorizer))
242
+ .addStep(newNormalizeReferencesStep())
243
+ .addStep(newValidateReferencesStep(deps.store))
244
+ .addStep(newPreserveClientCredentialsStep())
245
+ .addStep(newPersistUpdatedClientStep(deps.clients))
246
+ .build()
247
+ .execute(reqCtx);
248
+ return redactPlatformClient(reqCtx.newState);
249
+ }
250
+
251
+ /**
252
+ * Delete — the platform's own clients refused before the delete; the
253
+ * client's policies cleaned up after. The client's outstanding tokens stop
254
+ * at their next request (the verifier's liveness read); the end-user
255
+ * accounts it provisioned stay, because an account belongs to the
256
+ * organization, not to one of its clients.
257
+ */
258
+ async function deleteClient(
259
+ deps: PlatformClientControllerDeps,
260
+ input: ApiResourceDeleteInput,
261
+ ctx: HandlerContext,
262
+ ): Promise<PlatformClient> {
263
+ type DeleteInput = typeof PlatformClientCommandController.method.delete.input;
264
+ const reqCtx = new RequestContext(
265
+ PlatformClientCommandController.method.delete.input,
266
+ input,
267
+ callerIdentityOf(ctx),
268
+ kindOf(ctx),
269
+ );
270
+ // ApiResourceDeleteInput carries resource_id, not the `value` field
271
+ // ExtractResourceId reads (OAuthApp's pattern).
272
+ reqCtx.set(RESOURCE_ID_KEY, input.resourceId);
273
+ await newPipeline<DeleteInput>("platformclient-delete", deps.logger)
274
+ .addStep(
275
+ newAuthorizeStep(
276
+ PlatformClientCommandController.method.delete,
277
+ deps.authorizer,
278
+ ),
279
+ )
280
+ .addStep(newValidateProtoStep())
281
+ .addStep(newLoadExistingClientForDeleteStep<DeleteInput>(deps.clients))
282
+ .addStep(
283
+ newRefuseSystemManagedStep<DeleteInput>("deleted", EXISTING_RESOURCE_KEY),
284
+ )
285
+ .addStep(newDeleteClientStep<DeleteInput>(deps.clients))
286
+ .addStep(
287
+ newCleanupIamPoliciesStep(deps.authorizationLifecycle, deps.logger),
288
+ )
289
+ .build()
290
+ .execute(reqCtx);
291
+ return redactPlatformClient(storedClientOf(reqCtx, EXISTING_RESOURCE_KEY));
292
+ }
293
+
294
+ /**
295
+ * RotateSecret — a new secret for the loaded client (the client_id stays),
296
+ * shown once in the response. Tokens already minted stay valid until they
297
+ * expire; deleting the client is what revokes them.
298
+ */
299
+ async function rotateSecret(
300
+ deps: PlatformClientControllerDeps,
301
+ id: PlatformClientId,
302
+ ctx: HandlerContext,
303
+ ): Promise<PlatformClientCreateResponse> {
304
+ type RotateInput =
305
+ typeof PlatformClientCommandController.method.rotateSecret.input;
306
+ const reqCtx = new RequestContext(
307
+ PlatformClientCommandController.method.rotateSecret.input,
308
+ id,
309
+ callerIdentityOf(ctx),
310
+ kindOf(ctx),
311
+ );
312
+ await newPipeline<RotateInput>("platformclient-rotate-secret", deps.logger)
313
+ .addStep(
314
+ newAuthorizeStep(
315
+ PlatformClientCommandController.method.rotateSecret,
316
+ deps.authorizer,
317
+ ),
318
+ )
319
+ .addStep(newValidateProtoStep())
320
+ .addStep(newLoadTargetClientStep<RotateInput>(deps.clients))
321
+ .addStep(
322
+ newRefuseSystemManagedStep<RotateInput>("rotated", TARGET_RESOURCE_KEY),
323
+ )
324
+ .addStep(newRotateClientCredentialsStep<RotateInput>())
325
+ .addStep(newPersistTargetClientStep<RotateInput>(deps.clients))
326
+ .build()
327
+ .execute(reqCtx);
328
+ return create(PlatformClientCreateResponseSchema, {
329
+ platformClient: redactPlatformClient(
330
+ storedClientOf(reqCtx, TARGET_RESOURCE_KEY),
331
+ ),
332
+ clientSecret: parkedClientSecret(reqCtx),
333
+ });
334
+ }
335
+
336
+ async function get(
337
+ deps: PlatformClientControllerDeps,
338
+ id: ApiResourceId,
339
+ ctx: HandlerContext,
340
+ ): Promise<PlatformClient> {
341
+ type GetInput = typeof PlatformClientQueryController.method.get.input;
342
+ const reqCtx = new RequestContext(
343
+ PlatformClientQueryController.method.get.input,
344
+ id,
345
+ callerIdentityOf(ctx),
346
+ kindOf(ctx),
347
+ );
348
+ await newPipeline<GetInput>("platformclient-get", deps.logger)
349
+ .addStep(
350
+ newAuthorizeStep(
351
+ PlatformClientQueryController.method.get,
352
+ deps.authorizer,
353
+ ),
354
+ )
355
+ .addStep(newValidateProtoStep())
356
+ .addStep(newLoadTargetClientStep<GetInput>(deps.clients))
357
+ .build()
358
+ .execute(reqCtx);
359
+ return redactPlatformClient(storedClientOf(reqCtx, TARGET_RESOURCE_KEY));
360
+ }
361
+
362
+ /** GetByReference — load by org/slug, then authorize the loaded client as `get` would. */
363
+ async function getByReference(
364
+ deps: PlatformClientControllerDeps,
365
+ ref: ApiResourceReference,
366
+ ctx: HandlerContext,
367
+ ): Promise<PlatformClient> {
368
+ const reqCtx = new RequestContext(
369
+ PlatformClientQueryController.method.getByReference.input,
370
+ ref,
371
+ callerIdentityOf(ctx),
372
+ kindOf(ctx),
373
+ );
374
+ await newPipeline<
375
+ typeof PlatformClientQueryController.method.getByReference.input
376
+ >("platformclient-get-by-reference", deps.logger)
377
+ .addStep(
378
+ newAuthorizeStep(
379
+ PlatformClientQueryController.method.getByReference,
380
+ deps.authorizer,
381
+ ),
382
+ )
383
+ .addStep(newValidateProtoStep())
384
+ .addStep(newLoadClientByReferenceStep(deps.clients))
385
+ .addStep(
386
+ newAuthorizeResolvedTargetStep(
387
+ deps.authorizer,
388
+ loadedTargetAsMethod(PlatformClientQueryController.method.get),
389
+ ),
390
+ )
391
+ .build()
392
+ .execute(reqCtx);
393
+ return redactPlatformClient(storedClientOf(reqCtx, TARGET_RESOURCE_KEY));
394
+ }
395
+
396
+ /**
397
+ * ListByOrg — the organization's clients the caller may view, newest
398
+ * first, each redacted. The annotation admits anyone who can view the
399
+ * organization; the list read scope then keeps only the clients a `get`
400
+ * would return.
401
+ */
402
+ async function listByOrg(
403
+ deps: PlatformClientControllerDeps,
404
+ input: ListPlatformClientsByOrgInput,
405
+ ctx: HandlerContext,
406
+ ): Promise<PlatformClients> {
407
+ const caller = callerIdentityOf(ctx);
408
+ const reqCtx = new RequestContext(
409
+ PlatformClientQueryController.method.listByOrg.input,
410
+ input,
411
+ caller,
412
+ kindOf(ctx),
413
+ );
414
+ await newPipeline<
415
+ typeof PlatformClientQueryController.method.listByOrg.input
416
+ >("platformclient-list-by-org", deps.logger)
417
+ .addStep(
418
+ newAuthorizeStep(
419
+ PlatformClientQueryController.method.listByOrg,
420
+ deps.authorizer,
421
+ ),
422
+ )
423
+ .addStep(newValidateProtoStep())
424
+ .build()
425
+ .execute(reqCtx);
426
+
427
+ let candidates: ReadonlyArray<PlatformClient>;
428
+ try {
429
+ candidates = await deps.clients.findByOrg(input.org);
430
+ } catch (error) {
431
+ throw internalError(error, "failed to list platform clients");
432
+ }
433
+ const visible = await restrictListByReadScope(
434
+ deps.listReadScope,
435
+ caller,
436
+ ApiResourceKind.platform_client,
437
+ candidates,
438
+ input.org,
439
+ );
440
+ const entries = [...visible]
441
+ .sort((a, b) =>
442
+ compareCreatedAtDesc(
443
+ a.status?.audit?.specAudit?.createdAt,
444
+ b.status?.audit?.specAudit?.createdAt,
445
+ ),
446
+ )
447
+ .map(redactPlatformClient);
448
+ return create(PlatformClientsSchema, { entries });
449
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * PlatformClient credential material — byte-compatible with every client
3
+ * Stigmer Cloud has issued since the Java service, because the rows (and
4
+ * the secrets integrators hold) move under this code unchanged:
5
+ *
6
+ * - client_id: "stgm_cid_" + 32 random bytes, base64url (43 characters).
7
+ * Public by design — safe in logs and client-side code — so its
8
+ * existence is no secret and the mint may answer an unknown id and a
9
+ * wrong secret with the same copy at different speeds.
10
+ * - client_secret: "stgm_cs_" + 48 random bytes, base64url (64
11
+ * characters): 384 bits, returned once and never stored.
12
+ * - the stored hash: SHA-256 over the whole prefixed secret, base64url
13
+ * (43 characters). A slow KDF defends low-entropy passwords, not
14
+ * 384-bit random tokens — ApiKey's precedent (domain/apikey/keymaterial.ts).
15
+ * - the fingerprint: the secret's last six characters, for display.
16
+ *
17
+ * The comparison hashes the presented secret and compares the two hashes in
18
+ * constant time, so neither the secret nor the stored hash leaks through
19
+ * timing.
20
+ */
21
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
22
+
23
+ export const CLIENT_ID_PREFIX = "stgm_cid_";
24
+ export const CLIENT_SECRET_PREFIX = "stgm_cs_";
25
+
26
+ /** Random bytes behind a client_id. */
27
+ const CLIENT_ID_BYTES = 32;
28
+
29
+ /** Random bytes behind a client_secret: 384 bits. */
30
+ const CLIENT_SECRET_BYTES = 48;
31
+
32
+ /** Characters of the secret kept for display. */
33
+ export const SECRET_FINGERPRINT_LENGTH = 6;
34
+
35
+ export function generateClientId(): string {
36
+ return CLIENT_ID_PREFIX + randomBytes(CLIENT_ID_BYTES).toString("base64url");
37
+ }
38
+
39
+ export function generateClientSecret(): string {
40
+ return (
41
+ CLIENT_SECRET_PREFIX +
42
+ randomBytes(CLIENT_SECRET_BYTES).toString("base64url")
43
+ );
44
+ }
45
+
46
+ /** SHA-256 over the whole prefixed secret, base64url — the stored form. */
47
+ export function hashClientSecret(secret: string): string {
48
+ return createHash("sha256").update(secret, "utf8").digest("base64url");
49
+ }
50
+
51
+ /** The secret's last six characters (the whole secret when shorter). */
52
+ export function secretFingerprint(secret: string): string {
53
+ return secret.length <= SECRET_FINGERPRINT_LENGTH
54
+ ? secret
55
+ : secret.slice(-SECRET_FINGERPRINT_LENGTH);
56
+ }
57
+
58
+ /** Whether `secret` hashes to `storedHash`, compared in constant time. */
59
+ export function secretMatchesHash(secret: string, storedHash: string): boolean {
60
+ if (storedHash === "") {
61
+ return false;
62
+ }
63
+ const presented = Buffer.from(hashClientSecret(secret), "utf8");
64
+ const stored = Buffer.from(storedHash, "utf8");
65
+ return (
66
+ presented.length === stored.length && timingSafeEqual(presented, stored)
67
+ );
68
+ }