From 765f6c0e15fb0b5360147e4a795f5891d0f78d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 16 Oct 2026 02:59:47 +0200 Subject: [PATCH 1/4] test(sharing): count both v1.43 admissions in the manage matrix The biomarker merge and the supplement composition PUT each brought a strict driver and an owned effect, and each branch raised the count from 88 to 89 on its own. Together the inventory holds 90. --- tests/integration/sharing-manage-handler-matrix.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/sharing-manage-handler-matrix.test.ts b/tests/integration/sharing-manage-handler-matrix.test.ts index 28686b708..8cb459941 100644 --- a/tests/integration/sharing-manage-handler-matrix.test.ts +++ b/tests/integration/sharing-manage-handler-matrix.test.ts @@ -282,7 +282,7 @@ const ROUTE_IMPORTS = import.meta.glob>( describe("the complete MANAGE handler matrix", () => { it("starts with a non-empty, exact handler inventory", () => { - expect(ADMITTED_MUTATING_HANDLERS.length).toBe(89); + expect(ADMITTED_MUTATING_HANDLERS.length).toBe(90); }); }); @@ -2953,13 +2953,13 @@ describe("the conditions the admissions were granted on", () => { describe("the complete MANAGE handler matrix", () => { it("registers one strict actor-and-effect driver for every admission", () => { const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort(); - expect(STRICT_DRIVER_KEYS.size).toBe(89); + expect(STRICT_DRIVER_KEYS.size).toBe(90); expect([...STRICT_DRIVER_KEYS].sort()).toEqual(expected); }); it("executes every registered driver through its owned effect and actor audit", () => { const expected = ADMITTED_MUTATING_HANDLERS.map(matrixKey).sort(); - expect(REAL_EFFECT_KEYS.size).toBe(89); + expect(REAL_EFFECT_KEYS.size).toBe(90); expect([...REAL_EFFECT_KEYS].sort()).toEqual(expected); }); }); From f5ed62276e7ba617156c45e1565ed423092cff51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 16 Oct 2026 02:59:47 +0200 Subject: [PATCH 2/4] test(wipe): seed a column fenced by a CHECK list with a value it admits The schema-driven seeder wrote a synthetic tag into every required text column. The v1.43 tables fence some of them with CHECK (col IN (...)), so the seed was refused before either wipe ran. Read each single-column IN-list check from pg_constraint and seed its first admitted value. --- tests/integration/wipe-seeder.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/integration/wipe-seeder.ts b/tests/integration/wipe-seeder.ts index c23514fc9..23274df00 100644 --- a/tests/integration/wipe-seeder.ts +++ b/tests/integration/wipe-seeder.ts @@ -133,6 +133,27 @@ export async function seedEveryTable( if (!enums.has(row.typname)) enums.set(row.typname, row.enumlabel); } + // A column fenced by a single-column `CHECK (col IN (...))` takes the first + // value the fence admits; a synthesised tag would be refused before the wipe + // ever ran. Postgres stores the IN list as `col = ANY (ARRAY['a'::text, ...])`. + const checkRows = await prisma.$queryRaw< + Array<{ table_name: string; column_name: string; def: string }> + >` + SELECT con.conrelid::regclass::text AS table_name, + att.attname AS column_name, + pg_get_constraintdef(con.oid) AS def + FROM pg_constraint con + JOIN pg_attribute att + ON att.attrelid = con.conrelid AND att.attnum = con.conkey[1] + WHERE con.contype = 'c' + AND con.connamespace = 'public'::regnamespace + AND cardinality(con.conkey) = 1`; + const admitted = new Map(); + for (const row of checkRows) { + const first = /=\s*ANY\s*\(\s*\(?ARRAY\['((?:[^']|'')*)'/.exec(row.def); + if (first) admitted.set(`${row.table_name}.${row.column_name}`, first[1]); + } + const byTable = new Map(); for (const column of columns) { if (SEED_SKIP.has(column.table_name)) continue; @@ -206,7 +227,12 @@ export async function seedEveryTable( continue; } names.push(column.column_name); - values.push(synthesise(column.udt_name, enums, `seed-${tag++}`)); + const fenced = admitted.get(`${table}.${column.column_name}`); + values.push( + fenced !== undefined + ? `'${fenced}'` + : synthesise(column.udt_name, enums, `seed-${tag++}`), + ); } const sql = From 87335d3080609022148295d8f0442a0c6d1be596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 16 Oct 2026 02:59:48 +0200 Subject: [PATCH 3/4] test(managed-profiles): compare the guardian refusals without the request id Every error envelope now carries a fresh meta.requestId. The unknown-id and the not-yours refusal still have to match byte for byte on everything else; compare them through the shared helper, as the other enumeration checks do. --- .../integration/managed-profile-surface.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/integration/managed-profile-surface.test.ts b/tests/integration/managed-profile-surface.test.ts index 253db33a0..1758a5a52 100644 --- a/tests/integration/managed-profile-surface.test.ts +++ b/tests/integration/managed-profile-surface.test.ts @@ -22,6 +22,7 @@ import { cookieJar, headerJar, queuedSessionIds } from "./mock-next-headers"; import { getPrismaClient, switchSessionTo, truncateAllTables } from "./setup"; import { createManagedProfile } from "@/lib/managed-profiles/create"; import { acceptGrant, inviteGrant } from "@/lib/sharing/grants"; +import { bodyWithoutRequestId } from "@/__tests__/helpers/error-envelope"; process.env.API_TOKEN_HMAC_KEY ??= "test-hmac-key-managed-profile-surface-32-bytes-min-1234509876"; @@ -294,9 +295,18 @@ describe("who looks after a managed profile (real Postgres)", () => { const stranger = await person("stranger"); signIn(stranger); - const refused = await (await listGuardians(profile.id)).json(); - const unknown = await (await listGuardians("does-not-exist")).json(); - expect(unknown).toEqual(refused); + // `meta.requestId` is a fresh id per request and says nothing about the + // profile; every other byte has to match. + const refusedRes = await listGuardians(profile.id); + const unknownRes = await listGuardians("does-not-exist"); + expect(unknownRes.status).toBe(refusedRes.status); + const refused = await refusedRes.clone().json(); + const unknown = await unknownRes.clone().json(); + expect(typeof refused.meta?.requestId).toBe("string"); + expect(typeof unknown.meta?.requestId).toBe("string"); + expect(await bodyWithoutRequestId(unknownRes)).toBe( + await bodyWithoutRequestId(refusedRes), + ); }); it.each(["READ", "WRITE", "MANAGE"] as const)( From fc4423c93435b76d36c72ebb8370bc618e1464d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 16 Oct 2026 02:59:48 +0200 Subject: [PATCH 4/4] test(environment): stop asserting the readable location columns 0409 dropped The free-text backfill no longer seals home, period or day locations, so its summary has no counts for them, and a stored day has no readable lat left to be null. --- .../environment-air-quality-history.test.ts | 4 +++- .../free-text-encryption-backfill.test.ts | 23 ++----------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/tests/integration/environment-air-quality-history.test.ts b/tests/integration/environment-air-quality-history.test.ts index 249ecd696..26ef059c2 100644 --- a/tests/integration/environment-air-quality-history.test.ts +++ b/tests/integration/environment-air-quality-history.test.ts @@ -261,8 +261,10 @@ describe("air-quality history backfill", () => { source: "TRAVEL", pm25Mean: 12, tempMax: 5, - lat: null, }); + // The place lives only in the sealed copy; migration 0409 dropped the + // readable columns. + expect(byDate.get("2020-03-01")).not.toHaveProperty("lat"); expect( openLocation(byDate.get("2020-03-01")!.locationEncrypted!), ).toMatchObject({ lat: 51.5, lon: 7.0 }); diff --git a/tests/integration/free-text-encryption-backfill.test.ts b/tests/integration/free-text-encryption-backfill.test.ts index f35b9a577..1733c5130 100644 --- a/tests/integration/free-text-encryption-backfill.test.ts +++ b/tests/integration/free-text-encryption-backfill.test.ts @@ -126,6 +126,8 @@ describe("free-text encryption backfill (real Postgres)", () => { // The boot call is deferred past the startup storm. expect(first.sent.every((s) => s.startAfter === 180)).toBe(true); + // The summary carries no location counts: migration 0409 dropped the + // readable home, period and day locations, so there is nothing to seal. expect(await runFreeTextEncryptionBackfillForUser("ft-a")).toEqual({ conversationTitlesMigrated: 1, metricNotesMigrated: 0, @@ -133,9 +135,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 0, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); expect(await runFreeTextEncryptionBackfillForUser("ft-b")).toEqual({ conversationTitlesMigrated: 0, @@ -144,9 +143,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 0, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); const conversations = await prisma.coachConversation.findMany({ @@ -195,9 +191,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 0, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); }); @@ -282,9 +275,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 0, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); expect(await runFreeTextEncryptionBackfillForUser("pc-b")).toEqual({ conversationTitlesMigrated: 0, @@ -293,9 +283,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 1, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); const practitioners = await prisma.practitioner.findMany({ @@ -356,9 +343,6 @@ describe("free-text encryption backfill (real Postgres)", () => { routeGeometriesMigrated: 0, appointmentAddressesCleared: 0, contactAuditRowsScrubbed: 0, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); }); @@ -456,9 +440,6 @@ describe("free-text encryption backfill (real Postgres)", () => { expect(summaryA).toMatchObject({ appointmentAddressesCleared: 1, contactAuditRowsScrubbed: 1, - homeLocationsMigrated: 0, - travelLocationsMigrated: 0, - dayLocationsMigrated: 0, }); await runFreeTextEncryptionBackfillForUser("ad-b");