@@ -1315,7 +1315,7 @@ describe("hot-tier vertical slice", () => {
13151315 } ] ) ;
13161316 } ) ;
13171317
1318- it ( "recovers a stuck same-group drain with one bounded recovery timer " , async ( ) => {
1318+ it ( "keeps one bounded recovery timer for a stuck same-group drain" , async ( ) => {
13191319 const redis = new RedisClient ( { endpoint : "redis://unused" } ) ;
13201320 const redisCache = new RedisCacheService ( redis , {
13211321 ttlSeconds : 300 ,
@@ -1331,8 +1331,7 @@ describe("hot-tier vertical slice", () => {
13311331
13321332 const neverSettles = new Promise < never > ( ( ) => { } ) ;
13331333 const drainCalls : string [ ] = [ ] ;
1334- const refreshCalls : Array < { groupId : string ; query : string } > = [ ] ;
1335- let callCount = 0 ;
1334+ const warnSpy = spy ( logger , "warn" ) ;
13361335 const graphitiAsync = new GraphitiAsyncService (
13371336 {
13381337 getEpisodes ( ) {
@@ -1341,55 +1340,57 @@ describe("hot-tier vertical slice", () => {
13411340 searchMemoryFacts ( ) {
13421341 return Promise . resolve ( [ ] ) ;
13431342 } ,
1344- searchNodesWithStatus ( input : { query : string ; groupIds : string [ ] } ) {
1345- refreshCalls . push ( { groupId : input . groupIds [ 0 ] , query : input . query } ) ;
1343+ searchNodesWithStatus ( ) {
13461344 return Promise . resolve ( { nodes : [ ] , degraded : false } ) ;
13471345 } ,
13481346 } as never ,
13491347 redisCache ,
13501348 {
13511349 drainGroup ( groupId : string ) {
13521350 drainCalls . push ( groupId ) ;
1353- callCount += 1 ;
1354- if ( callCount === 1 ) return neverSettles ;
1355- if ( callCount === 2 ) {
1356- return Promise . resolve ( { status : "success" as const , drained : 1 } ) ;
1357- }
1358- return Promise . resolve ( { status : "empty" as const , drained : 0 } ) ;
1351+ return neverSettles ;
13591352 } ,
13601353 } as never ,
13611354 1 ,
1355+ 1 ,
13621356 ) ;
13631357
1364- const drainRecoveryTimers = (
1365- graphitiAsync as unknown as {
1366- drainRecoveryTimers : Map <
1367- string ,
1368- { run : Promise < void > ; timer : ReturnType < typeof setTimeout > }
1369- > ;
1370- }
1371- ) . drainRecoveryTimers ;
1358+ try {
1359+ const drainRecoveryTimers = (
1360+ graphitiAsync as unknown as {
1361+ drainRecoveryTimers : Map <
1362+ string ,
1363+ { run : Promise < void > ; timer : ReturnType < typeof setTimeout > }
1364+ > ;
1365+ }
1366+ ) . drainRecoveryTimers ;
13721367
1373- graphitiAsync . scheduleDrain ( "group-1" ) ;
1374- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1375- graphitiAsync . scheduleDrain ( "group-1" ) ;
1376- graphitiAsync . scheduleDrain ( "group-1" ) ;
1368+ graphitiAsync . scheduleDrain ( "group-1" ) ;
1369+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1370+ graphitiAsync . scheduleDrain ( "group-1" ) ;
1371+ graphitiAsync . scheduleDrain ( "group-1" ) ;
13771372
1378- assertEquals ( drainRecoveryTimers . size , 1 ) ;
1373+ assertEquals ( drainRecoveryTimers . size , 1 ) ;
13791374
1380- await waitFor ( ( ) => drainCalls . length === 3 ) ;
1381- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1382- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1375+ await waitFor ( ( ) => warnSpy . calls . length === 1 ) ;
1376+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
13831377
1384- assertEquals ( drainCalls , [ "group-1" , "group-1" , "group-1" ] ) ;
1385- assertEquals ( refreshCalls , [ {
1386- groupId : "group-1" ,
1387- query : "cached query" ,
1388- } ] ) ;
1389- assertEquals ( drainRecoveryTimers . size , 0 ) ;
1378+ assertEquals ( drainCalls , [ "group-1" ] ) ;
1379+ assertEquals (
1380+ warnSpy . calls [ 0 ] . args [ 0 ] ,
1381+ "Graphiti drain recovery timeout exceeded; leaving in-flight drain intact" ,
1382+ ) ;
1383+ assertEquals ( warnSpy . calls [ 0 ] . args [ 1 ] , {
1384+ groupId : "group-1" ,
1385+ timeoutMs : 1 ,
1386+ } ) ;
1387+ assertEquals ( drainRecoveryTimers . size , 0 ) ;
1388+ } finally {
1389+ warnSpy . restore ( ) ;
1390+ }
13901391 } ) ;
13911392
1392- it ( "recovers a stuck drain even without a duplicate schedule signal" , async ( ) => {
1393+ it ( "warns on a stuck drain even without a duplicate schedule signal" , async ( ) => {
13931394 const redis = new RedisClient ( { endpoint : "redis://unused" } ) ;
13941395 const redisCache = new RedisCacheService ( redis , {
13951396 ttlSeconds : 300 ,
@@ -1405,8 +1406,7 @@ describe("hot-tier vertical slice", () => {
14051406
14061407 const neverSettles = new Promise < never > ( ( ) => { } ) ;
14071408 const drainCalls : string [ ] = [ ] ;
1408- const refreshCalls : Array < { groupId : string ; query : string } > = [ ] ;
1409- let callCount = 0 ;
1409+ const warnSpy = spy ( logger , "warn" ) ;
14101410 const graphitiAsync = new GraphitiAsyncService (
14111411 {
14121412 getEpisodes ( ) {
@@ -1415,37 +1415,39 @@ describe("hot-tier vertical slice", () => {
14151415 searchMemoryFacts ( ) {
14161416 return Promise . resolve ( [ ] ) ;
14171417 } ,
1418- searchNodesWithStatus ( input : { query : string ; groupIds : string [ ] } ) {
1419- refreshCalls . push ( { groupId : input . groupIds [ 0 ] , query : input . query } ) ;
1418+ searchNodesWithStatus ( ) {
14201419 return Promise . resolve ( { nodes : [ ] , degraded : false } ) ;
14211420 } ,
14221421 } as never ,
14231422 redisCache ,
14241423 {
14251424 drainGroup ( groupId : string ) {
14261425 drainCalls . push ( groupId ) ;
1427- callCount += 1 ;
1428- if ( callCount === 1 ) return neverSettles ;
1429- if ( callCount === 2 ) {
1430- return Promise . resolve ( { status : "success" as const , drained : 1 } ) ;
1431- }
1432- return Promise . resolve ( { status : "empty" as const , drained : 0 } ) ;
1426+ return neverSettles ;
14331427 } ,
14341428 } as never ,
14351429 1 ,
1430+ 1 ,
14361431 ) ;
14371432
1438- graphitiAsync . scheduleDrain ( "group-1" ) ;
1433+ try {
1434+ graphitiAsync . scheduleDrain ( "group-1" ) ;
14391435
1440- await waitFor ( ( ) => drainCalls . length === 3 ) ;
1441- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1442- await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
1436+ await waitFor ( ( ) => warnSpy . calls . length === 1 ) ;
1437+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
14431438
1444- assertEquals ( drainCalls , [ "group-1" , "group-1" , "group-1" ] ) ;
1445- assertEquals ( refreshCalls , [ {
1446- groupId : "group-1" ,
1447- query : "cached query" ,
1448- } ] ) ;
1439+ assertEquals ( drainCalls , [ "group-1" ] ) ;
1440+ assertEquals (
1441+ warnSpy . calls [ 0 ] . args [ 0 ] ,
1442+ "Graphiti drain recovery timeout exceeded; leaving in-flight drain intact" ,
1443+ ) ;
1444+ assertEquals ( warnSpy . calls [ 0 ] . args [ 1 ] , {
1445+ groupId : "group-1" ,
1446+ timeoutMs : 1 ,
1447+ } ) ;
1448+ } finally {
1449+ warnSpy . restore ( ) ;
1450+ }
14491451 } ) ;
14501452
14511453 it ( "preserves warm cache entry when refresh degrades during node search" , async ( ) => {
0 commit comments