@astram/identity 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/cross-tenant-grant.object.d.ts.map +1 -1
  2. package/dist/cross-tenant-grant.object.js +3 -2
  3. package/dist/features/audit/directory-label-resolver.d.ts +4 -1
  4. package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
  5. package/dist/features/audit/directory-label-resolver.js +8 -5
  6. package/dist/features/audit/mariadb-audit-reader.d.ts +5 -1
  7. package/dist/features/audit/mariadb-audit-reader.d.ts.map +1 -1
  8. package/dist/features/audit/mariadb-audit-reader.js +10 -2
  9. package/dist/features/authz/access-bundle-reconcile.job.d.ts +4 -1
  10. package/dist/features/authz/access-bundle-reconcile.job.d.ts.map +1 -1
  11. package/dist/features/authz/access-bundle-reconcile.job.js +7 -4
  12. package/dist/features/authz/access-bundle-store.d.ts +4 -1
  13. package/dist/features/authz/access-bundle-store.d.ts.map +1 -1
  14. package/dist/features/authz/access-bundle-store.js +19 -16
  15. package/dist/features/authz/catalog-reconcile.job.d.ts +3 -1
  16. package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
  17. package/dist/features/authz/catalog-reconcile.job.js +3 -2
  18. package/dist/features/authz/db-permission-facts.adapter.d.ts +9 -3
  19. package/dist/features/authz/db-permission-facts.adapter.d.ts.map +1 -1
  20. package/dist/features/authz/db-permission-facts.adapter.js +22 -16
  21. package/dist/features/authz/grant-admin.service.d.ts +4 -1
  22. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  23. package/dist/features/authz/grant-admin.service.js +32 -37
  24. package/dist/features/authz/grant-ceiling.d.ts +6 -1
  25. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  26. package/dist/features/authz/grant-ceiling.js +19 -16
  27. package/dist/features/authz/mariadb-catalog-store.d.ts +4 -1
  28. package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
  29. package/dist/features/authz/mariadb-catalog-store.js +10 -7
  30. package/dist/features/authz/permission-version.bumper.d.ts +6 -1
  31. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  32. package/dist/features/authz/permission-version.bumper.js +13 -11
  33. package/dist/features/authz/record-authorization.d.ts +16 -2
  34. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  35. package/dist/features/authz/record-authorization.js +29 -5
  36. package/dist/features/authz/role-binding.d.ts +3 -2
  37. package/dist/features/authz/role-binding.d.ts.map +1 -1
  38. package/dist/features/authz/role-binding.js +7 -8
  39. package/dist/features/company/company-hierarchy.service.d.ts +6 -1
  40. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  41. package/dist/features/company/company-hierarchy.service.js +17 -16
  42. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts +5 -1
  43. package/dist/features/cross-tenant/cross-tenant-grant-read.service.d.ts.map +1 -1
  44. package/dist/features/cross-tenant/cross-tenant-grant-read.service.js +18 -14
  45. package/dist/features/framework-schemas.token.d.ts +8 -0
  46. package/dist/features/framework-schemas.token.d.ts.map +1 -0
  47. package/dist/features/framework-schemas.token.js +10 -0
  48. package/dist/features/identity-feature.module.d.ts +36 -2
  49. package/dist/features/identity-feature.module.d.ts.map +1 -1
  50. package/dist/features/identity-feature.module.js +142 -34
  51. package/dist/features/membership/membership-checker.d.ts +7 -1
  52. package/dist/features/membership/membership-checker.d.ts.map +1 -1
  53. package/dist/features/membership/membership-checker.js +9 -4
  54. package/dist/features/migration-ledger-check.d.ts +15 -0
  55. package/dist/features/migration-ledger-check.d.ts.map +1 -0
  56. package/dist/features/migration-ledger-check.js +26 -0
  57. package/dist/features/scope/scope-closure.service.d.ts +8 -1
  58. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  59. package/dist/features/scope/scope-closure.service.js +22 -19
  60. package/dist/features/scope/scope-subtree.resolver.d.ts +7 -1
  61. package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -1
  62. package/dist/features/scope/scope-subtree.resolver.js +9 -4
  63. package/dist/features/service-account/lock-service-accounts.d.ts +3 -1
  64. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -1
  65. package/dist/features/service-account/lock-service-accounts.js +4 -4
  66. package/dist/features/service-account/no-expiry-key-report.d.ts +9 -3
  67. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -1
  68. package/dist/features/service-account/no-expiry-key-report.js +17 -10
  69. package/dist/features/service-account/service-account.store.d.ts +8 -1
  70. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  71. package/dist/features/service-account/service-account.store.js +19 -14
  72. package/dist/features/user/user.service.d.ts +7 -3
  73. package/dist/features/user/user.service.d.ts.map +1 -1
  74. package/dist/features/user/user.service.js +12 -4
  75. package/dist/record-share.object.d.ts.map +1 -1
  76. package/dist/record-share.object.js +3 -2
  77. package/dist/schema.d.ts +15 -2
  78. package/dist/schema.d.ts.map +1 -1
  79. package/dist/schema.js +17 -2
  80. package/migrations/1710000000001-CreateIdentityOrgModel.cjs +14 -4
  81. package/migrations/1710000000002-CreateMembershipAndPostings.cjs +14 -4
  82. package/migrations/1710000000003-CreateIdentityCredential.cjs +14 -4
  83. package/migrations/1710000000004-CreateSessionTables.cjs +14 -4
  84. package/migrations/1710000000005-CreateMfaAndTokenSurface.cjs +14 -4
  85. package/migrations/1710000000010-CreateAuditLog.cjs +14 -4
  86. package/migrations/1710000000011-CreateCrossTenantGrant.cjs +15 -4
  87. package/migrations/1710000000012-SeedCrossTenantGrantPerms.cjs +15 -4
  88. package/migrations/1710000000013-CreateSharedResource.cjs +14 -4
  89. package/migrations/1710000000018-CreateServiceAccount.cjs +13 -4
  90. package/migrations/1710000000019-AddSessionMustReenroll.cjs +14 -4
  91. package/migrations/1710000000020-CreateEmailChangeToken.cjs +15 -5
  92. package/migrations/1710000000021-AddUsersEmailUniqueIndex.cjs +24 -13
  93. package/migrations/1710000000022-CreateStoredFile.cjs +14 -4
  94. package/migrations/1710000000099-SeedDevIdentity.cjs +14 -6
  95. package/migrations/1710000000100-SeedDevTenantBAndRoot.cjs +14 -6
  96. package/package.json +4 -4
@@ -9,8 +9,12 @@ var IdentityFeatureModule_1;
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.IdentityFeatureModule = void 0;
11
11
  exports.assertRequestPathUsesIdentityAuthorization = assertRequestPathUsesIdentityAuthorization;
12
+ exports.assertFrameworkSchemasAgree = assertFrameworkSchemasAgree;
13
+ exports.assertFrameworkDatabaseAgrees = assertFrameworkDatabaseAgrees;
14
+ exports.engineDbFromEnv = engineDbFromEnv;
12
15
  const common_1 = require("@nestjs/common");
13
16
  const core_1 = require("@nestjs/core");
17
+ const core_2 = require("@astram/core");
14
18
  const engine_1 = require("@astram/engine");
15
19
  const guard_1 = require("@astram/guard");
16
20
  const objects_js_1 = require("../objects.js");
@@ -22,6 +26,7 @@ const role_service_js_1 = require("./role/role.service.js");
22
26
  const role_controller_js_1 = require("./role/role.controller.js");
23
27
  const user_service_js_1 = require("./user/user.service.js");
24
28
  const email_address_changer_token_js_1 = require("./user/email-address-changer.token.js");
29
+ const framework_schemas_token_js_1 = require("./framework-schemas.token.js");
25
30
  const user_controller_js_1 = require("./user/user.controller.js");
26
31
  const membership_controller_js_1 = require("./membership/membership.controller.js");
27
32
  const membership_service_js_1 = require("./membership/membership.service.js");
@@ -34,6 +39,7 @@ const permission_version_bumper_js_1 = require("./authz/permission-version.bumpe
34
39
  const record_authorization_js_1 = require("./authz/record-authorization.js");
35
40
  const db_permission_facts_adapter_js_1 = require("./authz/db-permission-facts.adapter.js");
36
41
  const session_revocation_check_js_1 = require("./user/session-revocation-check.js");
42
+ const migration_ledger_check_js_1 = require("./migration-ledger-check.js");
37
43
  const access_bundle_store_js_1 = require("./authz/access-bundle-store.js");
38
44
  const grant_admin_service_js_1 = require("./authz/grant-admin.service.js");
39
45
  const grant_admin_controller_js_1 = require("./authz/grant-admin.controller.js");
@@ -91,9 +97,65 @@ function assertRequestPathUsesIdentityAuthorization(authority) {
91
97
  }
92
98
  return true;
93
99
  }
94
- /** Map guard's env DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). */
95
- function engineDbFromEnv() {
96
- const db = (0, guard_1.loadDbConfig)();
100
+ /** DI token of the boot check that identity and guard use the same framework database names. */
101
+ const IDENTITY_FRAMEWORK_SCHEMAS_CHECK = Symbol('IDENTITY_FRAMEWORK_SCHEMAS_CHECK');
102
+ /**
103
+ * The boot check behind {@link IDENTITY_FRAMEWORK_SCHEMAS_CHECK} (configurable-framework-db,
104
+ * ADR-0037; reviewer R2): the names `IdentityFeatureModule.forRoot` resolved — and handed to the
105
+ * engine, the audit sinks, the reader and every identity store — must equal guard's
106
+ * `FRAMEWORK_SCHEMAS`, or engine/guard/identity would split across databases. A guard that
107
+ * provides no `FRAMEWORK_SCHEMAS` (older than this seam) may only run on the default names.
108
+ * Throws (fail closed) with the namespaces and names only.
109
+ */
110
+ function assertFrameworkSchemasAgree(identity, guard) {
111
+ const names = (s) => `namespace=${s.namespace} app=${s.app} identity=${s.identity} authz=${s.authz} audit=${s.audit} layout=${s.layout ?? 'four'}`;
112
+ if (guard === undefined || guard === null) {
113
+ if (identity.isDefault)
114
+ return true;
115
+ throw new Error(`IdentityFeatureModule: resolved ${names(identity)}, but @astram/guard provides no ` +
116
+ 'FRAMEWORK_SCHEMAS; upgrade @astram/guard so every framework module uses the same names.');
117
+ }
118
+ const same = identity.app === guard.app &&
119
+ identity.identity === guard.identity &&
120
+ identity.authz === guard.authz &&
121
+ identity.audit === guard.audit &&
122
+ (identity.layout ?? 'four') === (guard.layout ?? 'four');
123
+ if (!same) {
124
+ throw new Error(`IdentityFeatureModule: framework database names disagree — identity ${names(identity)}; ` +
125
+ `guard ${names(guard)}. Pass the same names (or none) to ShieldModule and ` +
126
+ 'IdentityFeatureModule.');
127
+ }
128
+ return true;
129
+ }
130
+ /** DI token of the boot check that guard's runtime database equals identity's (QA P2-5). */
131
+ const IDENTITY_FRAMEWORK_DATABASE_CHECK = Symbol('IDENTITY_FRAMEWORK_DATABASE_CHECK');
132
+ /**
133
+ * The boot check behind {@link IDENTITY_FRAMEWORK_DATABASE_CHECK} (G-007 QA P2-5): the runtime
134
+ * database identity's engine and authz pool connect to (derived from `forRoot({ schemas })`)
135
+ * must equal guard's `APP_CONFIG.db.database`, or unqualified queries of guard and identity would
136
+ * land in different databases. No guard `APP_CONFIG` visible ⇒ nothing to compare. Throws (fail
137
+ * closed) with database names only.
138
+ */
139
+ function assertFrameworkDatabaseAgrees(identityDatabase, guardConfig) {
140
+ const guardDatabase = guardConfig?.db?.database;
141
+ if (guardDatabase === undefined || guardDatabase === identityDatabase)
142
+ return true;
143
+ throw new Error(`IdentityFeatureModule: runtime database ${identityDatabase} differs from @astram/guard's ` +
144
+ `${guardDatabase}. Pass the same names (or none) to ShieldModule and ` +
145
+ 'IdentityFeatureModule, and one APP_DB_NAME.');
146
+ }
147
+ /**
148
+ * Map guard's DbConfig → the engine's EngineDbConfig (adds the pool cap the engine needs). The
149
+ * database and default user follow `schemas` (the names `forRoot` resolved or was given); an
150
+ * explicit `APP_DB_USER` / `APP_DB_NAME` still wins (QA P2-5).
151
+ */
152
+ function engineDbFromEnv(schemas, env = process.env) {
153
+ const db = (0, guard_1.loadDbConfig)(env, schemas);
154
+ // POOL_MAX_ENGINE, else APP_DB_MAX_CONNECTIONS, else 10; a set but invalid value throws naming
155
+ // the variable. Guard's single-layout pool budget parses these the same way.
156
+ const connectionLimit = (0, record_authorization_js_1.strictPoolSize)(env.POOL_MAX_ENGINE, 'POOL_MAX_ENGINE') ??
157
+ (0, record_authorization_js_1.strictPoolSize)(env.APP_DB_MAX_CONNECTIONS, 'APP_DB_MAX_CONNECTIONS') ??
158
+ 10;
97
159
  const ssl = (0, guard_1.mariadbSslOption)(db.tls);
98
160
  return {
99
161
  host: db.host,
@@ -103,7 +165,7 @@ function engineDbFromEnv() {
103
165
  database: db.database,
104
166
  // The engine's runtime CRUD pool cap. A conservative default; the app can raise it later via
105
167
  // an explicit EngineModule.forRoot if it needs more. (ADR-0018 budget lives app-side.)
106
- connectionLimit: Number(process.env.APP_DB_MAX_CONNECTIONS ?? 10),
168
+ connectionLimit,
107
169
  // Phase 16: APP_DB_TLS / APP_DB_TLS_CA (production accounts are REQUIRE SSL).
108
170
  ...(ssl ? { ssl } : {}),
109
171
  };
@@ -134,22 +196,34 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
134
196
  * cross-tenant / policy authority, grant ceiling, guided rule-builder schema and owner-share
135
197
  * resolver. Without them a consumer type is unregistered everywhere (denied), not policy-
136
198
  * authorable and not shareable ("type '…' is not registered — cannot share").
199
+ * @param schemas the resolved framework database names (configurable-framework-db, ADR-0037).
200
+ * Absent ⇒ resolved ONCE here from `SHIELD_DB_NAMESPACE` (unset ⇒ `shield_*`; invalid ⇒
201
+ * throws). The one set is handed to the engine (registrations + its audit sink), the seven
202
+ * audit sinks, the audit reader and every identity store; a boot check refuses names that
203
+ * differ from guard's `FRAMEWORK_SCHEMAS`. Never read per request.
137
204
  */
138
- static forRoot({ rootIds, extraObjects = [], } = {}) {
205
+ static forRoot({ rootIds, extraObjects = [], schemas: givenSchemas, } = {}) {
206
+ // The framework database names, resolved ONCE (boot) for every SQL path built below.
207
+ // Caller-supplied names are checked before any SQL is built (fail closed, Shield #1).
208
+ const schemas = givenSchemas !== undefined
209
+ ? (0, core_2.assertFrameworkSchemaNames)(givenSchemas, 'IdentityFeatureModule.forRoot schemas')
210
+ : (0, core_2.resolveFrameworkSchemas)(process.env);
139
211
  // ONE shared shield_authz pool for the whole grant-admin stack (ADR-0011): the
140
212
  // permission-version bumper, the AccessBundleStore, the GrantAdminService, and the two
141
213
  // reconcile jobs all run over this single pool (don't open several). Opened once at
142
214
  // forRoot() (not import time) — mirrors the pre-existing bump-pool pattern. Since Phase 9 #7
143
215
  // it also backs the request path's decision authority (provided to ShieldModule below).
144
- const authzPool = (0, record_authorization_js_1.createIdentityAuthzPool)();
216
+ const authzPool = (0, record_authorization_js_1.createIdentityAuthzPool)(schemas);
217
+ // QA P2-5: the engine and the authz pool connect to the database derived from `schemas`.
218
+ const engineDb = engineDbFromEnv(schemas);
145
219
  // The ONE record authorization (Phase 9 #7, closes KI #55/#57): built here from this module's
146
220
  // own `extraObjects`, provided to ShieldModule under SHIELD_RECORD_AUTHORIZATION, and reused for
147
221
  // every internal authority below. Its registry is the ONE key universe: the grant ceiling sees
148
222
  // every key the request path grants (review M5 / Phase 8 #4), and a consumer type is
149
223
  // registered for the cross-tenant writer and the policy author too.
150
- const recordAuthorization = (0, record_authorization_js_1.buildRecordAuthorizationInputs)(authzPool, extraObjects);
224
+ const recordAuthorization = (0, record_authorization_js_1.buildRecordAuthorizationInputs)(authzPool, extraObjects, schemas);
151
225
  const ceilingRegistry = recordAuthorization.registry;
152
- const bumper = new permission_version_bumper_js_1.PermissionVersionBumper();
226
+ const bumper = new permission_version_bumper_js_1.PermissionVersionBumper(undefined, schemas);
153
227
  const postingVersionBump = {
154
228
  bumpTenant: (tenantUuid) => bumper.bumpForTenant(tenantUuid, authzPool),
155
229
  // A user disabled/re-enabled: every membership tenant (autocommit pool → published at once).
@@ -162,7 +236,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
162
236
  // self-escalation ceiling: a non-root grantor may only grant a key they themselves hold —
163
237
  // resolved over the SAME authz pool + registry the object-type gate uses (one source of
164
238
  // truth for "what a principal holds"). ALWAYS wired here, so production always enforces.
165
- const accessBundleStore = new access_bundle_store_js_1.AccessBundleStore(authzPool);
239
+ const accessBundleStore = new access_bundle_store_js_1.AccessBundleStore(authzPool, schemas);
166
240
  // (`ceilingRegistry` is the shared record-authorization registry built at the top of forRoot.)
167
241
  // `rootIds` marks platform roots as never dominated.
168
242
  // rootIds is passed EXPLICITLY (review F5): `null` states "not configured". The dominance
@@ -173,30 +247,33 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
173
247
  'ShieldModule.forRoot({ rootIds }). Platform administrators are still protected via ' +
174
248
  'users.u_is_platform_superuser.');
175
249
  }
176
- const grantCeiling = new grant_ceiling_js_1.GrantCeiling(authzPool, ceilingRegistry, rootIds ?? null);
250
+ const grantCeiling = new grant_ceiling_js_1.GrantCeiling(authzPool, ceilingRegistry, rootIds ?? null, undefined, schemas);
251
+ // Every identity audit sink writes `<audit>.audit_log` of the resolved names (Shield #6: the
252
+ // audit DB of the same namespace).
253
+ const auditSinkConfig = { schema: schemas.audit };
177
254
  // Grant administration is audited on the grant's own transaction (D6, fail-closed).
178
- const grantAdminAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log defaults
179
- const grantAdminService = new grant_admin_service_js_1.GrantAdminService(authzPool, bumper, accessBundleStore, grantCeiling, grantAdminAuditSink);
255
+ const grantAdminAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
256
+ const grantAdminService = new grant_admin_service_js_1.GrantAdminService(authzPool, bumper, accessBundleStore, grantCeiling, grantAdminAuditSink, schemas);
180
257
  // Scope closure (Task 1.2 of the scoped-hierarchy program): the reconciler + node
181
258
  // move/delete maintenance, over the SAME shared authzPool (it reaches BOTH shield_app —
182
259
  // companies — and shield_authz — scope_closure). Reuses the SAME `bumper` instance so a
183
260
  // moveNode's session-invalidation joins the move's transaction. reconcileClosure() is
184
261
  // kicked post-boot by the CatalogBootstrapProvider (mirrors the catalog reconcile).
185
- const scopeClosureService = new scope_closure_service_js_1.ScopeClosureService(authzPool, bumper);
262
+ const scopeClosureService = new scope_closure_service_js_1.ScopeClosureService(authzPool, bumper, schemas);
186
263
  // Company hierarchy (security hardening #2): the ONLY path that moves/deletes/attaches a company
187
264
  // node — through the closure service above — audited on the move/delete transaction.
188
265
  const companyHierarchyService = new company_hierarchy_service_js_1.CompanyHierarchyService(authzPool, scopeClosureService, grantCeiling, // Phase 8 #3: a company delete must dominate every member
189
- new engine_1.MariaDbAuditSink({}));
266
+ new engine_1.MariaDbAuditSink(auditSinkConfig), undefined, schemas);
190
267
  // Audit viewer (Phase 5): the AuditReader + LabelResolver over the SAME shared authzPool
191
268
  // (the reader queries shield_audit + the resolver/actor-lookup query shield_app, both
192
269
  // reachable by that pool). `auditNameToId` is the actor-name -> uuid reverse lookup for the
193
270
  // actor filter (astram3's exact closure). A mariadb Pool.query(sql, params) structurally
194
271
  // satisfies the reader/resolver's `AuditQueryable` — pass authzPool directly. Built at
195
272
  // forRoot() (not via Nest DI) because it needs the hand-built pool, then provided useValue.
196
- const auditReader = new mariadb_audit_reader_js_1.MariaDbAuditReader(authzPool);
197
- const auditLabels = new directory_label_resolver_js_1.DirectoryLabelResolver(authzPool);
273
+ const auditReader = new mariadb_audit_reader_js_1.MariaDbAuditReader(authzPool, { schema: schemas.audit });
274
+ const auditLabels = new directory_label_resolver_js_1.DirectoryLabelResolver(authzPool, schemas);
198
275
  const auditNameToId = async (name) => {
199
- const rows = (await authzPool.query('SELECT u_uuid AS id FROM `shield_app`.`users` WHERE u_username = ? LIMIT 1', [name]));
276
+ const rows = (await authzPool.query(`SELECT u_uuid AS id FROM \`${schemas.app}\`.\`users\` WHERE u_username = ? LIMIT 1`, [name]));
200
277
  return rows[0]?.id ?? null;
201
278
  };
202
279
  const auditReadService = new audit_read_service_js_1.AuditReadService(auditReader, auditLabels, auditNameToId);
@@ -220,7 +297,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
220
297
  // consumer passes to ShieldModule.forRoot (root-correctness for control-plane grants).
221
298
  const authorizeCrossTenantGrant = (0, grant_authorize_js_1.makeAuthorizeCrossTenantGrant)({
222
299
  authority: crossTenantAuthority,
223
- readRoleIds: (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId),
300
+ readRoleIds: (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId, schemas),
224
301
  isRoot: (principalId) => (rootIds ?? []).includes(principalId),
225
302
  });
226
303
  // Audit bridge: map guard's `GrantAuditEvent` → the engine's `MariaDbAuditSink` (the SAME
@@ -235,7 +312,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
235
312
  // never happen for a cross-tenant grant) is de-duped to ONE row.
236
313
  // BEST-EFFORT: a throw here must NEVER fail the lifecycle write (the writer already swallows a
237
314
  // sink throw; we also guard here so a mapping/DB error is inert).
238
- const grantAuditSink = new engine_1.MariaDbAuditSink({}); // defaults: shield_audit.audit_log
315
+ const grantAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
239
316
  const grantAudit = {
240
317
  record: async (event) => {
241
318
  try {
@@ -266,6 +343,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
266
343
  db: authzPool,
267
344
  authorize: authorizeCrossTenantGrant,
268
345
  audit: grantAudit,
346
+ schema: schemas.authz,
269
347
  });
270
348
  // The read side (inbox/mine/incoming/offerable-types) over the same pool + the merged object
271
349
  // set (offerable = tenantScoped types only, the consumer's included, KI #57) + the per-type
@@ -278,7 +356,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
278
356
  return [reg.typeKey, (0, engine_1.effectiveActions)(reg)];
279
357
  }));
280
358
  const actionsByType = (typeKey) => actionsByTypeKey.get(typeKey);
281
- const crossTenantGrantReadService = new cross_tenant_grant_read_service_js_1.CrossTenantGrantReadService(authzPool, [...objects_js_1.identityObjects, ...extraObjects], (typeKey) => actionsByType(typeKey) ?? []);
359
+ const crossTenantGrantReadService = new cross_tenant_grant_read_service_js_1.CrossTenantGrantReadService(authzPool, [...objects_js_1.identityObjects, ...extraObjects], (typeKey) => actionsByType(typeKey) ?? [], schemas);
282
360
  // ── GUIDED RULE-BUILDER (list-scoping engine Phase 5, §9.1) ──────────────────────────────
283
361
  // The policy-authoring surface over the guard `MariaDbPolicyStore` + core `makeAuthorPolicy`.
284
362
  // Reuses the SAME shared registry + `crossTenantAuthority` (a DecisionAuthority over the
@@ -286,7 +364,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
286
364
  // core AuditSink is one-arg (`record(event)`); the engine `MariaDbAuditSink` is two-arg
287
365
  // (`record(event, query)`) — so bind the query to the shared authzPool (mirrors the
288
366
  // `grantAudit` bridge above). Best-effort audit (a sink throw must not fail the author).
289
- const policyAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log defaults
367
+ const policyAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
290
368
  const policyAudit = {
291
369
  record: async (event) => {
292
370
  try {
@@ -297,7 +375,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
297
375
  }
298
376
  },
299
377
  };
300
- const policyStore = new guard_1.MariaDbPolicyStore(authzPool);
378
+ const policyStore = new guard_1.MariaDbPolicyStore(authzPool, { schema: schemas.authz });
301
379
  // The guided-builder SCHEMA source — the authorable field/action metadata, reflected from the
302
380
  // registered identity object types (engine `reflectObjectType` yields fields WITH their
303
381
  // `type`, which the core registry descriptor drops). Only tenantScoped, owned DATA-plane types
@@ -335,7 +413,9 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
335
413
  // and the writer rejects "type 'note' is not registered — cannot share." (Live-acceptance fix.)
336
414
  const shareTypeInfoByKey = new Map();
337
415
  for (const input of [...objects_js_1.identityObjects, ...extraObjects]) {
338
- const reg = typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input;
416
+ // R1 / KI #1 (spec v2 §5.B): resolve `FRAMEWORK_SCHEMA` tokens to the resolved names before
417
+ // the schema reaches `RecordShareWriter` SQL; an unresolvable token throws (fail closed).
418
+ const reg = (0, engine_1.resolveObjectTypeSchema)(typeof input === 'function' ? (0, engine_1.reflectObjectType)(input) : input, schemas);
339
419
  const owner = (0, engine_1.ownerColumn)(reg);
340
420
  const pk = reg.primaryKey.kind === 'uuid' ? (0, engine_1.uuidPkColumn)(reg.primaryKey) : null;
341
421
  // Only OWNED types with a uuid PK + an owner column are shareable; others are omitted (the
@@ -355,10 +435,11 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
355
435
  const shareTypeResolver = {
356
436
  resolve: (typeKey) => shareTypeInfoByKey.get(typeKey),
357
437
  };
358
- const shareAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log
438
+ const shareAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
359
439
  const recordShareWriter = new guard_1.RecordShareWriter({
360
440
  db: authzPool,
361
441
  types: shareTypeResolver,
442
+ schema: schemas.authz,
362
443
  audit: {
363
444
  record: async (event) => {
364
445
  try {
@@ -386,11 +467,11 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
386
467
  // shield_app.service_account — the accounts — AND shield_authz.service_account_key — the hashed
387
468
  // keys). The guard's MariaDbApiKeyStore + Argon2idPasswordHasher back the credential lifecycle;
388
469
  // the service enforces own-tenant + the ADR-0029 ceiling (scope ⊆ mine, roles ⊆ mine).
389
- const serviceAccountStore = new service_account_store_js_1.ServiceAccountStore(authzPool);
390
- const serviceAccountKeyStore = new guard_1.MariaDbApiKeyStore(authzPool);
470
+ const serviceAccountStore = new service_account_store_js_1.ServiceAccountStore(authzPool, schemas);
471
+ const serviceAccountKeyStore = new guard_1.MariaDbApiKeyStore(authzPool, schemas);
391
472
  const serviceAccountHasher = new guard_1.Argon2idPasswordHasher();
392
473
  // Best-effort audit → the SAME shield_audit sink pattern (a sink throw never fails the op).
393
- const serviceAccountAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log defaults
474
+ const serviceAccountAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
394
475
  const serviceAccountAudit = {
395
476
  record: async (event) => {
396
477
  try {
@@ -412,12 +493,12 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
412
493
  // shield_app.users lookup the audit actor filter uses (inverted). Unknown → null (view falls
413
494
  // back to no name, never an id).
414
495
  const resolveActorName = async (id) => {
415
- const rows = (await authzPool.query('SELECT u_username AS name FROM `shield_app`.`users` WHERE u_uuid = ? LIMIT 1', [id]));
496
+ const rows = (await authzPool.query(`SELECT u_username AS name FROM \`${schemas.app}\`.\`users\` WHERE u_uuid = ? LIMIT 1`, [id]));
416
497
  return rows[0]?.name ?? null;
417
498
  };
418
499
  const serviceAccountService = new service_account_service_js_1.ServiceAccountService(serviceAccountStore, serviceAccountKeyStore, serviceAccountHasher, serviceAccountAudit, resolveActorName,
419
500
  // The ceiling's roles: in-effect postings (the same source as gate 5 and GrantCeiling).
420
- (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId));
501
+ (principalId, tenantId) => (0, db_permission_facts_adapter_js_1.readRoleIdsForTenant)(authzPool, principalId, tenantId, schemas));
421
502
  // NOTE: the machine AUTH path (the `ServiceAuthenticator` behind `SHIELD_SERVICE_AUTHENTICATOR`)
422
503
  // is provided by the guard's own AuthModule (under `auth: true`), NOT here — a token provided in
423
504
  // this later, sibling feature module is invisible to the APP_GUARD in ShieldModule (that DI
@@ -427,7 +508,7 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
427
508
  // Stored files (file-storage SPEC §5 I-K): `file_access` events go to the SAME shield_audit
428
509
  // sink over the shared authzPool. Not best-effort here: FilesService decides (a read event
429
510
  // that cannot be written withholds the bytes).
430
- const filesAuditSink = new engine_1.MariaDbAuditSink({}); // shield_audit.audit_log defaults
511
+ const filesAuditSink = new engine_1.MariaDbAuditSink(auditSinkConfig);
431
512
  const filesAudit = {
432
513
  record: (event) => filesAuditSink.record(event, (sql, params) => authzPool.query(sql, params)),
433
514
  };
@@ -442,10 +523,13 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
442
523
  // snapshot is empty and no bundles reconcile into shield_authz.access_bundle.
443
524
  engine_1.EngineModule.forFeature([], { accessBundles: index_js_1.identityAccessBundles }),
444
525
  // The engine's runtime CRUD pipe + the identity object registrations.
526
+ // R2: the SAME resolved names, so the engine's registrations (FRAMEWORK_SCHEMA tokens) and
527
+ // its audit sink cannot land in another namespace than identity's own SQL.
445
528
  engine_1.EngineModule.forRoot({
446
- db: engineDbFromEnv(),
529
+ db: engineDb,
447
530
  registrations: [...objects_js_1.identityObjects],
448
531
  listScope,
532
+ schemas,
449
533
  }),
450
534
  // NestJS DiscoveryService/MetadataScanner — the boot bootstrap provider reflects the
451
535
  // live controllers through this to build the OperationInventory.
@@ -499,8 +583,11 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
499
583
  // (collision refusal + login-hint follow in one transaction), over the shared authz pool.
500
584
  {
501
585
  provide: email_address_changer_token_js_1.IDENTITY_EMAIL_ADDRESS_CHANGER,
502
- useValue: new guard_1.MariaDbEmailAddressChanger(authzPool),
586
+ useValue: new guard_1.MariaDbEmailAddressChanger(authzPool, Date.now, schemas),
503
587
  },
588
+ // The same resolved names for identity services Nest constructs (UserService's
589
+ // email-change audit sink), never a second env read.
590
+ { provide: framework_schemas_token_js_1.IDENTITY_FRAMEWORK_SCHEMAS, useValue: schemas },
504
591
  // ADR-0035 (GAPS #6): attach the guard's Redis permission-version cache to the ONE shared
505
592
  // bumper, so every committed bump (postings, grants, hierarchy) is published to Redis,
506
593
  // where gate 5 reads it. Provided by the auth surface under `auth: true` (through the
@@ -522,6 +609,27 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
522
609
  useFactory: assertRequestPathUsesIdentityAuthorization,
523
610
  inject: [{ token: guard_1.SHIELD_DECISION_AUTHORITY, optional: true }],
524
611
  },
612
+ // configurable-framework-db (R2): fail the boot unless guard's FRAMEWORK_SCHEMAS equal the
613
+ // names this module resolved (and gave the engine, the sinks and every store).
614
+ {
615
+ provide: IDENTITY_FRAMEWORK_SCHEMAS_CHECK,
616
+ useFactory: (guardSchemas) => assertFrameworkSchemasAgree(schemas, guardSchemas),
617
+ inject: [{ token: guard_1.FRAMEWORK_SCHEMAS, optional: true }],
618
+ },
619
+ // QA P2-5: and guard's runtime database must be the one identity connects to.
620
+ {
621
+ provide: IDENTITY_FRAMEWORK_DATABASE_CHECK,
622
+ useFactory: (guardConfig) => assertFrameworkDatabaseAgrees(engineDb.database, guardConfig),
623
+ inject: [{ token: guard_1.APP_CONFIG, optional: true }],
624
+ },
625
+ // configurable-framework-db spec 5.D: the identity migrations are in the ledger, checked
626
+ // once at application bootstrap over the shared runtime pool (production fails, dev warns).
627
+ // Guard + identity ids; one ledger read shared with AuthModule's check (guard's registry).
628
+ {
629
+ provide: migration_ledger_check_js_1.IDENTITY_MIGRATION_LEDGER_CHECK,
630
+ useFactory: (registry) => (0, migration_ledger_check_js_1.identityMigrationLedgerCheck)(authzPool, schemas, process.env, registry ?? null),
631
+ inject: [{ token: guard_1.SHIELD_MIGRATION_LEDGER_REGISTRY, optional: true }],
632
+ },
525
633
  {
526
634
  provide: IDENTITY_VERSION_PUBLISHER_LINK,
527
635
  useFactory: (cache) => {
@@ -562,13 +670,13 @@ let IdentityFeatureModule = IdentityFeatureModule_1 = class IdentityFeatureModul
562
670
  // ceiling know, so a consumer type's keys are grantable and its rows are not orphaned.
563
671
  {
564
672
  provide: catalog_reconcile_job_js_1.CatalogReconcileJob,
565
- useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, recordAuthorization.registry),
673
+ useValue: new catalog_reconcile_job_js_1.CatalogReconcileJob(authzPool, recordAuthorization.registry, schemas),
566
674
  },
567
675
  // The bundle half: reconciles the merged AccessBundleSpec[] (ENGINE_ALL_ACCESS_BUNDLES,
568
676
  // reflected by the engine from identityAccessBundles) into shield_authz.access_bundle.
569
677
  {
570
678
  provide: access_bundle_reconcile_job_js_1.AccessBundleReconcileJob,
571
- useFactory: (bundles) => new access_bundle_reconcile_job_js_1.AccessBundleReconcileJob(authzPool, bundles),
679
+ useFactory: (bundles) => new access_bundle_reconcile_job_js_1.AccessBundleReconcileJob(authzPool, bundles, schemas),
572
680
  inject: [engine_1.ENGINE_ALL_ACCESS_BUNDLES],
573
681
  },
574
682
  // Scope closure reconciler + node move/delete maintenance (Task 1.2), built at forRoot()
@@ -30,9 +30,15 @@
30
30
  * a switch. All SQL values are `?`-bound (never interpolated).
31
31
  */
32
32
  import type { Pool } from 'mariadb';
33
+ import { type IdentitySchemaNames } from '../../schema.js';
33
34
  export declare class DbMembershipChecker {
34
35
  private readonly pool;
35
- constructor(pool: Pool);
36
+ private readonly schemas;
37
+ /**
38
+ * @param schemas resolved framework names; the membership table lives in `schemas.app`.
39
+ * Absent ⇒ resolved from the namespace, once, here.
40
+ */
41
+ constructor(pool: Pool, schemas?: IdentitySchemaNames);
36
42
  /**
37
43
  * Does `principalId` hold an ACTIVE membership at `nodeId`? A single existence read —
38
44
  * `true` iff a row with `cm_is_active = 1 AND cm_status = 'active'` exists. Throws on a
@@ -1 +1 @@
1
- {"version":3,"file":"membership-checker.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAKpC,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEvC;;;;OAIG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAWjF"}
1
+ {"version":3,"file":"membership-checker.d.ts","sourceRoot":"","sources":["../../../src/features/membership/membership-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,qBAAa,mBAAmB;IAM5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B;;;OAGG;gBAEgB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,mBAA+C;IAG3E;;;;OAIG;IACG,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAWjF"}
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DbMembershipChecker = void 0;
4
- /** Schema the membership table lives in (the app schema). */
5
- const APP = 'shield_app';
4
+ const schema_js_1 = require("../../schema.js");
6
5
  class DbMembershipChecker {
7
6
  pool;
8
- constructor(pool) {
7
+ schemas;
8
+ /**
9
+ * @param schemas resolved framework names; the membership table lives in `schemas.app`.
10
+ * Absent ⇒ resolved from the namespace, once, here.
11
+ */
12
+ constructor(pool, schemas = (0, schema_js_1.frameworkSchemasFromEnv)()) {
9
13
  this.pool = pool;
14
+ this.schemas = schemas;
10
15
  }
11
16
  /**
12
17
  * Does `principalId` hold an ACTIVE membership at `nodeId`? A single existence read —
@@ -15,7 +20,7 @@ class DbMembershipChecker {
15
20
  */
16
21
  async hasActiveMembership(principalId, nodeId) {
17
22
  const rows = (await this.pool.query(`SELECT 1 AS ok
18
- FROM \`${APP}\`.\`company_membership\`
23
+ FROM \`${this.schemas.app}\`.\`company_membership\`
19
24
  WHERE cm_user_uuid = ? AND cm_company_uuid = ?
20
25
  AND cm_is_active = 1 AND cm_status = 'active'
21
26
  LIMIT 1`, [principalId, nodeId]));
@@ -0,0 +1,15 @@
1
+ import type { FrameworkSchemas } from '@astram/core';
2
+ import { FrameworkMigrationLedgerCheck, type MigrationLedgerRegistry } from '@astram/guard';
3
+ /** DI token of identity's ledger check (guard's `AuthModule` uses its own token). */
4
+ export declare const IDENTITY_MIGRATION_LEDGER_CHECK: unique symbol;
5
+ /**
6
+ * configurable-framework-db spec 5.D: the boot hook that checks every guard AND identity
7
+ * migration is in the `@astram/migrate` ledger (identity reads guard's authz tables; guard never
8
+ * imports identity, ADR-0003). Runs once at application bootstrap, never per request (Shield
9
+ * #12). With guard's per-app `registry`, it shares one ledger read with `AuthModule`'s check.
10
+ * Outside `NODE_ENV` development/test a missing id, ledger table or unreadable ledger fails boot.
11
+ */
12
+ export declare function identityMigrationLedgerCheck(pool: {
13
+ query(sql: string): Promise<unknown>;
14
+ }, schemas: FrameworkSchemas, env?: NodeJS.ProcessEnv, registry?: MigrationLedgerRegistry | null): FrameworkMigrationLedgerCheck;
15
+ //# sourceMappingURL=migration-ledger-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-ledger-check.d.ts","sourceRoot":"","sources":["../../src/features/migration-ledger-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,6BAA6B,EAG7B,KAAK,uBAAuB,EAC7B,MAAM,eAAe,CAAC;AAGvB,qFAAqF;AACrF,eAAO,MAAM,+BAA+B,eAA4C,CAAC;AAEzF;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE;IAAE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAC9C,OAAO,EAAE,gBAAgB,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,uBAAuB,GAAG,IAAW,GAC9C,6BAA6B,CAY/B"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDENTITY_MIGRATION_LEDGER_CHECK = void 0;
4
+ exports.identityMigrationLedgerCheck = identityMigrationLedgerCheck;
5
+ const common_1 = require("@nestjs/common");
6
+ const guard_1 = require("@astram/guard");
7
+ const migrations_dir_js_1 = require("../migrations-dir.js");
8
+ /** DI token of identity's ledger check (guard's `AuthModule` uses its own token). */
9
+ exports.IDENTITY_MIGRATION_LEDGER_CHECK = Symbol('IDENTITY_MIGRATION_LEDGER_CHECK');
10
+ /**
11
+ * configurable-framework-db spec 5.D: the boot hook that checks every guard AND identity
12
+ * migration is in the `@astram/migrate` ledger (identity reads guard's authz tables; guard never
13
+ * imports identity, ADR-0003). Runs once at application bootstrap, never per request (Shield
14
+ * #12). With guard's per-app `registry`, it shares one ledger read with `AuthModule`'s check.
15
+ * Outside `NODE_ENV` development/test a missing id, ledger table or unreadable ledger fails boot.
16
+ */
17
+ function identityMigrationLedgerCheck(pool, schemas, env = process.env, registry = null) {
18
+ return new guard_1.FrameworkMigrationLedgerCheck({
19
+ query: (sql) => pool.query(sql),
20
+ // Same candidates as guard's check (G-007 QA P1), so the registry shares one probe + read.
21
+ ...(0, guard_1.resolveMigrationLedgerCheck)(env, schemas),
22
+ migrationsDir: [guard_1.guardMigrationsDir, migrations_dir_js_1.identityMigrationsDir],
23
+ nodeEnv: env.NODE_ENV,
24
+ logger: new common_1.Logger('IdentityFeatureModule'),
25
+ }, registry);
26
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ScopeEdge } from '@astram/guard';
2
+ import { type IdentitySchemaNames } from '../../schema.js';
2
3
  /**
3
4
  * The minimal query surface a transaction connection must expose. A `mariadb`
4
5
  * `PoolConnection` satisfies it structurally; kept framework-agnostic (mirrors `Queryable`
@@ -30,7 +31,13 @@ export interface ScopeVersionBumper {
30
31
  export declare class ScopeClosureService {
31
32
  private readonly pool;
32
33
  private readonly bumper;
33
- constructor(pool: ScopePool, bumper: ScopeVersionBumper);
34
+ private readonly schemas;
35
+ /**
36
+ * @param schemas resolved framework names: the node tree (`companies`) lives in `schemas.app`,
37
+ * the closure table in `schemas.authz` (ADR-0011 / Task 1.1). Absent ⇒ resolved from the
38
+ * namespace, once, here.
39
+ */
40
+ constructor(pool: ScopePool, bumper: ScopeVersionBumper, schemas?: IdentitySchemaNames);
34
41
  /**
35
42
  * Rebuild `scope_closure` from the current `companies.c_parent_uuid` edges. Idempotent
36
43
  * (delete-then-insert in a tx → a second run over the same tree writes the identical set).
@@ -1 +1 @@
1
- {"version":3,"file":"scope-closure.service.d.ts","sourceRoot":"","sources":["../../../src/features/scope/scope-closure.service.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAOlF;;;;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,iGAAiG;AACjG,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,aAAa,IAAI,OAAO,CACtB,SAAS,GAAG;QACV,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,2FAA2F;QAC3F,OAAO,CAAC,IAAI,IAAI,CAAC;KAClB,CACF,CAAC;CACH;AAOD,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,gBAAgB,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,qFAAqF;IACrF,cAAc,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,kBAAkB;IAG7C;;;;;;OAMG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAsBvF;;;;;;OAMG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjD;;;;;;;OAOG;YACW,UAAU;IAwCxB;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GAAG,IAAI;IAC5B,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GACjD,OAAO,CAAC,IAAI,CAAC;IAiFhB;;;;;;;;;;;;;;;;OAgBG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM;IAChB,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC;IAClD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,GAAG,SAAS,MAAM,EAAE,CAAC,GACpE,OAAO,CAAC,IAAI,CAAC;IA6ChB,yFAAyF;YAC3E,SAAS;IAOvB;;;;OAIG;YACW,cAAc;CAiB7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG;IAC7D,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CA8BA;AAED,kGAAkG;AAClG,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM;CAQ7B"}
1
+ {"version":3,"file":"scope-closure.service.d.ts","sourceRoot":"","sources":["../../../src/features/scope/scope-closure.service.ts"],"names":[],"mappings":"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"}