@@ -792,32 +792,33 @@ describe("run-ops split — CONCURRENT global-scope idempotency dedup across two
792792 const taskIdentifier = "child-task" ;
793793 const concern = new IdempotencyKeyConcern ( prisma14 as never , { } as never , { } as never ) ;
794794
795- const { bRes, cRes } = await driveWinnerPlusTwoLosers ( {
796- concern,
797- router,
798- buffer : bufferHarness ,
799- env,
800- idempotencyKey,
801- taskIdentifier,
802- // Winner child is a genuinely-expired run: an EXPIRED status AND an already-expired key, so
803- // handleExistingRun's expiry branch clears it. Both attributes matter for determinism: the
804- // expiry drives the clear; the EXPIRED status keeps the SECOND loser on the clear path even
805- // after the first loser's clear has NULLed the key + expiry (a plain PENDING winner would
806- // then look "live" to the second reader and dedup by accident, masking the duplicate).
807- winnerSeed : { status : "EXPIRED" , idempotencyKeyExpiresAt : new Date ( Date . now ( ) - 60_000 ) } ,
808- a : {
809- spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
810- childResidency : "NEW" ,
811- } ,
812- b : {
813- spec : { parentResidency : "LEGACY" , idempotencyKey, scope : "global" , taskIdentifier } ,
814- childResidency : "LEGACY" ,
815- } ,
816- c : {
817- spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
818- childResidency : "NEW" ,
819- } ,
820- } ) ;
795+ const { bRes, cRes, childWFriendly, childBFriendly, childCFriendly } =
796+ await driveWinnerPlusTwoLosers ( {
797+ concern,
798+ router,
799+ buffer : bufferHarness ,
800+ env,
801+ idempotencyKey,
802+ taskIdentifier,
803+ // Winner child is a genuinely-expired run: an EXPIRED status AND an already-expired key, so
804+ // handleExistingRun's expiry branch clears it. Both attributes matter for determinism: the
805+ // expiry drives the clear; the EXPIRED status keeps the SECOND loser on the clear path even
806+ // after the first loser's clear has NULLed the key + expiry (a plain PENDING winner would
807+ // then look "live" to the second reader and dedup by accident, masking the duplicate).
808+ winnerSeed : { status : "EXPIRED" , idempotencyKeyExpiresAt : new Date ( Date . now ( ) - 60_000 ) } ,
809+ a : {
810+ spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
811+ childResidency : "NEW" ,
812+ } ,
813+ b : {
814+ spec : { parentResidency : "LEGACY" , idempotencyKey, scope : "global" , taskIdentifier } ,
815+ childResidency : "LEGACY" ,
816+ } ,
817+ c : {
818+ spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
819+ childResidency : "NEW" ,
820+ } ,
821+ } ) ;
821822
822823 // The winner's key was cleared, so only the recreated child(ren) still carry the key. Exactly
823824 // ONE new child ⇒ the reacquire serialised the two losers' recreate across the split.
@@ -826,6 +827,14 @@ describe("run-ops split — CONCURRENT global-scope idempotency dedup across two
826827 // Exactly one loser recreated (claim reacquired); the other resolved to it as a cached hit.
827828 const cachedCount = [ bRes , cRes ] . filter ( ( r ) => r . isCached ) . length ;
828829 expect ( cachedCount ) . toBe ( 1 ) ;
830+ // The cached loser must resolve to the RECREATED run (the other loser's fresh child), NOT the
831+ // cleared winner — a stale isCached still pointing at childWFriendly would otherwise slip past.
832+ const recreaterFriendly = bRes . isCached ? childCFriendly : childBFriendly ;
833+ const cachedLoser = bRes . isCached ? bRes : cRes ;
834+ if ( cachedLoser . isCached === true ) {
835+ expect ( cachedLoser . run . friendlyId ) . toBe ( recreaterFriendly ) ;
836+ expect ( cachedLoser . run . friendlyId ) . not . toBe ( childWFriendly ) ;
837+ }
829838 // Both losers genuinely contended on the claim (winner + two losers ⇒ ≥3 attempts).
830839 expect ( bufferHarness . claimCalls ) . toBeGreaterThanOrEqual ( 3 ) ;
831840 }
@@ -847,33 +856,42 @@ describe("run-ops split — CONCURRENT global-scope idempotency dedup across two
847856 const taskIdentifier = "child-task" ;
848857 const concern = new IdempotencyKeyConcern ( prisma14 as never , { } as never , { } as never ) ;
849858
850- const { bRes, cRes } = await driveWinnerPlusTwoLosers ( {
851- concern,
852- router,
853- buffer : bufferHarness ,
854- env,
855- idempotencyKey,
856- taskIdentifier,
857- // Winner child carries a LIVE (future) key but a failed status → status-clear branch fires.
858- winnerSeed : { status : "COMPLETED_WITH_ERRORS" } ,
859- a : {
860- spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
861- childResidency : "NEW" ,
862- } ,
863- b : {
864- spec : { parentResidency : "LEGACY" , idempotencyKey, scope : "global" , taskIdentifier } ,
865- childResidency : "LEGACY" ,
866- } ,
867- c : {
868- spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
869- childResidency : "NEW" ,
870- } ,
871- } ) ;
859+ const { bRes, cRes, childWFriendly, childBFriendly, childCFriendly } =
860+ await driveWinnerPlusTwoLosers ( {
861+ concern,
862+ router,
863+ buffer : bufferHarness ,
864+ env,
865+ idempotencyKey,
866+ taskIdentifier,
867+ // Winner child carries a LIVE (future) key but a failed status → status-clear branch fires.
868+ winnerSeed : { status : "COMPLETED_WITH_ERRORS" } ,
869+ a : {
870+ spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
871+ childResidency : "NEW" ,
872+ } ,
873+ b : {
874+ spec : { parentResidency : "LEGACY" , idempotencyKey, scope : "global" , taskIdentifier } ,
875+ childResidency : "LEGACY" ,
876+ } ,
877+ c : {
878+ spec : { parentResidency : "NEW" , idempotencyKey, scope : "global" , taskIdentifier } ,
879+ childResidency : "NEW" ,
880+ } ,
881+ } ) ;
872882
873883 const counts = await countChildren ( prisma14 , prisma17 , env , idempotencyKey , taskIdentifier ) ;
874884 expect ( counts . total ) . toBe ( 1 ) ;
875885 const cachedCount = [ bRes , cRes ] . filter ( ( r ) => r . isCached ) . length ;
876886 expect ( cachedCount ) . toBe ( 1 ) ;
887+ // The cached loser must resolve to the RECREATED run (the other loser's fresh child), NOT the
888+ // cleared winner — a stale isCached still pointing at childWFriendly would otherwise slip past.
889+ const recreaterFriendly = bRes . isCached ? childCFriendly : childBFriendly ;
890+ const cachedLoser = bRes . isCached ? bRes : cRes ;
891+ if ( cachedLoser . isCached === true ) {
892+ expect ( cachedLoser . run . friendlyId ) . toBe ( recreaterFriendly ) ;
893+ expect ( cachedLoser . run . friendlyId ) . not . toBe ( childWFriendly ) ;
894+ }
877895 expect ( bufferHarness . claimCalls ) . toBeGreaterThanOrEqual ( 3 ) ;
878896 }
879897 ) ;
0 commit comments