@astram/guard 0.18.0 → 0.19.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.
@@ -0,0 +1,90 @@
1
+ import { type LoggerService } from '@nestjs/common';
2
+ import type { Pool } from 'mariadb';
3
+ import { type Clock, type FrameworkSchemas, type HashFunction } from '@astram/core';
4
+ /** Lookback for the incremental scan (SPEC §10). */
5
+ export declare const AUDIT_SEAL_LOOKBACK_MS: number;
6
+ /** Full sweep and table re-probe period (SPEC §10). */
7
+ export declare const AUDIT_SEAL_SWEEP_MS: number;
8
+ /** Candidate rows per keyset page (SPEC §10). */
9
+ export declare const AUDIT_SEAL_PAGE = 1000;
10
+ /** Keyset pages per tick (SPEC §10). */
11
+ export declare const AUDIT_SEAL_PAGES = 10;
12
+ /** Tenants sealed per tick (SPEC §10). */
13
+ export declare const AUDIT_SEAL_TENANTS = 200;
14
+ /** Parked (stuck) tenants held at most (SPEC §10). */
15
+ export declare const AUDIT_SEAL_STUCK = 200;
16
+ /** The logger surface the sealer uses (Nest `Logger` shape). */
17
+ export type AuditSealerLogger = Pick<LoggerService, 'log' | 'warn' | 'error'>;
18
+ export interface AuditChainSealerOptions {
19
+ /** `SHIELD_DB_POOL`. Only `getConnection()` is used: every statement runs on the lock's session. */
20
+ readonly pool: Pool;
21
+ /** Resolved framework database names (`FRAMEWORK_SCHEMAS`). */
22
+ readonly schemas: FrameworkSchemas;
23
+ /** Chain hash (`NodeSha256Hash` in production). */
24
+ readonly hash: HashFunction;
25
+ /** Rows per tenant per tick (`AUDIT_SEAL_BATCH`). */
26
+ readonly batch: number;
27
+ /** Sealer clock: `ac_sealed_at_ms` and the sweep period. Default `Date.now`. */
28
+ readonly clock?: Clock;
29
+ /** Default `new Logger('Shield')`. */
30
+ readonly logger?: AuditSealerLogger;
31
+ }
32
+ /**
33
+ * A log-safe class for an error: a typed core reason, a driver error code / errno, or
34
+ * `unexpected`. Never the message (it can echo row content, e.g. a duplicate-key value).
35
+ */
36
+ export declare function auditSealErrorClass(err: unknown): string;
37
+ /** The leader lock name: `<ns>:audit_sealer` (single layout: `<db>:audit_sealer`), ≤ 64 chars. */
38
+ export declare function auditSealerLockName(schemas: Pick<FrameworkSchemas, 'namespace'>): string;
39
+ export declare class AuditChainSealer {
40
+ private readonly pool;
41
+ private readonly hash;
42
+ private readonly batch;
43
+ private readonly clock;
44
+ private readonly logger;
45
+ private readonly lockName;
46
+ private readonly logTable;
47
+ private readonly chainTable;
48
+ private readonly checkpointTable;
49
+ /** Sealer clock at the start of the last sweep; `undefined` ⇒ the next leader tick sweeps. */
50
+ private lastSweepAtMs;
51
+ /** Tenants parked until the next sweep (exact strings). */
52
+ private readonly stuck;
53
+ /** `(reason, tenant)` keys already logged this sweep. */
54
+ private readonly logged;
55
+ /** The stuck-set overflow was logged this sweep. */
56
+ private overflowLogged;
57
+ /**
58
+ * Backlog cursor (SPEC §5.G step 2): the oldest position the last tick left unsealed because
59
+ * of the batch / page / tenant caps (stuck tenants excluded). Leader memory only.
60
+ */
61
+ private cursor;
62
+ constructor(options: AuditChainSealerOptions);
63
+ /**
64
+ * One tick. Always releases the lock and returns (or destroys) the connection before it
65
+ * settles. A tenant batch error never rejects; an unexpected error (e.g. the candidate read
66
+ * failing) rejects after the cleanup.
67
+ */
68
+ tick(): Promise<void>;
69
+ private forgetLeadership;
70
+ private sealOnce;
71
+ /** The oldest position the caps left behind, skipping stuck tenants; `undefined` ⇒ none. */
72
+ private oldestLeftBehind;
73
+ /** `MIN(MAX(ac_occurred_at_ms), db now) − lookback`, or `undefined` for an empty chain. */
74
+ private readWindowStart;
75
+ /**
76
+ * Keyset-paged candidate read, grouped by exact tenant string, ≤ `batch` rows per tenant and
77
+ * ≤ 200 tenants (first appearance in keyset order; the rest wait for the next tick). The
78
+ * first page starts after `windowStart`, or at the backlog cursor `start` when given. Also
79
+ * returns what the caps left behind, for the backlog cursor.
80
+ */
81
+ private readCandidates;
82
+ /** Each tenant's head: the `MAX(ac_sequence)` chain row, else its latest checkpoint. */
83
+ private readHeads;
84
+ /** Link and insert one tenant's batch in one transaction; errors stay with this tenant. */
85
+ private sealTenant;
86
+ /** Log a failed batch (once per tenant and reason per sweep) and park it if it repeats. */
87
+ private recordFailure;
88
+ private logOnce;
89
+ }
90
+ //# sourceMappingURL=audit-chain-sealer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-chain-sealer.d.ts","sourceRoot":"","sources":["../../src/audit/audit-chain-sealer.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,SAAS,CAAC;AACpD,OAAO,EASL,KAAK,KAAK,EACV,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,cAAc,CAAC;AAEtB,oDAAoD;AACpD,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAClD,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,QAAc,CAAC;AAC/C,iDAAiD;AACjD,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,wCAAwC;AACxC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,0CAA0C;AAC1C,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAOpC,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,oGAAoG;IACpG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,sCAAsC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC;AAyDD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CASxD;AAUD,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG,MAAM,CAOxF;AAOD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,8FAA8F;IAC9F,OAAO,CAAC,aAAa,CAAqB;IAC1C,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,oDAAoD;IACpD,OAAO,CAAC,cAAc,CAAS;IAC/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAA6B;gBAE/B,OAAO,EAAE,uBAAuB;IAa5C;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD3B,OAAO,CAAC,gBAAgB;YAQV,QAAQ;IAwCtB,4FAA4F;IAC5F,OAAO,CAAC,gBAAgB;IAWxB,2FAA2F;YAC7E,eAAe;IAa7B;;;;;OAKG;YACW,cAAc;IA0H5B,wFAAwF;YAC1E,SAAS;IAyDvB,2FAA2F;YAC7E,UAAU;IAmExB,2FAA2F;IAC3F,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,OAAO;CAMhB"}
@@ -0,0 +1,520 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditChainSealer = exports.AUDIT_SEAL_STUCK = exports.AUDIT_SEAL_TENANTS = exports.AUDIT_SEAL_PAGES = exports.AUDIT_SEAL_PAGE = exports.AUDIT_SEAL_SWEEP_MS = exports.AUDIT_SEAL_LOOKBACK_MS = void 0;
4
+ exports.auditSealErrorClass = auditSealErrorClass;
5
+ exports.auditSealerLockName = auditSealerLockName;
6
+ /**
7
+ * `AuditChainSealer` — one tick of the deferred audit sealer (audit-chain-store SPEC §5.G,
8
+ * §10, §11, §13).
9
+ *
10
+ * Writers keep appending plain rows to `<audit>.audit_log`; one leader per namespace (named
11
+ * lock) chains every committed row per tenant into the append-only `<audit>.audit_chain`.
12
+ * A tick:
13
+ * 1. checks out ONE pool connection and takes `GET_LOCK('<ns>:audit_sealer', 0)`; not
14
+ * acquired ⇒ return the connection, no work;
15
+ * 2. window start `W = MIN(MAX(ac_occurred_at_ms), db now) − 15 min` from the chain alone
16
+ * (`idx_ac_occurred`); empty chain ⇒ no bound. The first tick of a leader and then one
17
+ * tick per hour is a sweep: no bound, the stuck set and the logged-once memory clear.
18
+ * Backlog cursor: when the batch / page / tenant caps leave rows behind, the oldest such
19
+ * `(al_occurred_at_ms, al_uuid)` (stuck tenants excluded) is kept in memory and the next
20
+ * tick starts its keyset there instead of at `W` when it is older; cleared when a tick
21
+ * leaves nothing behind, on lock loss and at each sweep (which may set it again);
22
+ * 3./4. reads candidates in keyset pages on `idx_audit_time` (1000 rows, ≤ 10 pages, stops at
23
+ * 200 tenants), anti-joined into `uq_ac_al_uuid`, excluding parked tenants and tenants
24
+ * that already hold `batch` rows this tick; groups them by exact tenant string in Node;
25
+ * reads each tenant's head (chain PK, else latest checkpoint, else genesis);
26
+ * 5. links each tenant's rows with core `auditEventFromRow` + `linkEntry` and inserts them
27
+ * in ONE transaction per tenant on the same connection. A failed batch rolls back alone;
28
+ * a repeating error (duplicate key 1062, `AuditRowError`, `AuditValidationError`) parks
29
+ * the tenant until the next sweep (≤ 200 parked), anything else retries next tick;
30
+ * 6. `finally`: `RELEASE_LOCK`, then return the connection; if the release fails (or the
31
+ * lock state is unknown) destroy the connection instead, so a pooled connection never
32
+ * carries the lock.
33
+ *
34
+ * Every `audit_log` ⇄ chain comparison and every tenant filter on `audit_log` carries
35
+ * `COLLATE utf8mb4_nopad_bin` on the `audit_log` side (exact bytes, NO PAD; `audit_log` is
36
+ * `utf8mb4_unicode_ci`, a bare comparison raises error 1267). Database names come from
37
+ * `FrameworkSchemas`, never a literal. Logs carry fixed text, tenant and sequence only:
38
+ * never `err.message` and never row content (SPEC §11).
39
+ */
40
+ const node_crypto_1 = require("node:crypto");
41
+ const common_1 = require("@nestjs/common");
42
+ const core_1 = require("@astram/core");
43
+ /** Lookback for the incremental scan (SPEC §10). */
44
+ exports.AUDIT_SEAL_LOOKBACK_MS = 15 * 60_000;
45
+ /** Full sweep and table re-probe period (SPEC §10). */
46
+ exports.AUDIT_SEAL_SWEEP_MS = 60 * 60_000;
47
+ /** Candidate rows per keyset page (SPEC §10). */
48
+ exports.AUDIT_SEAL_PAGE = 1000;
49
+ /** Keyset pages per tick (SPEC §10). */
50
+ exports.AUDIT_SEAL_PAGES = 10;
51
+ /** Tenants sealed per tick (SPEC §10). */
52
+ exports.AUDIT_SEAL_TENANTS = 200;
53
+ /** Parked (stuck) tenants held at most (SPEC §10). */
54
+ exports.AUDIT_SEAL_STUCK = 200;
55
+ /** MariaDB's limit on a `GET_LOCK` name. */
56
+ const MAX_LOCK_NAME = 64;
57
+ /** MariaDB duplicate-key error number. */
58
+ const ER_DUP_ENTRY = 1062;
59
+ /** A BIGINT column value as a `bigint` for ordering; `undefined` when it is not an integer. */
60
+ function ordinalOf(value) {
61
+ try {
62
+ if (typeof value === 'bigint')
63
+ return value;
64
+ if (typeof value === 'number' && Number.isInteger(value))
65
+ return BigInt(value);
66
+ if (typeof value === 'string' && /^-?\d+$/.test(value.trim()))
67
+ return BigInt(value.trim());
68
+ }
69
+ catch {
70
+ // fall through
71
+ }
72
+ return undefined;
73
+ }
74
+ /** Thrown inside a tick when the connection can no longer be trusted (rollback failed). */
75
+ class ConnectionUnusable extends Error {
76
+ constructor() {
77
+ super('audit_sealer_connection_unusable');
78
+ this.name = 'ConnectionUnusable';
79
+ }
80
+ }
81
+ const DRIVER_CODE = /^[A-Z][A-Z0-9_]{0,63}$/;
82
+ /**
83
+ * A log-safe class for an error: a typed core reason, a driver error code / errno, or
84
+ * `unexpected`. Never the message (it can echo row content, e.g. a duplicate-key value).
85
+ */
86
+ function auditSealErrorClass(err) {
87
+ if (err instanceof core_1.AuditRowError)
88
+ return `row_rejected:${err.reason}`;
89
+ if (err instanceof core_1.AuditValidationError)
90
+ return `event_rejected:${err.reason}`;
91
+ if (typeof err === 'object' && err !== null) {
92
+ const { code, errno } = err;
93
+ if (typeof code === 'string' && DRIVER_CODE.test(code))
94
+ return `db_error:${code}`;
95
+ if (typeof errno === 'number' && Number.isSafeInteger(errno))
96
+ return `db_error:${errno}`;
97
+ }
98
+ return 'unexpected';
99
+ }
100
+ /** Errors that repeat on every retry of the same rows, so the tenant is parked (SPEC §5.G.3). */
101
+ function isRepeating(err) {
102
+ if (err instanceof core_1.AuditRowError || err instanceof core_1.AuditValidationError)
103
+ return true;
104
+ return (typeof err === 'object' && err !== null && err.errno === ER_DUP_ENTRY);
105
+ }
106
+ /** The leader lock name: `<ns>:audit_sealer` (single layout: `<db>:audit_sealer`), ≤ 64 chars. */
107
+ function auditSealerLockName(schemas) {
108
+ const name = `${schemas.namespace}:audit_sealer`;
109
+ if (name.length <= MAX_LOCK_NAME)
110
+ return name;
111
+ // A 55-char namespace or 64-char single-layout database exceeds MariaDB's limit: keep the
112
+ // name unique per namespace with a digest (same approach as the migrate ledger lock).
113
+ const digest = (0, node_crypto_1.createHash)('sha256').update(schemas.namespace, 'utf8').digest('hex');
114
+ return `audit_sealer:${digest.slice(0, MAX_LOCK_NAME - 'audit_sealer:'.length)}`;
115
+ }
116
+ const quoteId = (name) => `\`${name.replace(/`/g, '``')}\``;
117
+ /** `?, ?, …` for `n` parameters. */
118
+ const placeholders = (n) => Array.from({ length: n }, () => '?').join(', ');
119
+ class AuditChainSealer {
120
+ pool;
121
+ hash;
122
+ batch;
123
+ clock;
124
+ logger;
125
+ lockName;
126
+ logTable;
127
+ chainTable;
128
+ checkpointTable;
129
+ /** Sealer clock at the start of the last sweep; `undefined` ⇒ the next leader tick sweeps. */
130
+ lastSweepAtMs;
131
+ /** Tenants parked until the next sweep (exact strings). */
132
+ stuck = new Set();
133
+ /** `(reason, tenant)` keys already logged this sweep. */
134
+ logged = new Set();
135
+ /** The stuck-set overflow was logged this sweep. */
136
+ overflowLogged = false;
137
+ /**
138
+ * Backlog cursor (SPEC §5.G step 2): the oldest position the last tick left unsealed because
139
+ * of the batch / page / tenant caps (stuck tenants excluded). Leader memory only.
140
+ */
141
+ cursor;
142
+ constructor(options) {
143
+ this.pool = options.pool;
144
+ this.hash = options.hash;
145
+ this.batch = options.batch;
146
+ this.clock = options.clock ?? { nowMs: () => Date.now() };
147
+ this.logger = options.logger ?? new common_1.Logger('Shield');
148
+ this.lockName = auditSealerLockName(options.schemas);
149
+ const db = quoteId(options.schemas.audit);
150
+ this.logTable = `${db}.${quoteId('audit_log')}`;
151
+ this.chainTable = `${db}.${quoteId('audit_chain')}`;
152
+ this.checkpointTable = `${db}.${quoteId('audit_chain_checkpoint')}`;
153
+ }
154
+ /**
155
+ * One tick. Always releases the lock and returns (or destroys) the connection before it
156
+ * settles. A tenant batch error never rejects; an unexpected error (e.g. the candidate read
157
+ * failing) rejects after the cleanup.
158
+ */
159
+ async tick() {
160
+ const conn = await this.pool.getConnection();
161
+ /** GET_LOCK's answer; stays `undefined` (outcome unknown) when the statement fails. */
162
+ let locked;
163
+ let destroy = false;
164
+ try {
165
+ const rows = (await conn.query('SELECT GET_LOCK(?, 0) AS acquired', [
166
+ this.lockName,
167
+ ]));
168
+ const acquired = rows[0]?.acquired;
169
+ locked = acquired !== null && acquired !== undefined && Number(acquired) === 1;
170
+ if (!locked) {
171
+ // Another instance leads. If this one leads again later it is a new leader: it sweeps
172
+ // first and starts with an empty stuck set.
173
+ this.forgetLeadership();
174
+ return;
175
+ }
176
+ await this.sealOnce(conn);
177
+ }
178
+ catch (err) {
179
+ if (!(err instanceof ConnectionUnusable))
180
+ throw err;
181
+ destroy = true;
182
+ }
183
+ finally {
184
+ if (locked === undefined) {
185
+ // GET_LOCK failed: whether the session holds the lock is unknown, so never pool it.
186
+ destroy = true;
187
+ }
188
+ else if (locked) {
189
+ try {
190
+ await conn.query('SELECT RELEASE_LOCK(?) AS released', [this.lockName]);
191
+ }
192
+ catch {
193
+ destroy = true;
194
+ }
195
+ }
196
+ if (destroy) {
197
+ try {
198
+ conn.destroy();
199
+ }
200
+ catch {
201
+ // The socket is closed either way; the server drops the session's lock with it.
202
+ }
203
+ }
204
+ else {
205
+ try {
206
+ await conn.release();
207
+ }
208
+ catch {
209
+ // Returning a connection to a closing pool is non-fatal.
210
+ }
211
+ }
212
+ }
213
+ }
214
+ forgetLeadership() {
215
+ this.lastSweepAtMs = undefined;
216
+ this.stuck.clear();
217
+ this.logged.clear();
218
+ this.overflowLogged = false;
219
+ this.cursor = undefined;
220
+ }
221
+ async sealOnce(conn) {
222
+ const now = this.clock.nowMs();
223
+ const sweep = this.lastSweepAtMs === undefined || now - this.lastSweepAtMs >= exports.AUDIT_SEAL_SWEEP_MS;
224
+ if (sweep) {
225
+ this.lastSweepAtMs = now;
226
+ this.stuck.clear();
227
+ this.logged.clear();
228
+ this.overflowLogged = false;
229
+ // The sweep scans from the start; it sets the cursor again if it leaves rows behind.
230
+ this.cursor = undefined;
231
+ }
232
+ const windowStart = sweep ? undefined : await this.readWindowStart(conn);
233
+ // Start from the backlog cursor instead of W when it is older (SPEC §5.G step 2).
234
+ const start = windowStart !== undefined &&
235
+ this.cursor !== undefined &&
236
+ this.cursor.ordinal <= BigInt(windowStart)
237
+ ? this.cursor
238
+ : undefined;
239
+ const read = await this.readCandidates(conn, start === undefined ? windowStart : undefined, start);
240
+ // Rows left behind by the caps, before sealing: if the connection dies mid-tick, the next
241
+ // tick still resumes from here.
242
+ this.cursor = this.oldestLeftBehind(read);
243
+ const { groups } = read;
244
+ if (groups.size === 0)
245
+ return;
246
+ const heads = await this.readHeads(conn, [...groups.keys()]);
247
+ for (const [tenant, rows] of groups) {
248
+ await this.sealTenant(conn, tenant, rows, heads.get(tenant));
249
+ }
250
+ // Tenants parked during this tick wait for the sweep: their rows do not hold the cursor.
251
+ this.cursor = this.oldestLeftBehind(read);
252
+ }
253
+ /** The oldest position the caps left behind, skipping stuck tenants; `undefined` ⇒ none. */
254
+ oldestLeftBehind(read) {
255
+ let oldest = read.tail;
256
+ for (const [tenant, position] of read.leftBehind) {
257
+ if (this.stuck.has(tenant))
258
+ continue;
259
+ if (oldest === undefined || read.order.get(position) < read.order.get(oldest)) {
260
+ oldest = position;
261
+ }
262
+ }
263
+ return oldest;
264
+ }
265
+ /** `MIN(MAX(ac_occurred_at_ms), db now) − lookback`, or `undefined` for an empty chain. */
266
+ async readWindowStart(conn) {
267
+ const rows = (await conn.query(`SELECT MAX(ac_occurred_at_ms) AS max_ms,
268
+ CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS SIGNED) AS now_ms
269
+ FROM ${this.chainTable}`));
270
+ const row = rows[0];
271
+ if (row?.max_ms === null || row?.max_ms === undefined)
272
+ return undefined;
273
+ const max = (0, core_1.toSafeInteger)(row.max_ms);
274
+ const dbNow = row.now_ms === null || row.now_ms === undefined ? max : (0, core_1.toSafeInteger)(row.now_ms);
275
+ return Math.min(max, dbNow) - exports.AUDIT_SEAL_LOOKBACK_MS;
276
+ }
277
+ /**
278
+ * Keyset-paged candidate read, grouped by exact tenant string, ≤ `batch` rows per tenant and
279
+ * ≤ 200 tenants (first appearance in keyset order; the rest wait for the next tick). The
280
+ * first page starts after `windowStart`, or at the backlog cursor `start` when given. Also
281
+ * returns what the caps left behind, for the backlog cursor.
282
+ */
283
+ async readCandidates(conn, windowStart, start) {
284
+ const groups = new Map();
285
+ const full = new Set();
286
+ const leftBehind = new Map();
287
+ const order = new Map();
288
+ /** Per full tenant, the read-order index of its last taken row. */
289
+ const lastTaken = new Map();
290
+ let after = start;
291
+ let index = 0;
292
+ let lastRow;
293
+ let exhausted = false;
294
+ const positionOf = (row, inclusive) => {
295
+ const ordinal = ordinalOf(row.al_occurred_at_ms);
296
+ if (ordinal === undefined)
297
+ return undefined;
298
+ return { occurred: row.al_occurred_at_ms, ordinal, uuid: row.al_uuid, inclusive };
299
+ };
300
+ for (let page = 0; page < exports.AUDIT_SEAL_PAGES; page++) {
301
+ const where = [];
302
+ const params = [];
303
+ if (windowStart !== undefined) {
304
+ where.push('al_occurred_at_ms > ?');
305
+ params.push(windowStart);
306
+ }
307
+ if (after !== undefined) {
308
+ // The cursor row itself is still unsealed: an inclusive start keeps it (`>=`).
309
+ where.push(`(al_occurred_at_ms > ? OR (al_occurred_at_ms = ? AND al_uuid ${after.inclusive ? '>=' : '>'} ?))`);
310
+ params.push(after.occurred, after.occurred, after.uuid);
311
+ }
312
+ const excluded = [...this.stuck, ...full];
313
+ if (excluded.length > 0) {
314
+ where.push(`al_tenant_id COLLATE utf8mb4_nopad_bin NOT IN (${placeholders(excluded.length)})`);
315
+ params.push(...excluded);
316
+ }
317
+ where.push(`NOT EXISTS (SELECT 1 FROM ${this.chainTable}
318
+ WHERE ac_al_uuid = al_uuid COLLATE utf8mb4_nopad_bin)`);
319
+ params.push(exports.AUDIT_SEAL_PAGE);
320
+ const rows = (await conn.query(`SELECT al_uuid, al_type, al_occurred_at_ms, al_actor_id, al_tenant_id, al_action_key,
321
+ al_resource_json, al_decision_json, al_attributes_json
322
+ FROM ${this.logTable}
323
+ WHERE ${where.join('\n AND ')}
324
+ ORDER BY al_occurred_at_ms, al_uuid
325
+ LIMIT ?`, params));
326
+ for (const row of rows) {
327
+ const i = index++;
328
+ const tenant = row.al_tenant_id;
329
+ if (this.stuck.has(tenant))
330
+ continue;
331
+ let group = groups.get(tenant);
332
+ let skip = false;
333
+ if (group === undefined) {
334
+ if (groups.size >= exports.AUDIT_SEAL_TENANTS) {
335
+ skip = true; // tenant cap
336
+ }
337
+ else {
338
+ group = [];
339
+ groups.set(tenant, group);
340
+ }
341
+ }
342
+ else if (group.length >= this.batch) {
343
+ skip = true; // batch cap
344
+ }
345
+ if (skip || group === undefined) {
346
+ if (!leftBehind.has(tenant)) {
347
+ const position = positionOf(row, true);
348
+ if (position !== undefined) {
349
+ leftBehind.set(tenant, position);
350
+ order.set(position, i);
351
+ }
352
+ }
353
+ continue;
354
+ }
355
+ group.push(row);
356
+ if (group.length >= this.batch) {
357
+ full.add(tenant);
358
+ lastTaken.set(tenant, { index: i, row });
359
+ }
360
+ }
361
+ lastRow = rows[rows.length - 1] ?? lastRow;
362
+ if (rows.length < exports.AUDIT_SEAL_PAGE) {
363
+ exhausted = true;
364
+ break;
365
+ }
366
+ if (groups.size >= exports.AUDIT_SEAL_TENANTS)
367
+ break;
368
+ const last = rows[rows.length - 1];
369
+ after = { occurred: last.al_occurred_at_ms, uuid: last.al_uuid, inclusive: false };
370
+ }
371
+ // A full tenant's later rows were excluded from the following pages (NOT IN), so it may
372
+ // have rows left even when none was seen: resume strictly after its last taken row.
373
+ for (const [tenant, taken] of lastTaken) {
374
+ if (leftBehind.has(tenant))
375
+ continue;
376
+ const position = positionOf(taken.row, false);
377
+ if (position === undefined)
378
+ continue;
379
+ leftBehind.set(tenant, position);
380
+ // Strictly after row `index`: before any row read later, after the row itself.
381
+ order.set(position, taken.index + 0.5);
382
+ }
383
+ // Paging stopped on `pages` or `tenants` with a full last page: the rest was never read.
384
+ let tail;
385
+ if (!exhausted && lastRow !== undefined) {
386
+ tail = positionOf(lastRow, false);
387
+ if (tail !== undefined)
388
+ order.set(tail, index - 1 + 0.5);
389
+ }
390
+ return { groups, leftBehind, tail, order };
391
+ }
392
+ /** Each tenant's head: the `MAX(ac_sequence)` chain row, else its latest checkpoint. */
393
+ async readHeads(conn, tenants) {
394
+ const heads = new Map();
395
+ const chainRows = (await conn.query(`SELECT ac.ac_tenant_id, ac.ac_sequence, ac.ac_entry_hash,
396
+ CASE WHEN al.al_uuid IS NULL THEN 1 ELSE 0 END AS al_missing
397
+ FROM (SELECT ac_tenant_id AS head_tenant, MAX(ac_sequence) AS head_sequence
398
+ FROM ${this.chainTable}
399
+ WHERE ac_tenant_id IN (${placeholders(tenants.length)})
400
+ GROUP BY ac_tenant_id) h
401
+ JOIN ${this.chainTable} ac
402
+ ON ac.ac_tenant_id = h.head_tenant AND ac.ac_sequence = h.head_sequence
403
+ LEFT JOIN ${this.logTable} al
404
+ ON al.al_uuid = ac.ac_al_uuid COLLATE utf8mb4_unicode_ci
405
+ AND al.al_uuid COLLATE utf8mb4_nopad_bin = ac.ac_al_uuid`, tenants));
406
+ for (const r of chainRows) {
407
+ heads.set(r.ac_tenant_id, {
408
+ anchor: {
409
+ tenantId: r.ac_tenant_id,
410
+ sequenceNumber: (0, core_1.toSafeInteger)(r.ac_sequence),
411
+ entryHash: r.ac_entry_hash,
412
+ },
413
+ missingRow: Number(r.al_missing) === 1,
414
+ });
415
+ }
416
+ const rest = tenants.filter((t) => !heads.has(t));
417
+ if (rest.length === 0)
418
+ return heads;
419
+ const checkpoints = (await conn.query(`SELECT c.acc_tenant_id, c.acc_sequence, c.acc_entry_hash
420
+ FROM (SELECT acc_tenant_id AS cp_tenant, MAX(acc_sequence) AS cp_sequence
421
+ FROM ${this.checkpointTable}
422
+ WHERE acc_tenant_id IN (${placeholders(rest.length)})
423
+ GROUP BY acc_tenant_id) m
424
+ JOIN ${this.checkpointTable} c
425
+ ON c.acc_tenant_id = m.cp_tenant AND c.acc_sequence = m.cp_sequence`, rest));
426
+ for (const r of checkpoints) {
427
+ heads.set(r.acc_tenant_id, {
428
+ anchor: {
429
+ tenantId: r.acc_tenant_id,
430
+ sequenceNumber: (0, core_1.toSafeInteger)(r.acc_sequence),
431
+ entryHash: r.acc_entry_hash,
432
+ },
433
+ missingRow: false,
434
+ });
435
+ }
436
+ return heads;
437
+ }
438
+ /** Link and insert one tenant's batch in one transaction; errors stay with this tenant. */
439
+ async sealTenant(conn, tenant, rows, head) {
440
+ if (head?.missingRow) {
441
+ // Keep sealing from the head's hash; audit:verify keeps reporting missing_row there.
442
+ this.logOnce('head_missing_row', tenant, `audit sealer: tenant ${JSON.stringify(tenant)} head at sequence ` +
443
+ `${head.anchor.sequenceNumber} has no audit_log row; sealing continues from its hash`);
444
+ }
445
+ let prev = head?.anchor;
446
+ /** The sequence being linked or inserted; a failure is reported at it. */
447
+ let sequence = prev !== undefined ? prev.sequenceNumber + 1 : 0;
448
+ let inTransaction = false;
449
+ try {
450
+ const links = rows.map((row) => {
451
+ sequence = prev !== undefined ? prev.sequenceNumber + 1 : 0;
452
+ const event = (0, core_1.auditEventFromRow)(row);
453
+ const link = (0, core_1.linkEntry)(event, prev, this.hash);
454
+ prev = { tenantId: tenant, sequenceNumber: link.sequenceNumber, entryHash: link.entryHash };
455
+ return { row, link, occurredAtMs: event.occurredAtMs };
456
+ });
457
+ const sealedAtMs = this.clock.nowMs();
458
+ await conn.beginTransaction();
459
+ inTransaction = true;
460
+ for (const { row, link, occurredAtMs } of links) {
461
+ sequence = link.sequenceNumber;
462
+ await conn.query(`INSERT INTO ${this.chainTable}
463
+ (ac_tenant_id, ac_sequence, ac_al_uuid, ac_content_hash, ac_prev_hash,
464
+ ac_entry_hash, ac_occurred_at_ms, ac_sealed_at_ms)
465
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
466
+ tenant,
467
+ link.sequenceNumber,
468
+ row.al_uuid,
469
+ link.contentHash,
470
+ link.prevHash,
471
+ link.entryHash,
472
+ occurredAtMs,
473
+ sealedAtMs,
474
+ ]);
475
+ }
476
+ await conn.commit();
477
+ inTransaction = false;
478
+ }
479
+ catch (err) {
480
+ let rollbackFailed = false;
481
+ if (inTransaction) {
482
+ try {
483
+ await conn.rollback();
484
+ }
485
+ catch {
486
+ rollbackFailed = true;
487
+ }
488
+ }
489
+ this.recordFailure(tenant, sequence, err);
490
+ if (rollbackFailed)
491
+ throw new ConnectionUnusable();
492
+ }
493
+ }
494
+ /** Log a failed batch (once per tenant and reason per sweep) and park it if it repeats. */
495
+ recordFailure(tenant, sequence, err) {
496
+ const reason = auditSealErrorClass(err);
497
+ const repeating = isRepeating(err);
498
+ if (repeating && this.stuck.size >= exports.AUDIT_SEAL_STUCK && !this.stuck.has(tenant)) {
499
+ // Systemic failure: one count-only line per sweep, no per-tenant log, retried each tick.
500
+ if (!this.overflowLogged) {
501
+ this.overflowLogged = true;
502
+ this.logger.error(`audit sealer: ${exports.AUDIT_SEAL_STUCK} tenants are parked (the maximum); further ` +
503
+ 'tenants failing with a repeating error are retried every tick until the next sweep');
504
+ }
505
+ return;
506
+ }
507
+ if (repeating)
508
+ this.stuck.add(tenant);
509
+ this.logOnce(reason, tenant, `audit sealer: tenant ${JSON.stringify(tenant)} batch rolled back at sequence ${sequence} ` +
510
+ `(${reason}); ${repeating ? 'parked until the next sweep' : 'retried next tick'}`);
511
+ }
512
+ logOnce(reason, tenant, message) {
513
+ const key = `${reason}\u0000${tenant}`;
514
+ if (this.logged.has(key))
515
+ return;
516
+ this.logged.add(key);
517
+ this.logger.error(message);
518
+ }
519
+ }
520
+ exports.AuditChainSealer = AuditChainSealer;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `AUDIT_SEAL_*` env loader for the deferred audit sealer (audit-chain-store SPEC §5.I, §10,
3
+ * §13). Read once at boot by the persistence wiring; an invalid value fails the boot with the
4
+ * variable name and its bounds, never the value (it might be a misplaced secret). Unset, empty
5
+ * or whitespace-only ⇒ the default (as `loadMessageConfig`).
6
+ */
7
+ /** The two tunables of the sealer (the sealer itself cannot be switched off, SPEC §11). */
8
+ export interface AuditSealConfig {
9
+ /** Milliseconds between the end of one tick and the start of the next. */
10
+ readonly intervalMs: number;
11
+ /** Rows sealed per tenant per tick. */
12
+ readonly batch: number;
13
+ }
14
+ export declare const DEFAULT_AUDIT_SEAL_INTERVAL_MS = 2000;
15
+ export declare const MIN_AUDIT_SEAL_INTERVAL_MS = 500;
16
+ export declare const MAX_AUDIT_SEAL_INTERVAL_MS = 60000;
17
+ export declare const DEFAULT_AUDIT_SEAL_BATCH = 500;
18
+ export declare const MIN_AUDIT_SEAL_BATCH = 1;
19
+ export declare const MAX_AUDIT_SEAL_BATCH = 5000;
20
+ /** Load and validate `AUDIT_SEAL_INTERVAL_MS` and `AUDIT_SEAL_BATCH`. Throws on an invalid value. */
21
+ export declare function loadAuditSealConfig(env: NodeJS.ProcessEnv): AuditSealConfig;
22
+ //# sourceMappingURL=audit-seal-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-seal-config.d.ts","sourceRoot":"","sources":["../../src/audit/audit-seal-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,0BAA0B,QAAS,CAAC;AACjD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAqBzC,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,eAAe,CAiB3E"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * `AUDIT_SEAL_*` env loader for the deferred audit sealer (audit-chain-store SPEC §5.I, §10,
4
+ * §13). Read once at boot by the persistence wiring; an invalid value fails the boot with the
5
+ * variable name and its bounds, never the value (it might be a misplaced secret). Unset, empty
6
+ * or whitespace-only ⇒ the default (as `loadMessageConfig`).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.MAX_AUDIT_SEAL_BATCH = exports.MIN_AUDIT_SEAL_BATCH = exports.DEFAULT_AUDIT_SEAL_BATCH = exports.MAX_AUDIT_SEAL_INTERVAL_MS = exports.MIN_AUDIT_SEAL_INTERVAL_MS = exports.DEFAULT_AUDIT_SEAL_INTERVAL_MS = void 0;
10
+ exports.loadAuditSealConfig = loadAuditSealConfig;
11
+ exports.DEFAULT_AUDIT_SEAL_INTERVAL_MS = 2000;
12
+ exports.MIN_AUDIT_SEAL_INTERVAL_MS = 500;
13
+ exports.MAX_AUDIT_SEAL_INTERVAL_MS = 60_000;
14
+ exports.DEFAULT_AUDIT_SEAL_BATCH = 500;
15
+ exports.MIN_AUDIT_SEAL_BATCH = 1;
16
+ exports.MAX_AUDIT_SEAL_BATCH = 5000;
17
+ /** Plain decimal digits only (no sign, exponent, radix prefix or fraction). */
18
+ const PLAIN_DIGITS = /^[0-9]{1,10}$/;
19
+ function boundedInteger(env, name, fallback, min, max) {
20
+ const value = env[name]?.trim();
21
+ if (!value)
22
+ return fallback;
23
+ const n = PLAIN_DIGITS.test(value) ? Number(value) : NaN;
24
+ if (!Number.isSafeInteger(n) || n < min || n > max) {
25
+ throw new Error(`${name} must be a whole number between ${min} and ${max}`);
26
+ }
27
+ return n;
28
+ }
29
+ /** Load and validate `AUDIT_SEAL_INTERVAL_MS` and `AUDIT_SEAL_BATCH`. Throws on an invalid value. */
30
+ function loadAuditSealConfig(env) {
31
+ return {
32
+ intervalMs: boundedInteger(env, 'AUDIT_SEAL_INTERVAL_MS', exports.DEFAULT_AUDIT_SEAL_INTERVAL_MS, exports.MIN_AUDIT_SEAL_INTERVAL_MS, exports.MAX_AUDIT_SEAL_INTERVAL_MS),
33
+ batch: boundedInteger(env, 'AUDIT_SEAL_BATCH', exports.DEFAULT_AUDIT_SEAL_BATCH, exports.MIN_AUDIT_SEAL_BATCH, exports.MAX_AUDIT_SEAL_BATCH),
34
+ };
35
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `AuditSealerService` — runs the deferred audit sealer in the app (audit-chain-store SPEC
3
+ * §5.H, §11, §12).
4
+ *
5
+ * Registered by `PersistenceModule.forRoot()`, so it exists exactly when `SHIELD_DB_POOL`
6
+ * does (`ShieldModule.forRoot({ auth: true })`); an enforcement-only app has neither. There
7
+ * is no switch to turn it off: only `AUDIT_SEAL_INTERVAL_MS` / `AUDIT_SEAL_BATCH` tune it.
8
+ *
9
+ * - Boot (`onApplicationBootstrap`): one bounded table probe on `information_schema.TABLES`;
10
+ * both `audit_log` and `audit_chain` present ⇒ tick every interval. `audit_log` absent
11
+ * (auth without identity), or the probe failing (DB down, a test's stub pool) ⇒ one warning,
12
+ * idle; `audit_chain` absent (migration 102 not applied) ⇒ one error per probe, idle. The
13
+ * probe repeats every sweep period (1 h) and starts or stops the ticks. Never throws.
14
+ * - Ticks never overlap: the next one is scheduled only after the previous settled. A
15
+ * rejected tick is logged by error class only and the loop continues.
16
+ * - Shutdown (`onModuleDestroy`, which runs before the pool is ended, and
17
+ * `onApplicationShutdown`): stops the timers and waits for an in-flight tick. Idempotent.
18
+ * - Every timer is `unref()`'d: the sealer never keeps a process alive. Nothing here touches
19
+ * the request path (Shield #12).
20
+ */
21
+ import { type OnApplicationBootstrap, type OnApplicationShutdown, type OnModuleDestroy } from '@nestjs/common';
22
+ import type { Pool } from 'mariadb';
23
+ import type { FrameworkSchemas } from '@astram/core';
24
+ import { type AuditSealConfig } from './audit-seal-config.js';
25
+ import { type AuditSealerLogger } from './audit-chain-sealer.js';
26
+ export interface AuditSealerServiceOptions {
27
+ /** Default `loadAuditSealConfig(process.env)`. */
28
+ readonly config?: AuditSealConfig;
29
+ /** Default `new Logger('Shield')`. */
30
+ readonly logger?: AuditSealerLogger;
31
+ /** Test seam; default an {@link AuditChainSealer} over the pool with {@link NodeSha256Hash}. */
32
+ readonly sealer?: {
33
+ tick(): Promise<unknown>;
34
+ };
35
+ }
36
+ export declare class AuditSealerService implements OnApplicationBootstrap, OnModuleDestroy, OnApplicationShutdown {
37
+ private readonly pool;
38
+ private readonly schemas;
39
+ private readonly config;
40
+ private readonly logger;
41
+ private readonly sealer;
42
+ private stopped;
43
+ private active;
44
+ private lastOutcome;
45
+ private tickTimer;
46
+ private probeTimer;
47
+ private inFlightTick;
48
+ private inFlightProbe;
49
+ constructor(pool: Pool, schemas: FrameworkSchemas, options?: AuditSealerServiceOptions);
50
+ onApplicationBootstrap(): Promise<void>;
51
+ onModuleDestroy(): Promise<void>;
52
+ onApplicationShutdown(): Promise<void>;
53
+ private stop;
54
+ /** Probe once (never overlapping, never throwing) and start or stop the ticks. */
55
+ private runProbe;
56
+ private probe;
57
+ /** Warnings once per change of state; the missing-chain error on every probe (SPEC §12). */
58
+ private report;
59
+ private schedule;
60
+ private runTick;
61
+ }
62
+ //# sourceMappingURL=audit-sealer.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-sealer.service.d.ts","sourceRoot":"","sources":["../../src/audit/audit-sealer.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,yBAAyB;IACxC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,sCAAsC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,gGAAgG;IAChG,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;CAChD;AAID,qBACa,kBACX,YAAW,sBAAsB,EAAE,eAAe,EAAE,qBAAqB;IAevE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IAEtD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,aAAa,CAA4B;gBAG9B,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,gBAAgB,EAC1C,OAAO,GAAE,yBAA8B;IAenC,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAIhC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;YAI9B,IAAI;IAWlB,kFAAkF;IAClF,OAAO,CAAC,QAAQ;YAwBF,KAAK;IA4BnB,4FAA4F;IAC5F,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,OAAO;CAehB"}
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AuditSealerService = void 0;
13
+ /**
14
+ * `AuditSealerService` — runs the deferred audit sealer in the app (audit-chain-store SPEC
15
+ * §5.H, §11, §12).
16
+ *
17
+ * Registered by `PersistenceModule.forRoot()`, so it exists exactly when `SHIELD_DB_POOL`
18
+ * does (`ShieldModule.forRoot({ auth: true })`); an enforcement-only app has neither. There
19
+ * is no switch to turn it off: only `AUDIT_SEAL_INTERVAL_MS` / `AUDIT_SEAL_BATCH` tune it.
20
+ *
21
+ * - Boot (`onApplicationBootstrap`): one bounded table probe on `information_schema.TABLES`;
22
+ * both `audit_log` and `audit_chain` present ⇒ tick every interval. `audit_log` absent
23
+ * (auth without identity), or the probe failing (DB down, a test's stub pool) ⇒ one warning,
24
+ * idle; `audit_chain` absent (migration 102 not applied) ⇒ one error per probe, idle. The
25
+ * probe repeats every sweep period (1 h) and starts or stops the ticks. Never throws.
26
+ * - Ticks never overlap: the next one is scheduled only after the previous settled. A
27
+ * rejected tick is logged by error class only and the loop continues.
28
+ * - Shutdown (`onModuleDestroy`, which runs before the pool is ended, and
29
+ * `onApplicationShutdown`): stops the timers and waits for an in-flight tick. Idempotent.
30
+ * - Every timer is `unref()`'d: the sealer never keeps a process alive. Nothing here touches
31
+ * the request path (Shield #12).
32
+ */
33
+ const common_1 = require("@nestjs/common");
34
+ const audit_seal_config_js_1 = require("./audit-seal-config.js");
35
+ const audit_chain_sealer_js_1 = require("./audit-chain-sealer.js");
36
+ const node_sha256_hash_js_1 = require("./node-sha256-hash.js");
37
+ /** Upper bound on the boot/re-probe query, so a hung pool cannot stall the boot hook. */
38
+ const PROBE_TIMEOUT_MS = 10_000;
39
+ let AuditSealerService = class AuditSealerService {
40
+ pool;
41
+ schemas;
42
+ config;
43
+ logger;
44
+ sealer;
45
+ stopped = false;
46
+ active = false;
47
+ lastOutcome;
48
+ tickTimer;
49
+ probeTimer;
50
+ inFlightTick;
51
+ inFlightProbe;
52
+ constructor(pool, schemas, options = {}) {
53
+ this.pool = pool;
54
+ this.schemas = schemas;
55
+ this.config = options.config ?? (0, audit_seal_config_js_1.loadAuditSealConfig)(process.env);
56
+ this.logger = options.logger ?? new common_1.Logger('Shield');
57
+ this.sealer =
58
+ options.sealer ??
59
+ new audit_chain_sealer_js_1.AuditChainSealer({
60
+ pool,
61
+ schemas,
62
+ hash: new node_sha256_hash_js_1.NodeSha256Hash(),
63
+ batch: this.config.batch,
64
+ logger: this.logger,
65
+ });
66
+ }
67
+ async onApplicationBootstrap() {
68
+ if (this.stopped)
69
+ return;
70
+ await this.runProbe();
71
+ if (this.stopped || this.probeTimer !== undefined)
72
+ return;
73
+ this.probeTimer = setInterval(() => void this.runProbe(), audit_chain_sealer_js_1.AUDIT_SEAL_SWEEP_MS);
74
+ this.probeTimer.unref();
75
+ }
76
+ async onModuleDestroy() {
77
+ await this.stop();
78
+ }
79
+ async onApplicationShutdown() {
80
+ await this.stop();
81
+ }
82
+ async stop() {
83
+ this.stopped = true;
84
+ this.active = false;
85
+ if (this.tickTimer !== undefined)
86
+ clearTimeout(this.tickTimer);
87
+ if (this.probeTimer !== undefined)
88
+ clearInterval(this.probeTimer);
89
+ this.tickTimer = undefined;
90
+ this.probeTimer = undefined;
91
+ await this.inFlightProbe;
92
+ await this.inFlightTick;
93
+ }
94
+ /** Probe once (never overlapping, never throwing) and start or stop the ticks. */
95
+ runProbe() {
96
+ if (this.inFlightProbe !== undefined)
97
+ return this.inFlightProbe;
98
+ const run = (async () => {
99
+ const outcome = await this.probe();
100
+ if (this.stopped)
101
+ return;
102
+ this.report(outcome);
103
+ this.lastOutcome = outcome;
104
+ if (outcome === 'ready') {
105
+ if (!this.active) {
106
+ this.active = true;
107
+ this.schedule();
108
+ }
109
+ }
110
+ else {
111
+ this.active = false;
112
+ if (this.tickTimer !== undefined)
113
+ clearTimeout(this.tickTimer);
114
+ this.tickTimer = undefined;
115
+ }
116
+ })().finally(() => {
117
+ this.inFlightProbe = undefined;
118
+ });
119
+ this.inFlightProbe = run;
120
+ return run;
121
+ }
122
+ async probe() {
123
+ let timer;
124
+ try {
125
+ const timeout = new Promise((_, reject) => {
126
+ timer = setTimeout(() => reject(new Error('probe_timeout')), PROBE_TIMEOUT_MS);
127
+ timer.unref();
128
+ });
129
+ const query = Promise.resolve().then(() => this.pool.query(`SELECT TABLE_NAME FROM information_schema.TABLES
130
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN ('audit_log', 'audit_chain')`, [this.schemas.audit]));
131
+ query.catch(() => undefined); // a late rejection after the timeout is not unhandled
132
+ const rows = await Promise.race([query, timeout]);
133
+ if (!Array.isArray(rows))
134
+ return 'probe_failed';
135
+ const names = new Set(rows.map((r) => r.TABLE_NAME));
136
+ if (!names.has('audit_log'))
137
+ return 'no_audit_log';
138
+ if (!names.has('audit_chain'))
139
+ return 'no_audit_chain';
140
+ return 'ready';
141
+ }
142
+ catch {
143
+ return 'probe_failed';
144
+ }
145
+ finally {
146
+ if (timer !== undefined)
147
+ clearTimeout(timer);
148
+ }
149
+ }
150
+ /** Warnings once per change of state; the missing-chain error on every probe (SPEC §12). */
151
+ report(outcome) {
152
+ const db = this.schemas.audit;
153
+ if (outcome === 'no_audit_chain') {
154
+ this.logger.error(`audit sealer idle: ${db}.audit_chain does not exist (apply framework migration ` +
155
+ '1710000000102); audit_log rows stay unsealed until it does');
156
+ return;
157
+ }
158
+ if (outcome === this.lastOutcome)
159
+ return;
160
+ if (outcome === 'no_audit_log') {
161
+ this.logger.warn(`audit sealer idle: ${db}.audit_log does not exist (no identity audit store); ` +
162
+ 're-checked every hour');
163
+ }
164
+ else if (outcome === 'probe_failed') {
165
+ this.logger.warn('audit sealer idle: the audit table check could not run; re-checked every hour');
166
+ }
167
+ }
168
+ schedule() {
169
+ if (!this.active || this.stopped || this.tickTimer !== undefined)
170
+ return;
171
+ this.tickTimer = setTimeout(() => {
172
+ this.tickTimer = undefined;
173
+ this.runTick();
174
+ }, this.config.intervalMs);
175
+ this.tickTimer.unref();
176
+ }
177
+ runTick() {
178
+ if (!this.active || this.stopped || this.inFlightTick !== undefined)
179
+ return;
180
+ this.inFlightTick = (async () => {
181
+ try {
182
+ await this.sealer.tick();
183
+ }
184
+ catch (err) {
185
+ this.logger.error(`audit sealer: tick failed (${(0, audit_chain_sealer_js_1.auditSealErrorClass)(err)}); retried next interval`);
186
+ }
187
+ })().finally(() => {
188
+ this.inFlightTick = undefined;
189
+ this.schedule();
190
+ });
191
+ }
192
+ };
193
+ exports.AuditSealerService = AuditSealerService;
194
+ exports.AuditSealerService = AuditSealerService = __decorate([
195
+ (0, common_1.Injectable)(),
196
+ __metadata("design:paramtypes", [Object, Object, Object])
197
+ ], AuditSealerService);
@@ -0,0 +1,6 @@
1
+ import type { HashFunction } from '@astram/core';
2
+ export declare class NodeSha256Hash implements HashFunction {
3
+ /** Lower-case hex SHA-256 of the UTF-8 bytes of `input`. */
4
+ hash(input: string): string;
5
+ }
6
+ //# sourceMappingURL=node-sha256-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-sha256-hash.d.ts","sourceRoot":"","sources":["../../src/audit/node-sha256-hash.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,qBAAa,cAAe,YAAW,YAAY;IACjD,4DAA4D;IAC5D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAG5B"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeSha256Hash = void 0;
4
+ /**
5
+ * `HashFunction` adapter over `node:crypto` SHA-256 for the durable audit chain
6
+ * (audit-chain-store SPEC §5.F). Core is zero-dependency, so the hash is injected into
7
+ * `linkEntry` / `verifyChain`. Lower-case hex of the UTF-8 bytes; never logs its input.
8
+ */
9
+ const node_crypto_1 = require("node:crypto");
10
+ class NodeSha256Hash {
11
+ /** Lower-case hex SHA-256 of the UTF-8 bytes of `input`. */
12
+ hash(input) {
13
+ return (0, node_crypto_1.createHash)('sha256').update(input, 'utf8').digest('hex');
14
+ }
15
+ }
16
+ exports.NodeSha256Hash = NodeSha256Hash;
@@ -33,8 +33,8 @@
33
33
  import { type DynamicModule } from '@nestjs/common';
34
34
  /**
35
35
  * The persistence module. `forRoot()` returns the two connection providers (both
36
- * exported so AuthModule can inject them) plus the shutdown holder. Opt-in: only
37
- * imported when auth is wired.
36
+ * exported so AuthModule can inject them), the shutdown holder and the audit sealer.
37
+ * Opt-in: only imported when auth is wired.
38
38
  */
39
39
  export declare class PersistenceModule {
40
40
  static forRoot(): DynamicModule;
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.module.d.ts","sourceRoot":"","sources":["../../src/auth/persistence.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAU,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AA0C3E;;;;GAIG;AACH,qBACa,iBAAiB;IAC5B,MAAM,CAAC,OAAO,IAAI,aAAa;CAOhC"}
1
+ {"version":3,"file":"persistence.module.d.ts","sourceRoot":"","sources":["../../src/auth/persistence.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAU,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AA2D3E;;;;GAIG;AACH,qBACa,iBAAiB;IAC5B,MAAM,CAAC,OAAO,IAAI,aAAa;CAOhC"}
@@ -41,7 +41,10 @@ var PersistenceModule_1;
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.PersistenceModule = void 0;
43
43
  const common_1 = require("@nestjs/common");
44
+ const app_config_js_1 = require("../config/app-config.js");
44
45
  const config_module_js_1 = require("../config/config.module.js");
46
+ const audit_sealer_service_js_1 = require("../audit/audit-sealer.service.js");
47
+ const audit_seal_config_js_1 = require("../audit/audit-seal-config.js");
45
48
  const tokens_js_1 = require("./tokens.js");
46
49
  const connections_js_1 = require("./connections.js");
47
50
  /**
@@ -74,16 +77,29 @@ const lifecycleProvider = {
74
77
  useFactory: (pool, redis) => new connections_js_1.ConnectionLifecycle(pool, redis),
75
78
  inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_REDIS],
76
79
  };
80
+ /**
81
+ * The deferred audit sealer (audit-chain-store SPEC §5.H): registered here so it exists
82
+ * exactly when {@link SHIELD_DB_POOL} does. `AUDIT_SEAL_*` is read now, so an invalid value
83
+ * fails the boot (SPEC §13). It ticks only once its table probe finds `audit_log` and
84
+ * `audit_chain`; a stub or unreachable pool leaves it idle (never a boot failure).
85
+ */
86
+ const auditSealerProvider = {
87
+ provide: audit_sealer_service_js_1.AuditSealerService,
88
+ useFactory: (pool, config) => new audit_sealer_service_js_1.AuditSealerService(pool, (0, app_config_js_1.frameworkSchemasOf)(config), {
89
+ config: (0, audit_seal_config_js_1.loadAuditSealConfig)(process.env),
90
+ }),
91
+ inject: [tokens_js_1.SHIELD_DB_POOL, config_module_js_1.APP_CONFIG],
92
+ };
77
93
  /**
78
94
  * The persistence module. `forRoot()` returns the two connection providers (both
79
- * exported so AuthModule can inject them) plus the shutdown holder. Opt-in: only
80
- * imported when auth is wired.
95
+ * exported so AuthModule can inject them), the shutdown holder and the audit sealer.
96
+ * Opt-in: only imported when auth is wired.
81
97
  */
82
98
  let PersistenceModule = PersistenceModule_1 = class PersistenceModule {
83
99
  static forRoot() {
84
100
  return {
85
101
  module: PersistenceModule_1,
86
- providers: [dbPoolProvider, redisProvider, lifecycleProvider],
102
+ providers: [dbPoolProvider, redisProvider, lifecycleProvider, auditSealerProvider],
87
103
  exports: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_REDIS],
88
104
  };
89
105
  }
package/dist/index.d.ts CHANGED
@@ -40,6 +40,8 @@ export { EncryptedFileSecretsProvider } from './secrets/encrypted-file-secrets.j
40
40
  export { resolveBootSecrets, createSecretsProviderFromEnv, assertNoUnresolvedSecretRefs, wasResolvedFromSecretRef, type ResolveBootSecretsOptions, } from './secrets/boot-secrets.js';
41
41
  export { S3ObjectStore, type S3ObjectStoreConfig, type S3ObjectStoreOptions, } from './storage/s3-object-store.js';
42
42
  export { NodeSha256Digest } from './storage/node-sha256-digest.js';
43
+ export { NodeSha256Hash } from './audit/node-sha256-hash.js';
44
+ export { loadAuditSealConfig, type AuditSealConfig } from './audit/audit-seal-config.js';
43
45
  export { ObjectStoreBootCheck } from './storage/object-store-boot-check.js';
44
46
  export { FileIntake, FILE_INTAKE_REQUEST_KEY, FILE_INTAKE_DEFAULT_MAX_BYTES, type FileIntakeOptions, type FileIntakeResult, } from './storage/file-intake.interceptor.js';
45
47
  export type { ShieldStorageOption } from './storage/storage.providers.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,oBAAoB,EACpB,uBAAuB,EACvB,UAAU,EACV,SAAS,GACV,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,YAAY,EACZ,yBAAyB,EAGzB,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAI7B,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,MAAM,EACN,UAAU,EACV,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,2CAA2C,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAKhF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAKpG,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AACjG,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,eAAe,GAChB,MAAM,uCAAuC,CAAC;AAM/C,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,QAAQ,GACT,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAS/E,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,iBAAiB,GACvB,MAAM,qCAAqC,CAAC;AAU7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAKnF,OAAO,EACL,qBAAqB,EACrB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACrC,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAKlE,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAKlE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAInF,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,UAAU,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,GACpC,MAAM,wBAAwB,CAAC;AAOhC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,oBAAoB,EACpB,wBAAwB,EAExB,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,EAEZ,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAEzB,wBAAwB,EACxB,eAAe,EACf,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,kCAAkC,EAClC,uBAAuB,EACvB,KAAK,aAAa,GACnB,MAAM,wCAAwC,CAAC;AAShD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,aAAa,IAAI,oBAAoB,GAC3C,MAAM,iCAAiC,CAAC;AAUzC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,KAAK,kBAAkB,GACxB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,cAAc,GACpB,MAAM,yCAAyC,CAAC;AASjD,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,KAAK,cAAc,GACpB,MAAM,+CAA+C,CAAC;AAWvD,OAAO,EACL,sBAAsB,EACtB,gCAAgC,EAChC,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,sCAAsC,CAAC;AAQ9C,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,iDAAiD,CAAC;AAYzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EACL,0BAA0B,EAC1B,KAAK,+BAA+B,GACrC,MAAM,sCAAsC,CAAC;AAM9C,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,iCAAiC,EACjC,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,kCAAkC,GACxC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,GACzB,MAAM,wCAAwC,CAAC;AAOhD,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAI1F,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGpG,OAAO,EACL,6BAA6B,EAC7B,uBAAuB,EACvB,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,uBAAuB,GAC7B,MAAM,kCAAkC,CAAC;AAO1C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGxB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,wBAAwB,EAG7B,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,kBAAkB,EAEvB,eAAe,EACf,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAezD,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAIxB,wBAAwB,EACxB,wBAAwB,EAExB,+BAA+B,EAC/B,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EAEtB,iBAAiB,EAEjB,uBAAuB,EAEvB,6BAA6B,GAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAI5F,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,SAAS,GACV,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQ3E,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAS/F,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQ3F,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM9E,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,aAAa,GACd,MAAM,4BAA4B,CAAC;AAMpC,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,0CAA0C,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mDAAmD,CAAC;AAC/F,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,GACjB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG9E,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,+BAA+B,EAC/B,2BAA2B,EAC3B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,wBAAwB,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAK5E,YAAY,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAMlE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAOjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,mBAAmB,GACzB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAQ5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,oBAAoB,EACpB,uBAAuB,EACvB,UAAU,EACV,SAAS,GACV,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,YAAY,EACZ,yBAAyB,EAGzB,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAI7B,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,MAAM,EACN,UAAU,EACV,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,2CAA2C,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAKhF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAKpG,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AACjG,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,eAAe,GAChB,MAAM,uCAAuC,CAAC;AAM/C,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,YAAY,EACV,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,QAAQ,GACT,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAS/E,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,iBAAiB,GACvB,MAAM,qCAAqC,CAAC;AAU7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAKnF,OAAO,EACL,qBAAqB,EACrB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACrC,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAKlE,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAKlE,OAAO,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAInF,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAGnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EACL,UAAU,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,GACpC,MAAM,wBAAwB,CAAC;AAOhC,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,oBAAoB,EACpB,wBAAwB,EAExB,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,EAEZ,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAEzB,wBAAwB,EACxB,eAAe,EACf,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,kCAAkC,EAClC,uBAAuB,EACvB,KAAK,aAAa,GACnB,MAAM,wCAAwC,CAAC;AAShD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,aAAa,IAAI,oBAAoB,GAC3C,MAAM,iCAAiC,CAAC;AAUzC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,KAAK,kBAAkB,GACxB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,cAAc,GACpB,MAAM,yCAAyC,CAAC;AASjD,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EACL,8BAA8B,EAC9B,8BAA8B,EAC9B,KAAK,cAAc,GACpB,MAAM,+CAA+C,CAAC;AAWvD,OAAO,EACL,sBAAsB,EACtB,gCAAgC,EAChC,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,sCAAsC,CAAC;AAQ9C,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,iDAAiD,CAAC;AAYzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EACL,0BAA0B,EAC1B,KAAK,+BAA+B,GACrC,MAAM,sCAAsC,CAAC;AAM9C,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,iCAAiC,EACjC,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,kCAAkC,GACxC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,GACzB,MAAM,wCAAwC,CAAC;AAOhD,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAI1F,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAGpG,OAAO,EACL,6BAA6B,EAC7B,uBAAuB,EACvB,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,uBAAuB,GAC7B,MAAM,kCAAkC,CAAC;AAO1C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGxB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,wBAAwB,EAG7B,gBAAgB,EAChB,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,kBAAkB,EAEvB,eAAe,EACf,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EACL,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAezD,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAIxB,wBAAwB,EACxB,wBAAwB,EAExB,+BAA+B,EAC/B,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EAEtB,iBAAiB,EAEjB,uBAAuB,EAEvB,6BAA6B,GAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAI5F,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,aAAa,EACb,SAAS,GACV,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQ3E,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAS/F,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQ3F,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM9E,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,aAAa,GACd,MAAM,4BAA4B,CAAC;AAMpC,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,0CAA0C,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mDAAmD,CAAC;AAC/F,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,GACjB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG9E,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,+BAA+B,EAC/B,2BAA2B,EAC3B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,wBAAwB,GAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAK5E,YAAY,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAMlE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAOjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,mBAAmB,GACzB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAQ5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -17,9 +17,9 @@
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.wasResolvedFromSecretRef = exports.assertNoUnresolvedSecretRefs = exports.createSecretsProviderFromEnv = exports.resolveBootSecrets = exports.EncryptedFileSecretsProvider = exports.LocalKekCrypto = exports.resolveAuthLinkBaseUrls = exports.CredentialExistsError = exports.isLockedHash = exports.lockedPasswordHash = exports.SHIELD_PASSWORD_HASHER = exports.argon2idHasherProvider = exports.Argon2idPasswordHasher = exports.stubDecisionAuthority = exports.GlobalEnforcementGuard = exports.NO_SESSION_FACTS = exports.SHIELD_SESSION_FACTS_ALS = exports.MariaDbApiKeyStore = exports.isApiKey = exports.extractBearerToken = exports.principalFromClaims = exports.resolveRequestAuth = exports.resolveRequestPrincipal = exports.SessionTokenBinder = exports.SHIELD_SERVICE_AUTHENTICATOR = exports.SHIELD_SESSION_TOKEN_BINDER = exports.CatalogGatabilityError = exports.CatalogReconciler = exports.OperationInventoryScanner = exports.uuidArrayField = exports.optionalUuidField = exports.uuidField = exports.UuidPipe = exports.normalizeClassification = exports.DEFAULT_CLASSIFIED_ACTION = exports.CLASSIFICATION_METADATA = exports.Classified = exports.Public = exports.SHIELD_RECORD_AUTHORIZATION = exports.SHIELD_DECISION_AUTHORITY = exports.ShieldModule = exports.actionsOf = exports.actionsFor = exports.SERVICE_ACCOUNT_ACTIONS = exports.RECORD_SHARE_ACTIONS = exports.CROSS_TENANT_GRANT_ACTIONS = exports.IDENTITY_ACTIONS = exports.MODULE_KEY = exports.buildIdentityModuleRegistry = exports.guardMigrationsDir = void 0;
20
- exports.GrantStoreUnavailableError = exports.InvalidGrantError = exports.GrantNotPermittedError = exports.SHIELD_CROSS_TENANT_GRANT_WRITER = exports.CrossTenantGrantWriter = exports.SHIELD_CROSS_TENANT_GRANT_PORT = exports.MariaDbCrossTenantGrantAdapter = exports.makeShareDeleteCascade = exports.ShareStoreUnavailableError = exports.ShareNotPermittedError = exports.SHIELD_RECORD_SHARE_WRITER = exports.RecordShareWriter = exports.SHIELD_RECORD_SHARE_PORT = exports.MariaDbRecordShareAdapter = exports.UnknownScopeFieldError = exports.translateListScope = exports.SHIELD_LIST_SCOPE_PORT = exports.MariaDbListScopeAdapter = exports.SHIELD_POLICY_STORE = exports.MariaDbPolicyStore = exports.SHIELD_RECORD_RULE_PORT = exports.MariaDbRecordPermissionRuleAdapter = exports.accessBindingTtlMs = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.RedisRecentRotationStore = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = exports.DEFAULT_OBJECT_STORAGE_MAX_BYTES = exports.loadObjectStorageConfig = exports.SHIELD_FILE_INGEST = exports.SHIELD_OBJECT_STORE = exports.FILE_INTAKE_DEFAULT_MAX_BYTES = exports.FILE_INTAKE_REQUEST_KEY = exports.FileIntake = exports.ObjectStoreBootCheck = exports.NodeSha256Digest = exports.S3ObjectStore = void 0;
21
- exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.mariadbSslOption = exports.assertNoReservedRootId = exports.ORG_BOOTSTRAP_PRINCIPAL_ID = exports.rootIdsFromEnv = exports.loadDbTlsConfig = exports.isDevOrTestNodeEnv = exports.DEFAULT_LOGIN_IP_LIMIT = exports.loadRefreshReplayWindowMs = exports.loadLoginIpLimit = exports.DEFAULT_REDIS_URL = exports.DEFAULT_DB_CONFIG = exports.assertRefreshHmacKey = exports.loadRefreshHmacKey = exports.loadSessionLifetimes = exports.loadRedisConfig = exports.loadDbConfig = exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = exports.evaluatePoolBudget = exports.loadPoolBudget = exports.loadAppConfig = exports.frameworkSchemasOf = exports.FRAMEWORK_SCHEMAS = exports.APP_CONFIG = exports.resolveMigrationLedgerCheck = exports.resolveMigrationLedger = exports.frameworkMigrationIds = exports.assertFrameworkMigrationsApplied = exports.SHIELD_MIGRATION_LEDGER_REGISTRY = exports.MigrationLedgerRegistry = exports.FrameworkMigrationLedgerCheck = exports.frameworkDatabasesLogLine = exports.assertPoolBudget = exports.BootSequence = exports.buildListScopeFn = exports.MariaDbSessionRestrictionStore = exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.DEFAULT_PERMISSION_VERSION_TTL_MS = exports.PERMISSION_VERSION_KEY_PREFIX = exports.RedisPermissionVersionCache = exports.withRedisPermissionVersion = exports.buildRecordAuthorization = exports.SHIELD_OPERATION_AUTHORITY_PORT = exports.ManifestOperationAuthorityAdapter = void 0;
22
- exports.ScopeUnknownParentError = exports.ScopeDepthError = exports.ScopeCycleError = exports.buildClosureRows = exports.ConfirmEmailController = exports.AccountController = exports.generateRecoveryCodes = exports.buildOtpauthUri = exports.generateTotpSecret = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.challengeMatches = exports.credentialFingerprint = exports.CredentialMfaStepUp = exports.RedisPendingMfaStore = exports.PendingMfaStore = exports.AuthController = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.ProfileNameTakenError = exports.maskPhone = exports.MAX_MESSAGE_SEND_TIMEOUT_MS = exports.DEFAULT_MESSAGE_SEND_TIMEOUT_MS = exports.EMAIL_PROVIDER_VALUES = exports.loadMessageConfig = exports.MariaDbEmailAddressChanger = exports.maskEmail = exports.isEmailShaped = exports.canonicalEmail = exports.hmacSha1 = exports.resolveTotpVerifier = exports.makeDefaultTotpVerifier = exports.AuthModule = exports.MariaDbCredentialResolver = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_MESSAGE_DISPATCHER = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = void 0;
20
+ exports.GrantNotPermittedError = exports.SHIELD_CROSS_TENANT_GRANT_WRITER = exports.CrossTenantGrantWriter = exports.SHIELD_CROSS_TENANT_GRANT_PORT = exports.MariaDbCrossTenantGrantAdapter = exports.makeShareDeleteCascade = exports.ShareStoreUnavailableError = exports.ShareNotPermittedError = exports.SHIELD_RECORD_SHARE_WRITER = exports.RecordShareWriter = exports.SHIELD_RECORD_SHARE_PORT = exports.MariaDbRecordShareAdapter = exports.UnknownScopeFieldError = exports.translateListScope = exports.SHIELD_LIST_SCOPE_PORT = exports.MariaDbListScopeAdapter = exports.SHIELD_POLICY_STORE = exports.MariaDbPolicyStore = exports.SHIELD_RECORD_RULE_PORT = exports.MariaDbRecordPermissionRuleAdapter = exports.accessBindingTtlMs = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.RedisRecentRotationStore = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = exports.DEFAULT_OBJECT_STORAGE_MAX_BYTES = exports.loadObjectStorageConfig = exports.SHIELD_FILE_INGEST = exports.SHIELD_OBJECT_STORE = exports.FILE_INTAKE_DEFAULT_MAX_BYTES = exports.FILE_INTAKE_REQUEST_KEY = exports.FileIntake = exports.ObjectStoreBootCheck = exports.loadAuditSealConfig = exports.NodeSha256Hash = exports.NodeSha256Digest = exports.S3ObjectStore = void 0;
21
+ exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.mariadbSslOption = exports.assertNoReservedRootId = exports.ORG_BOOTSTRAP_PRINCIPAL_ID = exports.rootIdsFromEnv = exports.loadDbTlsConfig = exports.isDevOrTestNodeEnv = exports.DEFAULT_LOGIN_IP_LIMIT = exports.loadRefreshReplayWindowMs = exports.loadLoginIpLimit = exports.DEFAULT_REDIS_URL = exports.DEFAULT_DB_CONFIG = exports.assertRefreshHmacKey = exports.loadRefreshHmacKey = exports.loadSessionLifetimes = exports.loadRedisConfig = exports.loadDbConfig = exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = exports.evaluatePoolBudget = exports.loadPoolBudget = exports.loadAppConfig = exports.frameworkSchemasOf = exports.FRAMEWORK_SCHEMAS = exports.APP_CONFIG = exports.resolveMigrationLedgerCheck = exports.resolveMigrationLedger = exports.frameworkMigrationIds = exports.assertFrameworkMigrationsApplied = exports.SHIELD_MIGRATION_LEDGER_REGISTRY = exports.MigrationLedgerRegistry = exports.FrameworkMigrationLedgerCheck = exports.frameworkDatabasesLogLine = exports.assertPoolBudget = exports.BootSequence = exports.buildListScopeFn = exports.MariaDbSessionRestrictionStore = exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.DEFAULT_PERMISSION_VERSION_TTL_MS = exports.PERMISSION_VERSION_KEY_PREFIX = exports.RedisPermissionVersionCache = exports.withRedisPermissionVersion = exports.buildRecordAuthorization = exports.SHIELD_OPERATION_AUTHORITY_PORT = exports.ManifestOperationAuthorityAdapter = exports.GrantStoreUnavailableError = exports.InvalidGrantError = void 0;
22
+ exports.ScopeUnknownParentError = exports.ScopeDepthError = exports.ScopeCycleError = exports.buildClosureRows = exports.ConfirmEmailController = exports.AccountController = exports.generateRecoveryCodes = exports.buildOtpauthUri = exports.generateTotpSecret = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.challengeMatches = exports.credentialFingerprint = exports.CredentialMfaStepUp = exports.RedisPendingMfaStore = exports.PendingMfaStore = exports.AuthController = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.ProfileNameTakenError = exports.maskPhone = exports.MAX_MESSAGE_SEND_TIMEOUT_MS = exports.DEFAULT_MESSAGE_SEND_TIMEOUT_MS = exports.EMAIL_PROVIDER_VALUES = exports.loadMessageConfig = exports.MariaDbEmailAddressChanger = exports.maskEmail = exports.isEmailShaped = exports.canonicalEmail = exports.hmacSha1 = exports.resolveTotpVerifier = exports.makeDefaultTotpVerifier = exports.AuthModule = exports.MariaDbCredentialResolver = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_MESSAGE_DISPATCHER = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = void 0;
23
23
  // Framework authz migrations (shield_authz DDL — permission / role_permission /
24
24
  // authz_permission_version). Absolute path a consumer's `@astram/migrate` setup merges in.
25
25
  var migrations_dir_js_1 = require("./migrations-dir.js");
@@ -153,6 +153,12 @@ var s3_object_store_js_1 = require("./storage/s3-object-store.js");
153
153
  Object.defineProperty(exports, "S3ObjectStore", { enumerable: true, get: function () { return s3_object_store_js_1.S3ObjectStore; } });
154
154
  var node_sha256_digest_js_1 = require("./storage/node-sha256-digest.js");
155
155
  Object.defineProperty(exports, "NodeSha256Digest", { enumerable: true, get: function () { return node_sha256_digest_js_1.NodeSha256Digest; } });
156
+ // Durable audit chain (audit-chain-store SPEC §5.F, §5.I): the chain hash adapter and the
157
+ // `AUDIT_SEAL_*` loader. The sealer itself is wired by `ShieldModule.forRoot({ auth: true })`.
158
+ var node_sha256_hash_js_1 = require("./audit/node-sha256-hash.js");
159
+ Object.defineProperty(exports, "NodeSha256Hash", { enumerable: true, get: function () { return node_sha256_hash_js_1.NodeSha256Hash; } });
160
+ var audit_seal_config_js_1 = require("./audit/audit-seal-config.js");
161
+ Object.defineProperty(exports, "loadAuditSealConfig", { enumerable: true, get: function () { return audit_seal_config_js_1.loadAuditSealConfig; } });
156
162
  var object_store_boot_check_js_1 = require("./storage/object-store-boot-check.js");
157
163
  Object.defineProperty(exports, "ObjectStoreBootCheck", { enumerable: true, get: function () { return object_store_boot_check_js_1.ObjectStoreBootCheck; } });
158
164
  var file_intake_interceptor_js_1 = require("./storage/file-intake.interceptor.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/guard",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "license": "MIT",
5
5
  "description": "Astram framework — the enforcement guard + real-world adapters (the 'hands'). One global deny-by-default gate every request passes through (ADR-0002), classification decorators, boot-pool assertion (ADR-0018), the forRoot() registration seam, and the DB/Redis/crypto adapters that connect the sealed core to a real backend. Depends on @astram/core (ADR-0003, ADR-0023).",
6
6
  "type": "commonjs",
@@ -36,8 +36,8 @@
36
36
  "argon2": "^0.41.1",
37
37
  "ioredis": "^5.4.1",
38
38
  "mariadb": "^3.5.4",
39
- "@astram/core": "1.9.0",
40
- "@astram/engine": "0.10.1"
39
+ "@astram/core": "1.10.0",
40
+ "@astram/engine": "0.10.2"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@nestjs/common": "^11",
@@ -45,7 +45,7 @@
45
45
  "@nestjs/platform-express": "^11",
46
46
  "reflect-metadata": "^0.2",
47
47
  "rxjs": "^7",
48
- "@astram/messaging": "^0.1.1"
48
+ "@astram/messaging": "^0.1.2"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@astram/messaging": {
@@ -68,7 +68,12 @@
68
68
  "typescript": "^5.7.0",
69
69
  "unplugin-swc": "^1.5.1",
70
70
  "vitest": "^2.1.8",
71
- "@astram/messaging": "0.1.1"
71
+ "@astram/messaging": "0.1.2"
72
+ },
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "git+https://github.com/ASTRAM-2-0/astram-framework.git",
76
+ "directory": "packages/guard"
72
77
  },
73
78
  "scripts": {
74
79
  "build": "tsc -p tsconfig.build.json",