@@ -30,6 +30,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
3030import type { VerifyStack } from '@objectstack/verify' ;
3131import { ceremoniedFixture } from './upgrade-harness/ceremony.js' ;
3232import { bootFixture } from './upgrade-harness/populate-fixture.js' ;
33+ import { relaxTenantColumns } from './pre-18-tenant-column.testkit.js' ;
3334
3435const fixtureSql = readFileSync ( new URL ( './upgrade-harness/fixture-18.0/database.sql' , import . meta. url ) , 'utf8' ) ;
3536
@@ -176,6 +177,85 @@ describe('[18.x step 7b] the organization key part across the 18.0 → HEAD upgr
176177 } ) ;
177178 } ) ;
178179
180+ describe ( 'a store 18.0.0 created from empty: birth-marked, nullable tenant columns, COALESCE organization indexes' , ( ) => {
181+ // 18.0.0 (published) births a store with its ownership marker at shape
182+ // generation 1 — the generation HEAD still requires — while it still built
183+ // every tenant column nullable and keyed organization uniques through the
184+ // COALESCE form. Measured on the published 18.0.0 for this part: 34 tenant
185+ // columns, none NOT NULL; 10 of 14 organization uniques in the COALESCE
186+ // form; then HEAD booted it with no refusal and `os migrate plan` read
187+ // 0 changes. This leg models that store from a HEAD birth (same marker),
188+ // the tenant columns relaxed through the drift door and the indexes
189+ // re-spelled exactly as 18.0.0 wrote them, so the CERTIFICATION is the
190+ // only witness: no tenant column here is NOT NULL.
191+ const born = join ( dir , 'born-18.0.0.db' ) ;
192+ let stack : VerifyStack | undefined ;
193+ let respelled : Array < { name : string ; sql : string } > = [ ] ;
194+
195+ beforeAll ( async ( ) => {
196+ const first = await bootFixture ( born ) ;
197+ try {
198+ const ql = ( await first . kernel . getServiceAsync ( 'objectql' ) ) as { getDriverForObject ( object : string ) : unknown } ;
199+ // The tables whose organization unique 18.0.0 keyed through COALESCE —
200+ // the ones whose equivalence is under test. (`sys_setting`'s identity
201+ // index is its runtime migration's, rebuilt every boot.)
202+ const tenantTables = [
203+ ...new Set ( organizationUniqueIndexes ( born ) . map ( ( i ) => i . table ) . filter ( ( t ) => t !== 'sys_setting' ) ) ,
204+ ] ;
205+ await relaxTenantColumns ( ql , tenantTables ) ;
206+ } finally {
207+ await first . stop ( ) ;
208+ }
209+ const db = new DatabaseSync ( born ) ;
210+ try {
211+ for ( const index of organizationUniqueIndexes ( born ) ) {
212+ // `sys_setting`'s identity index is its runtime migration's, rebuilt every boot.
213+ if ( index . table === 'sys_setting' ) continue ;
214+ const sql = index . sql . replace ( / \( \s * ( [ ` " ] ? ) o r g a n i z a t i o n _ i d \1\s * ( [ , ) ] ) / , "(COALESCE($1organization_id$1, '__global__')$2" )
215+ . replace ( / , \s * ( [ ` " ] ? ) o r g a n i z a t i o n _ i d \1\s * ( [ , ) ] ) / , ", COALESCE($1organization_id$1, '__global__')$2" ) ;
216+ if ( sql === index . sql ) continue ;
217+ db . exec ( `DROP INDEX "${ index . name } "` ) ;
218+ db . exec ( sql ) ;
219+ respelled . push ( { name : index . name , sql } ) ;
220+ }
221+ } finally {
222+ db . close ( ) ;
223+ }
224+ stack = await bootFixture ( born ) ;
225+ } , 600_000 ) ;
226+
227+ afterAll ( async ( ) => {
228+ await stack ?. stop ( ) ;
229+ } ) ;
230+
231+ it ( 'is the 18.0.0 shape: a verified generation-1 birth marker, no NOT NULL tenant column, COALESCE indexes' , ( ) => {
232+ const db = new DatabaseSync ( born , { readOnly : true } ) ;
233+ try {
234+ const marker = db . prepare ( 'SELECT verified_at, details FROM sys_migration WHERE id = ?' ) . get (
235+ 'adr-0131-organization-ownership' ,
236+ ) as { verified_at : unknown ; details : string } | undefined ;
237+ expect ( marker ?. verified_at ) . toBeTruthy ( ) ;
238+ expect ( JSON . parse ( marker ! . details ) . birth ?. shapeGeneration ) . toBe ( 1 ) ;
239+ } finally {
240+ db . close ( ) ;
241+ }
242+ expect ( respelled . length , 'the store holds COALESCE organization indexes' ) . toBeGreaterThanOrEqual ( 10 ) ;
243+ for ( const index of respelled ) {
244+ const table = organizationUniqueIndexes ( born ) . find ( ( i ) => i . name === index . name ) ! . table ;
245+ expect ( organizationNotNull ( born , table ) , `${ table } .organization_id` ) . toBe ( false ) ;
246+ }
247+ } ) ;
248+
249+ it ( "HEAD boots it, and its differ reports zero recreate_index and zero destructive drift" , async ( ) => {
250+ expectNoDestructiveIndexDrift ( await managedDrift ( stack ! ) ) ;
251+ } ) ;
252+
253+ it ( 'every COALESCE index survives the HEAD boot byte for byte' , ( ) => {
254+ const after = new Map ( organizationUniqueIndexes ( born ) . map ( ( i ) => [ i . name , i . sql ] ) ) ;
255+ for ( const index of respelled ) expect ( after . get ( index . name ) , index . name ) . toBe ( index . sql ) ;
256+ } ) ;
257+ } ) ;
258+
179259 describe ( 'a store HEAD creates from empty' , ( ) => {
180260 const fresh = join ( dir , 'fresh.db' ) ;
181261 let stack : VerifyStack | undefined ;
0 commit comments