Skip to content

Commit 1b2bb66

Browse files
calvinbrewerclaude
andcommitted
chore: address CodeRabbit review feedback on PR #473
Fixes the actionable findings CodeRabbit raised on the rebased PR state. - `.github/workflows/tests.yml` — the comment justifying the postgres-eql:17-2.3.1 image pin still said "protect-ffi 0.21.x". After the 0.23.0 bump that's a stale lockstep reference; flip to 0.23.x so future maintainers don't read it backwards. - `packages/schema/src/index.ts` — tighten `encryptConfigSchema.v` from `z.number()` to `z.literal(1)`. `buildEncryptConfig` only emits `1`, and protect-ffi 0.22.0+ rejects any other value at `newClient` with `UNSUPPORTED_CONFIG_VERSION`; enforcing it at the zod layer fails bad configs earlier (and surfaces a cleaner error than crossing into the native module first). - `packages/{protect,stack}/__tests__/number-protect.test.ts` — the "// Forward compatibility: new encryptions should NOT have k field" comment contradicted the asserting branch right below (post-EQL-v2.3 the discriminator IS present). Reword to match the actual contract and tighten `toHaveProperty('k')` to `toHaveProperty('k', 'ct')`. - `packages/{protect,stack}/__tests__/json-protect.test.ts` — same tightening for all sites. These columns are typed `dataType('json')` without `searchableJson()`, so no ste_vec index is configured and the FFI emits a scalar payload — `'ct'` is the correct discriminator at every assertion site. Verified: pnpm build (10/10 packages, full TURBO), pnpm test (14/14 packages, 1876 passed | 20 skipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2f5cb41 commit 1b2bb66

6 files changed

Lines changed: 94 additions & 90 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
# Postgres + EQL for the integration tests. Official EQL image —
1717
# PostgreSQL 17 with EQL pre-installed via /docker-entrypoint-initdb.d.
1818
# Pinned to eql-2.3.1 to match the EQL payload format the code emits
19-
# (protect-ffi 0.21.x); bump in lockstep with the protect-ffi upgrade.
19+
# (protect-ffi 0.23.x); bump in lockstep with the protect-ffi upgrade.
2020
services:
2121
postgres:
2222
image: ghcr.io/cipherstash/postgres-eql:17-2.3.1

packages/protect/__tests__/json-protect.test.ts

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('JSON encryption and decryption', () => {
5555
}
5656

5757
// Verify encrypted field
58-
expect(ciphertext.data).toHaveProperty('k')
58+
expect(ciphertext.data).toHaveProperty('k', 'ct')
5959

6060
const plaintext = await protectClient.decrypt(ciphertext.data)
6161

@@ -107,7 +107,7 @@ describe('JSON encryption and decryption', () => {
107107
}
108108

109109
// Verify encrypted field
110-
expect(ciphertext.data).toHaveProperty('k')
110+
expect(ciphertext.data).toHaveProperty('k', 'ct')
111111

112112
const plaintext = await protectClient.decrypt(ciphertext.data)
113113

@@ -149,7 +149,7 @@ describe('JSON encryption and decryption', () => {
149149
}
150150

151151
// Verify encrypted field
152-
expect(ciphertext.data).toHaveProperty('k')
152+
expect(ciphertext.data).toHaveProperty('k', 'ct')
153153

154154
const plaintext = await protectClient.decrypt(ciphertext.data)
155155

@@ -176,7 +176,7 @@ describe('JSON encryption and decryption', () => {
176176
}
177177

178178
// Verify encrypted field
179-
expect(ciphertext.data).toHaveProperty('k')
179+
expect(ciphertext.data).toHaveProperty('k', 'ct')
180180

181181
const plaintext = await protectClient.decrypt(ciphertext.data)
182182

@@ -214,9 +214,9 @@ describe('JSON model encryption and decryption', () => {
214214
}
215215

216216
// Verify encrypted fields
217-
expect(encryptedModel.data.email).toHaveProperty('k')
218-
expect(encryptedModel.data.address).toHaveProperty('k')
219-
expect(encryptedModel.data.json).toHaveProperty('k')
217+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
218+
expect(encryptedModel.data.address).toHaveProperty('k', 'ct')
219+
expect(encryptedModel.data.json).toHaveProperty('k', 'ct')
220220

221221
// Verify non-encrypted fields remain unchanged
222222
expect(encryptedModel.data.id).toBe('1')
@@ -254,8 +254,8 @@ describe('JSON model encryption and decryption', () => {
254254
}
255255

256256
// Verify encrypted fields
257-
expect(encryptedModel.data.email).toHaveProperty('k')
258-
expect(encryptedModel.data.address).toHaveProperty('k')
257+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
258+
expect(encryptedModel.data.address).toHaveProperty('k', 'ct')
259259
expect(encryptedModel.data.json).toBeNull()
260260

261261
const decryptedResult = await protectClient.decryptModel<User>(
@@ -289,8 +289,8 @@ describe('JSON model encryption and decryption', () => {
289289
}
290290

291291
// Verify encrypted fields
292-
expect(encryptedModel.data.email).toHaveProperty('k')
293-
expect(encryptedModel.data.address).toHaveProperty('k')
292+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
293+
expect(encryptedModel.data.address).toHaveProperty('k', 'ct')
294294
expect(encryptedModel.data.json).toBeUndefined()
295295

296296
const decryptedResult = await protectClient.decryptModel<User>(
@@ -326,13 +326,13 @@ describe('JSON bulk encryption and decryption', () => {
326326
expect(encryptedData.data).toHaveLength(3)
327327
expect(encryptedData.data[0]).toHaveProperty('id', 'user1')
328328
expect(encryptedData.data[0]).toHaveProperty('data')
329-
expect(encryptedData.data[0].data).toHaveProperty('k')
329+
expect(encryptedData.data[0].data).toHaveProperty('k', 'ct')
330330
expect(encryptedData.data[1]).toHaveProperty('id', 'user2')
331331
expect(encryptedData.data[1]).toHaveProperty('data')
332-
expect(encryptedData.data[1].data).toHaveProperty('k')
332+
expect(encryptedData.data[1].data).toHaveProperty('k', 'ct')
333333
expect(encryptedData.data[2]).toHaveProperty('id', 'user3')
334334
expect(encryptedData.data[2]).toHaveProperty('data')
335-
expect(encryptedData.data[2].data).toHaveProperty('k')
335+
expect(encryptedData.data[2].data).toHaveProperty('k', 'ct')
336336

337337
// Now decrypt the data
338338
const decryptedData = await protectClient.bulkDecrypt(encryptedData.data)
@@ -380,13 +380,13 @@ describe('JSON bulk encryption and decryption', () => {
380380
expect(encryptedData.data).toHaveLength(3)
381381
expect(encryptedData.data[0]).toHaveProperty('id', 'user1')
382382
expect(encryptedData.data[0]).toHaveProperty('data')
383-
expect(encryptedData.data[0].data).toHaveProperty('k')
383+
expect(encryptedData.data[0].data).toHaveProperty('k', 'ct')
384384
expect(encryptedData.data[1]).toHaveProperty('id', 'user2')
385385
expect(encryptedData.data[1]).toHaveProperty('data')
386386
expect(encryptedData.data[1].data).toBeNull()
387387
expect(encryptedData.data[2]).toHaveProperty('id', 'user3')
388388
expect(encryptedData.data[2]).toHaveProperty('data')
389-
expect(encryptedData.data[2].data).toHaveProperty('k')
389+
expect(encryptedData.data[2].data).toHaveProperty('k', 'ct')
390390

391391
// Now decrypt the data
392392
const decryptedData = await protectClient.bulkDecrypt(encryptedData.data)
@@ -447,12 +447,12 @@ describe('JSON bulk encryption and decryption', () => {
447447
}
448448

449449
// Verify encrypted fields for each model
450-
expect(encryptedModels.data[0].email).toHaveProperty('k')
451-
expect(encryptedModels.data[0].address).toHaveProperty('k')
452-
expect(encryptedModels.data[0].json).toHaveProperty('k')
453-
expect(encryptedModels.data[1].email).toHaveProperty('k')
454-
expect(encryptedModels.data[1].address).toHaveProperty('k')
455-
expect(encryptedModels.data[1].json).toHaveProperty('k')
450+
expect(encryptedModels.data[0].email).toHaveProperty('k', 'ct')
451+
expect(encryptedModels.data[0].address).toHaveProperty('k', 'ct')
452+
expect(encryptedModels.data[0].json).toHaveProperty('k', 'ct')
453+
expect(encryptedModels.data[1].email).toHaveProperty('k', 'ct')
454+
expect(encryptedModels.data[1].address).toHaveProperty('k', 'ct')
455+
expect(encryptedModels.data[1].json).toHaveProperty('k', 'ct')
456456

457457
// Verify non-encrypted fields remain unchanged
458458
expect(encryptedModels.data[0].id).toBe('1')
@@ -511,7 +511,7 @@ describe('JSON encryption with lock context', () => {
511511
}
512512

513513
// Verify encrypted field
514-
expect(ciphertext.data).toHaveProperty('k')
514+
expect(ciphertext.data).toHaveProperty('k', 'ct')
515515

516516
const plaintext = await protectClient
517517
.decrypt(ciphertext.data)
@@ -557,8 +557,8 @@ describe('JSON encryption with lock context', () => {
557557
}
558558

559559
// Verify encrypted fields
560-
expect(encryptedModel.data.email).toHaveProperty('k')
561-
expect(encryptedModel.data.json).toHaveProperty('k')
560+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
561+
expect(encryptedModel.data.json).toHaveProperty('k', 'ct')
562562

563563
const decryptedResult = await protectClient
564564
.decryptModel(encryptedModel.data)
@@ -606,10 +606,10 @@ describe('JSON encryption with lock context', () => {
606606
expect(encryptedData.data).toHaveLength(2)
607607
expect(encryptedData.data[0]).toHaveProperty('id', 'user1')
608608
expect(encryptedData.data[0]).toHaveProperty('data')
609-
expect(encryptedData.data[0].data).toHaveProperty('k')
609+
expect(encryptedData.data[0].data).toHaveProperty('k', 'ct')
610610
expect(encryptedData.data[1]).toHaveProperty('id', 'user2')
611611
expect(encryptedData.data[1]).toHaveProperty('data')
612-
expect(encryptedData.data[1].data).toHaveProperty('k')
612+
expect(encryptedData.data[1].data).toHaveProperty('k', 'ct')
613613

614614
// Decrypt with lock context
615615
const decryptedData = await protectClient
@@ -670,8 +670,8 @@ describe('JSON nested object encryption', () => {
670670
}
671671

672672
// Verify encrypted fields
673-
expect(encryptedModel.data.email).toHaveProperty('k')
674-
expect(encryptedModel.data.metadata?.profile).toHaveProperty('k')
673+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
674+
expect(encryptedModel.data.metadata?.profile).toHaveProperty('k', 'ct')
675675
expect(encryptedModel.data.metadata?.settings?.preferences).toHaveProperty(
676676
'c',
677677
)
@@ -714,7 +714,7 @@ describe('JSON nested object encryption', () => {
714714
}
715715

716716
// Verify null fields are preserved
717-
expect(encryptedModel.data.email).toHaveProperty('k')
717+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
718718
expect(encryptedModel.data.metadata?.profile).toBeNull()
719719
expect(encryptedModel.data.metadata?.settings?.preferences).toBeNull()
720720

@@ -753,7 +753,7 @@ describe('JSON nested object encryption', () => {
753753
}
754754

755755
// Verify undefined fields are preserved
756-
expect(encryptedModel.data.email).toHaveProperty('k')
756+
expect(encryptedModel.data.email).toHaveProperty('k', 'ct')
757757
expect(encryptedModel.data.metadata?.profile).toBeUndefined()
758758
expect(encryptedModel.data.metadata?.settings?.preferences).toBeUndefined()
759759

@@ -799,7 +799,7 @@ describe('JSON edge cases and error handling', () => {
799799
}
800800

801801
// Verify encrypted field
802-
expect(ciphertext.data).toHaveProperty('k')
802+
expect(ciphertext.data).toHaveProperty('k', 'ct')
803803

804804
const plaintext = await protectClient.decrypt(ciphertext.data)
805805

@@ -847,7 +847,7 @@ describe('JSON edge cases and error handling', () => {
847847
}
848848

849849
// Verify encrypted field
850-
expect(ciphertext.data).toHaveProperty('k')
850+
expect(ciphertext.data).toHaveProperty('k', 'ct')
851851

852852
const plaintext = await protectClient.decrypt(ciphertext.data)
853853

@@ -948,7 +948,7 @@ describe('JSON advanced scenarios', () => {
948948
}
949949

950950
// Verify encrypted field
951-
expect(ciphertext.data).toHaveProperty('k')
951+
expect(ciphertext.data).toHaveProperty('k', 'ct')
952952

953953
const plaintext = await protectClient.decrypt(ciphertext.data)
954954

@@ -975,7 +975,7 @@ describe('JSON advanced scenarios', () => {
975975
}
976976

977977
// Verify encrypted field
978-
expect(ciphertext.data).toHaveProperty('k')
978+
expect(ciphertext.data).toHaveProperty('k', 'ct')
979979

980980
const plaintext = await protectClient.decrypt(ciphertext.data)
981981

@@ -1010,7 +1010,7 @@ describe('JSON advanced scenarios', () => {
10101010
}
10111011

10121012
// Verify encrypted field
1013-
expect(ciphertext.data).toHaveProperty('k')
1013+
expect(ciphertext.data).toHaveProperty('k', 'ct')
10141014

10151015
const plaintext = await protectClient.decrypt(ciphertext.data)
10161016

@@ -1045,7 +1045,7 @@ describe('JSON advanced scenarios', () => {
10451045
}
10461046

10471047
// Verify encrypted field
1048-
expect(ciphertext.data).toHaveProperty('k')
1048+
expect(ciphertext.data).toHaveProperty('k', 'ct')
10491049

10501050
const plaintext = await protectClient.decrypt(ciphertext.data)
10511051

@@ -1081,7 +1081,7 @@ describe('JSON advanced scenarios', () => {
10811081
}
10821082

10831083
// Verify encrypted field
1084-
expect(ciphertext.data).toHaveProperty('k')
1084+
expect(ciphertext.data).toHaveProperty('k', 'ct')
10851085

10861086
const plaintext = await protectClient.decrypt(ciphertext.data)
10871087

@@ -1140,7 +1140,7 @@ describe('JSON error handling and edge cases', () => {
11401140
}
11411141

11421142
// Verify encrypted field
1143-
expect(ciphertext.data).toHaveProperty('k')
1143+
expect(ciphertext.data).toHaveProperty('k', 'ct')
11441144

11451145
const plaintext = await protectClient.decrypt(ciphertext.data)
11461146

@@ -1169,7 +1169,7 @@ describe('JSON error handling and edge cases', () => {
11691169
}
11701170

11711171
// Verify encrypted field
1172-
expect(ciphertext.data).toHaveProperty('k')
1172+
expect(ciphertext.data).toHaveProperty('k', 'ct')
11731173

11741174
const plaintext = await protectClient.decrypt(ciphertext.data)
11751175

@@ -1206,7 +1206,7 @@ describe('JSON error handling and edge cases', () => {
12061206
}
12071207

12081208
// Verify encrypted field
1209-
expect(ciphertext.data).toHaveProperty('k')
1209+
expect(ciphertext.data).toHaveProperty('k', 'ct')
12101210

12111211
const plaintext = await protectClient.decrypt(ciphertext.data)
12121212

packages/protect/__tests__/number-protect.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,10 @@ describe('Bulk encryption and decryption', () => {
304304
expect(encryptedData.data[2]).toHaveProperty('data')
305305
expect(encryptedData.data[2].data).toHaveProperty('c')
306306

307-
// Forward compatibility: new encryptions should NOT have k field
308-
expect(encryptedData.data[0].data).toHaveProperty('k')
309-
expect(encryptedData.data[1].data).toHaveProperty('k')
310-
expect(encryptedData.data[2].data).toHaveProperty('k')
307+
// EQL v2.3: scalar encryptions carry the `k: 'ct'` discriminator
308+
expect(encryptedData.data[0].data).toHaveProperty('k', 'ct')
309+
expect(encryptedData.data[1].data).toHaveProperty('k', 'ct')
310+
expect(encryptedData.data[2].data).toHaveProperty('k', 'ct')
311311

312312
// Verify all encrypted values are different
313313
const getCiphertext = (data: { c?: unknown } | null | undefined) => data?.c

packages/schema/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,12 @@ const tableSchema = z.record(columnSchema).default({})
8989

9090
const tablesSchema = z.record(tableSchema).default({})
9191

92+
// `v` is locked to `1` because protect-ffi `0.22.0+` rejects any other value at
93+
// `newClient` with `UNSUPPORTED_CONFIG_VERSION`. Enforcing it here means a bad
94+
// hand-rolled config fails at zod-validation time instead of crossing into the
95+
// native module first.
9296
export const encryptConfigSchema = z.object({
93-
v: z.number(),
97+
v: z.literal(1),
9498
tables: tablesSchema,
9599
})
96100

0 commit comments

Comments
 (0)