@parmanasystems/audit-db 1.5.0 → 1.9.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.
package/dist/index.d.ts CHANGED
@@ -103,13 +103,9 @@ declare class AuditDb {
103
103
  ping(): Promise<void>;
104
104
  disconnect(): Promise<void>;
105
105
  migrate(): Promise<void>;
106
- /** Fire-and-forget — never throws, never delays the caller. */
107
106
  recordDecision(attestation: ExecutionAttestation): void;
108
- /** Fire-and-forget — never throws, never delays the caller. */
109
107
  recordVerification(executionId: string, result: VerificationResult): void;
110
- /** Fire-and-forget — never throws, never delays the caller. */
111
108
  recordSecurityEvent(event: SecurityEventInput): void;
112
- /** Fire-and-forget — never throws, never delays the caller. */
113
109
  recordApiAccess(access: ApiAccessInput): void;
114
110
  getDecisionTimeline(limit?: number, filter?: DecisionFilter): Promise<DecisionTimelineRow[]>;
115
111
  getStats(): Promise<AuditStats>;
@@ -118,6 +114,9 @@ declare class AuditDb {
118
114
  getSecurityDashboard(): Promise<SecurityDashboardRow[]>;
119
115
  hasExecution(executionId: string): Promise<boolean>;
120
116
  storePendingExecution(attestation: ExecutionAttestation): Promise<void>;
117
+ set(key: string, value: string): Promise<void>;
118
+ get(key: string): Promise<string | null>;
119
+ markExecuted(executionId: string): Promise<void>;
121
120
  close(): Promise<void>;
122
121
  }
123
122
 
package/dist/index.js CHANGED
@@ -124,10 +124,14 @@ async function runMigrations(client) {
124
124
  var AuditDb = class {
125
125
  pool;
126
126
  constructor(connectionString) {
127
- this.pool = new Pool({ connectionString });
127
+ this.pool = new Pool({
128
+ connectionString
129
+ });
128
130
  }
129
131
  async ping() {
130
- await this.pool.query("SELECT 1");
132
+ await this.pool.query(
133
+ "SELECT 1"
134
+ );
131
135
  }
132
136
  async disconnect() {
133
137
  await this.pool.end();
@@ -135,19 +139,34 @@ var AuditDb = class {
135
139
  async migrate() {
136
140
  const client = await this.pool.connect();
137
141
  try {
138
- await runMigrations(client);
142
+ await runMigrations(
143
+ client
144
+ );
139
145
  } finally {
140
146
  client.release();
141
147
  }
142
148
  }
143
- /** Fire-and-forget — never throws, never delays the caller. */
149
+ // ----------------------------------
150
+ // Decision recording
151
+ // ----------------------------------
144
152
  recordDecision(attestation) {
145
153
  this.pool.query(
146
- `INSERT INTO audit_decisions
147
- (execution_id, decision, execution_state,
148
- runtime_hash, signature, attestation, executed_at)
149
- VALUES ($1,$2,$3,$4,$5,$6,$7)
150
- ON CONFLICT (execution_id) DO NOTHING`,
154
+ `
155
+ INSERT INTO audit_decisions
156
+ (
157
+ execution_id,
158
+ decision,
159
+ execution_state,
160
+ runtime_hash,
161
+ signature,
162
+ attestation,
163
+ executed_at
164
+ )
165
+ VALUES ($1,$2,$3,$4,$5,$6,$7)
166
+
167
+ ON CONFLICT (execution_id)
168
+ DO NOTHING
169
+ `,
151
170
  [
152
171
  attestation.execution_id,
153
172
  attestation.decision,
@@ -159,12 +178,22 @@ var AuditDb = class {
159
178
  ]
160
179
  ).catch(() => void 0);
161
180
  }
162
- /** Fire-and-forget — never throws, never delays the caller. */
181
+ // ----------------------------------
182
+ // Verification recording
183
+ // ----------------------------------
163
184
  recordVerification(executionId, result) {
164
185
  this.pool.query(
165
- `INSERT INTO audit_verifications
166
- (execution_id, valid, signature_verified, runtime_verified, schema_compatible)
167
- VALUES ($1,$2,$3,$4,$5)`,
186
+ `
187
+ INSERT INTO audit_verifications
188
+ (
189
+ execution_id,
190
+ valid,
191
+ signature_verified,
192
+ runtime_verified,
193
+ schema_compatible
194
+ )
195
+ VALUES ($1,$2,$3,$4,$5)
196
+ `,
168
197
  [
169
198
  executionId,
170
199
  result.valid,
@@ -174,12 +203,24 @@ var AuditDb = class {
174
203
  ]
175
204
  ).catch(() => void 0);
176
205
  }
177
- /** Fire-and-forget — never throws, never delays the caller. */
206
+ // ----------------------------------
207
+ // Security event recording
208
+ // ----------------------------------
178
209
  recordSecurityEvent(event) {
179
210
  this.pool.query(
180
- `INSERT INTO audit_security_events
181
- (event_type, severity, ip_address, path, method, user_agent, details)
182
- VALUES ($1,$2,$3,$4,$5,$6,$7)`,
211
+ `
212
+ INSERT INTO audit_security_events
213
+ (
214
+ event_type,
215
+ severity,
216
+ ip_address,
217
+ path,
218
+ method,
219
+ user_agent,
220
+ details
221
+ )
222
+ VALUES ($1,$2,$3,$4,$5,$6,$7)
223
+ `,
183
224
  [
184
225
  event.event_type,
185
226
  event.severity,
@@ -191,12 +232,24 @@ var AuditDb = class {
191
232
  ]
192
233
  ).catch(() => void 0);
193
234
  }
194
- /** Fire-and-forget — never throws, never delays the caller. */
235
+ // ----------------------------------
236
+ // API access recording
237
+ // ----------------------------------
195
238
  recordApiAccess(access) {
196
239
  this.pool.query(
197
- `INSERT INTO audit_api_access
198
- (method, path, status_code, response_time_ms, ip_address, user_agent, execution_id)
199
- VALUES ($1,$2,$3,$4,$5,$6,$7)`,
240
+ `
241
+ INSERT INTO audit_api_access
242
+ (
243
+ method,
244
+ path,
245
+ status_code,
246
+ response_time_ms,
247
+ ip_address,
248
+ user_agent,
249
+ execution_id
250
+ )
251
+ VALUES ($1,$2,$3,$4,$5,$6,$7)
252
+ `,
200
253
  [
201
254
  access.method,
202
255
  access.path,
@@ -208,98 +261,196 @@ var AuditDb = class {
208
261
  ]
209
262
  ).catch(() => void 0);
210
263
  }
264
+ // ----------------------------------
265
+ // Decision timeline
266
+ // ----------------------------------
211
267
  async getDecisionTimeline(limit = 100, filter) {
212
268
  const conditions = [];
213
269
  const values = [];
214
270
  if (filter?.policy_id) {
215
- values.push(filter.policy_id);
216
- conditions.push(`policy_id = $${values.length}`);
271
+ values.push(
272
+ filter.policy_id
273
+ );
274
+ conditions.push(
275
+ `policy_id = $${values.length}`
276
+ );
217
277
  }
218
278
  if (filter?.decision) {
219
- values.push(filter.decision);
220
- conditions.push(`decision = $${values.length}`);
279
+ values.push(
280
+ filter.decision
281
+ );
282
+ conditions.push(
283
+ `decision = $${values.length}`
284
+ );
221
285
  }
222
286
  if (filter?.from_date) {
223
- values.push(filter.from_date);
224
- conditions.push(`executed_at >= $${values.length}`);
287
+ values.push(
288
+ filter.from_date
289
+ );
290
+ conditions.push(
291
+ `executed_at >= $${values.length}`
292
+ );
225
293
  }
226
294
  if (filter?.to_date) {
227
- values.push(filter.to_date);
228
- conditions.push(`executed_at <= $${values.length}`);
295
+ values.push(
296
+ filter.to_date
297
+ );
298
+ conditions.push(
299
+ `executed_at <= $${values.length}`
300
+ );
229
301
  }
230
302
  values.push(limit);
231
303
  const limitParam = `$${values.length}`;
232
304
  const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
233
305
  const { rows } = await this.pool.query(
234
- `SELECT * FROM view_decision_timeline ${where} ORDER BY executed_at DESC LIMIT ${limitParam}`,
306
+ `
307
+ SELECT *
308
+ FROM view_decision_timeline
309
+
310
+ ${where}
311
+
312
+ ORDER BY executed_at DESC
313
+
314
+ LIMIT ${limitParam}
315
+ `,
235
316
  values
236
317
  );
237
318
  return rows;
238
319
  }
320
+ // ----------------------------------
321
+ // Stats
322
+ // ----------------------------------
239
323
  async getStats() {
240
- const { rows } = await this.pool.query(`
241
- SELECT
242
- (SELECT COUNT(*) FROM audit_decisions)::text AS total_decisions,
243
- (SELECT COUNT(*) FROM audit_decisions WHERE executed_at >= CURRENT_DATE)::text AS decisions_today,
244
- (SELECT COUNT(*) FROM audit_verifications)::text AS total_verifications,
245
- (SELECT COUNT(*) FROM audit_verifications WHERE valid = true)::text AS valid_verifications,
246
- (SELECT COUNT(*) FROM audit_verifications WHERE valid = false)::text AS invalid_verifications,
247
- (SELECT COUNT(*) FROM audit_security_events)::text AS total_security_events,
248
- (SELECT COUNT(*) FROM audit_api_access)::text AS total_api_calls
249
- `);
324
+ const { rows } = await this.pool.query(
325
+ `
326
+ SELECT
327
+
328
+ (
329
+ SELECT COUNT(*)
330
+ FROM audit_decisions
331
+ )::text AS total_decisions,
332
+
333
+ (
334
+ SELECT COUNT(*)
335
+ FROM audit_decisions
336
+ WHERE executed_at >= CURRENT_DATE
337
+ )::text AS decisions_today,
338
+
339
+ (
340
+ SELECT COUNT(*)
341
+ FROM audit_verifications
342
+ )::text AS total_verifications,
343
+
344
+ (
345
+ SELECT COUNT(*)
346
+ FROM audit_verifications
347
+ WHERE valid = true
348
+ )::text AS valid_verifications,
349
+
350
+ (
351
+ SELECT COUNT(*)
352
+ FROM audit_verifications
353
+ WHERE valid = false
354
+ )::text AS invalid_verifications,
355
+
356
+ (
357
+ SELECT COUNT(*)
358
+ FROM audit_security_events
359
+ )::text AS total_security_events,
360
+
361
+ (
362
+ SELECT COUNT(*)
363
+ FROM audit_api_access
364
+ )::text AS total_api_calls
365
+ `
366
+ );
250
367
  return rows[0];
251
368
  }
369
+ // ----------------------------------
370
+ // Decision lookup
371
+ // ----------------------------------
252
372
  async getDecisionById(executionId) {
253
373
  const { rows } = await this.pool.query(
254
- `SELECT * FROM audit_decisions WHERE execution_id = $1`,
374
+ `
375
+ SELECT *
376
+ FROM audit_decisions
377
+ WHERE execution_id = $1
378
+ `,
255
379
  [executionId]
256
380
  );
257
381
  return rows[0] ?? null;
258
382
  }
383
+ // ----------------------------------
384
+ // Verification lookup
385
+ // ----------------------------------
259
386
  async getVerificationsByExecution(executionId) {
260
387
  const { rows } = await this.pool.query(
261
- `SELECT * FROM audit_verifications
262
- WHERE execution_id = $1
263
- ORDER BY verified_at DESC`,
388
+ `
389
+ SELECT *
390
+ FROM audit_verifications
391
+
392
+ WHERE execution_id = $1
393
+
394
+ ORDER BY verified_at DESC
395
+ `,
264
396
  [executionId]
265
397
  );
266
398
  return rows;
267
399
  }
400
+ // ----------------------------------
401
+ // Security dashboard
402
+ // ----------------------------------
268
403
  async getSecurityDashboard() {
269
404
  const { rows } = await this.pool.query(
270
- `SELECT * FROM view_security_dashboard ORDER BY event_count DESC`
405
+ `
406
+ SELECT *
407
+ FROM view_security_dashboard
408
+
409
+ ORDER BY event_count DESC
410
+ `
271
411
  );
272
412
  return rows;
273
413
  }
414
+ // ----------------------------------
415
+ // Replay protection
416
+ // ----------------------------------
274
417
  async hasExecution(executionId) {
275
418
  const { rows } = await this.pool.query(
276
419
  `
277
- SELECT execution_id
278
- FROM audit_decisions
279
- WHERE execution_id = $1
280
- LIMIT 1
281
- `,
420
+ SELECT execution_id
421
+
422
+ FROM audit_decisions
423
+
424
+ WHERE execution_id = $1
425
+
426
+ LIMIT 1
427
+ `,
282
428
  [executionId]
283
429
  );
284
430
  return rows.length > 0;
285
431
  }
432
+ // ----------------------------------
433
+ // Pending override storage
434
+ // ----------------------------------
286
435
  async storePendingExecution(attestation) {
287
436
  await this.pool.query(
288
437
  `
289
- INSERT INTO audit_decisions
290
- (
291
- execution_id,
292
- decision,
293
- execution_state,
294
- runtime_hash,
295
- signature,
296
- attestation,
297
- executed_at
298
- )
299
- VALUES ($1,$2,$3,$4,$5,$6,$7)
300
- ON CONFLICT (execution_id)
301
- DO NOTHING
302
- `,
438
+ INSERT INTO audit_decisions
439
+ (
440
+ execution_id,
441
+ decision,
442
+ execution_state,
443
+ runtime_hash,
444
+ signature,
445
+ attestation,
446
+ executed_at
447
+ )
448
+
449
+ VALUES ($1,$2,$3,$4,$5,$6,$7)
450
+
451
+ ON CONFLICT (execution_id)
452
+ DO NOTHING
453
+ `,
303
454
  [
304
455
  attestation.execution_id,
305
456
  attestation.decision,
@@ -311,6 +462,82 @@ var AuditDb = class {
311
462
  ]
312
463
  );
313
464
  }
465
+ // ----------------------------------
466
+ // Redis-compatible KV set
467
+ // ----------------------------------
468
+ async set(key, value) {
469
+ const executionId = key.replace(
470
+ "pending:",
471
+ ""
472
+ );
473
+ await this.pool.query(
474
+ `
475
+ INSERT INTO audit_decisions
476
+ (
477
+ execution_id,
478
+ decision,
479
+ execution_state,
480
+ attestation,
481
+ executed_at
482
+ )
483
+
484
+ VALUES ($1,$2,$3,$4,$5)
485
+
486
+ ON CONFLICT (execution_id)
487
+ DO NOTHING
488
+ `,
489
+ [
490
+ executionId,
491
+ {},
492
+ "pending_override",
493
+ value,
494
+ (/* @__PURE__ */ new Date()).toISOString()
495
+ ]
496
+ );
497
+ }
498
+ // ----------------------------------
499
+ // Redis-compatible KV get
500
+ // ----------------------------------
501
+ async get(key) {
502
+ const executionId = key.replace(
503
+ "pending:",
504
+ ""
505
+ );
506
+ const { rows } = await this.pool.query(
507
+ `
508
+ SELECT attestation
509
+
510
+ FROM audit_decisions
511
+
512
+ WHERE execution_id = $1
513
+
514
+ LIMIT 1
515
+ `,
516
+ [executionId]
517
+ );
518
+ if (rows.length === 0) {
519
+ return null;
520
+ }
521
+ return rows[0].attestation;
522
+ }
523
+ // ----------------------------------
524
+ // Completed execution marking
525
+ // ----------------------------------
526
+ async markExecuted(executionId) {
527
+ await this.pool.query(
528
+ `
529
+ UPDATE audit_decisions
530
+
531
+ SET execution_state = 'completed'
532
+
533
+ WHERE execution_id = $1
534
+ `,
535
+ [executionId]
536
+ );
537
+ }
538
+ // ----------------------------------
539
+ // Cleanup
540
+ // ----------------------------------
314
541
  async close() {
315
542
  await this.pool.end();
316
543
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/migrations.ts"],"sourcesContent":["import { Pool } from \"pg\";\r\nimport type { PoolClient } from \"pg\";\r\n\r\nimport type { ExecutionAttestation } from \"@parmanasystems/execution\";\r\nimport type { VerificationResult } from \"@parmanasystems/verifier\";\r\n\r\nimport type {\r\n AuditDecision,\r\n AuditVerification,\r\n SecurityEventInput,\r\n ApiAccessInput,\r\n DecisionTimelineRow,\r\n DecisionFilter,\r\n SecurityDashboardRow,\r\n AuditStats,\r\n} from \"./types.js\";\r\nimport { runMigrations } from \"./migrations.js\";\r\n\r\nexport class AuditDb {\r\n private readonly pool: InstanceType<typeof Pool>;\r\n\r\n constructor(connectionString: string) {\r\n this.pool = new Pool({ connectionString });\r\n }\r\n\r\n async ping(): Promise<void> {\r\n await this.pool.query(\"SELECT 1\");\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n await this.pool.end();\r\n }\r\n\r\n async migrate(): Promise<void> {\r\n const client: PoolClient = await this.pool.connect();\r\n try {\r\n await runMigrations(client);\r\n } finally {\r\n client.release();\r\n }\r\n }\r\n\r\n /** Fire-and-forget — never throws, never delays the caller. */\r\n recordDecision(attestation: ExecutionAttestation): void {\r\n this.pool\r\n .query(\r\n `INSERT INTO audit_decisions\r\n (execution_id, decision, execution_state,\r\n runtime_hash, signature, attestation, executed_at)\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n ON CONFLICT (execution_id) DO NOTHING`,\r\n [\r\n attestation.execution_id,\r\n attestation.decision,\r\n attestation.execution_state,\r\n attestation.runtime_hash,\r\n attestation.signature,\r\n JSON.stringify(attestation),\r\n new Date().toISOString(),\r\n ],\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n /** Fire-and-forget — never throws, never delays the caller. */\r\n recordVerification(executionId: string, result: VerificationResult): void {\r\n this.pool\r\n .query(\r\n `INSERT INTO audit_verifications\r\n (execution_id, valid, signature_verified, runtime_verified, schema_compatible)\r\n VALUES ($1,$2,$3,$4,$5)`,\r\n [\r\n executionId,\r\n result.valid,\r\n result.checks.signature_verified,\r\n result.checks.runtime_verified,\r\n result.checks.schema_compatible,\r\n ],\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n /** Fire-and-forget — never throws, never delays the caller. */\r\n recordSecurityEvent(event: SecurityEventInput): void {\r\n this.pool\r\n .query(\r\n `INSERT INTO audit_security_events\r\n (event_type, severity, ip_address, path, method, user_agent, details)\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)`,\r\n [\r\n event.event_type,\r\n event.severity,\r\n event.ip_address ?? null,\r\n event.path ?? null,\r\n event.method ?? null,\r\n event.user_agent ?? null,\r\n event.details != null ? JSON.stringify(event.details) : null,\r\n ],\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n /** Fire-and-forget — never throws, never delays the caller. */\r\n recordApiAccess(access: ApiAccessInput): void {\r\n this.pool\r\n .query(\r\n `INSERT INTO audit_api_access\r\n (method, path, status_code, response_time_ms, ip_address, user_agent, execution_id)\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)`,\r\n [\r\n access.method,\r\n access.path,\r\n access.status_code,\r\n access.response_time_ms ?? null,\r\n access.ip_address ?? null,\r\n access.user_agent ?? null,\r\n access.execution_id ?? null,\r\n ],\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n async getDecisionTimeline(limit = 100, filter?: DecisionFilter): Promise<DecisionTimelineRow[]> {\r\n const conditions: string[] = [];\r\n const values: unknown[] = [];\r\n\r\n if (filter?.policy_id) {\r\n values.push(filter.policy_id);\r\n conditions.push(`policy_id = $${values.length}`);\r\n }\r\n if (filter?.decision) {\r\n values.push(filter.decision);\r\n conditions.push(`decision = $${values.length}`);\r\n }\r\n if (filter?.from_date) {\r\n values.push(filter.from_date);\r\n conditions.push(`executed_at >= $${values.length}`);\r\n }\r\n if (filter?.to_date) {\r\n values.push(filter.to_date);\r\n conditions.push(`executed_at <= $${values.length}`);\r\n }\r\n\r\n values.push(limit);\r\n const limitParam = `$${values.length}`;\r\n const where = conditions.length ? `WHERE ${conditions.join(\" AND \")}` : \"\";\r\n\r\n const { rows } = await this.pool.query<DecisionTimelineRow>(\r\n `SELECT * FROM view_decision_timeline ${where} ORDER BY executed_at DESC LIMIT ${limitParam}`,\r\n values,\r\n );\r\n return rows;\r\n }\r\n\r\n async getStats(): Promise<AuditStats> {\r\n const { rows } = await this.pool.query<AuditStats>(`\r\n SELECT\r\n (SELECT COUNT(*) FROM audit_decisions)::text AS total_decisions,\r\n (SELECT COUNT(*) FROM audit_decisions WHERE executed_at >= CURRENT_DATE)::text AS decisions_today,\r\n (SELECT COUNT(*) FROM audit_verifications)::text AS total_verifications,\r\n (SELECT COUNT(*) FROM audit_verifications WHERE valid = true)::text AS valid_verifications,\r\n (SELECT COUNT(*) FROM audit_verifications WHERE valid = false)::text AS invalid_verifications,\r\n (SELECT COUNT(*) FROM audit_security_events)::text AS total_security_events,\r\n (SELECT COUNT(*) FROM audit_api_access)::text AS total_api_calls\r\n `);\r\n return rows[0]!;\r\n }\r\n\r\n async getDecisionById(executionId: string): Promise<AuditDecision | null> {\r\n const { rows } = await this.pool.query<AuditDecision>(\r\n `SELECT * FROM audit_decisions WHERE execution_id = $1`,\r\n [executionId],\r\n );\r\n return rows[0] ?? null;\r\n }\r\n\r\n async getVerificationsByExecution(executionId: string): Promise<AuditVerification[]> {\r\n const { rows } = await this.pool.query<AuditVerification>(\r\n `SELECT * FROM audit_verifications\r\n WHERE execution_id = $1\r\n ORDER BY verified_at DESC`,\r\n [executionId],\r\n );\r\n return rows;\r\n }\r\n\r\n async getSecurityDashboard(): Promise<SecurityDashboardRow[]> {\r\n const { rows } = await this.pool.query<SecurityDashboardRow>(\r\n `SELECT * FROM view_security_dashboard ORDER BY event_count DESC`,\r\n );\r\n return rows;\r\n }\r\n async hasExecution(\r\n executionId: string\r\n): Promise<boolean> {\r\n\r\n const { rows } =\r\n await this.pool.query(\r\n `\r\n SELECT execution_id\r\n FROM audit_decisions\r\n WHERE execution_id = $1\r\n LIMIT 1\r\n `,\r\n [executionId]\r\n );\r\n\r\n return rows.length > 0;\r\n}\r\n\r\nasync storePendingExecution(\r\n attestation: ExecutionAttestation\r\n): Promise<void> {\r\n\r\n await this.pool.query(\r\n `\r\n INSERT INTO audit_decisions\r\n (\r\n execution_id,\r\n decision,\r\n execution_state,\r\n runtime_hash,\r\n signature,\r\n attestation,\r\n executed_at\r\n )\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n ON CONFLICT (execution_id)\r\n DO NOTHING\r\n `,\r\n [\r\n attestation.execution_id,\r\n attestation.decision,\r\n \"pending_override\",\r\n attestation.runtime_hash,\r\n attestation.signature,\r\n JSON.stringify(attestation),\r\n new Date().toISOString()\r\n ]\r\n );\r\n}\r\n async close(): Promise<void> {\r\n await this.pool.end();\r\n }\r\n}\r\n","import type { PoolClient } from \"pg\";\r\n\r\n// Inlined so the bundle has no runtime fs dependency.\r\n// The canonical reference lives in schema.sql alongside this file.\r\nconst SCHEMA_SQL = `\r\nCREATE TABLE IF NOT EXISTS audit_decisions (\r\n id BIGSERIAL PRIMARY KEY,\r\n execution_id UUID NOT NULL UNIQUE,\r\n policy_id TEXT NOT NULL,\r\n policy_version TEXT NOT NULL,\r\n schema_version TEXT NOT NULL,\r\n runtime_version TEXT NOT NULL,\r\n runtime_hash TEXT NOT NULL,\r\n decision TEXT NOT NULL,\r\n signals_hash TEXT NOT NULL,\r\n signature TEXT NOT NULL,\r\n attestation JSONB NOT NULL,\r\n executed_at TIMESTAMPTZ NOT NULL,\r\n recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_policy_id\r\n ON audit_decisions (policy_id);\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_executed_at\r\n ON audit_decisions (executed_at DESC);\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_decision\r\n ON audit_decisions (decision);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_verifications (\r\n id BIGSERIAL PRIMARY KEY,\r\n execution_id UUID NOT NULL,\r\n valid BOOLEAN NOT NULL,\r\n signature_verified BOOLEAN NOT NULL,\r\n runtime_verified BOOLEAN NOT NULL,\r\n schema_compatible BOOLEAN NOT NULL,\r\n verified_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_execution_id\r\n ON audit_verifications (execution_id);\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_valid\r\n ON audit_verifications (valid);\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_verified_at\r\n ON audit_verifications (verified_at DESC);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_security_events (\r\n id BIGSERIAL PRIMARY KEY,\r\n event_type TEXT NOT NULL,\r\n severity TEXT NOT NULL CHECK (severity IN ('low','medium','high','critical')),\r\n ip_address TEXT,\r\n path TEXT,\r\n method TEXT,\r\n user_agent TEXT,\r\n details JSONB,\r\n occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_event_type\r\n ON audit_security_events (event_type);\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_severity\r\n ON audit_security_events (severity);\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_occurred_at\r\n ON audit_security_events (occurred_at DESC);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_api_access (\r\n id BIGSERIAL PRIMARY KEY,\r\n method TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n status_code INTEGER NOT NULL,\r\n response_time_ms INTEGER,\r\n ip_address TEXT,\r\n user_agent TEXT,\r\n execution_id UUID,\r\n accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_path\r\n ON audit_api_access (path);\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_status_code\r\n ON audit_api_access (status_code);\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_accessed_at\r\n ON audit_api_access (accessed_at DESC);\r\n\r\nCREATE OR REPLACE VIEW view_decision_timeline AS\r\nSELECT\r\n d.execution_id,\r\n d.policy_id,\r\n d.policy_version,\r\n d.decision,\r\n d.runtime_version,\r\n d.runtime_hash,\r\n d.executed_at,\r\n d.recorded_at,\r\n v.valid AS verification_valid,\r\n v.signature_verified,\r\n v.runtime_verified,\r\n v.schema_compatible,\r\n v.verified_at\r\nFROM audit_decisions d\r\nLEFT JOIN audit_verifications v ON d.execution_id = v.execution_id;\r\n\r\nCREATE OR REPLACE VIEW view_security_dashboard AS\r\nSELECT\r\n event_type,\r\n severity,\r\n COUNT(*) AS event_count,\r\n MAX(occurred_at) AS last_occurrence,\r\n MIN(occurred_at) AS first_occurrence\r\nFROM audit_security_events\r\nGROUP BY event_type, severity;\r\n`;\r\n\r\nexport async function runMigrations(client: PoolClient): Promise<void> {\r\n await client.query(\"BEGIN\");\r\n try {\r\n await client.query(SCHEMA_SQL);\r\n await client.query(\"COMMIT\");\r\n } catch (err) {\r\n await client.query(\"ROLLBACK\");\r\n throw err;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/migrations.ts"],"sourcesContent":["import { Pool } from \"pg\";\r\nimport type { PoolClient } from \"pg\";\r\n\r\nimport type { ExecutionAttestation } from \"@parmanasystems/execution\";\r\nimport type { VerificationResult } from \"@parmanasystems/verifier\";\r\n\r\nimport type {\r\n AuditDecision,\r\n AuditVerification,\r\n SecurityEventInput,\r\n ApiAccessInput,\r\n DecisionTimelineRow,\r\n DecisionFilter,\r\n SecurityDashboardRow,\r\n AuditStats,\r\n} from \"./types.js\";\r\n\r\nimport { runMigrations } from \"./migrations.js\";\r\n\r\nexport class AuditDb {\r\n\r\n private readonly pool:\r\n InstanceType<typeof Pool>;\r\n\r\n constructor(\r\n connectionString: string\r\n ) {\r\n\r\n this.pool =\r\n new Pool({\r\n connectionString\r\n });\r\n }\r\n\r\n async ping(): Promise<void> {\r\n\r\n await this.pool.query(\r\n \"SELECT 1\"\r\n );\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n\r\n await this.pool.end();\r\n }\r\n\r\n async migrate(): Promise<void> {\r\n\r\n const client:\r\n PoolClient =\r\n await this.pool.connect();\r\n\r\n try {\r\n\r\n await runMigrations(\r\n client\r\n );\r\n\r\n } finally {\r\n\r\n client.release();\r\n }\r\n }\r\n\r\n // ----------------------------------\r\n // Decision recording\r\n // ----------------------------------\r\n\r\n recordDecision(\r\n attestation: ExecutionAttestation\r\n ): void {\r\n\r\n this.pool\r\n .query(\r\n `\r\n INSERT INTO audit_decisions\r\n (\r\n execution_id,\r\n decision,\r\n execution_state,\r\n runtime_hash,\r\n signature,\r\n attestation,\r\n executed_at\r\n )\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n\r\n ON CONFLICT (execution_id)\r\n DO NOTHING\r\n `,\r\n [\r\n attestation.execution_id,\r\n attestation.decision,\r\n attestation.execution_state,\r\n attestation.runtime_hash,\r\n attestation.signature,\r\n JSON.stringify(attestation),\r\n new Date().toISOString()\r\n ]\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n // ----------------------------------\r\n // Verification recording\r\n // ----------------------------------\r\n\r\n recordVerification(\r\n executionId: string,\r\n result: VerificationResult\r\n ): void {\r\n\r\n this.pool\r\n .query(\r\n `\r\n INSERT INTO audit_verifications\r\n (\r\n execution_id,\r\n valid,\r\n signature_verified,\r\n runtime_verified,\r\n schema_compatible\r\n )\r\n VALUES ($1,$2,$3,$4,$5)\r\n `,\r\n [\r\n executionId,\r\n result.valid,\r\n result.checks.signature_verified,\r\n result.checks.runtime_verified,\r\n result.checks.schema_compatible\r\n ]\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n // ----------------------------------\r\n // Security event recording\r\n // ----------------------------------\r\n\r\n recordSecurityEvent(\r\n event: SecurityEventInput\r\n ): void {\r\n\r\n this.pool\r\n .query(\r\n `\r\n INSERT INTO audit_security_events\r\n (\r\n event_type,\r\n severity,\r\n ip_address,\r\n path,\r\n method,\r\n user_agent,\r\n details\r\n )\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n `,\r\n [\r\n event.event_type,\r\n event.severity,\r\n event.ip_address ?? null,\r\n event.path ?? null,\r\n event.method ?? null,\r\n event.user_agent ?? null,\r\n\r\n event.details != null\r\n ? JSON.stringify(event.details)\r\n : null\r\n ]\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n // ----------------------------------\r\n // API access recording\r\n // ----------------------------------\r\n\r\n recordApiAccess(\r\n access: ApiAccessInput\r\n ): void {\r\n\r\n this.pool\r\n .query(\r\n `\r\n INSERT INTO audit_api_access\r\n (\r\n method,\r\n path,\r\n status_code,\r\n response_time_ms,\r\n ip_address,\r\n user_agent,\r\n execution_id\r\n )\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n `,\r\n [\r\n access.method,\r\n access.path,\r\n access.status_code,\r\n access.response_time_ms ?? null,\r\n access.ip_address ?? null,\r\n access.user_agent ?? null,\r\n access.execution_id ?? null\r\n ]\r\n )\r\n .catch(() => undefined);\r\n }\r\n\r\n // ----------------------------------\r\n // Decision timeline\r\n // ----------------------------------\r\n\r\n async getDecisionTimeline(\r\n limit = 100,\r\n filter?: DecisionFilter\r\n ): Promise<DecisionTimelineRow[]> {\r\n\r\n const conditions: string[] = [];\r\n\r\n const values: unknown[] = [];\r\n\r\n if (filter?.policy_id) {\r\n\r\n values.push(\r\n filter.policy_id\r\n );\r\n\r\n conditions.push(\r\n `policy_id = $${values.length}`\r\n );\r\n }\r\n\r\n if (filter?.decision) {\r\n\r\n values.push(\r\n filter.decision\r\n );\r\n\r\n conditions.push(\r\n `decision = $${values.length}`\r\n );\r\n }\r\n\r\n if (filter?.from_date) {\r\n\r\n values.push(\r\n filter.from_date\r\n );\r\n\r\n conditions.push(\r\n `executed_at >= $${values.length}`\r\n );\r\n }\r\n\r\n if (filter?.to_date) {\r\n\r\n values.push(\r\n filter.to_date\r\n );\r\n\r\n conditions.push(\r\n `executed_at <= $${values.length}`\r\n );\r\n }\r\n\r\n values.push(limit);\r\n\r\n const limitParam =\r\n `$${values.length}`;\r\n\r\n const where =\r\n conditions.length\r\n ? `WHERE ${conditions.join(\" AND \")}`\r\n : \"\";\r\n\r\n const { rows } =\r\n await this.pool.query<DecisionTimelineRow>(\r\n `\r\n SELECT *\r\n FROM view_decision_timeline\r\n\r\n ${where}\r\n\r\n ORDER BY executed_at DESC\r\n\r\n LIMIT ${limitParam}\r\n `,\r\n values\r\n );\r\n\r\n return rows;\r\n }\r\n\r\n // ----------------------------------\r\n // Stats\r\n // ----------------------------------\r\n\r\n async getStats(): Promise<AuditStats> {\r\n\r\n const { rows } =\r\n await this.pool.query<AuditStats>(\r\n `\r\n SELECT\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_decisions\r\n )::text AS total_decisions,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_decisions\r\n WHERE executed_at >= CURRENT_DATE\r\n )::text AS decisions_today,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_verifications\r\n )::text AS total_verifications,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_verifications\r\n WHERE valid = true\r\n )::text AS valid_verifications,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_verifications\r\n WHERE valid = false\r\n )::text AS invalid_verifications,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_security_events\r\n )::text AS total_security_events,\r\n\r\n (\r\n SELECT COUNT(*)\r\n FROM audit_api_access\r\n )::text AS total_api_calls\r\n `\r\n );\r\n\r\n return rows[0]!;\r\n }\r\n\r\n // ----------------------------------\r\n // Decision lookup\r\n // ----------------------------------\r\n\r\n async getDecisionById(\r\n executionId: string\r\n ): Promise<AuditDecision | null> {\r\n\r\n const { rows } =\r\n await this.pool.query<AuditDecision>(\r\n `\r\n SELECT *\r\n FROM audit_decisions\r\n WHERE execution_id = $1\r\n `,\r\n [executionId]\r\n );\r\n\r\n return rows[0] ?? null;\r\n }\r\n\r\n // ----------------------------------\r\n // Verification lookup\r\n // ----------------------------------\r\n\r\n async getVerificationsByExecution(\r\n executionId: string\r\n ): Promise<AuditVerification[]> {\r\n\r\n const { rows } =\r\n await this.pool.query<AuditVerification>(\r\n `\r\n SELECT *\r\n FROM audit_verifications\r\n\r\n WHERE execution_id = $1\r\n\r\n ORDER BY verified_at DESC\r\n `,\r\n [executionId]\r\n );\r\n\r\n return rows;\r\n }\r\n\r\n // ----------------------------------\r\n // Security dashboard\r\n // ----------------------------------\r\n\r\n async getSecurityDashboard():\r\n Promise<SecurityDashboardRow[]> {\r\n\r\n const { rows } =\r\n await this.pool.query<SecurityDashboardRow>(\r\n `\r\n SELECT *\r\n FROM view_security_dashboard\r\n\r\n ORDER BY event_count DESC\r\n `\r\n );\r\n\r\n return rows;\r\n }\r\n\r\n // ----------------------------------\r\n // Replay protection\r\n // ----------------------------------\r\n\r\n async hasExecution(\r\n executionId: string\r\n ): Promise<boolean> {\r\n\r\n const { rows } =\r\n await this.pool.query(\r\n `\r\n SELECT execution_id\r\n\r\n FROM audit_decisions\r\n\r\n WHERE execution_id = $1\r\n\r\n LIMIT 1\r\n `,\r\n [executionId]\r\n );\r\n\r\n return rows.length > 0;\r\n }\r\n\r\n // ----------------------------------\r\n // Pending override storage\r\n // ----------------------------------\r\n\r\n async storePendingExecution(\r\n attestation: ExecutionAttestation\r\n ): Promise<void> {\r\n\r\n await this.pool.query(\r\n `\r\n INSERT INTO audit_decisions\r\n (\r\n execution_id,\r\n decision,\r\n execution_state,\r\n runtime_hash,\r\n signature,\r\n attestation,\r\n executed_at\r\n )\r\n\r\n VALUES ($1,$2,$3,$4,$5,$6,$7)\r\n\r\n ON CONFLICT (execution_id)\r\n DO NOTHING\r\n `,\r\n [\r\n attestation.execution_id,\r\n attestation.decision,\r\n \"pending_override\",\r\n attestation.runtime_hash,\r\n attestation.signature,\r\n JSON.stringify(attestation),\r\n new Date().toISOString()\r\n ]\r\n );\r\n }\r\n\r\n // ----------------------------------\r\n // Redis-compatible KV set\r\n // ----------------------------------\r\n\r\n async set(\r\n key: string,\r\n value: string\r\n ): Promise<void> {\r\n\r\n const executionId =\r\n key.replace(\r\n \"pending:\",\r\n \"\"\r\n );\r\n\r\n await this.pool.query(\r\n `\r\n INSERT INTO audit_decisions\r\n (\r\n execution_id,\r\n decision,\r\n execution_state,\r\n attestation,\r\n executed_at\r\n )\r\n\r\n VALUES ($1,$2,$3,$4,$5)\r\n\r\n ON CONFLICT (execution_id)\r\n DO NOTHING\r\n `,\r\n [\r\n executionId,\r\n {},\r\n \"pending_override\",\r\n value,\r\n new Date().toISOString()\r\n ]\r\n );\r\n }\r\n\r\n // ----------------------------------\r\n // Redis-compatible KV get\r\n // ----------------------------------\r\n\r\n async get(\r\n key: string\r\n ): Promise<string | null> {\r\n\r\n const executionId =\r\n key.replace(\r\n \"pending:\",\r\n \"\"\r\n );\r\n\r\n const { rows } =\r\n await this.pool.query(\r\n `\r\n SELECT attestation\r\n\r\n FROM audit_decisions\r\n\r\n WHERE execution_id = $1\r\n\r\n LIMIT 1\r\n `,\r\n [executionId]\r\n );\r\n\r\n if (rows.length === 0) {\r\n return null;\r\n }\r\n\r\n return rows[0].attestation;\r\n }\r\n\r\n // ----------------------------------\r\n // Completed execution marking\r\n // ----------------------------------\r\n\r\n async markExecuted(\r\n executionId: string\r\n ): Promise<void> {\r\n\r\n await this.pool.query(\r\n `\r\n UPDATE audit_decisions\r\n\r\n SET execution_state = 'completed'\r\n\r\n WHERE execution_id = $1\r\n `,\r\n [executionId]\r\n );\r\n }\r\n\r\n // ----------------------------------\r\n // Cleanup\r\n // ----------------------------------\r\n\r\n async close(): Promise<void> {\r\n\r\n await this.pool.end();\r\n }\r\n}","import type { PoolClient } from \"pg\";\r\n\r\n// Inlined so the bundle has no runtime fs dependency.\r\n// The canonical reference lives in schema.sql alongside this file.\r\nconst SCHEMA_SQL = `\r\nCREATE TABLE IF NOT EXISTS audit_decisions (\r\n id BIGSERIAL PRIMARY KEY,\r\n execution_id UUID NOT NULL UNIQUE,\r\n policy_id TEXT NOT NULL,\r\n policy_version TEXT NOT NULL,\r\n schema_version TEXT NOT NULL,\r\n runtime_version TEXT NOT NULL,\r\n runtime_hash TEXT NOT NULL,\r\n decision TEXT NOT NULL,\r\n signals_hash TEXT NOT NULL,\r\n signature TEXT NOT NULL,\r\n attestation JSONB NOT NULL,\r\n executed_at TIMESTAMPTZ NOT NULL,\r\n recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_policy_id\r\n ON audit_decisions (policy_id);\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_executed_at\r\n ON audit_decisions (executed_at DESC);\r\nCREATE INDEX IF NOT EXISTS idx_audit_decisions_decision\r\n ON audit_decisions (decision);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_verifications (\r\n id BIGSERIAL PRIMARY KEY,\r\n execution_id UUID NOT NULL,\r\n valid BOOLEAN NOT NULL,\r\n signature_verified BOOLEAN NOT NULL,\r\n runtime_verified BOOLEAN NOT NULL,\r\n schema_compatible BOOLEAN NOT NULL,\r\n verified_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_execution_id\r\n ON audit_verifications (execution_id);\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_valid\r\n ON audit_verifications (valid);\r\nCREATE INDEX IF NOT EXISTS idx_audit_verifications_verified_at\r\n ON audit_verifications (verified_at DESC);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_security_events (\r\n id BIGSERIAL PRIMARY KEY,\r\n event_type TEXT NOT NULL,\r\n severity TEXT NOT NULL CHECK (severity IN ('low','medium','high','critical')),\r\n ip_address TEXT,\r\n path TEXT,\r\n method TEXT,\r\n user_agent TEXT,\r\n details JSONB,\r\n occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_event_type\r\n ON audit_security_events (event_type);\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_severity\r\n ON audit_security_events (severity);\r\nCREATE INDEX IF NOT EXISTS idx_audit_security_events_occurred_at\r\n ON audit_security_events (occurred_at DESC);\r\n\r\nCREATE TABLE IF NOT EXISTS audit_api_access (\r\n id BIGSERIAL PRIMARY KEY,\r\n method TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n status_code INTEGER NOT NULL,\r\n response_time_ms INTEGER,\r\n ip_address TEXT,\r\n user_agent TEXT,\r\n execution_id UUID,\r\n accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\r\n);\r\n\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_path\r\n ON audit_api_access (path);\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_status_code\r\n ON audit_api_access (status_code);\r\nCREATE INDEX IF NOT EXISTS idx_audit_api_access_accessed_at\r\n ON audit_api_access (accessed_at DESC);\r\n\r\nCREATE OR REPLACE VIEW view_decision_timeline AS\r\nSELECT\r\n d.execution_id,\r\n d.policy_id,\r\n d.policy_version,\r\n d.decision,\r\n d.runtime_version,\r\n d.runtime_hash,\r\n d.executed_at,\r\n d.recorded_at,\r\n v.valid AS verification_valid,\r\n v.signature_verified,\r\n v.runtime_verified,\r\n v.schema_compatible,\r\n v.verified_at\r\nFROM audit_decisions d\r\nLEFT JOIN audit_verifications v ON d.execution_id = v.execution_id;\r\n\r\nCREATE OR REPLACE VIEW view_security_dashboard AS\r\nSELECT\r\n event_type,\r\n severity,\r\n COUNT(*) AS event_count,\r\n MAX(occurred_at) AS last_occurrence,\r\n MIN(occurred_at) AS first_occurrence\r\nFROM audit_security_events\r\nGROUP BY event_type, severity;\r\n`;\r\n\r\nexport async function runMigrations(client: PoolClient): Promise<void> {\r\n await client.query(\"BEGIN\");\r\n try {\r\n await client.query(SCHEMA_SQL);\r\n await client.query(\"COMMIT\");\r\n } catch (err) {\r\n await client.query(\"ROLLBACK\");\r\n throw err;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parmanasystems/audit-db",
3
- "version": "1.5.0",
3
+ "version": "1.9.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "PostgreSQL audit database client for parmanasystems governance decisions, verifications, and security events.",
@@ -19,8 +19,8 @@
19
19
  ],
20
20
  "sideEffects": false,
21
21
  "dependencies": {
22
- "@parmanasystems/execution": "^1.5.0",
23
- "@parmanasystems/verifier": "^1.5.0",
22
+ "@parmanasystems/execution": "^1.9.0",
23
+ "@parmanasystems/verifier": "^1.9.0",
24
24
  "pg": "^8.13.3"
25
25
  },
26
26
  "devDependencies": {