@stigmer/server 3.15.2 → 3.15.3-dev.20260914100347
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.
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +103 -9
- package/dist/boot/compose.js.map +1 -1
- package/dist/domain/iampolicy/access-lists.d.ts +27 -0
- package/dist/domain/iampolicy/access-lists.d.ts.map +1 -0
- package/dist/domain/iampolicy/access-lists.js +124 -0
- package/dist/domain/iampolicy/access-lists.js.map +1 -0
- package/dist/domain/iampolicy/constants.d.ts +135 -0
- package/dist/domain/iampolicy/constants.d.ts.map +1 -0
- package/dist/domain/iampolicy/constants.js +203 -0
- package/dist/domain/iampolicy/constants.js.map +1 -0
- package/dist/domain/iampolicy/controller.d.ts +29 -0
- package/dist/domain/iampolicy/controller.d.ts.map +1 -0
- package/dist/domain/iampolicy/controller.js +451 -0
- package/dist/domain/iampolicy/controller.js.map +1 -0
- package/dist/domain/iampolicy/display-resolver.d.ts +6 -0
- package/dist/domain/iampolicy/display-resolver.d.ts.map +1 -0
- package/dist/domain/iampolicy/display-resolver.js +90 -0
- package/dist/domain/iampolicy/display-resolver.js.map +1 -0
- package/dist/domain/iampolicy/grant-path.d.ts +42 -0
- package/dist/domain/iampolicy/grant-path.d.ts.map +1 -0
- package/dist/domain/iampolicy/grant-path.js +183 -0
- package/dist/domain/iampolicy/grant-path.js.map +1 -0
- package/dist/domain/iampolicy/grant-scope.d.ts +4 -0
- package/dist/domain/iampolicy/grant-scope.d.ts.map +1 -0
- package/dist/domain/iampolicy/grant-scope.js +34 -0
- package/dist/domain/iampolicy/grant-scope.js.map +1 -0
- package/dist/domain/iampolicy/membership.d.ts +111 -0
- package/dist/domain/iampolicy/membership.d.ts.map +1 -0
- package/dist/domain/iampolicy/membership.js +152 -0
- package/dist/domain/iampolicy/membership.js.map +1 -0
- package/dist/domain/iampolicy/permissions.d.ts +19 -0
- package/dist/domain/iampolicy/permissions.d.ts.map +1 -0
- package/dist/domain/iampolicy/permissions.js +27 -0
- package/dist/domain/iampolicy/permissions.js.map +1 -0
- package/dist/domain/iampolicy/resource-store.d.ts +4 -0
- package/dist/domain/iampolicy/resource-store.d.ts.map +1 -0
- package/dist/domain/iampolicy/resource-store.js +135 -0
- package/dist/domain/iampolicy/resource-store.js.map +1 -0
- package/dist/domain/iampolicy/role-lifecycle.d.ts +9 -0
- package/dist/domain/iampolicy/role-lifecycle.d.ts.map +1 -0
- package/dist/domain/iampolicy/role-lifecycle.js +106 -0
- package/dist/domain/iampolicy/role-lifecycle.js.map +1 -0
- package/dist/domain/iampolicy/roles.d.ts +11 -0
- package/dist/domain/iampolicy/roles.d.ts.map +1 -0
- package/dist/domain/iampolicy/roles.js +78 -0
- package/dist/domain/iampolicy/roles.js.map +1 -0
- package/dist/domain/iampolicy/specs.d.ts +7 -0
- package/dist/domain/iampolicy/specs.d.ts.map +1 -0
- package/dist/domain/iampolicy/specs.js +45 -0
- package/dist/domain/iampolicy/specs.js.map +1 -0
- package/dist/domain/iampolicy/steps.d.ts +18 -0
- package/dist/domain/iampolicy/steps.d.ts.map +1 -0
- package/dist/domain/iampolicy/steps.js +117 -0
- package/dist/domain/iampolicy/steps.js.map +1 -0
- package/dist/domain/iampolicy/store-contract.d.ts +8 -0
- package/dist/domain/iampolicy/store-contract.d.ts.map +1 -0
- package/dist/domain/iampolicy/store-contract.js +258 -0
- package/dist/domain/iampolicy/store-contract.js.map +1 -0
- package/dist/domain/iampolicy/store.d.ts +78 -0
- package/dist/domain/iampolicy/store.d.ts.map +1 -0
- package/dist/domain/iampolicy/store.js +13 -0
- package/dist/domain/iampolicy/store.js.map +1 -0
- package/dist/domain/iampolicy/wire-refusals.d.ts +25 -0
- package/dist/domain/iampolicy/wire-refusals.d.ts.map +1 -0
- package/dist/domain/iampolicy/wire-refusals.js +76 -0
- package/dist/domain/iampolicy/wire-refusals.js.map +1 -0
- package/dist/domain/identityaccount/constants.d.ts +33 -0
- package/dist/domain/identityaccount/constants.d.ts.map +1 -1
- package/dist/domain/identityaccount/constants.js +2 -48
- package/dist/domain/identityaccount/constants.js.map +1 -1
- package/dist/domain/identityaccount/controller.d.ts +8 -1
- package/dist/domain/identityaccount/controller.d.ts.map +1 -1
- package/dist/domain/identityaccount/controller.js +54 -29
- package/dist/domain/identityaccount/controller.js.map +1 -1
- package/dist/domain/identityaccount/operator.d.ts.map +1 -1
- package/dist/domain/identityaccount/operator.js +2 -4
- package/dist/domain/identityaccount/operator.js.map +1 -1
- package/dist/domain/identityaccount/provisioning.d.ts +20 -1
- package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
- package/dist/domain/identityaccount/provisioning.js +17 -3
- package/dist/domain/identityaccount/provisioning.js.map +1 -1
- package/dist/domain/identityaccount/resolve.d.ts +23 -0
- package/dist/domain/identityaccount/resolve.d.ts.map +1 -1
- package/dist/domain/identityaccount/resolve.js +17 -0
- package/dist/domain/identityaccount/resolve.js.map +1 -1
- package/dist/domain/identityaccount/store-contract.d.ts +4 -18
- package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
- package/dist/domain/identityaccount/store-contract.js +12 -47
- package/dist/domain/identityaccount/store-contract.js.map +1 -1
- package/dist/extensions/authorization-queries.d.ts +81 -0
- package/dist/extensions/authorization-queries.d.ts.map +1 -0
- package/dist/extensions/authorization-queries.js +2 -0
- package/dist/extensions/authorization-queries.js.map +1 -0
- package/dist/extensions/drivers.d.ts +49 -2
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/identity.d.ts +14 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +18 -1
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/policy-grant-scope.d.ts +50 -0
- package/dist/extensions/policy-grant-scope.d.ts.map +1 -0
- package/dist/extensions/policy-grant-scope.js +2 -0
- package/dist/extensions/policy-grant-scope.js.map +1 -0
- package/dist/extensions/registry.d.ts +23 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +30 -0
- package/dist/extensions/registry.js.map +1 -1
- package/dist/extensions/resource-authorization.d.ts +63 -0
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +56 -1
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +155 -1
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/interceptors/auth.d.ts +12 -0
- package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
- package/dist/pipeline/interceptors/auth.js +13 -1
- package/dist/pipeline/interceptors/auth.js.map +1 -1
- package/dist/pipeline/steps/authorize.d.ts +20 -5
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +37 -17
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/defaults.d.ts +10 -0
- package/dist/pipeline/steps/defaults.d.ts.map +1 -1
- package/dist/pipeline/steps/defaults.js +39 -0
- package/dist/pipeline/steps/defaults.js.map +1 -1
- package/dist/store/port-contract.d.ts +61 -0
- package/dist/store/port-contract.d.ts.map +1 -0
- package/dist/store/port-contract.js +67 -0
- package/dist/store/port-contract.js.map +1 -0
- package/package.json +4 -4
- package/src/boot/compose.ts +107 -10
- package/src/domain/iampolicy/__tests__/access-lists.test.ts +238 -0
- package/src/domain/iampolicy/__tests__/constants.test.ts +388 -0
- package/src/domain/iampolicy/__tests__/controller.test.ts +960 -0
- package/src/domain/iampolicy/__tests__/display-resolver.test.ts +138 -0
- package/src/domain/iampolicy/__tests__/grant-path.test.ts +474 -0
- package/src/domain/iampolicy/__tests__/grant-scope.test.ts +61 -0
- package/src/domain/iampolicy/__tests__/iampolicy.test.ts +532 -0
- package/src/domain/iampolicy/__tests__/membership.test.ts +447 -0
- package/src/domain/iampolicy/__tests__/permissions.test.ts +41 -0
- package/src/domain/iampolicy/__tests__/resource-store.test.ts +165 -0
- package/src/domain/iampolicy/__tests__/role-lifecycle.test.ts +361 -0
- package/src/domain/iampolicy/__tests__/roles.test.ts +53 -0
- package/src/domain/iampolicy/__tests__/specs.test.ts +41 -0
- package/src/domain/iampolicy/__tests__/steps.test.ts +278 -0
- package/src/domain/iampolicy/__tests__/support.ts +195 -0
- package/src/domain/iampolicy/__tests__/wire-refusals.test.ts +117 -0
- package/src/domain/iampolicy/access-lists.ts +191 -0
- package/src/domain/iampolicy/constants.ts +243 -0
- package/src/domain/iampolicy/controller.ts +798 -0
- package/src/domain/iampolicy/display-resolver.ts +123 -0
- package/src/domain/iampolicy/grant-path.ts +270 -0
- package/src/domain/iampolicy/grant-scope.ts +36 -0
- package/src/domain/iampolicy/membership.ts +298 -0
- package/src/domain/iampolicy/permissions.ts +35 -0
- package/src/domain/iampolicy/resource-store.ts +186 -0
- package/src/domain/iampolicy/role-lifecycle.ts +132 -0
- package/src/domain/iampolicy/roles.ts +95 -0
- package/src/domain/iampolicy/specs.ts +53 -0
- package/src/domain/iampolicy/steps.ts +178 -0
- package/src/domain/iampolicy/store-contract.ts +450 -0
- package/src/domain/iampolicy/store.ts +103 -0
- package/src/domain/iampolicy/wire-refusals.ts +110 -0
- package/src/domain/identityaccount/__tests__/provisioning.test.ts +19 -7
- package/src/domain/identityaccount/__tests__/resolve.test.ts +113 -2
- package/src/domain/identityaccount/constants.ts +16 -31
- package/src/domain/identityaccount/controller.ts +65 -35
- package/src/domain/identityaccount/operator.ts +5 -5
- package/src/domain/identityaccount/provisioning.ts +43 -5
- package/src/domain/identityaccount/resolve.ts +43 -0
- package/src/domain/identityaccount/store-contract.ts +22 -70
- package/src/extensions/__tests__/composed-support.ts +87 -0
- package/src/extensions/__tests__/iam-policy-composed.test.ts +373 -0
- package/src/extensions/__tests__/iam-policy-points.test.ts +169 -0
- package/src/extensions/__tests__/identity-account-composed.test.ts +12 -78
- package/src/extensions/__tests__/identity.test.ts +62 -0
- package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
- package/src/extensions/authorization-queries.ts +97 -0
- package/src/extensions/drivers.ts +49 -2
- package/src/extensions/identity.ts +21 -0
- package/src/extensions/policy-grant-scope.ts +50 -0
- package/src/extensions/registry.ts +62 -1
- package/src/extensions/resource-authorization.ts +65 -0
- package/src/index.ts +20 -0
- package/src/pipeline/__tests__/grantable-roles-for.test.ts +87 -0
- package/src/pipeline/__tests__/kind-by-enum-name.test.ts +107 -0
- package/src/pipeline/__tests__/kind-served-by-edition.test.ts +107 -0
- package/src/pipeline/apiresource-meta.ts +181 -1
- package/src/pipeline/interceptors/auth.ts +14 -1
- package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +73 -3
- package/src/pipeline/steps/__tests__/authorize.test.ts +134 -9
- package/src/pipeline/steps/__tests__/derived-id.test.ts +49 -0
- package/src/pipeline/steps/authorize.ts +46 -21
- package/src/pipeline/steps/defaults.ts +42 -0
- package/src/store/__tests__/port-contract.test.ts +123 -0
- 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
|
+
}
|