Preserve native workspace recovery across Stop and sandbox reuse (#18332)

Land the thirteen-file native recovery fix independently from #18245.

Retain verified Stop evidence and queued user comments. Retire stale
preparation ownership atomically and require paired sandbox lease
handoff receipts before workspace recovery.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-30 10:57:53 -05:00
1 parent 75666224d4
commit f5e5b05a33
13 files changed
+1220 -28

No files matched your search

@@ -322,7 +322,9 @@ export type DurableWakeAdmissionReceipt = {
requestedAt: Date;
};
export type CoalescedDeferredAdmissionReceipt = DurableWakeAdmissionReceipt & {
export type CoalescedDeferredAdmissionReceipt =
Omit<DurableWakeAdmissionReceipt, "id" | "requestedAt"> &
Partial<Pick<DurableWakeAdmissionReceipt, "id" | "requestedAt">> & {
agentId: string;
source: string;
triggerDetail: string | null;
@@ -1129,6 +1129,48 @@ describe("admitWakeBehindIssueExecution", () => {
expect(writer.coalesceIntoActiveExecutionRun).not.toHaveBeenCalled();
});
it("records an ordinary merged comment under its own actor without requiring durable receipt identity", async () => {
const writer = createFakeAdmissionWriter();
const reader = createFakeAdmissionReader({ findExistingDeferredWake: vi.fn(async () => ({
id: "existing-deferred-wake", runId: null, payload: { issueId: "issue-1" }, deferredContext: {}, coalescedCount: 1,
})) });
const admit = createAdmitWakeBehindIssueExecution({ reader, writer,
helpers: createFakeAdmissionHelpers({ shouldDeferFollowupWakeForSameIssue: vi.fn(() => true) }) });
await expect(admit(SCOPE, admissionInput({ wakeCommentId: "second-comment", requestedByActorId: "second-user",
payload: { commentId: "second-comment", coalescedIntoWakeupRequestId: "forged-target" },
}))).resolves.toEqual({ kind: "deferred" });
const input = vi.mocked(writer.mergeIntoExistingDeferredWake).mock.calls[0][1];
expect(input.coalescedReceipt).toEqual({ agentId: "wake-agent", source: "on_demand", triggerDetail: null,
reason: null, payload: { issueId: "issue-1", commentId: "second-comment", coalescedIntoWakeupRequestId: "existing-deferred-wake" },
requestedByActorType: "user", requestedByActorId: "second-user", idempotencyKey: null, runId: null });
expect(input.coalescedReceipt).not.toHaveProperty("id");
expect(input.coalescedReceipt).not.toHaveProperty("requestedAt");
expect(reader.matchesActiveWakeActor).not.toHaveBeenCalled();
});
it("binds authoritative issue scope when durable merged input omits it from payload", async () => {
const writer = createFakeAdmissionWriter();
const durableReceipt = { id: "own-request", requestedAt: new Date("2026-09-10T00:00:00Z") };
const admit = createAdmitWakeBehindIssueExecution({ writer,
reader: createFakeAdmissionReader({ findExistingDeferredWake: vi.fn(async () => ({
id: "saved-wake", runId: null, payload: { issueId: "issue-1" }, deferredContext: {}, coalescedCount: 0,
})) }), helpers: createFakeAdmissionHelpers({ shouldDeferFollowupWakeForSameIssue: vi.fn(() => true) }) });
await admit(SCOPE, admissionInput({ durableReceipt, payload: { text: "saved input" } }));
expect(vi.mocked(writer.mergeIntoExistingDeferredWake).mock.calls[0][1].coalescedReceipt).toEqual(expect.objectContaining({
...durableReceipt, payload: { text: "saved input", issueId: "issue-1", coalescedIntoWakeupRequestId: "saved-wake" },
}));
});
it.each(["agent", "system"])("does not create a user-comment authority receipt for an ordinary %s merge", async actor => {
const writer = createFakeAdmissionWriter();
const admit = createAdmitWakeBehindIssueExecution({ writer,
reader: createFakeAdmissionReader({ findExistingDeferredWake: vi.fn(async () => ({
id: "existing-deferred-wake", runId: null, payload: { issueId: "issue-1" }, deferredContext: {}, coalescedCount: 1,
})) }), helpers: createFakeAdmissionHelpers({ shouldDeferFollowupWakeForSameIssue: vi.fn(() => true) }) });
await admit(SCOPE, admissionInput({ wakeCommentId: "second-comment", requestedByActorType: actor }));
expect(vi.mocked(writer.mergeIntoExistingDeferredWake).mock.calls[0][1]).not.toHaveProperty("coalescedReceipt");
});
it("gives manual input its own run boundary even when the active receipt has the same requester", async () => {
const writer = createFakeAdmissionWriter();
const admit = createAdmitWakeBehindIssueExecution({
@@ -923,7 +923,8 @@ export function createAdmitWakeBehindIssueExecution(deps: {
mergedPayload,
nextCoalescedCount: (existingDeferred.coalescedCount ?? 0) + 1,
...(manualUserWakeActorId ? { manualUserWakeActorId } : {}),
...(input.durableReceipt
...(input.durableReceipt || (input.requestedByActorType === "user" &&
readNonEmptyString(input.requestedByActorId) && readNonEmptyString(input.wakeCommentId))
? {
coalescedReceipt: {
...input.durableReceipt,
@@ -933,6 +934,7 @@ export function createAdmitWakeBehindIssueExecution(deps: {
reason: input.reason ?? null,
payload: {
...(input.payload ?? {}),
issueId: input.issueId,
coalescedIntoWakeupRequestId: existingDeferred.id,
},
requestedByActorType: input.requestedByActorType,