Skip to content

Commit c04ffe3

Browse files
committed
test(migrate): domain-typed target column; tighten eql_v3_ prefix
Two review-thread fixes on #649: the v3 backfill integration table now types email_encrypted with a domain carrying the real eql_v3_* storage CHECK shape (both scalar and SteVec arms), so the $N::jsonb write exercises the implicit jsonb→domain cast + CHECK enforcement instead of plain jsonb; and classifyEqlDomain matches the 'eql_v3_' prefix with the underscore so a hypothetical eql_v30_* generation can't classify as v3 (negative pins added). Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
1 parent 8b8a712 commit c04ffe3

3 files changed

Lines changed: 30 additions & 6 deletions

File tree

packages/migrate/src/__tests__/backfill-v3.integration.test.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* - the leak guard (`isEncryptedPayload`) accepts BOTH v3 wire shapes — flat
77
* scalars (`{v:3, i, c}`) and SteVec documents (`{v:3, k:'sv', i, sv}`) —
88
* so a v3 client's output flows through `runBackfill` unmodified;
9-
* - the `$N::jsonb` write lands v3 envelopes in the target column;
9+
* - the `$N::jsonb` write lands v3 envelopes in a DOMAIN-typed target column
10+
* (implicit jsonb→domain cast + CHECK enforcement, the same assignment
11+
* path a real `eql_v3_*` column takes);
1012
* - `countEncrypted` (the v3 verification primitive — v3 has no
1113
* `eql_v2.count_encrypted_with_active_config`) counts them.
1214
*
@@ -19,9 +21,9 @@
1921
* ```
2022
*
2123
* No CipherStash credentials required — payloads are deterministic v3-shaped
22-
* markers. End-to-end proof against a real `eql_v3_*` domain (whose CHECK
23-
* constraint demands real ciphertext structure) lives with the live-crypto
24-
* harness, not here.
24+
* markers, and the target domain mirrors the real `eql_v3_*` storage CHECK
25+
* (structure only; real-ciphertext proof lives with the live-crypto
26+
* harness, not here).
2527
*/
2628

2729
import 'dotenv/config'
@@ -50,6 +52,22 @@ describe.skipIf(!runIntegration)('runBackfill with EQL v3 payloads', () => {
5052
await db.query('DROP SCHEMA IF EXISTS cipherstash CASCADE')
5153
await db.query('DROP SCHEMA IF EXISTS migrate_v3_test CASCADE')
5254
await db.query('CREATE SCHEMA migrate_v3_test')
55+
// A domain with the SAME CHECK shape as the real `public.eql_v3_*`
56+
// storage domains (object with v/i/c keys, v = 3), so the backfill's
57+
// `$N::jsonb` write exercises the domain-typed assignment path — the
58+
// implicit jsonb→domain cast plus CHECK enforcement — without needing
59+
// an EQL install. A payload the real domain would reject fails here
60+
// too (pinned below).
61+
await db.query(`
62+
CREATE DOMAIN migrate_v3_test.eql_v3_text_t AS jsonb
63+
CHECK (
64+
jsonb_typeof(VALUE) = 'object'
65+
AND VALUE ? 'v'
66+
AND VALUE ? 'i'
67+
AND VALUE->>'v' = '3'
68+
AND (VALUE ? 'c' OR (VALUE->>'k' = 'sv' AND VALUE ? 'sv'))
69+
)
70+
`)
5371
await installMigrationsSchema(db)
5472
} finally {
5573
db.release()
@@ -107,7 +125,7 @@ describe.skipIf(!runIntegration)('runBackfill with EQL v3 payloads', () => {
107125
CREATE TABLE migrate_v3_test.users (
108126
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
109127
email text NOT NULL,
110-
email_encrypted jsonb
128+
email_encrypted migrate_v3_test.eql_v3_text_t
111129
)
112130
`)
113131
await db.query(

packages/migrate/src/__tests__/version.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ describe('classifyEqlDomain', () => {
3232
expect(classifyEqlDomain('text')).toBeNull()
3333
expect(classifyEqlDomain('jsonb')).toBeNull()
3434
expect(classifyEqlDomain('citext')).toBeNull()
35+
// Prefix is `eql_v3_` with the underscore — a hypothetical future
36+
// `eql_v30_*` generation must not classify as v3.
37+
expect(classifyEqlDomain('eql_v30_text')).toBeNull()
38+
expect(classifyEqlDomain('eql_v3')).toBeNull()
3539
})
3640
})
3741

packages/migrate/src/version.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export interface EncryptedColumnInfo {
3636
*/
3737
export function classifyEqlDomain(domain: string): EqlVersion | null {
3838
if (domain === 'eql_v2_encrypted') return 2
39-
if (domain.startsWith('eql_v3')) return 3
39+
// Underscore included: a bare `startsWith('eql_v3')` would also claim
40+
// hypothetical future generations like `eql_v30_*`.
41+
if (domain.startsWith('eql_v3_')) return 3
4042
return null
4143
}
4244

0 commit comments

Comments
 (0)