@@ -349,8 +349,10 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
349349 // union DESC of all 5: e, d, c, b, a -> first 4.
350350 expect ( pageB . batches . map ( ( b ) => b . id ) ) . toEqual ( [ "batch_e" , "batch_d" , "batch_c" , "batch_b" ] ) ;
351351 expect ( legacySpyB . counts . findMany ) . toBeGreaterThan ( 0 ) ;
352- // cursor parity: next is the composite (createdAt, id) cursor of the 4th row, previous undefined.
353- expect ( pageB . pagination . next ?. endsWith ( "_batch_b" ) ) . toBe ( true ) ;
352+ // cursor parity: next is the FULL composite (createdAt, id) cursor of the 4th row (batch_b),
353+ // previous undefined. Assert the complete value so a bad timestamp prefix can't pass.
354+ const bRow = pageB . batches . find ( ( b ) => b . id === "batch_b" ) ! ;
355+ expect ( pageB . pagination . next ) . toBe ( `${ new Date ( bRow . createdAt ) . getTime ( ) } _batch_b` ) ;
354356 expect ( pageB . pagination . previous ) . toBeUndefined ( ) ;
355357 expect ( pageB . hasAnyBatches ) . toBe ( true ) ;
356358 }
@@ -438,7 +440,9 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
438440 const expectedPage = direct . slice ( 0 , 2 ) ;
439441 expect ( page . batches . map ( ( b ) => b . id ) ) . toEqual ( expectedPage . map ( ( r ) => r . id ) ) ;
440442 expect (
441- hasMore ? page . pagination . next ?. endsWith ( `_${ expectedPage [ 1 ] . id } ` ) : ! page . pagination . next
443+ hasMore
444+ ? page . pagination . next === `${ expectedPage [ 1 ] . createdAt . getTime ( ) } _${ expectedPage [ 1 ] . id } `
445+ : ! page . pagination . next
442446 ) . toBe ( true ) ;
443447 expect ( page . pagination . previous ) . toBeUndefined ( ) ;
444448 expect ( page . hasAnyBatches ) . toBe ( true ) ;
@@ -537,6 +541,61 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
537541 }
538542 ) ;
539543
544+ // Overlap regression: batches duplicated on BOTH stores (mid-migration copies) must de-dupe to one
545+ // each without dropping rows or underfilling pages. Proves the merge needs no post-dedup refill:
546+ // the union of each store's top-(pageSize+1) always contains the global top, and the next page
547+ // re-queries both stores from the cursor.
548+ heteroPostgresTest (
549+ "flipped org: batches duplicated across both stores de-dupe without dropping rows across pagination" ,
550+ async ( { prisma14, prisma17 } ) => {
551+ const ctx = await seedParents ( prisma14 , "ovl" ) ;
552+ await mirrorEnvParents ( prisma17 , ctx , "ovl" ) ;
553+
554+ const at = ( secondsAgo : number ) => new Date ( Date . now ( ) - secondsAgo * 1000 ) ;
555+ // a..e newest->oldest. a,b,c live on BOTH DBs (dup); d,e only on legacy.
556+ const rows = [
557+ { id : "batch_ov_a" , both : true , s : 1 } ,
558+ { id : "batch_ov_b" , both : true , s : 2 } ,
559+ { id : "batch_ov_c" , both : true , s : 3 } ,
560+ { id : "batch_ov_d" , both : false , s : 4 } ,
561+ { id : "batch_ov_e" , both : false , s : 5 } ,
562+ ] ;
563+ for ( const r of rows ) {
564+ await createBatch ( prisma14 , ctx , {
565+ id : r . id ,
566+ friendlyId : `fr_${ r . id } ` ,
567+ createdAt : at ( r . s ) ,
568+ } ) ;
569+ if ( r . both ) {
570+ await createBatch ( prisma17 , ctx , {
571+ id : r . id ,
572+ friendlyId : `fr_${ r . id } ` ,
573+ createdAt : at ( r . s ) ,
574+ } ) ;
575+ }
576+ }
577+
578+ const seen : string [ ] = [ ] ;
579+ let cursor : string | undefined = undefined ;
580+ for ( let i = 0 ; i < 10 ; i ++ ) {
581+ const presenter = new BatchListPresenter ( prisma17 , prisma17 , {
582+ runOpsNew : prisma17 ,
583+ runOpsLegacyReplica : prisma14 ,
584+ controlPlaneReplica : prisma14 ,
585+ splitEnabled : true ,
586+ } ) ;
587+ const page = await presenter . call (
588+ baseCall ( ctx , { pageSize : 2 , cursor, direction : "forward" } )
589+ ) ;
590+ seen . push ( ...page . batches . map ( ( b ) => b . id ) ) ;
591+ if ( ! page . pagination . next ) break ;
592+ cursor = page . pagination . next ;
593+ }
594+ // Every batch exactly once, newest-first; the three dups collapsed to one each.
595+ expect ( seen ) . toEqual ( [ "batch_ov_a" , "batch_ov_b" , "batch_ov_c" , "batch_ov_d" , "batch_ov_e" ] ) ;
596+ }
597+ ) ;
598+
540599 heteroRunOpsPostgresTest (
541600 "scan against dedicated RunOpsPrismaClient (splitEnabled): returns batches from new DB" ,
542601 async ( { prisma14, prisma17 } ) => {
0 commit comments