@@ -3565,6 +3565,7 @@ describe("registerPiContextHandler", () => {
35653565 renderedBoundary : contention
35663566 ? { endMessageId : "entry-1" , ordinal : 1 }
35673567 : { endMessageId : "entry-2" , ordinal : 2 } ,
3568+ m1RenderedCoverage : null ,
35683569 syntheticLeadingCount : 0 ,
35693570 } ) ,
35703571 ) ;
@@ -3728,6 +3729,167 @@ describe("registerPiContextHandler", () => {
37283729 closeQuietly ( db ) ;
37293730 }
37303731 } ) ;
3732+
3733+ it ( "drains on m[1]-only coverage: fresh publication, no HARD fold" , async ( ) => {
3734+ const db = createTestDb ( ) ;
3735+ const sessionId = "ses-pi-marker-m1-coverage" ;
3736+ const appendCompaction = mock ( ( ) => "compact-1" ) ;
3737+ // The exact shape a normal publication produces: m[0] still carries
3738+ // the empty pre-publication baseline (renderedBoundary <none> — no
3739+ // HARD fold has moved the compartment into m[0]), while the new
3740+ // compartment rendered into THIS pass's m[1] delta covers the marker.
3741+ const restoreInjection = contextHandlerInternals . setInjectM0M1PiForTests (
3742+ ( _state , _db , _messages ) => ( {
3743+ injected : true ,
3744+ compartmentCount : 1 ,
3745+ factCount : 0 ,
3746+ memoryCount : 0 ,
3747+ skippedVisibleMessages : 0 ,
3748+ m0Materialized : false ,
3749+ m0Reason : null ,
3750+ m0Bytes : 35 ,
3751+ m1Bytes : 518 ,
3752+ contentionExhausted : false ,
3753+ renderedBoundary : { endMessageId : null , ordinal : null } ,
3754+ m1RenderedCoverage : { endMessageId : "entry-2" , ordinal : 2 } ,
3755+ syntheticLeadingCount : 0 ,
3756+ } ) ,
3757+ ) ;
3758+ try {
3759+ seedCompartment ( db , sessionId ) ;
3760+ setPendingPiCompactionMarkerState ( db , sessionId , {
3761+ firstKeptEntryId : "entry-3" ,
3762+ endMessageId : "entry-2" ,
3763+ ordinal : 2 ,
3764+ tokensBefore : 10 ,
3765+ summary : "summary" ,
3766+ publishedAt : 1 ,
3767+ } ) ;
3768+ signalPiDeferredHistoryRefresh ( sessionId ) ;
3769+ signalPiPendingMaterialization ( sessionId ) ;
3770+
3771+ await runDrainPass ( {
3772+ db,
3773+ sessionId,
3774+ appendCompaction,
3775+ contextPercent : 90 ,
3776+ } ) ;
3777+
3778+ expect ( appendCompaction ) . toHaveBeenCalledTimes ( 1 ) ;
3779+ expect ( getPendingPiCompactionMarkerState ( db , sessionId ) ) . toBeNull ( ) ;
3780+ expect ( consumeDeferredHistoryRefresh ( sessionId ) ) . toBe ( false ) ;
3781+ } finally {
3782+ restoreInjection ( ) ;
3783+ clearContextHandlerSession ( sessionId ) ;
3784+ closeQuietly ( db ) ;
3785+ }
3786+ } ) ;
3787+
3788+ it ( "preserves the marker when a sibling-fallback serves stale m[1] (null coverage)" , async ( ) => {
3789+ const db = createTestDb ( ) ;
3790+ const sessionId = "ses-pi-marker-sibling-fallback" ;
3791+ const appendCompaction = mock ( ( ) => "compact-1" ) ;
3792+ // softRefreshCachedM1Pi's sibling-fallback serves a sibling's stale
3793+ // cached m[1] with recomputed=false while contentionExhausted stays
3794+ // FALSE — the contention veto alone does not catch it, so the
3795+ // injection reports null m[1] coverage and the drain must skip.
3796+ const restoreInjection = contextHandlerInternals . setInjectM0M1PiForTests (
3797+ ( _state , _db , _messages ) => ( {
3798+ injected : true ,
3799+ compartmentCount : 1 ,
3800+ factCount : 0 ,
3801+ memoryCount : 0 ,
3802+ skippedVisibleMessages : 0 ,
3803+ m0Materialized : false ,
3804+ m0Reason : null ,
3805+ m0Bytes : 35 ,
3806+ m1Bytes : 518 ,
3807+ contentionExhausted : false ,
3808+ renderedBoundary : { endMessageId : null , ordinal : null } ,
3809+ m1RenderedCoverage : null ,
3810+ syntheticLeadingCount : 0 ,
3811+ } ) ,
3812+ ) ;
3813+ try {
3814+ seedCompartment ( db , sessionId ) ;
3815+ setPendingPiCompactionMarkerState ( db , sessionId , {
3816+ firstKeptEntryId : "entry-3" ,
3817+ endMessageId : "entry-2" ,
3818+ ordinal : 2 ,
3819+ tokensBefore : 10 ,
3820+ summary : "summary" ,
3821+ publishedAt : 1 ,
3822+ } ) ;
3823+ signalPiDeferredHistoryRefresh ( sessionId ) ;
3824+ signalPiPendingMaterialization ( sessionId ) ;
3825+
3826+ await runDrainPass ( {
3827+ db,
3828+ sessionId,
3829+ appendCompaction,
3830+ contextPercent : 90 ,
3831+ } ) ;
3832+
3833+ expect ( appendCompaction ) . not . toHaveBeenCalled ( ) ;
3834+ expect ( getPendingPiCompactionMarkerState ( db , sessionId ) ) . not . toBeNull ( ) ;
3835+ // The deferred-history signal survives so the next FRESH render
3836+ // (non-fallback) retries the drain instead of losing the marker.
3837+ expect ( consumeDeferredHistoryRefresh ( sessionId ) ) . toBe ( true ) ;
3838+ } finally {
3839+ restoreInjection ( ) ;
3840+ clearContextHandlerSession ( sessionId ) ;
3841+ closeQuietly ( db ) ;
3842+ }
3843+ } ) ;
3844+
3845+ it ( "does not fire the drain on a pure defer pass even with coverage present" , async ( ) => {
3846+ const db = createTestDb ( ) ;
3847+ const sessionId = "ses-pi-marker-defer-no-drain" ;
3848+ const appendCompaction = mock ( ( ) => "compact-1" ) ;
3849+ // Regression pin for the deferredHistoryDrainEligible gate: a pure
3850+ // SOFT+ defer/replay pass (no history-refresh consumption, no
3851+ // materialization this pass) must never drain — even when the
3852+ // pending marker exists and the injection reports full coverage.
3853+ const restoreInjection = contextHandlerInternals . setInjectM0M1PiForTests (
3854+ ( _state , _db , _messages ) => ( {
3855+ injected : true ,
3856+ compartmentCount : 1 ,
3857+ factCount : 0 ,
3858+ memoryCount : 0 ,
3859+ skippedVisibleMessages : 0 ,
3860+ m0Materialized : false ,
3861+ m0Reason : null ,
3862+ m0Bytes : 35 ,
3863+ m1Bytes : 518 ,
3864+ contentionExhausted : false ,
3865+ renderedBoundary : { endMessageId : "entry-2" , ordinal : 2 } ,
3866+ m1RenderedCoverage : { endMessageId : "entry-2" , ordinal : 2 } ,
3867+ syntheticLeadingCount : 0 ,
3868+ } ) ,
3869+ ) ;
3870+ try {
3871+ seedCompartment ( db , sessionId ) ;
3872+ setPendingPiCompactionMarkerState ( db , sessionId , {
3873+ firstKeptEntryId : "entry-3" ,
3874+ endMessageId : "entry-2" ,
3875+ ordinal : 2 ,
3876+ tokensBefore : 10 ,
3877+ summary : "summary" ,
3878+ publishedAt : 1 ,
3879+ } ) ;
3880+ // Deliberately NO deferred-history / materialization signals and
3881+ // no pressure: this is a replay pass, not a busting pass.
3882+
3883+ await runDrainPass ( { db, sessionId, appendCompaction } ) ;
3884+
3885+ expect ( appendCompaction ) . not . toHaveBeenCalled ( ) ;
3886+ expect ( getPendingPiCompactionMarkerState ( db , sessionId ) ) . not . toBeNull ( ) ;
3887+ } finally {
3888+ restoreInjection ( ) ;
3889+ clearContextHandlerSession ( sessionId ) ;
3890+ closeQuietly ( db ) ;
3891+ }
3892+ } ) ;
37313893 } ) ;
37323894} ) ;
37333895
0 commit comments