fix: bind crash cleanup to normalized session-open receipts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-22 09:38:20 -05:00
1 parent 37ad1edf06
commit 3592b04c2b
2 files changed
+7 -5

No files matched your search

@@ -3858,7 +3858,7 @@ describe("retained native cleanup activation", () => {
describe("stopped native conversation physical cleanup", () => {
it.each(["codex", "acpx"].flatMap(provider => [
"stopped", "provider_alive", "worker_alive", "missing_receipt", "wrong_receipt", "new_launch",
"foreign_run", "foreign_company", "foreign_runner", "remote", "unreleased", "changed_state", "changed_pid", "symlink", "startup_intent", "pending_identity", "wrong_schema", "replacement", "replacement_alive", "agent_alive", "checkpoint_owner_alive", "diagnostic_owner_alive",
"foreign_run", "foreign_company", "foreign_runner", "remote", "unreleased", "changed_state", "changed_pid", "symlink", "startup_intent", "pending_identity", "wrong_schema", "replacement", "replacement_alive", "agent_alive", "checkpoint_owner_alive", "diagnostic_owner_alive", "normalized_session_receipt",
].map(mode => ({ provider, mode }))))("$provider $mode", async ({ provider, mode }) => {
const base = await mkdtemp(join(tmpdir(), "native-conversation-cleanup-"));
const previous = process.env.PAPERCLIP_RUNNER_STATE_DIR;
@@ -3894,8 +3894,9 @@ describe("stopped native conversation physical cleanup", () => {
...(mode === "startup_intent" ? { startupAttempt: { phase: "intent" } } : {}),
...(mode === "pending_identity" ? { pendingEvents: [{ eventType: "session.started" }] } : {}),
};
const normalizedEvent = mode === "normalized_session_receipt" ? { ...event, turnId: undefined, itemId: undefined } : event;
const receipt = { sourceEventId: `${identity.runnerInstanceId}:${identity.runId}:1`,
sourcePayloadSha256: mode === "wrong_receipt" ? "wrong" : hash(event), payload: { prpEvent: event } };
sourcePayloadSha256: mode === "wrong_receipt" ? "wrong" : hash(normalizedEvent), payload: { prpEvent: normalizedEvent } };
const stop = { eventType: mode === "new_launch" ? "native.process_start_requested" : "native.local_process_stopped",
payload: { processPid: mode === "worker_alive" ? process.pid : 99_999_999, processGroupId: mode === "worker_alive" ? process.pid : 99_999_999 } };
let queryIndex = 0;
@@ -3919,11 +3920,11 @@ describe("stopped native conversation physical cleanup", () => {
await writeFile(join(root, `runner/${provider}-provider-state.json`), JSON.stringify(providerState));
if (mode === "symlink") { await rename(runnerPath, join(base, "external")); await symlink(join(base, "external"), runnerPath); }
const proof = await verifyStoppedNativeSessionForContinuation(db, run);
if (["stopped", "changed_state", "changed_pid", "replacement"].includes(mode)) {
if (["stopped", "changed_state", "changed_pid", "replacement", "normalized_session_receipt"].includes(mode)) {
expect(proof).not.toBeNull();
if (mode === "changed_state") await writeFile(runnerPath, "{}");
const kill = mode === "changed_pid" ? vi.spyOn(process, "kill").mockReturnValue(true) : null;
try { expect(proof!.retire()).toBe(mode === "stopped" || mode === "replacement"); } finally { kill?.mockRestore(); }
try { expect(proof!.retire()).toBe(["stopped", "replacement", "normalized_session_receipt"].includes(mode)); } finally { kill?.mockRestore(); }
expect(await readFile(join(root, `runner/${provider}-provider-state.json`), "utf8")).toBe(JSON.stringify(providerState));
} else expect(proof).toBeNull();
} finally {
@@ -2429,8 +2429,9 @@ export async function verifyStoppedNativeSessionForContinuation(
const durableProvider = record(durable.payload);
if (!receipt || !validatePrpEvent(durable).ok || durable.sourceInstanceId !== event.sourceInstanceId ||
durable.runId !== run.id || durable.normalizedSessionId !== run.nativeSessionId ||
// Normalized session-open receipts omit turn/item IDs. Those belong
// to the durable runner identity checked above, not the open event.
durable.sourceSeq !== event.sourceSeq || durable.eventType !== event.eventType ||
durable.turnId !== event.turnId || durable.itemId !== event.itemId ||
receipt.sourcePayloadSha256 !== nativeSha256(durable) ||
(durableProvider.processId !== undefined && durableProvider.processId !== provider.processId) ||
(durableProvider.driverSessionId ?? durableProvider.providerSessionId) !== provider.providerSessionId) return null;