@astram/identity 0.11.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 (96) hide show
  1. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  2. package/dist/cross-tenant-grant.object.js +3 -2
  3. package/dist/features/audit/directory-label-resolver.d.ts +4 -1
  4. package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
  5. package/dist/features/audit/directory-label-resolver.js +8 -5
  6. package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
  7. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  8. package/dist/features/audit/mariadb-audit-reader.js +10 -2
  9. package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
  10. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  11. package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
  12. package/dist/features/authz/access-bundle-store.d.ts +4 -1
  13. package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
  14. package/dist/features/authz/access-bundle-store.js +19 -16
  15. package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
  16. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  17. package/dist/features/authz/catalog-reconcile.job.js +3 -2
  18. package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
  19. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  20. package/dist/features/authz/db-permission-facts.adapter.js +22 -16
  21. package/dist/features/authz/grant-admin.service.d.ts +4 -1
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +32 -37
  24. package/dist/features/authz/grant-ceiling.d.ts +6 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +19 -16
  27. package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
  28. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  29. package/dist/features/authz/mariadb-catalog-store.js +10 -7
  30. package/dist/features/authz/permission-version.bumper.d.ts +6 -1
  31. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  32. package/dist/features/authz/permission-version.bumper.js +13 -11
  33. package/dist/features/authz/record-authorization.d.ts +8 -2
  34. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  35. package/dist/features/authz/record-authorization.js +8 -4
  36. package/dist/features/authz/role-binding.d.ts +3 -2
  37. package/dist/features/authz/role-binding.d.ts.map +1 -1
  38. package/dist/features/authz/role-binding.js +7 -8
  39. package/dist/features/company/company-hierarchy.service.d.ts +6 -1
  40. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  41. package/dist/features/company/company-hierarchy.service.js +17 -16
  42. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +5 -1
  43. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  44. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +18 -14
  45. package/dist/features/framework-schemas.token.d.ts +8 -0
  46. package/dist/features/framework-schemas.token.d.ts.map +1 -0
  47. package/dist/features/framework-schemas.token.js +10 -0
  48. package/dist/features/identity-feature.module.d.ts +29 -1
  49. package/dist/features/identity-feature.module.d.ts.map +1 -1
  50. package/dist/features/identity-feature.module.js +134 -33
  51. package/dist/features/membership/membership-checker.d.ts +7 -1
  52. package/dist/features/membership/membership-checker.d.ts.map +1 -1
  53. package/dist/features/membership/membership-checker.js +9 -4
  54. package/dist/features/migration-ledger-check.d.ts +15 -0
  55. package/dist/features/migration-ledger-check.d.ts.map +1 -0
  56. package/dist/features/migration-ledger-check.js +26 -0
  57. package/dist/features/scope/scope-closure.service.d.ts +8 -1
  58. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  59. package/dist/features/scope/scope-closure.service.js +22 -19
  60. package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
  61. package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
  62. package/dist/features/scope/scope-subtree.resolver.js +9 -4
  63. package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
  64. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
  65. package/dist/features/service-account/lock-service-accounts.js +4 -4
  66. package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
  67. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
  68. package/dist/features/service-account/no-expiry-key-report.js +17 -10
  69. package/dist/features/service-account/service-account.store.d.ts +8 -1
  70. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  71. package/dist/features/service-account/service-account.store.js +19 -14
  72. package/dist/features/user/user.service.d.ts +7 -3
  73. package/dist/features/user/user.service.d.ts.map +1 -1
  74. package/dist/features/user/user.service.js +12 -4
  75. package/dist/record-share.object.d.ts.map +1 -1
  76. package/dist/record-share.object.js +3 -2
  77. package/dist/schema.d.ts +15 -2
  78. package/dist/schema.d.ts.map +1 -1
  79. package/dist/schema.js +17 -2
  80. package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
  81. package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
  82. package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
  83. package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
  84. package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
  85. package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
  86. package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
  87. package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
  88. package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
  89. package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
  90. package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
  91. package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
  92. package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
  93. package/migrations/1710000000022-CreateStoredFile.cjs +14 -4
  94. package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
  95. package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
  96. package/package.json +4 -4
@@ -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;
@@ -66,7 +67,10 @@ export declare class CrossTenantGrantReadService {
66
67
  * `cross_tenant_grant` -> share/accept/revoke) — the single source every
67
68
  * action-vocabulary consumer uses, so a per-type set can never drift between callers.
68
69
  */
69
- 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);
70
74
  /**
71
75
  * Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
72
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;IAkBpC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAnBhC;;;;;;;;;;;;;;;OAeG;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;
@@ -127,10 +127,14 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
127
127
  * `cross_tenant_grant` -> share/accept/revoke) — the single source every
128
128
  * action-vocabulary consumer uses, so a per-type set can never drift between callers.
129
129
  */
130
- 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)()) {
131
134
  this.pool = pool;
132
135
  this.offerableTypeInputs = offerableTypeInputs;
133
136
  this.actionsByType = actionsByType;
137
+ this.joins = joinsFor(schemas);
134
138
  }
135
139
  /**
136
140
  * Pending offers sitting in the CALLER's tenant inbox: `ctg_grantee_tenant_uuid =
@@ -142,7 +146,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
142
146
  if (!tenantId)
143
147
  return [];
144
148
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
145
- ${JOINS}
149
+ ${this.joins}
146
150
  WHERE ctg.ctg_grantee_tenant_uuid = ?
147
151
  AND ctg.ctg_status = 'offered'
148
152
  AND ctg.ctg_expires_at_ms > ?
@@ -170,7 +174,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
170
174
  if (!tenantId)
171
175
  return [];
172
176
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
173
- ${JOINS}
177
+ ${this.joins}
174
178
  WHERE ctg.ctg_owning_tenant_uuid = ?
175
179
  ORDER BY ctg.ctg_created_at DESC`, [tenantId]));
176
180
  return rows.map((row) => ({
@@ -199,7 +203,7 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
199
203
  if (!tenantId)
200
204
  return [];
201
205
  const rows = (await this.pool.query(`SELECT ${SELECT_COLUMNS}
202
- ${JOINS}
206
+ ${this.joins}
203
207
  WHERE ctg.ctg_grantee_tenant_uuid = ?
204
208
  AND ctg.ctg_status = 'accepted'
205
209
  ORDER BY ctg.ctg_expires_at_ms ASC`, [tenantId]));
@@ -273,5 +277,5 @@ let CrossTenantGrantReadService = class CrossTenantGrantReadService {
273
277
  exports.CrossTenantGrantReadService = CrossTenantGrantReadService;
274
278
  exports.CrossTenantGrantReadService = CrossTenantGrantReadService = __decorate([
275
279
  (0, common_1.Injectable)(),
276
- __metadata("design:paramtypes", [Object, Array, Function])
280
+ __metadata("design:paramtypes", [Object, Array, Function, Object])
277
281
  ], CrossTenantGrantReadService);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The framework database names `IdentityFeatureModule.forRoot` resolved (configurable-framework-db,
3
+ * ADR-0037), for identity services Nest constructs (e.g. `UserService`'s email-change audit sink),
4
+ * so they use the SAME set as every store forRoot builds, not a second env read. Internal to
5
+ * identity; guard's `FRAMEWORK_SCHEMAS` is checked equal at boot.
6
+ */
7
+ export declare const IDENTITY_FRAMEWORK_SCHEMAS: unique symbol;
8
+ //# sourceMappingURL=framework-schemas.token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-schemas.token.d.ts","sourceRoot":"","sources":["../../src/features/framework-schemas.token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,eAAuC,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDENTITY_FRAMEWORK_SCHEMAS = void 0;
4
+ /**
5
+ * The framework database names `IdentityFeatureModule.forRoot` resolved (configurable-framework-db,
6
+ * ADR-0037), for identity services Nest constructs (e.g. `UserService`'s email-change audit sink),
7
+ * so they use the SAME set as every store forRoot builds, not a second env read. Internal to
8
+ * identity; guard's `FRAMEWORK_SCHEMAS` is checked equal at boot.
9
+ */
10
+ exports.IDENTITY_FRAMEWORK_SCHEMAS = Symbol('IDENTITY_FRAMEWORK_SCHEMAS');
@@ -1,4 +1,5 @@
1
1
  import { type DynamicModule } from '@nestjs/common';
2
+ import { type FrameworkSchemas } from '@astram/core';
2
3
  import { type ObjectTypeInput } from '@astram/engine';
3
4
  /**
4
5
  * The boot check behind {@link IDENTITY_DECISION_AUTHORITY_CHECK}: with this module wired, the
@@ -8,6 +9,27 @@ import { type ObjectTypeInput } from '@astram/engine';
8
9
  * different registry. (Another source next to this one is refused by `ShieldModule` itself.)
9
10
  */
10
11
  export declare function assertRequestPathUsesIdentityAuthorization(authority: unknown): true;
12
+ /**
13
+ * The boot check behind {@link IDENTITY_FRAMEWORK_SCHEMAS_CHECK} (configurable-framework-db,
14
+ * ADR-0037; reviewer R2): the names `IdentityFeatureModule.forRoot` resolved — and handed to the
15
+ * engine, the audit sinks, the reader and every identity store — must equal guard's
16
+ * `FRAMEWORK_SCHEMAS`, or engine/guard/identity would split across databases. A guard that
17
+ * provides no `FRAMEWORK_SCHEMAS` (older than this seam) may only run on the default names.
18
+ * Throws (fail closed) with the namespaces and names only.
19
+ */
20
+ export declare function assertFrameworkSchemasAgree(identity: FrameworkSchemas, guard: FrameworkSchemas | null | undefined): true;
21
+ /**
22
+ * The boot check behind {@link IDENTITY_FRAMEWORK_DATABASE_CHECK} (G-007 QA P2-5): the runtime
23
+ * database identity's engine and authz pool connect to (derived from `forRoot({ schemas })`)
24
+ * must equal guard's `APP_CONFIG.db.database`, or unqualified queries of guard and identity would
25
+ * land in different databases. No guard `APP_CONFIG` visible ⇒ nothing to compare. Throws (fail
26
+ * closed) with database names only.
27
+ */
28
+ export declare function assertFrameworkDatabaseAgrees(identityDatabase: string, guardConfig: {
29
+ readonly db?: {
30
+ readonly database?: string;
31
+ };
32
+ } | null | undefined): true;
11
33
  /**
12
34
  * IdentityFeatureModule — the framework-owned identity FEATURE surface (company/user/role CRUD;
13
35
  * more in later phases) as thin controllers over the engine's RecordsService. The CONSUMER imports
@@ -34,10 +56,16 @@ export declare class IdentityFeatureModule {
34
56
  * cross-tenant / policy authority, grant ceiling, guided rule-builder schema and owner-share
35
57
  * resolver. Without them a consumer type is unregistered everywhere (denied), not policy-
36
58
  * authorable and not shareable ("type '…' is not registered — cannot share").
59
+ * @param schemas the resolved framework database names (configurable-framework-db, ADR-0037).
60
+ * Absent ⇒ resolved ONCE here from `SHIELD_DB_NAMESPACE` (unset ⇒ `shield_*`; invalid ⇒
61
+ * throws). The one set is handed to the engine (registrations + its audit sink), the seven
62
+ * audit sinks, the audit reader and every identity store; a boot check refuses names that
63
+ * differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
37
64
  */
38
- static forRoot({ rootIds, extraObjects, }?: {
65
+ static forRoot({ rootIds, extraObjects, schemas: givenSchemas, }?: {
39
66
  rootIds?: readonly string[];
40
67
  extraObjects?: readonly ObjectTypeInput[];
68
+ schemas?: FrameworkSchemas;
41
69
  }): DynamicModule;
42
70
  }
43
71
  //# sourceMappingURL=identity-feature.module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,EAcL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAiGxB;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAoBD;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,GAClB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;KACtC,GAAG,aAAa;CAigBvB"}
1
+ {"version":3,"file":"identity-feature.module.d.ts","sourceRoot":"","sources":["../../src/features/identity-feature.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAeL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AA0GxB;;;;;;GAMG;AACH,wBAAgB,0CAA0C,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAUnF;AAKD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,CAuBN;AAKD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,IAAI,CAQN;AAwBD;;;;;;;;;;GAUG;AACH,qBACa,qBAAqB;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,OAAO,CAAC,EACb,OAAO,EACP,YAAiB,EACjB,OAAO,EAAE,YAAY,GACtB,GAAE;QACD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,YAAY,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,gBAAgB,CAAC;KACvB,GAAG,aAAa;CA2jBvB"}