@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,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IamPolicyStore PORT-CONTRACT KIT (20260913.01, T01_0_plan.md §3a;
|
|
3
|
+
* the 2a A11/A12 discipline): every behavior an implementation of store.ts
|
|
4
|
+
* must satisfy identically, as cases a driver's test iterates. Open source
|
|
5
|
+
* runs them over its own adapter (resource-store.ts) on sqlite and Postgres
|
|
6
|
+
* in __tests__/resource-store.test.ts; a composition runs the SAME cases
|
|
7
|
+
* over the store it registers as `drivers.iamPolicyStore` (the cloud's
|
|
8
|
+
* `cloud.iam_policy` store, S3), so "the port holds" is one statement
|
|
9
|
+
* proven per driver, never restated per repository.
|
|
10
|
+
*
|
|
11
|
+
* Shape. A list of declarations over the port-contract runner
|
|
12
|
+
* (store/port-contract.ts): the runner owns the fresh fixture per case,
|
|
13
|
+
* the cleanup, the rule that a failing assertion wins over a failing
|
|
14
|
+
* cleanup, and the reason a kit returns cases instead of calling vitest's
|
|
15
|
+
* `describe`. `disconnect` is the one escape hatch: the port has no
|
|
16
|
+
* lifecycle by design, yet "an outage never reads as no grant" is the line
|
|
17
|
+
* that matters most.
|
|
18
|
+
*
|
|
19
|
+
* The corners A12 taught are cases from the start, not later additions:
|
|
20
|
+
* every relation on a pair; distinct principals, not rows; the scope-tuple
|
|
21
|
+
* exclusions verbatim; unknown-id delete a no-op; a held id refused.
|
|
22
|
+
*
|
|
23
|
+
* What the kit deliberately does not carry: open source's derived-id
|
|
24
|
+
* refusal and its primary-key read, the cloud's column layout and SQLSTATE
|
|
25
|
+
* 23505 mapping — driver-physical, each driver's own tests. Every fixture
|
|
26
|
+
* row carries its triple's DERIVED id, so "a held triple" and "a held id"
|
|
27
|
+
* are one case here; a composition's legacy random ids are the grant
|
|
28
|
+
* path's by-triple read to converge (store.ts header), not a store promise.
|
|
29
|
+
*
|
|
30
|
+
* The duplicate arm checks `instanceof DuplicatePolicyError` on the
|
|
31
|
+
* exported class, because that is how the grant path's race arm catches
|
|
32
|
+
* it: a driver throwing a lookalike fails here for the reason it would
|
|
33
|
+
* fail a grant.
|
|
34
|
+
*
|
|
35
|
+
* Case names are the contract lines, in the port's words. The OSS test pins
|
|
36
|
+
* the full list, so a case cannot drop out of the kit unnoticed.
|
|
37
|
+
*/
|
|
38
|
+
import assert from "node:assert/strict";
|
|
39
|
+
import { create, equals } from "@bufbuild/protobuf";
|
|
40
|
+
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
41
|
+
import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
|
|
42
|
+
import { IamPolicySpecSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
43
|
+
import { portContractCases } from "../../store/port-contract.js";
|
|
44
|
+
import { IAM_POLICY_API_VERSION, IAM_POLICY_KIND, policyIdFor, } from "./constants.js";
|
|
45
|
+
import { DuplicatePolicyError } from "./store.js";
|
|
46
|
+
const ALICE = "ida_wtr3jcf281yfk9xx61kj59fsme";
|
|
47
|
+
const BOB = "ida_0byc5k14t1e7b7kdxft7hwz1f7";
|
|
48
|
+
const AGENT = "agt_01hzzzzzzzzzzzzzzzzzzzzzzz";
|
|
49
|
+
function spec(principal, relation, resource) {
|
|
50
|
+
return create(IamPolicySpecSchema, {
|
|
51
|
+
principal: {
|
|
52
|
+
kind: principal.kind,
|
|
53
|
+
id: principal.id,
|
|
54
|
+
relation: principal.relation ?? "",
|
|
55
|
+
},
|
|
56
|
+
relation,
|
|
57
|
+
resource: { kind: resource.kind, id: resource.id },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** `identity_account:<id>` holds `<relation>` on `organization:<org>`. */
|
|
61
|
+
function orgRole(accountId, relation, orgId) {
|
|
62
|
+
return spec({ kind: "identity_account", id: accountId }, relation, {
|
|
63
|
+
kind: "organization",
|
|
64
|
+
id: orgId,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A row as the domain writes it: the contract's apiVersion and kind, the
|
|
69
|
+
* triple's derived id, and a creator stamp so two rows for one triple can
|
|
70
|
+
* be told apart ("the first row stands").
|
|
71
|
+
*/
|
|
72
|
+
function policyRow(policySpec, createdBy = ALICE) {
|
|
73
|
+
return create(IamPolicySchema, {
|
|
74
|
+
apiVersion: IAM_POLICY_API_VERSION,
|
|
75
|
+
kind: IAM_POLICY_KIND,
|
|
76
|
+
metadata: create(ApiResourceMetadataSchema, {
|
|
77
|
+
id: policyIdFor(policySpec),
|
|
78
|
+
}),
|
|
79
|
+
spec: policySpec,
|
|
80
|
+
status: { audit: { specAudit: { createdBy: { id: createdBy } } } },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function idOf(policy) {
|
|
84
|
+
const id = policy.metadata?.id ?? "";
|
|
85
|
+
assert.notEqual(id, "", "a fixture policy must carry an id");
|
|
86
|
+
return id;
|
|
87
|
+
}
|
|
88
|
+
function relationsOf(policies) {
|
|
89
|
+
return policies.map((policy) => policy.spec?.relation ?? "").sort();
|
|
90
|
+
}
|
|
91
|
+
async function saveAll(store, policies) {
|
|
92
|
+
for (const policy of policies) {
|
|
93
|
+
await store.save(policy);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const CASES = [
|
|
97
|
+
[
|
|
98
|
+
"save then findById round-trips the policy",
|
|
99
|
+
async ({ store }) => {
|
|
100
|
+
const policy = policyRow(orgRole(ALICE, "admin", "acme"));
|
|
101
|
+
await store.save(policy);
|
|
102
|
+
const found = await store.findById(idOf(policy));
|
|
103
|
+
assert.ok(found !== undefined, "a saved policy must be readable by id");
|
|
104
|
+
assert.ok(equals(IamPolicySchema, found, policy), "findById must return the policy exactly as it was saved");
|
|
105
|
+
assert.equal(await store.findById("iamp_00000000000000000000000000"), undefined, "an unknown id must read as undefined, not as an error");
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
"a second save under a held triple is DuplicatePolicyError, and the first row stands",
|
|
110
|
+
async ({ store }) => {
|
|
111
|
+
const triple = orgRole(ALICE, "admin", "acme");
|
|
112
|
+
await store.save(policyRow(triple, ALICE));
|
|
113
|
+
await assert.rejects(store.save(policyRow(triple, BOB)), DuplicatePolicyError, "save under a held id must raise the exported DuplicatePolicyError, never overwrite");
|
|
114
|
+
const standing = await store.findById(policyIdFor(triple));
|
|
115
|
+
assert.equal(standing?.status?.audit?.specAudit?.createdBy?.id, ALICE, "the first writer's row must stand after a refused duplicate");
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
"findByPrincipalAndResource answers every relation on the pair and nothing on another",
|
|
120
|
+
async ({ store }) => {
|
|
121
|
+
await saveAll(store, [
|
|
122
|
+
policyRow(orgRole(ALICE, "admin", "acme")),
|
|
123
|
+
policyRow(orgRole(ALICE, "viewer", "acme")),
|
|
124
|
+
policyRow(orgRole(ALICE, "member", "globex")),
|
|
125
|
+
policyRow(orgRole(BOB, "member", "acme")),
|
|
126
|
+
]);
|
|
127
|
+
assert.deepEqual(relationsOf(await store.findByPrincipalAndResource("identity_account", ALICE, "organization", "acme")), ["admin", "viewer"], "the pair's rows are every relation the principal holds on the resource");
|
|
128
|
+
assert.deepEqual(await store.findByPrincipalAndResource("identity_account", BOB, "organization", "globex"), [], "a pair with no rows must answer an empty list");
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
[
|
|
132
|
+
"findByPrincipal and findByResource answer each side of a row",
|
|
133
|
+
async ({ store }) => {
|
|
134
|
+
const structural = spec({ kind: "organization", id: "acme" }, "organization", { kind: "agent", id: AGENT });
|
|
135
|
+
await saveAll(store, [
|
|
136
|
+
policyRow(orgRole(ALICE, "owner", "acme")),
|
|
137
|
+
policyRow(structural),
|
|
138
|
+
policyRow(orgRole(BOB, "member", "globex")),
|
|
139
|
+
]);
|
|
140
|
+
assert.deepEqual((await store.findByPrincipal("organization", "acme")).map(idOf), [policyIdFor(structural)], "findByPrincipal answers the rows where the ref is the principal");
|
|
141
|
+
assert.deepEqual((await store.findByResource("organization", "acme")).map(idOf), [policyIdFor(orgRole(ALICE, "owner", "acme"))], "findByResource answers the rows where the ref is the resource");
|
|
142
|
+
assert.deepEqual(await store.findByResource("organization", "nowhere"), [], "a resource with no rows must answer an empty list");
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
"findByResourceWithRelations filters to the allowlist; an empty allowlist matches nothing",
|
|
147
|
+
async ({ store }) => {
|
|
148
|
+
await saveAll(store, [
|
|
149
|
+
policyRow(orgRole(ALICE, "admin", "acme")),
|
|
150
|
+
policyRow(orgRole(BOB, "member", "acme")),
|
|
151
|
+
policyRow(spec({ kind: "organization", id: "acme" }, "organization", {
|
|
152
|
+
kind: "organization",
|
|
153
|
+
id: "acme",
|
|
154
|
+
})),
|
|
155
|
+
]);
|
|
156
|
+
assert.deepEqual(relationsOf(await store.findByResourceWithRelations("organization", "acme", [
|
|
157
|
+
"admin",
|
|
158
|
+
"member",
|
|
159
|
+
"viewer",
|
|
160
|
+
])), ["admin", "member"], "only rows whose relation is in the allowlist are answered");
|
|
161
|
+
assert.deepEqual(await store.findByResourceWithRelations("organization", "acme", []), [], "an empty allowlist must match nothing on every edition");
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
[
|
|
165
|
+
"countDistinctPrincipalsByResource counts (kind, id) pairs, not rows, optionally by principal kind",
|
|
166
|
+
async ({ store }) => {
|
|
167
|
+
const roles = ["owner", "admin", "member", "viewer"];
|
|
168
|
+
await saveAll(store, [
|
|
169
|
+
policyRow(orgRole(ALICE, "admin", "acme")),
|
|
170
|
+
policyRow(orgRole(ALICE, "viewer", "acme")),
|
|
171
|
+
policyRow(orgRole(BOB, "member", "acme")),
|
|
172
|
+
policyRow(spec({ kind: "organization", id: "globex" }, "member", {
|
|
173
|
+
kind: "organization",
|
|
174
|
+
id: "acme",
|
|
175
|
+
})),
|
|
176
|
+
policyRow(orgRole(BOB, "member", "globex")),
|
|
177
|
+
]);
|
|
178
|
+
assert.equal(await store.countDistinctPrincipalsByResource("organization", "acme", undefined, roles), 3, "alice (two rows), bob and the organization principal are three principals, not four rows");
|
|
179
|
+
assert.equal(await store.countDistinctPrincipalsByResource("organization", "acme", "identity_account", roles), 2, "the principal-kind filter narrows to identity accounts");
|
|
180
|
+
assert.equal(await store.countDistinctPrincipalsByResource("organization", "acme", undefined, ["owner"]), 0, "no row in the allowlist is a count of zero");
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
"findScopeTuple skips identity_account and team principals and owner and creator relations",
|
|
185
|
+
async ({ store }) => {
|
|
186
|
+
const scope = spec({ kind: "organization", id: "acme" }, "organization", {
|
|
187
|
+
kind: "agent",
|
|
188
|
+
id: AGENT,
|
|
189
|
+
});
|
|
190
|
+
await saveAll(store, [
|
|
191
|
+
policyRow(spec({ kind: "identity_account", id: ALICE }, "owner", {
|
|
192
|
+
kind: "agent",
|
|
193
|
+
id: AGENT,
|
|
194
|
+
})),
|
|
195
|
+
policyRow(spec({ kind: "team", id: "tm_1", relation: "member" }, "viewer", {
|
|
196
|
+
kind: "agent",
|
|
197
|
+
id: AGENT,
|
|
198
|
+
})),
|
|
199
|
+
policyRow(spec({ kind: "organization", id: "acme" }, "creator", {
|
|
200
|
+
kind: "agent",
|
|
201
|
+
id: AGENT,
|
|
202
|
+
})),
|
|
203
|
+
policyRow(scope),
|
|
204
|
+
]);
|
|
205
|
+
assert.equal((await store.findScopeTuple("agent", AGENT))?.metadata?.id, policyIdFor(scope), "the one structural link is the row whose principal and relation are both structural");
|
|
206
|
+
assert.equal(await store.findScopeTuple("agent", "agt_nowhere"), undefined, "a resource with no structural link must read as undefined");
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
[
|
|
210
|
+
"deleteById removes the row; the triple is free to be granted again",
|
|
211
|
+
async ({ store }) => {
|
|
212
|
+
const policy = policyRow(orgRole(ALICE, "admin", "acme"));
|
|
213
|
+
await store.save(policy);
|
|
214
|
+
await store.deleteById(idOf(policy));
|
|
215
|
+
assert.equal(await store.findById(idOf(policy)), undefined, "a deleted policy must not be readable by id");
|
|
216
|
+
assert.deepEqual(await store.findByPrincipalAndResource("identity_account", ALICE, "organization", "acme"), [], "a deleted policy must not be readable by its pair");
|
|
217
|
+
await assert.doesNotReject(store.save(policy), "the triple of a deleted policy must be savable again");
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
[
|
|
221
|
+
"deleteById of an unknown id resolves",
|
|
222
|
+
async ({ store }) => {
|
|
223
|
+
await assert.doesNotReject(store.deleteById("iamp_00000000000000000000000000"), "deleting an id that is not held must resolve, not reject");
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
[
|
|
227
|
+
"a disconnected store is an infrastructure fault, never 'not found'",
|
|
228
|
+
async (fixture) => {
|
|
229
|
+
await fixture.disconnect();
|
|
230
|
+
await assert.rejects(fixture.store.findById(policyIdFor(orgRole(ALICE, "admin", "acme"))), "an id read on a disconnected store must reject, never read as undefined");
|
|
231
|
+
await assert.rejects(fixture.store.findByPrincipalAndResource("identity_account", ALICE, "organization", "acme"), "a pair read on a disconnected store must reject, never read as an empty list");
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
[
|
|
235
|
+
"two concurrent saves of one triple end in one row and DuplicatePolicyErrors only",
|
|
236
|
+
async ({ store }) => {
|
|
237
|
+
const triple = orgRole(ALICE, "admin", "acme");
|
|
238
|
+
const results = await Promise.allSettled([
|
|
239
|
+
store.save(policyRow(triple, ALICE)),
|
|
240
|
+
store.save(policyRow(triple, BOB)),
|
|
241
|
+
]);
|
|
242
|
+
for (const result of results) {
|
|
243
|
+
if (result.status === "rejected") {
|
|
244
|
+
assert.ok(result.reason instanceof DuplicatePolicyError, `a losing concurrent save must fail as DuplicatePolicyError, got ${String(result.reason)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
assert.ok(results.some((result) => result.status === "fulfilled"), "at least one of two concurrent saves must win");
|
|
248
|
+
const rows = await store.findByPrincipalAndResource("identity_account", ALICE, "organization", "acme");
|
|
249
|
+
assert.equal(rows.length, 1, "one triple must end as exactly one row");
|
|
250
|
+
assert.equal(rows[0]?.metadata?.id, policyIdFor(triple), "the surviving row carries the triple's derived id");
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
];
|
|
254
|
+
/** The port's cases over `makeFixture`, one fresh fixture per case. */
|
|
255
|
+
export function iamPolicyStoreContract(makeFixture) {
|
|
256
|
+
return portContractCases(CASES, makeFixture);
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=store-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-contract.js","sourceRoot":"","sources":["../../../src/domain/iampolicy/store-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IamPolicyStore PORT (20260913.01, T01_0_plan.md §3a; Q-OR-1): the
|
|
3
|
+
* storage contract the IamPolicy domain writes and reads through, cut from
|
|
4
|
+
* the cloud's row store (iam/policy/store.ts) to what BOTH editions call.
|
|
5
|
+
* The second OSS domain whose persistence is a port rather than the
|
|
6
|
+
* generic Store (the identity account was the first): the cloud serves the
|
|
7
|
+
* same controller over its own `cloud.iam_policy` table by registering an
|
|
8
|
+
* implementation as `drivers.iamPolicyStore`, so the domain's grant path
|
|
9
|
+
* and handlers take this interface, never `Store`. Open source's own
|
|
10
|
+
* implementation is resource-store.ts over the generic Store.
|
|
11
|
+
*
|
|
12
|
+
* What the port deliberately does not carry: `markSynced` and
|
|
13
|
+
* `findUnsynced` (the cloud's tuple-mirror bookkeeping and boot backfill;
|
|
14
|
+
* its store keeps them as extras) and `findByIdentityAccountInOrg` (the
|
|
15
|
+
* cloud's org-column arm of revocation — the column was never stamped by
|
|
16
|
+
* any writer, so it matched nothing; dropped, Q-OR-9). A port does not
|
|
17
|
+
* carry methods only one edition calls or no edition needs.
|
|
18
|
+
*
|
|
19
|
+
* Contract every implementation must satisfy — proven by the port-contract
|
|
20
|
+
* kit (store-contract.ts, exported), which the OSS adapter's test iterates
|
|
21
|
+
* on both drivers and a composition's driver test iterates too:
|
|
22
|
+
* - `save` is create-only in effect: a save under a held ID raises
|
|
23
|
+
* DuplicatePolicyError, never a silent overwrite. Under derived ids a
|
|
24
|
+
* held triple IS a held id, which is what the kit exercises; where a
|
|
25
|
+
* composition still holds legacy random ids, "one row per triple" is
|
|
26
|
+
* the grant path's by-triple read (grant-path.ts), not this promise;
|
|
27
|
+
* - `deleteById` of an unknown id resolves;
|
|
28
|
+
* - the finds answer rows by the axes named, exactly:
|
|
29
|
+
* `findByPrincipalAndResource` every relation on the pair;
|
|
30
|
+
* `findByResourceWithRelations` the allowlist only, an empty allowlist
|
|
31
|
+
* matching nothing; `countDistinctPrincipalsByResource` distinct
|
|
32
|
+
* (kind, id) principals — not rows — over the allowlist, `undefined`
|
|
33
|
+
* principal kind meaning any (callers normalize the wire's `""`);
|
|
34
|
+
* `findScopeTuple` the resource's one structural link — a principal
|
|
35
|
+
* that is not `identity_account` or `team` and a relation that is not
|
|
36
|
+
* `owner` or `creator`, the cloud SQL's exclusion arms verbatim (`team`
|
|
37
|
+
* is a Java-era name no ApiResourceKind carries; kept so the two
|
|
38
|
+
* editions' filters read alike);
|
|
39
|
+
* - a typed not-found reads as `undefined`; any other storage failure
|
|
40
|
+
* propagates as the infrastructure fault it is (the ratified
|
|
41
|
+
* store-fault mapping — an outage must never read as "no grant").
|
|
42
|
+
*/
|
|
43
|
+
import type { IamPolicy } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
|
|
44
|
+
/**
|
|
45
|
+
* Raised by `save` when the id is already held, so the grant path runs its
|
|
46
|
+
* race arm (re-read the winner by triple; answer it as the duplicate).
|
|
47
|
+
* `cause` carries a driver's own error (the cloud's SQLSTATE 23505) for
|
|
48
|
+
* the logs; the message is the port's.
|
|
49
|
+
*/
|
|
50
|
+
export declare class DuplicatePolicyError extends Error {
|
|
51
|
+
constructor(message: string, options?: {
|
|
52
|
+
cause?: unknown;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export interface IamPolicyStore {
|
|
56
|
+
/** Insert; a held id raises DuplicatePolicyError. */
|
|
57
|
+
save(policy: IamPolicy): Promise<void>;
|
|
58
|
+
/** Removes the row; no error when it does not exist. */
|
|
59
|
+
deleteById(id: string): Promise<void>;
|
|
60
|
+
findById(id: string): Promise<IamPolicy | undefined>;
|
|
61
|
+
/** Every row naming the principal — the cleanup's principal-side arm. */
|
|
62
|
+
findByPrincipal(principalKind: string, principalId: string): Promise<ReadonlyArray<IamPolicy>>;
|
|
63
|
+
/** Every row on the resource — the cleanup's resource-side arm. */
|
|
64
|
+
findByResource(resourceKind: string, resourceId: string): Promise<ReadonlyArray<IamPolicy>>;
|
|
65
|
+
/** Every relation the principal holds on the resource — the by-triple read's candidates. */
|
|
66
|
+
findByPrincipalAndResource(principalKind: string, principalId: string, resourceKind: string, resourceId: string): Promise<ReadonlyArray<IamPolicy>>;
|
|
67
|
+
/** Rows on the resource whose relation is in the allowlist (the assignable roles); an empty allowlist matches nothing. */
|
|
68
|
+
findByResourceWithRelations(resourceKind: string, resourceId: string, relations: ReadonlyArray<string>): Promise<ReadonlyArray<IamPolicy>>;
|
|
69
|
+
/** Distinct (kind, id) principals holding an allowlisted relation on the resource; `undefined` kind = any. */
|
|
70
|
+
countDistinctPrincipalsByResource(resourceKind: string, resourceId: string, principalKind: string | undefined, relations: ReadonlyArray<string>): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* The resource's scope tuple (the hierarchy walk's one step): a policy
|
|
73
|
+
* on the resource whose principal is structural (not identity_account
|
|
74
|
+
* or team) and whose relation is structural (not owner or creator).
|
|
75
|
+
*/
|
|
76
|
+
findScopeTuple(resourceKind: string, resourceId: string): Promise<IamPolicy | undefined>;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/domain/iampolicy/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oDAAoD,CAAC;AAEpF;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAED,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,wDAAwD;IACxD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IACrD,yEAAyE;IACzE,eAAe,CACb,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IACrC,mEAAmE;IACnE,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IACrC,4FAA4F;IAC5F,0BAA0B,CACxB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IACrC,0HAA0H;IAC1H,2BAA2B,CACzB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IACrC,8GAA8G;IAC9G,iCAAiC,CAC/B,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB;;;;OAIG;IACH,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACnC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised by `save` when the id is already held, so the grant path runs its
|
|
3
|
+
* race arm (re-read the winner by triple; answer it as the duplicate).
|
|
4
|
+
* `cause` carries a driver's own error (the cloud's SQLSTATE 23505) for
|
|
5
|
+
* the logs; the message is the port's.
|
|
6
|
+
*/
|
|
7
|
+
export class DuplicatePolicyError extends Error {
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = "DuplicatePolicyError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/domain/iampolicy/store.ts"],"names":[],"mappings":"AA4CA;;;;;GAKG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
2
|
+
import type { ApiResourceRef, IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
3
|
+
/** A triple the wire may say: both refs present, both kinds resolved. */
|
|
4
|
+
export interface AdmittedTriple {
|
|
5
|
+
readonly principal: ApiResourceRef;
|
|
6
|
+
readonly resource: ApiResourceRef;
|
|
7
|
+
readonly principalKind: ApiResourceKind;
|
|
8
|
+
readonly resourceKind: ApiResourceKind;
|
|
9
|
+
}
|
|
10
|
+
/** The kind a `resource.kind` string names, or INVALID_ARGUMENT with the cloud's sentence. */
|
|
11
|
+
export declare function requireKnownResourceKind(kind: string): ApiResourceKind;
|
|
12
|
+
/** The kind a `principal.kind` string names, or INVALID_ARGUMENT with the principal sentence. */
|
|
13
|
+
export declare function requireKnownPrincipalKind(kind: string): ApiResourceKind;
|
|
14
|
+
/**
|
|
15
|
+
* The whole triple, in the order the cloud refused it: no delimiter in any
|
|
16
|
+
* field (the canonical text would be ambiguous, Q-S2-1), then the resource
|
|
17
|
+
* kind, then the principal kind.
|
|
18
|
+
*/
|
|
19
|
+
export declare function requireWellFormedTriple(spec: IamPolicySpec): AdmittedTriple;
|
|
20
|
+
/** Both refs, present — absence is a caller that bypassed the boundary, not wire input. */
|
|
21
|
+
export declare function refsOf(spec: IamPolicySpec): {
|
|
22
|
+
principal: ApiResourceRef;
|
|
23
|
+
resource: ApiResourceRef;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=wire-refusals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-refusals.d.ts","sourceRoot":"","sources":["../../../src/domain/iampolicy/wire-refusals.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AACtH,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACd,MAAM,qDAAqD,CAAC;AAU7D,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;CACxC;AAED,8FAA8F;AAC9F,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAStE;AAED,iGAAiG;AACjG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CASvE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,CAe3E;AAED,2FAA2F;AAC3F,wBAAgB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG;IAC3C,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;CAC1B,CAKA"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IamPolicy domain's wire refusals (20260913.01; slice 2 ruling
|
|
3
|
+
* Q-S2-1, slice 5 ruling Q-S5-2): the ONE place a kind string that came
|
|
4
|
+
* off the wire, or a whole policy triple, is turned into INVALID_ARGUMENT
|
|
5
|
+
* with the domain's byte-pinned copy (constants.ts). Three callers share
|
|
6
|
+
* it so the rule cannot drift between them — the grant path (before any
|
|
7
|
+
* read, write or lifecycle hook), the create chain's ValidateGrantableRole
|
|
8
|
+
* step (before its role arms, so an unknown kind is never answered with a
|
|
9
|
+
* role sentence) and the query controller (checkMyPermission's resource,
|
|
10
|
+
* checkAuthorization's policy, the two listings' kind strings, the three
|
|
11
|
+
* row reads' refs).
|
|
12
|
+
*
|
|
13
|
+
* Why the match is exact (the `kindByEnumName` doctrine,
|
|
14
|
+
* pipeline/apiresource-meta.ts): an `ApiResourceRef.kind` is the enum
|
|
15
|
+
* MEMBER name and the derived policy id hashes the spec's text (Q-OR-9), so
|
|
16
|
+
* a lenient match would let "Organization" and "organization" mint two
|
|
17
|
+
* rows for one grant. Where the refusal runs (Q-S6-1, 2026-09-14): the
|
|
18
|
+
* controller calls it BEFORE position 1 on every lane, so no Authorizer in
|
|
19
|
+
* any edition is ever asked about a kind that is not a kind — a kind
|
|
20
|
+
* string that names no kind names no authorization target (the cloud's
|
|
21
|
+
* handlers kept this order; the resolver's unknown-kind deny stays as the
|
|
22
|
+
* backstop for a stored row on `get`). The grant path and the
|
|
23
|
+
* ValidateGrantableRole step keep their own calls for the callers that
|
|
24
|
+
* reach them without the controller (the lifecycle, the membership rules,
|
|
25
|
+
* the unit suites), so a garbage row never enters the store from any door.
|
|
26
|
+
*
|
|
27
|
+
* A spec missing either reference is a plain Error, not a refusal: the
|
|
28
|
+
* protovalidate boundary marks both `required`, so absence is a contract
|
|
29
|
+
* bug in a caller that bypassed it, never a client's mistake.
|
|
30
|
+
*/
|
|
31
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
32
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
33
|
+
import { kindByEnumName } from "../../pipeline/apiresource-meta.js";
|
|
34
|
+
import { malformedTripleField, malformedTripleMessage, unknownPrincipalKindMessage, unknownResourceKindMessage, } from "./constants.js";
|
|
35
|
+
/** The kind a `resource.kind` string names, or INVALID_ARGUMENT with the cloud's sentence. */
|
|
36
|
+
export function requireKnownResourceKind(kind) {
|
|
37
|
+
const resolved = kindByEnumName(kind);
|
|
38
|
+
if (resolved === ApiResourceKind.api_resource_kind_unknown) {
|
|
39
|
+
throw new ConnectError(unknownResourceKindMessage(kind), Code.InvalidArgument);
|
|
40
|
+
}
|
|
41
|
+
return resolved;
|
|
42
|
+
}
|
|
43
|
+
/** The kind a `principal.kind` string names, or INVALID_ARGUMENT with the principal sentence. */
|
|
44
|
+
export function requireKnownPrincipalKind(kind) {
|
|
45
|
+
const resolved = kindByEnumName(kind);
|
|
46
|
+
if (resolved === ApiResourceKind.api_resource_kind_unknown) {
|
|
47
|
+
throw new ConnectError(unknownPrincipalKindMessage(kind), Code.InvalidArgument);
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The whole triple, in the order the cloud refused it: no delimiter in any
|
|
53
|
+
* field (the canonical text would be ambiguous, Q-S2-1), then the resource
|
|
54
|
+
* kind, then the principal kind.
|
|
55
|
+
*/
|
|
56
|
+
export function requireWellFormedTriple(spec) {
|
|
57
|
+
const { principal, resource } = refsOf(spec);
|
|
58
|
+
const malformed = malformedTripleField(spec);
|
|
59
|
+
if (malformed !== undefined) {
|
|
60
|
+
throw new ConnectError(malformedTripleMessage(malformed), Code.InvalidArgument);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
principal,
|
|
64
|
+
resource,
|
|
65
|
+
resourceKind: requireKnownResourceKind(resource.kind),
|
|
66
|
+
principalKind: requireKnownPrincipalKind(principal.kind),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Both refs, present — absence is a caller that bypassed the boundary, not wire input. */
|
|
70
|
+
export function refsOf(spec) {
|
|
71
|
+
if (spec.principal === undefined || spec.resource === undefined) {
|
|
72
|
+
throw new Error("policy spec must carry principal and resource");
|
|
73
|
+
}
|
|
74
|
+
return { principal: spec.principal, resource: spec.resource };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=wire-refusals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-refusals.js","sourceRoot":"","sources":["../../../src/domain/iampolicy/wire-refusals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AAMtH,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AAUxB,8FAA8F;AAC9F,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,QAAQ,KAAK,eAAe,CAAC,yBAAyB,EAAE,CAAC;QAC3D,MAAM,IAAI,YAAY,CACpB,0BAA0B,CAAC,IAAI,CAAC,EAChC,IAAI,CAAC,eAAe,CACrB,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,QAAQ,KAAK,eAAe,CAAC,yBAAyB,EAAE,CAAC;QAC3D,MAAM,IAAI,YAAY,CACpB,2BAA2B,CAAC,IAAI,CAAC,EACjC,IAAI,CAAC,eAAe,CACrB,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAmB;IACzD,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,YAAY,CACpB,sBAAsB,CAAC,SAAS,CAAC,EACjC,IAAI,CAAC,eAAe,CACrB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,SAAS;QACT,QAAQ;QACR,YAAY,EAAE,wBAAwB,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrD,aAAa,EAAE,yBAAyB,CAAC,SAAS,CAAC,IAAI,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,MAAM,CAAC,IAAmB;IAIxC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;AAChE,CAAC"}
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-account domain constants (20260911.11, T01_1_review.md A1, A2,
|
|
3
|
+
* A7): the derived account id, the one function that turns a caller into
|
|
4
|
+
* an issuer subject, and the byte-pinned wire copy.
|
|
5
|
+
*
|
|
6
|
+
* The derived id (A1). A direct account's id is a pure function of its
|
|
7
|
+
* subject: `ida_` + the top 130 bits of sha256(idp_id) as 26 lowercase
|
|
8
|
+
* Crockford-base32 characters, through `derivedId` in
|
|
9
|
+
* pipeline/steps/defaults.ts (the one encoder every derived kind shares
|
|
10
|
+
* since 20260913.01 slice 2; the IamPolicy id is the other). The shape is
|
|
11
|
+
* indistinguishable from a minted `{prefix}_{ulid}`, so nothing downstream
|
|
12
|
+
* learns a second id grammar, and the PRIMARY KEY becomes the one home of
|
|
13
|
+
* "one account per subject": the generic store has no secondary uniqueness
|
|
14
|
+
* and its save is an upsert (`store/sqlite/store.ts`), so a
|
|
15
|
+
* lookup-then-insert on a scan could never close the first-login race. Two
|
|
16
|
+
* replicas deriving the same id converge by primary key with no migration
|
|
17
|
+
* and no second copy of any field. The hash is one-way, so an issuer
|
|
18
|
+
* subject never leaks into the `created_by.id` stamps that ride on every
|
|
19
|
+
* resource. The golden vectors in __tests__/constants.test.ts are
|
|
20
|
+
* wire-adjacent contract: a change here re-addresses every direct account
|
|
21
|
+
* open source ever created.
|
|
22
|
+
*
|
|
23
|
+
* `idpIdOf` (A2) is the ONE place a CallerIdentity becomes a subject. An
|
|
24
|
+
* issuer-vouched token is read for its `sub` (already verified at
|
|
25
|
+
* position 1, so this is a read of trusted state, never a verification);
|
|
26
|
+
* a caller with no issuer and no token is the trusted-local operator,
|
|
27
|
+
* whose subject is `local|<identityId>`. It deliberately knows nothing
|
|
28
|
+
* about API keys: every verifier resolves its stamp to the account when
|
|
29
|
+
* one exists (A6), so a credential's shape is the verifier's business.
|
|
30
|
+
*
|
|
31
|
+
* The copy moved from the cloud's iam/account/handlers.ts as-is: the CLI,
|
|
32
|
+
* console and SDK show these sentences verbatim, so they are contract.
|
|
33
|
+
*/
|
|
1
34
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
2
35
|
/** The kind's id prefix (kind_meta `identity_account.id_prefix`). */
|
|
3
36
|
export declare const ACCOUNT_ID_PREFIX = "ida";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAKtD;AAsBD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD;AAMD,uDAAuD;AACvD,eAAO,MAAM,oCAAoC,0DACQ,CAAC;AAE1D,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,kEACmC,CAAC;AAElE,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,0DACiB,CAAC;AAE1D,uGAAuG;AACvG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,yCACC,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,gFACmC,CAAC;AAEhF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
|
@@ -1,41 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Identity-account domain constants (20260911.11, T01_1_review.md A1, A2,
|
|
3
|
-
* A7): the derived account id, the one function that turns a caller into
|
|
4
|
-
* an issuer subject, and the byte-pinned wire copy.
|
|
5
|
-
*
|
|
6
|
-
* The derived id (A1). A direct account's id is a pure function of its
|
|
7
|
-
* subject: `ida_` + the top 130 bits of sha256(idp_id) as 26 lowercase
|
|
8
|
-
* Crockford-base32 characters. The shape is indistinguishable from a
|
|
9
|
-
* minted `{prefix}_{ulid}`, so nothing downstream learns a second id
|
|
10
|
-
* grammar, and the PRIMARY KEY becomes the one home of "one account per
|
|
11
|
-
* subject": the generic store has no secondary uniqueness and its save is
|
|
12
|
-
* an upsert (`store/sqlite/store.ts`), so a lookup-then-insert on a scan
|
|
13
|
-
* could never close the first-login race. Two replicas deriving the same
|
|
14
|
-
* id converge by primary key with no migration and no second copy of any
|
|
15
|
-
* field. The hash is one-way, so an issuer subject never leaks into the
|
|
16
|
-
* `created_by.id` stamps that ride on every resource. The golden vectors in
|
|
17
|
-
* __tests__/constants.test.ts are wire-adjacent contract: a change here
|
|
18
|
-
* re-addresses every direct account open source ever created.
|
|
19
|
-
*
|
|
20
|
-
* `idpIdOf` (A2) is the ONE place a CallerIdentity becomes a subject. An
|
|
21
|
-
* issuer-vouched token is read for its `sub` (already verified at
|
|
22
|
-
* position 1, so this is a read of trusted state, never a verification);
|
|
23
|
-
* a caller with no issuer and no token is the trusted-local operator,
|
|
24
|
-
* whose subject is `local|<identityId>`. It deliberately knows nothing
|
|
25
|
-
* about API keys: every verifier resolves its stamp to the account when
|
|
26
|
-
* one exists (A6), so a credential's shape is the verifier's business.
|
|
27
|
-
*
|
|
28
|
-
* The copy moved from the cloud's iam/account/handlers.ts as-is: the CLI,
|
|
29
|
-
* console and SDK show these sentences verbatim, so they are contract.
|
|
30
|
-
*/
|
|
31
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { derivedId } from "../../pipeline/steps/defaults.js";
|
|
32
2
|
/** The kind's id prefix (kind_meta `identity_account.id_prefix`). */
|
|
33
3
|
export const ACCOUNT_ID_PREFIX = "ida";
|
|
34
|
-
/** Crockford base32, lowercased — the alphabet every minted ULID id uses. */
|
|
35
|
-
const CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
36
|
-
const DERIVED_ID_CHARS = 26;
|
|
37
|
-
const DERIVED_ID_BITS = BigInt(DERIVED_ID_CHARS * 5);
|
|
38
|
-
const SHA256_BITS = 256n;
|
|
39
4
|
/**
|
|
40
5
|
* The derived account id of a direct account with subject `idpId`. Pure,
|
|
41
6
|
* total over non-empty input; an empty subject has no principal and so
|
|
@@ -45,18 +10,7 @@ export function accountIdFor(idpId) {
|
|
|
45
10
|
if (idpId === "") {
|
|
46
11
|
throw new Error("idp_id must not be empty");
|
|
47
12
|
}
|
|
48
|
-
|
|
49
|
-
let bits = 0n;
|
|
50
|
-
for (const byte of digest) {
|
|
51
|
-
bits = (bits << 8n) | BigInt(byte);
|
|
52
|
-
}
|
|
53
|
-
let top = bits >> (SHA256_BITS - DERIVED_ID_BITS);
|
|
54
|
-
let encoded = "";
|
|
55
|
-
for (let i = 0; i < DERIVED_ID_CHARS; i++) {
|
|
56
|
-
encoded = CROCKFORD_ALPHABET[Number(top & 31n)] + encoded;
|
|
57
|
-
top >>= 5n;
|
|
58
|
-
}
|
|
59
|
-
return `${ACCOUNT_ID_PREFIX}_${encoded}`;
|
|
13
|
+
return derivedId(ACCOUNT_ID_PREFIX, idpId);
|
|
60
14
|
}
|
|
61
15
|
/**
|
|
62
16
|
* The subject namespace of the trusted-local posture. An OIDC subject is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAE7D,qEAAqE;AACrE,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,SAAS,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AAE5C,mEAAmE;AACnE,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,OAAO,GAAG,mBAAmB,GAAG,UAAU,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CAAC,MAAsB;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACnD,OAAO,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,QAAgB;IACpC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CACxC,CAAC;QACvB,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAC;AAEjD,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,iEAAiE;AACjE,8EAA8E;AAE9E,uDAAuD;AACvD,MAAM,CAAC,MAAM,oCAAoC,GAC/C,uDAAuD,CAAC;AAE1D,2FAA2F;AAC3F,MAAM,CAAC,MAAM,iBAAiB,GAC5B,+DAA+D,CAAC;AAElE,qGAAqG;AACrG,MAAM,CAAC,MAAM,2BAA2B,GACtC,uDAAuD,CAAC;AAE1D,uGAAuG;AACvG,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO,+BAA+B,MAAM,EAAE,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GACrC,sCAAsC,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAC1C,6EAA6E,CAAC;AAEhF,MAAM,UAAU,8BAA8B,CAAC,MAAc;IAC3D,OAAO,GAAG,MAAM,wBAAwB,+BAA+B,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,aAAqB;IACzD,OAAO,iDAAiD,aAAa,mDAAmD,CAAC;AAC3H,CAAC"}
|