@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.
- 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/list-read-scope.d.ts +41 -4
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +44 -7
- package/dist/extensions/list-read-scope.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 +77 -1
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +183 -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/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +7 -26
- package/dist/pipeline/steps/authorization-tuples.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/pipeline/steps/shapes.d.ts +18 -0
- package/dist/pipeline/steps/shapes.d.ts.map +1 -1
- package/dist/pipeline/steps/shapes.js +29 -0
- package/dist/pipeline/steps/shapes.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__/list-read-scope.test.ts +138 -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/list-read-scope.ts +87 -9
- 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__/inherited-authorization-parent.test.ts +51 -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 +218 -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/authorization-tuples.ts +7 -28
- package/src/pipeline/steps/authorize.ts +46 -21
- package/src/pipeline/steps/defaults.ts +42 -0
- package/src/pipeline/steps/shapes.ts +31 -0
- package/src/store/__tests__/port-contract.test.ts +123 -0
- package/src/store/port-contract.ts +102 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the IamPolicy domain's constants (project 20260913.01,
|
|
3
|
+
* T01_0_plan.md §3a, T01_1_review.md Q-OR-9):
|
|
4
|
+
*
|
|
5
|
+
* - the policy-id derivation — `iamp_` + the top 130 bits of sha256 over
|
|
6
|
+
* the triple's canonical text as 26 lowercase Crockford-base32
|
|
7
|
+
* characters. The canonical text IS the tuple notation the contract's
|
|
8
|
+
* own comment documents (`principal.kind:principal.id#principal.relation
|
|
9
|
+
* @resource.kind:resource.id#relation`, spec.proto), so the natural key
|
|
10
|
+
* has one spelling and the primary key is the one home of "one row per
|
|
11
|
+
* triple". The golden vectors are wire-adjacent constants: a change
|
|
12
|
+
* re-addresses every policy open source ever wrote;
|
|
13
|
+
* - `BLUEPRINT_KINDS` — the kinds an admin authors, the legacy-creator
|
|
14
|
+
* rule's whole scan (Q-OR-6b); sessions and executions are personal
|
|
15
|
+
* and never appear here;
|
|
16
|
+
* - the byte-pinned copy moved from the cloud's handlers as-is, plus the
|
|
17
|
+
* new sentences (the two edition refusals, the unknown permission, the
|
|
18
|
+
* unknown principal kind, the malformed triple);
|
|
19
|
+
* - the canonical text's one weakness and its closure (slice 2 ruling
|
|
20
|
+
* Q-S2-1): `ApiResourceRef` fields carry no character pattern, so an
|
|
21
|
+
* id or relation holding `:`, `#` or `@` could spell another triple's
|
|
22
|
+
* text. `malformedTripleField` names the offending field and
|
|
23
|
+
* `policyIdFor` refuses to hash such a spec; the grant path turns the
|
|
24
|
+
* same check into INVALID_ARGUMENT before any read or write.
|
|
25
|
+
*/
|
|
26
|
+
import { create } from "@bufbuild/protobuf";
|
|
27
|
+
import { describe, expect, it } from "vitest";
|
|
28
|
+
|
|
29
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
30
|
+
import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
31
|
+
import { IamPolicySpecSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
AUTHENTICATION_REQUIRED_MESSAGE,
|
|
35
|
+
AUTHORIZATION_QUERIES_UNIMPLEMENTED_MESSAGE,
|
|
36
|
+
BLUEPRINT_KINDS,
|
|
37
|
+
IAM_POLICY_API_VERSION,
|
|
38
|
+
IAM_POLICY_KIND,
|
|
39
|
+
PER_RESOURCE_GRANTS_UNIMPLEMENTED_MESSAGE,
|
|
40
|
+
POLICY_ID_PREFIX,
|
|
41
|
+
TRIPLE_DELIMITERS,
|
|
42
|
+
USER_GRANT_PRINCIPAL_KINDS,
|
|
43
|
+
canonicalTripleText,
|
|
44
|
+
malformedTripleField,
|
|
45
|
+
malformedTripleMessage,
|
|
46
|
+
noGrantableRolesMessage,
|
|
47
|
+
policyIdFor,
|
|
48
|
+
policyNotFoundMessage,
|
|
49
|
+
principalNotGrantableMessage,
|
|
50
|
+
roleNotGrantableMessage,
|
|
51
|
+
unknownPermissionMessage,
|
|
52
|
+
unknownPrincipalKindMessage,
|
|
53
|
+
unknownResourceKindMessage,
|
|
54
|
+
} from "../constants.js";
|
|
55
|
+
|
|
56
|
+
const CROCKFORD_ID = /^iamp_[0-9abcdefghjkmnpqrstvwxyz]{26}$/;
|
|
57
|
+
|
|
58
|
+
function spec(
|
|
59
|
+
principal: { kind: string; id: string; relation?: string },
|
|
60
|
+
relation: string,
|
|
61
|
+
resource: { kind: string; id: string },
|
|
62
|
+
): IamPolicySpec {
|
|
63
|
+
return create(IamPolicySpecSchema, {
|
|
64
|
+
principal: {
|
|
65
|
+
kind: principal.kind,
|
|
66
|
+
id: principal.id,
|
|
67
|
+
relation: principal.relation ?? "",
|
|
68
|
+
},
|
|
69
|
+
relation,
|
|
70
|
+
resource: { kind: resource.kind, id: resource.id },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ALICE = "ida_wtr3jcf281yfk9xx61kj59fsme";
|
|
75
|
+
const AGENT = "agt_01hzzzzzzzzzzzzzzzzzzzzzzz";
|
|
76
|
+
|
|
77
|
+
describe("policyIdFor — the derived policy id", () => {
|
|
78
|
+
it("pins the golden vectors (a change re-addresses every OSS policy)", () => {
|
|
79
|
+
expect(
|
|
80
|
+
policyIdFor(
|
|
81
|
+
spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
82
|
+
kind: "organization",
|
|
83
|
+
id: "acme",
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
).toBe("iamp_vkbjb6nvqm77vtwnr2bj0x95f9");
|
|
87
|
+
expect(
|
|
88
|
+
policyIdFor(
|
|
89
|
+
spec({ kind: "identity_account", id: ALICE }, "member", {
|
|
90
|
+
kind: "organization",
|
|
91
|
+
id: "acme",
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
).toBe("iamp_8excwectzw4xzrphy43zpar661");
|
|
95
|
+
// A structural link (the cloud's scope tuple) derives the same way: the
|
|
96
|
+
// id is the triple's, whatever the relation means.
|
|
97
|
+
expect(
|
|
98
|
+
policyIdFor(
|
|
99
|
+
spec({ kind: "organization", id: "acme" }, "organization", {
|
|
100
|
+
kind: "agent",
|
|
101
|
+
id: AGENT,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
).toBe("iamp_e8ak7be793avgz5tk4abmew2fp");
|
|
105
|
+
// The public-viewer wildcard principal is a legal triple.
|
|
106
|
+
expect(
|
|
107
|
+
policyIdFor(
|
|
108
|
+
spec({ kind: "identity_account", id: "*" }, "viewer", {
|
|
109
|
+
kind: "agent",
|
|
110
|
+
id: AGENT,
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
).toBe("iamp_w5wqxkyrtbspjyp3jd6ce565q2");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("has the shape of every minted id: prefix, underscore, 26 Crockford chars", () => {
|
|
117
|
+
expect(POLICY_ID_PREFIX).toBe("iamp");
|
|
118
|
+
expect(
|
|
119
|
+
policyIdFor(
|
|
120
|
+
spec({ kind: "identity_account", id: ALICE }, "viewer", {
|
|
121
|
+
kind: "organization",
|
|
122
|
+
id: "acme",
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
).toMatch(CROCKFORD_ID);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("changes with every axis of the triple, the principal's relation qualifier included", () => {
|
|
129
|
+
const base = spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
130
|
+
kind: "organization",
|
|
131
|
+
id: "acme",
|
|
132
|
+
});
|
|
133
|
+
const variants = [
|
|
134
|
+
spec({ kind: "team", id: ALICE }, "admin", {
|
|
135
|
+
kind: "organization",
|
|
136
|
+
id: "acme",
|
|
137
|
+
}),
|
|
138
|
+
spec({ kind: "identity_account", id: "ida_other" }, "admin", {
|
|
139
|
+
kind: "organization",
|
|
140
|
+
id: "acme",
|
|
141
|
+
}),
|
|
142
|
+
spec(
|
|
143
|
+
{ kind: "identity_account", id: ALICE, relation: "member" },
|
|
144
|
+
"admin",
|
|
145
|
+
{ kind: "organization", id: "acme" },
|
|
146
|
+
),
|
|
147
|
+
spec({ kind: "identity_account", id: ALICE }, "member", {
|
|
148
|
+
kind: "organization",
|
|
149
|
+
id: "acme",
|
|
150
|
+
}),
|
|
151
|
+
spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
152
|
+
kind: "environment",
|
|
153
|
+
id: "acme",
|
|
154
|
+
}),
|
|
155
|
+
spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
156
|
+
kind: "organization",
|
|
157
|
+
id: "globex",
|
|
158
|
+
}),
|
|
159
|
+
];
|
|
160
|
+
const ids = new Set([policyIdFor(base), ...variants.map(policyIdFor)]);
|
|
161
|
+
expect(ids.size).toBe(variants.length + 1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("refuses a spec missing either reference — a triple is the whole key", () => {
|
|
165
|
+
expect(() =>
|
|
166
|
+
policyIdFor(create(IamPolicySpecSchema, { relation: "admin" })),
|
|
167
|
+
).toThrow("policy spec must carry principal and resource");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("hashes exactly the proto's tuple notation, the principal's `#` present even when its relation is empty", () => {
|
|
171
|
+
expect(
|
|
172
|
+
canonicalTripleText(
|
|
173
|
+
spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
174
|
+
kind: "organization",
|
|
175
|
+
id: "acme",
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
).toBe(`identity_account:${ALICE}#@organization:acme#admin`);
|
|
179
|
+
expect(
|
|
180
|
+
canonicalTripleText(
|
|
181
|
+
spec({ kind: "team", id: "tm_1", relation: "member" }, "viewer", {
|
|
182
|
+
kind: "agent",
|
|
183
|
+
id: AGENT,
|
|
184
|
+
}),
|
|
185
|
+
),
|
|
186
|
+
).toBe(`team:tm_1#member@agent:${AGENT}#viewer`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("refuses a triple whose text would be ambiguous — a delimiter inside any field (Q-S2-1)", () => {
|
|
190
|
+
// `a#b` with an empty qualifier and `a` with qualifier `b#` would spell
|
|
191
|
+
// one text; the refusal is what keeps one text one triple.
|
|
192
|
+
expect(() =>
|
|
193
|
+
policyIdFor(
|
|
194
|
+
spec({ kind: "identity_account", id: "a#b" }, "admin", {
|
|
195
|
+
kind: "organization",
|
|
196
|
+
id: "acme",
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
).toThrow(malformedTripleMessage("principal.id"));
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("malformedTripleField — the delimiter check the id and the writer share", () => {
|
|
204
|
+
it("names the delimiters the canonical text is built from", () => {
|
|
205
|
+
expect([...TRIPLE_DELIMITERS]).toEqual([":", "#", "@"]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("names the first field holding a delimiter, in the text's order", () => {
|
|
209
|
+
const base = {
|
|
210
|
+
principal: { kind: "identity_account", id: ALICE, relation: "" },
|
|
211
|
+
relation: "admin",
|
|
212
|
+
resource: { kind: "organization", id: "acme" },
|
|
213
|
+
};
|
|
214
|
+
const cases: ReadonlyArray<[IamPolicySpec, string]> = [
|
|
215
|
+
[
|
|
216
|
+
spec(
|
|
217
|
+
{ ...base.principal, kind: "identity:account" },
|
|
218
|
+
base.relation,
|
|
219
|
+
base.resource,
|
|
220
|
+
),
|
|
221
|
+
"principal.kind",
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
spec(
|
|
225
|
+
{ ...base.principal, id: `${ALICE}@x` },
|
|
226
|
+
base.relation,
|
|
227
|
+
base.resource,
|
|
228
|
+
),
|
|
229
|
+
"principal.id",
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
spec(
|
|
233
|
+
{ ...base.principal, relation: "mem#ber" },
|
|
234
|
+
base.relation,
|
|
235
|
+
base.resource,
|
|
236
|
+
),
|
|
237
|
+
"principal.relation",
|
|
238
|
+
],
|
|
239
|
+
[
|
|
240
|
+
spec(base.principal, base.relation, {
|
|
241
|
+
...base.resource,
|
|
242
|
+
kind: "org:anization",
|
|
243
|
+
}),
|
|
244
|
+
"resource.kind",
|
|
245
|
+
],
|
|
246
|
+
[
|
|
247
|
+
spec(base.principal, base.relation, {
|
|
248
|
+
...base.resource,
|
|
249
|
+
id: "acme#admin",
|
|
250
|
+
}),
|
|
251
|
+
"resource.id",
|
|
252
|
+
],
|
|
253
|
+
[spec(base.principal, "ad@min", base.resource), "relation"],
|
|
254
|
+
];
|
|
255
|
+
for (const [malformed, field] of cases) {
|
|
256
|
+
expect(malformedTripleField(malformed), field).toBe(field);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("is undefined for a well-formed triple, the public wildcard principal included", () => {
|
|
261
|
+
expect(
|
|
262
|
+
malformedTripleField(
|
|
263
|
+
spec({ kind: "identity_account", id: ALICE }, "admin", {
|
|
264
|
+
kind: "organization",
|
|
265
|
+
id: "acme",
|
|
266
|
+
}),
|
|
267
|
+
),
|
|
268
|
+
).toBeUndefined();
|
|
269
|
+
expect(
|
|
270
|
+
malformedTripleField(
|
|
271
|
+
spec({ kind: "identity_account", id: "*" }, "viewer", {
|
|
272
|
+
kind: "agent",
|
|
273
|
+
id: AGENT,
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
).toBeUndefined();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("the contract's identity strings", () => {
|
|
281
|
+
it("stamps the proto's apiVersion const, not the Java-era value (Q-OR-9)", () => {
|
|
282
|
+
expect(IAM_POLICY_API_VERSION).toBe("iam.stigmer.ai/v1");
|
|
283
|
+
expect(IAM_POLICY_KIND).toBe("IamPolicy");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("BLUEPRINT_KINDS — the legacy-creator rule's scan (Q-OR-6b)", () => {
|
|
288
|
+
it("is exactly the six kinds an admin authors, in registry order", () => {
|
|
289
|
+
expect([...BLUEPRINT_KINDS]).toEqual([
|
|
290
|
+
ApiResourceKind.agent,
|
|
291
|
+
ApiResourceKind.workflow,
|
|
292
|
+
ApiResourceKind.skill,
|
|
293
|
+
ApiResourceKind.mcp_server,
|
|
294
|
+
ApiResourceKind.environment,
|
|
295
|
+
ApiResourceKind.schedule,
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("never names a personal kind — those stay with their creator by DD-002 rule 2, no role needed", () => {
|
|
300
|
+
for (const personal of [
|
|
301
|
+
ApiResourceKind.session,
|
|
302
|
+
ApiResourceKind.agent_execution,
|
|
303
|
+
ApiResourceKind.workflow_execution,
|
|
304
|
+
ApiResourceKind.api_key,
|
|
305
|
+
ApiResourceKind.memory,
|
|
306
|
+
]) {
|
|
307
|
+
expect(BLUEPRINT_KINDS).not.toContain(personal);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("USER_GRANT_PRINCIPAL_KINDS — who a person may grant a role to (Q-S9-2)", () => {
|
|
313
|
+
it("is exactly the identity account: a role names a person; `team` is not an ApiResourceKind, so no wire spec can name it", () => {
|
|
314
|
+
expect([...USER_GRANT_PRINCIPAL_KINDS]).toEqual([
|
|
315
|
+
ApiResourceKind.identity_account,
|
|
316
|
+
]);
|
|
317
|
+
expect(ApiResourceKind).not.toHaveProperty("team");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("names no kind the hierarchy walk would read as a structural parent", () => {
|
|
321
|
+
// The two sets meet by construction (resource-store.ts derives its
|
|
322
|
+
// non-structural exclusion from this one), so this pins the
|
|
323
|
+
// consequence: an organization, an agent or the platform can never
|
|
324
|
+
// be a person's grantee.
|
|
325
|
+
for (const structural of [
|
|
326
|
+
ApiResourceKind.organization,
|
|
327
|
+
ApiResourceKind.platform,
|
|
328
|
+
ApiResourceKind.agent,
|
|
329
|
+
ApiResourceKind.identity_provider,
|
|
330
|
+
]) {
|
|
331
|
+
expect(USER_GRANT_PRINCIPAL_KINDS).not.toContain(structural);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("byte-pinned copy", () => {
|
|
337
|
+
it("moves the cloud's sentences as-is", () => {
|
|
338
|
+
expect(policyNotFoundMessage("iamp_x")).toBe(
|
|
339
|
+
"IAM policy not found: iamp_x",
|
|
340
|
+
);
|
|
341
|
+
expect(noGrantableRolesMessage("agent_execution")).toBe(
|
|
342
|
+
"No roles can be granted on resource kind 'agent_execution'. Role assignments for this resource kind are system-managed.",
|
|
343
|
+
);
|
|
344
|
+
expect(
|
|
345
|
+
roleNotGrantableMessage("editor", "organization", [
|
|
346
|
+
"owner",
|
|
347
|
+
"admin",
|
|
348
|
+
"member",
|
|
349
|
+
"viewer",
|
|
350
|
+
]),
|
|
351
|
+
).toBe(
|
|
352
|
+
"Role 'editor' cannot be granted on resource kind 'organization'. Grantable roles: [owner, admin, member, viewer]",
|
|
353
|
+
);
|
|
354
|
+
expect(AUTHENTICATION_REQUIRED_MESSAGE).toBe(
|
|
355
|
+
"Authentication required to check permissions",
|
|
356
|
+
);
|
|
357
|
+
expect(unknownResourceKindMessage("organisation")).toBe(
|
|
358
|
+
"Unknown resource kind: 'organisation'",
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("spells the two new writer refusals in the cloud sentence's shape", () => {
|
|
363
|
+
expect(unknownPrincipalKindMessage("team")).toBe(
|
|
364
|
+
"Unknown principal kind: 'team'",
|
|
365
|
+
);
|
|
366
|
+
expect(malformedTripleMessage("resource.id")).toBe(
|
|
367
|
+
"policy resource.id must not contain ':', '#' or '@'",
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("spells the principal refusal in the role sentence's shape, listing the grantable principal kinds", () => {
|
|
372
|
+
expect(principalNotGrantableMessage("organization")).toBe(
|
|
373
|
+
"Principal kind 'organization' cannot be granted a role. Grantable principal kinds: [identity_account]",
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("names the edition in the two new refusals, and quotes the unknown permission single-quoted", () => {
|
|
378
|
+
expect(PER_RESOURCE_GRANTS_UNIMPLEMENTED_MESSAGE).toBe(
|
|
379
|
+
"per-resource access grants are served by the Enterprise and Cloud editions",
|
|
380
|
+
);
|
|
381
|
+
expect(AUTHORIZATION_QUERIES_UNIMPLEMENTED_MESSAGE).toBe(
|
|
382
|
+
"authorization queries are served by the Enterprise and Cloud editions",
|
|
383
|
+
);
|
|
384
|
+
expect(unknownPermissionMessage("can_fly")).toBe(
|
|
385
|
+
"unknown permission 'can_fly'",
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
});
|