@@ -469,6 +469,68 @@ describe("checkCompartmentTrigger", () => {
469469 expect ( result ) . toEqual ( { shouldFire : false } ) ;
470470 } ) ;
471471
472+ it ( "projects aged reasoning only when the provider can clear it from the wire" , ( ) => {
473+ // Match issue #274's shape: at 36.8% usage, 70% of tagged bytes are
474+ // aged reasoning. Counting it as reclaimable projects 11.0%, below the
475+ // 16.2% post-drop target; a provider that cannot clear it must instead
476+ // fire the historian at the real 36.8% pressure.
477+ useTempDataHome ( "compartment-trigger-unavailable-reasoning-" ) ;
478+ const sessionId = "ses-unavailable-reasoning" ;
479+ createOpenCodeDb ( sessionId , [
480+ { id : "m-1" , role : "user" , text : "a " . repeat ( 3500 ) } ,
481+ { id : "m-2" , role : "assistant" , text : "done" } ,
482+ { id : "m-3" , role : "user" , text : "b " . repeat ( 3500 ) } ,
483+ { id : "m-4" , role : "assistant" , text : "done" } ,
484+ { id : "m-5" , role : "user" , text : "c " . repeat ( 3500 ) } ,
485+ { id : "m-6" , role : "assistant" , text : "done" } ,
486+ { id : "m-7" , role : "user" , text : "protected tail 1" } ,
487+ { id : "m-8" , role : "user" , text : "protected tail 2" } ,
488+ { id : "m-9" , role : "user" , text : "protected tail 3" } ,
489+ { id : "m-10" , role : "user" , text : "protected tail 4" } ,
490+ { id : "m-11" , role : "user" , text : "protected tail 5" } ,
491+ ] ) ;
492+ const db = openDatabase ( ) ;
493+ // Total tagged bytes = 1,000; tag 1's 700 reasoning bytes are old
494+ // enough for clearing because tag 51 establishes the age cutoff.
495+ insertTag ( db , sessionId , "m-1" , "message" , 299 , 1 , 700 ) ;
496+ insertTag ( db , sessionId , "m-11" , "message" , 1 , 51 ) ;
497+ const runTrigger = ( reasoningProjection : {
498+ providerID ?: string ;
499+ canClearReasoning ?: boolean ;
500+ } ) =>
501+ checkCompartmentTrigger (
502+ db ,
503+ sessionId ,
504+ makeSessionMeta ( sessionId , 36.8 ) ,
505+ { percentage : 36.8 , inputTokens : 736_000 } ,
506+ 36.8 ,
507+ 21.6 ,
508+ 6500 ,
509+ 50 ,
510+ undefined ,
511+ undefined ,
512+ undefined ,
513+ undefined ,
514+ undefined ,
515+ reasoningProjection ,
516+ ) ;
517+
518+ // Mutation direction: removing the provider gate would count the 700
519+ // reasoning bytes, project 11.0%, and make this assertion fail.
520+ expect ( runTrigger ( { providerID : "opencode" } ) ) . toMatchObject ( {
521+ shouldFire : true ,
522+ reason : "projected_headroom" ,
523+ } ) ;
524+ // Anthropic safely clears reasoning through its empty-content sentinel,
525+ // so the aged bytes stay projected as reclaimable and suppress this fire.
526+ expect ( runTrigger ( { providerID : "anthropic" } ) ) . toEqual ( { shouldFire : false } ) ;
527+ // Pi clears typed thinking for every provider, so its explicit shared
528+ // trigger capability retains the same projection on an OpenCode-like id.
529+ expect ( runTrigger ( { providerID : "opencode" , canClearReasoning : true } ) ) . toEqual ( {
530+ shouldFire : false ,
531+ } ) ;
532+ } ) ;
533+
472534 it ( "does not force-fire at 80% when pending drops are enough to bring usage below target" , ( ) => {
473535 useTempDataHome ( "compartment-trigger-force-skip-" ) ;
474536 createOpenCodeDb ( "ses-force-skip" , [
0 commit comments