@stigmer/server 3.37.1 → 3.38.0
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 +3 -0
- package/dist/boot/compose.js.map +1 -1
- package/dist/domain/iampolicy/grant-path.d.ts +7 -3
- package/dist/domain/iampolicy/grant-path.d.ts.map +1 -1
- package/dist/domain/iampolicy/grant-path.js +10 -2
- package/dist/domain/iampolicy/grant-path.js.map +1 -1
- package/dist/domain/organization/controller.d.ts +4 -1
- package/dist/domain/organization/controller.d.ts.map +1 -1
- package/dist/domain/organization/controller.js +28 -4
- package/dist/domain/organization/controller.js.map +1 -1
- package/dist/domain/organization/steps.d.ts +22 -0
- package/dist/domain/organization/steps.d.ts.map +1 -1
- package/dist/domain/organization/steps.js +50 -2
- package/dist/domain/organization/steps.js.map +1 -1
- package/dist/extensions/gate-slots.d.ts +14 -1
- package/dist/extensions/gate-slots.d.ts.map +1 -1
- package/dist/extensions/gate-slots.js +1 -0
- package/dist/extensions/gate-slots.js.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.d.ts +6 -2
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +6 -2
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/package.json +6 -6
- package/src/boot/compose.ts +3 -0
- package/src/domain/iampolicy/__tests__/grant-path.test.ts +40 -7
- package/src/domain/iampolicy/grant-path.ts +19 -5
- package/src/domain/organization/__tests__/organization-delete.test.ts +266 -0
- package/src/domain/organization/controller.ts +40 -8
- package/src/domain/organization/steps.ts +66 -2
- package/src/extensions/__tests__/registry.test.ts +3 -0
- package/src/extensions/gate-slots.ts +14 -0
- package/src/pipeline/steps/authorization-tuples.ts +6 -2
|
@@ -10,16 +10,28 @@
|
|
|
10
10
|
* uniqueness guarantee, mirroring cloud's OrganizationCreateHandler
|
|
11
11
|
* (CheckDuplicate + CopySlugToId) step-for-step.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* The same deviation shapes the delete: a freed slug is anyone's to take,
|
|
14
|
+
* so newRevokeOrganizationPoliciesStep revokes the organization's policy
|
|
15
|
+
* rows BEFORE its row is deleted, and fails the delete when it cannot.
|
|
16
|
+
*
|
|
17
|
+
* Proven by organization.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
18
|
+
* __tests__/organization.test.ts and __tests__/organization-delete.test.ts.
|
|
15
19
|
*/
|
|
20
|
+
import { create } from "@bufbuild/protobuf";
|
|
21
|
+
import type { DescMessage } from "@bufbuild/protobuf";
|
|
22
|
+
|
|
16
23
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
17
24
|
import type { Store } from "../../store/interface.js";
|
|
18
25
|
import { alreadyExistsError, internalError } from "../../pipeline/errors.js";
|
|
19
26
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
20
27
|
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
28
|
+
import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
|
|
21
29
|
import { metadataOf } from "../../pipeline/steps/shapes.js";
|
|
30
|
+
import type { IamPolicyGrantPath } from "../iampolicy/grant-path.js";
|
|
22
31
|
|
|
32
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
33
|
+
import { ApiResourceRefSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
34
|
+
import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
23
35
|
import { OrganizationSchema } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
|
|
24
36
|
|
|
25
37
|
/**
|
|
@@ -98,3 +110,55 @@ export function newCopySlugToIdStep(): PipelineStep<typeof OrganizationSchema> {
|
|
|
98
110
|
},
|
|
99
111
|
};
|
|
100
112
|
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Revokes every policy row that names the organization, as principal or as
|
|
116
|
+
* resource, before the delete removes its row: the rows its members hold
|
|
117
|
+
* on it, and the scope link every organization-scoped resource holds to
|
|
118
|
+
* it. Runs after the `org-delete:pre-delete` slot and before
|
|
119
|
+
* DeleteResource.
|
|
120
|
+
*
|
|
121
|
+
* The generic delete chains clean up after the row and log a fault,
|
|
122
|
+
* because a deleted resource's rows grant nothing once it is gone. An
|
|
123
|
+
* organization is the exception: its id is its slug, the delete frees the
|
|
124
|
+
* slug, and a row left behind would grant whoever creates the slug next.
|
|
125
|
+
* So this step does not catch. A fault fails the delete with the
|
|
126
|
+
* organization in place, and a retry resumes where the revocation stopped
|
|
127
|
+
* (the grant path revokes the organization's owners last, so the owner
|
|
128
|
+
* who retries still can). The grant path fires the composed driver's
|
|
129
|
+
* `onPolicyRevoked` before each row, so a composition's tuples go with
|
|
130
|
+
* their rows. The post-delete CleanupIamPolicies still runs: it catches
|
|
131
|
+
* whatever a concurrent write named the organization with in between.
|
|
132
|
+
*/
|
|
133
|
+
export function newRevokeOrganizationPoliciesStep<Desc extends DescMessage>(
|
|
134
|
+
grantPath: IamPolicyGrantPath,
|
|
135
|
+
): PipelineStep<Desc> {
|
|
136
|
+
return {
|
|
137
|
+
name: "RevokeOrganizationPolicies",
|
|
138
|
+
async execute(ctx: RequestContext<Desc>): Promise<void> {
|
|
139
|
+
const organization = ctx.get(EXISTING_RESOURCE_KEY) as
|
|
140
|
+
| Organization
|
|
141
|
+
| undefined;
|
|
142
|
+
const id = organization?.metadata?.id ?? "";
|
|
143
|
+
if (id === "") {
|
|
144
|
+
throw internalError(
|
|
145
|
+
new Error("organization delete reached RevokeOrganizationPolicies without its loaded row"),
|
|
146
|
+
"failed to remove the organization's access policies",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await grantPath.cleanupResource(
|
|
151
|
+
create(ApiResourceRefSchema, {
|
|
152
|
+
kind: ApiResourceKind[ApiResourceKind.organization],
|
|
153
|
+
id,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw internalError(
|
|
158
|
+
error,
|
|
159
|
+
"failed to remove the organization's access policies",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -491,6 +491,9 @@ describe("resolveExtensions — loud-fail throws (DD-006 §2b)", () => {
|
|
|
491
491
|
// The ninth: the organization create chain before Persist, where a
|
|
492
492
|
// limit on which organizations may exist refuses with nothing written.
|
|
493
493
|
"org-create:pre-side-effect-gate",
|
|
494
|
+
// The tenth: the organization delete chain before any write, where an
|
|
495
|
+
// edition removes or refuses what it keeps for the organization.
|
|
496
|
+
"org-delete:pre-delete",
|
|
494
497
|
"sandbox-acquisition:gate",
|
|
495
498
|
"session-create:pre-side-effect-gate",
|
|
496
499
|
]);
|
|
@@ -53,6 +53,19 @@
|
|
|
53
53
|
* limit belongs here too. `apply` delegates to create on its create arm,
|
|
54
54
|
* so the slot fires there as well.
|
|
55
55
|
*
|
|
56
|
+
* The tenth, `org-delete:pre-delete`: the organization delete chain after
|
|
57
|
+
* LoadExistingForDelete and before any write, so the organization exists
|
|
58
|
+
* and is loaded (EXISTING_RESOURCE_KEY) while its steps run. An
|
|
59
|
+
* organization's id is its slug and its delete frees the slug for anyone,
|
|
60
|
+
* so whatever an edition keeps for the organization must go before the row
|
|
61
|
+
* does, or be refused: a row left behind would belong to whoever creates
|
|
62
|
+
* the slug next. Its steps may refuse (Enterprise refuses while an
|
|
63
|
+
* identity provider still signs in platform-managed organizations) or
|
|
64
|
+
* remove the edition's own rows; either way a throw fails the delete with
|
|
65
|
+
* the organization intact, and the retry re-runs every step, so each owns
|
|
66
|
+
* its idempotency. After the slot the chain revokes the organization's own
|
|
67
|
+
* policy rows, also before the row and also failing closed.
|
|
68
|
+
*
|
|
56
69
|
* Two enforcement layers, deliberately redundant, both derived from the
|
|
57
70
|
* ONE literal tuple below (lockstep by construction):
|
|
58
71
|
* - GateSlotName (compile time): the union of declared slot literals.
|
|
@@ -92,6 +105,7 @@ export const GATE_SLOT_NAMES = [
|
|
|
92
105
|
"identity-account-provision:post-persist",
|
|
93
106
|
"iam-policy-create:pre-side-effect-gate",
|
|
94
107
|
"org-create:pre-side-effect-gate",
|
|
108
|
+
"org-delete:pre-delete",
|
|
95
109
|
] as const;
|
|
96
110
|
|
|
97
111
|
/** The declared slot-name union — a registration outside it fails tsc. */
|
|
@@ -330,8 +330,12 @@ export function newCreateAuthorizationTuplesStep<Desc extends DescMessage>(
|
|
|
330
330
|
/**
|
|
331
331
|
* CleanupIamPolicies — after the store delete in every delete chain.
|
|
332
332
|
* Best-effort by contract: a driver failure is logged and the delete
|
|
333
|
-
* succeeds (Java's DeleteOperationCleanupIamPoliciesStep swallows all
|
|
334
|
-
*
|
|
333
|
+
* succeeds (Java's DeleteOperationCleanupIamPoliciesStep swallows all),
|
|
334
|
+
* because a deleted resource's rows grant nothing once it is gone. The
|
|
335
|
+
* organization is the one kind whose id another caller can take next, so
|
|
336
|
+
* its chain revokes the organization's rows before the row, failing
|
|
337
|
+
* closed (RevokeOrganizationPolicies, domain/organization/steps.ts), and
|
|
338
|
+
* this step is only its backstop there.
|
|
335
339
|
*/
|
|
336
340
|
export function newCleanupIamPoliciesStep<Desc extends DescMessage>(
|
|
337
341
|
lifecycle: ResourceAuthorizationLifecycle | undefined,
|