@@ -2268,3 +2268,222 @@ describe("createOptimizedContextStore metadata ownership (CL-10667)", () => {
22682268 expect ( ( await resumed . load ( ) ) . tokenUsage ) . toEqual ( USAGE_TWO ) ;
22692269 } ) ;
22702270} ) ;
2271+
2272+ describe ( "createOptimizedContextStore load drops pending operations for answered calls" , ( ) => {
2273+ function callTurn ( callId : string , timestamp : number ) : ConversationTurn {
2274+ return {
2275+ role : "assistant" ,
2276+ content : [
2277+ { type : "tool_call" , id : callId , name : "run_shell" , arguments : { } } ,
2278+ ] ,
2279+ timestamp,
2280+ } ;
2281+ }
2282+
2283+ function resultTurn (
2284+ callId : string ,
2285+ text : string ,
2286+ timestamp : number ,
2287+ ) : ConversationTurn {
2288+ return {
2289+ role : "user" ,
2290+ content : [
2291+ { type : "tool_result" , callId, content : [ { type : "text" , text } ] } ,
2292+ ] ,
2293+ timestamp,
2294+ } ;
2295+ }
2296+
2297+ function approvalOp ( correlationId : string , callId : string ) {
2298+ return {
2299+ correlationId,
2300+ kind : "approval" as const ,
2301+ registeredAt : 1_700_000_000_000 ,
2302+ gateId : `pending-${ correlationId } ` ,
2303+ timeoutAt : Date . now ( ) + 60_000 ,
2304+ suspendedCall : { id : callId , name : "run_shell" , arguments : { } } ,
2305+ } ;
2306+ }
2307+
2308+ function markerOp ( correlationId : string ) {
2309+ return {
2310+ correlationId,
2311+ kind : "approval" as const ,
2312+ registeredAt : 1_700_000_000_000 ,
2313+ gateId : `pending-${ correlationId } ` ,
2314+ } ;
2315+ }
2316+
2317+ function placeCrashState (
2318+ dir : string ,
2319+ turns : ConversationTurn [ ] ,
2320+ pendingOperations : unknown [ ] ,
2321+ ) : void {
2322+ fs . writeFileSync ( path . join ( dir , TURNS_FILE ) , jsonl ( turns ) ) ;
2323+ fs . writeFileSync (
2324+ path . join ( dir , "metadata.json" ) ,
2325+ JSON . stringify ( { pendingOperations, connectorState : null } , null , 2 ) ,
2326+ ) ;
2327+ }
2328+
2329+ // The crash window between the result-turn write and the metadata op-removal
2330+ // write leaves turns answering a parked call while metadata.json still lists
2331+ // its pending operation. load() is the only path into the rebuilt
2332+ // reactor's rehydrateGates, so the stale operation must die here.
2333+ test ( "drops an answered operation while keeping unanswered and marker operations" , async ( ) => {
2334+ const dir = tempDir ( ) ;
2335+ placeCrashState (
2336+ dir ,
2337+ [ callTurn ( "call-answered" , 1 ) , resultTurn ( "call-answered" , "ok" , 2 ) ] ,
2338+ [
2339+ approvalOp ( "stale-corr" , "call-answered" ) ,
2340+ approvalOp ( "live-corr" , "call-live" ) ,
2341+ markerOp ( "marker-corr" ) ,
2342+ ] ,
2343+ ) ;
2344+
2345+ const store = await createOptimizedContextStore ( dir ) ;
2346+ const loaded = await store . load ( ) ;
2347+ expect (
2348+ loaded . pendingOperations . map ( ( op ) => op . correlationId ) . sort ( ) ,
2349+ ) . toEqual ( [ "live-corr" , "marker-corr" ] ) ;
2350+ } ) ;
2351+
2352+ test ( "repeated loads are stable and emit no metadata write" , async ( ) => {
2353+ const dir = tempDir ( ) ;
2354+ placeCrashState (
2355+ dir ,
2356+ [ callTurn ( "call-answered" , 1 ) , resultTurn ( "call-answered" , "ok" , 2 ) ] ,
2357+ [ approvalOp ( "stale-corr" , "call-answered" ) , markerOp ( "marker-corr" ) ] ,
2358+ ) ;
2359+
2360+ const store = await createOptimizedContextStore ( dir ) ;
2361+ const before = fs . readFileSync ( path . join ( dir , "metadata.json" ) , "utf8" ) ;
2362+ const first = await store . load ( ) ;
2363+ const second = await store . load ( ) ;
2364+ expect ( second . pendingOperations ) . toEqual ( first . pendingOperations ) ;
2365+ expect ( first . pendingOperations . map ( ( op ) => op . correlationId ) ) . toEqual ( [
2366+ "marker-corr" ,
2367+ ] ) ;
2368+ expect ( fs . readFileSync ( path . join ( dir , "metadata.json" ) , "utf8" ) ) . toBe (
2369+ before ,
2370+ ) ;
2371+ } ) ;
2372+
2373+ test ( "the next metadata write persists the removal" , async ( ) => {
2374+ const dir = tempDir ( ) ;
2375+ placeCrashState (
2376+ dir ,
2377+ [ callTurn ( "call-answered" , 1 ) , resultTurn ( "call-answered" , "ok" , 2 ) ] ,
2378+ [
2379+ approvalOp ( "stale-corr" , "call-answered" ) ,
2380+ approvalOp ( "live-corr" , "call-live" ) ,
2381+ ] ,
2382+ ) ;
2383+
2384+ const store = await createOptimizedContextStore ( dir ) ;
2385+ const loaded = await store . load ( ) ;
2386+ await store . writeMetadata ( {
2387+ pendingOperations : loaded . pendingOperations ,
2388+ tokenUsage : loaded . tokenUsage ,
2389+ } ) ;
2390+
2391+ const persisted = JSON . parse (
2392+ fs . readFileSync ( path . join ( dir , "metadata.json" ) , "utf8" ) ,
2393+ ) as { pendingOperations : { correlationId : string } [ ] } ;
2394+ expect ( persisted . pendingOperations . map ( ( op ) => op . correlationId ) ) . toEqual ( [
2395+ "live-corr" ,
2396+ ] ) ;
2397+
2398+ const resumed = await createOptimizedContextStore ( dir ) ;
2399+ expect (
2400+ ( await resumed . load ( ) ) . pendingOperations . map ( ( op ) => op . correlationId ) ,
2401+ ) . toEqual ( [ "live-corr" ] ) ;
2402+ } ) ;
2403+
2404+ test ( "crash-window replay across segments yields no re-armable gate" , async ( ) => {
2405+ const dir = tempDir ( ) ;
2406+ fs . writeFileSync (
2407+ path . join ( dir , TURNS_FILE ) ,
2408+ jsonl ( [ callTurn ( "call-answered" , 1 ) ] ) ,
2409+ ) ;
2410+ fs . writeFileSync (
2411+ path . join ( dir , segmentFileName ( TURNS_FILE , 1 ) ) ,
2412+ jsonl ( [ resultTurn ( "call-answered" , "ok" , 2 ) ] ) ,
2413+ ) ;
2414+ fs . writeFileSync (
2415+ path . join ( dir , "metadata.json" ) ,
2416+ JSON . stringify ( {
2417+ pendingOperations : [
2418+ approvalOp ( "stale-corr" , "call-answered" ) ,
2419+ approvalOp ( "live-corr" , "call-live" ) ,
2420+ markerOp ( "marker-corr" ) ,
2421+ ] ,
2422+ connectorState : null ,
2423+ } ) ,
2424+ ) ;
2425+
2426+ const store = await createOptimizedContextStore ( dir ) ;
2427+ const loaded = await store . load ( ) ;
2428+ const answered = new Set (
2429+ loaded . turns . flatMap ( ( turn ) =>
2430+ turn . content . flatMap ( ( block ) =>
2431+ block . type === "tool_result" ? [ block . callId ] : [ ] ,
2432+ ) ,
2433+ ) ,
2434+ ) ;
2435+ // Every operation load() hands the rebuilt reactor is safe to rehydrate:
2436+ // none parks a call the turns already answer.
2437+ for ( const op of loaded . pendingOperations ) {
2438+ if ( op . suspendedCall !== undefined ) {
2439+ expect ( answered . has ( op . suspendedCall . id ) ) . toBe ( false ) ;
2440+ }
2441+ }
2442+ expect (
2443+ loaded . pendingOperations . map ( ( op ) => op . correlationId ) . sort ( ) ,
2444+ ) . toEqual ( [ "live-corr" , "marker-corr" ] ) ;
2445+ } ) ;
2446+
2447+ test ( "matches a scrubbed on-disk operation by call id" , async ( ) => {
2448+ const dir = tempDir ( ) ;
2449+ const store = await createOptimizedContextStore ( dir ) ;
2450+ const secret = { api_key : "hunter2" } ;
2451+ await store . writeMetadata ( {
2452+ pendingOperations : [
2453+ {
2454+ ...approvalOp ( "scrubbed-corr" , "call-scrubbed" ) ,
2455+ suspendedCall : {
2456+ id : "call-scrubbed" ,
2457+ name : "run_shell" ,
2458+ arguments : secret ,
2459+ } ,
2460+ approvalSnapshot : {
2461+ name : "run_shell" ,
2462+ description : "runs a shell command" ,
2463+ inputSchema : { } ,
2464+ arguments : secret ,
2465+ } ,
2466+ } ,
2467+ ] ,
2468+ tokenUsage : {
2469+ input : 0 ,
2470+ output : 0 ,
2471+ cacheRead : 0 ,
2472+ cacheWrite : 0 ,
2473+ thinking : 0 ,
2474+ } ,
2475+ } ) ;
2476+ // The on-disk approval snapshot is redacted while suspendedCall keeps
2477+ // the raw call; matching must work by call id alone.
2478+ const raw = fs . readFileSync ( path . join ( dir , "metadata.json" ) , "utf8" ) ;
2479+ expect ( raw ) . toContain ( "[redacted" ) ;
2480+ expect ( raw ) . toContain ( "call-scrubbed" ) ;
2481+ await store . writeTurns ( [
2482+ callTurn ( "call-scrubbed" , 1 ) ,
2483+ resultTurn ( "call-scrubbed" , "ok" , 2 ) ,
2484+ ] ) ;
2485+
2486+ const resumed = await createOptimizedContextStore ( dir ) ;
2487+ expect ( ( await resumed . load ( ) ) . pendingOperations ) . toEqual ( [ ] ) ;
2488+ } ) ;
2489+ } ) ;
0 commit comments