@@ -239,4 +239,205 @@ describe("redis-only throws on a miss instead of serving empty Postgres", () =>
239239 ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
240240 expect ( h . delegateTouched ) . not . toContain ( "findSnapshotCompletedWaitpointIds" ) ;
241241 } ) ;
242+
243+ it ( "findExecutionSnapshot throws on a by-id miss at redis-only" , async ( ) => {
244+ const h = missHarness ( { globalMode : "redis-only" } ) ;
245+
246+ await expect (
247+ h . decorated . findExecutionSnapshot ( {
248+ where : { id : "snap_1" , runId : "run_x" } ,
249+ select : { createdAt : true } ,
250+ } as never )
251+ ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
252+ expect ( h . delegateTouched ) . not . toContain ( "findExecutionSnapshot" ) ;
253+ } ) ;
254+ } ) ;
255+
256+ // The unified decision: when the run→org cache is COLD (readModeFor undefined) and some org is
257+ // redis-only, the sync gate cannot tell whether THIS run is redis-only. It must resolve the run's
258+ // org authoritatively rather than either strand a redis-only run (empty Postgres) or over-throw a
259+ // pre-cutover run. This harness supplies that authoritative hook and a configurable Redis read.
260+ function authHarness ( opts : {
261+ globalMode : SnapshotStoreMode ;
262+ read : "miss" | "dangling" | "error" ;
263+ readModeFor ?: ( runId : string , environmentId ?: string ) => SnapshotStoreMode | undefined ;
264+ anyOrgRedisOnly ?: ( ) => boolean ;
265+ anyOrgReadEnabled ?: ( ) => boolean ;
266+ authoritative ?: ( runId : string ) => Promise < SnapshotStoreMode | undefined > ;
267+ } ) {
268+ const danglingRead = {
269+ id : "snap_head" ,
270+ seq : 1 ,
271+ isValid : true ,
272+ entry : { id : "snap_head" , createdAt : new Date ( ) . toISOString ( ) } ,
273+ raw : "{}" ,
274+ danglingCycle : true ,
275+ } ;
276+ const redisRead = ( ) => {
277+ if ( opts . read === "error" ) throw new Error ( "redis brownout" ) ;
278+ return Promise . resolve ( opts . read === "dangling" ? danglingRead : null ) ;
279+ } ;
280+ const redis = new Proxy ( { } as RedisSnapshotStore , {
281+ get : ( _t , prop ) => {
282+ if ( prop === "getSinceCreatedAt" )
283+ return ( ) => ( opts . read === "error" ? redisRead ( ) : Promise . resolve ( { kind : "miss" } ) ) ;
284+ if ( prop === "getSnapshotWaitpointIds" )
285+ return ( ) =>
286+ opts . read === "error"
287+ ? redisRead ( )
288+ : Promise . resolve ( { present : false , distinctIds : [ ] , order : [ ] } ) ;
289+ return ( ) => redisRead ( ) ;
290+ } ,
291+ } ) ;
292+
293+ const delegateTouched : string [ ] = [ ] ;
294+ const delegate = new Proxy ( { } as Record < string , unknown > , {
295+ get : ( _t , prop ) => {
296+ return ( ...__ : unknown [ ] ) => {
297+ delegateTouched . push ( String ( prop ) ) ;
298+ return Promise . resolve ( { id : "pg_fallback" } ) ;
299+ } ;
300+ } ,
301+ } ) as unknown as RunStore ;
302+
303+ const authoritativeCalls : string [ ] = [ ] ;
304+ const modeResolver : SnapshotStoreModeResolver = {
305+ resolve : ( ) => opts . globalMode ,
306+ ...( opts . readModeFor && {
307+ readModeFor : opts . readModeFor as SnapshotStoreModeResolver [ "readModeFor" ] ,
308+ } ) ,
309+ ...( opts . anyOrgRedisOnly && { anyOrgRedisOnly : opts . anyOrgRedisOnly } ) ,
310+ ...( opts . anyOrgReadEnabled && { anyOrgReadEnabled : opts . anyOrgReadEnabled } ) ,
311+ ...( opts . authoritative && {
312+ readModeForAuthoritative : ( async ( runId : string ) => {
313+ authoritativeCalls . push ( runId ) ;
314+ return opts . authoritative ! ( runId ) ;
315+ } ) as SnapshotStoreModeResolver [ "readModeForAuthoritative" ] ,
316+ } ) ,
317+ } ;
318+
319+ const decorated = new TaskRunExecutionSnapshotStore ( delegate , {
320+ store : redis ,
321+ mode : opts . globalMode ,
322+ modeResolver,
323+ readPercent : 100 ,
324+ } ) ;
325+
326+ return { decorated, delegateTouched, authoritativeCalls } ;
327+ }
328+
329+ describe ( "redis-only fallback resolves a cold run→org cache authoritatively" , ( ) => {
330+ it ( "routing branch: a cold-cache redis-only run at global dual-write THROWS, never empty PG" , async ( ) => {
331+ const h = authHarness ( {
332+ globalMode : "dual-write" ,
333+ read : "miss" ,
334+ readModeFor : ( ) => undefined ,
335+ anyOrgRedisOnly : ( ) => true ,
336+ authoritative : async ( ) => "redis-only" ,
337+ } ) ;
338+
339+ await expect (
340+ h . decorated . findLatestExecutionSnapshot ( "run_cold" , undefined , "env_a" )
341+ ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
342+ expect ( h . delegateTouched ) . not . toContain ( "findLatestExecutionSnapshot" ) ;
343+ expect ( h . authoritativeCalls ) . toContain ( "run_cold" ) ;
344+ } ) ;
345+
346+ it ( "routing branch: a cold-cache dual-write run falls back (no over-throw)" , async ( ) => {
347+ const h = authHarness ( {
348+ globalMode : "dual-write" ,
349+ read : "miss" ,
350+ readModeFor : ( ) => undefined ,
351+ anyOrgRedisOnly : ( ) => true ,
352+ authoritative : async ( ) => "dual-write" ,
353+ } ) ;
354+
355+ const result = await h . decorated . findLatestExecutionSnapshot ( "run_cold" , undefined , "env_a" ) ;
356+ expect ( result ) . toEqual ( { id : "pg_fallback" } ) ;
357+ expect ( h . delegateTouched ) . toContain ( "findLatestExecutionSnapshot" ) ;
358+ } ) ;
359+
360+ it ( "off/dual-write with no org redis-only falls back WITHOUT an authoritative read" , async ( ) => {
361+ const h = authHarness ( {
362+ globalMode : "dual-write" ,
363+ read : "miss" ,
364+ readModeFor : ( ) => undefined ,
365+ anyOrgRedisOnly : ( ) => false ,
366+ authoritative : async ( ) => "redis-only" ,
367+ } ) ;
368+
369+ const result = await h . decorated . findLatestExecutionSnapshot ( "run_x" , undefined , "env_a" ) ;
370+ expect ( result ) . toEqual ( { id : "pg_fallback" } ) ;
371+ expect ( h . authoritativeCalls ) . toEqual ( [ ] ) ;
372+ } ) ;
373+
374+ it ( "MISS at global redis-read with a cold-cache pre-cutover run FALLS BACK (coexistence)" , async ( ) => {
375+ const h = authHarness ( {
376+ globalMode : "redis-read" ,
377+ read : "miss" ,
378+ anyOrgReadEnabled : ( ) => true ,
379+ readModeFor : ( ) => undefined ,
380+ anyOrgRedisOnly : ( ) => true ,
381+ authoritative : async ( ) => "redis-read" ,
382+ } ) ;
383+
384+ const result = await h . decorated . findLatestExecutionSnapshot ( "run_pre" , undefined , "env_a" ) ;
385+ expect ( result ) . toEqual ( { id : "pg_fallback" } ) ;
386+ expect ( h . delegateTouched ) . toContain ( "findLatestExecutionSnapshot" ) ;
387+ } ) ;
388+
389+ it ( "MISS at global redis-read with a cold-cache redis-only run THROWS" , async ( ) => {
390+ const h = authHarness ( {
391+ globalMode : "redis-read" ,
392+ read : "miss" ,
393+ anyOrgReadEnabled : ( ) => true ,
394+ readModeFor : ( ) => undefined ,
395+ anyOrgRedisOnly : ( ) => true ,
396+ authoritative : async ( ) => "redis-only" ,
397+ } ) ;
398+
399+ await expect (
400+ h . decorated . findLatestExecutionSnapshot ( "run_ro" , undefined , "env_a" )
401+ ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
402+ expect ( h . delegateTouched ) . not . toContain ( "findLatestExecutionSnapshot" ) ;
403+ } ) ;
404+
405+ it ( "fails closed (THROWS) when the authoritative read itself fails and some org is redis-only" , async ( ) => {
406+ const h = authHarness ( {
407+ globalMode : "redis-read" ,
408+ read : "miss" ,
409+ anyOrgReadEnabled : ( ) => true ,
410+ readModeFor : ( ) => undefined ,
411+ anyOrgRedisOnly : ( ) => true ,
412+ authoritative : async ( ) => {
413+ throw new Error ( "run→org read timed out" ) ;
414+ } ,
415+ } ) ;
416+
417+ await expect (
418+ h . decorated . findLatestExecutionSnapshot ( "run_cold" , undefined , "env_a" )
419+ ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
420+ expect ( h . delegateTouched ) . not . toContain ( "findLatestExecutionSnapshot" ) ;
421+ } ) ;
422+
423+ it ( "danglingCycle at global redis-only THROWS, never empty PG" , async ( ) => {
424+ const h = authHarness ( { globalMode : "redis-only" , read : "dangling" } ) ;
425+
426+ await expect (
427+ h . decorated . findLatestExecutionSnapshot ( "run_x" , undefined , "env_a" )
428+ ) . rejects . toThrow ( / r e d i s - o n l y / ) ;
429+ expect ( h . delegateTouched ) . not . toContain ( "findLatestExecutionSnapshot" ) ;
430+ } ) ;
431+
432+ it ( "danglingCycle below redis-only still falls back to Postgres" , async ( ) => {
433+ const h = authHarness ( {
434+ globalMode : "redis-read" ,
435+ read : "dangling" ,
436+ anyOrgReadEnabled : ( ) => true ,
437+ } ) ;
438+
439+ const result = await h . decorated . findLatestExecutionSnapshot ( "run_x" , undefined , "env_a" ) ;
440+ expect ( result ) . toEqual ( { id : "pg_fallback" } ) ;
441+ expect ( h . delegateTouched ) . toContain ( "findLatestExecutionSnapshot" ) ;
442+ } ) ;
242443} ) ;
0 commit comments