@stigmer/server 3.15.2 → 3.15.3-dev.20260914121148

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 (217) hide show
  1. package/dist/boot/compose.d.ts.map +1 -1
  2. package/dist/boot/compose.js +103 -9
  3. package/dist/boot/compose.js.map +1 -1
  4. package/dist/domain/iampolicy/access-lists.d.ts +27 -0
  5. package/dist/domain/iampolicy/access-lists.d.ts.map +1 -0
  6. package/dist/domain/iampolicy/access-lists.js +124 -0
  7. package/dist/domain/iampolicy/access-lists.js.map +1 -0
  8. package/dist/domain/iampolicy/constants.d.ts +135 -0
  9. package/dist/domain/iampolicy/constants.d.ts.map +1 -0
  10. package/dist/domain/iampolicy/constants.js +203 -0
  11. package/dist/domain/iampolicy/constants.js.map +1 -0
  12. package/dist/domain/iampolicy/controller.d.ts +29 -0
  13. package/dist/domain/iampolicy/controller.d.ts.map +1 -0
  14. package/dist/domain/iampolicy/controller.js +451 -0
  15. package/dist/domain/iampolicy/controller.js.map +1 -0
  16. package/dist/domain/iampolicy/display-resolver.d.ts +6 -0
  17. package/dist/domain/iampolicy/display-resolver.d.ts.map +1 -0
  18. package/dist/domain/iampolicy/display-resolver.js +90 -0
  19. package/dist/domain/iampolicy/display-resolver.js.map +1 -0
  20. package/dist/domain/iampolicy/grant-path.d.ts +42 -0
  21. package/dist/domain/iampolicy/grant-path.d.ts.map +1 -0
  22. package/dist/domain/iampolicy/grant-path.js +183 -0
  23. package/dist/domain/iampolicy/grant-path.js.map +1 -0
  24. package/dist/domain/iampolicy/grant-scope.d.ts +4 -0
  25. package/dist/domain/iampolicy/grant-scope.d.ts.map +1 -0
  26. package/dist/domain/iampolicy/grant-scope.js +34 -0
  27. package/dist/domain/iampolicy/grant-scope.js.map +1 -0
  28. package/dist/domain/iampolicy/membership.d.ts +111 -0
  29. package/dist/domain/iampolicy/membership.d.ts.map +1 -0
  30. package/dist/domain/iampolicy/membership.js +152 -0
  31. package/dist/domain/iampolicy/membership.js.map +1 -0
  32. package/dist/domain/iampolicy/permissions.d.ts +19 -0
  33. package/dist/domain/iampolicy/permissions.d.ts.map +1 -0
  34. package/dist/domain/iampolicy/permissions.js +27 -0
  35. package/dist/domain/iampolicy/permissions.js.map +1 -0
  36. package/dist/domain/iampolicy/resource-store.d.ts +4 -0
  37. package/dist/domain/iampolicy/resource-store.d.ts.map +1 -0
  38. package/dist/domain/iampolicy/resource-store.js +135 -0
  39. package/dist/domain/iampolicy/resource-store.js.map +1 -0
  40. package/dist/domain/iampolicy/role-lifecycle.d.ts +9 -0
  41. package/dist/domain/iampolicy/role-lifecycle.d.ts.map +1 -0
  42. package/dist/domain/iampolicy/role-lifecycle.js +106 -0
  43. package/dist/domain/iampolicy/role-lifecycle.js.map +1 -0
  44. package/dist/domain/iampolicy/roles.d.ts +11 -0
  45. package/dist/domain/iampolicy/roles.d.ts.map +1 -0
  46. package/dist/domain/iampolicy/roles.js +78 -0
  47. package/dist/domain/iampolicy/roles.js.map +1 -0
  48. package/dist/domain/iampolicy/specs.d.ts +7 -0
  49. package/dist/domain/iampolicy/specs.d.ts.map +1 -0
  50. package/dist/domain/iampolicy/specs.js +45 -0
  51. package/dist/domain/iampolicy/specs.js.map +1 -0
  52. package/dist/domain/iampolicy/steps.d.ts +18 -0
  53. package/dist/domain/iampolicy/steps.d.ts.map +1 -0
  54. package/dist/domain/iampolicy/steps.js +117 -0
  55. package/dist/domain/iampolicy/steps.js.map +1 -0
  56. package/dist/domain/iampolicy/store-contract.d.ts +8 -0
  57. package/dist/domain/iampolicy/store-contract.d.ts.map +1 -0
  58. package/dist/domain/iampolicy/store-contract.js +258 -0
  59. package/dist/domain/iampolicy/store-contract.js.map +1 -0
  60. package/dist/domain/iampolicy/store.d.ts +78 -0
  61. package/dist/domain/iampolicy/store.d.ts.map +1 -0
  62. package/dist/domain/iampolicy/store.js +13 -0
  63. package/dist/domain/iampolicy/store.js.map +1 -0
  64. package/dist/domain/iampolicy/wire-refusals.d.ts +25 -0
  65. package/dist/domain/iampolicy/wire-refusals.d.ts.map +1 -0
  66. package/dist/domain/iampolicy/wire-refusals.js +76 -0
  67. package/dist/domain/iampolicy/wire-refusals.js.map +1 -0
  68. package/dist/domain/identityaccount/constants.d.ts +33 -0
  69. package/dist/domain/identityaccount/constants.d.ts.map +1 -1
  70. package/dist/domain/identityaccount/constants.js +2 -48
  71. package/dist/domain/identityaccount/constants.js.map +1 -1
  72. package/dist/domain/identityaccount/controller.d.ts +8 -1
  73. package/dist/domain/identityaccount/controller.d.ts.map +1 -1
  74. package/dist/domain/identityaccount/controller.js +54 -29
  75. package/dist/domain/identityaccount/controller.js.map +1 -1
  76. package/dist/domain/identityaccount/operator.d.ts.map +1 -1
  77. package/dist/domain/identityaccount/operator.js +2 -4
  78. package/dist/domain/identityaccount/operator.js.map +1 -1
  79. package/dist/domain/identityaccount/provisioning.d.ts +20 -1
  80. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
  81. package/dist/domain/identityaccount/provisioning.js +17 -3
  82. package/dist/domain/identityaccount/provisioning.js.map +1 -1
  83. package/dist/domain/identityaccount/resolve.d.ts +23 -0
  84. package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
  85. package/dist/domain/identityaccount/resolve.js +17 -0
  86. package/dist/domain/identityaccount/resolve.js.map +1 -1
  87. package/dist/domain/identityaccount/store-contract.d.ts +4 -18
  88. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
  89. package/dist/domain/identityaccount/store-contract.js +12 -47
  90. package/dist/domain/identityaccount/store-contract.js.map +1 -1
  91. package/dist/extensions/authorization-queries.d.ts +81 -0
  92. package/dist/extensions/authorization-queries.d.ts.map +1 -0
  93. package/dist/extensions/authorization-queries.js +2 -0
  94. package/dist/extensions/authorization-queries.js.map +1 -0
  95. package/dist/extensions/drivers.d.ts +49 -2
  96. package/dist/extensions/drivers.d.ts.map +1 -1
  97. package/dist/extensions/identity.d.ts +14 -0
  98. package/dist/extensions/identity.d.ts.map +1 -1
  99. package/dist/extensions/identity.js +18 -1
  100. package/dist/extensions/identity.js.map +1 -1
  101. package/dist/extensions/list-read-scope.d.ts +41 -4
  102. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  103. package/dist/extensions/list-read-scope.js +44 -7
  104. package/dist/extensions/list-read-scope.js.map +1 -1
  105. package/dist/extensions/policy-grant-scope.d.ts +50 -0
  106. package/dist/extensions/policy-grant-scope.d.ts.map +1 -0
  107. package/dist/extensions/policy-grant-scope.js +2 -0
  108. package/dist/extensions/policy-grant-scope.js.map +1 -0
  109. package/dist/extensions/registry.d.ts +23 -1
  110. package/dist/extensions/registry.d.ts.map +1 -1
  111. package/dist/extensions/registry.js +30 -0
  112. package/dist/extensions/registry.js.map +1 -1
  113. package/dist/extensions/resource-authorization.d.ts +63 -0
  114. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  115. package/dist/index.d.ts +7 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +2 -0
  118. package/dist/index.js.map +1 -1
  119. package/dist/pipeline/apiresource-meta.d.ts +77 -1
  120. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  121. package/dist/pipeline/apiresource-meta.js +183 -1
  122. package/dist/pipeline/apiresource-meta.js.map +1 -1
  123. package/dist/pipeline/interceptors/auth.d.ts +12 -0
  124. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  125. package/dist/pipeline/interceptors/auth.js +13 -1
  126. package/dist/pipeline/interceptors/auth.js.map +1 -1
  127. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  128. package/dist/pipeline/steps/authorization-tuples.js +7 -26
  129. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  130. package/dist/pipeline/steps/authorize.d.ts +20 -5
  131. package/dist/pipeline/steps/authorize.d.ts.map +1 -1
  132. package/dist/pipeline/steps/authorize.js +37 -17
  133. package/dist/pipeline/steps/authorize.js.map +1 -1
  134. package/dist/pipeline/steps/defaults.d.ts +10 -0
  135. package/dist/pipeline/steps/defaults.d.ts.map +1 -1
  136. package/dist/pipeline/steps/defaults.js +39 -0
  137. package/dist/pipeline/steps/defaults.js.map +1 -1
  138. package/dist/pipeline/steps/shapes.d.ts +18 -0
  139. package/dist/pipeline/steps/shapes.d.ts.map +1 -1
  140. package/dist/pipeline/steps/shapes.js +29 -0
  141. package/dist/pipeline/steps/shapes.js.map +1 -1
  142. package/dist/store/port-contract.d.ts +61 -0
  143. package/dist/store/port-contract.d.ts.map +1 -0
  144. package/dist/store/port-contract.js +67 -0
  145. package/dist/store/port-contract.js.map +1 -0
  146. package/package.json +4 -4
  147. package/src/boot/compose.ts +107 -10
  148. package/src/domain/iampolicy/__tests__/access-lists.test.ts +238 -0
  149. package/src/domain/iampolicy/__tests__/constants.test.ts +388 -0
  150. package/src/domain/iampolicy/__tests__/controller.test.ts +960 -0
  151. package/src/domain/iampolicy/__tests__/display-resolver.test.ts +138 -0
  152. package/src/domain/iampolicy/__tests__/grant-path.test.ts +474 -0
  153. package/src/domain/iampolicy/__tests__/grant-scope.test.ts +61 -0
  154. package/src/domain/iampolicy/__tests__/iampolicy.test.ts +532 -0
  155. package/src/domain/iampolicy/__tests__/membership.test.ts +447 -0
  156. package/src/domain/iampolicy/__tests__/permissions.test.ts +41 -0
  157. package/src/domain/iampolicy/__tests__/resource-store.test.ts +165 -0
  158. package/src/domain/iampolicy/__tests__/role-lifecycle.test.ts +361 -0
  159. package/src/domain/iampolicy/__tests__/roles.test.ts +53 -0
  160. package/src/domain/iampolicy/__tests__/specs.test.ts +41 -0
  161. package/src/domain/iampolicy/__tests__/steps.test.ts +278 -0
  162. package/src/domain/iampolicy/__tests__/support.ts +195 -0
  163. package/src/domain/iampolicy/__tests__/wire-refusals.test.ts +117 -0
  164. package/src/domain/iampolicy/access-lists.ts +191 -0
  165. package/src/domain/iampolicy/constants.ts +243 -0
  166. package/src/domain/iampolicy/controller.ts +798 -0
  167. package/src/domain/iampolicy/display-resolver.ts +123 -0
  168. package/src/domain/iampolicy/grant-path.ts +270 -0
  169. package/src/domain/iampolicy/grant-scope.ts +36 -0
  170. package/src/domain/iampolicy/membership.ts +298 -0
  171. package/src/domain/iampolicy/permissions.ts +35 -0
  172. package/src/domain/iampolicy/resource-store.ts +186 -0
  173. package/src/domain/iampolicy/role-lifecycle.ts +132 -0
  174. package/src/domain/iampolicy/roles.ts +95 -0
  175. package/src/domain/iampolicy/specs.ts +53 -0
  176. package/src/domain/iampolicy/steps.ts +178 -0
  177. package/src/domain/iampolicy/store-contract.ts +450 -0
  178. package/src/domain/iampolicy/store.ts +103 -0
  179. package/src/domain/iampolicy/wire-refusals.ts +110 -0
  180. package/src/domain/identityaccount/__tests__/provisioning.test.ts +19 -7
  181. package/src/domain/identityaccount/__tests__/resolve.test.ts +113 -2
  182. package/src/domain/identityaccount/constants.ts +16 -31
  183. package/src/domain/identityaccount/controller.ts +65 -35
  184. package/src/domain/identityaccount/operator.ts +5 -5
  185. package/src/domain/identityaccount/provisioning.ts +43 -5
  186. package/src/domain/identityaccount/resolve.ts +43 -0
  187. package/src/domain/identityaccount/store-contract.ts +22 -70
  188. package/src/extensions/__tests__/composed-support.ts +87 -0
  189. package/src/extensions/__tests__/iam-policy-composed.test.ts +373 -0
  190. package/src/extensions/__tests__/iam-policy-points.test.ts +169 -0
  191. package/src/extensions/__tests__/identity-account-composed.test.ts +12 -78
  192. package/src/extensions/__tests__/identity.test.ts +62 -0
  193. package/src/extensions/__tests__/list-read-scope.test.ts +138 -0
  194. package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
  195. package/src/extensions/authorization-queries.ts +97 -0
  196. package/src/extensions/drivers.ts +49 -2
  197. package/src/extensions/identity.ts +21 -0
  198. package/src/extensions/list-read-scope.ts +87 -9
  199. package/src/extensions/policy-grant-scope.ts +50 -0
  200. package/src/extensions/registry.ts +62 -1
  201. package/src/extensions/resource-authorization.ts +65 -0
  202. package/src/index.ts +20 -0
  203. package/src/pipeline/__tests__/grantable-roles-for.test.ts +87 -0
  204. package/src/pipeline/__tests__/inherited-authorization-parent.test.ts +51 -0
  205. package/src/pipeline/__tests__/kind-by-enum-name.test.ts +107 -0
  206. package/src/pipeline/__tests__/kind-served-by-edition.test.ts +107 -0
  207. package/src/pipeline/apiresource-meta.ts +218 -1
  208. package/src/pipeline/interceptors/auth.ts +14 -1
  209. package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +73 -3
  210. package/src/pipeline/steps/__tests__/authorize.test.ts +134 -9
  211. package/src/pipeline/steps/__tests__/derived-id.test.ts +49 -0
  212. package/src/pipeline/steps/authorization-tuples.ts +7 -28
  213. package/src/pipeline/steps/authorize.ts +46 -21
  214. package/src/pipeline/steps/defaults.ts +42 -0
  215. package/src/pipeline/steps/shapes.ts +31 -0
  216. package/src/store/__tests__/port-contract.test.ts +123 -0
  217. package/src/store/port-contract.ts +102 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The port-contract runner — the scaffolding every domain store PORT's
3
+ * contract kit is built on (identity-account's store-contract.ts since
4
+ * 20260911.11 A11; IamPolicy's since 20260913.01 slice 2, when this was
5
+ * lifted out of the first kit so the second did not carry a copy).
6
+ *
7
+ * A port kit is a list of named cases a DRIVER's test iterates: open
8
+ * source runs them over its adapter on sqlite and Postgres, a composition
9
+ * runs the same list over the store it registers as a driver, so "the port
10
+ * holds" is one statement proven per driver, never restated per
11
+ * repository. This module owns the part every kit shares and no kit should
12
+ * restate: a FRESH fixture per case, the body run over it, cleanup after,
13
+ * and the rule that a failing body is the failure reported (a cleanup that
14
+ * fails after a failed body must not replace the assertion that matters
15
+ * with a teardown detail; a cleanup that fails after a passing body is a
16
+ * real failure).
17
+ *
18
+ * Shape. The package's precedent is store/__tests__/store-contract.ts —
19
+ * `describeStoreContract(makeFixture)` over the generic Store, a vitest
20
+ * `describe`. A port kit is consumed by ANOTHER package's tests through the
21
+ * published barrel, so it ships in dist/ and must not import vitest (a
22
+ * devDependency that would enter the root barrel's runtime import graph).
23
+ * It therefore returns cases instead of calling `describe`, asserts through
24
+ * node:assert/strict, and the consumer's framework does
25
+ * `for (const c of cases) it(c.name, c.run)`. Do not "fix" it back to the
26
+ * precedent's vitest shape.
27
+ *
28
+ * `disconnect` is the one escape hatch a port cannot express: a port has
29
+ * no lifecycle by design (a composition owns its store's), yet "an outage
30
+ * never reads as not-found" is the line that matters most, so a fixture
31
+ * cuts its store from the database and the kit asserts that the fault
32
+ * propagates. `cleanup` must tolerate a disconnected store.
33
+ */
34
+ /** One fresh, isolated store per case, plus the one escape hatch the port cannot express. */
35
+ export interface PortContractFixture<Port> {
36
+ readonly store: Port;
37
+ /**
38
+ * Cuts the store from its database so every later call is an
39
+ * infrastructure fault. `cleanup` still runs afterwards and must tolerate
40
+ * a disconnected store (both OSS drivers' `close` is idempotent; a pool
41
+ * fixture ends a per-case pool here and skips it in cleanup).
42
+ */
43
+ disconnect(): Promise<void>;
44
+ cleanup(): Promise<void>;
45
+ }
46
+ export interface PortContractCase {
47
+ /** The contract line, in the port's words; the framework prints it as the test name. */
48
+ readonly name: string;
49
+ /** Makes a FRESH fixture, runs the case, cleans up. A failing assertion wins over a failing cleanup. */
50
+ run(): Promise<void>;
51
+ }
52
+ /** A case body over a live fixture; the runner owns the fixture's lifecycle around it. */
53
+ export type PortContractBody<Port> = (fixture: PortContractFixture<Port>) => Promise<void>;
54
+ /** A kit's declaration: the contract line and the body that proves it. */
55
+ export type PortContractDeclaration<Port> = readonly [
56
+ name: string,
57
+ body: PortContractBody<Port>
58
+ ];
59
+ /** The kit's declarations as runnable cases over `makeFixture`, one fresh fixture per case, in declared order. */
60
+ export declare function portContractCases<Port>(declarations: ReadonlyArray<PortContractDeclaration<Port>>, makeFixture: () => Promise<PortContractFixture<Port>>): ReadonlyArray<PortContractCase>;
61
+ //# sourceMappingURL=port-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-contract.d.ts","sourceRoot":"","sources":["../../src/store/port-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,mBAAmB,CAAC,IAAI;IACvC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB;;;;;OAKG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wGAAwG;IACxG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,CAAC,IAAI,IAAI,CACnC,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,KAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,0EAA0E;AAC1E,MAAM,MAAM,uBAAuB,CAAC,IAAI,IAAI,SAAS;IACnD,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC;CAC7B,CAAC;AA6BF,kHAAkH;AAClH,wBAAgB,iBAAiB,CAAC,IAAI,EACpC,YAAY,EAAE,aAAa,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,EAC1D,WAAW,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,GACpD,aAAa,CAAC,gBAAgB,CAAC,CAKjC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The port-contract runner — the scaffolding every domain store PORT's
3
+ * contract kit is built on (identity-account's store-contract.ts since
4
+ * 20260911.11 A11; IamPolicy's since 20260913.01 slice 2, when this was
5
+ * lifted out of the first kit so the second did not carry a copy).
6
+ *
7
+ * A port kit is a list of named cases a DRIVER's test iterates: open
8
+ * source runs them over its adapter on sqlite and Postgres, a composition
9
+ * runs the same list over the store it registers as a driver, so "the port
10
+ * holds" is one statement proven per driver, never restated per
11
+ * repository. This module owns the part every kit shares and no kit should
12
+ * restate: a FRESH fixture per case, the body run over it, cleanup after,
13
+ * and the rule that a failing body is the failure reported (a cleanup that
14
+ * fails after a failed body must not replace the assertion that matters
15
+ * with a teardown detail; a cleanup that fails after a passing body is a
16
+ * real failure).
17
+ *
18
+ * Shape. The package's precedent is store/__tests__/store-contract.ts —
19
+ * `describeStoreContract(makeFixture)` over the generic Store, a vitest
20
+ * `describe`. A port kit is consumed by ANOTHER package's tests through the
21
+ * published barrel, so it ships in dist/ and must not import vitest (a
22
+ * devDependency that would enter the root barrel's runtime import graph).
23
+ * It therefore returns cases instead of calling `describe`, asserts through
24
+ * node:assert/strict, and the consumer's framework does
25
+ * `for (const c of cases) it(c.name, c.run)`. Do not "fix" it back to the
26
+ * precedent's vitest shape.
27
+ *
28
+ * `disconnect` is the one escape hatch a port cannot express: a port has
29
+ * no lifecycle by design (a composition owns its store's), yet "an outage
30
+ * never reads as not-found" is the line that matters most, so a fixture
31
+ * cuts its store from the database and the kit asserts that the fault
32
+ * propagates. `cleanup` must tolerate a disconnected store.
33
+ */
34
+ /**
35
+ * Runs `body` over a fresh fixture. The body's failure is the one reported:
36
+ * a cleanup failure after a failed body would otherwise replace the
37
+ * assertion that matters with a teardown detail.
38
+ */
39
+ async function withFixture(makeFixture, body) {
40
+ const fixture = await makeFixture();
41
+ let failed = false;
42
+ try {
43
+ await body(fixture);
44
+ }
45
+ catch (error) {
46
+ failed = true;
47
+ throw error;
48
+ }
49
+ finally {
50
+ try {
51
+ await fixture.cleanup();
52
+ }
53
+ catch (cleanupError) {
54
+ if (!failed) {
55
+ throw cleanupError;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ /** The kit's declarations as runnable cases over `makeFixture`, one fresh fixture per case, in declared order. */
61
+ export function portContractCases(declarations, makeFixture) {
62
+ return declarations.map(([name, body]) => ({
63
+ name,
64
+ run: () => withFixture(makeFixture, body),
65
+ }));
66
+ }
67
+ //# sourceMappingURL=port-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-contract.js","sourceRoot":"","sources":["../../src/store/port-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAiCH;;;;GAIG;AACH,KAAK,UAAU,WAAW,CACxB,WAAqD,EACrD,IAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;IACpC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,IAAI,CAAC;QACd,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,YAAY,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,YAAY,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,iBAAiB,CAC/B,YAA0D,EAC1D,WAAqD;IAErD,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI;QACJ,GAAG,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC;KAC1C,CAAC,CAAC,CAAC;AACN,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/server",
3
- "version": "3.15.2",
3
+ "version": "3.15.3-dev.20260914121148",
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.15.2",
66
- "@stigmer/temporal-codecs": "3.15.2",
67
- "@stigmer/zip-structure": "3.15.2",
65
+ "@stigmer/protos": "3.15.3-dev.20260914121148",
66
+ "@stigmer/temporal-codecs": "3.15.3-dev.20260914121148",
67
+ "@stigmer/zip-structure": "3.15.3-dev.20260914121148",
68
68
  "@temporalio/activity": "1.16.2",
69
69
  "@temporalio/client": "1.16.2",
70
70
  "@temporalio/common": "1.16.2",
@@ -72,6 +72,12 @@ import {
72
72
  import { ensureOperatorAccount } from "../domain/identityaccount/operator.js";
73
73
  import { newDirectAccountProvisioner } from "../domain/identityaccount/provisioning.js";
74
74
  import { newResourceIdentityAccountStore } from "../domain/identityaccount/resource-store.js";
75
+ import { registerIamPolicyServices } from "../domain/iampolicy/controller.js";
76
+ import { newIamPolicyGrantPath } from "../domain/iampolicy/grant-path.js";
77
+ import { newOrganizationOnlyGrantScope } from "../domain/iampolicy/grant-scope.js";
78
+ import { newMembershipRules } from "../domain/iampolicy/membership.js";
79
+ import { newResourceIamPolicyStore } from "../domain/iampolicy/resource-store.js";
80
+ import { newBuiltInRoleLifecycle } from "../domain/iampolicy/role-lifecycle.js";
75
81
  import { newIssuerDiscovery } from "../identity/oidc-discovery.js";
76
82
  import { newOidcUserInfoClient } from "../identity/oidc-userinfo.js";
77
83
  import { newApiKeyIdentityVerifier } from "../domain/apikey/verifier.js";
@@ -285,13 +291,13 @@ export async function composeServer(
285
291
  // position 1 of every chain calls it (O2).
286
292
  const authorizer =
287
293
  extensions.authorizer ?? newPermissiveSingleTeamAuthorizer();
288
- // The C2 tuple-lifecycle driver (ruling Q2): undefined = the three
289
- // shared tuple steps (CreateAuthorizationTuples / CleanupIamPolicies /
290
- // UpdateVisibilityTuples) no-op — OSS behavior byte-identical. Handed
291
- // to every resource controller as an explicit dependency, the same
292
- // threading as the authorizer.
293
- const authorizationLifecycle =
294
- extensions.drivers.resourceAuthorizationLifecycle;
294
+ // The built-in authorization POSTURE (20260913.01, Q-OR-6), named once:
295
+ // no unit registered an Authorizer, so the roles open source records
296
+ // exist to feed the built-in one. Under it — and only under it — the
297
+ // identity-accounts stage below installs the built-in role lifecycle
298
+ // and the membership rules; a composition with its own Authorizer has
299
+ // its own onboarding and gets neither.
300
+ const builtInAuthorization = extensions.authorizer === undefined;
295
301
 
296
302
  // Stage: storage — the driver selection seam (DD-010): DATABASE_URL
297
303
  // present → Postgres (async connect + advisory-locked migrations), else
@@ -338,11 +344,67 @@ export async function composeServer(
338
344
  const identityAccounts =
339
345
  extensions.drivers.identityAccountStore ??
340
346
  newResourceIdentityAccountStore(store);
347
+ // The IamPolicy row half (20260913.01): the store PORT bound the same way
348
+ // (`drivers.iamPolicyStore`, Q-OR-10; open source's adapter over `store`
349
+ // when absent) and the domain's ONE grant and revoke path built over it.
350
+ // The grant path's lifecycle is the composed DRIVER — the cloud's tuple
351
+ // hooks — never the built-in lifecycle below, which sits ON TOP of the
352
+ // grant path; that direction keeps the two acyclic (S1 finding B). Both
353
+ // bindings are what the IamPolicy controller registers over (routes).
354
+ const iamPolicies =
355
+ extensions.drivers.iamPolicyStore ?? newResourceIamPolicyStore(store);
356
+ const iamPolicyGrantPath = newIamPolicyGrantPath({
357
+ policies: iamPolicies,
358
+ lifecycle: extensions.drivers.resourceAuthorizationLifecycle,
359
+ logger,
360
+ });
361
+ // The C2 tuple-lifecycle driver (ruling Q2): undefined = the three
362
+ // shared tuple steps (CreateAuthorizationTuples / CleanupIamPolicies /
363
+ // UpdateVisibilityTuples) no-op before any resolution work — OSS
364
+ // behavior byte-identical. Handed to every resource controller as an
365
+ // explicit dependency, the same threading as the authorizer.
366
+ const authorizationLifecycle =
367
+ extensions.drivers.resourceAuthorizationLifecycle;
368
+ // The lifecycle the ORGANIZATION and IDENTITY-ACCOUNT controllers get
369
+ // (20260913.01 slice 4, Q-S4-8): the composed driver when a unit
370
+ // registers one; otherwise, under the built-in posture, open source's
371
+ // role lifecycle — the organization creator's `owner` row, cleanup on
372
+ // organization and account deletes — the row half of the seam that
373
+ // records authorization at lifecycle points in every edition
374
+ // (extensions/resource-authorization.ts, corrected 2026-09-13). Those
375
+ // two kinds ONLY, because a lifecycle on a controller turns on the
376
+ // steps' full event RESOLUTION for that kind, and resolution fails a
377
+ // request whose configured parent id is missing (Java parity the
378
+ // cloud relies on) — a state open source admits by contract (a memory
379
+ // captured with no credential has no subject). The built-in object is
380
+ // a two-kind role recorder, so only two controllers see it; every other
381
+ // controller keeps the composed driver above and stays byte-identical.
382
+ const roleLifecycle =
383
+ authorizationLifecycle ??
384
+ (builtInAuthorization
385
+ ? newBuiltInRoleLifecycle({
386
+ grantPath: iamPolicyGrantPath,
387
+ accounts: identityAccounts,
388
+ })
389
+ : undefined);
390
+ // The operator seam is read once here for both its consumers below: the
391
+ // trusted-local account ensure and the membership rules' operator-email
392
+ // arm (which also applies under an authentication posture — the
393
+ // operator who signs in through OIDC is admin of what the laptop has).
394
+ const operatorIdentity = operatorIdentitySnapshot();
395
+ const membership = builtInAuthorization
396
+ ? newMembershipRules({
397
+ grantPath: iamPolicyGrantPath,
398
+ policies: iamPolicies,
399
+ store,
400
+ operatorEmail: operatorIdentity.email,
401
+ })
402
+ : undefined;
341
403
  const identityAccountPath = {
342
404
  accounts: identityAccounts,
343
405
  logger,
344
406
  authorizer,
345
- authorizationLifecycle,
407
+ authorizationLifecycle: roleLifecycle,
346
408
  };
347
409
  const createIdentityAccount = newCreateAccountPath(
348
410
  identityAccountPath,
@@ -377,11 +439,22 @@ export async function composeServer(
377
439
  if (!requireAuthentication) {
378
440
  const operator = await ensureOperatorAccount(
379
441
  { accounts: identityAccounts, createAccount: createIdentityAccount },
380
- operatorIdentitySnapshot(),
442
+ operatorIdentity,
381
443
  );
382
444
  logger.info("trusted-local operator account ensured", {
383
445
  accountId: operator.metadata?.id ?? "",
384
446
  });
447
+ // The laptop's one principal owns what the laptop has (Q-OR-6c):
448
+ // `owner` on every organization with no owner row, create-if-absent,
449
+ // so the Members page tells the truth from the first boot after roles
450
+ // exist. Under the built-in posture only — a unit with its own
451
+ // Authorizer and no authentication posture keeps its own rules.
452
+ if (membership !== undefined) {
453
+ await membership.ensureOperatorOwnership(operator);
454
+ logger.info("trusted-local operator ownership ensured", {
455
+ accountId: operator.metadata?.id ?? "",
456
+ });
457
+ }
385
458
  }
386
459
 
387
460
  // Stage: keys — the ratified fail-loud boot ASYMMETRY (D2 cross-domain
@@ -899,7 +972,9 @@ export async function composeServer(
899
972
  logger,
900
973
  authorizer,
901
974
  gateSteps: extensions.gateSteps,
902
- authorizationLifecycle,
975
+ // The role lifecycle (the identity stage): the composed driver, or
976
+ // open source's owner-row writer under the built-in posture.
977
+ authorizationLifecycle: roleLifecycle,
903
978
  organizationDirectory: extensions.drivers.organizationDirectory,
904
979
  });
905
980
  // ApiKey is the first domain born AFTER the Go port (O3, 20260827.06 —
@@ -919,11 +994,33 @@ export async function composeServer(
919
994
  // composition adds what differs per edition through the registry —
920
995
  // the federation capability (the four federated RPC arms) and the
921
996
  // provision slot's gate steps (the cloud's personal organization).
997
+ // The membership rules are core, selected by posture (20260913.01).
922
998
  registerIdentityAccountServices(router, {
923
999
  ...identityAccountPath,
924
1000
  provisioner: identityAccountProvisioner,
925
1001
  federation: extensions.drivers.identityFederation,
926
1002
  gateSteps: extensions.gateSteps,
1003
+ membership,
1004
+ });
1005
+ // IamPolicy (20260913.01): the ROW half served once by open source in
1006
+ // every edition over the store port and the grant path bound in the
1007
+ // identity stage. A composition adds what differs per edition through
1008
+ // the registry — a wider grant scope (`drivers.policyGrantScope`; the
1009
+ // default is open source's organization-only scope, installed at this
1010
+ // `??` site like the permissive Authorizer) and the tuple-half query
1011
+ // engine (`drivers.authorizationQueries`; absent, those RPCs refuse
1012
+ // UNIMPLEMENTED). The chains take no lifecycle: a policy is the
1013
+ // authorization record, not a protected resource.
1014
+ registerIamPolicyServices(router, {
1015
+ grantPath: iamPolicyGrantPath,
1016
+ policies: iamPolicies,
1017
+ accounts: identityAccounts,
1018
+ authorizer,
1019
+ grantScope:
1020
+ extensions.drivers.policyGrantScope ?? newOrganizationOnlyGrantScope(),
1021
+ queries: extensions.drivers.authorizationQueries,
1022
+ edition: extensions.edition,
1023
+ logger,
927
1024
  });
928
1025
  registerEnvironmentServices(router, {
929
1026
  store,
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Pins access-lists.ts (20260913.01 slice 5): the row-driven answer to
3
+ * "who has access, with which roles" — the hierarchy walk over scope
4
+ * tuples (child → parent through the resource's one structural link, at
5
+ * most five steps, `platform` terminal) and the grouping of assignable-
6
+ * role rows by principal in first-seen order, enriched where the display
7
+ * resolver answers and rendered as kind/id with the id as its name
8
+ * everywhere else (the Java PrincipalEnricher fallback). Moved from the
9
+ * cloud's iam/policy/access-lists.ts; the arms below are the Java
10
+ * contract read from that file.
11
+ */
12
+ import { create } from "@bufbuild/protobuf";
13
+ import { describe, expect, it } from "vitest";
14
+
15
+ import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
16
+ import { ApiResourceRefViewSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
17
+ import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
18
+
19
+ import { buildPrincipalAccessList, resolveHierarchy } from "../access-lists.js";
20
+ import type { PrincipalDisplayResolver } from "../access-lists.js";
21
+ import { policyIdFor } from "../constants.js";
22
+ import { fakeIamPolicyStore, orgRole, triple } from "./support.js";
23
+
24
+ function row(spec: IamPolicySpec) {
25
+ return create(IamPolicySchema, {
26
+ metadata: { id: policyIdFor(spec) },
27
+ spec,
28
+ });
29
+ }
30
+
31
+ /** `child` is scoped under `parent`: the structural link the walk follows. */
32
+ function scopeLink(
33
+ child: { kind: string; id: string },
34
+ parent: { kind: string; id: string },
35
+ ): IamPolicySpec {
36
+ return triple(parent, parent.kind, child);
37
+ }
38
+
39
+ const nobody: PrincipalDisplayResolver = {
40
+ resolveIdentityAccounts: () => Promise.resolve(new Map()),
41
+ };
42
+
43
+ describe("resolveHierarchy", () => {
44
+ it("is the resource alone when inherited grants are not asked for", async () => {
45
+ const store = fakeIamPolicyStore();
46
+ await store.save(
47
+ row(
48
+ scopeLink(
49
+ { kind: "agent", id: "a1" },
50
+ { kind: "organization", id: "acme" },
51
+ ),
52
+ ),
53
+ );
54
+ expect(await resolveHierarchy(store, "agent", "a1", false)).toEqual([
55
+ { kind: "agent", id: "a1" },
56
+ ]);
57
+ });
58
+
59
+ it("climbs the scope links child-first and stops where no link exists", async () => {
60
+ const store = fakeIamPolicyStore();
61
+ await store.save(
62
+ row(
63
+ scopeLink(
64
+ { kind: "agent", id: "a1" },
65
+ { kind: "organization", id: "acme" },
66
+ ),
67
+ ),
68
+ );
69
+ expect(await resolveHierarchy(store, "agent", "a1", true)).toEqual([
70
+ { kind: "agent", id: "a1" },
71
+ { kind: "organization", id: "acme" },
72
+ ]);
73
+ });
74
+
75
+ it("stops at platform, the terminal kind, even when platform carries a link", async () => {
76
+ const store = fakeIamPolicyStore();
77
+ await store.save(
78
+ row(
79
+ scopeLink(
80
+ { kind: "organization", id: "acme" },
81
+ { kind: "platform", id: "stigmer" },
82
+ ),
83
+ ),
84
+ );
85
+ await store.save(
86
+ row(
87
+ scopeLink(
88
+ { kind: "platform", id: "stigmer" },
89
+ { kind: "organization", id: "loop" },
90
+ ),
91
+ ),
92
+ );
93
+ expect(await resolveHierarchy(store, "organization", "acme", true)).toEqual(
94
+ [
95
+ { kind: "organization", id: "acme" },
96
+ { kind: "platform", id: "stigmer" },
97
+ ],
98
+ );
99
+ });
100
+
101
+ it("walks at most five steps — a cycle cannot spin it", async () => {
102
+ const store = fakeIamPolicyStore();
103
+ await store.save(
104
+ row(scopeLink({ kind: "agent", id: "a" }, { kind: "agent", id: "b" })),
105
+ );
106
+ await store.save(
107
+ row(scopeLink({ kind: "agent", id: "b" }, { kind: "agent", id: "a" })),
108
+ );
109
+ const levels = await resolveHierarchy(store, "agent", "a", true);
110
+ expect(levels).toHaveLength(6);
111
+ });
112
+
113
+ it("ignores rows whose principal is a person or whose relation is owner/creator — those are grants, not links", async () => {
114
+ const store = fakeIamPolicyStore();
115
+ await store.save(row(orgRole("ida_alice", "owner", "acme")));
116
+ await store.save(
117
+ row(
118
+ triple({ kind: "organization", id: "parent" }, "owner", {
119
+ kind: "organization",
120
+ id: "acme",
121
+ }),
122
+ ),
123
+ );
124
+ expect(await resolveHierarchy(store, "organization", "acme", true)).toEqual(
125
+ [{ kind: "organization", id: "acme" }],
126
+ );
127
+ });
128
+ });
129
+
130
+ describe("buildPrincipalAccessList", () => {
131
+ it("answers nothing for a resource with no assignable-role rows — structural rows never show", async () => {
132
+ const store = fakeIamPolicyStore();
133
+ await store.save(
134
+ row(
135
+ scopeLink(
136
+ { kind: "agent", id: "a1" },
137
+ { kind: "organization", id: "acme" },
138
+ ),
139
+ ),
140
+ );
141
+ expect(
142
+ await buildPrincipalAccessList(store, nobody, [
143
+ { kind: "agent", id: "a1" },
144
+ ]),
145
+ ).toEqual([]);
146
+ });
147
+
148
+ it("groups by principal in first-seen order, one RoleGrant per row, direct grants without an owner resource", async () => {
149
+ const store = fakeIamPolicyStore();
150
+ await store.save(row(orgRole("ida_bob", "member", "acme")));
151
+ await store.save(row(orgRole("ida_alice", "owner", "acme")));
152
+ await store.save(row(orgRole("ida_bob", "viewer", "acme")));
153
+ const entries = await buildPrincipalAccessList(store, nobody, [
154
+ { kind: "organization", id: "acme" },
155
+ ]);
156
+ expect(entries.map((e) => e.principal?.id)).toEqual([
157
+ "ida_bob",
158
+ "ida_alice",
159
+ ]);
160
+ const bob = entries[0];
161
+ expect(bob?.roles.map((g) => g.role?.code)).toEqual(["member", "viewer"]);
162
+ expect(
163
+ bob?.roles.every((g) => !g.isInherited && g.ownerResource === undefined),
164
+ ).toBe(true);
165
+ });
166
+
167
+ it("marks grants from a parent level as inherited and names the owning level", async () => {
168
+ const store = fakeIamPolicyStore();
169
+ await store.save(row(orgRole("ida_alice", "admin", "acme")));
170
+ const entries = await buildPrincipalAccessList(store, nobody, [
171
+ { kind: "agent", id: "a1" },
172
+ { kind: "organization", id: "acme" },
173
+ ]);
174
+ expect(entries).toHaveLength(1);
175
+ const grant = entries[0]?.roles[0];
176
+ expect(grant?.isInherited).toBe(true);
177
+ expect(grant?.ownerResource?.kind).toBe("organization");
178
+ expect(grant?.ownerResource?.id).toBe("acme");
179
+ expect(grant?.role?.code).toBe("admin");
180
+ });
181
+
182
+ it("renders an unresolved principal of ANY kind as kind/id with the id as its name", async () => {
183
+ const store = fakeIamPolicyStore();
184
+ await store.save(row(orgRole("ida_ghost", "viewer", "acme")));
185
+ await store.save(
186
+ row(
187
+ triple({ kind: "team", id: "tm_1" }, "viewer", {
188
+ kind: "organization",
189
+ id: "acme",
190
+ }),
191
+ ),
192
+ );
193
+ const entries = await buildPrincipalAccessList(store, nobody, [
194
+ { kind: "organization", id: "acme" },
195
+ ]);
196
+ expect(
197
+ entries.map((e) => [
198
+ e.principal?.kind,
199
+ e.principal?.id,
200
+ e.principal?.name,
201
+ ]),
202
+ ).toEqual([
203
+ ["identity_account", "ida_ghost", "ida_ghost"],
204
+ ["team", "tm_1", "tm_1"],
205
+ ]);
206
+ });
207
+
208
+ it("asks the resolver once for the identity-account ids and uses its view where it answers", async () => {
209
+ const store = fakeIamPolicyStore();
210
+ await store.save(row(orgRole("ida_alice", "owner", "acme")));
211
+ await store.save(row(orgRole("ida_bob", "member", "acme")));
212
+ const asked: string[][] = [];
213
+ const resolver: PrincipalDisplayResolver = {
214
+ resolveIdentityAccounts: (ids) => {
215
+ asked.push([...ids]);
216
+ return Promise.resolve(
217
+ new Map([
218
+ [
219
+ "ida_alice",
220
+ create(ApiResourceRefViewSchema, {
221
+ kind: "identity_account",
222
+ id: "ida_alice",
223
+ name: "Alice",
224
+ email: "alice@example.com",
225
+ }),
226
+ ],
227
+ ]),
228
+ );
229
+ },
230
+ };
231
+ const entries = await buildPrincipalAccessList(store, resolver, [
232
+ { kind: "organization", id: "acme" },
233
+ ]);
234
+ expect(asked).toEqual([["ida_alice", "ida_bob"]]);
235
+ expect(entries.map((e) => e.principal?.name)).toEqual(["Alice", "ida_bob"]);
236
+ expect(entries[0]?.principal?.email).toBe("alice@example.com");
237
+ });
238
+ });