@astram/guard 0.14.0 → 0.15.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 (95) hide show
  1. package/dist/auth/auth.module.d.ts.map +1 -1
  2. package/dist/auth/auth.module.js +57 -30
  3. package/dist/authentication/mariadb-api-key-store.d.ts +10 -1
  4. package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
  5. package/dist/authentication/mariadb-api-key-store.js +20 -10
  6. package/dist/authentication/mariadb-credential-resolver.d.ts +8 -1
  7. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  8. package/dist/authentication/mariadb-credential-resolver.js +15 -5
  9. package/dist/authentication/mariadb-credential-writer.d.ts +6 -1
  10. package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
  11. package/dist/authentication/mariadb-credential-writer.js +9 -3
  12. package/dist/authentication/mariadb-email-address-changer.d.ts +10 -1
  13. package/dist/authentication/mariadb-email-address-changer.d.ts.map +1 -1
  14. package/dist/authentication/mariadb-email-address-changer.js +22 -9
  15. package/dist/authentication/mariadb-email-change-token-store.d.ts +8 -1
  16. package/dist/authentication/mariadb-email-change-token-store.d.ts.map +1 -1
  17. package/dist/authentication/mariadb-email-change-token-store.js +18 -9
  18. package/dist/authentication/mariadb-mfa-device-store.d.ts +6 -1
  19. package/dist/authentication/mariadb-mfa-device-store.d.ts.map +1 -1
  20. package/dist/authentication/mariadb-mfa-device-store.js +22 -13
  21. package/dist/authentication/mariadb-profile-resolver.d.ts +6 -1
  22. package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
  23. package/dist/authentication/mariadb-profile-resolver.js +12 -6
  24. package/dist/authentication/mariadb-profile-writer.d.ts +6 -1
  25. package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
  26. package/dist/authentication/mariadb-profile-writer.js +11 -4
  27. package/dist/authentication/mariadb-recovery-code-store.d.ts +6 -1
  28. package/dist/authentication/mariadb-recovery-code-store.d.ts.map +1 -1
  29. package/dist/authentication/mariadb-recovery-code-store.js +11 -5
  30. package/dist/authentication/mariadb-reset-token-store.d.ts +6 -1
  31. package/dist/authentication/mariadb-reset-token-store.d.ts.map +1 -1
  32. package/dist/authentication/mariadb-reset-token-store.js +11 -5
  33. package/dist/authentication/mariadb-session-sweeper.d.ts +6 -1
  34. package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
  35. package/dist/authentication/mariadb-session-sweeper.js +11 -5
  36. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
  37. package/dist/authz/cross-tenant-grant-writer.js +4 -3
  38. package/dist/authz/list-scope-sql.d.ts +10 -2
  39. package/dist/authz/list-scope-sql.d.ts.map +1 -1
  40. package/dist/authz/list-scope-sql.js +4 -4
  41. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
  42. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +4 -3
  43. package/dist/authz/mariadb-list-scope-adapter.d.ts.map +1 -1
  44. package/dist/authz/mariadb-list-scope-adapter.js +4 -3
  45. package/dist/authz/mariadb-policy-store.d.ts.map +1 -1
  46. package/dist/authz/mariadb-policy-store.js +4 -3
  47. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
  48. package/dist/authz/mariadb-record-rule-adapter.js +4 -3
  49. package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -1
  50. package/dist/authz/mariadb-record-share-adapter.js +4 -3
  51. package/dist/authz/record-share-cascade.d.ts.map +1 -1
  52. package/dist/authz/record-share-cascade.js +4 -3
  53. package/dist/authz/record-share-writer.d.ts.map +1 -1
  54. package/dist/authz/record-share-writer.js +4 -3
  55. package/dist/authz/wire-record-authorization.d.ts +8 -0
  56. package/dist/authz/wire-record-authorization.d.ts.map +1 -1
  57. package/dist/authz/wire-record-authorization.js +14 -6
  58. package/dist/boot/boot-sequence.d.ts +4 -0
  59. package/dist/boot/boot-sequence.d.ts.map +1 -1
  60. package/dist/boot/boot-sequence.js +13 -1
  61. package/dist/boot/migration-ledger-check.d.ts +94 -0
  62. package/dist/boot/migration-ledger-check.d.ts.map +1 -0
  63. package/dist/boot/migration-ledger-check.js +280 -0
  64. package/dist/config/app-config.d.ts +29 -3
  65. package/dist/config/app-config.d.ts.map +1 -1
  66. package/dist/config/app-config.js +37 -6
  67. package/dist/config/config.module.d.ts +7 -0
  68. package/dist/config/config.module.d.ts.map +1 -1
  69. package/dist/config/config.module.js +8 -1
  70. package/dist/config/framework-schema-names.d.ts +29 -0
  71. package/dist/config/framework-schema-names.d.ts.map +1 -0
  72. package/dist/config/framework-schema-names.js +41 -0
  73. package/dist/identity/permission-facts.adapter.d.ts +7 -1
  74. package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
  75. package/dist/identity/permission-facts.adapter.js +15 -6
  76. package/dist/index.d.ts +5 -2
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +18 -2
  79. package/dist/module/shield.module.d.ts.map +1 -1
  80. package/dist/module/shield.module.js +43 -7
  81. package/dist/session/mariadb-session-record-store.d.ts +8 -1
  82. package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
  83. package/dist/session/mariadb-session-record-store.js +23 -13
  84. package/dist/session/session-restriction-store.d.ts +8 -1
  85. package/dist/session/session-restriction-store.d.ts.map +1 -1
  86. package/dist/session/session-restriction-store.js +15 -5
  87. package/migrations/1710000000006-CreateAuthzSchema.cjs +15 -4
  88. package/migrations/1710000000007-ExtendPermissionAndRolePermission.cjs +11 -3
  89. package/migrations/1710000000008-CreateAccessBundleTables.cjs +14 -4
  90. package/migrations/1710000000009-CreateRecordPermissionRule.cjs +15 -4
  91. package/migrations/1710000000014-CreateRecordShare.cjs +15 -4
  92. package/migrations/1710000000015-CreateScopeClosure.cjs +14 -4
  93. package/migrations/1710000000016-AlterCompanyParentFkRestrict.cjs +20 -9
  94. package/migrations/1710000000017-CreateServiceAccountKey.cjs +14 -4
  95. package/package.json +3 -3
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FrameworkMigrationLedgerCheck = exports.MigrationLedgerRegistry = exports.SHIELD_GUARD_MIGRATION_LEDGER_CHECK = exports.SHIELD_MIGRATION_LEDGER_REGISTRY = void 0;
4
+ exports.frameworkMigrationIds = frameworkMigrationIds;
5
+ exports.resolveMigrationLedger = resolveMigrationLedger;
6
+ exports.resolveMigrationLedgerCheck = resolveMigrationLedgerCheck;
7
+ exports.assertFrameworkMigrationsApplied = assertFrameworkMigrationsApplied;
8
+ /**
9
+ * Boot check: every framework migration a package ships is recorded in the `@astram/migrate`
10
+ * ledger (configurable-framework-db spec 5.D, §13).
11
+ *
12
+ * The ledger lives where `astram-migrate` writes it (spec 5.G): `<ns>_app` when
13
+ * `SHIELD_DB_NAMESPACE` is set, else `DDL_DB_NAME ?? APP_DB_NAME ?? <default app database>` of
14
+ * the migrate job's env; the table is `APP_MIGRATION_TABLE ?? 'schema_migrations'`. With the
15
+ * namespace set the check reads `<ns>_app` directly. In default mode it cannot know the migrate
16
+ * job's env, so it probes core `migrationLedgerCandidates` in order (`DDL_DB_NAME`, the runtime
17
+ * `APP_DB_NAME`, `shield_app`; one information_schema probe each, at bootstrap only) and reads the
18
+ * first ledger table that exists (G-007 QA P1). That ledger must hold every id; none found fails
19
+ * closed naming every location tried.
20
+ *
21
+ * Missing ids, no ledger table, or an unreadable ledger:
22
+ * - outside `NODE_ENV` development/test (unset included) ⇒ throws, boot fails (Shield #1);
23
+ * - in development/test ⇒ one warning, boot continues.
24
+ * An unreadable ledger is never treated as "all applied". Messages carry the database name, the
25
+ * table name, migration ids and a driver error code only: never a user, host, password or the
26
+ * driver's message text (core 70-secrets).
27
+ *
28
+ * Runs once, at application bootstrap, never per request (Shield #12). Who checks what:
29
+ * - `AuthModule` (`auth: true`) checks `guardMigrationsDir` over its runtime pool;
30
+ * - `@astram/identity` checks `guardMigrationsDir` AND `identityMigrationsDir` over its own pool
31
+ * (identity reads guard's authz tables; guard never imports identity, ADR-0003);
32
+ * - an enforcement-only app (no `auth`, no identity) opens no DB pool and reads no framework
33
+ * table, so it checks nothing.
34
+ * When both run in one app, the per-app {@link MigrationLedgerRegistry} (provided by
35
+ * `ShieldModule`) merges them: every hook registers its directories when constructed, and the
36
+ * first hook to bootstrap runs ONE SELECT per ledger location over the union; the others await
37
+ * that same result (a failure fails every one of them). Without the registry each hook runs alone.
38
+ */
39
+ const node_fs_1 = require("node:fs");
40
+ const core_1 = require("@astram/core");
41
+ const app_config_js_1 = require("../config/app-config.js");
42
+ /** At most this many ids are listed in the message (spec §13). */
43
+ const MAX_LISTED_IDS = 10;
44
+ /** Same id rule as `@astram/migrate`'s loader, restricted to the shipped `.cjs` files. */
45
+ const MIGRATION_FILE = /^(\d+)-.+\.cjs$/;
46
+ /** MariaDB "table doesn't exist". */
47
+ const ER_NO_SUCH_TABLE = 1146;
48
+ /** `1`, `01` and `001` are the same migration id (migrate orders ids numerically). */
49
+ function idKey(id) {
50
+ return id.replace(/^0+(?=\d)/, '');
51
+ }
52
+ /** The migration ids shipped in `dir` (`.cjs` files only), ascending. */
53
+ function frameworkMigrationIds(dir) {
54
+ const ids = [];
55
+ for (const file of (0, node_fs_1.readdirSync)(dir)) {
56
+ const m = MIGRATION_FILE.exec(file);
57
+ if (m)
58
+ ids.push(m[1]);
59
+ }
60
+ return sortIds(ids);
61
+ }
62
+ /** Ascending numeric order over decimal id strings. */
63
+ function sortIds(ids) {
64
+ return ids.sort((a, b) => {
65
+ const ka = idKey(a);
66
+ const kb = idKey(b);
67
+ return ka.length - kb.length || (ka < kb ? -1 : ka > kb ? 1 : 0);
68
+ });
69
+ }
70
+ /**
71
+ * The migrate CLI's ledger location rule (core `resolveMigrationLedgerLocation`: `<ns>_app` when
72
+ * namespaced, else `DDL_DB_NAME ?? <default app>`). The boot check itself uses
73
+ * {@link resolveMigrationLedgerCheck}, which also tries the runtime `APP_DB_NAME`.
74
+ * @throws FrameworkSchemaConfigError on a conflicting `DDL_DB_NAME` or invalid names.
75
+ */
76
+ function resolveMigrationLedger(env, schemas) {
77
+ return (0, core_1.resolveMigrationLedgerLocation)(schemas, env);
78
+ }
79
+ /**
80
+ * The boot check's ledger locations for this process's env (G-007 QA P1): core
81
+ * `migrationLedgerCandidates`, split into the first location and its `alternatives`. Namespace
82
+ * set ⇒ `<ns>_app` only; default ⇒ `DDL_DB_NAME`, the runtime `APP_DB_NAME`, `shield_app`.
83
+ * Spread it into {@link FrameworkMigrationsCheckOptions}.
84
+ * @throws FrameworkSchemaConfigError on a conflicting `DDL_DB_NAME` or invalid names.
85
+ */
86
+ function resolveMigrationLedgerCheck(env, schemas) {
87
+ const [first, ...alternatives] = (0, core_1.migrationLedgerCandidates)(schemas, env);
88
+ return { database: first.database, table: first.table, alternatives };
89
+ }
90
+ /** The driver error code only (never its message: it can carry user@host). */
91
+ function errorCode(err) {
92
+ if (typeof err === 'object' && err !== null) {
93
+ const { code, errno } = err;
94
+ if (typeof code === 'string' && /^[A-Z0-9_]{1,64}$/.test(code))
95
+ return code;
96
+ if (typeof errno === 'number')
97
+ return `errno ${errno}`;
98
+ }
99
+ return 'unknown error';
100
+ }
101
+ function isNoSuchTable(err) {
102
+ if (typeof err !== 'object' || err === null)
103
+ return false;
104
+ const { code, errno } = err;
105
+ return code === 'ER_NO_SUCH_TABLE' || errno === ER_NO_SUCH_TABLE;
106
+ }
107
+ /** No candidate holds a ledger table: name every location, tell the operator what to set. */
108
+ function notFoundMessage(tried, unverified) {
109
+ const where = tried.map((l) => `${l.database}.${l.table}`).join(', ');
110
+ return (`Shield migration ledger not found (tried, in order: ${where}); ` +
111
+ `${unverified} framework migrations are unverified. Set DDL_DB_NAME to the ledger database, ` +
112
+ 'the same value for `astram-migrate` and the server, and run `astram-migrate migrate` ' +
113
+ 'with the deploy account before starting the server.');
114
+ }
115
+ /** One information_schema probe: does `l.database` hold the table `l.table`? Names validated. */
116
+ function probeSql(l) {
117
+ return ('SELECT 1 AS present FROM information_schema.TABLES ' +
118
+ `WHERE TABLE_SCHEMA = '${l.database}' AND TABLE_NAME = '${l.table}' LIMIT 1`);
119
+ }
120
+ function missingMessage(database, missing) {
121
+ const listed = missing.slice(0, MAX_LISTED_IDS);
122
+ const more = missing.length - listed.length;
123
+ const ids = more > 0 ? `${listed.join(', ')}, … ${more} more` : listed.join(', ');
124
+ return (`Shield database ${database} is missing ${missing.length} framework migrations (${ids}). ` +
125
+ 'Run `astram-migrate migrate` with the deploy account before starting the server.');
126
+ }
127
+ /**
128
+ * Compare the shipped migration ids in `migrationsDir` with the ledger; throw (production) or
129
+ * warn (development/test) when any is missing or the ledger cannot be read.
130
+ * @throws Error naming the database and up to 10 missing ids, outside development/test; and in
131
+ * every environment for an invalid ledger database or table name (checked before any query).
132
+ */
133
+ async function assertFrameworkMigrationsApplied(options) {
134
+ // Every location is validated before any query (operator names: any plain MariaDB identifier).
135
+ const locations = [
136
+ { database: options.database, table: options.table },
137
+ ...(options.alternatives ?? []),
138
+ ].map((l) => {
139
+ const database = (0, core_1.assertLedgerDatabaseName)(l.database, 'migration ledger database');
140
+ if (typeof l.table !== 'string' || !core_1.MIGRATION_LEDGER_TABLE_PATTERN.test(l.table)) {
141
+ throw new Error(`APP_MIGRATION_TABLE is not a valid table name; expected ${core_1.MIGRATION_LEDGER_TABLE_PATTERN.source}`);
142
+ }
143
+ return { database, table: l.table };
144
+ });
145
+ let { database, table } = locations[0];
146
+ const devOrTest = (0, app_config_js_1.isDevOrTestNodeEnv)(options.nodeEnv);
147
+ const fail = (message, missing) => {
148
+ if (!devOrTest)
149
+ throw new Error(message);
150
+ options.logger.warn(`${message} (NODE_ENV=${options.nodeEnv}: continuing)`);
151
+ return { ok: false, missing };
152
+ };
153
+ let shipped;
154
+ try {
155
+ const dirs = typeof options.migrationsDir === 'string' ? [options.migrationsDir] : options.migrationsDir;
156
+ shipped = sortIds([...new Set(dirs.flatMap((d) => frameworkMigrationIds(d)))]);
157
+ }
158
+ catch (err) {
159
+ return fail(`Shield database ${database}: cannot read the framework migrations directory ` +
160
+ `(${errorCode(err)}); the package install is incomplete.`, []);
161
+ }
162
+ if (shipped.length === 0)
163
+ return { ok: true, missing: [] };
164
+ // Several candidates (default namespace): use the first whose ledger table exists. A probe
165
+ // error is never "not here" or "all applied": it fails like an unreadable ledger.
166
+ if (locations.length > 1) {
167
+ let found;
168
+ for (const l of locations) {
169
+ let rows;
170
+ try {
171
+ rows = await options.query(probeSql(l));
172
+ }
173
+ catch (err) {
174
+ return fail(`Shield database ${l.database}: cannot look for the migration ledger ` +
175
+ `${l.database}.${l.table} (${errorCode(err)}); ${shipped.length} framework ` +
176
+ 'migrations are unverified.', shipped);
177
+ }
178
+ if (!Array.isArray(rows)) {
179
+ return fail(`Shield database ${l.database}: cannot look for the migration ledger ` +
180
+ `${l.database}.${l.table} (UNEXPECTED_RESULT); ${shipped.length} framework ` +
181
+ 'migrations are unverified.', shipped);
182
+ }
183
+ if (rows.length > 0) {
184
+ found = l;
185
+ break;
186
+ }
187
+ }
188
+ if (found === undefined)
189
+ return fail(notFoundMessage(locations, shipped.length), shipped);
190
+ ({ database, table } = found);
191
+ }
192
+ let applied;
193
+ try {
194
+ const rows = await options.query(`SELECT id FROM \`${database}\`.\`${table}\``);
195
+ if (!Array.isArray(rows))
196
+ throw Object.assign(new Error(), { code: 'UNEXPECTED_RESULT' });
197
+ applied = new Set(rows.map((r) => idKey(String(r?.id ?? ''))));
198
+ }
199
+ catch (err) {
200
+ // No ledger table: nothing was ever migrated here, so every shipped id is missing.
201
+ if (isNoSuchTable(err))
202
+ return fail(missingMessage(database, shipped), shipped);
203
+ return fail(`Shield database ${database}: cannot read the migration ledger ${database}.${table} ` +
204
+ `(${errorCode(err)}); ${shipped.length} framework migrations are unverified.`, shipped);
205
+ }
206
+ const missing = shipped.filter((id) => !applied.has(idKey(id)));
207
+ if (missing.length === 0)
208
+ return { ok: true, missing: [] };
209
+ return fail(missingMessage(database, missing), missing);
210
+ }
211
+ /** DI token of the per-app {@link MigrationLedgerRegistry} (provided by `ShieldModule`). */
212
+ exports.SHIELD_MIGRATION_LEDGER_REGISTRY = Symbol('SHIELD_MIGRATION_LEDGER_REGISTRY');
213
+ /** DI token of guard's own check (`AuthModule`); identity provides its check under its own token. */
214
+ exports.SHIELD_GUARD_MIGRATION_LEDGER_CHECK = Symbol('SHIELD_GUARD_MIGRATION_LEDGER_CHECK');
215
+ /**
216
+ * Merges the ledger checks of one application (guard's `AuthModule` and identity) so each ledger
217
+ * location is read once. Hooks {@link register} at construction (all providers exist before any
218
+ * bootstrap hook runs); the first {@link run} for a location checks the union of every registered
219
+ * directory and caches that promise, so later hooks share the result, rejection included.
220
+ */
221
+ class MigrationLedgerRegistry {
222
+ entries = new Map();
223
+ /** The whole candidate list: hooks with the same list share one probe + read. */
224
+ static key(l) {
225
+ return [l, ...(l.alternatives ?? [])]
226
+ .map((x) => `${x.database}\u0000${x.table}`)
227
+ .join('\u0001');
228
+ }
229
+ /** Add `options.migrationsDir` to the check of `options`' ledger location. */
230
+ register(options) {
231
+ const dirs = typeof options.migrationsDir === 'string' ? [options.migrationsDir] : options.migrationsDir;
232
+ const key = MigrationLedgerRegistry.key(options);
233
+ const entry = this.entries.get(key);
234
+ if (entry === undefined) {
235
+ this.entries.set(key, { options, dirs: new Set(dirs) });
236
+ return;
237
+ }
238
+ if (entry.result !== undefined) {
239
+ throw new Error('MigrationLedgerRegistry: a check registered after the ledger was read.');
240
+ }
241
+ for (const d of dirs)
242
+ entry.dirs.add(d);
243
+ }
244
+ /** The (shared) check of `location`'s ledger over every registered directory. */
245
+ run(location) {
246
+ const entry = this.entries.get(MigrationLedgerRegistry.key(location));
247
+ // Never registered: nothing to share, nothing claims "checked" (fail closed).
248
+ if (entry === undefined) {
249
+ return Promise.reject(new Error('MigrationLedgerRegistry: ledger location not registered.'));
250
+ }
251
+ entry.result ??= assertFrameworkMigrationsApplied({
252
+ ...entry.options,
253
+ migrationsDir: [...entry.dirs],
254
+ });
255
+ return entry.result;
256
+ }
257
+ }
258
+ exports.MigrationLedgerRegistry = MigrationLedgerRegistry;
259
+ /**
260
+ * The boot hook: runs {@link assertFrameworkMigrationsApplied} once, when Nest bootstraps the
261
+ * application (after module init, before the HTTP server listens). Never on a request. With a
262
+ * {@link MigrationLedgerRegistry}, it shares one read per ledger location with the app's other
263
+ * hooks; without one (`null`/absent), it checks alone.
264
+ */
265
+ class FrameworkMigrationLedgerCheck {
266
+ options;
267
+ registry;
268
+ constructor(options, registry = null) {
269
+ this.options = options;
270
+ this.registry = registry;
271
+ registry?.register(options);
272
+ }
273
+ async onApplicationBootstrap() {
274
+ if (this.registry)
275
+ await this.registry.run(this.options);
276
+ else
277
+ await assertFrameworkMigrationsApplied(this.options);
278
+ }
279
+ }
280
+ exports.FrameworkMigrationLedgerCheck = FrameworkMigrationLedgerCheck;
@@ -23,7 +23,7 @@
23
23
  * 8 × (12 + 8 + 1) + 40 = 208 ≤ 250 ✓
24
24
  * ─────────────────────────────────────────────────────────────────────────────
25
25
  */
26
- import type { SecretsProvider } from '@astram/core';
26
+ import type { FrameworkSchemas, SecretsProvider } from '@astram/core';
27
27
  export interface AppConfig {
28
28
  /** HTTP port the Nest app listens on. */
29
29
  readonly port: number;
@@ -74,7 +74,20 @@ export interface AppConfig {
74
74
  * composition (boot fails loud).
75
75
  */
76
76
  readonly refreshReplayWindowMs?: number;
77
+ /**
78
+ * The four framework database names (configurable-framework-db, ADR-0037), resolved once from
79
+ * `SHIELD_DB_NAMESPACE` by core `resolveFrameworkSchemas` (unset ⇒ the `shield_*` defaults).
80
+ * Optional so hand-built configs keep compiling; absent ⇒ {@link frameworkSchemasOf} resolves
81
+ * from `process.env` once at boot. Provided app-wide under `FRAMEWORK_SCHEMAS`.
82
+ */
83
+ readonly schemas?: FrameworkSchemas;
77
84
  }
85
+ /**
86
+ * The resolved framework database names for a config: `config.schemas`, else resolved from
87
+ * `process.env` (a hand-built config without `schemas`). Call at boot only (Shield #12).
88
+ * @throws FrameworkSchemaConfigError on an invalid `SHIELD_DB_NAMESPACE` (fail closed).
89
+ */
90
+ export declare function frameworkSchemasOf(config: Pick<AppConfig, 'schemas'>): FrameworkSchemas;
78
91
  /** The per-client-IP login failure limit (failures within `windowMs` that lock the IP). */
79
92
  export interface LoginIpLimitConfig {
80
93
  readonly maxAttempts: number;
@@ -205,7 +218,11 @@ export declare function isDevOrTestNodeEnv(raw: string | undefined): boolean;
205
218
  * perAccountMax.shield_schema_svc ← POOL_MAX_SCHEMA_SVC
206
219
  */
207
220
  export declare function loadPoolBudget(env?: NodeJS.ProcessEnv): PoolBudgetConfig;
208
- /** Documented defaults for the runtime MariaDB descriptor (local docker substrate). */
221
+ /**
222
+ * Documented defaults for the runtime MariaDB descriptor (local docker substrate), for the
223
+ * default namespace. Under `SHIELD_DB_NAMESPACE` the user and database follow the namespace
224
+ * (`<ns>_core`, `<ns>_identity`; see {@link loadDbConfig}).
225
+ */
209
226
  export declare const DEFAULT_DB_CONFIG: Omit<DbConfig, 'password'>;
210
227
  /** Documented default Redis url (local docker substrate). */
211
228
  export declare const DEFAULT_REDIS_URL = "redis://localhost:6379";
@@ -219,8 +236,17 @@ export declare const DEFAULT_REDIS_URL = "redis://localhost:6379";
219
236
  * connect time (loud), which is correct for a runtime that needs real creds; the
220
237
  * secret is deliberately NOT hard-required at config load (unlike the HMAC key,
221
238
  * which is a fail-closed security invariant even for unit tests).
239
+ *
240
+ * Namespace (configurable-framework-db, spec 5.C): `APP_DB_USER` unset ⇒ `<ns>_core`;
241
+ * `APP_DB_NAME` unset ⇒ `<ns>_identity`. A set `APP_DB_NAME` must be one of the four resolved
242
+ * framework names, else this throws naming the variable (fail closed: an unqualified query must
243
+ * not reach another environment's database). An invalid `SHIELD_DB_NAMESPACE` throws too.
244
+ *
245
+ * `schemas` (optional): names the caller already resolved (e.g. `IdentityFeatureModule.forRoot
246
+ * ({ schemas })`). When given they replace the env namespace for the defaults and the
247
+ * `APP_DB_NAME` check, so the runtime database cannot drift from them (G-007 QA P2-5).
222
248
  */
223
- export declare function loadDbConfig(env?: NodeJS.ProcessEnv): DbConfig;
249
+ export declare function loadDbConfig(env?: NodeJS.ProcessEnv, schemas?: FrameworkSchemas): DbConfig;
224
250
  /**
225
251
  * `APP_DB_TLS` = `off` (default) | `on`; `APP_DB_TLS_CA` = PEM path (only with `on`). Anything
226
252
  * else throws at boot: a typo must not silently fall back to plaintext.
@@ -1 +1 @@
1
- {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_OBJECT_STORAGE_MAX_BYTES = exports.DEFAULT_REDIS_URL = exports.DEFAULT_DB_CONFIG = exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = exports.DEFAULT_REDIS_COMMAND_TIMEOUT_MS = exports.DEFAULT_LOGIN_IP_LIMIT = void 0;
4
+ exports.frameworkSchemasOf = frameworkSchemasOf;
4
5
  exports.isDevOrTestNodeEnv = isDevOrTestNodeEnv;
5
6
  exports.loadPoolBudget = loadPoolBudget;
6
7
  exports.loadDbConfig = loadDbConfig;
@@ -17,6 +18,14 @@ exports.loadObjectStorageConfig = loadObjectStorageConfig;
17
18
  exports.evaluatePoolBudget = evaluatePoolBudget;
18
19
  const core_1 = require("@astram/core");
19
20
  const loopback_host_js_1 = require("./loopback-host.js");
21
+ /**
22
+ * The resolved framework database names for a config: `config.schemas`, else resolved from
23
+ * `process.env` (a hand-built config without `schemas`). Call at boot only (Shield #12).
24
+ * @throws FrameworkSchemaConfigError on an invalid `SHIELD_DB_NAMESPACE` (fail closed).
25
+ */
26
+ function frameworkSchemasOf(config) {
27
+ return config.schemas ?? (0, core_1.resolveFrameworkSchemas)(process.env);
28
+ }
20
29
  /** Plan D3 default: 50 failed logins per client IP per 15 minutes. */
21
30
  exports.DEFAULT_LOGIN_IP_LIMIT = {
22
31
  maxAttempts: 50,
@@ -102,12 +111,18 @@ function loadPoolBudget(env = process.env) {
102
111
  // lifetimes. All pure env-readers with documented defaults; the ONLY hard-
103
112
  // required value is the refresh HMAC key (fail-closed).
104
113
  // ---------------------------------------------------------------------------
105
- /** Documented defaults for the runtime MariaDB descriptor (local docker substrate). */
114
+ /** The default-namespace framework names (`SHIELD_DB_NAMESPACE` unset). */
115
+ const DEFAULT_FRAMEWORK_SCHEMAS = (0, core_1.resolveFrameworkSchemas)({});
116
+ /**
117
+ * Documented defaults for the runtime MariaDB descriptor (local docker substrate), for the
118
+ * default namespace. Under `SHIELD_DB_NAMESPACE` the user and database follow the namespace
119
+ * (`<ns>_core`, `<ns>_identity`; see {@link loadDbConfig}).
120
+ */
106
121
  exports.DEFAULT_DB_CONFIG = {
107
122
  host: 'localhost',
108
123
  port: 3306,
109
- user: 'shield_core',
110
- database: 'shield_identity',
124
+ user: (0, core_1.frameworkAccountName)(DEFAULT_FRAMEWORK_SCHEMAS.namespace, 'core'),
125
+ database: DEFAULT_FRAMEWORK_SCHEMAS.identity,
111
126
  };
112
127
  /** Documented default Redis url (local docker substrate). */
113
128
  exports.DEFAULT_REDIS_URL = 'redis://localhost:6379';
@@ -139,14 +154,29 @@ function parseOptionalPositiveInt(raw) {
139
154
  * connect time (loud), which is correct for a runtime that needs real creds; the
140
155
  * secret is deliberately NOT hard-required at config load (unlike the HMAC key,
141
156
  * which is a fail-closed security invariant even for unit tests).
157
+ *
158
+ * Namespace (configurable-framework-db, spec 5.C): `APP_DB_USER` unset ⇒ `<ns>_core`;
159
+ * `APP_DB_NAME` unset ⇒ `<ns>_identity`. A set `APP_DB_NAME` must be one of the four resolved
160
+ * framework names, else this throws naming the variable (fail closed: an unqualified query must
161
+ * not reach another environment's database). An invalid `SHIELD_DB_NAMESPACE` throws too.
162
+ *
163
+ * `schemas` (optional): names the caller already resolved (e.g. `IdentityFeatureModule.forRoot
164
+ * ({ schemas })`). When given they replace the env namespace for the defaults and the
165
+ * `APP_DB_NAME` check, so the runtime database cannot drift from them (G-007 QA P2-5).
142
166
  */
143
- function loadDbConfig(env = process.env) {
167
+ function loadDbConfig(env = process.env, schemas = (0, core_1.resolveFrameworkSchemas)(env)) {
168
+ const database = parseString(env.APP_DB_NAME, schemas.identity);
169
+ const allowed = [schemas.app, schemas.identity, schemas.authz, schemas.audit];
170
+ if (!allowed.includes(database)) {
171
+ throw new core_1.FrameworkSchemaConfigError(`APP_DB_NAME must be one of the framework databases for namespace ` +
172
+ `"${schemas.namespace}" (${allowed.join(', ')}); unset it for ${schemas.identity}.`);
173
+ }
144
174
  return {
145
175
  host: parseString(env.APP_DB_HOST, exports.DEFAULT_DB_CONFIG.host),
146
176
  port: parsePort(env.APP_DB_PORT, exports.DEFAULT_DB_CONFIG.port),
147
- user: parseString(env.APP_DB_USER, exports.DEFAULT_DB_CONFIG.user),
177
+ user: parseString(env.APP_DB_USER, (0, core_1.frameworkAccountName)(schemas.namespace, 'core')),
148
178
  password: parseString(env.APP_DB_PASSWORD, ''),
149
- database: parseString(env.APP_DB_NAME, exports.DEFAULT_DB_CONFIG.database),
179
+ database,
150
180
  tls: loadDbTlsConfig(env),
151
181
  };
152
182
  }
@@ -281,6 +311,7 @@ function assertRefreshHmacKey(key) {
281
311
  function loadAppConfig(env = process.env) {
282
312
  const refreshReplayWindowMs = loadRefreshReplayWindowMs(env);
283
313
  return {
314
+ schemas: (0, core_1.resolveFrameworkSchemas)(env),
284
315
  port: parsePort(env.PORT, 3000),
285
316
  environment: parseEnvironment(env.NODE_ENV),
286
317
  pool: loadPoolBudget(env),
@@ -1,5 +1,12 @@
1
1
  /** DI token for the resolved {@link AppConfig}. */
2
2
  export declare const APP_CONFIG: unique symbol;
3
+ /**
4
+ * DI token for the resolved framework database names (core `FrameworkSchemas`:
5
+ * `{ namespace, app, identity, authz, audit, isDefault }`, frozen; configurable-framework-db,
6
+ * ADR-0037). Provided and exported app-wide by `ShieldModule` from `APP_CONFIG`, resolved once at
7
+ * boot; identity and app modules inject it instead of hard-coding `shield_*` names.
8
+ */
9
+ export declare const FRAMEWORK_SCHEMAS: unique symbol;
3
10
  /**
4
11
  * Minimal config module (Scaffold).
5
12
  *
@@ -1 +1 @@
1
- {"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../../src/config/config.module.ts"],"names":[],"mappings":"AAIA,mDAAmD;AACnD,eAAO,MAAM,UAAU,eAAuB,CAAC;AAE/C;;;;;;GAMG;AACH,qBAaa,YAAY;CAAG"}
1
+ {"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../../src/config/config.module.ts"],"names":[],"mappings":"AAIA,mDAAmD;AACnD,eAAO,MAAM,UAAU,eAAuB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAE7D;;;;;;GAMG;AACH,qBAaa,YAAY;CAAG"}
@@ -6,12 +6,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.ConfigModule = exports.APP_CONFIG = void 0;
9
+ exports.ConfigModule = exports.FRAMEWORK_SCHEMAS = exports.APP_CONFIG = void 0;
10
10
  const common_1 = require("@nestjs/common");
11
11
  const app_config_js_1 = require("./app-config.js");
12
12
  const boot_secrets_js_1 = require("../secrets/boot-secrets.js");
13
13
  /** DI token for the resolved {@link AppConfig}. */
14
14
  exports.APP_CONFIG = Symbol('APP_CONFIG');
15
+ /**
16
+ * DI token for the resolved framework database names (core `FrameworkSchemas`:
17
+ * `{ namespace, app, identity, authz, audit, isDefault }`, frozen; configurable-framework-db,
18
+ * ADR-0037). Provided and exported app-wide by `ShieldModule` from `APP_CONFIG`, resolved once at
19
+ * boot; identity and app modules inject it instead of hard-coding `shield_*` names.
20
+ */
21
+ exports.FRAMEWORK_SCHEMAS = Symbol('FRAMEWORK_SCHEMAS');
15
22
  /**
16
23
  * Minimal config module (Scaffold).
17
24
  *
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The framework database names a guard store or adapter targets (configurable-framework-db,
3
+ * spec §5.C, ADR-0037). Every store takes them from its constructor; the module wiring passes
4
+ * `AppConfig.schemas` / `FRAMEWORK_SCHEMAS`, so no SQL in guard carries a database literal.
5
+ *
6
+ * SECURITY (Shield #12): names are resolved at CONSTRUCTION (boot), never per request; a store
7
+ * built outside the wiring resolves `SHIELD_DB_NAMESPACE` once here, so it can never split names
8
+ * across two namespaces. An invalid namespace throws (fail closed, Shield #1). Names reach SQL
9
+ * only after core's pattern check, which admits no backtick, quote, dot or whitespace.
10
+ */
11
+ import { type FrameworkSchemas } from '@astram/core';
12
+ /** The four framework database names (core `FrameworkSchemas` without its metadata). */
13
+ export type FrameworkSchemaNames = Pick<FrameworkSchemas, 'app' | 'identity' | 'authz' | 'audit'>;
14
+ /**
15
+ * `given` (each name checked with core `assertSchemaName`), else the names resolved from
16
+ * `SHIELD_DB_NAMESPACE` NOW. Call from a constructor or a boot-time factory only, never on a
17
+ * request path.
18
+ * @throws FrameworkSchemaConfigError on an invalid `SHIELD_DB_NAMESPACE` or an unsafe given name.
19
+ */
20
+ export declare function schemaNamesAtConstruction(given?: FrameworkSchemaNames): FrameworkSchemaNames;
21
+ /**
22
+ * An authz adapter's `opts.schema`: the given name, checked with core `assertSchemaName`, else
23
+ * the authz name resolved from `SHIELD_DB_NAMESPACE` NOW. Constructors / boot factories only.
24
+ * @throws FrameworkSchemaConfigError on an unsafe given name or an invalid namespace.
25
+ */
26
+ export declare function authzSchemaAtConstruction(given?: string): string;
27
+ /** The backtick-quoted `` `schema`.`table` `` reference for a validated database name. */
28
+ export declare function qualifiedTableName(schema: string, table: string): string;
29
+ //# sourceMappingURL=framework-schema-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework-schema-names.d.ts","sourceRoot":"","sources":["../../src/config/framework-schema-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AAEtB,wFAAwF;AACxF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAElG;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CAI5F;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAExE"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.schemaNamesAtConstruction = schemaNamesAtConstruction;
4
+ exports.authzSchemaAtConstruction = authzSchemaAtConstruction;
5
+ exports.qualifiedTableName = qualifiedTableName;
6
+ /**
7
+ * The framework database names a guard store or adapter targets (configurable-framework-db,
8
+ * spec §5.C, ADR-0037). Every store takes them from its constructor; the module wiring passes
9
+ * `AppConfig.schemas` / `FRAMEWORK_SCHEMAS`, so no SQL in guard carries a database literal.
10
+ *
11
+ * SECURITY (Shield #12): names are resolved at CONSTRUCTION (boot), never per request; a store
12
+ * built outside the wiring resolves `SHIELD_DB_NAMESPACE` once here, so it can never split names
13
+ * across two namespaces. An invalid namespace throws (fail closed, Shield #1). Names reach SQL
14
+ * only after core's pattern check, which admits no backtick, quote, dot or whitespace.
15
+ */
16
+ const core_1 = require("@astram/core");
17
+ /**
18
+ * `given` (each name checked with core `assertSchemaName`), else the names resolved from
19
+ * `SHIELD_DB_NAMESPACE` NOW. Call from a constructor or a boot-time factory only, never on a
20
+ * request path.
21
+ * @throws FrameworkSchemaConfigError on an invalid `SHIELD_DB_NAMESPACE` or an unsafe given name.
22
+ */
23
+ function schemaNamesAtConstruction(given) {
24
+ return given !== undefined
25
+ ? (0, core_1.assertFrameworkSchemaNames)(given, 'schemas')
26
+ : (0, core_1.resolveFrameworkSchemas)(process.env);
27
+ }
28
+ /**
29
+ * An authz adapter's `opts.schema`: the given name, checked with core `assertSchemaName`, else
30
+ * the authz name resolved from `SHIELD_DB_NAMESPACE` NOW. Constructors / boot factories only.
31
+ * @throws FrameworkSchemaConfigError on an unsafe given name or an invalid namespace.
32
+ */
33
+ function authzSchemaAtConstruction(given) {
34
+ return given !== undefined
35
+ ? (0, core_1.assertSchemaName)(given, 'opts.schema')
36
+ : (0, core_1.resolveFrameworkSchemas)(process.env).authz;
37
+ }
38
+ /** The backtick-quoted `` `schema`.`table` `` reference for a validated database name. */
39
+ function qualifiedTableName(schema, table) {
40
+ return `\`${schema}\`.\`${table}\``;
41
+ }
@@ -8,6 +8,7 @@
8
8
  * never a fabricated empty projection (that would silently read as "you have no grants").
9
9
  */
10
10
  import type { Pool } from 'mariadb';
11
+ import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
11
12
  export interface AuthFactDto {
12
13
  readonly action: string;
13
14
  readonly allowed: boolean;
@@ -29,7 +30,12 @@ export declare class MariaDbPermissionFactsAdapter {
29
30
  private readonly listActions;
30
31
  private readonly controlPlaneKeys;
31
32
  private readonly now;
32
- constructor(pool: Pool, listActions: () => readonly string[], controlPlaneKeys: readonly string[], now?: () => number);
33
+ /** The org (`<app>`) and authorization (`<authz>`) databases, resolved at construction. */
34
+ private readonly app;
35
+ private readonly authz;
36
+ constructor(pool: Pool, listActions: () => readonly string[], controlPlaneKeys: readonly string[], now?: () => number,
37
+ /** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
38
+ schemas?: FrameworkSchemaNames);
33
39
  projectFor(principalId: string, tenantId: string, isRoot: boolean): Promise<AuthFactProjection>;
34
40
  currentVersionFor(_principalId: string, tenantId: string): Promise<number>;
35
41
  private readRoleIdsForTenant;
@@ -1 +1 @@
1
- {"version":3,"file":"permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../src/identity/permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AACD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,yFAAyF;AACzF,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,OAAO,SAA8B;CAIlD;AAYD,qBAAa,6BAA6B;IAEtC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAHH,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,EACpC,gBAAgB,EAAE,SAAS,MAAM,EAAE,EACnC,GAAG,GAAE,MAAM,MAAiB;IAGzC,UAAU,CACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,kBAAkB,CAAC;IAmBxB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YASlE,oBAAoB;YAepB,aAAa;YASb,WAAW;IAQzB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;CAaxB"}
1
+ {"version":3,"file":"permission-facts.adapter.d.ts","sourceRoot":"","sources":["../../src/identity/permission-facts.adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AACD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,yFAAyF;AACzF,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,OAAO,SAA8B;CAIlD;AAYD,qBAAa,6BAA6B;IAMtC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAGZ,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,EACpC,gBAAgB,EAAE,SAAS,MAAM,EAAE,EACnC,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAO1B,UAAU,CACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,kBAAkB,CAAC;IAmBxB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YASlE,oBAAoB;YAepB,aAAa;YASb,WAAW;IAQzB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;CAaxB"}