@astram/identity 0.10.0 → 0.12.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 (127) hide show
  1. package/dist/auth-facts-keys.d.ts +16 -0
  2. package/dist/auth-facts-keys.d.ts.map +1 -1
  3. package/dist/auth-facts-keys.js +26 -10
  4. package/dist/cross-tenant-grant.object.d.ts +2 -2
  5. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  6. package/dist/cross-tenant-grant.object.js +8 -4
  7. package/dist/features/audit/directory-label-resolver.d.ts +4 -1
  8. package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
  9. package/dist/features/audit/directory-label-resolver.js +8 -5
  10. package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
  11. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  12. package/dist/features/audit/mariadb-audit-reader.js +10 -2
  13. package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
  14. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  15. package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
  16. package/dist/features/authz/access-bundle-store.d.ts +4 -1
  17. package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
  18. package/dist/features/authz/access-bundle-store.js +19 -16
  19. package/dist/features/authz/catalog-bootstrap.provider.d.ts +4 -1
  20. package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
  21. package/dist/features/authz/catalog-bootstrap.provider.js +37 -6
  22. package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
  23. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  24. package/dist/features/authz/catalog-reconcile.job.js +3 -2
  25. package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
  26. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  27. package/dist/features/authz/db-permission-facts.adapter.js +22 -16
  28. package/dist/features/authz/grant-admin.service.d.ts +4 -1
  29. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  30. package/dist/features/authz/grant-admin.service.js +32 -37
  31. package/dist/features/authz/grant-ceiling.d.ts +6 -1
  32. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  33. package/dist/features/authz/grant-ceiling.js +19 -16
  34. package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
  35. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  36. package/dist/features/authz/mariadb-catalog-store.js +10 -7
  37. package/dist/features/authz/permission-version.bumper.d.ts +6 -1
  38. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  39. package/dist/features/authz/permission-version.bumper.js +13 -11
  40. package/dist/features/authz/record-authorization.d.ts +8 -2
  41. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  42. package/dist/features/authz/record-authorization.js +8 -4
  43. package/dist/features/authz/role-binding.d.ts +3 -2
  44. package/dist/features/authz/role-binding.d.ts.map +1 -1
  45. package/dist/features/authz/role-binding.js +7 -8
  46. package/dist/features/company/company-hierarchy.service.d.ts +6 -1
  47. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  48. package/dist/features/company/company-hierarchy.service.js +17 -16
  49. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +11 -6
  50. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  51. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +24 -19
  52. package/dist/features/files/files.controller.d.ts +27 -0
  53. package/dist/features/files/files.controller.d.ts.map +1 -0
  54. package/dist/features/files/files.controller.js +94 -0
  55. package/dist/features/files/files.service.d.ts +87 -0
  56. package/dist/features/files/files.service.d.ts.map +1 -0
  57. package/dist/features/files/files.service.js +311 -0
  58. package/dist/features/framework-schemas.token.d.ts +8 -0
  59. package/dist/features/framework-schemas.token.d.ts.map +1 -0
  60. package/dist/features/framework-schemas.token.js +10 -0
  61. package/dist/features/identity-feature.module.d.ts +29 -1
  62. package/dist/features/identity-feature.module.d.ts.map +1 -1
  63. package/dist/features/identity-feature.module.js +168 -41
  64. package/dist/features/membership/membership-checker.d.ts +7 -1
  65. package/dist/features/membership/membership-checker.d.ts.map +1 -1
  66. package/dist/features/membership/membership-checker.js +9 -4
  67. package/dist/features/migration-ledger-check.d.ts +15 -0
  68. package/dist/features/migration-ledger-check.d.ts.map +1 -0
  69. package/dist/features/migration-ledger-check.js +26 -0
  70. package/dist/features/policy/policy.service.d.ts +2 -1
  71. package/dist/features/policy/policy.service.d.ts.map +1 -1
  72. package/dist/features/policy/policy.service.js +15 -0
  73. package/dist/features/scope/scope-closure.service.d.ts +8 -1
  74. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  75. package/dist/features/scope/scope-closure.service.js +22 -19
  76. package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
  77. package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
  78. package/dist/features/scope/scope-subtree.resolver.js +9 -4
  79. package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
  80. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
  81. package/dist/features/service-account/lock-service-accounts.js +4 -4
  82. package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
  83. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
  84. package/dist/features/service-account/no-expiry-key-report.js +17 -10
  85. package/dist/features/service-account/service-account.store.d.ts +8 -1
  86. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  87. package/dist/features/service-account/service-account.store.js +19 -14
  88. package/dist/features/share/share.service.d.ts +19 -2
  89. package/dist/features/share/share.service.d.ts.map +1 -1
  90. package/dist/features/share/share.service.js +36 -2
  91. package/dist/features/user/user.service.d.ts +7 -3
  92. package/dist/features/user/user.service.d.ts.map +1 -1
  93. package/dist/features/user/user.service.js +12 -4
  94. package/dist/index.d.ts +9 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +13 -1
  97. package/dist/objects.d.ts.map +1 -1
  98. package/dist/objects.js +2 -0
  99. package/dist/record-share.object.d.ts +1 -1
  100. package/dist/record-share.object.d.ts.map +1 -1
  101. package/dist/record-share.object.js +7 -3
  102. package/dist/schema.d.ts +15 -2
  103. package/dist/schema.d.ts.map +1 -1
  104. package/dist/schema.js +17 -2
  105. package/dist/service-account.object.d.ts +3 -2
  106. package/dist/service-account.object.d.ts.map +1 -1
  107. package/dist/service-account.object.js +4 -2
  108. package/dist/stored-file.object.d.ts +35 -0
  109. package/dist/stored-file.object.d.ts.map +1 -0
  110. package/dist/stored-file.object.js +94 -0
  111. package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
  112. package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
  113. package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
  114. package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
  115. package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
  116. package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
  117. package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
  118. package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
  119. package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
  120. package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
  121. package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
  122. package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
  123. package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
  124. package/migrations/1710000000022-CreateStoredFile.cjs +53 -0
  125. package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
  126. package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
  127. package/package.json +8 -4
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import type { Pool } from 'mariadb';
15
15
  import type { PermissionFactsPort, CoarseGrants, ModuleRegistry } from '@astram/core';
16
+ import { type IdentitySchemaNames } from '../../schema.js';
16
17
  /**
17
18
  * Expand raw grant strings (exact or wildcard) to the set of EXACT registered keys.
18
19
  * Bounded to `allKeys` — a wildcard can never yield a key that isn't registered.
@@ -31,10 +32,11 @@ export declare function expandGrantKeys(rawKeys: string[], allKeys: string[]): s
31
32
  * window read. Making THIS function kind-aware gives all three callers correct grants uniformly
32
33
  * (one definition of "a principal's roles"), preserving the single `decide()` path.
33
34
  *
35
+ * @param schemas the resolved framework names (the tables live in `schemas.app`).
34
36
  * @param now injectable clock (epoch-ms), defaults to `Date.now` — bound as a `?`
35
37
  * integer against the BIGINT `mp_effective_*_ms` columns (timezone-immune).
36
38
  */
37
- export declare function readRoleIdsForTenant(pool: Pool, principalId: string, tenantId: string, now?: () => number): Promise<string[]>;
39
+ export declare function readRoleIdsForTenant(pool: Pool, principalId: string, tenantId: string, schemas: IdentitySchemaNames, now?: () => number): Promise<string[]>;
38
40
  /**
39
41
  * The next instant (epoch-ms, strictly after `now`) at which this USER's in-effect roles in
40
42
  * `tenantId` change by themselves — the earliest future `mp_effective_from_ms` (a posting starts)
@@ -42,7 +44,7 @@ export declare function readRoleIdsForTenant(pool: Pool, principalId: string, te
42
44
  * these moments, so cached facts must expire there (ADR-0035). `null` when there is none, and for
43
45
  * a service account (its roles have no windows). A query failure propagates (fail closed).
44
46
  */
45
- export declare function readNextPostingBoundaryMs(pool: Pool, principalId: string, tenantId: string, now?: () => number): Promise<number | null>;
47
+ export declare function readNextPostingBoundaryMs(pool: Pool, principalId: string, tenantId: string, schemas: IdentitySchemaNames, now?: () => number): Promise<number | null>;
46
48
  export declare class DbPermissionFactsAdapter implements PermissionFactsPort {
47
49
  private readonly pool;
48
50
  private readonly registry;
@@ -52,13 +54,17 @@ export declare class DbPermissionFactsAdapter implements PermissionFactsPort {
52
54
  * Injectable so a test can fix the clock relative to a seeded expired/active posting.
53
55
  */
54
56
  private readonly now;
57
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
58
+ private readonly schemas;
55
59
  constructor(pool: Pool, registry: ModuleRegistry,
56
60
  /**
57
61
  * Injectable clock (epoch-ms) for the posting effective-window compare. Defaults to
58
62
  * `Date.now`. Bound as a `?` integer against the BIGINT `_ms` columns — timezone-immune.
59
63
  * Injectable so a test can fix the clock relative to a seeded expired/active posting.
60
64
  */
61
- now?: () => number);
65
+ now?: () => number,
66
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
67
+ schemas?: IdentitySchemaNames);
62
68
  currentPermissionsVersion(_principalId: string, tenantId: string): Promise<number>;
63
69
  /**
64
70
  * The coarse grants + (ADR-0035) the LIVE role ids they were derived from + the time bound of
@@ -1 +1 @@
1
- {"version":3,"file":"db-permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../../src/features/authz/db-permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKtF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9E;AA0CD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAED,qBAAa,wBAAyB,YAAW,mBAAmB;IAEhE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAPH,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC;;;;OAIG;IACc,GAAG,GAAE,MAAM,MAAiB;IAGzC,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxF;;;;;;;OAOG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YAuBtE,WAAW;IASzB;;;OAGG;YACW,WAAW;YAIX,aAAa;CAS5B"}
1
+ {"version":3,"file":"db-permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../../src/features/authz/db-permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEtF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIpF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9E;AA2CD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,EAC5B,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,EAC5B,GAAG,GAAE,MAAM,MAAiB,GAC3B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAED,qBAAa,wBAAyB,YAAW,mBAAmB;IAEhE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBATP,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC;;;;OAIG;IACc,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAGrE,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxF;;;;;;;OAOG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;YA6BtE,WAAW;IASzB;;;OAGG;YACW,WAAW;YAIX,aAAa;CAS5B"}
@@ -5,6 +5,7 @@ exports.expandGrantKeys = expandGrantKeys;
5
5
  exports.readRoleIdsForTenant = readRoleIdsForTenant;
6
6
  exports.readNextPostingBoundaryMs = readNextPostingBoundaryMs;
7
7
  const core_1 = require("@astram/core");
8
+ const schema_js_1 = require("../../schema.js");
8
9
  const MODULE_PREFIX = 'identity';
9
10
  /**
10
11
  * Expand raw grant strings (exact or wildcard) to the set of EXACT registered keys.
@@ -58,8 +59,8 @@ function parseRoleIdsJson(v) {
58
59
  * membership path). A DISABLED account returns `[]` (in-scope but no roles → deny everything —
59
60
  * defense-in-depth alongside the key's own revoke check).
60
61
  */
61
- async function readServiceAccountRoleIds(pool, principalId, tenantId) {
62
- const rows = (await pool.query(`SELECT sa_role_ids, sa_status FROM \`shield_app\`.\`service_account\`
62
+ async function readServiceAccountRoleIds(pool, principalId, tenantId, schemas) {
63
+ const rows = (await pool.query(`SELECT sa_role_ids, sa_status FROM \`${schemas.app}\`.\`service_account\`
63
64
  WHERE sa_id = ? AND sa_tenant_id = ? LIMIT 1`, [principalId, tenantId]));
64
65
  const row = rows[0];
65
66
  if (row === undefined)
@@ -78,12 +79,13 @@ async function readServiceAccountRoleIds(pool, principalId, tenantId) {
78
79
  * window read. Making THIS function kind-aware gives all three callers correct grants uniformly
79
80
  * (one definition of "a principal's roles"), preserving the single `decide()` path.
80
81
  *
82
+ * @param schemas the resolved framework names (the tables live in `schemas.app`).
81
83
  * @param now injectable clock (epoch-ms), defaults to `Date.now` — bound as a `?`
82
84
  * integer against the BIGINT `mp_effective_*_ms` columns (timezone-immune).
83
85
  */
84
- async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now) {
86
+ async function readRoleIdsForTenant(pool, principalId, tenantId, schemas, now = Date.now) {
85
87
  // Service-account path (ADR-0033): its roles live on the account row, not memberships.
86
- const saRoles = await readServiceAccountRoleIds(pool, principalId, tenantId);
88
+ const saRoles = await readServiceAccountRoleIds(pool, principalId, tenantId, schemas);
87
89
  if (saRoles !== null)
88
90
  return saRoles;
89
91
  // Human path: roles from the active membership's in-effect postings — of an ACTIVE account only
@@ -92,9 +94,9 @@ async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now)
92
94
  // has a users row).
93
95
  const nowMs = now();
94
96
  const rows = (await pool.query(`SELECT DISTINCT mp.mp_role_uuid AS role_uuid
95
- FROM \`shield_app\`.\`company_membership\` cm
96
- JOIN \`shield_app\`.\`users\` u ON u.u_uuid = cm.cm_user_uuid AND u.u_status = 'active'
97
- JOIN \`shield_app\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
97
+ FROM \`${schemas.app}\`.\`company_membership\` cm
98
+ JOIN \`${schemas.app}\`.\`users\` u ON u.u_uuid = cm.cm_user_uuid AND u.u_status = 'active'
99
+ JOIN \`${schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
98
100
  WHERE cm.cm_user_uuid = ? AND cm.cm_company_uuid = ? AND cm.cm_is_active = 1
99
101
  AND (mp.mp_effective_from_ms IS NULL OR mp.mp_effective_from_ms <= ?)
100
102
  AND (mp.mp_effective_until_ms IS NULL OR mp.mp_effective_until_ms > ?)`, [principalId, tenantId, nowMs, nowMs]));
@@ -107,7 +109,7 @@ async function readRoleIdsForTenant(pool, principalId, tenantId, now = Date.now)
107
109
  * these moments, so cached facts must expire there (ADR-0035). `null` when there is none, and for
108
110
  * a service account (its roles have no windows). A query failure propagates (fail closed).
109
111
  */
110
- async function readNextPostingBoundaryMs(pool, principalId, tenantId, now = Date.now) {
112
+ async function readNextPostingBoundaryMs(pool, principalId, tenantId, schemas, now = Date.now) {
111
113
  const nowMs = now();
112
114
  const rows = (await pool.query(`SELECT MIN(CASE
113
115
  WHEN mp.mp_effective_from_ms IS NOT NULL AND mp.mp_effective_from_ms > ?
@@ -115,8 +117,8 @@ async function readNextPostingBoundaryMs(pool, principalId, tenantId, now = Date
115
117
  WHEN mp.mp_effective_until_ms IS NOT NULL AND mp.mp_effective_until_ms > ?
116
118
  THEN mp.mp_effective_until_ms
117
119
  END) AS next_boundary_ms
118
- FROM \`shield_app\`.\`company_membership\` cm
119
- JOIN \`shield_app\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
120
+ FROM \`${schemas.app}\`.\`company_membership\` cm
121
+ JOIN \`${schemas.app}\`.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
120
122
  WHERE cm.cm_user_uuid = ? AND cm.cm_company_uuid = ? AND cm.cm_is_active = 1`, [nowMs, nowMs, principalId, tenantId]));
121
123
  const raw = rows[0]?.next_boundary_ms;
122
124
  if (raw === null || raw === undefined)
@@ -128,20 +130,24 @@ class DbPermissionFactsAdapter {
128
130
  pool;
129
131
  registry;
130
132
  now;
133
+ schemas;
131
134
  constructor(pool, registry,
132
135
  /**
133
136
  * Injectable clock (epoch-ms) for the posting effective-window compare. Defaults to
134
137
  * `Date.now`. Bound as a `?` integer against the BIGINT `_ms` columns — timezone-immune.
135
138
  * Injectable so a test can fix the clock relative to a seeded expired/active posting.
136
139
  */
137
- now = Date.now) {
140
+ now = Date.now,
141
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
142
+ schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
138
143
  this.pool = pool;
139
144
  this.registry = registry;
140
145
  this.now = now;
146
+ this.schemas = schemas;
141
147
  }
142
148
  async currentPermissionsVersion(_principalId, tenantId) {
143
149
  try {
144
- const rows = (await this.pool.query(`SELECT apv_version FROM \`shield_authz\`.\`authz_permission_version\`
150
+ const rows = (await this.pool.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
145
151
  WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantId]));
146
152
  return rows.length > 0 ? Number(rows[0]?.apv_version) : 0;
147
153
  }
@@ -166,7 +172,7 @@ class DbPermissionFactsAdapter {
166
172
  version = await this.readVersion(tenantId);
167
173
  roleIds = await this.readRoleIds(principalId, tenantId);
168
174
  rawKeys = roleIds.length === 0 ? [] : await this.readGrantKeys(roleIds);
169
- validUntilMs = await readNextPostingBoundaryMs(this.pool, principalId, tenantId, this.now);
175
+ validUntilMs = await readNextPostingBoundaryMs(this.pool, principalId, tenantId, this.schemas, this.now);
170
176
  }
171
177
  catch (cause) {
172
178
  if (cause instanceof core_1.AuthorizationUnavailableError)
@@ -182,7 +188,7 @@ class DbPermissionFactsAdapter {
182
188
  };
183
189
  }
184
190
  async readVersion(tenantId) {
185
- const rows = (await this.pool.query(`SELECT apv_version FROM \`shield_authz\`.\`authz_permission_version\`
191
+ const rows = (await this.pool.query(`SELECT apv_version FROM \`${this.schemas.authz}\`.\`authz_permission_version\`
186
192
  WHERE apv_tenant_uuid = ? LIMIT 1`, [tenantId]));
187
193
  return rows.length > 0 ? Number(rows[0]?.apv_version) : 0;
188
194
  }
@@ -191,11 +197,11 @@ class DbPermissionFactsAdapter {
191
197
  * tenant — delegates to the shared {@link readRoleIdsForTenant} (single source).
192
198
  */
193
199
  async readRoleIds(principalId, tenantId) {
194
- return readRoleIdsForTenant(this.pool, principalId, tenantId, this.now);
200
+ return readRoleIdsForTenant(this.pool, principalId, tenantId, this.schemas, this.now);
195
201
  }
196
202
  async readGrantKeys(roleIds) {
197
203
  const placeholders = roleIds.map(() => '?').join(', ');
198
- const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`shield_authz\`.\`role_permission\`
204
+ const rows = (await this.pool.query(`SELECT rp_perm_key FROM \`${this.schemas.authz}\`.\`role_permission\`
199
205
  WHERE rp_role_uuid IN (${placeholders})`, roleIds));
200
206
  return rows.map((r) => r.rp_perm_key);
201
207
  }
@@ -4,6 +4,7 @@ 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';
7
+ import { type IdentitySchemaNames } from '../../schema.js';
7
8
  /**
8
9
  * The caller's reachable subtree, threaded from `req.principal` (Task 4.2). It scopes the
9
10
  * grant-admin reads so a caller sees ONLY roles bound within their subtree — the SAME cached
@@ -54,6 +55,7 @@ export declare class GrantAdminService {
54
55
  private readonly bumper;
55
56
  private readonly ceiling?;
56
57
  private readonly audit?;
58
+ private readonly schemas;
57
59
  private readonly bundles;
58
60
  /**
59
61
  * @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
@@ -63,8 +65,9 @@ export declare class GrantAdminService {
63
65
  * enforces. Root bypasses regardless.
64
66
  * @param audit The grant-administration audit sink (D6): written on the grant's own transaction
65
67
  * connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
68
+ * @param schemas Resolved framework names (absent ⇒ resolved from the namespace, once, here).
66
69
  */
67
- constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined);
70
+ constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined, schemas?: IdentitySchemaNames);
68
71
  /** All grantable exact keys (from the catalog) + the computed wildcard set. */
69
72
  listCatalog(): Promise<{
70
73
  catalog: CatalogRow[];
@@ -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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"}
@@ -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":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9D,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAWtE,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI1C;AAED,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,MAAM,EAAE,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAST;AAED;;;GAGG;AACH,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAQzB,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;IAf1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAGzB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,cAAc;IACzC;;;;;OAKG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IACjC,+FAA+F;IAC9E,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAU3E,+FAA+F;IAC/F,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI;IAS/C,qFAAqF;IACrF,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI;IAQ5C,oEAAoE;IACpE,OAAO,CAAC,cAAc;IAItB,+FAA+F;YACjF,cAAc;IAU5B,+FAA+F;YACjF,KAAK;IASnB;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAwB7D;;;;OAIG;IACG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAahF;;;;;;OAMG;IACG,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvF;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACG,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+D5E;;;;;;;;;;;;OAYG;IACG,0BAA0B,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BnF;;;;;;;;;OASG;IACG,qCAAqC,CACzC,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,SAAS,MAAM,EAAE,EACnC,SAAS,GAAE,QAAQ,GAAG,QAAmB,GACxC,OAAO,CAAC,IAAI,CAAC;IAsBhB;;;;;;;;OAQG;IACG,8BAA8B,CAClC,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;IA6BhB;;;;;;;;OAQG;IACG,uBAAuB,CAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,KAAK,EAAE;QAAE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAC9F,OAAO,CAAC,IAAI,CAAC;IAuChB,+FAA+F;IACzF,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAM3E;;;;;OAKG;IACG,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhF;;;;OAIG;IACG,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI9E"}
@@ -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"}