Skip to content

Commit 85da5a9

Browse files
committed
merge: Pi marker drain accepts m1-rendered coverage (liveness fix, panel-pinned)
2 parents 4fde593 + 44dc65a commit 85da5a9

7 files changed

Lines changed: 643 additions & 3 deletions

File tree

packages/e2e-tests/tests/pi-long-running-session.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,12 @@ describe("long-running Pi Magic Context session", () => {
364364

365365
// Phase 5: Historian publishes. Pi uses native JSONL compaction entries, not OpenCode's deferred marker blob.
366366
await send("turn 17: Pi historian trigger pressure with eligible long tail", "pi phase 5 historian trigger", HISTORIAN_TRIGGER_USAGE);
367-
await send("turn 18: Pi follow-up starts historian publication", "pi phase 5 historian follow-up");
367+
// Turn 18's RESPONSE usage stays high so the first ordinary drive
368+
// turn AFTER publication still sees >=85% and force-materializes
369+
// (canConsumeDeferredLate) — that is the consuming pass which drains
370+
// the staged marker. A low-usage turn 18 would leave the next pass
371+
// in defer, where the deferred-history drain gate never opens.
372+
await send("turn 18: Pi follow-up starts historian publication", "pi phase 5 historian follow-up", HISTORIAN_TRIGGER_USAGE);
368373
await h.waitFor(
369374
() => {
370375
const row = h
@@ -382,6 +387,15 @@ describe("long-running Pi Magic Context session", () => {
382387
expect(historianRange).not.toBeNull();
383388
expect(compartment.start_message).toBe(historianRange!.start);
384389
expect(compartment.end_message).toBe(historianRange!.end);
390+
// Ordinary drive turn (NO HARD bust / m0-mutation injection): the
391+
// publication's onPublished armed the deferred history-refresh +
392+
// materialization signals, and this pass force-materializes (turn
393+
// 18 kept pressure >=85%), rendering the new compartment into m[1].
394+
// The widened coverage predicate (m[0] boundary OR current-pass
395+
// m[1] delta) drains the staged marker into a native compaction
396+
// entry on this consuming pass — parity with OpenCode's
397+
// consuming-pass drain, which also rides ordinary send turns.
398+
await send("turn 18b: ordinary Pi drive turn consumes the historian publication", "pi phase 5 consuming pass");
385399
const compactions = await h.waitFor(() => {
386400
const entries = readCompactionEntries(h);
387401
return entries.length > 0 ? entries : null;

packages/pi-plugin/src/context-handler.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

packages/pi-plugin/src/context-handler.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,10 +4058,28 @@ function pendingPiMarkerCoveredByRenderedBoundary(
40584058
pending: PendingPiCompactionMarker,
40594059
injection: PiInjectionResult | null,
40604060
): boolean {
4061+
// Contention fallbacks must never authorize a native trim: the served
4062+
// bytes may lag the latest compartment snapshot.
40614063
if (!injection || injection.contentionExhausted) return false;
4064+
// m[0] arm: the boundary rendered into the m[0] snapshot.
40624065
const boundary = injection.renderedBoundary;
40634066
if (pending.endMessageId === boundary.endMessageId) return true;
4064-
return boundary.ordinal !== null && pending.ordinal <= boundary.ordinal;
4067+
if (boundary.ordinal !== null && pending.ordinal <= boundary.ordinal)
4068+
return true;
4069+
// m[1] arm (liveness fix): fresh publications render their compartment
4070+
// into the m[1] delta, not m[0] (which folds only on a HARD bust), so the
4071+
// m[0] snapshot boundary stays behind the pending marker until an
4072+
// unrelated HARD fold — starving the drain for hours in stable sessions.
4073+
// Accept coverage from a compartment actually rendered into m[1] THIS
4074+
// pass. The field is non-null only when m[1] was freshly recomputed this
4075+
// pass without a contention fallback (null on cached/sibling replay, where
4076+
// contentionExhausted alone would miss the sibling-fallback's stale
4077+
// bytes), so this restores OpenCode's consuming-pass drain parity without
4078+
// ever trimming getBranch() beyond content the model was shown this pass.
4079+
const m1Coverage = injection.m1RenderedCoverage;
4080+
if (!m1Coverage) return false;
4081+
if (pending.endMessageId === m1Coverage.endMessageId) return true;
4082+
return m1Coverage.ordinal !== null && pending.ordinal <= m1Coverage.ordinal;
40654083
}
40664084

40674085
function captureReasoningMutationRollback(
@@ -5184,9 +5202,10 @@ async function runPipeline(args: RunPipelineArgs): Promise<RunPipelineResult> {
51845202
suppressDeferredHistoryDrain = true;
51855203
preserveDeferredMaterializationForMarkerDrain = true;
51865204
const boundary = injectionResult?.renderedBoundary;
5205+
const m1Coverage = injectionResult?.m1RenderedCoverage;
51875206
sessionLog(
51885207
args.sessionId,
5189-
`Pi compaction-marker drain skipped: pending ordinal ${pending.ordinal} is newer than rendered boundary ${boundary?.ordinal ?? "<none>"} endMessageId=${boundary?.endMessageId ?? "<none>"}; preserving deferred signals`,
5208+
`Pi compaction-marker drain skipped: pending ordinal ${pending.ordinal} is newer than rendered boundary ${boundary?.ordinal ?? "<none>"} endMessageId=${boundary?.endMessageId ?? "<none>"} (m[1] coverage ${m1Coverage?.ordinal ?? "<none>"} endMessageId=${m1Coverage?.endMessageId ?? "<none>"}); preserving deferred signals`,
51905209
);
51915210
} else if (!args.appendCompaction || !args.readBranchEntries) {
51925211
suppressDeferredHistoryDrain = true;

0 commit comments

Comments
 (0)