Skip to content

Commit a297ed3

Browse files
calvinbrewerclaude
authored andcommitted
feat(prisma-next): EQL v3 bundle baseline migration from @cipherstash/eql
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 75560ab commit a297ed3

13 files changed

Lines changed: 596 additions & 14 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// ⚠️ GENERATED FILE - DO NOT EDIT
2+
// This file is automatically generated by 'prisma-next contract emit'.
3+
// To regenerate, run: prisma-next contract emit
4+
import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma-next/adapter-postgres/operation-types';
5+
import type {
6+
Bit,
7+
Char,
8+
CodecTypes as PgTypes,
9+
Interval,
10+
JsonValue,
11+
Numeric,
12+
Time,
13+
Timestamp,
14+
Timestamptz,
15+
Timetz,
16+
VarBit,
17+
Varchar,
18+
} from '@prisma-next/target-postgres/codec-types';
19+
20+
import type {
21+
ContractWithTypeMaps,
22+
TypeMaps as TypeMapsType,
23+
} from '@prisma-next/sql-contract/types';
24+
import type {
25+
Contract as ContractType,
26+
ExecutionHashBase,
27+
NamespaceId,
28+
ProfileHashBase,
29+
StorageHashBase,
30+
} from '@prisma-next/contract/types';
31+
32+
export type StorageHash =
33+
StorageHashBase<'sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7'>;
34+
export type ExecutionHash = ExecutionHashBase<string>;
35+
export type ProfileHash =
36+
ProfileHashBase<'sha256:9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb'>;
37+
38+
export type CodecTypes = PgTypes;
39+
export type LaneCodecTypes = CodecTypes;
40+
export type QueryOperationTypes = PgAdapterQueryOps<CodecTypes>;
41+
type DefaultLiteralValue<CodecId extends string, _Encoded> = CodecId extends keyof CodecTypes
42+
? CodecTypes[CodecId]['output']
43+
: _Encoded;
44+
45+
export type FieldOutputTypes = {
46+
readonly public: {
47+
readonly EqlV2Configuration: {
48+
readonly id: CodecTypes['pg/text@1']['output'];
49+
readonly state: CodecTypes['pg/text@1']['output'];
50+
readonly data: CodecTypes['pg/jsonb@1']['output'];
51+
};
52+
};
53+
};
54+
export type FieldInputTypes = {
55+
readonly public: {
56+
readonly EqlV2Configuration: {
57+
readonly id: CodecTypes['pg/text@1']['input'];
58+
readonly state: CodecTypes['pg/text@1']['input'];
59+
readonly data: CodecTypes['pg/jsonb@1']['input'];
60+
};
61+
};
62+
};
63+
export type TypeMaps = TypeMapsType<
64+
CodecTypes,
65+
QueryOperationTypes,
66+
FieldOutputTypes,
67+
FieldInputTypes
68+
>;
69+
70+
type ContractBase = Omit<
71+
ContractType<{
72+
readonly namespaces: {
73+
readonly public: {
74+
readonly id: 'public';
75+
readonly kind: 'sql-namespace';
76+
readonly entries: {
77+
readonly table: {
78+
readonly eql_v2_configuration: {
79+
columns: {
80+
readonly id: {
81+
readonly nativeType: 'text';
82+
readonly codecId: 'pg/text@1';
83+
readonly nullable: false;
84+
};
85+
readonly state: {
86+
readonly nativeType: 'text';
87+
readonly codecId: 'pg/text@1';
88+
readonly nullable: false;
89+
};
90+
readonly data: {
91+
readonly nativeType: 'jsonb';
92+
readonly codecId: 'pg/jsonb@1';
93+
readonly nullable: false;
94+
};
95+
};
96+
primaryKey: { readonly columns: readonly ['id'] };
97+
uniques: readonly [];
98+
indexes: readonly [];
99+
foreignKeys: readonly [];
100+
};
101+
};
102+
};
103+
};
104+
};
105+
readonly storageHash: StorageHash;
106+
}>,
107+
'roots' | 'domain'
108+
> & {
109+
readonly target: 'postgres';
110+
readonly targetFamily: 'sql';
111+
readonly roots: {
112+
readonly eql_v2_configuration: {
113+
readonly namespace: 'public' & NamespaceId;
114+
readonly model: 'EqlV2Configuration';
115+
};
116+
};
117+
readonly domain: {
118+
readonly namespaces: {
119+
readonly public: {
120+
readonly models: {
121+
readonly EqlV2Configuration: {
122+
readonly fields: {
123+
readonly id: {
124+
readonly nullable: false;
125+
readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
126+
};
127+
readonly state: {
128+
readonly nullable: false;
129+
readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' };
130+
};
131+
readonly data: {
132+
readonly nullable: false;
133+
readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/jsonb@1' };
134+
};
135+
};
136+
readonly relations: Record<string, never>;
137+
readonly storage: {
138+
readonly table: 'eql_v2_configuration';
139+
readonly namespaceId: 'public';
140+
readonly fields: {
141+
readonly id: { readonly column: 'id' };
142+
readonly state: { readonly column: 'state' };
143+
readonly data: { readonly column: 'data' };
144+
};
145+
};
146+
};
147+
};
148+
};
149+
};
150+
};
151+
readonly capabilities: {
152+
readonly postgres: {
153+
readonly distinctOn: true;
154+
readonly jsonAgg: true;
155+
readonly lateral: true;
156+
readonly limit: true;
157+
readonly orderBy: true;
158+
readonly returning: true;
159+
};
160+
readonly sql: {
161+
readonly defaultInInsert: true;
162+
readonly enums: true;
163+
readonly lateral: true;
164+
readonly returning: true;
165+
};
166+
};
167+
readonly extensionPacks: {};
168+
readonly meta: {};
169+
170+
readonly profileHash: ProfileHash;
171+
};
172+
173+
export type Contract = ContractWithTypeMaps<ContractBase, TypeMaps>;
174+
175+
export type Namespaces = Contract['storage']['namespaces'];
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"schemaVersion": "1",
3+
"targetFamily": "sql",
4+
"target": "postgres",
5+
"profileHash": "sha256:9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb",
6+
"roots": {
7+
"eql_v2_configuration": {
8+
"model": "EqlV2Configuration",
9+
"namespace": "public"
10+
}
11+
},
12+
"domain": {
13+
"namespaces": {
14+
"public": {
15+
"models": {
16+
"EqlV2Configuration": {
17+
"fields": {
18+
"data": {
19+
"nullable": false,
20+
"type": {
21+
"codecId": "pg/jsonb@1",
22+
"kind": "scalar"
23+
}
24+
},
25+
"id": {
26+
"nullable": false,
27+
"type": {
28+
"codecId": "pg/text@1",
29+
"kind": "scalar"
30+
}
31+
},
32+
"state": {
33+
"nullable": false,
34+
"type": {
35+
"codecId": "pg/text@1",
36+
"kind": "scalar"
37+
}
38+
}
39+
},
40+
"relations": {},
41+
"storage": {
42+
"fields": {
43+
"data": {
44+
"column": "data"
45+
},
46+
"id": {
47+
"column": "id"
48+
},
49+
"state": {
50+
"column": "state"
51+
}
52+
},
53+
"namespaceId": "public",
54+
"table": "eql_v2_configuration"
55+
}
56+
}
57+
}
58+
}
59+
}
60+
},
61+
"storage": {
62+
"namespaces": {
63+
"public": {
64+
"entries": {
65+
"table": {
66+
"eql_v2_configuration": {
67+
"columns": {
68+
"data": {
69+
"codecId": "pg/jsonb@1",
70+
"nativeType": "jsonb",
71+
"nullable": false
72+
},
73+
"id": {
74+
"codecId": "pg/text@1",
75+
"nativeType": "text",
76+
"nullable": false
77+
},
78+
"state": {
79+
"codecId": "pg/text@1",
80+
"nativeType": "text",
81+
"nullable": false
82+
}
83+
},
84+
"foreignKeys": [],
85+
"indexes": [],
86+
"primaryKey": {
87+
"columns": [
88+
"id"
89+
]
90+
},
91+
"uniques": []
92+
}
93+
}
94+
},
95+
"id": "public",
96+
"kind": "postgres-schema"
97+
}
98+
},
99+
"storageHash": "sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7"
100+
},
101+
"capabilities": {
102+
"postgres": {
103+
"distinctOn": true,
104+
"jsonAgg": true,
105+
"lateral": true,
106+
"limit": true,
107+
"orderBy": true,
108+
"returning": true
109+
},
110+
"sql": {
111+
"defaultInInsert": true,
112+
"enums": true,
113+
"lateral": true,
114+
"returning": true
115+
}
116+
},
117+
"extensionPacks": {},
118+
"meta": {},
119+
"_generated": {
120+
"warning": "⚠️ GENERATED FILE - DO NOT EDIT",
121+
"message": "This file is automatically generated by \"prisma-next contract emit\".",
122+
"regenerate": "To regenerate, run: prisma-next contract emit"
123+
}
124+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"from": "sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7",
3+
"to": "sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7",
4+
"providedInvariants": [
5+
"cipherstash:install-eql-v3-bundle-v1"
6+
],
7+
"createdAt": "2026-07-14T20:10:24.325Z",
8+
"migrationHash": "sha256:49917bcf99c88546e4edef9a6323a56045ea522313ee460674e0cc90d747b2c3"
9+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env -S node
2+
/**
3+
* CipherStash v3 baseline migration — install the EQL v3 bundle.
4+
*
5+
* The install SQL is sourced at EMIT time from `@cipherstash/eql/sql`
6+
* (see `../../src/migration/eql-bundle-v3.ts` — the same source the
7+
* stack's `installEqlV3IfNeeded` uses) and baked into `ops.json`
8+
* byte-for-byte. The bundle creates the 40 `public.eql_v3_*` storage
9+
* domains, the `eql_v3` operator-function schema (`eql_v3.eq`,
10+
* `eql_v3.ord_term`, …), the `eql_v3.query_*` operand domains, and the
11+
* `eql_v3_internal` helper schema.
12+
*
13+
* This is the SECOND migration in the cipherstash contract space, and
14+
* it is an **invariant-only edge**: the v3 bundle declares no
15+
* contract-space storage (no config table — unlike the v2 bundle's
16+
* `eql_v2_configuration`), so the storage hash does not move and
17+
* `describe()` returns `from === to === <the v2 baseline's to>`. The
18+
* edge exists to carry the `cipherstash:install-eql-v3-bundle-v1`
19+
* invariant: the apply-path planner (`findPathWithInvariants`) walks it
20+
* when the head ref requires that invariant.
21+
*
22+
* Authoring loop: this file is hand-edited (see
23+
* `docs/architecture docs/adrs/ADR 212 - Contract spaces.md`'s
24+
* contract-space package layout section). Re-emit `ops.json` /
25+
* `migration.json` after edits via
26+
* `pnpm exec tsx migrations/20260601T0100_install_eql_v3_bundle/migration.ts`.
27+
*/
28+
import {
29+
Migration,
30+
MigrationCLI,
31+
rawSql,
32+
} from '@prisma-next/target-postgres/migration'
33+
import { CIPHERSTASH_V3_INVARIANTS } from '../../src/extension-metadata/constants-v3'
34+
import {
35+
readInstallSql,
36+
releaseManifest,
37+
} from '../../src/migration/eql-bundle-v3'
38+
39+
const INSTALL_LABEL = `Install EQL v3 bundle (eql-${releaseManifest.eqlVersion}: public.eql_v3_* domains + eql_v3.* functions)`
40+
41+
export default class M extends Migration {
42+
override describe() {
43+
return {
44+
from: 'sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7',
45+
to: 'sha256:1e86a0160ba305fa74516b6d9449218308b258a51a913c1fc907e629f44568a7',
46+
}
47+
}
48+
49+
override get operations() {
50+
return [
51+
rawSql({
52+
id: 'cipherstash.install-eql-v3-bundle',
53+
label: INSTALL_LABEL,
54+
operationClass: 'additive',
55+
invariantId: CIPHERSTASH_V3_INVARIANTS.installBundle,
56+
target: { id: 'postgres' },
57+
precheck: [],
58+
execute: [{ description: INSTALL_LABEL, sql: readInstallSql() }],
59+
postcheck: [
60+
{
61+
description: 'verify the "eql_v3" operator schema exists',
62+
sql: "SELECT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'eql_v3')",
63+
},
64+
{
65+
// The storage domains live in `public` (not `eql_v3`) — by
66+
// design, mirroring the v2 `public.eql_v2_encrypted`
67+
// rationale: customer data columns declared against the
68+
// domains must survive a `DROP SCHEMA eql_v3 CASCADE`
69+
// re-install of the operator functions without losing the
70+
// columns.
71+
description:
72+
'verify the concrete domain "public.eql_v3_text_search" exists',
73+
sql: "SELECT to_regtype('public.eql_v3_text_search') IS NOT NULL",
74+
},
75+
{
76+
description: 'verify the eql_v3.eq operator function exists',
77+
sql: "SELECT EXISTS (SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'eql_v3' AND p.proname = 'eq')",
78+
},
79+
],
80+
}),
81+
]
82+
}
83+
}
84+
85+
MigrationCLI.run(import.meta.url, M)

0 commit comments

Comments
 (0)