@astram/identity 0.13.0 → 0.14.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 (88) hide show
  1. package/dist/features/authz/grant-admin.service.d.ts +1 -1
  2. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  3. package/dist/features/authz/grant-admin.service.js +8 -0
  4. package/dist/features/authz/grant-ceiling.d.ts +11 -3
  5. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  6. package/dist/features/authz/grant-ceiling.js +20 -3
  7. package/dist/features/authz/record-authorization.d.ts +1 -1
  8. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  9. package/dist/features/authz/record-authorization.js +3 -2
  10. package/dist/features/company/company-hierarchy.service.d.ts +6 -0
  11. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  12. package/dist/features/company/company-hierarchy.service.js +19 -0
  13. package/dist/features/identity-feature.module.d.ts.map +1 -1
  14. package/dist/features/identity-feature.module.js +13 -0
  15. package/dist/features/membership/membership.service.d.ts +9 -0
  16. package/dist/features/membership/membership.service.d.ts.map +1 -1
  17. package/dist/features/membership/membership.service.js +40 -0
  18. package/dist/features/membership/posting.service.d.ts +18 -0
  19. package/dist/features/membership/posting.service.d.ts.map +1 -1
  20. package/dist/features/membership/posting.service.js +85 -27
  21. package/dist/features/org-bootstrap/org-bootstrap-error.d.ts +20 -0
  22. package/dist/features/org-bootstrap/org-bootstrap-error.d.ts.map +1 -0
  23. package/dist/features/org-bootstrap/org-bootstrap-error.js +24 -0
  24. package/dist/features/org-bootstrap/org-bootstrap-file.d.ts +58 -0
  25. package/dist/features/org-bootstrap/org-bootstrap-file.d.ts.map +1 -0
  26. package/dist/features/org-bootstrap/org-bootstrap-file.js +323 -0
  27. package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts +79 -0
  28. package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts.map +1 -0
  29. package/dist/features/org-bootstrap/org-bootstrap-plan.js +288 -0
  30. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts +259 -0
  31. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts.map +1 -0
  32. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.js +604 -0
  33. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts +24 -0
  34. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts.map +1 -0
  35. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.js +39 -0
  36. package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts +19 -0
  37. package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts.map +1 -0
  38. package/dist/features/org-bootstrap/org-bootstrap.manifest.js +30 -0
  39. package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts +8 -0
  40. package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts.map +1 -0
  41. package/dist/features/org-bootstrap/org-bootstrap.tokens.js +10 -0
  42. package/dist/features/org-bootstrap/org-names.d.ts +12 -0
  43. package/dist/features/org-bootstrap/org-names.d.ts.map +1 -0
  44. package/dist/features/org-bootstrap/org-names.js +26 -0
  45. package/dist/features/org-bootstrap/org-state.reader.d.ts +15 -0
  46. package/dist/features/org-bootstrap/org-state.reader.d.ts.map +1 -0
  47. package/dist/features/org-bootstrap/org-state.reader.js +225 -0
  48. package/dist/features/org-bootstrap/root-password-source.d.ts +9 -0
  49. package/dist/features/org-bootstrap/root-password-source.d.ts.map +1 -0
  50. package/dist/features/org-bootstrap/root-password-source.js +57 -0
  51. package/dist/features/org-bootstrap/run-org-bootstrap.d.ts +42 -0
  52. package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -0
  53. package/dist/features/org-bootstrap/run-org-bootstrap.js +688 -0
  54. package/dist/features/org-bootstrap/set-password-links.d.ts +30 -0
  55. package/dist/features/org-bootstrap/set-password-links.d.ts.map +1 -0
  56. package/dist/features/org-bootstrap/set-password-links.js +55 -0
  57. package/dist/features/org-structure/org-catalog.service.d.ts +13 -0
  58. package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
  59. package/dist/features/org-structure/org-catalog.service.js +40 -0
  60. package/dist/features/role/role-owner-transition.d.ts +17 -0
  61. package/dist/features/role/role-owner-transition.d.ts.map +1 -0
  62. package/dist/features/role/role-owner-transition.js +23 -0
  63. package/dist/features/role/role.controller.d.ts +1 -1
  64. package/dist/features/role/role.controller.d.ts.map +1 -1
  65. package/dist/features/role/role.controller.js +6 -4
  66. package/dist/features/role/role.service.d.ts +8 -2
  67. package/dist/features/role/role.service.d.ts.map +1 -1
  68. package/dist/features/role/role.service.js +13 -3
  69. package/dist/features/user/credential-provisioner.token.d.ts +8 -0
  70. package/dist/features/user/credential-provisioner.token.d.ts.map +1 -0
  71. package/dist/features/user/credential-provisioner.token.js +10 -0
  72. package/dist/features/user/user.controller.d.ts +1 -3
  73. package/dist/features/user/user.controller.d.ts.map +1 -1
  74. package/dist/features/user/user.controller.js +2 -2
  75. package/dist/features/user/user.service.d.ts +28 -5
  76. package/dist/features/user/user.service.d.ts.map +1 -1
  77. package/dist/features/user/user.service.js +45 -12
  78. package/dist/index.d.ts +6 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +9 -1
  81. package/dist/internal.d.ts +2 -0
  82. package/dist/internal.d.ts.map +1 -1
  83. package/dist/internal.js +3 -1
  84. package/dist/role.object.d.ts +1 -0
  85. package/dist/role.object.d.ts.map +1 -1
  86. package/dist/role.object.js +8 -0
  87. package/migrations/1710000000101-AddRoleOwnerCompany.cjs +98 -0
  88. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import type { Principal } from '@astram/core';
3
- import type { AuditPort } from '@astram/engine';
3
+ import { type AuditPort } from '@astram/engine';
4
4
  import { PermissionVersionBumper } from './permission-version.bumper.js';
5
5
  import { AccessBundleStore } from './access-bundle-store.js';
6
6
  import { GrantCeiling } from './grant-ceiling.js';
@@ -1 +1 @@
1
- {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GrantAdminService = exports.BadKeyError = void 0;
4
4
  const core_1 = require("@astram/core");
5
+ const engine_1 = require("@astram/engine");
5
6
  const access_bundle_store_js_1 = require("./access-bundle-store.js");
6
7
  const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
7
8
  const grant_ceiling_js_1 = require("./grant-ceiling.js");
@@ -216,6 +217,12 @@ class GrantAdminService {
216
217
  return [labelOf(rowTenant)];
217
218
  return actor.isRoot === true ? tenants.map(labelOf) : [];
218
219
  };
220
+ // G-009 R6: a grantor bound to a declared operation (org bootstrap) is attributed in the row;
221
+ // an unbound grantor adds nothing, so today's attributes stay byte-identical.
222
+ const op = (0, engine_1.operationOf)(actor);
223
+ const operationAttributes = !op
224
+ ? {}
225
+ : { operation: op.operationKey, step: op.stepKey, onBehalfOf: op.onBehalfOf };
219
226
  const occurredAtMs = Date.now();
220
227
  for (const tenantId of new Set([actor.tenantId, ...tenants])) {
221
228
  const tenantNames = tenantsFor(tenantId);
@@ -231,6 +238,7 @@ class GrantAdminService {
231
238
  keys: [...keys],
232
239
  tenants: tenantNames,
233
240
  ...extra,
241
+ ...operationAttributes,
234
242
  },
235
243
  }, query);
236
244
  }
@@ -20,6 +20,14 @@ import { type IdentitySchemaNames } from '../../schema.js';
20
20
  export declare class GrantCeilingError extends Error {
21
21
  constructor(key: string, message?: string);
22
22
  }
23
+ /**
24
+ * The org-bootstrap exception (G-009 AC-7): `operationRoleIds` is the allow-set the runner computed
25
+ * from the file's roles for the company. It relaxes a role check ONLY for a principal bound (engine
26
+ * WeakMap binding, unforgeable) to `shield:org-bootstrap`, and only when EVERY id of the call is in the set.
27
+ */
28
+ export interface OperationRoleOptions {
29
+ readonly operationRoleIds?: readonly string[];
30
+ }
23
31
  /** The actor's reachable subtree: the session's cached closure, else the self node (non-widening). */
24
32
  export declare function subtreeOf(actor: Principal): readonly string[];
25
33
  /**
@@ -89,7 +97,7 @@ export declare class GrantCeiling {
89
97
  * A role holding nothing assigns nothing and passes; a role key the registry does not know
90
98
  * counts as not held (Phase 8 #4). Root bypasses. Store outage → 503.
91
99
  */
92
- assertCanAssignRoles(actor: Principal, roleIds: readonly string[]): Promise<void>;
100
+ assertCanAssignRoles(actor: Principal, roleIds: readonly string[], opts?: OperationRoleOptions): Promise<void>;
93
101
  /**
94
102
  * The first of a TARGET's raw keys (a role's `role_permission` rows) the actor does not cover,
95
103
  * or `undefined` when all are covered. Phase 8 #4: judged per RAW key with `coversKey`, so a key
@@ -166,12 +174,12 @@ export declare class GrantCeiling {
166
174
  * role not bound in the subtree is "not found" for the actor (no existence leak); a role also
167
175
  * used outside (a shared role) is root-only. Root bypasses.
168
176
  */
169
- assertRoleAdministrable(actor: Principal, roleUuid: string): Promise<void>;
177
+ assertRoleAdministrable(actor: Principal, roleUuid: string, opts?: OperationRoleOptions): Promise<void>;
170
178
  /**
171
179
  * Assert the actor may POST someone to the role (#11): the role is bound within the actor's
172
180
  * subtree or not bound at all — never a role also used outside it (a shared role's assignment
173
181
  * would widen another tenant's role). Pair with {@link assertCanAssignRoles}. Root bypasses.
174
182
  */
175
- assertRoleAssignable(actor: Principal, roleUuid: string): Promise<void>;
183
+ assertRoleAssignable(actor: Principal, roleUuid: string, opts?: OperationRoleOptions): Promise<void>;
176
184
  }
177
185
  //# sourceMappingURL=grant-ceiling.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grant-ceiling.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-ceiling.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,8 @@ exports.GrantCeiling = exports.GrantCeilingError = void 0;
4
4
  exports.subtreeOf = subtreeOf;
5
5
  exports.coversKey = coversKey;
6
6
  const core_1 = require("@astram/core");
7
+ const engine_1 = require("@astram/engine");
8
+ const org_bootstrap_manifest_js_1 = require("../org-bootstrap/org-bootstrap.manifest.js");
7
9
  const http_js_1 = require("../shared/http.js");
8
10
  const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
9
11
  const role_binding_js_1 = require("./role-binding.js");
@@ -18,6 +20,15 @@ class GrantCeilingError extends Error {
18
20
  }
19
21
  }
20
22
  exports.GrantCeilingError = GrantCeilingError;
23
+ function operationCovers(actor, roleIds, opts) {
24
+ const allowed = opts?.operationRoleIds;
25
+ if (allowed === undefined || allowed.length === 0 || roleIds.length === 0)
26
+ return false;
27
+ if ((0, engine_1.operationOf)(actor)?.operationKey !== org_bootstrap_manifest_js_1.ORG_BOOTSTRAP_OPERATION)
28
+ return false;
29
+ const set = new Set(allowed);
30
+ return roleIds.every((id) => set.has(id));
31
+ }
21
32
  /** The actor's reachable subtree: the session's cached closure, else the self node (non-widening). */
22
33
  function subtreeOf(actor) {
23
34
  return actor.scopeSubtreeNodeIds?.length ? actor.scopeSubtreeNodeIds : [actor.tenantId];
@@ -160,9 +171,11 @@ class GrantCeiling {
160
171
  * A role holding nothing assigns nothing and passes; a role key the registry does not know
161
172
  * counts as not held (Phase 8 #4). Root bypasses. Store outage → 503.
162
173
  */
163
- async assertCanAssignRoles(actor, roleIds) {
174
+ async assertCanAssignRoles(actor, roleIds, opts) {
164
175
  if (actor.isRoot === true)
165
176
  return;
177
+ if (operationCovers(actor, roleIds, opts))
178
+ return;
166
179
  const ids = [...new Set(roleIds)];
167
180
  if (ids.length === 0)
168
181
  return;
@@ -372,9 +385,11 @@ class GrantCeiling {
372
385
  * role not bound in the subtree is "not found" for the actor (no existence leak); a role also
373
386
  * used outside (a shared role) is root-only. Root bypasses.
374
387
  */
375
- async assertRoleAdministrable(actor, roleUuid) {
388
+ async assertRoleAdministrable(actor, roleUuid, opts) {
376
389
  if (actor.isRoot === true)
377
390
  return;
391
+ if (operationCovers(actor, [roleUuid], opts))
392
+ return;
378
393
  GrantCeiling.checkAdministrable(await this.roleBinding(actor, roleUuid));
379
394
  }
380
395
  /**
@@ -382,9 +397,11 @@ class GrantCeiling {
382
397
  * subtree or not bound at all — never a role also used outside it (a shared role's assignment
383
398
  * would widen another tenant's role). Pair with {@link assertCanAssignRoles}. Root bypasses.
384
399
  */
385
- async assertRoleAssignable(actor, roleUuid) {
400
+ async assertRoleAssignable(actor, roleUuid, opts) {
386
401
  if (actor.isRoot === true)
387
402
  return;
403
+ if (operationCovers(actor, [roleUuid], opts))
404
+ return;
388
405
  GrantCeiling.checkAssignable(await this.roleBinding(actor, roleUuid));
389
406
  }
390
407
  }
@@ -41,7 +41,7 @@ export declare function createIdentityAuthzPool(schemas?: FrameworkSchemas): Poo
41
41
  * - `registry` — bridged from `identityObjects` PLUS the consumer's `extraObjects` (their
42
42
  * `<module>:<type>:<action>` keys project into a principal's grants at gate 5; without them a
43
43
  * consumer type is `resource_not_registered`),
44
- * - `operationManifest` — empty (identity declares no trusted delegated operations).
44
+ * - `operationManifest` — the `shield:org-bootstrap` steps (G-009); no other delegated operation.
45
45
  */
46
46
  export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[],
47
47
  /** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
@@ -1 +1 @@
1
- {"version":3,"file":"record-authorization.d.ts","sourceRoot":"","sources":["../../../src/features/authz/record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxF;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAaxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,YAAY,GAAE,SAAS,eAAe,EAAO;AAC7C,0FAA0F;AAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,yBAAyB,CAS3B"}
1
+ {"version":3,"file":"record-authorization.d.ts","sourceRoot":"","sources":["../../../src/features/authz/record-authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAgB,EAAE,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxF;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAaxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,YAAY,GAAE,SAAS,eAAe,EAAO;AAC7C,0FAA0F;AAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,yBAAyB,CAS3B"}
@@ -22,6 +22,7 @@ exports.buildRecordAuthorizationInputs = buildRecordAuthorizationInputs;
22
22
  */
23
23
  const mariadb_1 = __importDefault(require("mariadb"));
24
24
  const guard_1 = require("@astram/guard");
25
+ const org_bootstrap_manifest_js_1 = require("../org-bootstrap/org-bootstrap.manifest.js");
25
26
  const objects_js_1 = require("../../objects.js");
26
27
  const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
27
28
  const session_facts_reader_js_1 = require("./session-facts.reader.js");
@@ -73,7 +74,7 @@ function createIdentityAuthzPool(schemas) {
73
74
  * - `registry` — bridged from `identityObjects` PLUS the consumer's `extraObjects` (their
74
75
  * `<module>:<type>:<action>` keys project into a principal's grants at gate 5; without them a
75
76
  * consumer type is `resource_not_registered`),
76
- * - `operationManifest` — empty (identity declares no trusted delegated operations).
77
+ * - `operationManifest` — the `shield:org-bootstrap` steps (G-009); no other delegated operation.
77
78
  */
78
79
  function buildRecordAuthorizationInputs(pool, extraObjects = [],
79
80
  /** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
@@ -84,6 +85,6 @@ schemas) {
84
85
  permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry, undefined, schemas),
85
86
  sessionFacts: new session_facts_reader_js_1.AlsSessionFactsReader(),
86
87
  registry,
87
- operationManifest: [],
88
+ operationManifest: org_bootstrap_manifest_js_1.ORG_BOOTSTRAP_MANIFEST,
88
89
  };
89
90
  }
@@ -115,6 +115,12 @@ export declare class CompanyHierarchyService {
115
115
  * Every change is audited on this transaction (names; ids only in structured attributes).
116
116
  */
117
117
  private retireDependents;
118
+ /**
119
+ * Step 5 of {@link retireDependents} (G-009 T14, SPEC §5 v4): the roles the company owns become
120
+ * shared. Each owner is cleared through the audited `transitionRoleOwner` seam on the delete's
121
+ * connection (the FK's SET NULL would do it silently, without an audit row).
122
+ */
123
+ private clearRoleOwners;
118
124
  /** Step 4 of {@link retireDependents}: revoke and audit the grants naming the company. */
119
125
  private revokeGrantsNaming;
120
126
  /** Lock the company, then require dominance over each of its members (see `remove`). */
@@ -1 +1 @@
1
- {"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AAQxF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAqCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAPP,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAG3E,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAkF9B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IAgCpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
1
+ {"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AASxF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAqCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAPP,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAG3E,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAmF9B;;;;OAIG;YACW,eAAe;IA6B7B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IAgCpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
@@ -6,6 +6,7 @@ const http_js_1 = require("../shared/http.js");
6
6
  const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
7
7
  const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
8
8
  const identity_errors_js_1 = require("../shared/identity-errors.js");
9
+ const role_owner_transition_js_1 = require("../role/role-owner-transition.js");
9
10
  const schema_js_1 = require("../../schema.js");
10
11
  /** A JSON-array column (the driver returns an array OR a string) → its string items; else []. */
11
12
  function jsonIdArray(v) {
@@ -233,8 +234,26 @@ class CompanyHierarchyService {
233
234
  });
234
235
  }
235
236
  await this.revokeGrantsNaming(conn, actor, nodeUuid, companyName);
237
+ await this.clearRoleOwners(conn, actor, nodeUuid, companyName);
236
238
  return [...touched];
237
239
  }
240
+ /**
241
+ * Step 5 of {@link retireDependents} (G-009 T14, SPEC §5 v4): the roles the company owns become
242
+ * shared. Each owner is cleared through the audited `transitionRoleOwner` seam on the delete's
243
+ * connection (the FK's SET NULL would do it silently, without an audit row).
244
+ */
245
+ async clearRoleOwners(conn, actor, nodeUuid, companyName) {
246
+ const owned = (await conn.query(`SELECT r_uuid, r_name FROM \`${this.schemas.app}\`.\`roles\` WHERE r_owner_company_uuid = ? ORDER BY r_uuid FOR UPDATE`, [nodeUuid]));
247
+ for (const role of owned) {
248
+ await (0, role_owner_transition_js_1.transitionRoleOwner)((sql, params) => conn.query(sql, params), role.r_uuid, nodeUuid, null, (action, attributes, query) => this.recordAudit({ query }, actor, action, {
249
+ eventType: 'role',
250
+ ...attributes,
251
+ role: role.r_name,
252
+ company: companyName,
253
+ reason: 'company_deleted',
254
+ }), this.schemas);
255
+ }
256
+ }
238
257
  /** Step 4 of {@link retireDependents}: revoke and audit the grants naming the company. */
239
258
  async revokeGrantsNaming(conn, actor, nodeUuid, companyName) {
240
259
  const grants = (await conn.query(`SELECT ctg_uuid, ctg_owning_tenant_uuid, ctg_grantee_tenant_uuid, ctg_grantee_kind,
@@ -1 +1 @@
1
- {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAYL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA2GxB;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAwBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAqBhB;AAED;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,GACtB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;KACvB,GAAG,aAAa;CA2jBvB"}
1
+ {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAYL,KAAK,cAAc,EAGnB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA+GxB;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAwBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,cAAc,CAqBhB;AAED;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,GACtB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;KACvB,GAAG,aAAa;CAqkBvB"}
@@ -17,6 +17,7 @@ const core_1 = require("@nestjs/core");
17
17
  const core_2 = require("@astram/core");
18
18
  const engine_1 = require("@astram/engine");
19
19
  const guard_1 = require("@astram/guard");
20
+ const internal_1 = require("@astram/guard/internal");
20
21
  const objects_js_1 = require("../objects.js");
21
22
  const index_js_1 = require("./access/index.js");
22
23
  const company_service_js_1 = require("./company/company.service.js");
@@ -26,7 +27,9 @@ const role_service_js_1 = require("./role/role.service.js");
26
27
  const role_controller_js_1 = require("./role/role.controller.js");
27
28
  const user_service_js_1 = require("./user/user.service.js");
28
29
  const email_address_changer_token_js_1 = require("./user/email-address-changer.token.js");
30
+ const credential_provisioner_token_js_1 = require("./user/credential-provisioner.token.js");
29
31
  const framework_schemas_token_js_1 = require("./framework-schemas.token.js");
32
+ const org_bootstrap_tokens_js_1 = require("./org-bootstrap/org-bootstrap.tokens.js");
30
33
  const user_controller_js_1 = require("./user/user.controller.js");
31
34
  const membership_controller_js_1 = require("./membership/membership.controller.js");
32
35
  const membership_service_js_1 = require("./membership/membership.service.js");
@@ -203,6 +206,8 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
203
206
  * differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
204
207
  */
205
208
  static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, } = {}) {
209
+ // G-009 N4: a reserved operation principal is never a root, whatever variable `rootIds` came from.
210
+ (0, guard_1.assertNoReservedRootId)(rootIds);
206
211
  // The framework database names, resolved ONCE (boot) for every SQL path built below.
207
212
  // Caller-supplied names are checked before any SQL is built (fail closed, Shield #1).
208
213
  const schemas = givenSchemas !== undefined
@@ -585,9 +590,17 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
585
590
  provide: email_address_changer_token_js_1.IDENTITY_EMAIL_ADDRESS_CHANGER,
586
591
  useValue: new guard_1.MariaDbEmailAddressChanger(authzPool, Date.now, schemas),
587
592
  },
593
+ // G-009 T7 (OD-6): the locked-credential writer UserService runs on the create transaction.
594
+ {
595
+ provide: credential_provisioner_token_js_1.IDENTITY_CREDENTIAL_PROVISIONER,
596
+ useValue: new internal_1.MariaDbCredentialProvisioner(authzPool, Date.now, schemas),
597
+ },
588
598
  // The same resolved names for identity services Nest constructs (UserService's
589
599
  // email-change audit sink), never a second env read.
590
600
  { provide: framework_schemas_token_js_1.IDENTITY_FRAMEWORK_SCHEMAS, useValue: schemas },
601
+ // G-009 T10: the ONE shared authz pool, for the org bootstrap runner (named lock, state reads,
602
+ // claim and root-flag writes). Not exported; the runner reads it with `app.get({ strict: false })`.
603
+ { provide: org_bootstrap_tokens_js_1.IDENTITY_AUTHZ_POOL, useValue: authzPool },
591
604
  // ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
592
605
  // bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
593
606
  // where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
@@ -41,6 +41,15 @@ export declare class MembershipService {
41
41
  * swallowed here). `createdBy` is stamped server-side (actorId-trusted).
42
42
  */
43
43
  addMembership(actor: Principal, userUuid: string, companyUuid: string): Promise<RecordId>;
44
+ /**
45
+ * Add an ACTIVE membership for `(userUuid, companyUuid)` in one create (G-009 R4): a brand-new
46
+ * membership has no other active card to displace, so no create-then-activate pair. A
47
+ * membership for that pair already existing is an `IdentityConflictError`, from the pre-check
48
+ * and from the `(cm_user_uuid, cm_company_uuid)` unique index (the pre-check read is SQL-scoped
49
+ * and can miss a row for an operation-bound principal); the driver message is never echoed.
50
+ * Bumps the company's tenant. The reach checks of `addMembership` apply unchanged.
51
+ */
52
+ addActiveMembership(actor: Principal, userUuid: string, companyUuid: string): Promise<RecordId>;
44
53
  /** List a user's membership cards (tenant-scoped by the pipe). */
45
54
  listMemberships(userUuid: string): Promise<readonly RecordView[]>;
46
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,qBACa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAQxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAXZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAQrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D;;;;OAIG;YACW,WAAW;IAQzB;;;;;;OAMG;YACW,WAAW;IAYzB;;;;;;OAMG;IACG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkB/F,kEAAkE;IAC5D,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAKvE;;;;;;;;;;;;;;;;;OAiBG;IACG,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC1F;;;;OAIG;IACG,gBAAgB,CACpB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,IAAI,CAAC;IAchB;;;OAGG;IACG,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYhF"}
1
+ {"version":3,"file":"membership.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AASzD,qBACa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAQxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAXZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAQrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D;;;;OAIG;YACW,WAAW;IAQzB;;;;;;OAMG;YACW,WAAW;IAYzB;;;;;;OAMG;IACG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkB/F;;;;;;;OAOG;IACG,mBAAmB,CACvB,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC;IAwBpB,kEAAkE;IAC5D,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAKvE;;;;;;;;;;;;;;;;;OAiBG;IACG,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC1F;;;;OAIG;IACG,gBAAgB,CACpB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC,IAAI,CAAC;IAchB;;;OAGG;IACG,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYhF"}
@@ -30,6 +30,13 @@ const engine_1 = require("@astram/engine");
30
30
  const posting_version_bump_js_1 = require("./posting-version-bump.js");
31
31
  const identity_errors_js_1 = require("../shared/identity-errors.js");
32
32
  const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
33
+ /** A driver duplicate-key error (errno 1062), recognised by errno/code only, never its message. */
34
+ function isDuplicateKey(err) {
35
+ if (typeof err !== 'object' || err === null)
36
+ return false;
37
+ const e = err;
38
+ return e.errno === 1062 || e.code === 'ER_DUP_ENTRY';
39
+ }
33
40
  let MembershipService = class MembershipService {
34
41
  records;
35
42
  ceiling;
@@ -101,6 +108,39 @@ let MembershipService = class MembershipService {
101
108
  fields: { userUuid, companyUuid, isActive: false, status: 'active' },
102
109
  });
103
110
  }
111
+ /**
112
+ * Add an ACTIVE membership for `(userUuid, companyUuid)` in one create (G-009 R4): a brand-new
113
+ * membership has no other active card to displace, so no create-then-activate pair. A
114
+ * membership for that pair already existing is an `IdentityConflictError`, from the pre-check
115
+ * and from the `(cm_user_uuid, cm_company_uuid)` unique index (the pre-check read is SQL-scoped
116
+ * and can miss a row for an operation-bound principal); the driver message is never echoed.
117
+ * Bumps the company's tenant. The reach checks of `addMembership` apply unchanged.
118
+ */
119
+ async addActiveMembership(actor, userUuid, companyUuid) {
120
+ if (actor.isRoot !== true) {
121
+ const user = await this.records.get('user', userUuid);
122
+ if (user === null)
123
+ throw new identity_errors_js_1.IdentityNotFoundError('User not found.');
124
+ }
125
+ const existing = await this.records.list('company_membership', { where: { userUuid } });
126
+ if (existing.rows.some((m) => m.fields?.companyUuid === companyUuid)) {
127
+ throw new identity_errors_js_1.IdentityConflictError('That user already has a membership in this company.');
128
+ }
129
+ let id;
130
+ try {
131
+ id = await this.records.create('company_membership', {
132
+ fields: { userUuid, companyUuid, isActive: true, status: 'active' },
133
+ });
134
+ }
135
+ catch (err) {
136
+ if (isDuplicateKey(err)) {
137
+ throw new identity_errors_js_1.IdentityConflictError('That user already has a membership in this company.');
138
+ }
139
+ throw err;
140
+ }
141
+ await this.bumpTenants([companyUuid]);
142
+ return id;
143
+ }
104
144
  /** List a user's membership cards (tenant-scoped by the pipe). */
105
145
  async listMemberships(userUuid) {
106
146
  const page = await this.records.list('company_membership', { where: { userUuid } });
@@ -53,5 +53,23 @@ export declare class PostingService {
53
53
  * runs BEFORE any write; the writes run in one transaction (see the file header).
54
54
  */
55
55
  replacePostings(actor: Principal, membershipUuid: string, desired: readonly PostingInput[]): Promise<void>;
56
+ /**
57
+ * Add postings to a membership WITHOUT touching the rest of its set (G-009 R4/R5): only tuples
58
+ * (role|department|branch) not already present are created; nothing is updated or deleted. The
59
+ * pre-checks, the in-transaction lock + re-check, the atomicity and the version bump are those
60
+ * of `replacePostings` (shared helpers). `opts.operationRoleIds` is forwarded to the ceiling
61
+ * calls (the org-bootstrap operation's declared roles). An inactive membership is refused.
62
+ */
63
+ addPostings(actor: Principal, membershipUuid: string, toAdd: readonly PostingInput[], opts?: {
64
+ operationRoleIds?: ReadonlySet<string> | readonly string[];
65
+ }): Promise<void>;
66
+ private assertEveryRole;
67
+ /** The membership through the subtree read wall (404 when out of reach) and its company. */
68
+ private reachMembership;
69
+ /** Authority for ADDED postings (binding rule, ceiling, org units); returns the distinct roles. */
70
+ private assertMayAdd;
71
+ /** Lock each added role row on the transaction, then re-run the binding rule (review F2). */
72
+ private lockAndRecheck;
73
+ private bumpTenant;
56
74
  }
57
75
  //# sourceMappingURL=posting.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"posting.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/posting.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACzC;AA0ED,qBACa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAKxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBARZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAKrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D,gEAAgE;IAC1D,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAK1E;;;;;OAKG;IACG,eAAe,CACnB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC;CA8GjB"}
1
+ {"version":3,"file":"posting.service.d.ts","sourceRoot":"","sources":["../../../src/features/membership/posting.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,YAAY,EAA6B,MAAM,2BAA2B,CAAC;AAOpF;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACzC;AA0ED,qBACa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAKxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBARZ,OAAO,EAAE,cAAc,EAGvB,OAAO,EAAE,YAAY,EAKrB,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D,gEAAgE;IAC1D,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC;IAK1E;;;;;OAKG;IACG,eAAe,CACnB,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAAC,IAAI,CAAC;IAyEhB;;;;;;OAMG;IACG,WAAW,CACf,KAAK,EAAE,SAAS,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,IAAI,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAA;KAAE,GACpE,OAAO,CAAC,IAAI,CAAC;IA2ChB,OAAO,CAAC,eAAe;IAQvB,4FAA4F;YAC9E,eAAe;IAY7B,mGAAmG;YACrF,YAAY;IAqB1B,6FAA6F;YAC/E,cAAc;YAYd,UAAU;CAkBzB"}
@@ -134,18 +134,9 @@ let PostingService = class PostingService {
134
134
  * runs BEFORE any write; the writes run in one transaction (see the file header).
135
135
  */
136
136
  async replacePostings(actor, membershipUuid, desired) {
137
- for (const d of desired) {
138
- if (typeof d?.roleUuid !== 'string' || d.roleUuid.length === 0) {
139
- throw new identity_errors_js_1.IdentityBadRequestError('Every assignment needs a role.');
140
- }
141
- }
137
+ this.assertEveryRole(desired);
142
138
  // 1) Reach: the membership must be visible through the subtree read wall (else 404).
143
- const membership = await this.records.get('company_membership', membershipUuid);
144
- if (membership === null)
145
- throw new identity_errors_js_1.IdentityNotFoundError('Membership not found.');
146
- const tenantUuid = typeof membership.fields?.companyUuid === 'string' && membership.fields.companyUuid.length > 0
147
- ? membership.fields.companyUuid
148
- : null;
139
+ const { membership, tenantUuid } = await this.reachMembership(membershipUuid);
149
140
  const current = (await this.records.list('membership_posting', { where: { membershipUuid } }))
150
141
  .rows;
151
142
  const desiredKeys = new Set(desired.map(naturalKey));
@@ -160,18 +151,7 @@ let PostingService = class PostingService {
160
151
  return true;
161
152
  });
162
153
  // 2) Authority for every ADDED role, before any write: binding rule, then the ceiling.
163
- const addedRoles = [...new Set(added.map((d) => d.roleUuid))];
164
- for (const roleUuid of addedRoles) {
165
- await this.ceiling.assertRoleAssignable(actor, roleUuid);
166
- }
167
- await this.ceiling.assertCanAssignRoles(actor, addedRoles);
168
- // Phase 9 #13b (review F4): an added posting's department/branch must be the membership
169
- // company's own (junction rows), never another tenant's (400).
170
- const present = (v) => typeof v === 'string' && v.length > 0;
171
- await this.ceiling.assertOrgUnitsInCompany(tenantUuid, {
172
- departmentUuids: added.map((d) => d.departmentUuid).filter(present),
173
- branchUuids: added.map((d) => d.branchUuid).filter(present),
174
- });
154
+ const addedRoles = await this.assertMayAdd(actor, tenantUuid, added, undefined);
175
155
  // Removing a PEER's postings strips their authority: the actor must dominate the member
176
156
  // (security review). Removing your OWN postings (self-demotion) stays allowed.
177
157
  const memberUuid = typeof membership.fields?.userUuid === 'string' ? membership.fields.userUuid : '';
@@ -187,10 +167,7 @@ let PostingService = class PostingService {
187
167
  // added role row on THIS transaction (grant administration locks the same row), then
188
168
  // re-run the binding rule: a binding another tenant committed while we waited is now
189
169
  // visible, and a role that became shared is refused before the INSERT (rolled back).
190
- for (const roleUuid of addedRoles) {
191
- await tx.lockForUpdate('role', roleUuid);
192
- await this.ceiling.assertRoleAssignable(actor, roleUuid);
193
- }
170
+ await this.lockAndRecheck(tx, actor, addedRoles, undefined);
194
171
  for (const d of added) {
195
172
  await tx.create('membership_posting', { fields: { ...stripEmpty(d), membershipUuid } });
196
173
  }
@@ -198,6 +175,87 @@ let PostingService = class PostingService {
198
175
  await tx.delete('membership_posting', r.id);
199
176
  }
200
177
  });
178
+ await this.bumpTenant(tenantUuid);
179
+ }
180
+ /**
181
+ * Add postings to a membership WITHOUT touching the rest of its set (G-009 R4/R5): only tuples
182
+ * (role|department|branch) not already present are created; nothing is updated or deleted. The
183
+ * pre-checks, the in-transaction lock + re-check, the atomicity and the version bump are those
184
+ * of `replacePostings` (shared helpers). `opts.operationRoleIds` is forwarded to the ceiling
185
+ * calls (the org-bootstrap operation's declared roles). An inactive membership is refused.
186
+ */
187
+ async addPostings(actor, membershipUuid, toAdd, opts) {
188
+ this.assertEveryRole(toAdd);
189
+ const { membership, tenantUuid } = await this.reachMembership(membershipUuid);
190
+ // TINYINT(1): the engine returns 1/0, not true/false (like `membership.controller.ts`).
191
+ if (!membership.fields?.isActive) {
192
+ throw new identity_errors_js_1.IdentityConflictError('That membership is not active.');
193
+ }
194
+ const ceilingOpts = opts?.operationRoleIds === undefined
195
+ ? undefined
196
+ : { operationRoleIds: [...opts.operationRoleIds] };
197
+ const current = (await this.records.list('membership_posting', { where: { membershipUuid } }))
198
+ .rows;
199
+ const seen = new Set(current.map((r) => naturalKey(r.fields)));
200
+ const added = toAdd.filter((d) => {
201
+ const k = naturalKey(d);
202
+ if (seen.has(k))
203
+ return false;
204
+ seen.add(k);
205
+ return true;
206
+ });
207
+ if (added.length === 0)
208
+ return; // nothing missing: no write, no bump
209
+ const addedRoles = await this.assertMayAdd(actor, tenantUuid, added, ceilingOpts);
210
+ await this.records.transaction(async (tx) => {
211
+ await this.lockAndRecheck(tx, actor, addedRoles, ceilingOpts);
212
+ for (const d of added) {
213
+ await tx.create('membership_posting', { fields: { ...stripEmpty(d), membershipUuid } });
214
+ }
215
+ });
216
+ await this.bumpTenant(tenantUuid);
217
+ }
218
+ assertEveryRole(postings) {
219
+ for (const d of postings) {
220
+ if (typeof d?.roleUuid !== 'string' || d.roleUuid.length === 0) {
221
+ throw new identity_errors_js_1.IdentityBadRequestError('Every assignment needs a role.');
222
+ }
223
+ }
224
+ }
225
+ /** The membership through the subtree read wall (404 when out of reach) and its company. */
226
+ async reachMembership(membershipUuid) {
227
+ const membership = await this.records.get('company_membership', membershipUuid);
228
+ if (membership === null)
229
+ throw new identity_errors_js_1.IdentityNotFoundError('Membership not found.');
230
+ const tenantUuid = typeof membership.fields?.companyUuid === 'string' && membership.fields.companyUuid.length > 0
231
+ ? membership.fields.companyUuid
232
+ : null;
233
+ return { membership, tenantUuid };
234
+ }
235
+ /** Authority for ADDED postings (binding rule, ceiling, org units); returns the distinct roles. */
236
+ async assertMayAdd(actor, tenantUuid, added, opts) {
237
+ const addedRoles = [...new Set(added.map((d) => d.roleUuid))];
238
+ for (const roleUuid of addedRoles) {
239
+ await this.ceiling.assertRoleAssignable(actor, roleUuid, opts);
240
+ }
241
+ await this.ceiling.assertCanAssignRoles(actor, addedRoles, opts);
242
+ // Phase 9 #13b (review F4): an added posting's department/branch must be the membership
243
+ // company's own (junction rows), never another tenant's (400).
244
+ const present = (v) => typeof v === 'string' && v.length > 0;
245
+ await this.ceiling.assertOrgUnitsInCompany(tenantUuid, {
246
+ departmentUuids: added.map((d) => d.departmentUuid).filter(present),
247
+ branchUuids: added.map((d) => d.branchUuid).filter(present),
248
+ });
249
+ return addedRoles;
250
+ }
251
+ /** Lock each added role row on the transaction, then re-run the binding rule (review F2). */
252
+ async lockAndRecheck(tx, actor, addedRoles, opts) {
253
+ for (const roleUuid of addedRoles) {
254
+ await tx.lockForUpdate('role', roleUuid);
255
+ await this.ceiling.assertRoleAssignable(actor, roleUuid, opts);
256
+ }
257
+ }
258
+ async bumpTenant(tenantUuid) {
201
259
  // ADR-0011 STALENESS FIX: a posting add/remove changes the effective roles (→ coarse
202
260
  // grants) of the membership's user in that company, so bump the tenant's permission
203
261
  // version — every live session in that tenant then reloads its cached facts on the next
@@ -0,0 +1,20 @@
1
+ import type { PlanKind } from './org-bootstrap-plan.js';
2
+ export type OrgBootstrapErrorCode = 'operator_required' | 'plan_conflict' | 'root_password_missing' | 'root_password_literal' | 'root_password_weak' | 'root_password_collision' | 'link_base_url' | 'links_need_terminal' | 'invalid_link_ttl' | 'not_wired' | 'lock_not_acquired' | 'step_failed';
3
+ /** What a run wrote, counted per plan kind. */
4
+ export type OrgBootstrapWritten = Partial<Record<PlanKind, number>>;
5
+ /**
6
+ * A refusal or failure of the org bootstrap run. The message is fixed text plus names from the
7
+ * operator's own file; it never carries a password, a link or token, or a driver message.
8
+ */
9
+ export declare class OrgBootstrapError extends Error {
10
+ readonly code: OrgBootstrapErrorCode;
11
+ /** `step_failed` only: the manifest step key that failed. */
12
+ readonly step?: string;
13
+ /** `step_failed` only: what the run had written before it stopped. */
14
+ readonly written?: OrgBootstrapWritten;
15
+ constructor(code: OrgBootstrapErrorCode, message: string, extra?: {
16
+ step?: string;
17
+ written?: OrgBootstrapWritten;
18
+ });
19
+ }
20
+ //# sourceMappingURL=org-bootstrap-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-bootstrap-error.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/org-bootstrap-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,eAAe,GACf,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,yBAAyB,GACzB,eAAe,GACf,qBAAqB,GACrB,kBAAkB,GAClB,WAAW,GACX,mBAAmB,GACnB,aAAa,CAAC;AAElB,+CAA+C;AAC/C,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;gBAGrC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;KAAO;CAQ/D"}