@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
@@ -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,18 @@
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';
17
19
  /**
18
20
  * A lean `mariadb` pool for the authz grant-reads (built from `APP_DB_*` env via guard's
19
21
  * `loadDbConfig`). Kept small — these are pre-gate coarse-grant reads, not the main CRUD pool.
20
22
  * Opens the pool at CALL time (never at import) — the caller owns closing it.
23
+ * `schemas` (the names `forRoot` resolved or was given) set the default database and user; absent
24
+ * ⇒ from the env namespace. An explicit `APP_DB_USER` / `APP_DB_NAME` wins (QA P2-5).
21
25
  */
22
- export declare function createIdentityAuthzPool(): Pool;
26
+ export declare function createIdentityAuthzPool(schemas?: FrameworkSchemas): Pool;
23
27
  /**
24
28
  * Build the `RecordAuthorizationInputs` over `pool`. `@astram/identity` supplies only the
25
29
  * app-schema ports:
@@ -31,5 +35,7 @@ export declare function createIdentityAuthzPool(): Pool;
31
35
  * consumer type is `resource_not_registered`),
32
36
  * - `operationManifest` — empty (identity declares no trusted delegated operations).
33
37
  */
34
- export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[]): RecordAuthorizationInputs;
38
+ export declare function buildRecordAuthorizationInputs(pool: Pool, extraObjects?: readonly ObjectTypeInput[],
39
+ /** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
40
+ schemas?: IdentitySchemaNames): RecordAuthorizationInputs;
35
41
  //# 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;;;;;;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"}
@@ -27,9 +27,11 @@ const session_facts_reader_js_1 = require("./session-facts.reader.js");
27
27
  * A lean `mariadb` pool for the authz grant-reads (built from `APP_DB_*` env via guard's
28
28
  * `loadDbConfig`). Kept small — these are pre-gate coarse-grant reads, not the main CRUD pool.
29
29
  * Opens the pool at CALL time (never at import) — the caller owns closing it.
30
+ * `schemas` (the names `forRoot` resolved or was given) set the default database and user; absent
31
+ * ⇒ from the env namespace. An explicit `APP_DB_USER` / `APP_DB_NAME` wins (QA P2-5).
30
32
  */
31
- function createIdentityAuthzPool() {
32
- const db = (0, guard_1.loadDbConfig)();
33
+ function createIdentityAuthzPool(schemas) {
34
+ const db = schemas === undefined ? (0, guard_1.loadDbConfig)() : (0, guard_1.loadDbConfig)(process.env, schemas);
33
35
  const ssl = (0, guard_1.mariadbSslOption)(db.tls);
34
36
  return mariadb_1.default.createPool({
35
37
  host: db.host,
@@ -53,11 +55,13 @@ function createIdentityAuthzPool() {
53
55
  * consumer type is `resource_not_registered`),
54
56
  * - `operationManifest` — empty (identity declares no trusted delegated operations).
55
57
  */
56
- function buildRecordAuthorizationInputs(pool, extraObjects = []) {
58
+ function buildRecordAuthorizationInputs(pool, extraObjects = [],
59
+ /** Resolved framework names for the facts adapter (absent ⇒ from the namespace, once). */
60
+ schemas) {
57
61
  const registry = (0, guard_1.buildIdentityModuleRegistry)([...objects_js_1.identityObjects, ...extraObjects]);
58
62
  return {
59
63
  pool,
60
- permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry),
64
+ permissionFacts: new db_permission_facts_adapter_js_1.DbPermissionFactsAdapter(pool, registry, undefined, schemas),
61
65
  sessionFacts: new session_facts_reader_js_1.AlsSessionFactsReader(),
62
66
  registry,
63
67
  operationManifest: [],
@@ -13,6 +13,7 @@
13
13
  * the role id are `?`-bound (never interpolated).
14
14
  */
15
15
  import type { Queryable } from './permission-version.bumper.js';
16
+ import type { IdentitySchemaNames } from '../../schema.js';
16
17
  /** Where a role is bound relative to a set of subtree nodes. */
17
18
  export interface RoleBinding {
18
19
  readonly inside: boolean;
@@ -23,7 +24,7 @@ export interface RoleBinding {
23
24
  * qualifies only if it is bound (`membership_posting → company_membership`) to a node in the
24
25
  * subtree. The caller splices `params` where `sql` sits in the surrounding query.
25
26
  */
26
- export declare function subtreeExists(nodeIds: readonly string[]): {
27
+ export declare function subtreeExists(nodeIds: readonly string[], schemas: IdentitySchemaNames): {
27
28
  sql: string;
28
29
  params: string[];
29
30
  };
@@ -32,5 +33,5 @@ export declare function subtreeExists(nodeIds: readonly string[]): {
32
33
  * query. An empty `nodeIds` is treated as "nothing is inside" (every binding is outside) — fail
33
34
  * closed, never an `IN ()` syntax error.
34
35
  */
35
- export declare function readRoleBinding(q: Queryable, roleUuid: string, nodeIds: readonly string[]): Promise<RoleBinding>;
36
+ export declare function readRoleBinding(q: Queryable, roleUuid: string, nodeIds: readonly string[], schemas: IdentitySchemaNames): Promise<RoleBinding>;
36
37
  //# sourceMappingURL=role-binding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-binding.d.ts","sourceRoot":"","sources":["../../../src/features/authz/role-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAIhE,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAQ3F;AASD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,SAAS,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,OAAO,CAAC,WAAW,CAAC,CAwBtB"}
1
+ {"version":3,"file":"role-binding.d.ts","sourceRoot":"","sources":["../../../src/features/authz/role-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,mBAAmB,GAC3B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAQnC;AASD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,SAAS,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,WAAW,CAAC,CAwBtB"}
@@ -2,17 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.subtreeExists = subtreeExists;
4
4
  exports.readRoleBinding = readRoleBinding;
5
- const APP = 'shield_app';
6
5
  /**
7
6
  * The shared EXISTS-subtree fragment for a non-root caller's READS (Task 4.2): a role row `r`
8
7
  * qualifies only if it is bound (`membership_posting → company_membership`) to a node in the
9
8
  * subtree. The caller splices `params` where `sql` sits in the surrounding query.
10
9
  */
11
- function subtreeExists(nodeIds) {
10
+ function subtreeExists(nodeIds, schemas) {
12
11
  const ph = nodeIds.map(() => '?').join(', ');
13
12
  const sql = ` AND EXISTS (` +
14
- `SELECT 1 FROM \`${APP}\`.\`membership_posting\` mp` +
15
- ` JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid` +
13
+ `SELECT 1 FROM \`${schemas.app}\`.\`membership_posting\` mp` +
14
+ ` JOIN \`${schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid` +
16
15
  ` WHERE mp.mp_role_uuid = r.r_uuid AND cm.cm_company_uuid IN (${ph}))`;
17
16
  return { sql, params: [...nodeIds] };
18
17
  }
@@ -29,7 +28,7 @@ function truthy(v) {
29
28
  * query. An empty `nodeIds` is treated as "nothing is inside" (every binding is outside) — fail
30
29
  * closed, never an `IN ()` syntax error.
31
30
  */
32
- async function readRoleBinding(q, roleUuid, nodeIds) {
31
+ async function readRoleBinding(q, roleUuid, nodeIds, schemas) {
33
32
  const ids = nodeIds.length > 0 ? [...nodeIds] : [];
34
33
  const inList = ids.length > 0 ? ids.map(() => '?').join(', ') : 'NULL';
35
34
  // `x IN (NULL)` is NULL (never true) and `x NOT IN (NULL)` is NULL too, so for an empty list the
@@ -40,12 +39,12 @@ async function readRoleBinding(q, roleUuid, nodeIds) {
40
39
  COALESCE(MAX(${outsideExpr}), 0) AS bound_outside
41
40
  FROM (
42
41
  SELECT cm.cm_company_uuid AS node
43
- FROM \`${APP}\`.\`membership_posting\` mp
44
- JOIN \`${APP}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
42
+ FROM \`${schemas.app}\`.\`membership_posting\` mp
43
+ JOIN \`${schemas.app}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid
45
44
  WHERE mp.mp_role_uuid = ?
46
45
  UNION ALL
47
46
  SELECT sa.sa_tenant_id AS node
48
- FROM \`${APP}\`.\`service_account\` sa
47
+ FROM \`${schemas.app}\`.\`service_account\` sa
49
48
  WHERE JSON_CONTAINS(sa.sa_role_ids, JSON_QUOTE(?))
50
49
  ) b`, [...ids, ...ids, roleUuid, roleUuid]));
51
50
  const row = rows[0];
@@ -21,6 +21,7 @@
21
21
  import type { Principal } from '@astram/core';
22
22
  import type { AuditPort } from '@astram/engine';
23
23
  import { ScopeClosureService } from '../scope/scope-closure.service.js';
24
+ import { type IdentitySchemaNames } from '../../schema.js';
24
25
  /** The read surface used for existence/name lookups (the shared authz pool satisfies it). */
25
26
  export interface HierarchyQueryable {
26
27
  query(sql: string, params?: readonly unknown[]): Promise<unknown>;
@@ -49,9 +50,13 @@ export declare class CompanyHierarchyService {
49
50
  private readonly ceiling;
50
51
  private readonly audit?;
51
52
  private readonly now;
53
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
54
+ private readonly schemas;
52
55
  constructor(pool: HierarchyQueryable, closure: ScopeClosureService,
53
56
  /** Phase 8 #3: required — a company delete must dominate every member (no silent skip). */
54
- ceiling: MemberDominance, audit?: AuditPort | undefined, now?: () => number);
57
+ ceiling: MemberDominance, audit?: AuditPort | undefined, now?: () => number,
58
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
59
+ schemas?: IdentitySchemaNames);
55
60
  /** The actor's own root node (see {@link ownRootOf}). */
56
61
  private ownRoot;
57
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AAWxF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAuCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBALH,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAG/C,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAiF9B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IA+BpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
1
+ {"version":3,"file":"company-hierarchy.service.d.ts","sourceRoot":"","sources":["../../../src/features/company/company-hierarchy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,mCAAmC,CAAC;AAQxF,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,8BAA8B,CAC5B,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,aAAa,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,CAAC,GACD,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAqCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAElD;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAPP,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,mBAAmB;IAC7C,2FAA2F;IAC1E,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,SAAS,YAAA,EACjB,GAAG,GAAE,MAAM,MAAiB;IAC7C,mFAAmF;IAClE,OAAO,GAAE,mBAA+C;IAG3E,yDAAyD;IACzD,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;YACW,UAAU;IAQxB,uDAAuD;YACzC,KAAK;YAUL,WAAW;IAqBzB,iGAAiG;IAC3F,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3F;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,gBAAgB;IAkF9B,0FAA0F;YAC5E,kBAAkB;IAmDhC,wFAAwF;YAC1E,sBAAsB;IAgCpC,wFAAwF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
@@ -6,8 +6,7 @@ const http_js_1 = require("../shared/http.js");
6
6
  const grant_ceiling_js_1 = require("../authz/grant-ceiling.js");
7
7
  const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
8
8
  const identity_errors_js_1 = require("../shared/identity-errors.js");
9
- const APP = 'shield_app';
10
- const AUTHZ = 'shield_authz';
9
+ const schema_js_1 = require("../../schema.js");
11
10
  /** A JSON-array column (the driver returns an array OR a string) → its string items; else []. */
12
11
  function jsonIdArray(v) {
13
12
  let arr = v;
@@ -34,14 +33,18 @@ class CompanyHierarchyService {
34
33
  ceiling;
35
34
  audit;
36
35
  now;
36
+ schemas;
37
37
  constructor(pool, closure,
38
38
  /** Phase 8 #3: required — a company delete must dominate every member (no silent skip). */
39
- ceiling, audit, now = Date.now) {
39
+ ceiling, audit, now = Date.now,
40
+ /** Resolved framework names (absent ⇒ resolved from the namespace, once, here). */
41
+ schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
40
42
  this.pool = pool;
41
43
  this.closure = closure;
42
44
  this.ceiling = ceiling;
43
45
  this.audit = audit;
44
46
  this.now = now;
47
+ this.schemas = schemas;
45
48
  }
46
49
  /** The actor's own root node (see {@link ownRootOf}). */
47
50
  ownRoot(actor) {
@@ -54,7 +57,7 @@ class CompanyHierarchyService {
54
57
  * stored spelling once and passes that down. Unknown → the input unchanged (later steps 404).
55
58
  */
56
59
  async storedUuid(uuid) {
57
- const rows = (await this.pool.query(`SELECT c_uuid FROM \`${APP}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [uuid]));
60
+ const rows = (await this.pool.query(`SELECT c_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [uuid]));
58
61
  return rows[0]?.c_uuid ?? uuid;
59
62
  }
60
63
  /** Names of the given companies (unknown → absent). */
@@ -62,7 +65,7 @@ class CompanyHierarchyService {
62
65
  const unique = [...new Set(ids)];
63
66
  if (unique.length === 0)
64
67
  return new Map();
65
- const rows = (await q.query(`SELECT c_uuid, c_name FROM \`${APP}\`.\`companies\` WHERE c_uuid IN (${unique.map(() => '?').join(', ')})`, unique));
68
+ const rows = (await q.query(`SELECT c_uuid, c_name FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid IN (${unique.map(() => '?').join(', ')})`, unique));
66
69
  return new Map(rows.map((r) => [r.c_uuid, r.c_name]));
67
70
  }
68
71
  async recordAudit(conn, actor, action, attributes, tenantId = actor.tenantId) {
@@ -109,7 +112,7 @@ class CompanyHierarchyService {
109
112
  if (newParentUuid !== null && !names.has(newParentUuid)) {
110
113
  throw new identity_errors_js_1.IdentityNotFoundError('Parent company not found.');
111
114
  }
112
- const oldParentRows = (await this.pool.query(`SELECT c_parent_uuid FROM \`${APP}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [nodeUuid]));
115
+ const oldParentRows = (await this.pool.query(`SELECT c_parent_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? LIMIT 1`, [nodeUuid]));
113
116
  const oldParent = oldParentRows[0]?.c_parent_uuid ?? null;
114
117
  const oldParentName = oldParent
115
118
  ? (await this.names(this.pool, [oldParent])).get(oldParent)
@@ -183,7 +186,7 @@ class CompanyHierarchyService {
183
186
  const agents = await (0, lock_service_accounts_js_1.lockServiceAccountsWhere)(conn, 'sa_id, sa_tenant_id, sa_label, sa_scope_subtree, sa_role_ids, sa_updated_at_ms', {
184
187
  sql: 'sa_tenant_id = ? OR JSON_CONTAINS(sa_scope_subtree, JSON_QUOTE(?))',
185
188
  params: [nodeUuid, nodeUuid],
186
- });
189
+ }, this.schemas);
187
190
  const own = agents.filter((a) => a.sa_tenant_id === nodeUuid);
188
191
  const others = agents.filter((a) => a.sa_tenant_id !== nodeUuid);
189
192
  // Root bypasses inside; a refusal (403, names no agent) rolls the whole delete back.
@@ -196,8 +199,8 @@ class CompanyHierarchyService {
196
199
  const touched = new Set();
197
200
  const editStamp = (a) => Math.max(nowMs, (a.sa_updated_at_ms == null ? 0 : Number(a.sa_updated_at_ms)) + 1);
198
201
  const retire = async (a, reason) => {
199
- await conn.query(`UPDATE \`${APP}\`.\`service_account\` SET sa_status = 'disabled', sa_updated_at_ms = ? WHERE sa_id = ?`, [editStamp(a), a.sa_id]);
200
- const res = (await conn.query(`UPDATE \`${AUTHZ}\`.\`service_account_key\`
202
+ await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_status = 'disabled', sa_updated_at_ms = ? WHERE sa_id = ?`, [editStamp(a), a.sa_id]);
203
+ const res = (await conn.query(`UPDATE \`${this.schemas.authz}\`.\`service_account_key\`
201
204
  SET sak_revoked_at_ms = ?
202
205
  WHERE sak_account_id = ? AND sak_revoked_at_ms IS NULL`, [nowMs, a.sa_id]));
203
206
  touched.add(a.sa_tenant_id);
@@ -218,7 +221,7 @@ class CompanyHierarchyService {
218
221
  await retire(a, 'scope_company_deleted');
219
222
  continue;
220
223
  }
221
- await conn.query(`UPDATE \`${APP}\`.\`service_account\` SET sa_scope_subtree = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), editStamp(a), a.sa_id]);
224
+ await conn.query(`UPDATE \`${this.schemas.app}\`.\`service_account\` SET sa_scope_subtree = ?, sa_updated_at_ms = ? WHERE sa_id = ?`, [JSON.stringify(remaining), editStamp(a), a.sa_id]);
222
225
  touched.add(a.sa_tenant_id);
223
226
  await this.recordAudit(conn, actor, 'service_account.updated', {
224
227
  eventType: 'service_account',
@@ -236,7 +239,7 @@ class CompanyHierarchyService {
236
239
  async revokeGrantsNaming(conn, actor, nodeUuid, companyName) {
237
240
  const grants = (await conn.query(`SELECT ctg_uuid, ctg_owning_tenant_uuid, ctg_grantee_tenant_uuid, ctg_grantee_kind,
238
241
  ctg_grantee_uuid, ctg_type_key, ctg_scope, ctg_allowed_actions
239
- FROM \`${AUTHZ}\`.\`cross_tenant_grant\`
242
+ FROM \`${this.schemas.authz}\`.\`cross_tenant_grant\`
240
243
  WHERE (ctg_owning_tenant_uuid = ? OR ctg_grantee_tenant_uuid = ?)
241
244
  AND ctg_status <> 'revoked'
242
245
  ORDER BY ctg_uuid
@@ -247,7 +250,7 @@ class CompanyHierarchyService {
247
250
  const otherSides = grants.map((g) => g.ctg_owning_tenant_uuid === nodeUuid ? g.ctg_grantee_tenant_uuid : g.ctg_owning_tenant_uuid);
248
251
  const names = await this.names(conn, otherSides);
249
252
  for (const [i, g] of grants.entries()) {
250
- await conn.query(`UPDATE \`${AUTHZ}\`.\`cross_tenant_grant\`
253
+ await conn.query(`UPDATE \`${this.schemas.authz}\`.\`cross_tenant_grant\`
251
254
  SET ctg_status = 'revoked', ctg_revoked_at = ?
252
255
  WHERE ctg_uuid = ? AND ctg_status <> 'revoked'`, [revokedAt, g.ctg_uuid]);
253
256
  const other = otherSides[i];
@@ -273,12 +276,10 @@ class CompanyHierarchyService {
273
276
  }
274
277
  /** Lock the company, then require dominance over each of its members (see `remove`). */
275
278
  async assertMayDeleteMembers(conn, actor, nodeUuid) {
276
- await conn.query(`SELECT c_uuid FROM \`${APP}\`.\`companies\` WHERE c_uuid = ? FOR UPDATE`, [
277
- nodeUuid,
278
- ]);
279
+ await conn.query(`SELECT c_uuid FROM \`${this.schemas.app}\`.\`companies\` WHERE c_uuid = ? FOR UPDATE`, [nodeUuid]);
279
280
  if (actor.isRoot === true)
280
281
  return;
281
- const rows = (await conn.query(`SELECT cm_user_uuid AS member FROM \`${APP}\`.\`company_membership\`
282
+ const rows = (await conn.query(`SELECT cm_user_uuid AS member FROM \`${this.schemas.app}\`.\`company_membership\`
282
283
  WHERE cm_company_uuid = ? FOR UPDATE`, [nodeUuid]));
283
284
  const members = [...new Set(rows.map((r) => r.member))].sort();
284
285
  for (const member of members) {
@@ -1,5 +1,6 @@
1
1
  import type { Pool } from 'mariadb';
2
2
  import type { ObjectTypeInput } from '@astram/engine';
3
+ import { type IdentitySchemaNames } from '../../schema.js';
3
4
  /** One row in the grantee's pending-offers inbox. */
4
5
  export interface InboxGrantRow {
5
6
  uuid: string;
@@ -59,13 +60,17 @@ export declare class CrossTenantGrantReadService {
59
60
  * surface uses, so a future tenantScoped type is picked up the same way it becomes
60
61
  * shareable.
61
62
  * @param actionsByType per-typeKey action vocabulary for the offerable-types read
62
- * (decision 1: "the actions per type come from the type's own action set"). Callers
63
- * pass `actionsFor`, which defaults every type to the CRUD `IDENTITY_ACTIONS` except
64
- * a type with its own override (e.g. `cross_tenant_grant` -> share/accept/revoke) —
65
- * the single source every action-vocabulary consumer uses, so a per-type set can
66
- * never drift between callers.
63
+ * (decision 1: "the actions per type come from the type's own action set"). The
64
+ * module passes its registration-based lookup (`effectiveActions` over the same
65
+ * `[...identityObjects, ...extraObjects]`, ADR-0038): CRUD plus a type's declared
66
+ * extras, or exactly its declared list for `crudActions: false` (e.g.
67
+ * `cross_tenant_grant` -> share/accept/revoke) — the single source every
68
+ * action-vocabulary consumer uses, so a per-type set can never drift between callers.
67
69
  */
68
- constructor(pool: Pool, offerableTypeInputs: readonly ObjectTypeInput[], actionsByType: (typeKey: string) => readonly string[]);
70
+ /** The shared FROM/JOIN block, built once from the resolved names. */
71
+ private readonly joins;
72
+ /** (`schemas`: resolved framework names; absent ⇒ resolved from the namespace, once, here.) */
73
+ constructor(pool: Pool, offerableTypeInputs: readonly ObjectTypeInput[], actionsByType: (typeKey: string) => readonly string[], schemas?: IdentitySchemaNames);
69
74
  /**
70
75
  * Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
71
76
  * tenantId AND ctg_status = 'offered' AND ctg_expires_at_ms > nowMs` (an expired
@@ -1 +1 @@
1
- {"version":3,"file":"cross-tenant-grant-read.service.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant-read.service.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA+FD,qBACa,2BAA2B;IAiBpC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAlBhC;;;;;;;;;;;;;;OAcG;gBAEgB,IAAI,EAAE,IAAI,EACV,mBAAmB,EAAE,SAAS,eAAe,EAAE,EAC/C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE;IAGxE;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IA0B1E;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyBzD;;;;;;;OAOG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyBjE;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAWxD"}
1
+ {"version":3,"file":"cross-tenant-grant-read.service.d.ts","sourceRoot":"","sources":["../../../src/features/cross-tenant/cross-tenant-grant-read.service.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAgGD,qBACa,2BAA2B;IAsBpC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAvBhC;;;;;;;;;;;;;;;OAeG;IACH,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,+FAA+F;gBAE5E,IAAI,EAAE,IAAI,EACV,mBAAmB,EAAE,SAAS,eAAe,EAAE,EAC/C,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,EACtE,OAAO,GAAE,mBAA+C;IAK1D;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IA0B1E;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyBzD;;;;;;;OAOG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyBjE;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAWxD"}
@@ -36,8 +36,7 @@ exports.CrossTenantGrantReadService = void 0;
36
36
  */
37
37
  const common_1 = require("@nestjs/common");
38
38
  const engine_1 = require("@astram/engine");
39
- const AUTHZ = 'shield_authz';
40
- const APP = 'shield_app';
39
+ const schema_js_1 = require("../../schema.js");
41
40
  /**
42
41
  * Parse `ctg_allowed_actions` to a `string[]`, accepting BOTH an already-parsed array
43
42
  * (production — the mariadb driver auto-parses the JSON column) and a JSON string
@@ -99,13 +98,14 @@ const SELECT_COLUMNS = `
99
98
  ctg.ctg_expires_at_ms,
100
99
  ctg.ctg_created_at
101
100
  `;
102
- const JOINS = `
103
- FROM \`${AUTHZ}\`.\`cross_tenant_grant\` ctg
104
- LEFT JOIN \`${APP}\`.\`companies\` owner_co ON owner_co.c_uuid = ctg.ctg_owning_tenant_uuid
105
- LEFT JOIN \`${APP}\`.\`companies\` grantee_co ON grantee_co.c_uuid = ctg.ctg_grantee_tenant_uuid
106
- LEFT JOIN \`${APP}\`.\`users\` gu ON ctg.ctg_grantee_kind = 'user' AND gu.u_uuid = ctg.ctg_grantee_uuid
107
- LEFT JOIN \`${APP}\`.\`roles\` gr ON ctg.ctg_grantee_kind = 'role' AND gr.r_uuid = ctg.ctg_grantee_uuid
108
- LEFT JOIN \`${APP}\`.\`users\` offerer ON offerer.u_uuid = ctg.ctg_offered_by
101
+ /** The FROM/JOIN block every read shares, over the resolved framework names. */
102
+ const joinsFor = ({ app, authz }) => `
103
+ FROM \`${authz}\`.\`cross_tenant_grant\` ctg
104
+ LEFT JOIN \`${app}\`.\`companies\` owner_co ON owner_co.c_uuid = ctg.ctg_owning_tenant_uuid
105
+ LEFT JOIN \`${app}\`.\`companies\` grantee_co ON grantee_co.c_uuid = ctg.ctg_grantee_tenant_uuid
106
+ LEFT JOIN \`${app}\`.\`users\` gu ON ctg.ctg_grantee_kind = 'user' AND gu.u_uuid = ctg.ctg_grantee_uuid
107
+ LEFT JOIN \`${app}\`.\`roles\` gr ON ctg.ctg_grantee_kind = 'role' AND gr.r_uuid = ctg.ctg_grantee_uuid
108
+ LEFT JOIN \`${app}\`.\`users\` offerer ON offerer.u_uuid = ctg.ctg_offered_by
109
109
  `;
110
110
  let CrossTenantGrantReadService = class CrossTenantGrantReadService {
111
111
  pool;
@@ -120,16 +120,21 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
120
120
  * surface uses, so a future tenantScoped type is picked up the same way it becomes
121
121
  * shareable.
122
122
  * @param actionsByType per-typeKey action vocabulary for the offerable-types read
123
- * (decision 1: "the actions per type come from the type's own action set"). Callers
124
- * pass `actionsFor`, which defaults every type to the CRUD `IDENTITY_ACTIONS` except
125
- * a type with its own override (e.g. `cross_tenant_grant` -> share/accept/revoke) —
126
- * the single source every action-vocabulary consumer uses, so a per-type set can
127
- * never drift between callers.
123
+ * (decision 1: "the actions per type come from the type's own action set"). The
124
+ * module passes its registration-based lookup (`effectiveActions` over the same
125
+ * `[...identityObjects, ...extraObjects]`, ADR-0038): CRUD plus a type's declared
126
+ * extras, or exactly its declared list for `crudActions: false` (e.g.
127
+ * `cross_tenant_grant` -> share/accept/revoke) — the single source every
128
+ * action-vocabulary consumer uses, so a per-type set can never drift between callers.
128
129
  */
129
- constructor(pool, offerableTypeInputs, actionsByType) {
130
+ /** The shared FROM/JOIN block, built once from the resolved names. */
131
+ joins;
132
+ /** (`schemas`: resolved framework names; absent ⇒ resolved from the namespace, once, here.) */
133
+ constructor(pool, offerableTypeInputs, actionsByType, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
130
134
  this.pool = pool;
131
135
  this.offerableTypeInputs = offerableTypeInputs;
132
136
  this.actionsByType = actionsByType;
137
+ this.joins = joinsFor(schemas);
133
138
  }
134
139
  /**
135
140
  * Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
@@ -141,7 +146,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
141
146
  if (!tenantId)
142
147
  return [];
143
148
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
144
- ${JOINS}
149
+ ${this.joins}
145
150
  WHERE ctg.ctg_grantee_tenant_uuid = ?
146
151
  AND ctg.ctg_status = 'offered'
147
152
  AND ctg.ctg_expires_at_ms > ?
@@ -169,7 +174,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
169
174
  if (!tenantId)
170
175
  return [];
171
176
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
172
- ${JOINS}
177
+ ${this.joins}
173
178
  WHERE ctg.ctg_owning_tenant_uuid = ?
174
179
  ORDER BY ctg.ctg_created_at DESC`, [tenantId]));
175
180
  return rows.map((row) => ({
@@ -198,7 +203,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
198
203
  if (!tenantId)
199
204
  return [];
200
205
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
201
- ${JOINS}
206
+ ${this.joins}
202
207
  WHERE ctg.ctg_grantee_tenant_uuid = ?
203
208
  AND ctg.ctg_status = 'accepted'
204
209
  ORDER BY ctg.ctg_expires_at_ms ASC`, [tenantId]));
@@ -272,5 +277,5 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
272
277
  exports.CrossTenantGrantReadService = CrossTenantGrantReadService;
273
278
  exports.CrossTenantGrantReadService = CrossTenantGrantReadService = __decorate([
274
279
  (0, common_1.Injectable)(),
275
- __metadata("design:paramtypes", [Object, Array, Function])
280
+ __metadata("design:paramtypes", [Object, Array, Function, Object])
276
281
  ], CrossTenantGrantReadService);