@stigmer/server 3.14.1 → 3.15.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.
Files changed (116) hide show
  1. package/dist/boot/compose.d.ts +8 -1
  2. package/dist/boot/compose.d.ts.map +1 -1
  3. package/dist/boot/compose.js +98 -12
  4. package/dist/boot/compose.js.map +1 -1
  5. package/dist/domain/apikey/verifier.d.ts +8 -1
  6. package/dist/domain/apikey/verifier.d.ts.map +1 -1
  7. package/dist/domain/apikey/verifier.js +14 -2
  8. package/dist/domain/apikey/verifier.js.map +1 -1
  9. package/dist/domain/identityaccount/constants.d.ts +66 -0
  10. package/dist/domain/identityaccount/constants.d.ts.map +1 -0
  11. package/dist/domain/identityaccount/constants.js +153 -0
  12. package/dist/domain/identityaccount/constants.js.map +1 -0
  13. package/dist/domain/identityaccount/controller.d.ts +34 -0
  14. package/dist/domain/identityaccount/controller.d.ts.map +1 -0
  15. package/dist/domain/identityaccount/controller.js +342 -0
  16. package/dist/domain/identityaccount/controller.js.map +1 -0
  17. package/dist/domain/identityaccount/operator.d.ts +14 -0
  18. package/dist/domain/identityaccount/operator.d.ts.map +1 -0
  19. package/dist/domain/identityaccount/operator.js +67 -0
  20. package/dist/domain/identityaccount/operator.js.map +1 -0
  21. package/dist/domain/identityaccount/provisioning.d.ts +52 -0
  22. package/dist/domain/identityaccount/provisioning.d.ts.map +1 -0
  23. package/dist/domain/identityaccount/provisioning.js +114 -0
  24. package/dist/domain/identityaccount/provisioning.js.map +1 -0
  25. package/dist/domain/identityaccount/resolve.d.ts +36 -0
  26. package/dist/domain/identityaccount/resolve.d.ts.map +1 -0
  27. package/dist/domain/identityaccount/resolve.js +12 -0
  28. package/dist/domain/identityaccount/resolve.js.map +1 -0
  29. package/dist/domain/identityaccount/resource-store.d.ts +4 -0
  30. package/dist/domain/identityaccount/resource-store.d.ts.map +1 -0
  31. package/dist/domain/identityaccount/resource-store.js +131 -0
  32. package/dist/domain/identityaccount/resource-store.js.map +1 -0
  33. package/dist/domain/identityaccount/steps.d.ts +79 -0
  34. package/dist/domain/identityaccount/steps.d.ts.map +1 -0
  35. package/dist/domain/identityaccount/steps.js +291 -0
  36. package/dist/domain/identityaccount/steps.js.map +1 -0
  37. package/dist/domain/identityaccount/store-contract.d.ts +22 -0
  38. package/dist/domain/identityaccount/store-contract.d.ts.map +1 -0
  39. package/dist/domain/identityaccount/store-contract.js +307 -0
  40. package/dist/domain/identityaccount/store-contract.js.map +1 -0
  41. package/dist/domain/identityaccount/store.d.ts +66 -0
  42. package/dist/domain/identityaccount/store.d.ts.map +1 -0
  43. package/dist/domain/identityaccount/store.js +13 -0
  44. package/dist/domain/identityaccount/store.js.map +1 -0
  45. package/dist/extensions/drivers.d.ts +26 -0
  46. package/dist/extensions/drivers.d.ts.map +1 -1
  47. package/dist/extensions/gate-slots.d.ts +16 -1
  48. package/dist/extensions/gate-slots.d.ts.map +1 -1
  49. package/dist/extensions/gate-slots.js +1 -0
  50. package/dist/extensions/gate-slots.js.map +1 -1
  51. package/dist/extensions/identity-federation.d.ts +36 -0
  52. package/dist/extensions/identity-federation.d.ts.map +1 -0
  53. package/dist/extensions/identity-federation.js +2 -0
  54. package/dist/extensions/identity-federation.js.map +1 -0
  55. package/dist/extensions/registry.d.ts +18 -1
  56. package/dist/extensions/registry.d.ts.map +1 -1
  57. package/dist/extensions/registry.js +20 -0
  58. package/dist/extensions/registry.js.map +1 -1
  59. package/dist/identity/oidc-discovery.d.ts +36 -0
  60. package/dist/identity/oidc-discovery.d.ts.map +1 -0
  61. package/dist/identity/oidc-discovery.js +60 -0
  62. package/dist/identity/oidc-discovery.js.map +1 -0
  63. package/dist/identity/oidc-userinfo.d.ts +30 -0
  64. package/dist/identity/oidc-userinfo.d.ts.map +1 -0
  65. package/dist/identity/oidc-userinfo.js +28 -0
  66. package/dist/identity/oidc-userinfo.js.map +1 -0
  67. package/dist/identity/oidc-verifier.d.ts +6 -0
  68. package/dist/identity/oidc-verifier.d.ts.map +1 -1
  69. package/dist/identity/oidc-verifier.js +35 -34
  70. package/dist/identity/oidc-verifier.js.map +1 -1
  71. package/dist/index.d.ts +7 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/pipeline/interceptors/auth.d.ts +10 -0
  76. package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
  77. package/dist/pipeline/interceptors/auth.js +9 -1
  78. package/dist/pipeline/interceptors/auth.js.map +1 -1
  79. package/package.json +4 -4
  80. package/src/boot/__tests__/auth-enabled-composition.test.ts +226 -2
  81. package/src/boot/compose.ts +118 -13
  82. package/src/domain/apikey/__tests__/verifier.test.ts +146 -22
  83. package/src/domain/apikey/verifier.ts +24 -2
  84. package/src/domain/identityaccount/__tests__/constants.test.ts +155 -0
  85. package/src/domain/identityaccount/__tests__/identityaccount.test.ts +484 -0
  86. package/src/domain/identityaccount/__tests__/operator.test.ts +180 -0
  87. package/src/domain/identityaccount/__tests__/provisioning.test.ts +281 -0
  88. package/src/domain/identityaccount/__tests__/resolve.test.ts +114 -0
  89. package/src/domain/identityaccount/__tests__/resource-store.test.ts +216 -0
  90. package/src/domain/identityaccount/__tests__/support.ts +58 -0
  91. package/src/domain/identityaccount/constants.ts +178 -0
  92. package/src/domain/identityaccount/controller.ts +667 -0
  93. package/src/domain/identityaccount/operator.ts +94 -0
  94. package/src/domain/identityaccount/provisioning.ts +182 -0
  95. package/src/domain/identityaccount/resolve.ts +52 -0
  96. package/src/domain/identityaccount/resource-store.ts +157 -0
  97. package/src/domain/identityaccount/steps.ts +377 -0
  98. package/src/domain/identityaccount/store-contract.ts +471 -0
  99. package/src/domain/identityaccount/store.ts +70 -0
  100. package/src/extensions/__tests__/identity-account-composed.test.ts +803 -0
  101. package/src/extensions/__tests__/identity-account-points.test.ts +129 -0
  102. package/src/extensions/__tests__/registry.test.ts +4 -0
  103. package/src/extensions/__tests__/tier-truthfulness.test.ts +299 -0
  104. package/src/extensions/drivers.ts +26 -0
  105. package/src/extensions/gate-slots.ts +16 -0
  106. package/src/extensions/identity-federation.ts +58 -0
  107. package/src/extensions/registry.ts +44 -1
  108. package/src/identity/__tests__/oidc-discovery.test.ts +122 -0
  109. package/src/identity/__tests__/oidc-userinfo.test.ts +103 -0
  110. package/src/identity/__tests__/oidc-verifier.test.ts +173 -3
  111. package/src/identity/oidc-discovery.ts +84 -0
  112. package/src/identity/oidc-userinfo.ts +64 -0
  113. package/src/identity/oidc-verifier.ts +42 -45
  114. package/src/index.ts +19 -0
  115. package/src/pipeline/interceptors/auth.ts +13 -1
  116. package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +184 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Identity-account domain steps (20260911.11; T01_1_review.md A1, A9).
3
+ *
4
+ * This is the first OSS domain whose persistence is a PORT
5
+ * (IdentityAccountStore) rather than the generic Store, so the cloud can
6
+ * serve the same controller over its own table. The shared steps that
7
+ * touch storage (CheckDuplicate, LoadExisting, LoadTarget, Persist, the
8
+ * delete pair) are typed over `Store`; this module brings the domain's
9
+ * own under the SAME names, so a chain reads onto the inventory unchanged
10
+ * (the organization domain's "own step, same name" precedent), and reuses
11
+ * every storage-free shared step as it is. The shared context keys are
12
+ * reused too, so BuildUpdateState finds EXISTING_RESOURCE_KEY exactly
13
+ * where it expects it.
14
+ *
15
+ * The domain's own rules:
16
+ * - DefaultAccountName: `metadata.name` falls back to the email, then
17
+ * the subject. ResolveSlug refuses an empty name and the field is not
18
+ * proto-required, and a machine subject whose IdP releases no email
19
+ * must still provision.
20
+ * - DeriveAccountId (A1): `metadata.id = accountIdFor(spec.idp_id)`,
21
+ * replacing whatever the caller sent (the organization's CopySlugToId
22
+ * precedent, `metadata.proto`'s documented exception). Runs BEFORE
23
+ * BuildNewState, which mints only when the id is empty.
24
+ * - CheckDuplicate: by SUBJECT, a primary-key read of the derived id —
25
+ * two accounts may share an email; the account's uniqueness is its
26
+ * subject, never its slug (the guideline's "domains with different
27
+ * uniqueness rules bring their own step under the same name").
28
+ * - AssignBackendFields: `is_machine_account` from the `@clients`
29
+ * suffix and `provisioning_mode direct` on create (the spec's
30
+ * "assigned by backend" fields); on update, every backend-assigned
31
+ * field and the email are preserved from the existing row (A9: the
32
+ * cloud's writable surface — first/last name, picture, preferences —
33
+ * and nothing else; the `preserveImmutableFields` shape).
34
+ * - GuardImmutableSubject (A1): a changed `spec.idp_id` is refused
35
+ * FAILED_PRECONDITION (the schedule domain's shape), never silently
36
+ * preserved — the subject IS the identity, and a client that sends a
37
+ * different one has a bug it should hear about.
38
+ *
39
+ * NOT_FOUND copy is the domain's (`Identity account not found: <id>`, the
40
+ * cloud's byte-pinned sentence); the shared `notFoundError` would render
41
+ * the kind_meta name `IdentityAccount`.
42
+ */
43
+ import { Code, ConnectError } from "@connectrpc/connect";
44
+ import { create } from "@bufbuild/protobuf";
45
+ import { IdentityAccountProvisioningMode } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/enum_pb";
46
+ import { IdentityAccountSpecSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/spec_pb";
47
+ import { alreadyExistsError, failedPreconditionError, internalError, invalidArgumentError, } from "../../pipeline/errors.js";
48
+ import { RESOURCE_ID_KEY } from "../../pipeline/steps/delete.js";
49
+ import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
50
+ import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
51
+ import { idValueOf, metadataOf } from "../../pipeline/steps/shapes.js";
52
+ import { accountIdFor, accountNotFoundMessage, idpIdImmutableMessage, isMachineSubject, } from "./constants.js";
53
+ import { DuplicateAccountError } from "./store.js";
54
+ /** The kind's name as the shared error constructors render it. */
55
+ const KIND_NAME = "IdentityAccount";
56
+ /** `Identity account not found: <handle>` as a NOT_FOUND ConnectError. */
57
+ export function accountNotFoundError(handle) {
58
+ return new ConnectError(accountNotFoundMessage(handle), Code.NotFound);
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Create
62
+ // ---------------------------------------------------------------------------
63
+ export function newDefaultAccountNameStep() {
64
+ return {
65
+ name: "DefaultAccountName",
66
+ execute(ctx) {
67
+ const metadata = requireMetadata(ctx, "default account name");
68
+ if (metadata.name === "") {
69
+ const spec = specOf(ctx);
70
+ metadata.name = spec.email !== "" ? spec.email : spec.idpId;
71
+ }
72
+ },
73
+ };
74
+ }
75
+ export function newDeriveAccountIdStep() {
76
+ return {
77
+ name: "DeriveAccountId",
78
+ execute(ctx) {
79
+ const metadata = requireMetadata(ctx, "derive account id");
80
+ // ValidateProto ran upstream (idp_id is required), so an empty
81
+ // subject here is a pipeline-ordering bug, not bad client input.
82
+ const idpId = specOf(ctx).idpId;
83
+ if (idpId === "") {
84
+ throw internalError(new Error("identity account idp_id is empty"), "derive account id");
85
+ }
86
+ metadata.id = accountIdFor(idpId);
87
+ },
88
+ };
89
+ }
90
+ /** Rejects a create whose SUBJECT already has an account (a primary-key read). */
91
+ export function newCheckDuplicateStep(accounts) {
92
+ return {
93
+ name: "CheckDuplicate",
94
+ async execute(ctx) {
95
+ const metadata = requireMetadata(ctx, "duplicate check");
96
+ const idpId = specOf(ctx).idpId;
97
+ let existing;
98
+ try {
99
+ existing = await accounts.findById(metadata.id);
100
+ }
101
+ catch (error) {
102
+ throw internalError(error, "failed to check for duplicate identity account");
103
+ }
104
+ if (existing !== undefined) {
105
+ throw alreadyExistsError(KIND_NAME, `subject '${idpId}'`);
106
+ }
107
+ },
108
+ };
109
+ }
110
+ /**
111
+ * The backend-assigned fields of a DIRECT account, applied in place: the
112
+ * machine flag from the subject's shape, `provisioning_mode direct`, no
113
+ * provider ref (a direct account is the platform's own subject). The ONE
114
+ * writer of these fields; the provisioner builds its spec through it too.
115
+ */
116
+ export function assignDirectBackendFields(spec) {
117
+ spec.isMachineAccount = isMachineSubject(spec.idpId);
118
+ spec.provisioningMode = IdentityAccountProvisioningMode.direct;
119
+ spec.identityProviderRef = undefined;
120
+ return spec;
121
+ }
122
+ export function newAssignBackendFieldsStep() {
123
+ return {
124
+ name: "AssignBackendFields",
125
+ execute(ctx) {
126
+ assignDirectBackendFields(specOf(ctx));
127
+ },
128
+ };
129
+ }
130
+ /** Saves the new account through the port; a held subject is ALREADY_EXISTS. */
131
+ export function newPersistNewAccountStep(accounts) {
132
+ return {
133
+ name: "Persist",
134
+ async execute(ctx) {
135
+ try {
136
+ await accounts.save(ctx.newState);
137
+ }
138
+ catch (error) {
139
+ if (error instanceof DuplicateAccountError) {
140
+ // The race the duplicate check could not see: another writer
141
+ // took the subject between the read and this save. The
142
+ // provisioner's race arm resolves the winner from this code.
143
+ throw alreadyExistsError(KIND_NAME, `subject '${specOf(ctx).idpId}'`);
144
+ }
145
+ throw internalError(error, "failed to save identity account");
146
+ }
147
+ },
148
+ };
149
+ }
150
+ // ---------------------------------------------------------------------------
151
+ // Update
152
+ // ---------------------------------------------------------------------------
153
+ /** Loads the existing account by id into EXISTING_RESOURCE_KEY; the domain's NOT_FOUND copy. */
154
+ export function newLoadExistingAccountStep(accounts) {
155
+ return {
156
+ name: "LoadExisting",
157
+ async execute(ctx) {
158
+ const metadata = requireMetadata(ctx, "load existing");
159
+ if (metadata.id === "") {
160
+ // By id only: an account's slug is derived from its display name
161
+ // and two accounts may share one, so a slug lookup could never be
162
+ // an identity.
163
+ throw invalidArgumentError("resource id is required for update");
164
+ }
165
+ const existing = await readAccount(accounts, metadata.id);
166
+ ctx.set(EXISTING_RESOURCE_KEY, existing);
167
+ },
168
+ };
169
+ }
170
+ export function newGuardImmutableSubjectStep() {
171
+ return {
172
+ name: "GuardImmutableSubject",
173
+ execute(ctx) {
174
+ const existing = existingAccountOf(ctx);
175
+ const existingIdpId = existing.spec?.idpId ?? "";
176
+ if (specOf(ctx).idpId !== existingIdpId) {
177
+ throw failedPreconditionError(idpIdImmutableMessage(existingIdpId));
178
+ }
179
+ },
180
+ };
181
+ }
182
+ /** Preserves the backend-assigned fields and the email from the existing row (A9). */
183
+ export function newPreserveBackendFieldsStep() {
184
+ return {
185
+ name: "AssignBackendFields",
186
+ execute(ctx) {
187
+ const existingSpec = existingAccountOf(ctx).spec;
188
+ const spec = specOf(ctx);
189
+ spec.idpId = existingSpec?.idpId ?? spec.idpId;
190
+ spec.email = existingSpec?.email ?? "";
191
+ spec.isMachineAccount = existingSpec?.isMachineAccount ?? false;
192
+ spec.provisioningMode =
193
+ existingSpec?.provisioningMode ??
194
+ IdentityAccountProvisioningMode.identity_account_provisioning_mode_unspecified;
195
+ spec.identityProviderRef = existingSpec?.identityProviderRef;
196
+ },
197
+ };
198
+ }
199
+ /** Replaces the row through the port. */
200
+ export function newPersistUpdatedAccountStep(accounts) {
201
+ return {
202
+ name: "Persist",
203
+ async execute(ctx) {
204
+ try {
205
+ await accounts.update(ctx.newState);
206
+ }
207
+ catch (error) {
208
+ throw internalError(error, "failed to save identity account");
209
+ }
210
+ },
211
+ };
212
+ }
213
+ // ---------------------------------------------------------------------------
214
+ // Get / Delete (ID-wrapper inputs)
215
+ // ---------------------------------------------------------------------------
216
+ /** Loads the account named by an ID-wrapper input into TARGET_RESOURCE_KEY. */
217
+ export function newLoadTargetAccountStep(accounts) {
218
+ return {
219
+ name: "LoadTarget",
220
+ async execute(ctx) {
221
+ const id = idValueOf(ctx.input);
222
+ if (id === "") {
223
+ throw invalidArgumentError("resource id is required");
224
+ }
225
+ ctx.set(TARGET_RESOURCE_KEY, await readAccount(accounts, id));
226
+ },
227
+ };
228
+ }
229
+ /** Loads the doomed account (after ExtractResourceId) so delete can return it. */
230
+ export function newLoadExistingAccountForDeleteStep(accounts) {
231
+ return {
232
+ name: "LoadExistingForDelete",
233
+ async execute(ctx) {
234
+ ctx.set(EXISTING_RESOURCE_KEY, await readAccount(accounts, requireResourceId(ctx)));
235
+ },
236
+ };
237
+ }
238
+ export function newDeleteAccountStep(accounts) {
239
+ return {
240
+ name: "DeleteResource",
241
+ async execute(ctx) {
242
+ try {
243
+ await accounts.deleteById(requireResourceId(ctx));
244
+ }
245
+ catch (error) {
246
+ throw internalError(error, "failed to delete identity account");
247
+ }
248
+ },
249
+ };
250
+ }
251
+ // ---------------------------------------------------------------------------
252
+ // Shared readers
253
+ // ---------------------------------------------------------------------------
254
+ /** The account by id, or the domain's NOT_FOUND; any other fault propagates. */
255
+ async function readAccount(accounts, id) {
256
+ const account = await accounts.findById(id);
257
+ if (account === undefined) {
258
+ throw accountNotFoundError(id);
259
+ }
260
+ return account;
261
+ }
262
+ function requireMetadata(ctx, operation) {
263
+ const metadata = metadataOf(ctx.newState);
264
+ if (metadata === undefined) {
265
+ throw internalError(new Error("identity account metadata is nil"), operation);
266
+ }
267
+ return metadata;
268
+ }
269
+ /** The spec being built, created in place when the caller omitted it. */
270
+ function specOf(ctx) {
271
+ const state = ctx.newState;
272
+ if (state.spec === undefined) {
273
+ state.spec = create(IdentityAccountSpecSchema);
274
+ }
275
+ return state.spec;
276
+ }
277
+ function existingAccountOf(ctx) {
278
+ const existing = ctx.get(EXISTING_RESOURCE_KEY);
279
+ if (existing === undefined) {
280
+ throw internalError(new Error("existing identity account not found in context - LoadExisting must run first"), "identity account update ordering");
281
+ }
282
+ return existing;
283
+ }
284
+ function requireResourceId(ctx) {
285
+ const id = ctx.get(RESOURCE_ID_KEY);
286
+ if (typeof id !== "string" || id === "") {
287
+ throw internalError(new Error("resource id not found in context (ExtractResourceId must run first)"), "identity account delete ordering");
288
+ }
289
+ return id;
290
+ }
291
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/steps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { IdentityAccountStore } from "./store.js";
2
+ /** One fresh, isolated store per case, plus the one escape hatch the port cannot express. */
3
+ export interface IdentityAccountStoreContractFixture {
4
+ readonly store: IdentityAccountStore;
5
+ /**
6
+ * Cuts the store from its database so every later call is an
7
+ * infrastructure fault. `cleanup` still runs afterwards and must tolerate
8
+ * a disconnected store (both OSS drivers' `close` is idempotent; a pool
9
+ * fixture ends a per-case pool here and skips it in cleanup).
10
+ */
11
+ disconnect(): Promise<void>;
12
+ cleanup(): Promise<void>;
13
+ }
14
+ export interface IdentityAccountStoreContractCase {
15
+ /** The contract line, in the port's words; the framework prints it as the test name. */
16
+ readonly name: string;
17
+ /** Makes a FRESH fixture, runs the case, cleans up. A failing assertion wins over a failing cleanup. */
18
+ run(): Promise<void>;
19
+ }
20
+ /** The port's cases over `makeFixture`, one fresh fixture per case. */
21
+ export declare function identityAccountStoreContract(makeFixture: () => Promise<IdentityAccountStoreContractFixture>): ReadonlyArray<IdentityAccountStoreContractCase>;
22
+ //# sourceMappingURL=store-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-contract.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/store-contract.ts"],"names":[],"mappings":"AAwDA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,6FAA6F;AAC7F,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC;;;;;OAKG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,gCAAgC;IAC/C,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wGAAwG;IACxG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAkYD,uEAAuE;AACvE,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,OAAO,CAAC,mCAAmC,CAAC,GAC9D,aAAa,CAAC,gCAAgC,CAAC,CAKjD"}
@@ -0,0 +1,307 @@
1
+ /**
2
+ * The IdentityAccountStore PORT-CONTRACT KIT (20260911.11, T01_1_review.md
3
+ * A11 and A12): every behavior an implementation of store.ts must satisfy
4
+ * identically, as cases a driver's test iterates. Open source runs them
5
+ * over its own adapter (resource-store.ts) on sqlite and Postgres in
6
+ * __tests__/resource-store.test.ts; a composition runs the SAME cases over
7
+ * the store it registers as `drivers.identityAccountStore` (the cloud's
8
+ * `cloud.iam_identity_account` store), so "the port holds" is one
9
+ * statement proven per driver, never restated per repository.
10
+ *
11
+ * Shape. The package's precedent is store/__tests__/store-contract.ts —
12
+ * `describeStoreContract(makeFixture)`: a fresh fixture per test, named
13
+ * escape hatches for the arms the interface cannot express, driver-physical
14
+ * behavior left to each driver's own tests. This kit follows it in
15
+ * everything but the framework: it is consumed by ANOTHER package's tests,
16
+ * so it ships in dist/ and must not import vitest — a devDependency that
17
+ * would enter the root barrel's runtime import graph. It therefore returns
18
+ * cases instead of calling `describe`, asserts through node:assert/strict,
19
+ * and the consumer's framework does `for (const c of cases) it(c.name,
20
+ * c.run)`. Do not "fix" it back to the precedent's vitest shape.
21
+ *
22
+ * Every case makes a FRESH fixture, runs, and cleans up; a failing
23
+ * assertion wins over a failing cleanup. `disconnect` is the one escape
24
+ * hatch: the port has no lifecycle by design (a composition owns its
25
+ * store's), yet "an outage never reads as no account" is the line that
26
+ * matters most, so the fixture cuts the store from its database and the
27
+ * kit asserts that the fault propagates instead of reading as `undefined`.
28
+ *
29
+ * What the kit deliberately does not carry: open source's primary-key read
30
+ * (the findByField spy) and its derived-id refusal, the cloud's column
31
+ * layout and SQLSTATE 23505 mapping — driver-physical, each driver's own
32
+ * tests. Nor does any case hold a direct and a federated row under one
33
+ * subject: open source has no federated writer and its derived-id key could
34
+ * not hold both. The federated fixture carries the derived id of its
35
+ * subject, so the OSS adapter's mode filter — not an unreachable id — is
36
+ * what the direct-only arms exercise.
37
+ *
38
+ * The duplicate arm checks `instanceof DuplicateAccountError` on the
39
+ * exported class, because that is how the domain's race arm (steps.ts)
40
+ * catches it: a driver throwing a lookalike fails here for the reason it
41
+ * would fail provisioning.
42
+ *
43
+ * Case names are the contract lines, in the port's words. The OSS test pins
44
+ * the full list, so a case cannot drop out of the kit unnoticed.
45
+ */
46
+ import assert from "node:assert/strict";
47
+ import { create, equals } from "@bufbuild/protobuf";
48
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
49
+ import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
50
+ import { IdentityAccountProvisioningMode } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/enum_pb";
51
+ import { accountIdFor } from "./constants.js";
52
+ import { DuplicateAccountError } from "./store.js";
53
+ /** A direct account as the domain writes it: id derived from the subject, mode direct. */
54
+ function directAccount(overrides) {
55
+ return create(IdentityAccountSchema, {
56
+ apiVersion: "iam.stigmer.ai/v1",
57
+ kind: "IdentityAccount",
58
+ metadata: {
59
+ id: accountIdFor(overrides.idpId),
60
+ name: overrides.email ?? overrides.idpId,
61
+ },
62
+ spec: {
63
+ idpId: overrides.idpId,
64
+ email: overrides.email ?? "",
65
+ firstName: overrides.firstName ?? "",
66
+ provisioningMode: IdentityAccountProvisioningMode.direct,
67
+ },
68
+ });
69
+ }
70
+ /**
71
+ * A federated account: a customer IdP's subject, marked by its
72
+ * identity_provider_ref and mode. Its id is the derived id of its subject
73
+ * (see the header: the mode filter, not the id, must be what excludes it).
74
+ */
75
+ function federatedAccount(overrides) {
76
+ return create(IdentityAccountSchema, {
77
+ apiVersion: "iam.stigmer.ai/v1",
78
+ kind: "IdentityAccount",
79
+ metadata: { id: accountIdFor(overrides.idpId), name: overrides.email },
80
+ spec: {
81
+ idpId: overrides.idpId,
82
+ email: overrides.email,
83
+ provisioningMode: IdentityAccountProvisioningMode.federated,
84
+ identityProviderRef: {
85
+ org: "acme",
86
+ kind: ApiResourceKind.identity_provider,
87
+ slug: "acme-okta",
88
+ },
89
+ },
90
+ });
91
+ }
92
+ /** A direct account with no subject — the row no lookup could ever reach. */
93
+ function subjectlessDirectAccount() {
94
+ return create(IdentityAccountSchema, {
95
+ apiVersion: "iam.stigmer.ai/v1",
96
+ kind: "IdentityAccount",
97
+ metadata: { id: "ida_00000000000000000000000000", name: "subjectless" },
98
+ spec: {
99
+ idpId: "",
100
+ provisioningMode: IdentityAccountProvisioningMode.direct,
101
+ },
102
+ });
103
+ }
104
+ /** The id of the account, asserted present so a message names the row, not `undefined`. */
105
+ function idOf(account) {
106
+ const id = account.metadata?.id ?? "";
107
+ assert.notEqual(id, "", "a fixture account must carry an id");
108
+ return id;
109
+ }
110
+ function subjectsOf(accounts) {
111
+ return accounts.map((account) => account.spec?.idpId ?? "");
112
+ }
113
+ const CASES = [
114
+ [
115
+ "save then findById round-trips the account",
116
+ async ({ store }) => {
117
+ const account = directAccount({
118
+ idpId: "auth0|alice",
119
+ email: "alice@example.com",
120
+ });
121
+ await store.save(account);
122
+ const found = await store.findById(idOf(account));
123
+ assert.ok(found !== undefined, "a saved account must be readable by id");
124
+ assert.ok(equals(IdentityAccountSchema, found, account), "findById must return the account exactly as it was saved");
125
+ },
126
+ ],
127
+ [
128
+ "a second save under a held id is DuplicateAccountError, and the first row stands",
129
+ async ({ store }) => {
130
+ const first = directAccount({ idpId: "auth0|bob", firstName: "First" });
131
+ const second = directAccount({
132
+ idpId: "auth0|bob",
133
+ firstName: "Second",
134
+ });
135
+ await store.save(first);
136
+ await assert.rejects(store.save(second), DuplicateAccountError, "save under a held id must raise the exported DuplicateAccountError, never overwrite");
137
+ const standing = await store.findDirectByIdpId("auth0|bob");
138
+ assert.equal(standing?.spec?.firstName, "First", "the first writer's row must stand after a refused duplicate");
139
+ },
140
+ ],
141
+ [
142
+ "findDirectByIdpId answers a direct account by its subject and undefined for an unknown one",
143
+ async ({ store }) => {
144
+ await store.save(directAccount({ idpId: "auth0|carol" }));
145
+ const hit = await store.findDirectByIdpId("auth0|carol");
146
+ assert.equal(hit?.metadata?.id, accountIdFor("auth0|carol"), "the direct account for a subject must be found by that subject");
147
+ assert.equal(await store.findDirectByIdpId("auth0|nobody"), undefined, "an unknown subject must read as undefined, not as an error");
148
+ },
149
+ ],
150
+ [
151
+ "findByIdpId (any mode) answers the same direct account",
152
+ async ({ store }) => {
153
+ await store.save(directAccount({ idpId: "auth0|dave" }));
154
+ assert.equal((await store.findByIdpId("auth0|dave"))?.spec?.idpId, "auth0|dave", "findByIdpId must answer a direct account by its subject");
155
+ assert.equal(await store.findByIdpId("auth0|nobody"), undefined, "an unknown subject must read as undefined on the any-mode lookup too");
156
+ },
157
+ ],
158
+ [
159
+ "findDirectByEmail is an exact, case-sensitive match on direct accounts",
160
+ async ({ store }) => {
161
+ await store.save(directAccount({ idpId: "auth0|erin", email: "erin@example.com" }));
162
+ assert.equal((await store.findDirectByEmail("erin@example.com"))?.spec?.idpId, "auth0|erin", "the direct account with an email must be found by that exact email");
163
+ assert.equal(await store.findDirectByEmail("ERIN@example.com"), undefined, "the email match is exact: a different case is a different string");
164
+ assert.equal(await store.findDirectByEmail("nobody@example.com"), undefined, "an unknown email must read as undefined");
165
+ },
166
+ ],
167
+ [
168
+ "update replaces the row in place and keeps the id",
169
+ async ({ store }) => {
170
+ const account = directAccount({
171
+ idpId: "auth0|frank",
172
+ firstName: "Frank",
173
+ });
174
+ await store.save(account);
175
+ await store.update(directAccount({ idpId: "auth0|frank", firstName: "Francis" }));
176
+ const updated = await store.findById(idOf(account));
177
+ assert.equal(updated?.spec?.firstName, "Francis", "update must replace the stored row with the given one");
178
+ assert.equal(updated?.metadata?.id, idOf(account), "update must not move the row to another id");
179
+ },
180
+ ],
181
+ [
182
+ "deleteById removes the row; the subject is free to be provisioned again",
183
+ async ({ store }) => {
184
+ const account = directAccount({ idpId: "auth0|grace" });
185
+ await store.save(account);
186
+ await store.deleteById(idOf(account));
187
+ assert.equal(await store.findById(idOf(account)), undefined, "a deleted account must not be readable by id");
188
+ assert.equal(await store.findDirectByIdpId("auth0|grace"), undefined, "a deleted account must not be readable by subject");
189
+ await assert.doesNotReject(store.save(account), "the subject of a deleted account must be savable again");
190
+ },
191
+ ],
192
+ [
193
+ "findByIds answers the present ones in request order and skips the unknown",
194
+ async ({ store }) => {
195
+ const heidi = directAccount({ idpId: "auth0|heidi" });
196
+ const ivan = directAccount({ idpId: "auth0|ivan" });
197
+ await store.save(heidi);
198
+ await store.save(ivan);
199
+ const found = await store.findByIds([
200
+ idOf(ivan),
201
+ "ida_00000000000000000000000000",
202
+ idOf(heidi),
203
+ ]);
204
+ assert.deepEqual(subjectsOf(found), ["auth0|ivan", "auth0|heidi"], "findByIds must keep the request's order and skip ids that are not held");
205
+ },
206
+ ],
207
+ [
208
+ "a disconnected store is an infrastructure fault, never 'not found'",
209
+ async (fixture) => {
210
+ await fixture.disconnect();
211
+ await assert.rejects(fixture.store.findDirectByIdpId("auth0|anyone"), "a subject read on a disconnected store must reject, never read as undefined");
212
+ await assert.rejects(fixture.store.findById(accountIdFor("auth0|anyone")), "an id read on a disconnected store must reject, never read as undefined");
213
+ },
214
+ ],
215
+ [
216
+ "two concurrent saves of one subject end in fulfilments and DuplicateAccountErrors only, and the winner is readable by subject",
217
+ async ({ store }) => {
218
+ const results = await Promise.allSettled([
219
+ store.save(directAccount({ idpId: "auth0|race", firstName: "A" })),
220
+ store.save(directAccount({ idpId: "auth0|race", firstName: "B" })),
221
+ ]);
222
+ for (const result of results) {
223
+ if (result.status === "rejected") {
224
+ assert.ok(result.reason instanceof DuplicateAccountError, `a losing concurrent save must fail as DuplicateAccountError, got ${String(result.reason)}`);
225
+ }
226
+ }
227
+ assert.ok(results.some((result) => result.status === "fulfilled"), "at least one of two concurrent saves must win");
228
+ const winner = await store.findDirectByIdpId("auth0|race");
229
+ assert.equal(winner?.metadata?.id, accountIdFor("auth0|race"), "the winner must be readable by its subject");
230
+ },
231
+ ],
232
+ [
233
+ "findDirectByIdpId and findDirectByEmail never answer a federated account, while findByIdpId (any mode) does",
234
+ async ({ store }) => {
235
+ await store.save(federatedAccount({ idpId: "auth0|fed", email: "fed@example.com" }));
236
+ assert.equal(await store.findDirectByIdpId("auth0|fed"), undefined, "the direct-subject lookup must not resolve to a federated account");
237
+ assert.equal(await store.findDirectByEmail("fed@example.com"), undefined, "the direct-email lookup must not resolve to a federated account");
238
+ assert.equal((await store.findByIdpId("auth0|fed"))?.spec?.provisioningMode, IdentityAccountProvisioningMode.federated, "the any-mode subject lookup must answer the federated account");
239
+ },
240
+ ],
241
+ [
242
+ "findByIds answers one row per distinct id, in first-occurrence order; an empty request answers an empty list",
243
+ async ({ store }) => {
244
+ const judy = directAccount({ idpId: "auth0|judy" });
245
+ const kim = directAccount({ idpId: "auth0|kim" });
246
+ await store.save(judy);
247
+ await store.save(kim);
248
+ assert.deepEqual(subjectsOf(await store.findByIds([idOf(kim), idOf(judy), idOf(kim), idOf(judy)])), ["auth0|kim", "auth0|judy"], "a repeated id must answer once, at its first position");
249
+ assert.deepEqual(await store.findByIds([]), [], "an empty request must answer an empty list, not a scan");
250
+ },
251
+ ],
252
+ [
253
+ "update of an unknown id is a no-op: no row appears",
254
+ async ({ store }) => {
255
+ const ghost = directAccount({ idpId: "auth0|ghost" });
256
+ await store.update(ghost);
257
+ assert.equal(await store.findById(idOf(ghost)), undefined, "update must replace, never create: an unknown id must leave no row by id");
258
+ assert.equal(await store.findDirectByIdpId("auth0|ghost"), undefined, "update must replace, never create: an unknown id must leave no row by subject");
259
+ },
260
+ ],
261
+ [
262
+ "deleteById of an unknown id resolves",
263
+ async ({ store }) => {
264
+ await assert.doesNotReject(store.deleteById(accountIdFor("auth0|nobody")), "deleting an id that is not held must resolve, not reject");
265
+ },
266
+ ],
267
+ [
268
+ "save refuses a direct account with an empty idp_id",
269
+ async ({ store }) => {
270
+ await assert.rejects(store.save(subjectlessDirectAccount()), "a direct account with no subject has no principal and must be refused, never stored");
271
+ },
272
+ ],
273
+ ];
274
+ /**
275
+ * Runs `body` over a fresh fixture. The body's failure is the one reported:
276
+ * a cleanup failure after a failed body would otherwise replace the
277
+ * assertion that matters with a teardown detail.
278
+ */
279
+ async function withFixture(makeFixture, body) {
280
+ const fixture = await makeFixture();
281
+ let failed = false;
282
+ try {
283
+ await body(fixture);
284
+ }
285
+ catch (error) {
286
+ failed = true;
287
+ throw error;
288
+ }
289
+ finally {
290
+ try {
291
+ await fixture.cleanup();
292
+ }
293
+ catch (cleanupError) {
294
+ if (!failed) {
295
+ throw cleanupError;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ /** The port's cases over `makeFixture`, one fresh fixture per case. */
301
+ export function identityAccountStoreContract(makeFixture) {
302
+ return CASES.map(([name, body]) => ({
303
+ name,
304
+ run: () => withFixture(makeFixture, body),
305
+ }));
306
+ }
307
+ //# sourceMappingURL=store-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-contract.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/store-contract.ts"],"names":[],"mappings":"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"}