@stigmer/server 3.14.0 → 3.15.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 (178) hide show
  1. package/dist/boot/compose.d.ts +8 -1
  2. package/dist/boot/compose.d.ts.map +1 -1
  3. package/dist/boot/compose.js +98 -12
  4. package/dist/boot/compose.js.map +1 -1
  5. package/dist/domain/agentexecution/constants.d.ts +16 -0
  6. package/dist/domain/agentexecution/constants.d.ts.map +1 -1
  7. package/dist/domain/agentexecution/constants.js +22 -0
  8. package/dist/domain/agentexecution/constants.js.map +1 -1
  9. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  10. package/dist/domain/agentexecution/controller.js +10 -3
  11. package/dist/domain/agentexecution/controller.js.map +1 -1
  12. package/dist/domain/agentexecution/run-target.d.ts +26 -0
  13. package/dist/domain/agentexecution/run-target.d.ts.map +1 -0
  14. package/dist/domain/agentexecution/run-target.js +31 -0
  15. package/dist/domain/agentexecution/run-target.js.map +1 -0
  16. package/dist/domain/apikey/verifier.d.ts +8 -1
  17. package/dist/domain/apikey/verifier.d.ts.map +1 -1
  18. package/dist/domain/apikey/verifier.js +14 -2
  19. package/dist/domain/apikey/verifier.js.map +1 -1
  20. package/dist/domain/identityaccount/constants.d.ts +66 -0
  21. package/dist/domain/identityaccount/constants.d.ts.map +1 -0
  22. package/dist/domain/identityaccount/constants.js +153 -0
  23. package/dist/domain/identityaccount/constants.js.map +1 -0
  24. package/dist/domain/identityaccount/controller.d.ts +34 -0
  25. package/dist/domain/identityaccount/controller.d.ts.map +1 -0
  26. package/dist/domain/identityaccount/controller.js +342 -0
  27. package/dist/domain/identityaccount/controller.js.map +1 -0
  28. package/dist/domain/identityaccount/operator.d.ts +14 -0
  29. package/dist/domain/identityaccount/operator.d.ts.map +1 -0
  30. package/dist/domain/identityaccount/operator.js +67 -0
  31. package/dist/domain/identityaccount/operator.js.map +1 -0
  32. package/dist/domain/identityaccount/provisioning.d.ts +52 -0
  33. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -0
  34. package/dist/domain/identityaccount/provisioning.js +114 -0
  35. package/dist/domain/identityaccount/provisioning.js.map +1 -0
  36. package/dist/domain/identityaccount/resolve.d.ts +36 -0
  37. package/dist/domain/identityaccount/resolve.d.ts.map +1 -0
  38. package/dist/domain/identityaccount/resolve.js +12 -0
  39. package/dist/domain/identityaccount/resolve.js.map +1 -0
  40. package/dist/domain/identityaccount/resource-store.d.ts +4 -0
  41. package/dist/domain/identityaccount/resource-store.d.ts.map +1 -0
  42. package/dist/domain/identityaccount/resource-store.js +131 -0
  43. package/dist/domain/identityaccount/resource-store.js.map +1 -0
  44. package/dist/domain/identityaccount/steps.d.ts +79 -0
  45. package/dist/domain/identityaccount/steps.d.ts.map +1 -0
  46. package/dist/domain/identityaccount/steps.js +291 -0
  47. package/dist/domain/identityaccount/steps.js.map +1 -0
  48. package/dist/domain/identityaccount/store-contract.d.ts +22 -0
  49. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -0
  50. package/dist/domain/identityaccount/store-contract.js +307 -0
  51. package/dist/domain/identityaccount/store-contract.js.map +1 -0
  52. package/dist/domain/identityaccount/store.d.ts +66 -0
  53. package/dist/domain/identityaccount/store.d.ts.map +1 -0
  54. package/dist/domain/identityaccount/store.js +13 -0
  55. package/dist/domain/identityaccount/store.js.map +1 -0
  56. package/dist/domain/session/constants.d.ts +15 -0
  57. package/dist/domain/session/constants.d.ts.map +1 -0
  58. package/dist/domain/session/constants.js +17 -0
  59. package/dist/domain/session/constants.js.map +1 -0
  60. package/dist/domain/session/controller.d.ts.map +1 -1
  61. package/dist/domain/session/controller.js +10 -0
  62. package/dist/domain/session/controller.js.map +1 -1
  63. package/dist/domain/session/run-target.d.ts +15 -0
  64. package/dist/domain/session/run-target.d.ts.map +1 -0
  65. package/dist/domain/session/run-target.js +14 -0
  66. package/dist/domain/session/run-target.js.map +1 -0
  67. package/dist/domain/workflowexecution/constants.d.ts +9 -0
  68. package/dist/domain/workflowexecution/constants.d.ts.map +1 -1
  69. package/dist/domain/workflowexecution/constants.js +13 -0
  70. package/dist/domain/workflowexecution/constants.js.map +1 -1
  71. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  72. package/dist/domain/workflowexecution/controller.js +8 -0
  73. package/dist/domain/workflowexecution/controller.js.map +1 -1
  74. package/dist/domain/workflowexecution/run-target.d.ts +18 -0
  75. package/dist/domain/workflowexecution/run-target.d.ts.map +1 -0
  76. package/dist/domain/workflowexecution/run-target.js +23 -0
  77. package/dist/domain/workflowexecution/run-target.js.map +1 -0
  78. package/dist/extensions/drivers.d.ts +26 -0
  79. package/dist/extensions/drivers.d.ts.map +1 -1
  80. package/dist/extensions/gate-slots.d.ts +16 -1
  81. package/dist/extensions/gate-slots.d.ts.map +1 -1
  82. package/dist/extensions/gate-slots.js +1 -0
  83. package/dist/extensions/gate-slots.js.map +1 -1
  84. package/dist/extensions/identity-federation.d.ts +36 -0
  85. package/dist/extensions/identity-federation.d.ts.map +1 -0
  86. package/dist/extensions/identity-federation.js +2 -0
  87. package/dist/extensions/identity-federation.js.map +1 -0
  88. package/dist/extensions/registry.d.ts +18 -1
  89. package/dist/extensions/registry.d.ts.map +1 -1
  90. package/dist/extensions/registry.js +20 -0
  91. package/dist/extensions/registry.js.map +1 -1
  92. package/dist/identity/oidc-discovery.d.ts +36 -0
  93. package/dist/identity/oidc-discovery.d.ts.map +1 -0
  94. package/dist/identity/oidc-discovery.js +60 -0
  95. package/dist/identity/oidc-discovery.js.map +1 -0
  96. package/dist/identity/oidc-userinfo.d.ts +30 -0
  97. package/dist/identity/oidc-userinfo.d.ts.map +1 -0
  98. package/dist/identity/oidc-userinfo.js +28 -0
  99. package/dist/identity/oidc-userinfo.js.map +1 -0
  100. package/dist/identity/oidc-verifier.d.ts +6 -0
  101. package/dist/identity/oidc-verifier.d.ts.map +1 -1
  102. package/dist/identity/oidc-verifier.js +35 -34
  103. package/dist/identity/oidc-verifier.js.map +1 -1
  104. package/dist/index.d.ts +8 -0
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +10 -0
  107. package/dist/index.js.map +1 -1
  108. package/dist/pipeline/interceptors/auth.d.ts +10 -0
  109. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  110. package/dist/pipeline/interceptors/auth.js +9 -1
  111. package/dist/pipeline/interceptors/auth.js.map +1 -1
  112. package/dist/pipeline/steps/authorize-run-target.d.ts +105 -0
  113. package/dist/pipeline/steps/authorize-run-target.d.ts.map +1 -0
  114. package/dist/pipeline/steps/authorize-run-target.js +61 -0
  115. package/dist/pipeline/steps/authorize-run-target.js.map +1 -0
  116. package/dist/query/search/criteria.d.ts +10 -3
  117. package/dist/query/search/criteria.d.ts.map +1 -1
  118. package/dist/query/search/criteria.js +11 -4
  119. package/dist/query/search/criteria.js.map +1 -1
  120. package/package.json +4 -4
  121. package/src/boot/__tests__/auth-enabled-composition.test.ts +226 -2
  122. package/src/boot/compose.ts +118 -13
  123. package/src/domain/agentexecution/__tests__/run-gate.test.ts +245 -0
  124. package/src/domain/agentexecution/__tests__/run-target.test.ts +117 -0
  125. package/src/domain/agentexecution/constants.ts +25 -0
  126. package/src/domain/agentexecution/controller.ts +10 -3
  127. package/src/domain/agentexecution/run-target.ts +64 -0
  128. package/src/domain/apikey/__tests__/verifier.test.ts +146 -22
  129. package/src/domain/apikey/verifier.ts +24 -2
  130. package/src/domain/identityaccount/__tests__/constants.test.ts +155 -0
  131. package/src/domain/identityaccount/__tests__/identityaccount.test.ts +484 -0
  132. package/src/domain/identityaccount/__tests__/operator.test.ts +180 -0
  133. package/src/domain/identityaccount/__tests__/provisioning.test.ts +281 -0
  134. package/src/domain/identityaccount/__tests__/resolve.test.ts +114 -0
  135. package/src/domain/identityaccount/__tests__/resource-store.test.ts +216 -0
  136. package/src/domain/identityaccount/__tests__/support.ts +58 -0
  137. package/src/domain/identityaccount/constants.ts +178 -0
  138. package/src/domain/identityaccount/controller.ts +667 -0
  139. package/src/domain/identityaccount/operator.ts +94 -0
  140. package/src/domain/identityaccount/provisioning.ts +182 -0
  141. package/src/domain/identityaccount/resolve.ts +52 -0
  142. package/src/domain/identityaccount/resource-store.ts +157 -0
  143. package/src/domain/identityaccount/steps.ts +377 -0
  144. package/src/domain/identityaccount/store-contract.ts +471 -0
  145. package/src/domain/identityaccount/store.ts +70 -0
  146. package/src/domain/session/__tests__/run-gate.test.ts +219 -0
  147. package/src/domain/session/__tests__/run-target.test.ts +43 -0
  148. package/src/domain/session/constants.ts +17 -0
  149. package/src/domain/session/controller.ts +10 -0
  150. package/src/domain/session/run-target.ts +30 -0
  151. package/src/domain/workflowexecution/__tests__/run-gate.test.ts +200 -0
  152. package/src/domain/workflowexecution/__tests__/run-target.test.ts +80 -0
  153. package/src/domain/workflowexecution/constants.ts +17 -0
  154. package/src/domain/workflowexecution/controller.ts +10 -0
  155. package/src/domain/workflowexecution/run-target.ts +47 -0
  156. package/src/extensions/__tests__/extension-composition.test.ts +49 -0
  157. package/src/extensions/__tests__/identity-account-composed.test.ts +803 -0
  158. package/src/extensions/__tests__/identity-account-points.test.ts +129 -0
  159. package/src/extensions/__tests__/registry.test.ts +4 -0
  160. package/src/extensions/__tests__/tier-truthfulness.test.ts +299 -0
  161. package/src/extensions/drivers.ts +26 -0
  162. package/src/extensions/gate-slots.ts +16 -0
  163. package/src/extensions/identity-federation.ts +58 -0
  164. package/src/extensions/registry.ts +44 -1
  165. package/src/identity/__tests__/oidc-discovery.test.ts +122 -0
  166. package/src/identity/__tests__/oidc-userinfo.test.ts +103 -0
  167. package/src/identity/__tests__/oidc-verifier.test.ts +173 -3
  168. package/src/identity/oidc-discovery.ts +84 -0
  169. package/src/identity/oidc-userinfo.ts +64 -0
  170. package/src/identity/oidc-verifier.ts +42 -45
  171. package/src/index.ts +26 -0
  172. package/src/pipeline/__tests__/support.ts +34 -0
  173. package/src/pipeline/interceptors/auth.ts +13 -1
  174. package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +184 -0
  175. package/src/pipeline/steps/__tests__/authorize-run-target.test.ts +261 -0
  176. package/src/pipeline/steps/authorize-run-target.ts +148 -0
  177. package/src/query/search/__tests__/criteria.test.ts +18 -1
  178. package/src/query/search/criteria.ts +11 -4
@@ -5,7 +5,7 @@
5
5
  * discover).
6
6
  *
7
7
  * The searchable-kind set is DERIVED from the proto kind_meta extension
8
- * (not_search_indexed: false, tier != cloud_only) — Go's
8
+ * (not_search_indexed: false, tier == open_source) — Go's
9
9
  * SearchIndexedKinds() posture since stigmer/stigmer#439, where hand-copied
10
10
  * kind lists drifted from the proto and from each other. Go still carries a
11
11
  * hand map (SearchableKinds) pinned to the derivation by an invariant test;
@@ -13,6 +13,13 @@
13
13
  * registry unit test pins the registered extractors against this same
14
14
  * derivation (the #439 invariant's TS home).
15
15
  *
16
+ * The tier clause admits open_source alone, not "anything but cloud_only":
17
+ * the index is the core's, and a kind tiered enterprise (editions program,
18
+ * DD-001) must not enter it by accident. This derivation is edition-blind
19
+ * by design (the same code runs in every composition); an edition-aware
20
+ * derivation is a design act with a named trigger — the first searchable
21
+ * kind above open_source.
22
+ *
16
23
  * kinds are kept VERBATIM from the request; filtering to the searchable set
17
24
  * happens in effectiveKinds(). Keeping the raw request is what
18
25
  * distinguishes "no kinds requested" (discover mode) from "kinds requested
@@ -37,8 +44,8 @@ let searchIndexedKindsCache;
37
44
  let searchableKindSetCache;
38
45
  /**
39
46
  * Go SearchIndexedKinds(): every kind this edition's search read side
40
- * serves — kind_meta declares it search-indexed and servable outside the
41
- * cloud. Sorted ascending by enum value, exactly Go's sort. Kinds whose
47
+ * serves — kind_meta declares it search-indexed and open_source-tier.
48
+ * Sorted ascending by enum value, exactly Go's sort. Kinds whose
42
49
  * kind_meta cannot be read are skipped (Go's contract; the registry unit
43
50
  * test separately fails on such a defect).
44
51
  */
@@ -57,7 +64,7 @@ export function searchIndexedKinds() {
57
64
  catch {
58
65
  continue;
59
66
  }
60
- if (meta.notSearchIndexed || meta.tier === ResourceTier.cloud_only) {
67
+ if (meta.notSearchIndexed || meta.tier !== ResourceTier.open_source) {
61
68
  continue;
62
69
  }
63
70
  kinds.push(kind);
@@ -1 +1 @@
1
- {"version":3,"file":"criteria.js","sourceRoot":"","sources":["../../../src/query/search/criteria.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"criteria.js","sourceRoot":"","sources":["../../../src/query/search/criteria.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,YAAY,GACb,MAAM,qFAAqF,CAAC;AAE7F,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,0BAA0B;AAC1B,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,sBAAsB;AACtB,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAEpC,IAAI,uBAA+D,CAAC;AACpE,IAAI,sBAAgE,CAAC;AAErE;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,uBAAuB,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAsB,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,qBAAqB,CAAC,MAAM,EAAE,CAAC;YACjD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAyB,CAAC;YAC7C,IAAI,IAAI,KAAK,eAAe,CAAC,yBAAyB,EAAE,CAAC;gBACvD,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,WAAW,EAAE,CAAC;gBACpE,SAAS;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,uBAAuB,GAAG,KAAK,CAAC;IAClC,CAAC;IACD,OAAO,uBAAuB,CAAC;AACjC,CAAC;AAED,6EAA6E;AAC7E,SAAS,iBAAiB;IACxB,IAAI,sBAAsB,KAAK,SAAS,EAAE,CAAC;QACzC,sBAAsB,GAAG,IAAI,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,cAAc;IAEN;IACA;IACA;IACA;IACA;IACA;IACA;IAPnB,YACmB,cAA0C,EAC1C,SAAiB,EACjB,cAAsB,EACtB,iBAA0B,EAC1B,kBAA2B,EAC3B,eAAuB,EACvB,aAAqB;QANrB,mBAAc,GAAd,cAAc,CAA4B;QAC1C,cAAS,GAAT,SAAS,CAAQ;QACjB,mBAAc,GAAd,cAAc,CAAQ;QACtB,sBAAiB,GAAjB,iBAAiB,CAAS;QAC1B,uBAAkB,GAAlB,kBAAkB,CAAS;QAC3B,oBAAe,GAAf,eAAe,CAAQ;QACvB,kBAAa,GAAb,aAAa,CAAQ;IACrC,CAAC;IAEJ;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,MAAM,CACX,KAAiC,EACjC,KAAa,EACb,SAAiB,EACjB,aAAsB,EACtB,cAAuB,EACvB,UAAkB,EAClB,QAAgB;QAEhB,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,eAAe,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CACb,0CAA0C,gBAAgB,aAAa,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,cAAc,CACvB,CAAC,GAAG,KAAK,CAAC,EACV,eAAe,EACf,WAAW,CAAC,SAAS,CAAC,EACtB,aAAa,EACb,cAAc,EACd,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAC/B,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CACrE,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,KAAK;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,kBAAkB,CAAC;IACjC,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,iEAAiE;IACjE,QAAQ;QACN,OAAO,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC;IAC/B,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,cAAc,KAAK,EAAE,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,cAAc;QACZ,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,kBAAkB,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,wDAAwD;IACxD,MAAM;QACJ,OAAO,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;IACzD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/server",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "The Stigmer OSS control plane as a composable library: composeServer, the extension registry and the driver seams a composition builds on. The self-contained deployable of the same server is @stigmer/server-slim.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -62,9 +62,9 @@
62
62
  "@connectrpc/connect-node": "^2.0.0",
63
63
  "@kubernetes/client-node": "2.0.0",
64
64
  "@opentelemetry/api": "^1.9.0",
65
- "@stigmer/protos": "3.14.0",
66
- "@stigmer/temporal-codecs": "3.14.0",
67
- "@stigmer/zip-structure": "3.14.0",
65
+ "@stigmer/protos": "3.15.0",
66
+ "@stigmer/temporal-codecs": "3.15.0",
67
+ "@stigmer/zip-structure": "3.15.0",
68
68
  "@temporalio/activity": "1.16.2",
69
69
  "@temporalio/client": "1.16.2",
70
70
  "@temporalio/common": "1.16.2",
@@ -14,7 +14,14 @@
14
14
  * owning user (the runner's credential lane, ruling Q3 — the runner
15
15
  * presents exactly such a key via STIGMER_TOKEN);
16
16
  * 4. deleting the key revokes it on the very next request;
17
- * 5. garbage credentials keep the Q6 unclaimed-token rejection.
17
+ * 5. garbage credentials keep the Q6 unclaimed-token rejection;
18
+ * 6. (20260911.11) the identity-account arms: an unprovisioned subject is
19
+ * idp-shaped (whoAmI NOT_FOUND, writes stamped with the raw sub);
20
+ * provisionMyAccount creates the account under the derived id with
21
+ * the issuer's userinfo profile; from then on the verifier resolves
22
+ * the subject to the account id and API keys minted before OR after
23
+ * provisioning answer whoAmI with the owner's account; no operator
24
+ * account exists under the posture.
18
25
  */
19
26
  import { mkdtempSync, rmSync } from "node:fs";
20
27
  import { tmpdir } from "node:os";
@@ -30,8 +37,12 @@ import { SignJWT, exportJWK, generateKeyPair } from "jose";
30
37
  import type { GenerateKeyPairResult } from "jose";
31
38
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
32
39
 
40
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
33
41
  import { ApiKeyCommandController } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/command_pb";
34
42
  import { ApiKeyQueryController } from "@stigmer/protos/ai/stigmer/iam/apikey/v1/query_pb";
43
+ import { IdentityAccountCommandController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/command_pb";
44
+ import { IdentityAccountProvisioningMode } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/enum_pb";
45
+ import { IdentityAccountQueryController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/query_pb";
35
46
  import { PlatformQueryController } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
36
47
  import {
37
48
  Health,
@@ -42,6 +53,11 @@ import { loadConfig } from "../config.js";
42
53
  import { composeServer } from "../compose.js";
43
54
  import type { ComposedServer } from "../compose.js";
44
55
  import { createLogger } from "../logger.js";
56
+ import {
57
+ ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE,
58
+ USERINFO_UNAVAILABLE_PREFIX,
59
+ accountIdFor,
60
+ } from "../../domain/identityaccount/constants.js";
45
61
  import { AUTHENTICATION_TOKEN_MISSING_MESSAGE } from "../../pipeline/interceptors/auth.js";
46
62
 
47
63
  const AUDIENCE = "https://api.stigmer.test/";
@@ -50,8 +66,15 @@ let dir: string;
50
66
  let issuerServer: Server;
51
67
  let issuer: string;
52
68
  let privateKey: GenerateKeyPairResult["privateKey"];
69
+ // A lever for the UNAVAILABLE arm: the issuer's /userinfo answers 503 for
70
+ // this subject while set. Undefined = every subject is served.
71
+ let userinfoRefusesSub: string | undefined;
53
72
  let server: ComposedServer;
54
73
  let port: number;
74
+ // A key minted while its owner was still idp-shaped: its creator stamp is
75
+ // the raw sub. The identity-account arms prove that stamp resolves to the
76
+ // owner's account once one exists (20260911.11 A1's derived id).
77
+ let keyMintedBeforeProvisioning: string;
55
78
 
56
79
  function bearer(token: string): Interceptor {
57
80
  return (next) => (request) => {
@@ -88,8 +111,16 @@ beforeAll(async () => {
88
111
  };
89
112
  issuerServer = createServer((req, res) => {
90
113
  if (req.url === "/.well-known/openid-configuration") {
114
+ // The provisioning lane reads userinfo_endpoint from here (A8) —
115
+ // never a guessed `<issuer>/userinfo`.
91
116
  res.setHeader("content-type", "application/json");
92
- res.end(JSON.stringify({ issuer, jwks_uri: `${issuer}/jwks` }));
117
+ res.end(
118
+ JSON.stringify({
119
+ issuer,
120
+ jwks_uri: `${issuer}/jwks`,
121
+ userinfo_endpoint: `${issuer}/userinfo`,
122
+ }),
123
+ );
93
124
  return;
94
125
  }
95
126
  if (req.url === "/jwks") {
@@ -97,6 +128,43 @@ beforeAll(async () => {
97
128
  res.end(JSON.stringify({ keys: [jwk] }));
98
129
  return;
99
130
  }
131
+ if (req.url === "/userinfo") {
132
+ // The provisioning lane's profile source (20260911.11 A1/Q-IA-2):
133
+ // answers the bearer's own claims as an IdP would, given_name and
134
+ // family_name included — the signature was already proven upstream.
135
+ const header = req.headers.authorization ?? "";
136
+ const token = header.startsWith("Bearer ")
137
+ ? header.slice("Bearer ".length)
138
+ : "";
139
+ const payloadSegment = token.split(".")[1];
140
+ if (payloadSegment === undefined) {
141
+ res.statusCode = 401;
142
+ res.end();
143
+ return;
144
+ }
145
+ const claims = JSON.parse(
146
+ Buffer.from(payloadSegment, "base64url").toString("utf8"),
147
+ ) as { sub?: string; email?: string };
148
+ if (
149
+ userinfoRefusesSub !== undefined &&
150
+ claims.sub === userinfoRefusesSub
151
+ ) {
152
+ res.statusCode = 503;
153
+ res.end();
154
+ return;
155
+ }
156
+ res.setHeader("content-type", "application/json");
157
+ res.end(
158
+ JSON.stringify({
159
+ sub: claims.sub ?? "",
160
+ email: claims.email ?? "",
161
+ given_name: "Composed",
162
+ family_name: "User",
163
+ picture: `${issuer}/pictures/${encodeURIComponent(claims.sub ?? "")}`,
164
+ }),
165
+ );
166
+ return;
167
+ }
100
168
  res.statusCode = 404;
101
169
  res.end();
102
170
  });
@@ -221,6 +289,7 @@ describe("the full credential loop (OIDC login → API key → revocation)", ()
221
289
  expect(second.status?.audit?.specAudit?.createdBy?.id).toBe(
222
290
  "auth0|loop-user",
223
291
  );
292
+ keyMintedBeforeProvisioning = second.spec?.keyHash ?? "";
224
293
  });
225
294
 
226
295
  it("deleting the key revokes it on the very next request", async () => {
@@ -243,3 +312,158 @@ describe("the full credential loop (OIDC login → API key → revocation)", ()
243
312
  expect(ConnectError.from(error).rawMessage).toBe("invalid token");
244
313
  });
245
314
  });
315
+
316
+ describe("identity accounts under the OIDC posture (20260911.11; Q-IA-2, A1, A2)", () => {
317
+ const SUB = "auth0|loop-user";
318
+ const accountId = accountIdFor(SUB);
319
+
320
+ it("no operator account exists under an authentication posture — the boot-time ensure is trusted-local only", async () => {
321
+ const rows = await server.store.listResources(
322
+ ApiResourceKind.identity_account,
323
+ );
324
+ expect(rows).toHaveLength(0);
325
+ });
326
+
327
+ it("whoAmI for an unprovisioned subject is NOT_FOUND with the cloud's copy, and its writes stay stamped with the raw sub", async () => {
328
+ const token = await mintOidcToken(SUB, "loop@example.com");
329
+ const query = createClient(
330
+ IdentityAccountQueryController,
331
+ transportWith(token),
332
+ );
333
+ const error = await query.whoAmI({}).catch((e: unknown) => e);
334
+ expect(ConnectError.from(error).code).toBe(Code.NotFound);
335
+ expect(ConnectError.from(error).rawMessage).toBe(
336
+ ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE,
337
+ );
338
+ });
339
+
340
+ it("provisionMyAccount creates the account under the derived id with the userinfo profile", async () => {
341
+ const token = await mintOidcToken(SUB, "loop@example.com");
342
+ const command = createClient(
343
+ IdentityAccountCommandController,
344
+ transportWith(token),
345
+ );
346
+ const account = await command.provisionMyAccount({});
347
+ expect(account.metadata?.id).toBe(accountId);
348
+ expect(account.metadata?.name).toBe("loop@example.com");
349
+ expect(account.spec).toMatchObject({
350
+ idpId: SUB,
351
+ email: "loop@example.com",
352
+ firstName: "Composed",
353
+ lastName: "User",
354
+ provisioningMode: IdentityAccountProvisioningMode.direct,
355
+ isMachineAccount: false,
356
+ });
357
+ // The account is created BY the subject it is for: the stamp is the
358
+ // sub the verifier admitted idp-shaped — no account existed yet.
359
+ expect(account.status?.audit?.specAudit?.createdBy?.id).toBe(SUB);
360
+ });
361
+
362
+ it("from the next request on, the verifier resolves the subject to the account id — whoAmI answers and writes are stamped with it", async () => {
363
+ const token = await mintOidcToken(SUB, "loop@example.com");
364
+ const query = createClient(
365
+ IdentityAccountQueryController,
366
+ transportWith(token),
367
+ );
368
+ const me = await query.whoAmI({});
369
+ expect(me.metadata?.id).toBe(accountId);
370
+
371
+ const keys = createClient(ApiKeyCommandController, transportWith(token));
372
+ const key = await keys.create({
373
+ apiVersion: "iam.stigmer.ai/v1",
374
+ kind: "ApiKey",
375
+ metadata: { name: "minted after provisioning", org: "local" },
376
+ spec: {},
377
+ });
378
+ expect(key.status?.audit?.specAudit?.createdBy?.id).toBe(accountId);
379
+ expect(key.status?.audit?.specAudit?.createdBy?.email).toBe(
380
+ "loop@example.com",
381
+ );
382
+
383
+ // Over that key, whoAmI is the owner's account — the key is the user.
384
+ const overKey = createClient(
385
+ IdentityAccountQueryController,
386
+ transportWith(key.spec?.keyHash ?? ""),
387
+ );
388
+ expect((await overKey.whoAmI({})).metadata?.id).toBe(accountId);
389
+ });
390
+
391
+ it("a key minted BEFORE provisioning still resolves to the owner's account — the raw-sub stamp derives to the same id", async () => {
392
+ const overLegacyKey = createClient(
393
+ IdentityAccountQueryController,
394
+ transportWith(keyMintedBeforeProvisioning),
395
+ );
396
+ expect((await overLegacyKey.whoAmI({})).metadata?.id).toBe(accountId);
397
+ });
398
+
399
+ it("provisionMyAccount is idempotent for a provisioned subject", async () => {
400
+ const token = await mintOidcToken(SUB, "loop@example.com");
401
+ const command = createClient(
402
+ IdentityAccountCommandController,
403
+ transportWith(token),
404
+ );
405
+ const before = (
406
+ await server.store.listResources(ApiResourceKind.identity_account)
407
+ ).length;
408
+ const again = await command.provisionMyAccount({});
409
+ expect(again.metadata?.id).toBe(accountId);
410
+ expect(
411
+ (await server.store.listResources(ApiResourceKind.identity_account))
412
+ .length,
413
+ ).toBe(before);
414
+ });
415
+
416
+ it("two concurrent first logins for one subject end in exactly one account", async () => {
417
+ const token = await mintOidcToken("auth0|racer", "racer@example.com");
418
+ const command = createClient(
419
+ IdentityAccountCommandController,
420
+ transportWith(token),
421
+ );
422
+ const before = (
423
+ await server.store.listResources(ApiResourceKind.identity_account)
424
+ ).length;
425
+ const [a, b] = await Promise.all([
426
+ command.provisionMyAccount({}),
427
+ command.provisionMyAccount({}),
428
+ ]);
429
+ expect(a.metadata?.id).toBe(accountIdFor("auth0|racer"));
430
+ expect(b.metadata?.id).toBe(a.metadata?.id);
431
+ expect(
432
+ (await server.store.listResources(ApiResourceKind.identity_account))
433
+ .length,
434
+ ).toBe(before + 1);
435
+ });
436
+
437
+ it("a userinfo failure is UNAVAILABLE with the cloud's copy, and no account is created", async () => {
438
+ // A real token (the verifier must admit it) whose subject the issuer's
439
+ // /userinfo refuses while the lever is set.
440
+ const token = await mintOidcToken(
441
+ "auth0|userinfo-down",
442
+ "down@example.com",
443
+ );
444
+ userinfoRefusesSub = "auth0|userinfo-down";
445
+ try {
446
+ const command = createClient(
447
+ IdentityAccountCommandController,
448
+ transportWith(token),
449
+ );
450
+ const error = await command
451
+ .provisionMyAccount({})
452
+ .catch((e: unknown) => e);
453
+ expect(ConnectError.from(error).code).toBe(Code.Unavailable);
454
+ expect(
455
+ ConnectError.from(error).rawMessage.startsWith(
456
+ USERINFO_UNAVAILABLE_PREFIX,
457
+ ),
458
+ ).toBe(true);
459
+ const query = createClient(
460
+ IdentityAccountQueryController,
461
+ transportWith(token),
462
+ );
463
+ const missing = await query.whoAmI({}).catch((e: unknown) => e);
464
+ expect(ConnectError.from(missing).code).toBe(Code.NotFound);
465
+ } finally {
466
+ userinfoRefusesSub = undefined;
467
+ }
468
+ });
469
+ });
@@ -28,6 +28,7 @@ import path from "node:path";
28
28
 
29
29
  import type { ConnectRouter, Transport } from "@connectrpc/connect";
30
30
 
31
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
31
32
  import { HealthCheckResponse_ServingStatus as ServingStatus } from "@stigmer/protos/grpc/health/v1/health_pb";
32
33
 
33
34
  import { registerAgentServices } from "../domain/agent/controller.js";
@@ -64,6 +65,15 @@ import { registerChannelConversationServices } from "../domain/agentchannel/conv
64
65
  import { registerChannelMessageServices } from "../domain/agentchannel/message.js";
65
66
  import { registerAgentShareServices } from "../domain/agentshare/controller.js";
66
67
  import { registerApiKeyServices } from "../domain/apikey/controller.js";
68
+ import {
69
+ newCreateAccountPath,
70
+ registerIdentityAccountServices,
71
+ } from "../domain/identityaccount/controller.js";
72
+ import { ensureOperatorAccount } from "../domain/identityaccount/operator.js";
73
+ import { newDirectAccountProvisioner } from "../domain/identityaccount/provisioning.js";
74
+ import { newResourceIdentityAccountStore } from "../domain/identityaccount/resource-store.js";
75
+ import { newIssuerDiscovery } from "../identity/oidc-discovery.js";
76
+ import { newOidcUserInfoClient } from "../identity/oidc-userinfo.js";
67
77
  import { newApiKeyIdentityVerifier } from "../domain/apikey/verifier.js";
68
78
  import type { IdentityVerifier } from "../extensions/identity.js";
69
79
  import { registerArtifactServices } from "../domain/artifact/controller.js";
@@ -117,6 +127,7 @@ import {
117
127
  } from "../observability/rpc-metrics.js";
118
128
  import { buildInterceptorChain } from "../pipeline/chain.js";
119
129
  import { createVerifierChainInterceptor } from "../pipeline/interceptors/auth.js";
130
+ import { operatorIdentitySnapshot } from "../pipeline/steps/defaults.js";
120
131
  import { createErrorBoundaryInterceptor } from "../pipeline/interceptors/error-boundary.js";
121
132
  import { createRequestMetricsInterceptor } from "../pipeline/interceptors/request-metrics.js";
122
133
  import { newPermissiveSingleTeamAuthorizer } from "../pipeline/steps/authorize.js";
@@ -221,6 +232,13 @@ export interface ComposedServer {
221
232
  * them or reorder them, and both are the drift DD-007 rules out.
222
233
  */
223
234
  identityVerifiers: ReadonlyArray<IdentityVerifier>;
235
+ /**
236
+ * The one routes function both routers are built from (exposed for
237
+ * tests, the `store` posture — the tier-truthfulness invariant
238
+ * enumerates the services a composition registers through it; the
239
+ * server registers no reflection service, deliberately).
240
+ */
241
+ routes: (router: ConnectRouter) => void;
224
242
  /** Completes wiring, flips SERVING, binds the port; returns the bound port. */
225
243
  start(): Promise<number>;
226
244
  /** NOT_SERVING first, stop background work, drain connections. */
@@ -294,6 +312,75 @@ export async function composeServer(
294
312
  });
295
313
  }
296
314
 
315
+ // The require-authentication posture has two sources OR'd here: the
316
+ // OSS OIDC issuer (O3 rulings Q1+Q2) and a composed unit's declaration
317
+ // (registry point, entry 20260904.02). Neither source = the OSS
318
+ // trusted-local posture, byte-identical wire behavior. Resolved in the
319
+ // storage stage because the posture decides a WRITE below (the operator
320
+ // account); the verifier chain it also governs is built at the end.
321
+ const authEnabled = config.oidcIssuer !== "";
322
+ const requireAuthentication =
323
+ authEnabled || extensions.requireAuthentication !== undefined;
324
+
325
+ // Stage: identity accounts (20260911.11) — the first domain whose
326
+ // persistence is a PORT rather than the generic Store. The composed
327
+ // driver (`drivers.identityAccountStore`, Q-IA-9) serves when a unit
328
+ // registers one; otherwise open source's adapter over `store` installs
329
+ // HERE (the default lives with the consumer, never in the registry).
330
+ // This ONE binding is what the create path, the provisioner, the
331
+ // operator ensure and both OSS verifiers read, so a composed driver is
332
+ // followed everywhere by construction. The domain's one create path is
333
+ // built once and shared by the create RPC, provisioning and the operator
334
+ // ensure below.
335
+ const identityAccounts =
336
+ extensions.drivers.identityAccountStore ??
337
+ newResourceIdentityAccountStore(store);
338
+ const identityAccountPath = {
339
+ accounts: identityAccounts,
340
+ logger,
341
+ authorizer,
342
+ authorizationLifecycle,
343
+ };
344
+ const createIdentityAccount = newCreateAccountPath(
345
+ identityAccountPath,
346
+ ApiResourceKind.identity_account,
347
+ );
348
+ // ONE issuer discovery for every lane that reads a well-known document
349
+ // (A8): provisioning's userinfo client here and the OIDC verifier's
350
+ // JWKS location below share it, so an issuer is discovered once per
351
+ // process and validated the same way wherever it is consumed. Built in
352
+ // this stage, not inside routes() (which runs twice) — the same reason
353
+ // the searchRegistry lives here.
354
+ const issuerDiscovery = newIssuerDiscovery();
355
+ // Provisioning reads the caller's profile from the userinfo endpoint of
356
+ // the issuer that vouched for the token, discovered per issuer and
357
+ // cached (A8) — so a composition whose own verifiers name their issuer
358
+ // on the identity needs no knob here.
359
+ const identityAccountProvisioner = newDirectAccountProvisioner({
360
+ accounts: identityAccounts,
361
+ createAccount: createIdentityAccount,
362
+ userInfo: newOidcUserInfoClient(issuerDiscovery),
363
+ });
364
+ // Under the trusted-local posture the server knows its one principal
365
+ // from the operator seam (the SAME seam the interceptor stamps callers
366
+ // from — never config directly, so the row and the stamp cannot
367
+ // disagree; T01_1_review.md A2, A5) and states the fact at boot:
368
+ // create-if-absent, so preferences set later survive every reboot. A
369
+ // composition with an authentication posture never creates one — its
370
+ // users provision themselves on first sign-in. A stage that writes a
371
+ // resource is new here; it earns its place because it is the truth of
372
+ // that posture, and a failure is a loud boot throw like the storage
373
+ // stage's own.
374
+ if (!requireAuthentication) {
375
+ const operator = await ensureOperatorAccount(
376
+ { accounts: identityAccounts, createAccount: createIdentityAccount },
377
+ operatorIdentitySnapshot(),
378
+ );
379
+ logger.info("trusted-local operator account ensured", {
380
+ accountId: operator.metadata?.id ?? "",
381
+ });
382
+ }
383
+
297
384
  // Stage: keys — the ratified fail-loud boot ASYMMETRY (D2 cross-domain
298
385
  // invariants; Go server.go:277-293):
299
386
  //
@@ -799,6 +886,17 @@ export async function composeServer(
799
886
  authorizationLifecycle,
800
887
  listReadScope: extensions.drivers.listReadScope,
801
888
  });
889
+ // IdentityAccount (20260911.11): served once by open source in every
890
+ // edition over the store PORT bound in the identity-accounts stage; a
891
+ // composition adds what differs per edition through the registry —
892
+ // the federation capability (the four federated RPC arms) and the
893
+ // provision slot's gate steps (the cloud's personal organization).
894
+ registerIdentityAccountServices(router, {
895
+ ...identityAccountPath,
896
+ provisioner: identityAccountProvisioner,
897
+ federation: extensions.drivers.identityFederation,
898
+ gateSteps: extensions.gateSteps,
899
+ });
802
900
  registerEnvironmentServices(router, {
803
901
  store,
804
902
  logger,
@@ -1092,14 +1190,8 @@ export async function composeServer(
1092
1190
  const inProcessWiring = createInProcessClients(routes, logger);
1093
1191
  inProcess = inProcessWiring.clients;
1094
1192
 
1095
- // The require-authentication posture has two sources OR'd here: the
1096
- // OSS OIDC issuer (O3 rulings Q1+Q2) and a composed unit's declaration
1097
- // (registry point, entry 20260904.02). Neither source = the OSS
1098
- // trusted-local posture, byte-identical wire behavior.
1099
- const authEnabled = config.oidcIssuer !== "";
1100
- const requireAuthentication =
1101
- authEnabled || extensions.requireAuthentication !== undefined;
1102
- // What the posture registers, in Java's ProviderManager order (opaque
1193
+ // What the posture (resolved in the storage stage) registers, in Java's
1194
+ // ProviderManager order (opaque
1103
1195
  // keys first — the cheapest claim — then the JWT lanes), ahead of the
1104
1196
  // extension entries ("OSS entries first", the registry contract):
1105
1197
  // - the API-key verifier rides the POSTURE, not the issuer
@@ -1108,19 +1200,31 @@ export async function composeServer(
1108
1200
  // authentication posture must honor the keys it issues. Gating it on
1109
1201
  // the issuer left a posture-declaring composition minting keys
1110
1202
  // nothing verified.
1111
- // - the OIDC verifier rides the ISSUER alone: it resolves callers to
1112
- // the token's `sub`, which is the wrong principal for a composition
1113
- // whose own lanes resolve users to their account ids — that is why a
1114
- // declared posture never borrows the issuer knob.
1203
+ // - the OIDC verifier rides the ISSUER alone: it is open source's
1204
+ // single-issuer lane, and a composition with verifiers of its own
1205
+ // names their issuers and audiences itself and declares the posture
1206
+ // instead — so the knob and the extension entries never both claim
1207
+ // one JWT.
1208
+ // - both OSS verifiers resolve their subject through the identity-
1209
+ // account domain (20260911.11 Q-IA-2, A6): a provisioned user is
1210
+ // stamped with the account id whichever credential they present,
1211
+ // an unprovisioned one is admitted idp-shaped. They read the SAME
1212
+ // port the domain writes through (`identityAccounts`, the
1213
+ // identity-accounts stage — a composed driver included), so a row
1214
+ // provisioning creates resolves on the very next request.
1115
1215
  // The runner's credential in either posture is an operator-minted API
1116
1216
  // token via STIGMER_TOKEN (O3 ruling Q3) — no runner-specific verifier.
1117
1217
  const identityVerifiers = [
1118
- ...(requireAuthentication ? [newApiKeyIdentityVerifier(store)] : []),
1218
+ ...(requireAuthentication
1219
+ ? [newApiKeyIdentityVerifier({ store, accounts: identityAccounts })]
1220
+ : []),
1119
1221
  ...(authEnabled
1120
1222
  ? [
1121
1223
  newOidcIdentityVerifier({
1122
1224
  issuer: config.oidcIssuer,
1123
1225
  audience: config.oidcAudience,
1226
+ accounts: identityAccounts,
1227
+ discovery: issuerDiscovery,
1124
1228
  }),
1125
1229
  ]
1126
1230
  : []),
@@ -1203,6 +1307,7 @@ export async function composeServer(
1203
1307
  workflowExecutionStreamBroker,
1204
1308
  inProcessTransport: inProcessWiring.transport,
1205
1309
  identityVerifiers,
1310
+ routes,
1206
1311
 
1207
1312
  async start(): Promise<number> {
1208
1313
  // Temporal boot is NON-fatal end to end (Go server.go): a failed