@astram/identity 0.11.0 → 0.13.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 (96) hide show
  1. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  2. package/dist/cross-tenant-grant.object.js +3 -2
  3. package/dist/features/audit/directory-label-resolver.d.ts +4 -1
  4. package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
  5. package/dist/features/audit/directory-label-resolver.js +8 -5
  6. package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
  7. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  8. package/dist/features/audit/mariadb-audit-reader.js +10 -2
  9. package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
  10. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  11. package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
  12. package/dist/features/authz/access-bundle-store.d.ts +4 -1
  13. package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
  14. package/dist/features/authz/access-bundle-store.js +19 -16
  15. package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
  16. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  17. package/dist/features/authz/catalog-reconcile.job.js +3 -2
  18. package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
  19. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  20. package/dist/features/authz/db-permission-facts.adapter.js +22 -16
  21. package/dist/features/authz/grant-admin.service.d.ts +4 -1
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +32 -37
  24. package/dist/features/authz/grant-ceiling.d.ts +6 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +19 -16
  27. package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
  28. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  29. package/dist/features/authz/mariadb-catalog-store.js +10 -7
  30. package/dist/features/authz/permission-version.bumper.d.ts +6 -1
  31. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  32. package/dist/features/authz/permission-version.bumper.js +13 -11
  33. package/dist/features/authz/record-authorization.d.ts +16 -2
  34. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  35. package/dist/features/authz/record-authorization.js +29 -5
  36. package/dist/features/authz/role-binding.d.ts +3 -2
  37. package/dist/features/authz/role-binding.d.ts.map +1 -1
  38. package/dist/features/authz/role-binding.js +7 -8
  39. package/dist/features/company/company-hierarchy.service.d.ts +6 -1
  40. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  41. package/dist/features/company/company-hierarchy.service.js +17 -16
  42. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +5 -1
  43. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  44. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +18 -14
  45. package/dist/features/framework-schemas.token.d.ts +8 -0
  46. package/dist/features/framework-schemas.token.d.ts.map +1 -0
  47. package/dist/features/framework-schemas.token.js +10 -0
  48. package/dist/features/identity-feature.module.d.ts +36 -2
  49. package/dist/features/identity-feature.module.d.ts.map +1 -1
  50. package/dist/features/identity-feature.module.js +142 -34
  51. package/dist/features/membership/membership-checker.d.ts +7 -1
  52. package/dist/features/membership/membership-checker.d.ts.map +1 -1
  53. package/dist/features/membership/membership-checker.js +9 -4
  54. package/dist/features/migration-ledger-check.d.ts +15 -0
  55. package/dist/features/migration-ledger-check.d.ts.map +1 -0
  56. package/dist/features/migration-ledger-check.js +26 -0
  57. package/dist/features/scope/scope-closure.service.d.ts +8 -1
  58. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  59. package/dist/features/scope/scope-closure.service.js +22 -19
  60. package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
  61. package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
  62. package/dist/features/scope/scope-subtree.resolver.js +9 -4
  63. package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
  64. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
  65. package/dist/features/service-account/lock-service-accounts.js +4 -4
  66. package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
  67. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
  68. package/dist/features/service-account/no-expiry-key-report.js +17 -10
  69. package/dist/features/service-account/service-account.store.d.ts +8 -1
  70. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  71. package/dist/features/service-account/service-account.store.js +19 -14
  72. package/dist/features/user/user.service.d.ts +7 -3
  73. package/dist/features/user/user.service.d.ts.map +1 -1
  74. package/dist/features/user/user.service.js +12 -4
  75. package/dist/record-share.object.d.ts.map +1 -1
  76. package/dist/record-share.object.js +3 -2
  77. package/dist/schema.d.ts +15 -2
  78. package/dist/schema.d.ts.map +1 -1
  79. package/dist/schema.js +17 -2
  80. package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
  81. package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
  82. package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
  83. package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
  84. package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
  85. package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
  86. package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
  87. package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
  88. package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
  89. package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
  90. package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
  91. package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
  92. package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
  93. package/migrations/1710000000022-CreateStoredFile.cjs +14 -4
  94. package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
  95. package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
  96. package/package.json +4 -4
@@ -8,8 +8,7 @@ const grant_ceiling_js_1 = require("./grant-ceiling.js");
8
8
  const role_binding_js_1 = require("./role-binding.js");
9
9
  const identity_errors_js_1 = require("../shared/identity-errors.js");
10
10
  const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
11
- const AUTHZ = 'shield_authz';
12
- const APP = 'shield_app';
11
+ const schema_js_1 = require("../../schema.js");
13
12
  /** The grant-admin actions that REMOVE authority from a role's holders (Phase 8 #2). */
14
13
  const REVOKE_ACTIONS = new Set([
15
14
  'role.permission_revoked',
@@ -42,6 +41,7 @@ class GrantAdminService {
42
41
  bumper;
43
42
  ceiling;
44
43
  audit;
44
+ schemas;
45
45
  bundles;
46
46
  /**
47
47
  * @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
@@ -51,20 +51,22 @@ class GrantAdminService {
51
51
  * enforces. Root bypasses regardless.
52
52
  * @param audit The grant-administration audit sink (D6): written on the grant's own transaction
53
53
  * connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
54
+ * @param schemas Resolved framework names (absent ⇒ resolved from the namespace, once, here).
54
55
  */
55
- constructor(pool, bumper, bundleStore, ceiling, audit) {
56
+ constructor(pool, bumper, bundleStore, ceiling, audit, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
56
57
  this.pool = pool;
57
58
  this.bumper = bumper;
58
59
  this.ceiling = ceiling;
59
60
  this.audit = audit;
60
- this.bundles = bundleStore ?? new access_bundle_store_js_1.AccessBundleStore(pool);
61
+ this.schemas = schemas;
62
+ this.bundles = bundleStore ?? new access_bundle_store_js_1.AccessBundleStore(pool, schemas);
61
63
  }
62
64
  /** All grantable exact keys (from the catalog) + the computed wildcard set. */
63
65
  async listCatalog() {
64
66
  let rows;
65
67
  try {
66
68
  rows = (await this.pool.query(`SELECT perm_key, perm_label, perm_module, perm_type_key, perm_action
67
- FROM \`${AUTHZ}\`.\`permission\`
69
+ FROM \`${this.schemas.authz}\`.\`permission\`
68
70
  WHERE perm_status = 'active'
69
71
  ORDER BY perm_type_key, perm_action`));
70
72
  }
@@ -98,15 +100,15 @@ class GrantAdminService {
98
100
  const ph = subtreeScope.nodeIds.map(() => '?').join(', ');
99
101
  // Empty subtree → no placeholders → the check can't match → fail-closed empty.
100
102
  const bound = subtreeScope.nodeIds.length
101
- ? (await this.pool.query(`SELECT 1 FROM \`${APP}\`.\`membership_posting\` mp
102
- JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
103
+ ? (await this.pool.query(`SELECT 1 FROM \`${this.schemas.app}\`.\`membership_posting\` mp
104
+ JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
103
105
  WHERE mp.mp_role_uuid = ? AND cm.cm_company_uuid IN (${ph})
104
106
  LIMIT 1`, [roleUuid, ...subtreeScope.nodeIds]))
105
107
  : [];
106
108
  if (bound.length === 0)
107
109
  return { grants: [] };
108
110
  }
109
- const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid]));
111
+ const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid]));
110
112
  return { grants: rows.map((r) => r.rp_perm_key) };
111
113
  }
112
114
  catch (cause) {
@@ -147,11 +149,9 @@ class GrantAdminService {
147
149
  // Lock the role row FIRST — the posting path locks the same row before it inserts — then
148
150
  // re-run the binding rule on THIS connection, so a binding committed by a writer we waited
149
151
  // for is seen, and a role that became shared in the meantime is refused (rolled back).
150
- await conn.query(`SELECT r_uuid FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? FOR UPDATE`, [
151
- roleUuid,
152
- ]);
152
+ await conn.query(`SELECT r_uuid FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? FOR UPDATE`, [roleUuid]);
153
153
  if (this.ceiling && actor.isRoot !== true) {
154
- grant_ceiling_js_1.GrantCeiling.checkAdministrable(await (0, role_binding_js_1.readRoleBinding)(conn, roleUuid, (0, grant_ceiling_js_1.subtreeOf)(actor)));
154
+ grant_ceiling_js_1.GrantCeiling.checkAdministrable(await (0, role_binding_js_1.readRoleBinding)(conn, roleUuid, (0, grant_ceiling_js_1.subtreeOf)(actor), this.schemas));
155
155
  // Phase 8 #2: a revocation strips authority from every holder of the role, so the actor
156
156
  // must dominate each of them — checked under the role lock (no holder can appear unseen).
157
157
  // Phase 9 #9: that includes the service accounts holding the role, judged as a role
@@ -196,12 +196,12 @@ class GrantAdminService {
196
196
  if (!this.audit)
197
197
  return;
198
198
  const query = (sql, params) => conn.query(sql, params);
199
- const roleRows = (await query(`SELECT r_name FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
199
+ const roleRows = (await query(`SELECT r_name FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
200
200
  const tenants = [...new Set(bumpedTenants)];
201
201
  const nameOf = new Map();
202
202
  if (tenants.length > 0) {
203
203
  const ph = tenants.map(() => '?').join(', ');
204
- const rows = (await query(`SELECT c_uuid, c_name FROM \`${APP}\`.\`companies\` WHERE c_uuid IN (${ph})`, tenants));
204
+ const rows = (await query(`SELECT c_uuid, c_name FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid IN (${ph})`, tenants));
205
205
  for (const r of rows)
206
206
  nameOf.set(r.c_uuid, r.c_name);
207
207
  }
@@ -248,11 +248,11 @@ class GrantAdminService {
248
248
  const actorUuid = grantor.id;
249
249
  await this.writeGrants('role.permission_granted', roleUuid, [key], grantor, async (conn, k) => {
250
250
  // Idempotent insert — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
251
- await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
251
+ await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`role_permission\`
252
252
  (rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
253
253
  SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
254
254
  WHERE NOT EXISTS (
255
- SELECT 1 FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
255
+ SELECT 1 FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
256
256
  )`, [roleUuid, k, actorUuid ?? null, roleUuid, k]);
257
257
  });
258
258
  }
@@ -267,7 +267,7 @@ class GrantAdminService {
267
267
  roleUuid = await this.storedRoleUuid(roleUuid);
268
268
  await this.assertMayChangeGrants(roleUuid, [key], actor);
269
269
  await this.writeGrants('role.permission_revoked', roleUuid, [key], actor, async (conn, k) => {
270
- await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, k]);
270
+ await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, k]);
271
271
  });
272
272
  }
273
273
  /**
@@ -301,16 +301,14 @@ class GrantAdminService {
301
301
  if (this.ceiling && actor.isRoot !== true) {
302
302
  await this.ceiling.assertMayStripRoleFromServiceAccounts(actor, roleUuid, accounts.map((a) => a.sa_tenant_id), 'delete');
303
303
  }
304
- const keys = (await conn.query(`SELECT rp_perm_key FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid])).map((r) => r.rp_perm_key);
304
+ const keys = (await conn.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? ORDER BY rp_perm_key`, [roleUuid])).map((r) => r.rp_perm_key);
305
305
  const bumped = await this.bumper.bumpForRole(roleUuid, conn);
306
- await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ?`, [
307
- roleUuid,
308
- ]);
306
+ await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ?`, [roleUuid]);
309
307
  const nowMs = Date.now();
310
308
  for (const a of accounts) {
311
309
  const remaining = jsonIdArray(a.sa_role_ids).filter((id) => id !== roleUuid);
312
310
  const previous = a.sa_updated_at_ms === null ? 0 : Number(a.sa_updated_at_ms);
313
- await conn.query(`UPDATE \`${APP}\`.\`service_account\` SET sa_role_ids = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), Math.max(nowMs, previous + 1), a.sa_id]);
311
+ await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_role_ids = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), Math.max(nowMs, previous + 1), a.sa_id]);
314
312
  }
315
313
  await this.auditGrantChange(conn, 'role.permission_revoked', roleUuid, keys, actor, bumped, {
316
314
  roleDeleted: true,
@@ -349,14 +347,11 @@ class GrantAdminService {
349
347
  async storedRoleUuid(roleUuid) {
350
348
  if (typeof roleUuid !== 'string' || roleUuid.length === 0)
351
349
  return roleUuid;
352
- const rows = (await this.pool.query(`SELECT r_uuid FROM \`${APP}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
350
+ const rows = (await this.pool.query(`SELECT r_uuid FROM \`${this.schemas.app}\`.\`roles\` WHERE r_uuid = ? LIMIT 1`, [roleUuid]));
353
351
  return rows[0]?.r_uuid ?? roleUuid;
354
352
  }
355
353
  lockAccountsHoldingRole(conn, roleUuid) {
356
- return (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms', {
357
- sql: 'JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))',
358
- params: [roleUuid],
359
- });
354
+ return (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_role_ids, sa_updated_at_ms', { sql: 'JSON_CONTAINS(sa_role_ids, JSON_QUOTE(?))', params: [roleUuid] }, this.schemas);
360
355
  }
361
356
  // ── Access bundles ────────────────────────────────────────────────────────
362
357
  /** All active bundles + their resolved atomic keys, for the Access UI. */
@@ -385,11 +380,11 @@ class GrantAdminService {
385
380
  const actorUuid = grantor.id;
386
381
  await this.writeGrants('role.bundle_granted', roleUuid, keys, grantor, async (conn, key) => {
387
382
  // Idempotent — UNIQUE (rp_role_uuid, rp_perm_key); a duplicate is a no-op.
388
- await conn.query(`INSERT INTO \`${AUTHZ}\`.\`role_permission\`
383
+ await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`role_permission\`
389
384
  (rp_uuid, rp_role_uuid, rp_perm_key, rp_granted_at, rp_granted_by)
390
385
  SELECT UUID(), ?, ?, UTC_TIMESTAMP(), ?
391
386
  WHERE NOT EXISTS (
392
- SELECT 1 FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
387
+ SELECT 1 FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?
393
388
  )`, [roleUuid, key, actorUuid ?? null, roleUuid, key]);
394
389
  }, { bundle: bundleKey });
395
390
  }
@@ -405,7 +400,7 @@ class GrantAdminService {
405
400
  const keys = await this.resolveBundleKeysOrThrow(bundleKey);
406
401
  await this.assertMayChangeGrants(roleUuid, [...keys], actor);
407
402
  await this.writeGrants('role.bundle_revoked', roleUuid, keys, actor, async (conn, key) => {
408
- await conn.query(`DELETE FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
403
+ await conn.query(`DELETE FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid = ? AND rp_perm_key = ?`, [roleUuid, key]);
409
404
  }, { bundle: bundleKey });
410
405
  }
411
406
  /** Resolve a bundle key → its atomic keys; throw BadKeyError when unknown/orphaned. */
@@ -451,14 +446,14 @@ class GrantAdminService {
451
446
  // identical on BOTH the count and page queries so they can never disagree.
452
447
  const scope = subtreeScope.isRoot
453
448
  ? { sql: '', params: [] }
454
- : (0, role_binding_js_1.subtreeExists)(subtreeScope.nodeIds);
449
+ : (0, role_binding_js_1.subtreeExists)(subtreeScope.nodeIds, this.schemas);
455
450
  // Distinct granted roles (a role appears iff it has ≥1 role_permission row), filtered
456
451
  // by name search. Bind the LIKE term as a param (wildcards escaped).
457
452
  const likeClause = search ? `AND r.r_name LIKE ?` : '';
458
453
  const likeArgs = search ? [`%${search.replace(/[%_\\]/g, '\\$&')}%`] : [];
459
454
  const [{ total = 0 } = {}] = (await this.pool.query(`SELECT COUNT(DISTINCT rp.rp_role_uuid) AS total
460
- FROM \`${AUTHZ}\`.\`role_permission\` rp
461
- JOIN \`shield_app\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
455
+ FROM \`${this.schemas.authz}\`.\`role_permission\` rp
456
+ JOIN \`${this.schemas.app}\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
462
457
  WHERE 1 = 1${scope.sql} ${likeClause}`, [...scope.params, ...likeArgs]));
463
458
  // The page of role uuids + name + latest-grant metadata. name/grantedAt sort in SQL;
464
459
  // permCount falls back to a stable name sort here (re-sorted after aggregation).
@@ -466,8 +461,8 @@ class GrantAdminService {
466
461
  const pageRoles = (await this.pool.query(`SELECT rp.rp_role_uuid AS role_uuid,
467
462
  r.r_name AS role_name,
468
463
  MAX(rp.rp_granted_at) AS latest_at
469
- FROM \`${AUTHZ}\`.\`role_permission\` rp
470
- JOIN \`shield_app\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
464
+ FROM \`${this.schemas.authz}\`.\`role_permission\` rp
465
+ JOIN \`${this.schemas.app}\`.\`roles\` r ON r.r_uuid = rp.rp_role_uuid
471
466
  WHERE 1 = 1${scope.sql} ${likeClause}
472
467
  GROUP BY rp.rp_role_uuid, r.r_name
473
468
  ORDER BY ${orderCol} ${dir}, r.r_name ASC
@@ -482,8 +477,8 @@ class GrantAdminService {
482
477
  rp.rp_perm_key AS perm_key,
483
478
  rp.rp_granted_at AS granted_at,
484
479
  u.u_username AS granted_by_name
485
- FROM \`${AUTHZ}\`.\`role_permission\` rp
486
- LEFT JOIN \`shield_app\`.\`users\` u ON u.u_uuid = rp.rp_granted_by
480
+ FROM \`${this.schemas.authz}\`.\`role_permission\` rp
481
+ LEFT JOIN \`${this.schemas.app}\`.\`users\` u ON u.u_uuid = rp.rp_granted_by
487
482
  WHERE rp.rp_role_uuid IN (${ph})`, roleUuids));
488
483
  const byRole = new Map();
489
484
  for (const row of grantRows) {
@@ -15,6 +15,7 @@
15
15
  import type { Pool } from 'mariadb';
16
16
  import type { ModuleRegistry, Principal } from '@astram/core';
17
17
  import { type RoleBinding } from './role-binding.js';
18
+ import { type IdentitySchemaNames } from '../../schema.js';
18
19
  /** The grantor exceeds their own authority — cannot grant a key they do not hold (→ 403). */
19
20
  export declare class GrantCeilingError extends Error {
20
21
  constructor(key: string, message?: string);
@@ -43,6 +44,8 @@ export declare class GrantCeiling {
43
44
  private readonly registry;
44
45
  /** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
45
46
  private readonly now;
47
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
48
+ private readonly schemas;
46
49
  private readonly rootIds;
47
50
  constructor(pool: Pool, registry: ModuleRegistry,
48
51
  /**
@@ -53,7 +56,9 @@ export declare class GrantCeiling {
53
56
  */
54
57
  rootIds: readonly string[] | null,
55
58
  /** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
56
- now?: () => number);
59
+ now?: () => number,
60
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
61
+ schemas?: IdentitySchemaNames);
57
62
  /** The D1 administer rule over a binding read: used inside and nowhere outside (404 / 403). */
58
63
  static checkAdministrable(b: RoleBinding): void;
59
64
  /** The #11 assign rule over a binding read: never a role also used outside (403). */
@@ -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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"}
@@ -9,8 +9,7 @@ const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.
9
9
  const role_binding_js_1 = require("./role-binding.js");
10
10
  const identity_errors_js_1 = require("../shared/identity-errors.js");
11
11
  const service_account_service_js_1 = require("../service-account/service-account.service.js");
12
- const AUTHZ = 'shield_authz';
13
- const APP = 'shield_app';
12
+ const schema_js_1 = require("../../schema.js");
14
13
  /** The grantor exceeds their own authority — cannot grant a key they do not hold (→ 403). */
15
14
  class GrantCeilingError extends Error {
16
15
  constructor(key, message) {
@@ -54,6 +53,7 @@ class GrantCeiling {
54
53
  pool;
55
54
  registry;
56
55
  now;
56
+ schemas;
57
57
  rootIds;
58
58
  constructor(pool, registry,
59
59
  /**
@@ -64,10 +64,13 @@ class GrantCeiling {
64
64
  */
65
65
  rootIds,
66
66
  /** Injectable clock (epoch-ms) for the posting effective-window read; defaults to Date.now. */
67
- now = Date.now) {
67
+ now = Date.now,
68
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
69
+ schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
68
70
  this.pool = pool;
69
71
  this.registry = registry;
70
72
  this.now = now;
73
+ this.schemas = schemas;
71
74
  if (rootIds !== null && !Array.isArray(rootIds)) {
72
75
  throw new TypeError('GrantCeiling: rootIds is required (the root allowlist, or null when none is configured)');
73
76
  }
@@ -96,7 +99,7 @@ class GrantCeiling {
96
99
  if (roleIds.length === 0)
97
100
  return [];
98
101
  const ph = roleIds.map(() => '?').join(', ');
99
- const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, [...roleIds]));
102
+ const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, [...roleIds]));
100
103
  return rows.map((r) => r.rp_perm_key);
101
104
  }
102
105
  /** Run a store read, mapping any non-domain failure to AuthorizationUnavailableError (503). */
@@ -118,11 +121,11 @@ class GrantCeiling {
118
121
  */
119
122
  async effectiveKeys(grantor) {
120
123
  try {
121
- const roleIds = await (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, grantor.id, grantor.tenantId, this.now);
124
+ const roleIds = await (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, grantor.id, grantor.tenantId, this.schemas, this.now);
122
125
  if (roleIds.length === 0)
123
126
  return new Set();
124
127
  const ph = roleIds.map(() => '?').join(', ');
125
- const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${AUTHZ}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, roleIds));
128
+ const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\` WHERE rp_role_uuid IN (${ph})`, roleIds));
126
129
  const raw = rows.map((r) => r.rp_perm_key);
127
130
  const registered = this.registry.listActions().map((a) => a.permissionKey);
128
131
  return new Set((0, db_permission_facts_adapter_js_1.expandGrantKeys)(raw, registered));
@@ -198,13 +201,13 @@ class GrantCeiling {
198
201
  }
199
202
  const subtree = new Set(subtreeOf(actor));
200
203
  const { companies, roleIds, superuser, storedId } = await this.facts('target memberships', async () => {
201
- const su = (await this.pool.query(`SELECT u_uuid, u_is_platform_superuser AS su FROM \`${APP}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [targetUserId]));
204
+ const su = (await this.pool.query(`SELECT u_uuid, u_is_platform_superuser AS su FROM \`${this.schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [targetUserId]));
202
205
  const memberships = (await this.pool.query(`SELECT DISTINCT cm_company_uuid AS company
203
- FROM \`${APP}\`.\`company_membership\`
206
+ FROM \`${this.schemas.app}\`.\`company_membership\`
204
207
  WHERE cm_user_uuid = ?`, [targetUserId]));
205
208
  const postings = (await this.pool.query(`SELECT DISTINCT mp.mp_role_uuid AS role_uuid
206
- FROM \`${APP}\`.\`membership_posting\` mp
207
- JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
209
+ FROM \`${this.schemas.app}\`.\`membership_posting\` mp
210
+ JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
208
211
  WHERE cm.cm_user_uuid = ?`, [targetUserId]));
209
212
  return {
210
213
  companies: memberships.map((m) => m.company),
@@ -253,8 +256,8 @@ class GrantCeiling {
253
256
  return;
254
257
  const holders = await this.facts('role holders', async () => {
255
258
  const rows = (await this.pool.query(`SELECT DISTINCT cm.cm_user_uuid AS holder
256
- FROM \`${APP}\`.\`membership_posting\` mp
257
- JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
259
+ FROM \`${this.schemas.app}\`.\`membership_posting\` mp
260
+ JOIN \`${this.schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
258
261
  WHERE mp.mp_role_uuid = ?`, [roleUuid]));
259
262
  return rows.map((r) => r.holder);
260
263
  });
@@ -298,7 +301,7 @@ class GrantCeiling {
298
301
  const nodes = (0, service_account_service_js_1.ceilingOf)(admin, []);
299
302
  if (accountTenantIds.some((t) => !(0, service_account_service_js_1.ceilingAllowsNode)(nodes, t)))
300
303
  throw refuse();
301
- const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.now));
304
+ const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.schemas, this.now));
302
305
  if (!(0, service_account_service_js_1.ceilingAllowsRole)((0, service_account_service_js_1.ceilingOf)(admin, held), roleUuid))
303
306
  throw refuse();
304
307
  }
@@ -328,7 +331,7 @@ class GrantCeiling {
328
331
  throw refuse();
329
332
  if (accounts.every((a) => a.roleIds.length === 0))
330
333
  return;
331
- const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.now));
334
+ const held = await this.facts('held roles', () => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(this.pool, actor.id, actor.tenantId, this.schemas, this.now));
332
335
  const ceiling = (0, service_account_service_js_1.ceilingOf)(admin, held);
333
336
  if (accounts.some((a) => a.roleIds.some((r) => !(0, service_account_service_js_1.ceilingAllowsRole)(ceiling, r)))) {
334
337
  throw refuse();
@@ -350,7 +353,7 @@ class GrantCeiling {
350
353
  return;
351
354
  if (companyUuid === null)
352
355
  throw new identity_errors_js_1.IdentityBadRequestError(message);
353
- const rows = await this.facts('company org units', async () => (await this.pool.query(`SELECT ${unitCol} AS unit FROM \`${APP}\`.\`${table}\`
356
+ const rows = await this.facts('company org units', async () => (await this.pool.query(`SELECT ${unitCol} AS unit FROM \`${this.schemas.app}\`.\`${table}\`
354
357
  WHERE ${companyCol} = ? AND ${unitCol} IN (${wanted.map(() => '?').join(', ')})`, [companyUuid, ...wanted])));
355
358
  const linked = new Set(rows.map((r) => r.unit));
356
359
  if (wanted.some((u) => !linked.has(u)))
@@ -361,7 +364,7 @@ class GrantCeiling {
361
364
  }
362
365
  /** Where `roleUuid` is bound relative to the actor's subtree (postings + service accounts). */
363
366
  async roleBinding(actor, roleUuid) {
364
- return this.facts('role binding', () => (0, role_binding_js_1.readRoleBinding)(this.pool, roleUuid, subtreeOf(actor)));
367
+ return this.facts('role binding', () => (0, role_binding_js_1.readRoleBinding)(this.pool, roleUuid, subtreeOf(actor), this.schemas));
365
368
  }
366
369
  /**
367
370
  * Assert the actor may ADMINISTER the role — edit its grants, rename or delete it (#1, D1
@@ -1,5 +1,6 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import type { CatalogStore, CatalogPermissionRow } from '@astram/guard';
3
+ import { type IdentitySchemaNames } from '../../schema.js';
3
4
  /**
4
5
  * CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
5
6
  * permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
@@ -7,7 +8,9 @@ import type { CatalogStore, CatalogPermissionRow } from '@astram/guard';
7
8
  */
8
9
  export declare class MariaDbCatalogStore implements CatalogStore {
9
10
  private readonly pool;
10
- constructor(pool: Pool);
11
+ private readonly schemas;
12
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
13
+ constructor(pool: Pool, schemas?: IdentitySchemaNames);
11
14
  listPermissions(): Promise<CatalogPermissionRow[]>;
12
15
  upsertPermission(row: Omit<CatalogPermissionRow, 'status'>): Promise<boolean>;
13
16
  markOrphaned(keys: readonly string[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAIxE;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwBlD,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB7E,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAQzD"}
1
+ {"version":3,"file":"mariadb-catalog-store.d.ts","sourceRoot":"","sources":["../../../src/features/authz/mariadb-catalog-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IAGpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,4FAA4F;gBAEzE,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAGrE,eAAe,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwBlD,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB7E,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAQzD"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MariaDbCatalogStore = void 0;
4
- const AUTHZ = 'shield_authz';
4
+ const schema_js_1 = require("../../schema.js");
5
5
  /**
6
6
  * CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
7
7
  * permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
@@ -9,12 +9,15 @@ const AUTHZ = 'shield_authz';
9
9
  */
10
10
  class MariaDbCatalogStore {
11
11
  pool;
12
- constructor(pool) {
12
+ schemas;
13
+ /** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
14
+ constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
13
15
  this.pool = pool;
16
+ this.schemas = schemas;
14
17
  }
15
18
  async listPermissions() {
16
19
  const rows = (await this.pool.query(`SELECT perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status
17
- FROM \`${AUTHZ}\`.\`permission\``));
20
+ FROM \`${this.schemas.authz}\`.\`permission\``));
18
21
  return rows.map((r) => ({
19
22
  permissionKey: r.perm_key,
20
23
  moduleKey: r.perm_module,
@@ -26,10 +29,10 @@ class MariaDbCatalogStore {
26
29
  }));
27
30
  }
28
31
  async upsertPermission(row) {
29
- const res = (await this.pool.query(`INSERT INTO \`${AUTHZ}\`.\`permission\`
32
+ const res = (await this.pool.query(`INSERT INTO \`${this.schemas.authz}\`.\`permission\`
30
33
  (perm_uuid, perm_key, perm_module, perm_type_key, perm_action, perm_label, perm_tier, perm_status)
31
34
  SELECT UUID(), ?, ?, ?, ?, ?, ?, 'active'
32
- WHERE NOT EXISTS (SELECT 1 FROM \`${AUTHZ}\`.\`permission\` WHERE perm_key = ?)`, [
35
+ WHERE NOT EXISTS (SELECT 1 FROM \`${this.schemas.authz}\`.\`permission\` WHERE perm_key = ?)`, [
33
36
  row.permissionKey,
34
37
  row.moduleKey,
35
38
  row.typeKey,
@@ -44,13 +47,13 @@ class MariaDbCatalogStore {
44
47
  if (keys.length === 0)
45
48
  return;
46
49
  const ph = keys.map(() => '?').join(', ');
47
- await this.pool.query(`UPDATE \`${AUTHZ}\`.\`permission\` SET perm_status = 'orphaned' WHERE perm_key IN (${ph})`, [...keys]);
50
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'orphaned' WHERE perm_key IN (${ph})`, [...keys]);
48
51
  }
49
52
  async markActive(keys) {
50
53
  if (keys.length === 0)
51
54
  return;
52
55
  const ph = keys.map(() => '?').join(', ');
53
- await this.pool.query(`UPDATE \`${AUTHZ}\`.\`permission\` SET perm_status = 'active' WHERE perm_key IN (${ph})`, [...keys]);
56
+ await this.pool.query(`UPDATE \`${this.schemas.authz}\`.\`permission\` SET perm_status = 'active' WHERE perm_key IN (${ph})`, [...keys]);
54
57
  }
55
58
  }
56
59
  exports.MariaDbCatalogStore = MariaDbCatalogStore;
@@ -1,3 +1,4 @@
1
+ import { type IdentitySchemaNames } from '../../schema.js';
1
2
  /**
2
3
  * The minimal query surface this writer needs — a `mariadb` `Pool` (or `PoolConnection`)
3
4
  * satisfies it structurally, but the type itself stays framework-agnostic so this signature
@@ -29,6 +30,7 @@ export interface VersionPublisher {
29
30
  }
30
31
  export declare class PermissionVersionBumper {
31
32
  private publisher?;
33
+ private readonly schemas;
32
34
  /**
33
35
  * Bumped versions waiting for their transaction to commit, per connection (ADR-0035). A
34
36
  * transaction connection is a pooled object reused across requests, so callers either publish
@@ -39,8 +41,11 @@ export declare class PermissionVersionBumper {
39
41
  /**
40
42
  * @param publisher OPTIONAL Redis publisher (ADR-0035). Absent → DB-only bumps, exactly as
41
43
  * before (no read-back). `IdentityFeatureModule` attaches the guard's cache through DI.
44
+ * @param schemas resolved framework names: `authz_permission_version` lives in `schemas.authz`
45
+ * (ADR-0011), the membership/posting join `bumpForRole` walks in `schemas.app`. Absent ⇒
46
+ * resolved from the namespace, once, here.
42
47
  */
43
- constructor(publisher?: VersionPublisher | undefined);
48
+ constructor(publisher?: VersionPublisher | undefined, schemas?: IdentitySchemaNames);
44
49
  /** Attach the Redis publisher once it is resolved through DI (first attach wins). */
45
50
  attachVersionPublisher(publisher: VersionPublisher): void;
46
51
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"permission-version.bumper.d.ts","sourceRoot":"","sources":["../../../src/features/authz/permission-version.bumper.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAOD,qBAAa,uBAAuB;IAatB,OAAO,CAAC,SAAS,CAAC;IAZ9B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAEhE;;;OAGG;gBACiB,SAAS,CAAC,EAAE,gBAAgB,YAAA;IAEhD,qFAAqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIzD;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,kFAAkF;IAClF,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;YAIvB,WAAW;IAUzB;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAYvE;;;;;OAKG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmB1E;;;;;;OAMG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBvE;;;;;;OAMG;YACW,UAAU;CA4BzB"}
1
+ {"version":3,"file":"permission-version.bumper.d.ts","sourceRoot":"","sources":["../../../src/features/authz/permission-version.bumper.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;;;;;;;;;;;GAaG;AACH;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAOD,qBAAa,uBAAuB;IAiBhC,OAAO,CAAC,SAAS,CAAC;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB1B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAEhE;;;;;;OAMG;gBAEO,SAAS,CAAC,EAAE,gBAAgB,YAAA,EACnB,OAAO,GAAE,mBAA+C;IAG3E,qFAAqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIzD;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,kFAAkF;IAClF,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;YAIvB,WAAW;IAUzB;;;;OAIG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAYvE;;;;;OAKG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmB1E;;;;;;OAMG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgBvE;;;;;;OAMG;YACW,UAAU;CA4BzB"}
@@ -3,16 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PermissionVersionBumper = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
5
  const core_1 = require("@astram/core");
6
- /** Schema the `authz_permission_version` table lives in (ADR-0011). */
7
- const AUTHZ = 'shield_authz';
8
- /** Schema the membership/posting tables live in — the join `bumpForRole` walks. */
9
- const APP = 'shield_app';
6
+ const schema_js_1 = require("../../schema.js");
10
7
  /** An autocommit pool (it hands out connections) rather than one transaction's connection. */
11
8
  function isAutocommitPool(conn) {
12
9
  return typeof conn.getConnection === 'function';
13
10
  }
14
11
  class PermissionVersionBumper {
15
12
  publisher;
13
+ schemas;
16
14
  /**
17
15
  * Bumped versions waiting for their transaction to commit, per connection (ADR-0035). A
18
16
  * transaction connection is a pooled object reused across requests, so callers either publish
@@ -23,9 +21,13 @@ class PermissionVersionBumper {
23
21
  /**
24
22
  * @param publisher OPTIONAL Redis publisher (ADR-0035). Absent → DB-only bumps, exactly as
25
23
  * before (no read-back). `IdentityFeatureModule` attaches the guard's cache through DI.
24
+ * @param schemas resolved framework names: `authz_permission_version` lives in `schemas.authz`
25
+ * (ADR-0011), the membership/posting join `bumpForRole` walks in `schemas.app`. Absent ⇒
26
+ * resolved from the namespace, once, here.
26
27
  */
27
- constructor(publisher) {
28
+ constructor(publisher, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
28
29
  this.publisher = publisher;
30
+ this.schemas = schemas;
29
31
  }
30
32
  /** Attach the Redis publisher once it is resolved through DI (first attach wins). */
31
33
  attachVersionPublisher(publisher) {
@@ -100,13 +102,13 @@ class PermissionVersionBumper {
100
102
  */
101
103
  async tenantsForRole(roleUuid, conn) {
102
104
  const rows = (await conn.query(`SELECT DISTINCT cm.cm_company_uuid AS tenant
103
- FROM \`${APP}\`.\`company_membership\` cm
104
- JOIN \`${APP}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
105
+ FROM \`${this.schemas.app}\`.\`company_membership\` cm
106
+ JOIN \`${this.schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
105
107
  WHERE mp.mp_role_uuid = ? AND cm.cm_is_active = 1`, [roleUuid]));
106
108
  // Service accounts (ADR-0033) hold roles on `sa_role_ids`, not postings: a role held only by
107
109
  // machine principals must still invalidate their tenants' cached grants (security hardening).
108
110
  const saRows = (await conn.query(`SELECT DISTINCT sa.sa_tenant_id AS tenant
109
- FROM \`${APP}\`.\`service_account\` sa
111
+ FROM \`${this.schemas.app}\`.\`service_account\` sa
110
112
  WHERE JSON_CONTAINS(sa.sa_role_ids, JSON_QUOTE(?))`, [roleUuid]));
111
113
  return [...new Set([...rows, ...saRows].map((r) => r.tenant))];
112
114
  }
@@ -120,7 +122,7 @@ class PermissionVersionBumper {
120
122
  async bumpForUser(userUuid, conn) {
121
123
  try {
122
124
  const rows = (await conn.query(`SELECT DISTINCT cm.cm_company_uuid AS tenant
123
- FROM \`${APP}\`.\`company_membership\` cm
125
+ FROM \`${this.schemas.app}\`.\`company_membership\` cm
124
126
  WHERE cm.cm_user_uuid = ?`, [userUuid]));
125
127
  const tenants = [...new Set(rows.map((r) => r.tenant))];
126
128
  for (const tenant of tenants)
@@ -139,14 +141,14 @@ class PermissionVersionBumper {
139
141
  * (the TTL bounds it) and never fails the bump.
140
142
  */
141
143
  async bumpTenant(tenantUuid, conn) {
142
- await conn.query(`INSERT INTO \`${AUTHZ}\`.\`authz_permission_version\` (apv_tenant_uuid, apv_version)
144
+ await conn.query(`INSERT INTO \`${this.schemas.authz}\`.\`authz_permission_version\` (apv_tenant_uuid, apv_version)
143
145
  VALUES (?, 1)
144
146
  ON DUPLICATE KEY UPDATE apv_version = apv_version + 1`, [tenantUuid]);
145
147
  if (this.publisher === undefined)
146
148
  return;
147
149
  let version;
148
150
  try {
149
- const rows = (await conn.query(`SELECT apv_version FROM \`${AUTHZ}\`.\`authz_permission_version\`
151
+ const rows = (await conn.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
150
152
  WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantUuid]));
151
153
  version = Number(rows[0]?.apv_version);
152
154
  }
@@ -12,14 +12,26 @@
12
12
  * adapted to `@astram/identity`'s own registrations (`identityObjects`) and env prefix (`APP_DB_*`).
13
13
  */
14
14
  import { type Pool } from 'mariadb';
15
+ import type { FrameworkSchemas } from '@astram/core';
15
16
  import { type RecordAuthorizationInputs } from '@astram/guard';
16
17
  import type { ObjectTypeInput } from '@astram/engine';
18
+ import type { IdentitySchemaNames } from '../../schema.js';
19
+ /**
20
+ * A pool-size variable: unset or blank gives `undefined`; otherwise plain digits, a positive safe
21
+ * integer, or it throws naming the variable (never the value). The single-layout pool budget in
22
+ * `@astram/guard` (`loadPoolBudget`) applies the same rule, so the budget counts the pools that open.
23
+ */
24
+ export declare function strictPoolSize(raw: string | undefined, name: string): number | undefined;
25
+ /** The authz pool cap: `APP_AUTHZ_POOL_MAX` (strict), else 4. */
26
+ export declare function authzPoolLimit(env?: NodeJS.ProcessEnv): number;
17
27
  /**
18
28
  * A lean `mariadb` pool for the authz grant-reads (built from `APP_DB_*` env via guard's
19
29
  * `loadDbConfig`). Kept small — these are pre-gate coarse-grant reads, not the main CRUD pool.
20
30
  * Opens the pool at CALL time (never at import) — the caller owns closing it.
31
+ * `schemas` (the names `forRoot` resolved or was given) set the default database and user; absent
32
+ * ⇒ from the env namespace. An explicit `APP_DB_USER` / `APP_DB_NAME` wins (QA P2-5).
21
33
  */
22
- export declare function createIdentityAuthzPool(): Pool;
34
+ export declare function createIdentityAuthzPool(schemas?: FrameworkSchemas): Pool;
23
35
  /**
24
36
  * Build the `RecordAuthorizationInputs` over `pool`. `@astram/identity` supplies only the
25
37
  * app-schema ports:
@@ -31,5 +43,7 @@ export declare function createIdentityAuthzPool(): Pool;
31
43
  * consumer type is `resource_not_registered`),
32
44
  * - `operationManifest` — empty (identity declares no trusted delegated operations).
33
45
  */
34
- export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[]): RecordAuthorizationInputs;
46
+ export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[],
47
+ /** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
48
+ schemas?: IdentitySchemaNames): RecordAuthorizationInputs;
35
49
  //# sourceMappingURL=record-authorization.d.ts.map
@@ -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,EAIL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAKtD;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,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;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"}