@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.
@@ -0,0 +1,419 @@
1
+ -- verification schema: provider-neutral persistence for @splitin/verification-engine
2
+ -- Stores normalized statuses, reason codes, timestamps, hashes, and opaque provider IDs only.
3
+ -- Never persist raw webhooks, launch credentials, hosted URLs, documents, selfies, or expanded identity outputs.
4
+
5
+ CREATE SCHEMA IF NOT EXISTS verification;
6
+
7
+ CREATE TABLE verification.tenants (
8
+ tenant_key text PRIMARY KEY,
9
+ display_name text NOT NULL,
10
+ hash_secret_id text NOT NULL DEFAULT 'injected',
11
+ continuation_destinations text[] NOT NULL DEFAULT ARRAY['verification.resume'],
12
+ created_at timestamptz NOT NULL DEFAULT now()
13
+ );
14
+
15
+ CREATE TABLE verification.configuration_revisions (
16
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
17
+ id text NOT NULL,
18
+ provider text NOT NULL CHECK (provider ~ '^[a-z][a-z0-9_]{1,63}$'),
19
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
20
+ revision integer NOT NULL CHECK (revision > 0),
21
+ configuration_digest text NOT NULL CHECK (configuration_digest ~ '^[a-f0-9]{64}$'),
22
+ lifecycle text NOT NULL CHECK (lifecycle IN ('draft', 'approved', 'retired')),
23
+ proposed_by_actor_id text,
24
+ approved_by_actor_id text,
25
+ approved_at timestamptz,
26
+ created_at timestamptz NOT NULL DEFAULT now(),
27
+ PRIMARY KEY (tenant_key, id),
28
+ UNIQUE (tenant_key, provider, environment, revision),
29
+ CHECK (proposed_by_actor_id IS NULL OR approved_by_actor_id IS NULL OR proposed_by_actor_id <> approved_by_actor_id)
30
+ );
31
+
32
+ CREATE TABLE verification.provider_definitions (
33
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
34
+ provider text NOT NULL CHECK (provider ~ '^[a-z][a-z0-9_]{1,63}$'),
35
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
36
+ adapter_version text NOT NULL,
37
+ manifest_digest text NOT NULL CHECK (manifest_digest ~ '^[a-f0-9]{64}$'),
38
+ compiled_in_registry boolean NOT NULL DEFAULT true,
39
+ production_eligible boolean NOT NULL DEFAULT false,
40
+ created_at timestamptz NOT NULL DEFAULT now(),
41
+ updated_at timestamptz NOT NULL DEFAULT now(),
42
+ PRIMARY KEY (tenant_key, provider, environment)
43
+ );
44
+
45
+ CREATE TABLE verification.routes (
46
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
47
+ id text NOT NULL,
48
+ provider text NOT NULL CHECK (provider ~ '^[a-z][a-z0-9_]{1,63}$'),
49
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
50
+ package_code text NOT NULL,
51
+ country_code text CHECK (country_code IS NULL OR country_code ~ '^[A-Z]{2}$'),
52
+ required_capability text,
53
+ priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN 0 AND 10000),
54
+ cohort_min integer NOT NULL DEFAULT 0 CHECK (cohort_min BETWEEN 0 AND 99),
55
+ cohort_max integer NOT NULL DEFAULT 99 CHECK (cohort_max BETWEEN 0 AND 99),
56
+ window_start timestamptz,
57
+ window_end timestamptz,
58
+ allowlist_required boolean NOT NULL DEFAULT false,
59
+ allowlisted_subject_hashes text[] NOT NULL DEFAULT '{}',
60
+ configuration_revision_id text NOT NULL,
61
+ policy_version_id text NOT NULL,
62
+ lifecycle text NOT NULL CHECK (lifecycle IN ('draft', 'approved', 'active', 'retired')),
63
+ proposed_by_actor_id text,
64
+ approved_by_actor_id text,
65
+ approved_at timestamptz,
66
+ activated_at timestamptz,
67
+ created_at timestamptz NOT NULL DEFAULT now(),
68
+ updated_at timestamptz NOT NULL DEFAULT now(),
69
+ PRIMARY KEY (tenant_key, id),
70
+ CHECK (cohort_min <= cohort_max),
71
+ CHECK (
72
+ environment <> 'production'
73
+ OR lifecycle <> 'active'
74
+ OR (approved_by_actor_id IS NOT NULL AND proposed_by_actor_id IS DISTINCT FROM approved_by_actor_id)
75
+ )
76
+ );
77
+
78
+ CREATE TABLE verification.route_change_requests (
79
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
80
+ id text NOT NULL,
81
+ route_id text,
82
+ proposed_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
83
+ status text NOT NULL CHECK (status IN ('proposed', 'approved', 'rejected')),
84
+ reason text NOT NULL,
85
+ policy_version text NOT NULL,
86
+ proposed_by_actor_id text NOT NULL,
87
+ approved_by_actor_id text,
88
+ approved_at timestamptz,
89
+ expires_at timestamptz,
90
+ created_at timestamptz NOT NULL DEFAULT now(),
91
+ PRIMARY KEY (tenant_key, id),
92
+ CHECK (proposed_by_actor_id IS DISTINCT FROM approved_by_actor_id OR approved_by_actor_id IS NULL),
93
+ CHECK (jsonb_typeof(proposed_payload) = 'object')
94
+ );
95
+
96
+ CREATE TABLE verification.policy_versions (
97
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
98
+ id text NOT NULL,
99
+ version text NOT NULL,
100
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
101
+ lifecycle text NOT NULL CHECK (lifecycle IN ('draft', 'approved', 'active', 'retired')),
102
+ reason text NOT NULL,
103
+ expires_at timestamptz,
104
+ proposed_by_actor_id text,
105
+ approved_by_actor_id text,
106
+ approved_at timestamptz,
107
+ activated_at timestamptz,
108
+ created_at timestamptz NOT NULL DEFAULT now(),
109
+ PRIMARY KEY (tenant_key, id),
110
+ UNIQUE (tenant_key, version),
111
+ CHECK (proposed_by_actor_id IS NULL OR approved_by_actor_id IS NULL OR proposed_by_actor_id <> approved_by_actor_id),
112
+ CHECK ((lifecycle = 'active' AND activated_at IS NOT NULL) OR lifecycle <> 'active')
113
+ );
114
+
115
+ CREATE TABLE verification.protected_action_requirements (
116
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
117
+ id text NOT NULL,
118
+ action text NOT NULL,
119
+ package_code text NOT NULL,
120
+ policy_version_id text NOT NULL,
121
+ created_at timestamptz NOT NULL DEFAULT now(),
122
+ PRIMARY KEY (tenant_key, id),
123
+ UNIQUE (tenant_key, action, package_code, policy_version_id)
124
+ );
125
+
126
+ CREATE TABLE verification.attempts (
127
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
128
+ id text NOT NULL,
129
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
130
+ package_code text NOT NULL,
131
+ country_code text NOT NULL CHECK (country_code ~ '^[A-Z]{2}$'),
132
+ provider text NOT NULL,
133
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
134
+ adapter_version text NOT NULL,
135
+ manifest_digest text NOT NULL CHECK (manifest_digest ~ '^[a-f0-9]{64}$'),
136
+ configuration_revision text NOT NULL,
137
+ policy_version text NOT NULL,
138
+ provider_resource_id text,
139
+ provider_status text,
140
+ canonical_status text NOT NULL,
141
+ status_version bigint NOT NULL DEFAULT 0 CHECK (status_version >= 0),
142
+ idempotency_key text NOT NULL,
143
+ parent_attempt_id text,
144
+ purpose_action text,
145
+ purpose_resource_hash text CHECK (purpose_resource_hash IS NULL OR purpose_resource_hash ~ '^[a-f0-9]{64}$'),
146
+ route_id text NOT NULL,
147
+ selection_reason text NOT NULL,
148
+ normalized_reason_codes text[] NOT NULL DEFAULT '{}',
149
+ expires_at timestamptz,
150
+ create_claim_id text,
151
+ create_claim_expires_at timestamptz,
152
+ created_at timestamptz NOT NULL DEFAULT now(),
153
+ updated_at timestamptz NOT NULL DEFAULT now(),
154
+ PRIMARY KEY (tenant_key, id),
155
+ UNIQUE (tenant_key, idempotency_key),
156
+ UNIQUE (tenant_key, provider, provider_resource_id)
157
+ );
158
+
159
+ CREATE TABLE verification.provider_resource_lineage (
160
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
161
+ id text NOT NULL,
162
+ attempt_id text NOT NULL,
163
+ resource_type text NOT NULL,
164
+ provider_resource_id text NOT NULL,
165
+ relationship_code text NOT NULL,
166
+ provider_status text NOT NULL,
167
+ occurred_at timestamptz NOT NULL,
168
+ PRIMARY KEY (tenant_key, id),
169
+ UNIQUE (tenant_key, provider_resource_id, resource_type)
170
+ );
171
+
172
+ CREATE TABLE verification.decisions (
173
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
174
+ id text NOT NULL,
175
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
176
+ package_code text NOT NULL,
177
+ attempt_id text,
178
+ status text NOT NULL CHECK (status IN ('verified', 'declined', 'revoked', 'expired')),
179
+ source text NOT NULL CHECK (source IN ('provider', 'manual')),
180
+ policy_version text NOT NULL,
181
+ reason_codes text[] NOT NULL DEFAULT '{}',
182
+ effective_at timestamptz NOT NULL,
183
+ expires_at timestamptz,
184
+ revoked_at timestamptz,
185
+ proposer_actor_id text,
186
+ approver_actor_id text,
187
+ created_at timestamptz NOT NULL DEFAULT now(),
188
+ PRIMARY KEY (tenant_key, id),
189
+ CHECK (expires_at IS NULL OR expires_at > effective_at),
190
+ CHECK (proposer_actor_id IS NULL OR approver_actor_id IS NULL OR proposer_actor_id <> approver_actor_id)
191
+ );
192
+
193
+ CREATE TABLE verification.idempotency_claims (
194
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
195
+ claim_key text NOT NULL,
196
+ operation text NOT NULL,
197
+ attempt_id text,
198
+ state text NOT NULL CHECK (state IN ('claimed', 'completed', 'failed')),
199
+ result_ref text,
200
+ error_code text,
201
+ created_at timestamptz NOT NULL DEFAULT now(),
202
+ completed_at timestamptz,
203
+ PRIMARY KEY (tenant_key, claim_key)
204
+ );
205
+
206
+ CREATE TABLE verification.webhook_events (
207
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
208
+ id text NOT NULL,
209
+ provider text NOT NULL,
210
+ provider_event_key text NOT NULL CHECK (length(provider_event_key) BETWEEN 8 AND 256),
211
+ provider_resource_id text NOT NULL,
212
+ event_type text NOT NULL,
213
+ occurred_at timestamptz NOT NULL,
214
+ body_sha256 text NOT NULL CHECK (body_sha256 ~ '^[a-f0-9]{64}$'),
215
+ safe_metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
216
+ state text NOT NULL CHECK (state IN ('accepted', 'processing', 'completed', 'retryable', 'dead_letter')),
217
+ received_at timestamptz NOT NULL DEFAULT now(),
218
+ PRIMARY KEY (tenant_key, id),
219
+ UNIQUE (tenant_key, provider, provider_event_key),
220
+ CHECK (jsonb_typeof(safe_metadata) = 'object')
221
+ );
222
+
223
+ CREATE TABLE verification.webhook_leases (
224
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
225
+ event_id text NOT NULL,
226
+ lease_id text,
227
+ worker_id text,
228
+ expires_at timestamptz,
229
+ attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
230
+ next_attempt_at timestamptz NOT NULL DEFAULT now(),
231
+ last_error_code text,
232
+ PRIMARY KEY (tenant_key, event_id)
233
+ );
234
+
235
+ CREATE TABLE verification.reconciliation_jobs (
236
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
237
+ id text NOT NULL,
238
+ attempt_id text NOT NULL,
239
+ state text NOT NULL CHECK (state IN ('scheduled', 'processing', 'retryable', 'completed', 'dead_letter')),
240
+ lease_id text,
241
+ lease_expires_at timestamptz,
242
+ attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
243
+ next_attempt_at timestamptz NOT NULL DEFAULT now(),
244
+ last_error_code text,
245
+ created_at timestamptz NOT NULL DEFAULT now(),
246
+ PRIMARY KEY (tenant_key, id)
247
+ );
248
+
249
+ CREATE TABLE verification.redaction_jobs (
250
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
251
+ id text NOT NULL,
252
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
253
+ attempt_id text,
254
+ provider_resource_id text,
255
+ status text NOT NULL CHECK (status IN ('scheduled', 'processing', 'retryable', 'redacted', 'not_applicable', 'dead_letter')),
256
+ lease_id text,
257
+ lease_expires_at timestamptz,
258
+ attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
259
+ next_attempt_at timestamptz NOT NULL DEFAULT now(),
260
+ last_error_code text,
261
+ created_at timestamptz NOT NULL DEFAULT now(),
262
+ PRIMARY KEY (tenant_key, id)
263
+ );
264
+
265
+ CREATE TABLE verification.provider_health_observations (
266
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
267
+ id text NOT NULL,
268
+ provider text NOT NULL,
269
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
270
+ operation text NOT NULL,
271
+ outcome text NOT NULL CHECK (outcome IN ('success', 'retryable_failure', 'terminal_failure', 'unknown_status')),
272
+ safe_code text NOT NULL,
273
+ observed_at timestamptz NOT NULL,
274
+ latency_ms integer,
275
+ PRIMARY KEY (tenant_key, id)
276
+ );
277
+
278
+ CREATE TABLE verification.circuits (
279
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
280
+ provider text NOT NULL,
281
+ environment text NOT NULL CHECK (environment IN ('sandbox', 'production')),
282
+ state text NOT NULL CHECK (state IN ('closed', 'open', 'half_open')),
283
+ reason_code text,
284
+ open_until timestamptz,
285
+ consecutive_failures integer NOT NULL DEFAULT 0 CHECK (consecutive_failures >= 0),
286
+ drained_by_actor_id text,
287
+ updated_at timestamptz NOT NULL DEFAULT now(),
288
+ PRIMARY KEY (tenant_key, provider, environment)
289
+ );
290
+
291
+ CREATE TABLE verification.appeals (
292
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
293
+ id text NOT NULL,
294
+ attempt_id text NOT NULL,
295
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
296
+ status text NOT NULL CHECK (status IN ('open', 'approved', 'denied', 'more_information_requested', 'revoked', 'expired')),
297
+ reason text NOT NULL,
298
+ policy_version text NOT NULL,
299
+ proposed_by_actor_id text NOT NULL,
300
+ decided_by_actor_id text,
301
+ expires_at timestamptz,
302
+ created_at timestamptz NOT NULL DEFAULT now(),
303
+ updated_at timestamptz NOT NULL DEFAULT now(),
304
+ PRIMARY KEY (tenant_key, id)
305
+ );
306
+
307
+ CREATE TABLE verification.review_cases (
308
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
309
+ id text NOT NULL,
310
+ attempt_id text NOT NULL,
311
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
312
+ status text NOT NULL CHECK (status IN ('open', 'in_review', 'approved', 'denied', 'more_information_requested', 'revoked', 'expired')),
313
+ reason text NOT NULL,
314
+ policy_version text NOT NULL,
315
+ assigned_actor_id text,
316
+ created_at timestamptz NOT NULL DEFAULT now(),
317
+ updated_at timestamptz NOT NULL DEFAULT now(),
318
+ PRIMARY KEY (tenant_key, id)
319
+ );
320
+
321
+ CREATE TABLE verification.manual_decision_proposals (
322
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
323
+ id text NOT NULL,
324
+ review_case_id text,
325
+ attempt_id text NOT NULL,
326
+ proposed_status text NOT NULL CHECK (proposed_status IN ('verified', 'declined', 'revoked', 'expired')),
327
+ reason text NOT NULL,
328
+ policy_version text NOT NULL,
329
+ expires_at timestamptz,
330
+ proposed_by_actor_id text NOT NULL,
331
+ approved_by_actor_id text,
332
+ status text NOT NULL CHECK (status IN ('proposed', 'approved', 'rejected')),
333
+ created_at timestamptz NOT NULL DEFAULT now(),
334
+ PRIMARY KEY (tenant_key, id),
335
+ CHECK (proposed_by_actor_id IS DISTINCT FROM approved_by_actor_id OR approved_by_actor_id IS NULL)
336
+ );
337
+
338
+ CREATE TABLE verification.audit_events (
339
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
340
+ id text NOT NULL,
341
+ actor_id text NOT NULL,
342
+ actor_type text NOT NULL CHECK (actor_type IN ('user', 'operator', 'system')),
343
+ operation text NOT NULL,
344
+ resource_type text NOT NULL,
345
+ resource_id text,
346
+ reason_code text,
347
+ safe_metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
348
+ occurred_at timestamptz NOT NULL DEFAULT now(),
349
+ PRIMARY KEY (tenant_key, id),
350
+ CHECK (jsonb_typeof(safe_metadata) = 'object')
351
+ );
352
+
353
+ CREATE TABLE verification.continuations (
354
+ tenant_key text NOT NULL REFERENCES verification.tenants(tenant_key) ON DELETE RESTRICT,
355
+ key text NOT NULL,
356
+ token_hash text NOT NULL CHECK (token_hash ~ '^[a-f0-9]{64}$'),
357
+ action text NOT NULL,
358
+ resource_hash text NOT NULL CHECK (resource_hash ~ '^[a-f0-9]{64}$'),
359
+ subject_hash text NOT NULL CHECK (subject_hash ~ '^[a-f0-9]{64}$'),
360
+ destination_key text NOT NULL,
361
+ expires_at timestamptz NOT NULL,
362
+ consumed_at timestamptz,
363
+ PRIMARY KEY (tenant_key, key)
364
+ );
365
+
366
+ CREATE INDEX routes_active_selection_idx
367
+ ON verification.routes (tenant_key, environment, package_code, country_code, priority, id)
368
+ WHERE lifecycle = 'active';
369
+
370
+ CREATE INDEX attempts_active_idx
371
+ ON verification.attempts (tenant_key, subject_hash, package_code, updated_at DESC)
372
+ WHERE canonical_status IN ('created', 'pending_user_input', 'paused', 'processing', 'manual_review_required');
373
+
374
+ CREATE INDEX attempts_provider_resource_idx
375
+ ON verification.attempts (tenant_key, provider, provider_resource_id)
376
+ WHERE provider_resource_id IS NOT NULL;
377
+
378
+ CREATE INDEX decisions_valid_idx
379
+ ON verification.decisions (tenant_key, subject_hash, package_code, expires_at DESC)
380
+ WHERE status = 'verified' AND revoked_at IS NULL;
381
+
382
+ CREATE INDEX webhook_events_pending_idx
383
+ ON verification.webhook_events (tenant_key, provider, received_at, id)
384
+ WHERE state IN ('accepted', 'retryable', 'processing');
385
+
386
+ CREATE INDEX webhook_leases_work_idx
387
+ ON verification.webhook_leases (tenant_key, next_attempt_at, event_id)
388
+ WHERE lease_id IS NULL OR expires_at IS NOT NULL;
389
+
390
+ CREATE INDEX reconciliation_pending_idx
391
+ ON verification.reconciliation_jobs (tenant_key, next_attempt_at, id)
392
+ WHERE state IN ('scheduled', 'retryable', 'processing');
393
+
394
+ CREATE INDEX redaction_pending_idx
395
+ ON verification.redaction_jobs (tenant_key, next_attempt_at, id)
396
+ WHERE status IN ('scheduled', 'retryable', 'processing');
397
+
398
+ CREATE INDEX health_provider_idx
399
+ ON verification.provider_health_observations (tenant_key, provider, environment, observed_at DESC);
400
+
401
+ CREATE INDEX audit_tenant_idx
402
+ ON verification.audit_events (tenant_key, occurred_at DESC, id);
403
+
404
+ CREATE UNIQUE INDEX policy_one_active_per_env
405
+ ON verification.policy_versions (tenant_key, environment)
406
+ WHERE lifecycle = 'active';
407
+
408
+ CREATE OR REPLACE FUNCTION verification.reject_audit_mutation()
409
+ RETURNS trigger
410
+ LANGUAGE plpgsql
411
+ AS $$
412
+ BEGIN
413
+ RAISE EXCEPTION 'audit_events are insert-only';
414
+ END;
415
+ $$;
416
+
417
+ CREATE TRIGGER audit_events_immutable
418
+ BEFORE UPDATE OR DELETE ON verification.audit_events
419
+ FOR EACH ROW EXECUTE PROCEDURE verification.reject_audit_mutation();
@@ -0,0 +1,4 @@
1
+ DELETE FROM verification.routes WHERE tenant_key = 'default' AND id = 'rte_sandbox_example_human_idv';
2
+ DELETE FROM verification.configuration_revisions WHERE tenant_key = 'default' AND id = 'cfg_sandbox_example';
3
+ DELETE FROM verification.policy_versions WHERE tenant_key = 'default' AND id IN ('pol_sandbox_example', 'pol_production_unactivated');
4
+ DELETE FROM verification.tenants WHERE tenant_key = 'default';
@@ -0,0 +1,40 @@
1
+ -- Seed tenant `default` and sandbox examples. No active production route.
2
+
3
+ INSERT INTO verification.tenants (tenant_key, display_name, continuation_destinations)
4
+ VALUES ('default', 'Default tenant', ARRAY['verification.resume', 'application.home'])
5
+ ON CONFLICT (tenant_key) DO NOTHING;
6
+
7
+ INSERT INTO verification.policy_versions (
8
+ tenant_key, id, version, environment, lifecycle, reason,
9
+ proposed_by_actor_id, approved_by_actor_id, approved_at, activated_at
10
+ ) VALUES (
11
+ 'default', 'pol_sandbox_example', 'sandbox-example-1', 'sandbox', 'active',
12
+ 'Seeded sandbox example policy',
13
+ 'system:seed', 'system:seed-approver', now(), now()
14
+ ) ON CONFLICT (tenant_key, id) DO NOTHING;
15
+
16
+ INSERT INTO verification.policy_versions (
17
+ tenant_key, id, version, environment, lifecycle, reason, proposed_by_actor_id
18
+ ) VALUES (
19
+ 'default', 'pol_production_unactivated', 'production-unactivated', 'production', 'draft',
20
+ 'Seeded production policy is never auto-activated', 'system:seed'
21
+ ) ON CONFLICT (tenant_key, id) DO NOTHING;
22
+
23
+ INSERT INTO verification.configuration_revisions (
24
+ tenant_key, id, provider, environment, revision, configuration_digest, lifecycle,
25
+ proposed_by_actor_id, approved_by_actor_id, approved_at
26
+ ) VALUES (
27
+ 'default', 'cfg_sandbox_example', 'test_fake', 'sandbox', 1,
28
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
29
+ 'approved', 'system:seed', 'system:seed-approver', now()
30
+ ) ON CONFLICT (tenant_key, id) DO NOTHING;
31
+
32
+ INSERT INTO verification.routes (
33
+ tenant_key, id, provider, environment, package_code, country_code, priority,
34
+ configuration_revision_id, policy_version_id, lifecycle,
35
+ proposed_by_actor_id, approved_by_actor_id, approved_at, activated_at
36
+ ) VALUES (
37
+ 'default', 'rte_sandbox_example_human_idv', 'test_fake', 'sandbox', 'human_idv', 'US', 100,
38
+ 'cfg_sandbox_example', 'pol_sandbox_example', 'active',
39
+ 'system:seed', 'system:seed-approver', now(), now()
40
+ ) ON CONFLICT (tenant_key, id) DO NOTHING;
@@ -0,0 +1,8 @@
1
+ ALTER TABLE verification.policy_versions
2
+ DROP CONSTRAINT IF EXISTS production_retention_complete;
3
+
4
+ ALTER TABLE verification.policy_versions
5
+ DROP COLUMN IF EXISTS legal_hold,
6
+ DROP COLUMN IF EXISTS appeal_hold_days,
7
+ DROP COLUMN IF EXISTS provider_redaction_delay_days,
8
+ DROP COLUMN IF EXISTS decision_retention_days;
@@ -0,0 +1,28 @@
1
+ ALTER TABLE verification.policy_versions
2
+ ADD COLUMN IF NOT EXISTS decision_retention_days integer,
3
+ ADD COLUMN IF NOT EXISTS provider_redaction_delay_days integer,
4
+ ADD COLUMN IF NOT EXISTS appeal_hold_days integer,
5
+ ADD COLUMN IF NOT EXISTS legal_hold boolean NOT NULL DEFAULT false;
6
+
7
+ ALTER TABLE verification.policy_versions
8
+ DROP CONSTRAINT IF EXISTS production_retention_complete;
9
+
10
+ ALTER TABLE verification.policy_versions
11
+ ADD CONSTRAINT production_retention_complete CHECK (
12
+ environment <> 'production'
13
+ OR lifecycle <> 'active'
14
+ OR (
15
+ decision_retention_days IS NOT NULL
16
+ AND provider_redaction_delay_days IS NOT NULL
17
+ AND appeal_hold_days IS NOT NULL
18
+ )
19
+ );
20
+
21
+ COMMENT ON COLUMN verification.policy_versions.decision_retention_days IS
22
+ 'Adopter-selected verified-decision retention in days. Required before production activation.';
23
+ COMMENT ON COLUMN verification.policy_versions.provider_redaction_delay_days IS
24
+ 'Delay before provider redaction after a terminal decision. Required before production activation.';
25
+ COMMENT ON COLUMN verification.policy_versions.appeal_hold_days IS
26
+ 'Appeal hold window that delays redaction. Required before production activation.';
27
+ COMMENT ON COLUMN verification.policy_versions.legal_hold IS
28
+ 'When true, redaction jobs remain scheduled until the hold is cleared.';
@@ -0,0 +1,64 @@
1
+ -- Optional Supabase hardening. Apply after 001_init.sql.
2
+ -- Enables and forces RLS, revokes browser writes, and exposes only a service role.
3
+
4
+ ALTER TABLE verification.tenants ENABLE ROW LEVEL SECURITY;
5
+ ALTER TABLE verification.tenants FORCE ROW LEVEL SECURITY;
6
+ ALTER TABLE verification.configuration_revisions ENABLE ROW LEVEL SECURITY;
7
+ ALTER TABLE verification.configuration_revisions FORCE ROW LEVEL SECURITY;
8
+ ALTER TABLE verification.provider_definitions ENABLE ROW LEVEL SECURITY;
9
+ ALTER TABLE verification.provider_definitions FORCE ROW LEVEL SECURITY;
10
+ ALTER TABLE verification.routes ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE verification.routes FORCE ROW LEVEL SECURITY;
12
+ ALTER TABLE verification.route_change_requests ENABLE ROW LEVEL SECURITY;
13
+ ALTER TABLE verification.route_change_requests FORCE ROW LEVEL SECURITY;
14
+ ALTER TABLE verification.policy_versions ENABLE ROW LEVEL SECURITY;
15
+ ALTER TABLE verification.policy_versions FORCE ROW LEVEL SECURITY;
16
+ ALTER TABLE verification.protected_action_requirements ENABLE ROW LEVEL SECURITY;
17
+ ALTER TABLE verification.protected_action_requirements FORCE ROW LEVEL SECURITY;
18
+ ALTER TABLE verification.attempts ENABLE ROW LEVEL SECURITY;
19
+ ALTER TABLE verification.attempts FORCE ROW LEVEL SECURITY;
20
+ ALTER TABLE verification.provider_resource_lineage ENABLE ROW LEVEL SECURITY;
21
+ ALTER TABLE verification.provider_resource_lineage FORCE ROW LEVEL SECURITY;
22
+ ALTER TABLE verification.decisions ENABLE ROW LEVEL SECURITY;
23
+ ALTER TABLE verification.decisions FORCE ROW LEVEL SECURITY;
24
+ ALTER TABLE verification.idempotency_claims ENABLE ROW LEVEL SECURITY;
25
+ ALTER TABLE verification.idempotency_claims FORCE ROW LEVEL SECURITY;
26
+ ALTER TABLE verification.webhook_events ENABLE ROW LEVEL SECURITY;
27
+ ALTER TABLE verification.webhook_events FORCE ROW LEVEL SECURITY;
28
+ ALTER TABLE verification.webhook_leases ENABLE ROW LEVEL SECURITY;
29
+ ALTER TABLE verification.webhook_leases FORCE ROW LEVEL SECURITY;
30
+ ALTER TABLE verification.reconciliation_jobs ENABLE ROW LEVEL SECURITY;
31
+ ALTER TABLE verification.reconciliation_jobs FORCE ROW LEVEL SECURITY;
32
+ ALTER TABLE verification.redaction_jobs ENABLE ROW LEVEL SECURITY;
33
+ ALTER TABLE verification.redaction_jobs FORCE ROW LEVEL SECURITY;
34
+ ALTER TABLE verification.provider_health_observations ENABLE ROW LEVEL SECURITY;
35
+ ALTER TABLE verification.provider_health_observations FORCE ROW LEVEL SECURITY;
36
+ ALTER TABLE verification.circuits ENABLE ROW LEVEL SECURITY;
37
+ ALTER TABLE verification.circuits FORCE ROW LEVEL SECURITY;
38
+ ALTER TABLE verification.appeals ENABLE ROW LEVEL SECURITY;
39
+ ALTER TABLE verification.appeals FORCE ROW LEVEL SECURITY;
40
+ ALTER TABLE verification.review_cases ENABLE ROW LEVEL SECURITY;
41
+ ALTER TABLE verification.review_cases FORCE ROW LEVEL SECURITY;
42
+ ALTER TABLE verification.manual_decision_proposals ENABLE ROW LEVEL SECURITY;
43
+ ALTER TABLE verification.manual_decision_proposals FORCE ROW LEVEL SECURITY;
44
+ ALTER TABLE verification.audit_events ENABLE ROW LEVEL SECURITY;
45
+ ALTER TABLE verification.audit_events FORCE ROW LEVEL SECURITY;
46
+ ALTER TABLE verification.continuations ENABLE ROW LEVEL SECURITY;
47
+ ALTER TABLE verification.continuations FORCE ROW LEVEL SECURITY;
48
+
49
+ REVOKE ALL ON SCHEMA verification FROM PUBLIC;
50
+ REVOKE ALL ON ALL TABLES IN SCHEMA verification FROM PUBLIC;
51
+ REVOKE ALL ON ALL SEQUENCES IN SCHEMA verification FROM PUBLIC;
52
+ REVOKE ALL ON SCHEMA verification FROM anon, authenticated;
53
+ REVOKE ALL ON ALL TABLES IN SCHEMA verification FROM anon, authenticated;
54
+
55
+ DO $$
56
+ BEGIN
57
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
58
+ GRANT USAGE ON SCHEMA verification TO service_role;
59
+ GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA verification TO service_role;
60
+ REVOKE UPDATE, DELETE, TRUNCATE ON verification.audit_events FROM service_role;
61
+ GRANT INSERT ON verification.audit_events TO service_role;
62
+ END IF;
63
+ END
64
+ $$;
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@splitin/verification-postgres",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "PostgreSQL persistence for the self-hosted verification engine (schema verification).",
5
+ "license": "MIT",
6
+ "author": "SplitInTech, @ojchavan",
7
+ "homepage": "https://github.com/splitintech/open-internal-tools/tree/main/verification-adapter-sdk#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/splitintech/open-internal-tools.git",
11
+ "directory": "verification-adapter-sdk/packages/verification-postgres"
12
+ },
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "LICENSE",
31
+ "NOTICE",
32
+ "README.md",
33
+ "package.json",
34
+ "migrations",
35
+ "QUERY_PLANS.md"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json"
44
+ },
45
+ "dependencies": {
46
+ "@splitin/verification-engine": "0.1.0-beta.0"
47
+ },
48
+ "peerDependencies": {
49
+ "pg": "^8.13.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "pg": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "@types/pg": "^8.15.2",
58
+ "pg": "^8.23.0",
59
+ "tsup": "^8.5.0",
60
+ "typescript": "^5.8.3",
61
+ "vitest": "^3.2.4"
62
+ }
63
+ }