@@ -12,6 +12,7 @@ import type {
1212 InboundMessage ,
1313} from "@intx/types/runtime" ;
1414
15+ import { APPROVAL_TIMEOUT_RESULT_TEXT } from "../permission/decline-markers.js" ;
1516import type { PermissionGate } from "../permission/gate.js" ;
1617import { createExtraDeniedPathMatcher } from "../plugins/secret-guard-plugin.js" ;
1718import {
@@ -1193,10 +1194,9 @@ describe("approval resume retry re-await", () => {
11931194 expect ( delivered ) . toHaveLength ( 1 ) ;
11941195 } ) ;
11951196
1196- // Concurrent duplicates share one in-flight rejection (one gate, zero
1197- // deliveries), then a retry re-gates and delivers exactly once, and later
1198- // retries memoize.
1199- test ( "a shared rejection re-gates once, delivers once, then memoizes" , async ( ) => {
1197+ // Concurrent duplicates share one in-flight gate failure (one gate, one
1198+ // rejection delivered), and later retries memoize.
1199+ test ( "a shared gate failure rejects the parked call once, then memoizes" , async ( ) => {
12001200 const gate = Promise . withResolvers < { allow : boolean } > ( ) ;
12011201 const { resume, delivered, resolveSuspended } = retryHarness ( {
12021202 allow : true ,
@@ -1211,17 +1211,54 @@ describe("approval resume retry re-await", () => {
12111211 await expect ( first ) . rejects . toBe ( failure ) ;
12121212 await expect ( second ) . rejects . toBe ( failure ) ;
12131213 expect ( resolveSuspended ) . toHaveBeenCalledTimes ( 1 ) ;
1214- expect ( delivered ) . toHaveLength ( 0 ) ;
1215-
1216- expect ( await resume . handle ( suspension ( "corr-A" , "echo alpha" ) ) ) . toBe ( true ) ;
1217- expect ( resolveSuspended ) . toHaveBeenCalledTimes ( 2 ) ;
1214+ // Nothing retries a parked call, so the failure answers it.
12181215 expect ( delivered ) . toHaveLength ( 1 ) ;
1219- expect ( decisionBody ( onlyDecision ( delivered ) ) . outcome ) . toBe ( "approved " ) ;
1216+ expect ( decisionBody ( onlyDecision ( delivered ) ) . outcome ) . toBe ( "rejected " ) ;
12201217
12211218 expect ( await resume . handle ( suspension ( "corr-A" , "echo alpha" ) ) ) . toBe ( true ) ;
1222- expect ( resolveSuspended ) . toHaveBeenCalledTimes ( 2 ) ;
1219+ expect ( resolveSuspended ) . toHaveBeenCalledTimes ( 1 ) ;
12231220 expect ( delivered ) . toHaveLength ( 1 ) ;
12241221 } ) ;
1222+ test ( "a gate failure after the reactor timed the call out delivers nothing" , async ( ) => {
1223+ const delivered : InboundMessage [ ] = [ ] ;
1224+ let timedOut = false ;
1225+ const failure = new Error ( "gate exploded" ) ;
1226+ const resume = createApprovalResume ( {
1227+ getAgent : ( ) => ( {
1228+ deliver : ( message : InboundMessage ) => {
1229+ delivered . push ( message ) ;
1230+ } ,
1231+ history : async ( ) =>
1232+ timedOut
1233+ ? ( [
1234+ {
1235+ role : "user" ,
1236+ content : [
1237+ {
1238+ type : "tool_result" ,
1239+ callId : "call-A" ,
1240+ content : [
1241+ { type : "text" , text : APPROVAL_TIMEOUT_RESULT_TEXT } ,
1242+ ] ,
1243+ } ,
1244+ ] ,
1245+ } ,
1246+ ] as unknown as Awaited < ReturnType < Agent [ "history" ] > > )
1247+ : [ ] ,
1248+ } ) ,
1249+ resolveParkedCallId : ( ) => "call-A" ,
1250+ gate : {
1251+ resolveSuspended : async ( ) => {
1252+ timedOut = true ;
1253+ throw failure ;
1254+ } ,
1255+ } as unknown as PermissionGate ,
1256+ } ) ;
1257+ await expect (
1258+ resume . handle ( suspension ( "corr-A" , "echo alpha" ) ) ,
1259+ ) . rejects . toBe ( failure ) ;
1260+ expect ( delivered ) . toEqual ( [ ] ) ;
1261+ } ) ;
12251262} ) ;
12261263
12271264describe ( "parked overlay registration after the operator answers" , ( ) => {
0 commit comments