@@ -62,6 +62,66 @@ function countMatching<T>(items: readonly T[], predicate: (item: T) => boolean)
6262 return count ;
6363}
6464
65+ function requireRecord ( value : unknown , label : string ) : Record < string , unknown > {
66+ expect ( typeof value ) . toBe ( "object" ) ;
67+ expect ( value ) . not . toBeNull ( ) ;
68+ if ( typeof value !== "object" || value === null ) {
69+ throw new Error ( `${ label } was not an object` ) ;
70+ }
71+ return value as Record < string , unknown > ;
72+ }
73+
74+ function expectRecordFields ( record : Record < string , unknown > , fields : Record < string , unknown > ) {
75+ for ( const [ key , value ] of Object . entries ( fields ) ) {
76+ expect ( record [ key ] ) . toEqual ( value ) ;
77+ }
78+ }
79+
80+ function expectNumberField ( record : Record < string , unknown > , key : string ) {
81+ expect ( typeof record [ key ] ) . toBe ( "number" ) ;
82+ }
83+
84+ function requireMatchingRecord (
85+ items : readonly unknown [ ] ,
86+ fields : Record < string , unknown > ,
87+ label : string ,
88+ ) {
89+ const found = items . find ( ( item ) => {
90+ if ( typeof item !== "object" || item === null ) {
91+ return false ;
92+ }
93+ const record = item as Record < string , unknown > ;
94+ return Object . entries ( fields ) . every ( ( [ key , value ] ) => Object . is ( record [ key ] , value ) ) ;
95+ } ) ;
96+ expect ( found ) . toBeDefined ( ) ;
97+ if ( ! found ) {
98+ throw new Error ( `missing ${ label } ` ) ;
99+ }
100+ return requireRecord ( found , label ) ;
101+ }
102+
103+ function requireFirstMockCallArg ( mock : unknown , label : string ) {
104+ const calls = ( mock as { mock ?: { calls ?: unknown [ ] [ ] } } ) . mock ?. calls ;
105+ const call = calls ?. [ 0 ] ;
106+ expect ( call ) . toBeDefined ( ) ;
107+ if ( ! call ) {
108+ throw new Error ( `missing ${ label } call` ) ;
109+ }
110+ return requireRecord ( call [ 0 ] , `${ label } argument` ) ;
111+ }
112+
113+ function expectRecoveryCall (
114+ recoverStuckSession : unknown ,
115+ fields : Record < string , unknown > ,
116+ numberFields : readonly string [ ] ,
117+ ) {
118+ const params = requireFirstMockCallArg ( recoverStuckSession , "recoverStuckSession" ) ;
119+ expectRecordFields ( params , fields ) ;
120+ for ( const key of numberFields ) {
121+ expectNumberField ( params , key ) ;
122+ }
123+ }
124+
65125describe ( "diagnostic session state pruning" , ( ) => {
66126 beforeEach ( ( ) => {
67127 vi . useFakeTimers ( ) ;
@@ -234,18 +294,16 @@ describe("stuck session diagnostics threshold", () => {
234294
235295 const stuckEvents = events . filter ( ( event ) => event . type === "session.stuck" ) ;
236296 expect ( stuckEvents ) . toHaveLength ( 1 ) ;
237- expect ( stuckEvents [ 0 ] ) . toMatchObject ( {
297+ expectRecordFields ( requireRecord ( stuckEvents [ 0 ] , "stuck event" ) , {
238298 classification : "stale_session_state" ,
239299 reason : "stale_session_state" ,
240300 queueDepth : 0 ,
241301 } ) ;
242- expect ( recoverStuckSession ) . toHaveBeenCalledWith ( {
243- sessionId : "s1" ,
244- sessionKey : "main" ,
245- ageMs : expect . any ( Number ) ,
246- queueDepth : 0 ,
247- stateGeneration : expect . any ( Number ) ,
248- } ) ;
302+ expectRecoveryCall (
303+ recoverStuckSession ,
304+ { sessionId : "s1" , sessionKey : "main" , queueDepth : 0 } ,
305+ [ "ageMs" , "stateGeneration" ] ,
306+ ) ;
249307 } ) ;
250308
251309 it ( "keeps queued stale sessions eligible for lane recovery" , ( ) => {
@@ -274,18 +332,16 @@ describe("stuck session diagnostics threshold", () => {
274332 expect ( events . some ( ( event ) => event . type === "session.long_running" ) ) . toBe ( false ) ;
275333 const stuckEvents = events . filter ( ( event ) => event . type === "session.stuck" ) ;
276334 expect ( stuckEvents ) . toHaveLength ( 1 ) ;
277- expect ( stuckEvents [ 0 ] ) . toMatchObject ( {
335+ expectRecordFields ( requireRecord ( stuckEvents [ 0 ] , "stuck event" ) , {
278336 classification : "stale_session_state" ,
279337 reason : "queued_work_without_active_run" ,
280338 queueDepth : 1 ,
281339 } ) ;
282- expect ( recoverStuckSession ) . toHaveBeenCalledWith ( {
283- sessionId : "s1" ,
284- sessionKey : "main" ,
285- ageMs : expect . any ( Number ) ,
286- queueDepth : 1 ,
287- stateGeneration : expect . any ( Number ) ,
288- } ) ;
340+ expectRecoveryCall (
341+ recoverStuckSession ,
342+ { sessionId : "s1" , sessionKey : "main" , queueDepth : 1 } ,
343+ [ "ageMs" , "stateGeneration" ] ,
344+ ) ;
289345 } ) ;
290346
291347 it ( "does not warn while a processing session continues reporting progress" , ( ) => {
@@ -372,7 +428,7 @@ describe("stuck session diagnostics threshold", () => {
372428 expect ( events . some ( ( event ) => event . type === "session.stuck" ) ) . toBe ( false ) ;
373429 const stalledEvents = events . filter ( ( event ) => event . type === "session.stalled" ) ;
374430 expect ( stalledEvents ) . toHaveLength ( 1 ) ;
375- expect ( stalledEvents [ 0 ] ) . toMatchObject ( {
431+ expectRecordFields ( requireRecord ( stalledEvents [ 0 ] , "stalled event" ) , {
376432 classification : "stalled_agent_run" ,
377433 reason : "active_work_without_progress" ,
378434 activeWorkKind : "embedded_run" ,
@@ -411,10 +467,16 @@ describe("stuck session diagnostics threshold", () => {
411467 }
412468
413469 expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "terminalProgressStale=true" ) ) ;
414- expect ( events . findLast ( ( event ) => event . type === "session.stalled" ) ) . toMatchObject ( {
415- terminalProgressStale : true ,
416- lastProgressReason : "codex_app_server:notification:rawResponseItem/completed" ,
417- } ) ;
470+ expectRecordFields (
471+ requireRecord (
472+ events . findLast ( ( event ) => event . type === "session.stalled" ) ,
473+ "stalled event" ,
474+ ) ,
475+ {
476+ terminalProgressStale : true ,
477+ lastProgressReason : "codex_app_server:notification:rawResponseItem/completed" ,
478+ } ,
479+ ) ;
418480 } ) ;
419481
420482 it ( "aborts and drains embedded runs after an extended no-progress stall" , ( ) => {
@@ -446,19 +508,16 @@ describe("stuck session diagnostics threshold", () => {
446508
447509 const stalledEvents = events . filter ( ( event ) => event . type === "session.stalled" ) ;
448510 expect ( stalledEvents . length ) . toBeGreaterThan ( 0 ) ;
449- expect ( stalledEvents . at ( - 1 ) ) . toMatchObject ( {
511+ expectRecordFields ( requireRecord ( stalledEvents . at ( - 1 ) , "stalled event" ) , {
450512 classification : "stalled_agent_run" ,
451513 reason : "active_work_without_progress" ,
452514 activeWorkKind : "embedded_run" ,
453515 } ) ;
454- expect ( recoverStuckSession ) . toHaveBeenCalledWith ( {
455- sessionId : "s1" ,
456- sessionKey : "main" ,
457- ageMs : expect . any ( Number ) ,
458- queueDepth : 0 ,
459- allowActiveAbort : true ,
460- stateGeneration : expect . any ( Number ) ,
461- } ) ;
516+ expectRecoveryCall (
517+ recoverStuckSession ,
518+ { sessionId : "s1" , sessionKey : "main" , queueDepth : 0 , allowActiveAbort : true } ,
519+ [ "ageMs" , "stateGeneration" ] ,
520+ ) ;
462521 } ) ;
463522
464523 it ( "uses diagnostics.stuckSessionAbortMs for stalled active-work recovery" , ( ) => {
@@ -479,14 +538,11 @@ describe("stuck session diagnostics threshold", () => {
479538
480539 vi . advanceTimersByTime ( 61_000 ) ;
481540
482- expect ( recoverStuckSession ) . toHaveBeenCalledWith ( {
483- sessionId : "s1" ,
484- sessionKey : "main" ,
485- ageMs : expect . any ( Number ) ,
486- queueDepth : 0 ,
487- allowActiveAbort : true ,
488- stateGeneration : expect . any ( Number ) ,
489- } ) ;
541+ expectRecoveryCall (
542+ recoverStuckSession ,
543+ { sessionId : "s1" , sessionKey : "main" , queueDepth : 0 , allowActiveAbort : true } ,
544+ [ "ageMs" , "stateGeneration" ] ,
545+ ) ;
490546 } ) ;
491547
492548 it ( "marks diagnostic session state idle only after a mutating recovery outcome" , async ( ) => {
@@ -523,12 +579,10 @@ describe("stuck session diagnostics threshold", () => {
523579 const state = getDiagnosticSessionState ( { sessionId : "s1" , sessionKey : "main" } ) ;
524580 expect ( state . state ) . toBe ( "idle" ) ;
525581 expect ( state . queueDepth ) . toBe ( 0 ) ;
526- expect ( events ) . toContainEqual (
527- expect . objectContaining ( {
528- type : "session.recovery.completed" ,
529- status : "released" ,
530- action : "release_lane" ,
531- } ) ,
582+ requireMatchingRecord (
583+ events ,
584+ { type : "session.recovery.completed" , status : "released" , action : "release_lane" } ,
585+ "released recovery event" ,
532586 ) ;
533587 } ) ;
534588
@@ -569,12 +623,10 @@ describe("stuck session diagnostics threshold", () => {
569623 expect ( getDiagnosticSessionState ( { sessionId : "s1" , sessionKey : "main" } ) . state ) . toBe (
570624 "processing" ,
571625 ) ;
572- expect ( events ) . toContainEqual (
573- expect . objectContaining ( {
574- type : "session.recovery.completed" ,
575- status : "released" ,
576- stale : true ,
577- } ) ,
626+ requireMatchingRecord (
627+ events ,
628+ { type : "session.recovery.completed" , status : "released" , stale : true } ,
629+ "stale recovery event" ,
578630 ) ;
579631 } ) ;
580632
@@ -621,12 +673,14 @@ describe("stuck session diagnostics threshold", () => {
621673
622674 vi . advanceTimersByTime ( 60_000 ) ;
623675 expect ( recoverStuckSession ) . toHaveBeenCalledTimes ( 1 ) ;
624- expect ( events ) . toContainEqual (
625- expect . objectContaining ( {
676+ requireMatchingRecord (
677+ events ,
678+ {
626679 type : "session.recovery.completed" ,
627680 status : "skipped" ,
628681 outcomeReason : "already_in_flight" ,
629- } ) ,
682+ } ,
683+ "skipped recovery event" ,
630684 ) ;
631685
632686 resolveRecovery ?.( {
@@ -671,7 +725,7 @@ describe("stuck session diagnostics threshold", () => {
671725 expect ( events . some ( ( event ) => event . type === "session.stalled" ) ) . toBe ( false ) ;
672726 const longRunningEvents = events . filter ( ( event ) => event . type === "session.long_running" ) ;
673727 expect ( longRunningEvents ) . toHaveLength ( 1 ) ;
674- expect ( longRunningEvents [ 0 ] ) . toMatchObject ( {
728+ expectRecordFields ( requireRecord ( longRunningEvents [ 0 ] , "long-running event" ) , {
675729 classification : "long_running" ,
676730 reason : "active_work" ,
677731 activeWorkKind : "embedded_run" ,
@@ -751,7 +805,7 @@ describe("stuck session diagnostics threshold", () => {
751805 expect ( events . some ( ( event ) => event . type === "session.stalled" ) ) . toBe ( false ) ;
752806 const longRunningEvents = events . filter ( ( event ) => event . type === "session.long_running" ) ;
753807 expect ( longRunningEvents ) . toHaveLength ( 1 ) ;
754- expect ( longRunningEvents [ 0 ] ) . toMatchObject ( {
808+ expectRecordFields ( requireRecord ( longRunningEvents [ 0 ] , "long-running event" ) , {
755809 classification : "long_running" ,
756810 reason : "queued_behind_active_work" ,
757811 activeWorkKind : "embedded_run" ,
@@ -768,11 +822,10 @@ describe("stuck session diagnostics threshold", () => {
768822 } ) ;
769823 logSessionStateChange ( { sessionId : "s1" , sessionKey : "main" , state : "processing" } ) ;
770824
771- expect ( getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ) . toContainEqual (
772- expect . objectContaining ( {
773- type : "session.state" ,
774- outcome : "processing" ,
775- } ) ,
825+ requireMatchingRecord (
826+ getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ,
827+ { type : "session.state" , outcome : "processing" } ,
828+ "session state stability event" ,
776829 ) ;
777830 const [ event ] = getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ;
778831 expect ( event ) . not . toHaveProperty ( "sessionId" ) ;
@@ -856,8 +909,9 @@ describe("stuck session diagnostics threshold", () => {
856909 expect ( events ) . toContain ( "diagnostic.liveness.warning" ) ;
857910 expect ( warnSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( "liveness warning:" ) ) ;
858911 expect ( emitMemorySample ) . toHaveBeenLastCalledWith ( { emitSample : true } ) ;
859- expect ( getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ) . toContainEqual (
860- expect . objectContaining ( {
912+ requireMatchingRecord (
913+ getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ,
914+ {
861915 type : "diagnostic.liveness.warning" ,
862916 level : "info" ,
863917 reason : "cpu" ,
@@ -870,7 +924,8 @@ describe("stuck session diagnostics threshold", () => {
870924 active : 0 ,
871925 waiting : 0 ,
872926 queued : 0 ,
873- } ) ,
927+ } ,
928+ "idle liveness stability event" ,
874929 ) ;
875930 } ) ;
876931
@@ -898,14 +953,16 @@ describe("stuck session diagnostics threshold", () => {
898953 vi . advanceTimersByTime ( 30_000 ) ;
899954
900955 expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "liveness warning:" ) ) ;
901- expect ( getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ) . toContainEqual (
902- expect . objectContaining ( {
956+ requireMatchingRecord (
957+ getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ,
958+ {
903959 type : "diagnostic.liveness.warning" ,
904960 level : "warning" ,
905961 active : 0 ,
906962 waiting : 0 ,
907963 queued : 1 ,
908- } ) ,
964+ } ,
965+ "queued liveness stability event" ,
909966 ) ;
910967 } ) ;
911968
@@ -954,10 +1011,19 @@ describe("stuck session diagnostics threshold", () => {
9541011
9551012 expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "phase=startup.plugins.load" ) ) ;
9561013 expect ( warnSpy ) . toHaveBeenCalledWith ( expect . stringContaining ( "work=[queued=main(" ) ) ;
957- expect ( events . findLast ( ( event ) => event . type === "diagnostic.liveness.warning" ) ) . toMatchObject ( {
958- phase : "startup.plugins.load" ,
959- queuedWorkLabels : [ expect . stringContaining ( "main(" ) ] ,
960- } ) ;
1014+ const warning = requireRecord (
1015+ events . findLast ( ( event ) => event . type === "diagnostic.liveness.warning" ) ,
1016+ "liveness warning event" ,
1017+ ) ;
1018+ expect ( warning . phase ) . toBe ( "startup.plugins.load" ) ;
1019+ const queuedWorkLabels = warning . queuedWorkLabels ;
1020+ expect ( Array . isArray ( queuedWorkLabels ) ) . toBe ( true ) ;
1021+ if ( ! Array . isArray ( queuedWorkLabels ) ) {
1022+ throw new Error ( "liveness warning queuedWorkLabels was not an array" ) ;
1023+ }
1024+ expect (
1025+ queuedWorkLabels . some ( ( label ) => typeof label === "string" && label . includes ( "main(" ) ) ,
1026+ ) . toBe ( true ) ;
9611027 } ) ;
9621028
9631029 it ( "keeps transient event-loop max spikes debug-only when only background work is active" , ( ) => {
@@ -984,14 +1050,16 @@ describe("stuck session diagnostics threshold", () => {
9841050 vi . advanceTimersByTime ( 30_000 ) ;
9851051
9861052 expect ( warnSpy ) . not . toHaveBeenCalledWith ( expect . stringContaining ( "liveness warning:" ) ) ;
987- expect ( getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ) . toContainEqual (
988- expect . objectContaining ( {
1053+ requireMatchingRecord (
1054+ getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ,
1055+ {
9891056 type : "diagnostic.liveness.warning" ,
9901057 level : "warning" ,
9911058 active : 1 ,
9921059 waiting : 0 ,
9931060 queued : 0 ,
994- } ) ,
1061+ } ,
1062+ "active liveness stability event" ,
9951063 ) ;
9961064 } ) ;
9971065
@@ -1132,15 +1200,17 @@ describe("diagnostic stability snapshots", () => {
11321200 } ) ;
11331201 await flushDiagnosticEvents ( ) ;
11341202
1135- expect ( getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ) . toContainEqual (
1136- expect . objectContaining ( {
1203+ requireMatchingRecord (
1204+ getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ,
1205+ {
11371206 type : "message.delivery.error" ,
11381207 channel : "matrix" ,
11391208 deliveryKind : "text" ,
11401209 durationMs : 12 ,
11411210 outcome : "error" ,
11421211 reason : "TypeError" ,
1143- } ) ,
1212+ } ,
1213+ "bounded outbound delivery stability event" ,
11441214 ) ;
11451215 const [ event ] = getDiagnosticStabilitySnapshot ( { limit : 10 } ) . events ;
11461216 expect ( event ) . not . toHaveProperty ( "sessionKey" ) ;
0 commit comments