@splitin/verification-postgres 0.1.0-beta.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.js ADDED
@@ -0,0 +1,1487 @@
1
+ import { backoffSeconds, hmacSha256Hex } from '@splitin/verification-engine';
2
+
3
+ // src/catalog.ts
4
+ var SCHEMA_NAME = "verification";
5
+ var REQUIRED_TABLES = [
6
+ "tenants",
7
+ "configuration_revisions",
8
+ "provider_definitions",
9
+ "routes",
10
+ "route_change_requests",
11
+ "policy_versions",
12
+ "protected_action_requirements",
13
+ "attempts",
14
+ "provider_resource_lineage",
15
+ "decisions",
16
+ "idempotency_claims",
17
+ "webhook_events",
18
+ "webhook_leases",
19
+ "reconciliation_jobs",
20
+ "redaction_jobs",
21
+ "provider_health_observations",
22
+ "circuits",
23
+ "appeals",
24
+ "review_cases",
25
+ "manual_decision_proposals",
26
+ "audit_events",
27
+ "continuations"
28
+ ];
29
+ var REDACTION_STATUSES = [
30
+ "scheduled",
31
+ "processing",
32
+ "retryable",
33
+ "redacted",
34
+ "not_applicable",
35
+ "dead_letter"
36
+ ];
37
+
38
+ // src/executor.ts
39
+ var RecordingExecutor = class {
40
+ statements = [];
41
+ responses = [];
42
+ async query(sql, params = []) {
43
+ this.statements.push({ sql, params: [...params] });
44
+ const hit = [...this.responses].reverse().find((item) => sql.includes(item.match));
45
+ const rows = hit?.rows ?? [];
46
+ return { rows, rowCount: rows.length };
47
+ }
48
+ };
49
+ function iso(now) {
50
+ return now().toISOString();
51
+ }
52
+ function first(rows) {
53
+ return rows[0] ?? null;
54
+ }
55
+ function createPostgresStore(executor, options) {
56
+ if (!options.hashSecret) {
57
+ throw new Error("createPostgresStore requires an injected HMAC hash secret.");
58
+ }
59
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
60
+ const store = {
61
+ now,
62
+ async hashSubject(tenantKey, subjectReference) {
63
+ return hmacSha256Hex(options.hashSecret, `subject:${tenantKey}:${subjectReference}`);
64
+ },
65
+ async hashResource(tenantKey, resourceType, resourceReference) {
66
+ return hmacSha256Hex(options.hashSecret, `resource:${tenantKey}:${resourceType}:${resourceReference}`);
67
+ },
68
+ async transact(fn) {
69
+ await executor.query("BEGIN");
70
+ try {
71
+ const result = await fn(store);
72
+ await executor.query("COMMIT");
73
+ return result;
74
+ } catch (error) {
75
+ await executor.query("ROLLBACK");
76
+ throw error;
77
+ }
78
+ },
79
+ async getTenant(tenantKey) {
80
+ const result = await executor.query(
81
+ "SELECT tenant_key, display_name, continuation_destinations, created_at FROM verification.tenants WHERE tenant_key = $1",
82
+ [tenantKey]
83
+ );
84
+ return mapTenant(first(result.rows));
85
+ },
86
+ async ensureTenant(tenantKey, displayName = tenantKey) {
87
+ await executor.query(
88
+ `INSERT INTO verification.tenants (tenant_key, display_name)
89
+ VALUES ($1, $2) ON CONFLICT (tenant_key) DO NOTHING`,
90
+ [tenantKey, displayName]
91
+ );
92
+ return await store.getTenant(tenantKey) ?? {
93
+ tenantKey,
94
+ displayName,
95
+ continuationDestinations: ["verification.resume"],
96
+ createdAt: iso(now)
97
+ };
98
+ },
99
+ async getConfigurationRevision(tenantKey, id) {
100
+ const result = await executor.query(
101
+ "SELECT * FROM verification.configuration_revisions WHERE tenant_key = $1 AND id = $2",
102
+ [tenantKey, id]
103
+ );
104
+ return mapConfig(first(result.rows));
105
+ },
106
+ async listConfigurationRevisions(tenantKey) {
107
+ const result = await executor.query(
108
+ "SELECT * FROM verification.configuration_revisions WHERE tenant_key = $1",
109
+ [tenantKey]
110
+ );
111
+ return result.rows.map((row) => mapConfig(row));
112
+ },
113
+ async saveConfigurationRevision(revision) {
114
+ await executor.query(
115
+ `INSERT INTO verification.configuration_revisions (
116
+ tenant_key, id, provider, environment, revision, configuration_digest, lifecycle,
117
+ proposed_by_actor_id, approved_by_actor_id, approved_at, created_at
118
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
119
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
120
+ lifecycle = EXCLUDED.lifecycle,
121
+ approved_by_actor_id = EXCLUDED.approved_by_actor_id,
122
+ approved_at = EXCLUDED.approved_at`,
123
+ [
124
+ revision.tenantKey,
125
+ revision.id,
126
+ revision.provider,
127
+ revision.environment,
128
+ revision.revision,
129
+ revision.configurationDigest,
130
+ revision.lifecycle,
131
+ revision.proposedByActorId,
132
+ revision.approvedByActorId,
133
+ revision.approvedAt,
134
+ revision.createdAt
135
+ ]
136
+ );
137
+ },
138
+ async upsertProviderDefinition(definition) {
139
+ await executor.query(
140
+ `INSERT INTO verification.provider_definitions (
141
+ tenant_key, provider, environment, adapter_version, manifest_digest,
142
+ compiled_in_registry, production_eligible, created_at, updated_at
143
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
144
+ ON CONFLICT (tenant_key, provider, environment) DO UPDATE SET
145
+ adapter_version = EXCLUDED.adapter_version,
146
+ manifest_digest = EXCLUDED.manifest_digest,
147
+ updated_at = EXCLUDED.updated_at`,
148
+ [
149
+ definition.tenantKey,
150
+ definition.provider,
151
+ definition.environment,
152
+ definition.adapterVersion,
153
+ definition.manifestDigest,
154
+ definition.compiledInRegistry,
155
+ definition.productionEligible,
156
+ definition.createdAt,
157
+ definition.updatedAt
158
+ ]
159
+ );
160
+ },
161
+ async getProviderDefinition(tenantKey, provider, environment) {
162
+ const result = await executor.query(
163
+ "SELECT * FROM verification.provider_definitions WHERE tenant_key = $1 AND provider = $2 AND environment = $3",
164
+ [tenantKey, provider, environment]
165
+ );
166
+ return mapProviderDef(first(result.rows));
167
+ },
168
+ async listProviderDefinitions(tenantKey) {
169
+ const result = await executor.query(
170
+ "SELECT * FROM verification.provider_definitions WHERE tenant_key = $1",
171
+ [tenantKey]
172
+ );
173
+ return result.rows.map((row) => mapProviderDef(row));
174
+ },
175
+ async getRoute(tenantKey, routeId) {
176
+ const result = await executor.query(
177
+ "SELECT * FROM verification.routes WHERE tenant_key = $1 AND id = $2",
178
+ [tenantKey, routeId]
179
+ );
180
+ return mapRoute(first(result.rows));
181
+ },
182
+ async listRoutes(tenantKey) {
183
+ const result = await executor.query("SELECT * FROM verification.routes WHERE tenant_key = $1", [tenantKey]);
184
+ return result.rows.map((row) => mapRoute(row));
185
+ },
186
+ async listActiveRoutes(tenantKey, environment) {
187
+ const result = await executor.query(
188
+ `SELECT * FROM verification.routes
189
+ WHERE tenant_key = $1 AND environment = $2 AND lifecycle = 'active'
190
+ ORDER BY priority, id`,
191
+ [tenantKey, environment]
192
+ );
193
+ return result.rows.map((row) => mapRoute(row));
194
+ },
195
+ async saveRoute(route) {
196
+ await executor.query(
197
+ `INSERT INTO verification.routes (
198
+ tenant_key, id, provider, environment, package_code, country_code, required_capability,
199
+ priority, cohort_min, cohort_max, window_start, window_end, allowlist_required,
200
+ allowlisted_subject_hashes, configuration_revision_id, policy_version_id, lifecycle,
201
+ proposed_by_actor_id, approved_by_actor_id, approved_at, activated_at, created_at, updated_at
202
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23)
203
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
204
+ lifecycle = EXCLUDED.lifecycle, priority = EXCLUDED.priority,
205
+ approved_by_actor_id = EXCLUDED.approved_by_actor_id, approved_at = EXCLUDED.approved_at,
206
+ activated_at = EXCLUDED.activated_at, updated_at = EXCLUDED.updated_at`,
207
+ [
208
+ route.tenantKey,
209
+ route.id,
210
+ route.provider,
211
+ route.environment,
212
+ route.packageCode,
213
+ route.countryCode,
214
+ route.requiredCapability,
215
+ route.priority,
216
+ route.cohortMin,
217
+ route.cohortMax,
218
+ route.windowStart,
219
+ route.windowEnd,
220
+ route.allowlistRequired,
221
+ route.allowlistedSubjectHashes,
222
+ route.configurationRevisionId,
223
+ route.policyVersionId,
224
+ route.lifecycle,
225
+ route.proposedByActorId,
226
+ route.approvedByActorId,
227
+ route.approvedAt,
228
+ route.activatedAt,
229
+ route.createdAt,
230
+ route.updatedAt
231
+ ]
232
+ );
233
+ },
234
+ async saveRouteChangeRequest(request) {
235
+ await executor.query(
236
+ `INSERT INTO verification.route_change_requests (
237
+ tenant_key, id, route_id, proposed_payload, status, reason, policy_version,
238
+ proposed_by_actor_id, approved_by_actor_id, approved_at, expires_at, created_at
239
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
240
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
241
+ status = EXCLUDED.status, approved_by_actor_id = EXCLUDED.approved_by_actor_id, approved_at = EXCLUDED.approved_at`,
242
+ [
243
+ request.tenantKey,
244
+ request.id,
245
+ request.routeId,
246
+ request.proposedPayload,
247
+ request.status,
248
+ request.reason,
249
+ request.policyVersion,
250
+ request.proposedByActorId,
251
+ request.approvedByActorId,
252
+ request.approvedAt,
253
+ request.expiresAt,
254
+ request.createdAt
255
+ ]
256
+ );
257
+ },
258
+ async getRouteChangeRequest(tenantKey, id) {
259
+ const result = await executor.query(
260
+ "SELECT * FROM verification.route_change_requests WHERE tenant_key = $1 AND id = $2",
261
+ [tenantKey, id]
262
+ );
263
+ return mapRouteChange(first(result.rows));
264
+ },
265
+ async listRouteChangeRequests(tenantKey) {
266
+ const result = await executor.query(
267
+ "SELECT * FROM verification.route_change_requests WHERE tenant_key = $1",
268
+ [tenantKey]
269
+ );
270
+ return result.rows.map((row) => mapRouteChange(row));
271
+ },
272
+ async getActivePolicy(tenantKey, environment) {
273
+ const result = await executor.query(
274
+ `SELECT * FROM verification.policy_versions
275
+ WHERE tenant_key = $1 AND environment = $2 AND lifecycle = 'active'`,
276
+ [tenantKey, environment]
277
+ );
278
+ return mapPolicy(first(result.rows));
279
+ },
280
+ async getPolicyVersion(tenantKey, id) {
281
+ const result = await executor.query(
282
+ "SELECT * FROM verification.policy_versions WHERE tenant_key = $1 AND id = $2",
283
+ [tenantKey, id]
284
+ );
285
+ return mapPolicy(first(result.rows));
286
+ },
287
+ async listPolicyVersions(tenantKey) {
288
+ const result = await executor.query(
289
+ "SELECT * FROM verification.policy_versions WHERE tenant_key = $1",
290
+ [tenantKey]
291
+ );
292
+ return result.rows.map((row) => mapPolicy(row));
293
+ },
294
+ async savePolicyVersion(policy) {
295
+ await executor.query(
296
+ `INSERT INTO verification.policy_versions (
297
+ tenant_key, id, version, environment, lifecycle, reason, expires_at,
298
+ proposed_by_actor_id, approved_by_actor_id, approved_at, activated_at, created_at,
299
+ decision_retention_days, provider_redaction_delay_days, appeal_hold_days, legal_hold
300
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
301
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
302
+ lifecycle = EXCLUDED.lifecycle, approved_by_actor_id = EXCLUDED.approved_by_actor_id,
303
+ approved_at = EXCLUDED.approved_at, activated_at = EXCLUDED.activated_at, reason = EXCLUDED.reason,
304
+ decision_retention_days = EXCLUDED.decision_retention_days,
305
+ provider_redaction_delay_days = EXCLUDED.provider_redaction_delay_days,
306
+ appeal_hold_days = EXCLUDED.appeal_hold_days,
307
+ legal_hold = EXCLUDED.legal_hold`,
308
+ [
309
+ policy.tenantKey,
310
+ policy.id,
311
+ policy.version,
312
+ policy.environment,
313
+ policy.lifecycle,
314
+ policy.reason,
315
+ policy.expiresAt,
316
+ policy.proposedByActorId,
317
+ policy.approvedByActorId,
318
+ policy.approvedAt,
319
+ policy.activatedAt,
320
+ policy.createdAt,
321
+ policy.decisionRetentionDays,
322
+ policy.providerRedactionDelayDays,
323
+ policy.appealHoldDays,
324
+ policy.legalHold
325
+ ]
326
+ );
327
+ },
328
+ async listProtectedActionRequirements(tenantKey, action, policyVersionId) {
329
+ const result = await executor.query(
330
+ `SELECT * FROM verification.protected_action_requirements
331
+ WHERE tenant_key = $1 AND action = $2 AND policy_version_id = $3`,
332
+ [tenantKey, action, policyVersionId]
333
+ );
334
+ return result.rows.map((row) => mapRequirement(row));
335
+ },
336
+ async saveProtectedActionRequirement(requirement) {
337
+ await executor.query(
338
+ `INSERT INTO verification.protected_action_requirements (
339
+ tenant_key, id, action, package_code, policy_version_id, created_at
340
+ ) VALUES ($1,$2,$3,$4,$5,$6)
341
+ ON CONFLICT (tenant_key, id) DO NOTHING`,
342
+ [
343
+ requirement.tenantKey,
344
+ requirement.id,
345
+ requirement.action,
346
+ requirement.packageCode,
347
+ requirement.policyVersionId,
348
+ requirement.createdAt
349
+ ]
350
+ );
351
+ },
352
+ async getContinuationDestinations(tenantKey) {
353
+ const tenant = await store.getTenant(tenantKey);
354
+ return tenant?.continuationDestinations ?? ["verification.resume"];
355
+ },
356
+ async getAttempt(tenantKey, attemptId) {
357
+ const result = await executor.query(
358
+ "SELECT * FROM verification.attempts WHERE tenant_key = $1 AND id = $2",
359
+ [tenantKey, attemptId]
360
+ );
361
+ return mapAttempt(first(result.rows));
362
+ },
363
+ async getAttemptByIdempotencyKey(tenantKey, key) {
364
+ const result = await executor.query(
365
+ "SELECT * FROM verification.attempts WHERE tenant_key = $1 AND idempotency_key = $2",
366
+ [tenantKey, key]
367
+ );
368
+ return mapAttempt(first(result.rows));
369
+ },
370
+ async findAttemptByProviderResource(tenantKey, provider, providerResourceId) {
371
+ const result = await executor.query(
372
+ `SELECT * FROM verification.attempts
373
+ WHERE tenant_key = $1 AND provider = $2 AND provider_resource_id = $3`,
374
+ [tenantKey, provider, providerResourceId]
375
+ );
376
+ return mapAttempt(first(result.rows));
377
+ },
378
+ async listAttempts(tenantKey) {
379
+ const result = await executor.query("SELECT * FROM verification.attempts WHERE tenant_key = $1", [tenantKey]);
380
+ return result.rows.map((row) => mapAttempt(row));
381
+ },
382
+ async listLiveAttempts(tenantKey, subjectHash, packageCode) {
383
+ const result = await executor.query(
384
+ `SELECT * FROM verification.attempts
385
+ WHERE tenant_key = $1 AND subject_hash = $2 AND package_code = $3
386
+ AND canonical_status IN ('created','pending_user_input','paused','processing','manual_review_required')`,
387
+ [tenantKey, subjectHash, packageCode]
388
+ );
389
+ return result.rows.map((row) => mapAttempt(row));
390
+ },
391
+ async insertAttempt(attempt) {
392
+ await executor.query(
393
+ `INSERT INTO verification.attempts (
394
+ tenant_key, id, subject_hash, package_code, country_code, provider, environment,
395
+ adapter_version, manifest_digest, configuration_revision, policy_version,
396
+ provider_resource_id, provider_status, canonical_status, status_version, idempotency_key,
397
+ parent_attempt_id, purpose_action, purpose_resource_hash, route_id, selection_reason,
398
+ normalized_reason_codes, expires_at, create_claim_id, create_claim_expires_at, created_at, updated_at
399
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27)`,
400
+ [
401
+ attempt.tenantKey,
402
+ attempt.id,
403
+ attempt.subjectHash,
404
+ attempt.packageCode,
405
+ attempt.countryCode,
406
+ attempt.provider,
407
+ attempt.environment,
408
+ attempt.adapterVersion,
409
+ attempt.manifestDigest,
410
+ attempt.configurationRevision,
411
+ attempt.policyVersion,
412
+ attempt.providerResourceId,
413
+ attempt.providerStatus,
414
+ attempt.canonicalStatus,
415
+ attempt.statusVersion,
416
+ attempt.idempotencyKey,
417
+ attempt.parentAttemptId,
418
+ attempt.purposeAction,
419
+ attempt.purposeResourceHash,
420
+ attempt.routeId,
421
+ attempt.selectionReason,
422
+ attempt.normalizedReasonCodes,
423
+ attempt.expiresAt,
424
+ attempt.createClaimId,
425
+ attempt.createClaimExpiresAt,
426
+ attempt.createdAt,
427
+ attempt.updatedAt
428
+ ]
429
+ );
430
+ return attempt;
431
+ },
432
+ async updateAttempt(attempt) {
433
+ await executor.query(
434
+ `UPDATE verification.attempts SET
435
+ provider_resource_id = $3, provider_status = $4, canonical_status = $5, status_version = $6,
436
+ normalized_reason_codes = $7, expires_at = $8, create_claim_id = $9, create_claim_expires_at = $10,
437
+ parent_attempt_id = $11, selection_reason = $12, updated_at = $13
438
+ WHERE tenant_key = $1 AND id = $2`,
439
+ [
440
+ attempt.tenantKey,
441
+ attempt.id,
442
+ attempt.providerResourceId,
443
+ attempt.providerStatus,
444
+ attempt.canonicalStatus,
445
+ attempt.statusVersion,
446
+ attempt.normalizedReasonCodes,
447
+ attempt.expiresAt,
448
+ attempt.createClaimId,
449
+ attempt.createClaimExpiresAt,
450
+ attempt.parentAttemptId,
451
+ attempt.selectionReason,
452
+ attempt.updatedAt
453
+ ]
454
+ );
455
+ },
456
+ async insertLineage(row) {
457
+ await executor.query(
458
+ `INSERT INTO verification.provider_resource_lineage (
459
+ tenant_key, id, attempt_id, resource_type, provider_resource_id, relationship_code, provider_status, occurred_at
460
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
461
+ ON CONFLICT (tenant_key, provider_resource_id, resource_type) DO NOTHING`,
462
+ [
463
+ row.tenantKey,
464
+ row.id,
465
+ row.attemptId,
466
+ row.resourceType,
467
+ row.providerResourceId,
468
+ row.relationshipCode,
469
+ row.providerStatus,
470
+ row.occurredAt
471
+ ]
472
+ );
473
+ },
474
+ async listLineage(tenantKey, attemptId) {
475
+ const result = await executor.query(
476
+ "SELECT * FROM verification.provider_resource_lineage WHERE tenant_key = $1 AND attempt_id = $2",
477
+ [tenantKey, attemptId]
478
+ );
479
+ return result.rows.map((row) => mapLineage(row));
480
+ },
481
+ async getValidDecision(tenantKey, subjectHash, packageCode, at) {
482
+ const result = await executor.query(
483
+ `SELECT * FROM verification.decisions
484
+ WHERE tenant_key = $1 AND subject_hash = $2 AND package_code = $3
485
+ AND status = 'verified' AND revoked_at IS NULL
486
+ AND (expires_at IS NULL OR expires_at > $4)
487
+ ORDER BY effective_at DESC LIMIT 1`,
488
+ [tenantKey, subjectHash, packageCode, at.toISOString()]
489
+ );
490
+ return mapDecision(first(result.rows));
491
+ },
492
+ async insertDecision(decision) {
493
+ await executor.query(
494
+ `INSERT INTO verification.decisions (
495
+ tenant_key, id, subject_hash, package_code, attempt_id, status, source, policy_version,
496
+ reason_codes, effective_at, expires_at, revoked_at, proposer_actor_id, approver_actor_id, created_at
497
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`,
498
+ [
499
+ decision.tenantKey,
500
+ decision.id,
501
+ decision.subjectHash,
502
+ decision.packageCode,
503
+ decision.attemptId,
504
+ decision.status,
505
+ decision.source,
506
+ decision.policyVersion,
507
+ decision.reasonCodes,
508
+ decision.effectiveAt,
509
+ decision.expiresAt,
510
+ decision.revokedAt,
511
+ decision.proposerActorId,
512
+ decision.approverActorId,
513
+ decision.createdAt
514
+ ]
515
+ );
516
+ },
517
+ async listDecisions(tenantKey, subjectHash) {
518
+ const result = await executor.query(
519
+ subjectHash ? "SELECT * FROM verification.decisions WHERE tenant_key = $1 AND subject_hash = $2" : "SELECT * FROM verification.decisions WHERE tenant_key = $1",
520
+ subjectHash ? [tenantKey, subjectHash] : [tenantKey]
521
+ );
522
+ return result.rows.map((row) => mapDecision(row));
523
+ },
524
+ async revokeDecision(tenantKey, decisionId, at) {
525
+ await executor.query(
526
+ `UPDATE verification.decisions SET status = 'revoked', revoked_at = $3
527
+ WHERE tenant_key = $1 AND id = $2`,
528
+ [tenantKey, decisionId, at]
529
+ );
530
+ },
531
+ async claimIdempotency(claim) {
532
+ const inserted = await executor.query(
533
+ `INSERT INTO verification.idempotency_claims (
534
+ tenant_key, claim_key, operation, attempt_id, state, result_ref, error_code, created_at
535
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
536
+ ON CONFLICT (tenant_key, claim_key) DO NOTHING
537
+ RETURNING *`,
538
+ [
539
+ claim.tenantKey,
540
+ claim.claimKey,
541
+ claim.operation,
542
+ claim.attemptId,
543
+ claim.state,
544
+ claim.resultRef,
545
+ claim.errorCode,
546
+ claim.createdAt
547
+ ]
548
+ );
549
+ if (inserted.rows[0]) return { disposition: "claimed", claim: mapIdempotency(inserted.rows[0]) };
550
+ const existing = await executor.query(
551
+ "SELECT * FROM verification.idempotency_claims WHERE tenant_key = $1 AND claim_key = $2",
552
+ [claim.tenantKey, claim.claimKey]
553
+ );
554
+ return { disposition: "existing", claim: mapIdempotency(first(existing.rows)) ?? claim };
555
+ },
556
+ async completeIdempotency(tenantKey, key, resultRef) {
557
+ await executor.query(
558
+ `UPDATE verification.idempotency_claims
559
+ SET state = 'completed', result_ref = $3, completed_at = $4
560
+ WHERE tenant_key = $1 AND claim_key = $2`,
561
+ [tenantKey, key, resultRef, iso(now)]
562
+ );
563
+ },
564
+ async failIdempotency(tenantKey, key, errorCode) {
565
+ await executor.query(
566
+ `UPDATE verification.idempotency_claims
567
+ SET state = 'failed', error_code = $3, completed_at = $4
568
+ WHERE tenant_key = $1 AND claim_key = $2`,
569
+ [tenantKey, key, errorCode, iso(now)]
570
+ );
571
+ },
572
+ async getIdempotencyClaim(tenantKey, key) {
573
+ const result = await executor.query(
574
+ "SELECT * FROM verification.idempotency_claims WHERE tenant_key = $1 AND claim_key = $2",
575
+ [tenantKey, key]
576
+ );
577
+ return mapIdempotency(first(result.rows));
578
+ },
579
+ async claimWebhookEvent(input) {
580
+ const inserted = await executor.query(
581
+ `INSERT INTO verification.webhook_events (
582
+ tenant_key, id, provider, provider_event_key, provider_resource_id, event_type,
583
+ occurred_at, body_sha256, safe_metadata, state, received_at
584
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'accepted',$10)
585
+ ON CONFLICT (tenant_key, provider, provider_event_key) DO NOTHING
586
+ RETURNING *`,
587
+ [
588
+ input.tenantKey,
589
+ `wh_${input.providerEventKey}`.slice(0, 64),
590
+ input.provider,
591
+ input.providerEventKey,
592
+ input.providerResourceId,
593
+ input.eventType,
594
+ input.occurredAt,
595
+ input.bodySha256,
596
+ input.safeMetadata,
597
+ iso(now)
598
+ ]
599
+ );
600
+ const row = inserted.rows[0] ?? first((await executor.query(
601
+ `SELECT * FROM verification.webhook_events
602
+ WHERE tenant_key = $1 AND provider = $2 AND provider_event_key = $3`,
603
+ [input.tenantKey, input.provider, input.providerEventKey]
604
+ )).rows);
605
+ const event = mapWebhook(row);
606
+ if (!event) {
607
+ return { disposition: "claimed", event: {
608
+ tenantKey: input.tenantKey,
609
+ id: `wh_${input.providerEventKey}`.slice(0, 64),
610
+ provider: input.provider,
611
+ providerEventKey: input.providerEventKey,
612
+ providerResourceId: input.providerResourceId,
613
+ eventType: input.eventType,
614
+ occurredAt: input.occurredAt,
615
+ bodySha256: input.bodySha256,
616
+ safeMetadata: input.safeMetadata,
617
+ state: "accepted",
618
+ receivedAt: iso(now)
619
+ } };
620
+ }
621
+ if (event.bodySha256 !== input.bodySha256) {
622
+ await executor.query(
623
+ `UPDATE verification.webhook_events SET state = 'dead_letter'
624
+ WHERE tenant_key = $1 AND id = $2`,
625
+ [input.tenantKey, event.id]
626
+ );
627
+ return { disposition: "mismatch", event: { ...event, state: "dead_letter" } };
628
+ }
629
+ if (!inserted.rows[0]) return { disposition: "duplicate", event };
630
+ return { disposition: "claimed", event };
631
+ },
632
+ async getWebhookEvent(tenantKey, provider, eventKey) {
633
+ const result = await executor.query(
634
+ `SELECT * FROM verification.webhook_events
635
+ WHERE tenant_key = $1 AND provider = $2 AND provider_event_key = $3`,
636
+ [tenantKey, provider, eventKey]
637
+ );
638
+ return mapWebhook(first(result.rows));
639
+ },
640
+ async getWebhookEventById(tenantKey, eventId) {
641
+ const result = await executor.query(
642
+ "SELECT * FROM verification.webhook_events WHERE tenant_key = $1 AND id = $2",
643
+ [tenantKey, eventId]
644
+ );
645
+ return mapWebhook(first(result.rows));
646
+ },
647
+ async settleWebhookEvent(tenantKey, eventId, outcome, errorCode) {
648
+ await executor.query(
649
+ `UPDATE verification.webhook_events SET state = $3
650
+ WHERE tenant_key = $1 AND id = $2`,
651
+ [tenantKey, eventId, outcome]
652
+ );
653
+ if (errorCode) {
654
+ await executor.query(
655
+ `UPDATE verification.webhook_leases SET last_error_code = $3
656
+ WHERE tenant_key = $1 AND event_id = $2`,
657
+ [tenantKey, eventId, errorCode]
658
+ );
659
+ }
660
+ },
661
+ async recordHealth(observation) {
662
+ await executor.query(
663
+ `INSERT INTO verification.provider_health_observations (
664
+ tenant_key, id, provider, environment, operation, outcome, safe_code, observed_at, latency_ms
665
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
666
+ [
667
+ observation.tenantKey,
668
+ observation.id,
669
+ observation.provider,
670
+ observation.environment,
671
+ observation.operation,
672
+ observation.outcome,
673
+ observation.safeCode,
674
+ observation.observedAt,
675
+ observation.latencyMs
676
+ ]
677
+ );
678
+ },
679
+ async listHealth(tenantKey, provider) {
680
+ const result = await executor.query(
681
+ provider ? "SELECT * FROM verification.provider_health_observations WHERE tenant_key = $1 AND provider = $2" : "SELECT * FROM verification.provider_health_observations WHERE tenant_key = $1",
682
+ provider ? [tenantKey, provider] : [tenantKey]
683
+ );
684
+ return result.rows.map((row) => mapHealth(row));
685
+ },
686
+ async getCircuit(tenantKey, provider, environment) {
687
+ const result = await executor.query(
688
+ "SELECT * FROM verification.circuits WHERE tenant_key = $1 AND provider = $2 AND environment = $3",
689
+ [tenantKey, provider, environment]
690
+ );
691
+ return mapCircuit(first(result.rows)) ?? {
692
+ tenantKey,
693
+ provider,
694
+ environment,
695
+ state: "closed",
696
+ reasonCode: null,
697
+ openUntil: null,
698
+ consecutiveFailures: 0,
699
+ drainedByActorId: null,
700
+ updatedAt: iso(now)
701
+ };
702
+ },
703
+ async saveCircuit(circuit) {
704
+ await executor.query(
705
+ `INSERT INTO verification.circuits (
706
+ tenant_key, provider, environment, state, reason_code, open_until,
707
+ consecutive_failures, drained_by_actor_id, updated_at
708
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
709
+ ON CONFLICT (tenant_key, provider, environment) DO UPDATE SET
710
+ state = EXCLUDED.state, reason_code = EXCLUDED.reason_code, open_until = EXCLUDED.open_until,
711
+ consecutive_failures = EXCLUDED.consecutive_failures, drained_by_actor_id = EXCLUDED.drained_by_actor_id,
712
+ updated_at = EXCLUDED.updated_at`,
713
+ [
714
+ circuit.tenantKey,
715
+ circuit.provider,
716
+ circuit.environment,
717
+ circuit.state,
718
+ circuit.reasonCode,
719
+ circuit.openUntil,
720
+ circuit.consecutiveFailures,
721
+ circuit.drainedByActorId,
722
+ circuit.updatedAt
723
+ ]
724
+ );
725
+ },
726
+ async listCircuits(tenantKey) {
727
+ const result = await executor.query("SELECT * FROM verification.circuits WHERE tenant_key = $1", [tenantKey]);
728
+ return result.rows.map((row) => mapCircuit(row));
729
+ },
730
+ async saveAppeal(appeal) {
731
+ await executor.query(
732
+ `INSERT INTO verification.appeals (
733
+ tenant_key, id, attempt_id, subject_hash, status, reason, policy_version,
734
+ proposed_by_actor_id, decided_by_actor_id, expires_at, created_at, updated_at
735
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
736
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
737
+ status = EXCLUDED.status, reason = EXCLUDED.reason, decided_by_actor_id = EXCLUDED.decided_by_actor_id,
738
+ updated_at = EXCLUDED.updated_at`,
739
+ [
740
+ appeal.tenantKey,
741
+ appeal.id,
742
+ appeal.attemptId,
743
+ appeal.subjectHash,
744
+ appeal.status,
745
+ appeal.reason,
746
+ appeal.policyVersion,
747
+ appeal.proposedByActorId,
748
+ appeal.decidedByActorId,
749
+ appeal.expiresAt,
750
+ appeal.createdAt,
751
+ appeal.updatedAt
752
+ ]
753
+ );
754
+ },
755
+ async getAppeal(tenantKey, id) {
756
+ const result = await executor.query(
757
+ "SELECT * FROM verification.appeals WHERE tenant_key = $1 AND id = $2",
758
+ [tenantKey, id]
759
+ );
760
+ return mapAppeal(first(result.rows));
761
+ },
762
+ async listAppeals(tenantKey) {
763
+ const result = await executor.query("SELECT * FROM verification.appeals WHERE tenant_key = $1", [tenantKey]);
764
+ return result.rows.map((row) => mapAppeal(row));
765
+ },
766
+ async saveReviewCase(reviewCase) {
767
+ await executor.query(
768
+ `INSERT INTO verification.review_cases (
769
+ tenant_key, id, attempt_id, subject_hash, status, reason, policy_version, assigned_actor_id, created_at, updated_at
770
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
771
+ ON CONFLICT (tenant_key, id) DO UPDATE SET status = EXCLUDED.status, reason = EXCLUDED.reason, updated_at = EXCLUDED.updated_at`,
772
+ [
773
+ reviewCase.tenantKey,
774
+ reviewCase.id,
775
+ reviewCase.attemptId,
776
+ reviewCase.subjectHash,
777
+ reviewCase.status,
778
+ reviewCase.reason,
779
+ reviewCase.policyVersion,
780
+ reviewCase.assignedActorId,
781
+ reviewCase.createdAt,
782
+ reviewCase.updatedAt
783
+ ]
784
+ );
785
+ },
786
+ async getReviewCase(tenantKey, id) {
787
+ const result = await executor.query(
788
+ "SELECT * FROM verification.review_cases WHERE tenant_key = $1 AND id = $2",
789
+ [tenantKey, id]
790
+ );
791
+ return mapReview(first(result.rows));
792
+ },
793
+ async listReviewCases(tenantKey) {
794
+ const result = await executor.query("SELECT * FROM verification.review_cases WHERE tenant_key = $1", [tenantKey]);
795
+ return result.rows.map((row) => mapReview(row));
796
+ },
797
+ async saveManualDecisionProposal(proposal) {
798
+ await executor.query(
799
+ `INSERT INTO verification.manual_decision_proposals (
800
+ tenant_key, id, review_case_id, attempt_id, proposed_status, reason, policy_version,
801
+ expires_at, proposed_by_actor_id, approved_by_actor_id, status, created_at
802
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
803
+ ON CONFLICT (tenant_key, id) DO UPDATE SET
804
+ status = EXCLUDED.status, approved_by_actor_id = EXCLUDED.approved_by_actor_id`,
805
+ [
806
+ proposal.tenantKey,
807
+ proposal.id,
808
+ proposal.reviewCaseId,
809
+ proposal.attemptId,
810
+ proposal.proposedStatus,
811
+ proposal.reason,
812
+ proposal.policyVersion,
813
+ proposal.expiresAt,
814
+ proposal.proposedByActorId,
815
+ proposal.approvedByActorId,
816
+ proposal.status,
817
+ proposal.createdAt
818
+ ]
819
+ );
820
+ },
821
+ async getManualDecisionProposal(tenantKey, id) {
822
+ const result = await executor.query(
823
+ "SELECT * FROM verification.manual_decision_proposals WHERE tenant_key = $1 AND id = $2",
824
+ [tenantKey, id]
825
+ );
826
+ return mapProposal(first(result.rows));
827
+ },
828
+ async listManualDecisionProposals(tenantKey) {
829
+ const result = await executor.query(
830
+ "SELECT * FROM verification.manual_decision_proposals WHERE tenant_key = $1",
831
+ [tenantKey]
832
+ );
833
+ return result.rows.map((row) => mapProposal(row));
834
+ },
835
+ async saveContinuation(continuation) {
836
+ await executor.query(
837
+ `INSERT INTO verification.continuations (
838
+ tenant_key, key, token_hash, action, resource_hash, subject_hash, destination_key, expires_at, consumed_at
839
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
840
+ ON CONFLICT (tenant_key, key) DO UPDATE SET consumed_at = EXCLUDED.consumed_at`,
841
+ [
842
+ continuation.tenantKey,
843
+ continuation.key,
844
+ continuation.tokenHash,
845
+ continuation.action,
846
+ continuation.resourceHash,
847
+ continuation.subjectHash,
848
+ continuation.destinationKey,
849
+ continuation.expiresAt,
850
+ continuation.consumedAt
851
+ ]
852
+ );
853
+ },
854
+ async getContinuation(tenantKey, key) {
855
+ const result = await executor.query(
856
+ "SELECT * FROM verification.continuations WHERE tenant_key = $1 AND key = $2",
857
+ [tenantKey, key]
858
+ );
859
+ const row = first(result.rows);
860
+ if (!row) return null;
861
+ return {
862
+ tenantKey: String(row.tenant_key),
863
+ key: String(row.key),
864
+ tokenHash: String(row.token_hash),
865
+ action: String(row.action),
866
+ resourceHash: String(row.resource_hash),
867
+ subjectHash: String(row.subject_hash),
868
+ destinationKey: String(row.destination_key),
869
+ expiresAt: String(row.expires_at),
870
+ consumedAt: row.consumed_at ? String(row.consumed_at) : null
871
+ };
872
+ },
873
+ async appendAudit(event) {
874
+ await executor.query(
875
+ `INSERT INTO verification.audit_events (
876
+ tenant_key, id, actor_id, actor_type, operation, resource_type, resource_id, reason_code, safe_metadata, occurred_at
877
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`,
878
+ [
879
+ event.tenantKey,
880
+ event.id,
881
+ event.actorId,
882
+ event.actorType,
883
+ event.operation,
884
+ event.resourceType,
885
+ event.resourceId,
886
+ event.reasonCode,
887
+ event.safeMetadata,
888
+ event.occurredAt
889
+ ]
890
+ );
891
+ },
892
+ async listAudit(tenantKey) {
893
+ const result = await executor.query(
894
+ "SELECT * FROM verification.audit_events WHERE tenant_key = $1 ORDER BY occurred_at DESC",
895
+ [tenantKey]
896
+ );
897
+ return result.rows.map((row) => mapAudit(row));
898
+ },
899
+ async saveJob(job) {
900
+ if (job.kind === "webhook") {
901
+ await executor.query(
902
+ `INSERT INTO verification.webhook_leases (
903
+ tenant_key, event_id, next_attempt_at, attempt_count
904
+ ) VALUES ($1,$2,$3,$4)
905
+ ON CONFLICT (tenant_key, event_id) DO NOTHING`,
906
+ [job.tenantKey, job.eventId ?? job.id, job.nextAttemptAt, job.attemptCount]
907
+ );
908
+ return;
909
+ }
910
+ if (job.kind === "redact") {
911
+ await executor.query(
912
+ `INSERT INTO verification.redaction_jobs (
913
+ tenant_key, id, subject_hash, attempt_id, provider_resource_id, status,
914
+ next_attempt_at, attempt_count, created_at
915
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
916
+ ON CONFLICT (tenant_key, id) DO UPDATE SET status = EXCLUDED.status`,
917
+ [
918
+ job.tenantKey,
919
+ job.id,
920
+ job.subjectHash,
921
+ job.attemptId,
922
+ job.providerResourceId,
923
+ job.state,
924
+ job.nextAttemptAt,
925
+ job.attemptCount,
926
+ job.createdAt
927
+ ]
928
+ );
929
+ return;
930
+ }
931
+ await executor.query(
932
+ `INSERT INTO verification.reconciliation_jobs (
933
+ tenant_key, id, attempt_id, state, next_attempt_at, attempt_count, created_at
934
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7)
935
+ ON CONFLICT (tenant_key, id) DO UPDATE SET state = EXCLUDED.state`,
936
+ [job.tenantKey, job.id, job.attemptId, job.state, job.nextAttemptAt, job.attemptCount, job.createdAt]
937
+ );
938
+ },
939
+ async getJob(tenantKey, id) {
940
+ const redaction = await executor.query(
941
+ "SELECT * FROM verification.redaction_jobs WHERE tenant_key = $1 AND id = $2",
942
+ [tenantKey, id]
943
+ );
944
+ if (redaction.rows[0]) return mapRedactionJob(redaction.rows[0]);
945
+ const recon = await executor.query(
946
+ "SELECT * FROM verification.reconciliation_jobs WHERE tenant_key = $1 AND id = $2",
947
+ [tenantKey, id]
948
+ );
949
+ if (recon.rows[0]) return mapReconJob(recon.rows[0]);
950
+ const lease = await executor.query(
951
+ "SELECT * FROM verification.webhook_leases WHERE tenant_key = $1 AND event_id = $2",
952
+ [tenantKey, id]
953
+ );
954
+ return lease.rows[0] ? mapWebhookJob(lease.rows[0]) : null;
955
+ },
956
+ async listJobs(tenantKey, kind) {
957
+ if (kind === "redact") {
958
+ const result2 = await executor.query(
959
+ "SELECT * FROM verification.redaction_jobs WHERE tenant_key = $1",
960
+ [tenantKey]
961
+ );
962
+ return result2.rows.map((row) => mapRedactionJob(row));
963
+ }
964
+ if (kind === "webhook") {
965
+ const result2 = await executor.query(
966
+ "SELECT * FROM verification.webhook_leases WHERE tenant_key = $1",
967
+ [tenantKey]
968
+ );
969
+ return result2.rows.map((row) => mapWebhookJob(row));
970
+ }
971
+ const result = await executor.query(
972
+ "SELECT * FROM verification.reconciliation_jobs WHERE tenant_key = $1",
973
+ [tenantKey]
974
+ );
975
+ return result.rows.map((row) => mapReconJob(row));
976
+ },
977
+ async claimJobs(input) {
978
+ const claimed = [];
979
+ if (input.kinds.includes("webhook")) {
980
+ const result = await executor.query(
981
+ `UPDATE verification.webhook_leases AS target
982
+ SET lease_id = $3, worker_id = $4, expires_at = $5, attempt_count = target.attempt_count + 1
983
+ WHERE (target.tenant_key, target.event_id) IN (
984
+ SELECT tenant_key, event_id FROM verification.webhook_leases
985
+ WHERE tenant_key = $1
986
+ AND next_attempt_at <= $2
987
+ AND (lease_id IS NULL OR expires_at < $2)
988
+ ORDER BY next_attempt_at, event_id
989
+ FOR UPDATE SKIP LOCKED
990
+ LIMIT $6
991
+ )
992
+ RETURNING *`,
993
+ [
994
+ input.tenantKey,
995
+ input.now.toISOString(),
996
+ `lease_${input.workerId}`,
997
+ input.workerId,
998
+ new Date(input.now.getTime() + input.leaseSeconds * 1e3).toISOString(),
999
+ input.limit
1000
+ ]
1001
+ );
1002
+ claimed.push(...result.rows.map((row) => mapWebhookJob(row)));
1003
+ }
1004
+ if (input.kinds.includes("reconcile")) {
1005
+ const result = await executor.query(
1006
+ `UPDATE verification.reconciliation_jobs AS target
1007
+ SET state = 'processing', lease_id = $3, lease_expires_at = $4, attempt_count = target.attempt_count + 1
1008
+ WHERE (target.tenant_key, target.id) IN (
1009
+ SELECT tenant_key, id FROM verification.reconciliation_jobs
1010
+ WHERE tenant_key = $1
1011
+ AND state IN ('scheduled', 'retryable')
1012
+ AND next_attempt_at <= $2
1013
+ ORDER BY next_attempt_at, id
1014
+ FOR UPDATE SKIP LOCKED
1015
+ LIMIT $5
1016
+ )
1017
+ RETURNING *`,
1018
+ [
1019
+ input.tenantKey,
1020
+ input.now.toISOString(),
1021
+ `lease_${input.workerId}`,
1022
+ new Date(input.now.getTime() + input.leaseSeconds * 1e3).toISOString(),
1023
+ input.limit
1024
+ ]
1025
+ );
1026
+ claimed.push(...result.rows.map((row) => mapReconJob(row)));
1027
+ }
1028
+ if (input.kinds.includes("redact")) {
1029
+ const result = await executor.query(
1030
+ `UPDATE verification.redaction_jobs AS target
1031
+ SET status = 'processing', lease_id = $3, lease_expires_at = $4, attempt_count = target.attempt_count + 1
1032
+ WHERE (target.tenant_key, target.id) IN (
1033
+ SELECT tenant_key, id FROM verification.redaction_jobs
1034
+ WHERE tenant_key = $1
1035
+ AND status IN ('scheduled', 'retryable')
1036
+ AND next_attempt_at <= $2
1037
+ ORDER BY next_attempt_at, id
1038
+ FOR UPDATE SKIP LOCKED
1039
+ LIMIT $5
1040
+ )
1041
+ RETURNING *`,
1042
+ [
1043
+ input.tenantKey,
1044
+ input.now.toISOString(),
1045
+ `lease_${input.workerId}`,
1046
+ new Date(input.now.getTime() + input.leaseSeconds * 1e3).toISOString(),
1047
+ input.limit
1048
+ ]
1049
+ );
1050
+ claimed.push(...result.rows.map((row) => mapRedactionJob(row)));
1051
+ }
1052
+ return claimed.slice(0, input.limit);
1053
+ },
1054
+ async updateJob(job) {
1055
+ await store.saveJob(job);
1056
+ if (job.kind === "webhook") {
1057
+ await executor.query(
1058
+ `UPDATE verification.webhook_leases
1059
+ SET lease_id = $3, expires_at = $4, next_attempt_at = $5, last_error_code = $6, attempt_count = $7
1060
+ WHERE tenant_key = $1 AND event_id = $2`,
1061
+ [job.tenantKey, job.eventId ?? job.id, job.leaseId, job.leaseExpiresAt, job.nextAttemptAt, job.lastErrorCode, job.attemptCount]
1062
+ );
1063
+ }
1064
+ },
1065
+ async updateRedactionStatus(tenantKey, jobId, status) {
1066
+ await executor.query(
1067
+ "UPDATE verification.redaction_jobs SET status = $3 WHERE tenant_key = $1 AND id = $2",
1068
+ [tenantKey, jobId, status]
1069
+ );
1070
+ }
1071
+ };
1072
+ return store;
1073
+ }
1074
+ function str(row, key) {
1075
+ return String(row?.[key] ?? "");
1076
+ }
1077
+ function strNull(row, key) {
1078
+ const value = row?.[key];
1079
+ return value == null ? null : String(value);
1080
+ }
1081
+ function num(row, key) {
1082
+ return Number(row?.[key] ?? 0);
1083
+ }
1084
+ function bool(row, key) {
1085
+ return Boolean(row?.[key]);
1086
+ }
1087
+ function arr(row, key) {
1088
+ const value = row?.[key];
1089
+ return Array.isArray(value) ? value.map(String) : [];
1090
+ }
1091
+ function mapTenant(row) {
1092
+ if (!row) return null;
1093
+ return {
1094
+ tenantKey: str(row, "tenant_key"),
1095
+ displayName: str(row, "display_name"),
1096
+ continuationDestinations: arr(row, "continuation_destinations"),
1097
+ createdAt: str(row, "created_at")
1098
+ };
1099
+ }
1100
+ function mapConfig(row) {
1101
+ if (!row) return null;
1102
+ return {
1103
+ tenantKey: str(row, "tenant_key"),
1104
+ id: str(row, "id"),
1105
+ provider: str(row, "provider"),
1106
+ environment: str(row, "environment"),
1107
+ revision: num(row, "revision"),
1108
+ configurationDigest: str(row, "configuration_digest"),
1109
+ lifecycle: str(row, "lifecycle"),
1110
+ proposedByActorId: strNull(row, "proposed_by_actor_id"),
1111
+ approvedByActorId: strNull(row, "approved_by_actor_id"),
1112
+ approvedAt: strNull(row, "approved_at"),
1113
+ createdAt: str(row, "created_at")
1114
+ };
1115
+ }
1116
+ function mapProviderDef(row) {
1117
+ if (!row) return null;
1118
+ return {
1119
+ tenantKey: str(row, "tenant_key"),
1120
+ provider: str(row, "provider"),
1121
+ environment: str(row, "environment"),
1122
+ adapterVersion: str(row, "adapter_version"),
1123
+ manifestDigest: str(row, "manifest_digest"),
1124
+ compiledInRegistry: bool(row, "compiled_in_registry"),
1125
+ productionEligible: bool(row, "production_eligible"),
1126
+ createdAt: str(row, "created_at"),
1127
+ updatedAt: str(row, "updated_at")
1128
+ };
1129
+ }
1130
+ function mapRoute(row) {
1131
+ if (!row) return null;
1132
+ return {
1133
+ tenantKey: str(row, "tenant_key"),
1134
+ id: str(row, "id"),
1135
+ provider: str(row, "provider"),
1136
+ environment: str(row, "environment"),
1137
+ packageCode: str(row, "package_code"),
1138
+ countryCode: strNull(row, "country_code"),
1139
+ requiredCapability: strNull(row, "required_capability"),
1140
+ priority: num(row, "priority"),
1141
+ cohortMin: num(row, "cohort_min"),
1142
+ cohortMax: num(row, "cohort_max"),
1143
+ windowStart: strNull(row, "window_start"),
1144
+ windowEnd: strNull(row, "window_end"),
1145
+ allowlistRequired: bool(row, "allowlist_required"),
1146
+ allowlistedSubjectHashes: arr(row, "allowlisted_subject_hashes"),
1147
+ configurationRevisionId: str(row, "configuration_revision_id"),
1148
+ policyVersionId: str(row, "policy_version_id"),
1149
+ lifecycle: str(row, "lifecycle"),
1150
+ proposedByActorId: strNull(row, "proposed_by_actor_id"),
1151
+ approvedByActorId: strNull(row, "approved_by_actor_id"),
1152
+ approvedAt: strNull(row, "approved_at"),
1153
+ activatedAt: strNull(row, "activated_at"),
1154
+ createdAt: str(row, "created_at"),
1155
+ updatedAt: str(row, "updated_at")
1156
+ };
1157
+ }
1158
+ function mapRouteChange(row) {
1159
+ if (!row) return null;
1160
+ return {
1161
+ tenantKey: str(row, "tenant_key"),
1162
+ id: str(row, "id"),
1163
+ routeId: strNull(row, "route_id"),
1164
+ proposedPayload: row.proposed_payload ?? {},
1165
+ status: str(row, "status"),
1166
+ reason: str(row, "reason"),
1167
+ policyVersion: str(row, "policy_version"),
1168
+ proposedByActorId: str(row, "proposed_by_actor_id"),
1169
+ approvedByActorId: strNull(row, "approved_by_actor_id"),
1170
+ approvedAt: strNull(row, "approved_at"),
1171
+ expiresAt: strNull(row, "expires_at"),
1172
+ createdAt: str(row, "created_at")
1173
+ };
1174
+ }
1175
+ function mapPolicy(row) {
1176
+ if (!row) return null;
1177
+ return {
1178
+ tenantKey: str(row, "tenant_key"),
1179
+ id: str(row, "id"),
1180
+ version: str(row, "version"),
1181
+ environment: str(row, "environment"),
1182
+ lifecycle: str(row, "lifecycle"),
1183
+ reason: str(row, "reason"),
1184
+ expiresAt: strNull(row, "expires_at"),
1185
+ proposedByActorId: strNull(row, "proposed_by_actor_id"),
1186
+ approvedByActorId: strNull(row, "approved_by_actor_id"),
1187
+ approvedAt: strNull(row, "approved_at"),
1188
+ activatedAt: strNull(row, "activated_at"),
1189
+ createdAt: str(row, "created_at"),
1190
+ decisionRetentionDays: row.decision_retention_days == null ? null : num(row, "decision_retention_days"),
1191
+ providerRedactionDelayDays: row.provider_redaction_delay_days == null ? null : num(row, "provider_redaction_delay_days"),
1192
+ appealHoldDays: row.appeal_hold_days == null ? null : num(row, "appeal_hold_days"),
1193
+ legalHold: Boolean(row.legal_hold)
1194
+ };
1195
+ }
1196
+ function mapRequirement(row) {
1197
+ if (!row) return null;
1198
+ return {
1199
+ tenantKey: str(row, "tenant_key"),
1200
+ id: str(row, "id"),
1201
+ action: str(row, "action"),
1202
+ packageCode: str(row, "package_code"),
1203
+ policyVersionId: str(row, "policy_version_id"),
1204
+ createdAt: str(row, "created_at")
1205
+ };
1206
+ }
1207
+ function mapAttempt(row) {
1208
+ if (!row) return null;
1209
+ return {
1210
+ tenantKey: str(row, "tenant_key"),
1211
+ id: str(row, "id"),
1212
+ subjectHash: str(row, "subject_hash"),
1213
+ packageCode: str(row, "package_code"),
1214
+ countryCode: str(row, "country_code"),
1215
+ provider: str(row, "provider"),
1216
+ environment: str(row, "environment"),
1217
+ adapterVersion: str(row, "adapter_version"),
1218
+ manifestDigest: str(row, "manifest_digest"),
1219
+ configurationRevision: str(row, "configuration_revision"),
1220
+ policyVersion: str(row, "policy_version"),
1221
+ providerResourceId: strNull(row, "provider_resource_id"),
1222
+ providerStatus: strNull(row, "provider_status"),
1223
+ canonicalStatus: str(row, "canonical_status"),
1224
+ statusVersion: num(row, "status_version"),
1225
+ idempotencyKey: str(row, "idempotency_key"),
1226
+ parentAttemptId: strNull(row, "parent_attempt_id"),
1227
+ purposeAction: strNull(row, "purpose_action"),
1228
+ purposeResourceHash: strNull(row, "purpose_resource_hash"),
1229
+ routeId: str(row, "route_id"),
1230
+ selectionReason: str(row, "selection_reason"),
1231
+ normalizedReasonCodes: arr(row, "normalized_reason_codes"),
1232
+ expiresAt: strNull(row, "expires_at"),
1233
+ createClaimId: strNull(row, "create_claim_id"),
1234
+ createClaimExpiresAt: strNull(row, "create_claim_expires_at"),
1235
+ createdAt: str(row, "created_at"),
1236
+ updatedAt: str(row, "updated_at")
1237
+ };
1238
+ }
1239
+ function mapLineage(row) {
1240
+ if (!row) return null;
1241
+ return {
1242
+ tenantKey: str(row, "tenant_key"),
1243
+ id: str(row, "id"),
1244
+ attemptId: str(row, "attempt_id"),
1245
+ resourceType: str(row, "resource_type"),
1246
+ providerResourceId: str(row, "provider_resource_id"),
1247
+ relationshipCode: str(row, "relationship_code"),
1248
+ providerStatus: str(row, "provider_status"),
1249
+ occurredAt: str(row, "occurred_at")
1250
+ };
1251
+ }
1252
+ function mapDecision(row) {
1253
+ if (!row) return null;
1254
+ return {
1255
+ tenantKey: str(row, "tenant_key"),
1256
+ id: str(row, "id"),
1257
+ subjectHash: str(row, "subject_hash"),
1258
+ packageCode: str(row, "package_code"),
1259
+ attemptId: strNull(row, "attempt_id"),
1260
+ status: str(row, "status"),
1261
+ source: str(row, "source"),
1262
+ policyVersion: str(row, "policy_version"),
1263
+ reasonCodes: arr(row, "reason_codes"),
1264
+ effectiveAt: str(row, "effective_at"),
1265
+ expiresAt: strNull(row, "expires_at"),
1266
+ revokedAt: strNull(row, "revoked_at"),
1267
+ proposerActorId: strNull(row, "proposer_actor_id"),
1268
+ approverActorId: strNull(row, "approver_actor_id"),
1269
+ createdAt: str(row, "created_at")
1270
+ };
1271
+ }
1272
+ function mapIdempotency(row) {
1273
+ if (!row) return null;
1274
+ return {
1275
+ tenantKey: str(row, "tenant_key"),
1276
+ claimKey: str(row, "claim_key"),
1277
+ operation: str(row, "operation"),
1278
+ attemptId: strNull(row, "attempt_id"),
1279
+ state: str(row, "state"),
1280
+ resultRef: strNull(row, "result_ref"),
1281
+ errorCode: strNull(row, "error_code"),
1282
+ createdAt: str(row, "created_at"),
1283
+ completedAt: strNull(row, "completed_at")
1284
+ };
1285
+ }
1286
+ function mapWebhook(row) {
1287
+ if (!row) return null;
1288
+ return {
1289
+ tenantKey: str(row, "tenant_key"),
1290
+ id: str(row, "id"),
1291
+ provider: str(row, "provider"),
1292
+ providerEventKey: str(row, "provider_event_key"),
1293
+ providerResourceId: str(row, "provider_resource_id"),
1294
+ eventType: str(row, "event_type"),
1295
+ occurredAt: str(row, "occurred_at"),
1296
+ bodySha256: str(row, "body_sha256"),
1297
+ safeMetadata: row.safe_metadata ?? {},
1298
+ state: str(row, "state"),
1299
+ receivedAt: str(row, "received_at")
1300
+ };
1301
+ }
1302
+ function mapHealth(row) {
1303
+ if (!row) return null;
1304
+ return {
1305
+ tenantKey: str(row, "tenant_key"),
1306
+ id: str(row, "id"),
1307
+ provider: str(row, "provider"),
1308
+ environment: str(row, "environment"),
1309
+ operation: str(row, "operation"),
1310
+ outcome: str(row, "outcome"),
1311
+ safeCode: str(row, "safe_code"),
1312
+ observedAt: str(row, "observed_at"),
1313
+ latencyMs: row.latency_ms == null ? null : Number(row.latency_ms)
1314
+ };
1315
+ }
1316
+ function mapCircuit(row) {
1317
+ if (!row) return null;
1318
+ return {
1319
+ tenantKey: str(row, "tenant_key"),
1320
+ provider: str(row, "provider"),
1321
+ environment: str(row, "environment"),
1322
+ state: str(row, "state"),
1323
+ reasonCode: strNull(row, "reason_code"),
1324
+ openUntil: strNull(row, "open_until"),
1325
+ consecutiveFailures: num(row, "consecutive_failures"),
1326
+ drainedByActorId: strNull(row, "drained_by_actor_id"),
1327
+ updatedAt: str(row, "updated_at")
1328
+ };
1329
+ }
1330
+ function mapAppeal(row) {
1331
+ if (!row) return null;
1332
+ return {
1333
+ tenantKey: str(row, "tenant_key"),
1334
+ id: str(row, "id"),
1335
+ attemptId: str(row, "attempt_id"),
1336
+ subjectHash: str(row, "subject_hash"),
1337
+ status: str(row, "status"),
1338
+ reason: str(row, "reason"),
1339
+ policyVersion: str(row, "policy_version"),
1340
+ proposedByActorId: str(row, "proposed_by_actor_id"),
1341
+ decidedByActorId: strNull(row, "decided_by_actor_id"),
1342
+ expiresAt: strNull(row, "expires_at"),
1343
+ createdAt: str(row, "created_at"),
1344
+ updatedAt: str(row, "updated_at")
1345
+ };
1346
+ }
1347
+ function mapReview(row) {
1348
+ if (!row) return null;
1349
+ return {
1350
+ tenantKey: str(row, "tenant_key"),
1351
+ id: str(row, "id"),
1352
+ attemptId: str(row, "attempt_id"),
1353
+ subjectHash: str(row, "subject_hash"),
1354
+ status: str(row, "status"),
1355
+ reason: str(row, "reason"),
1356
+ policyVersion: str(row, "policy_version"),
1357
+ assignedActorId: strNull(row, "assigned_actor_id"),
1358
+ createdAt: str(row, "created_at"),
1359
+ updatedAt: str(row, "updated_at")
1360
+ };
1361
+ }
1362
+ function mapProposal(row) {
1363
+ if (!row) return null;
1364
+ return {
1365
+ tenantKey: str(row, "tenant_key"),
1366
+ id: str(row, "id"),
1367
+ reviewCaseId: strNull(row, "review_case_id"),
1368
+ attemptId: str(row, "attempt_id"),
1369
+ proposedStatus: str(row, "proposed_status"),
1370
+ reason: str(row, "reason"),
1371
+ policyVersion: str(row, "policy_version"),
1372
+ expiresAt: strNull(row, "expires_at"),
1373
+ proposedByActorId: str(row, "proposed_by_actor_id"),
1374
+ approvedByActorId: strNull(row, "approved_by_actor_id"),
1375
+ status: str(row, "status"),
1376
+ createdAt: str(row, "created_at")
1377
+ };
1378
+ }
1379
+ function mapAudit(row) {
1380
+ if (!row) return null;
1381
+ return {
1382
+ tenantKey: str(row, "tenant_key"),
1383
+ id: str(row, "id"),
1384
+ actorId: str(row, "actor_id"),
1385
+ actorType: str(row, "actor_type"),
1386
+ operation: str(row, "operation"),
1387
+ resourceType: str(row, "resource_type"),
1388
+ resourceId: strNull(row, "resource_id"),
1389
+ reasonCode: strNull(row, "reason_code"),
1390
+ safeMetadata: row.safe_metadata ?? {},
1391
+ occurredAt: str(row, "occurred_at")
1392
+ };
1393
+ }
1394
+ function mapRedactionJob(row) {
1395
+ if (!row) return null;
1396
+ return {
1397
+ tenantKey: str(row, "tenant_key"),
1398
+ id: str(row, "id"),
1399
+ kind: "redact",
1400
+ attemptId: strNull(row, "attempt_id"),
1401
+ eventId: null,
1402
+ subjectHash: strNull(row, "subject_hash"),
1403
+ providerResourceId: strNull(row, "provider_resource_id"),
1404
+ state: str(row, "status"),
1405
+ leaseId: strNull(row, "lease_id"),
1406
+ leaseExpiresAt: strNull(row, "lease_expires_at"),
1407
+ attemptCount: num(row, "attempt_count"),
1408
+ nextAttemptAt: str(row, "next_attempt_at"),
1409
+ lastErrorCode: strNull(row, "last_error_code"),
1410
+ createdAt: str(row, "created_at")
1411
+ };
1412
+ }
1413
+ function mapReconJob(row) {
1414
+ if (!row) return null;
1415
+ return {
1416
+ tenantKey: str(row, "tenant_key"),
1417
+ id: str(row, "id"),
1418
+ kind: "reconcile",
1419
+ attemptId: strNull(row, "attempt_id"),
1420
+ eventId: null,
1421
+ subjectHash: null,
1422
+ providerResourceId: null,
1423
+ state: str(row, "state"),
1424
+ leaseId: strNull(row, "lease_id"),
1425
+ leaseExpiresAt: strNull(row, "lease_expires_at"),
1426
+ attemptCount: num(row, "attempt_count"),
1427
+ nextAttemptAt: str(row, "next_attempt_at"),
1428
+ lastErrorCode: strNull(row, "last_error_code"),
1429
+ createdAt: str(row, "created_at")
1430
+ };
1431
+ }
1432
+ function mapWebhookJob(row) {
1433
+ if (!row) return null;
1434
+ return {
1435
+ tenantKey: str(row, "tenant_key"),
1436
+ id: str(row, "event_id"),
1437
+ kind: "webhook",
1438
+ attemptId: null,
1439
+ eventId: str(row, "event_id"),
1440
+ subjectHash: null,
1441
+ providerResourceId: null,
1442
+ state: "processing",
1443
+ leaseId: strNull(row, "lease_id"),
1444
+ leaseExpiresAt: strNull(row, "expires_at"),
1445
+ attemptCount: num(row, "attempt_count"),
1446
+ nextAttemptAt: str(row, "next_attempt_at"),
1447
+ lastErrorCode: strNull(row, "last_error_code"),
1448
+ createdAt: str(row, "next_attempt_at")
1449
+ };
1450
+ }
1451
+ function isStore(value) {
1452
+ return typeof value.claimJobs === "function";
1453
+ }
1454
+ function createPostgresQueue(executorOrStore, options = {}) {
1455
+ const store = isStore(executorOrStore) ? executorOrStore : createPostgresStore(executorOrStore, { hashSecret: options.hashSecret ?? "", now: options.now });
1456
+ const random = options.random ?? Math.random;
1457
+ return {
1458
+ enqueue: (job) => store.saveJob(job),
1459
+ claim: (input) => store.claimJobs(input),
1460
+ async complete(tenantKey, jobId, leaseId) {
1461
+ const job = await store.getJob(tenantKey, jobId);
1462
+ if (!job) return;
1463
+ await store.updateJob({
1464
+ ...job,
1465
+ state: job.kind === "redact" ? "redacted" : "completed",
1466
+ leaseId,
1467
+ leaseExpiresAt: null
1468
+ });
1469
+ },
1470
+ async retry(tenantKey, jobId, leaseId, retry) {
1471
+ const job = await store.getJob(tenantKey, jobId);
1472
+ if (!job) return;
1473
+ const delay = backoffSeconds(job.attemptCount, retry.retryAfterSeconds, random);
1474
+ await store.updateJob({
1475
+ ...job,
1476
+ state: retry.deadLetter ? "dead_letter" : "retryable",
1477
+ leaseId,
1478
+ lastErrorCode: retry.errorCode,
1479
+ nextAttemptAt: new Date(store.now().getTime() + delay * 1e3).toISOString()
1480
+ });
1481
+ }
1482
+ };
1483
+ }
1484
+
1485
+ export { REDACTION_STATUSES, REQUIRED_TABLES, RecordingExecutor, SCHEMA_NAME, createPostgresQueue, createPostgresStore };
1486
+ //# sourceMappingURL=index.js.map
1487
+ //# sourceMappingURL=index.js.map