@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,95 @@
1
+ /**
2
+ * IAM role metadata (20260913.01 slice 5): display metadata for the five
3
+ * assignable roles and the assignable-relation allowlist that keeps
4
+ * structural relations (parent links, runtime grants, observability
5
+ * usersets) out of every access listing BY CONSTRUCTION — reads filter to
6
+ * this set rather than maintaining a denylist a new structural relation
7
+ * could slip past. Moved as-is from the cloud's iam/policy/roles.ts, itself
8
+ * the port of Java's apishape/authorization/IamRoleMetadata; the cloud's
9
+ * `grantableRolesFor` did not come along — it lives in
10
+ * pipeline/apiresource-meta.ts as the contract's one read of
11
+ * `kind_meta.grantable_roles`, and the cloud registers its own reading of
12
+ * it as the `policyGrantScope` driver.
13
+ *
14
+ * The four words this table renders for an organization are the four
15
+ * roles every edition grants there (Q-OR-4) and the ladder entry 3's
16
+ * authorizer enforces; the copy is shown verbatim by the console's role
17
+ * badges and selector, so it is contract.
18
+ */
19
+ import { create } from "@bufbuild/protobuf";
20
+
21
+ import type { RoleInfo } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
22
+ import { RoleInfoSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
23
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
24
+
25
+ /** Display metadata for one assignable role (the Java ROLE_INFO_MAP copy). */
26
+ interface RoleDisplay {
27
+ readonly role: IamRole;
28
+ readonly name: string;
29
+ readonly description: string;
30
+ }
31
+
32
+ const ROLE_DISPLAY: ReadonlyArray<RoleDisplay> = [
33
+ {
34
+ role: IamRole.owner,
35
+ name: "Owner",
36
+ description: "Full control of the resource including ownership transfer",
37
+ },
38
+ {
39
+ role: IamRole.admin,
40
+ name: "Administrator",
41
+ description:
42
+ "Administrative access with most permissions except ownership transfer",
43
+ },
44
+ {
45
+ role: IamRole.member,
46
+ name: "Member",
47
+ description: "Read and limited write access to the resource",
48
+ },
49
+ {
50
+ role: IamRole.viewer,
51
+ name: "Viewer",
52
+ description: "Read-only access to the resource",
53
+ },
54
+ // Grantable on agent_channel only (channel-conversations DD-010);
55
+ // omitting it would let the grant succeed while hiding the participant
56
+ // from every access listing (the Java IamRoleMetadataTest lesson).
57
+ {
58
+ role: IamRole.participant,
59
+ name: "Participant",
60
+ description:
61
+ "Reply to customers and manage conversation takeover on a channel",
62
+ },
63
+ ];
64
+
65
+ const RELATION_TO_DISPLAY: ReadonlyMap<string, RoleDisplay> = new Map(
66
+ ROLE_DISPLAY.map((entry) => [IamRole[entry.role], entry]),
67
+ );
68
+
69
+ /** The allowlist for access-listing queries (Java assignableRelations). */
70
+ export function assignableRelations(): ReadonlyArray<string> {
71
+ return [...RELATION_TO_DISPLAY.keys()];
72
+ }
73
+
74
+ /** True when the relation names an assignable IAM role, never structural. */
75
+ export function isAssignableRole(relation: string): boolean {
76
+ return RELATION_TO_DISPLAY.has(relation);
77
+ }
78
+
79
+ /**
80
+ * Relation string → RoleInfo with display metadata; the default instance
81
+ * for unknown relations (the Java fromRelationString contract).
82
+ */
83
+ export function roleInfoFromRelation(relation: string): RoleInfo {
84
+ const display = RELATION_TO_DISPLAY.get(relation);
85
+ if (display === undefined) {
86
+ return create(RoleInfoSchema);
87
+ }
88
+ const code = IamRole[display.role];
89
+ return create(RoleInfoSchema, {
90
+ id: code,
91
+ code,
92
+ name: display.name,
93
+ description: display.description,
94
+ });
95
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The IamPolicy specs server code builds ITSELF, spelled once
3
+ * (20260913.01 slice 4). A wire caller hands the domain a spec; the
4
+ * built-in role lifecycle, the membership rules and slice 5's
5
+ * `revokeOrgAccess` and contextual `checkMyPermission` build their own.
6
+ * The derived policy id hashes the spec's exact text (constants.ts
7
+ * `policyIdFor`), so two spellings of one triple would be two rows — this
8
+ * module is where "identity_account:<id> holds <role> on
9
+ * organization:<org>" has one spelling in production code, the way the
10
+ * test support's `orgRole` is that spelling for tests.
11
+ *
12
+ * Kinds are spelled through `kindEnumName` (the ApiResourceRef vocabulary,
13
+ * pipeline/apiresource-meta.ts); relations are the `IamRole` member names,
14
+ * which are the FGA model's relation names and the proto's grantable-role
15
+ * vocabulary. The zero value `iam_role_unspecified` is not a relation and
16
+ * is refused (the `accountIdFor("")` precedent): hashing it would mint a
17
+ * real row for a role nobody holds.
18
+ */
19
+ import { create } from "@bufbuild/protobuf";
20
+
21
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
22
+ import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
23
+ import { IamPolicySpecSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
24
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
25
+
26
+ import { kindEnumName } from "../../pipeline/apiresource-meta.js";
27
+
28
+ /** The relation string a role grants — the enum member's own name. */
29
+ export function relationOf(role: IamRole): string {
30
+ if (role === IamRole.iam_role_unspecified) {
31
+ throw new Error("iam_role_unspecified is not a relation");
32
+ }
33
+ return IamRole[role];
34
+ }
35
+
36
+ /** `identity_account:<accountId>` holds `<role>` on `organization:<organizationId>` — the organization role. */
37
+ export function organizationRole(
38
+ accountId: string,
39
+ role: IamRole,
40
+ organizationId: string,
41
+ ): IamPolicySpec {
42
+ return create(IamPolicySpecSchema, {
43
+ principal: {
44
+ kind: kindEnumName(ApiResourceKind.identity_account),
45
+ id: accountId,
46
+ },
47
+ relation: relationOf(role),
48
+ resource: {
49
+ kind: kindEnumName(ApiResourceKind.organization),
50
+ id: organizationId,
51
+ },
52
+ });
53
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * IamPolicy pipeline steps (20260913.01 slice 5; Q-S5-1): the domain-local
3
+ * steps the six command chains splice after the shared `Authorize` and
4
+ * `ValidateProto`. The write itself is never here — every step that writes
5
+ * calls the ONE grant and revoke path (grant-path.ts, which owns the
6
+ * cloud#425 orderings, the by-triple read and the wire refusals) and leaves
7
+ * its answer under POLICY_RESULT_KEY for the handler. The chains are
8
+ * deliberately short: `Authorize → ValidateProto → <one of these>`, so the
9
+ * pipeline executor's fault mapping (a step's ConnectError keeps its code;
10
+ * a raw store fault becomes the sanitized INTERNAL) covers the path's two
11
+ * error shapes with no second idiom in the handlers.
12
+ *
13
+ * ValidateGrantableRole (the `create` chain only; `bootstrapPolicy` is the
14
+ * structural lane and skips it) is the one step with branching logic. It
15
+ * runs BEFORE Grant — the cloud's validate-before-write order, so a held
16
+ * legacy row under a role the kind does not grant is refused rather than
17
+ * answered as a duplicate — and reads, in this order (Q-OR-3 as refined;
18
+ * Q-S3-3):
19
+ * 1. the wire refusals: an unknown resource kind is `Unknown resource
20
+ * kind`, never a role sentence — the second line behind the
21
+ * controller's pre-position-1 refusal (Q-S6-1), kept so the step's
22
+ * own contract holds for whoever splices it;
23
+ * 2. the PROTO (`grantableRolesFor`): a kind that lists no role is
24
+ * system-managed in every edition — the cloud's byte-pinned copy,
25
+ * whatever the composed scope claims;
26
+ * 3. the composed SCOPE: a kind it lists nothing for is a per-resource
27
+ * grant this edition does not serve — UNIMPLEMENTED naming the
28
+ * editions that do;
29
+ * 4. proto ∩ scope, in proto order: the role must be in the
30
+ * intersection — the cloud's second copy listing it. Intersecting is
31
+ * what keeps a scope from WIDENING the contract by accident;
32
+ * 5. the PRINCIPAL (Q-S9-2, 2026-09-14; constants.ts
33
+ * USER_GRANT_PRINCIPAL_KINDS): a person grants roles to people — the
34
+ * identity account — and to nothing else. A row whose principal is a
35
+ * resource is a structural link (the hierarchy walk's parent edge),
36
+ * which is `bootstrapPolicy`'s to write; through this lane it would
37
+ * let a right on one organization read another's members. Last, so
38
+ * every answer the cloud's create gives today is unchanged and only
39
+ * the row it should never have written is refused.
40
+ */
41
+ import { create } from "@bufbuild/protobuf";
42
+ import { Code, ConnectError } from "@connectrpc/connect";
43
+
44
+ import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
45
+ import type { RevokeOrgAccessInputSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
46
+ import type {
47
+ ApiResourceRef,
48
+ ApiResourceRefSchema,
49
+ IamPolicySpecSchema,
50
+ } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
51
+ import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
52
+
53
+ import type { PolicyGrantScope } from "../../extensions/policy-grant-scope.js";
54
+ import { grantableRolesFor } from "../../pipeline/apiresource-meta.js";
55
+ import type { PipelineStep } from "../../pipeline/pipeline.js";
56
+ import type { RequestContext } from "../../pipeline/request-context.js";
57
+ import {
58
+ PER_RESOURCE_GRANTS_UNIMPLEMENTED_MESSAGE,
59
+ USER_GRANT_PRINCIPAL_KINDS,
60
+ noGrantableRolesMessage,
61
+ principalNotGrantableMessage,
62
+ roleNotGrantableMessage,
63
+ } from "./constants.js";
64
+ import type { IamPolicyGrantPath } from "./grant-path.js";
65
+ import {
66
+ requireKnownPrincipalKind,
67
+ requireKnownResourceKind,
68
+ } from "./wire-refusals.js";
69
+
70
+ /** The row a write chain answers — the granted row, or the revoked row (the default instance when there was none). */
71
+ export const POLICY_RESULT_KEY = "policyResult";
72
+
73
+ /** The `create` chain's role check; see the module header for the four arms. */
74
+ export function newValidateGrantableRoleStep(
75
+ scope: PolicyGrantScope,
76
+ ): PipelineStep<typeof IamPolicySpecSchema> {
77
+ return {
78
+ name: "ValidateGrantableRole",
79
+ execute(ctx: RequestContext<typeof IamPolicySpecSchema>): void {
80
+ const spec = ctx.input;
81
+ const kindName = spec.resource?.kind ?? "";
82
+ const kind = requireKnownResourceKind(kindName);
83
+ const contract = grantableRolesFor(kind);
84
+ if (contract.length === 0) {
85
+ throw new ConnectError(
86
+ noGrantableRolesMessage(kindName),
87
+ Code.InvalidArgument,
88
+ );
89
+ }
90
+ const scoped = new Set(scope.grantableRoles(kind));
91
+ if (scoped.size === 0) {
92
+ throw new ConnectError(
93
+ PER_RESOURCE_GRANTS_UNIMPLEMENTED_MESSAGE,
94
+ Code.Unimplemented,
95
+ );
96
+ }
97
+ const grantable = contract.filter((role) => scoped.has(role));
98
+ if (!grantable.some((role) => IamRole[role] === spec.relation)) {
99
+ throw new ConnectError(
100
+ roleNotGrantableMessage(
101
+ spec.relation,
102
+ kindName,
103
+ grantable.map((role) => IamRole[role]),
104
+ ),
105
+ Code.InvalidArgument,
106
+ );
107
+ }
108
+ const principalKindName = spec.principal?.kind ?? "";
109
+ const principalKind = requireKnownPrincipalKind(principalKindName);
110
+ if (!USER_GRANT_PRINCIPAL_KINDS.includes(principalKind)) {
111
+ throw new ConnectError(
112
+ principalNotGrantableMessage(principalKindName),
113
+ Code.InvalidArgument,
114
+ );
115
+ }
116
+ },
117
+ };
118
+ }
119
+
120
+ /** Grant the spec as the chain's caller; the row (fresh or held) is the result. */
121
+ export function newGrantStep(
122
+ grantPath: IamPolicyGrantPath,
123
+ ): PipelineStep<typeof IamPolicySpecSchema> {
124
+ return {
125
+ name: "Grant",
126
+ async execute(
127
+ ctx: RequestContext<typeof IamPolicySpecSchema>,
128
+ ): Promise<void> {
129
+ const { policy } = await grantPath.grant(ctx.input, ctx.callerIdentity);
130
+ ctx.set(POLICY_RESULT_KEY, policy);
131
+ },
132
+ };
133
+ }
134
+
135
+ /** Revoke the spec's row; absent, the default instance is the result (Java's idempotent delete). */
136
+ export function newRevokeStep(
137
+ grantPath: IamPolicyGrantPath,
138
+ ): PipelineStep<typeof IamPolicySpecSchema> {
139
+ return {
140
+ name: "Revoke",
141
+ async execute(
142
+ ctx: RequestContext<typeof IamPolicySpecSchema>,
143
+ ): Promise<void> {
144
+ const revoked = await grantPath.revokeBySpec(ctx.input);
145
+ ctx.set(POLICY_RESULT_KEY, revoked ?? create(IamPolicySchema));
146
+ },
147
+ };
148
+ }
149
+
150
+ /** Revoke every relation the account holds directly on the organization. */
151
+ export function newRevokeOrgAccessStep(
152
+ grantPath: IamPolicyGrantPath,
153
+ ): PipelineStep<typeof RevokeOrgAccessInputSchema> {
154
+ return {
155
+ name: "RevokeOrgAccess",
156
+ execute(
157
+ ctx: RequestContext<typeof RevokeOrgAccessInputSchema>,
158
+ ): Promise<void> {
159
+ return grantPath.revokeOrgAccess(
160
+ ctx.input.identityAccountId,
161
+ ctx.input.organizationId,
162
+ );
163
+ },
164
+ };
165
+ }
166
+
167
+ /** Revoke every row naming the ref as resource or principal — the deleted resource's cleanup. */
168
+ export function newCleanupResourceStep(
169
+ grantPath: IamPolicyGrantPath,
170
+ ): PipelineStep<typeof ApiResourceRefSchema> {
171
+ return {
172
+ name: "CleanupResource",
173
+ execute(ctx: RequestContext<typeof ApiResourceRefSchema>): Promise<void> {
174
+ const ref: ApiResourceRef = ctx.input;
175
+ return grantPath.cleanupResource(ref);
176
+ },
177
+ };
178
+ }