From 3da3d0c30719b1f09c5c8e85e7cc39b68de79a3a Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 17 Sep 2026 11:25:18 -0500 Subject: [PATCH] Verify warm continuity with run-scoped credential rotation Co-Authored-By: Paperclip --- tests/runner-e2e/README.md | 4 +++- tests/runner-e2e/catalog.ts | 1 + tests/runner-e2e/runner.spec.ts | 29 +++++++++++++++++++---------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index 7faf0d49dd8..498dfae20c8 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -126,7 +126,9 @@ three browser-driven turns on one issue. Every turn reads and extends the same nonce file, verifies host copy-back, records scheduler/run/end-to-end timing, and asserts `created`, `resumed`, `resumed` lease acquisition on one sandbox. Runner Codex additionally proves stable native session, provider session, -runner instance, PID, and process-start identity. Each turn is bounded to ten +and runner instance. With managed GitHub authentication, run-scoped credentials +require distinct process identities on each turn; without it, PID and process-start +identity remain stable. Each turn is bounded to ten minutes, the cell to thirty minutes, and cleanup explicitly deletes the sandbox rather than waiting for Daytona's idle timeout. diff --git a/tests/runner-e2e/catalog.ts b/tests/runner-e2e/catalog.ts index f2b3156847a..f5c14fd44e0 100644 --- a/tests/runner-e2e/catalog.ts +++ b/tests/runner-e2e/catalog.ts @@ -823,6 +823,7 @@ function warmTurnInstructions(turn: 1 | 2 | 3, nonce: string) { : `In a legacy runner, after verification POST exactly one request_confirmation to /api/issues/$PAPERCLIP_TASK_ID/interactions with {"kind":"request_confirmation","idempotencyKey":"daytona-warm-review-T${turn}-${nonce}","resolverPolicy":"human_only","title":"Warm continuity turn ${turn}","summary":"Review completed warm continuity turn ${turn}.","continuationPolicy":"wake_assignee","payload":{"version":1,"prompt":"Is this warm continuity task ready to complete after turn ${turn}?","acceptLabel":"Approve completion","rejectLabel":"Continue work","rejectRequiresReason":true,"allowDeclineReason":true,"supersedeOnUserComment":false,"target":{"type":"custom","key":"daytona_warm_turn_${turn}","revisionId":"${nonce}-T${turn}","label":"Warm continuity turn ${turn}"}}}. Capture the returned interaction id. Then make exactly one issue PATCH with {"status":"in_review","comment":"${marker}","reviewInteractionId":""}. Include Authorization and X-Paperclip-Run-Id on both writes. If the issue PATCH fails, retry only that PATCH and never create another interaction. Do not POST a separate comment. After both writes succeed, end the response and heartbeat immediately; do not wait or poll because the reviewer action will start the next turn.`; return [ `This is warm Daytona continuity turn ${turn} of 3. Work only in the current execution workspace.`, + "The workspace file is an internal continuity-test sentinel, not a requested file deliverable. Do not register or attach it. The only requested user-facing deliverable is the exact response marker below.", turn === 1 ? `Create ${file} with exactly this one line followed by a newline: ${lines[0]}` : `Before changing anything, read ${file} and verify its content is exactly ${lines.slice(0, -1).join("\\n")} followed by a newline. Then append exactly ${lines.at(-1)} followed by a newline.`, diff --git a/tests/runner-e2e/runner.spec.ts b/tests/runner-e2e/runner.spec.ts index f4b29cca939..313e6052616 100644 --- a/tests/runner-e2e/runner.spec.ts +++ b/tests/runner-e2e/runner.spec.ts @@ -2022,16 +2022,6 @@ for (const execution of executions) { label: "provider session", values: selectedRuns.map((candidate) => candidate.sessionIdAfter), }, - { - label: "runner pid", - values: selectedRuns.map((candidate) => candidate.processPid), - }, - { - label: "runner process fingerprint", - values: selectedRuns.map( - (candidate) => candidate.processStartedAt, - ), - }, ]; for (const { label, values } of stableIdentityFields) { if ( @@ -2048,6 +2038,25 @@ for (const execution of executions) { ); } } + + // Managed GitHub capabilities are run-scoped. The supervisor must + // rotate processes while preserving the durable provider session. + const managedCredentials = selectedRuns.every( + (candidate) => record(candidate.contextSnapshot).githubAuthenticationMode === "managed", + ); + const processIdentities: Array<{ label: string; values: unknown[] }> = [ + { label: "runner pid", values: selectedRuns.map((candidate) => candidate.processPid) }, + { label: "runner process fingerprint", values: selectedRuns.map((candidate) => candidate.processStartedAt) }, + ]; + for (const { label, values } of processIdentities) { + const expectedDistinct = managedCredentials ? selectedRuns.length : 1; + if (values.some((value) => value === null || value === undefined || String(value).length === 0) + || new Set(values).size !== expectedDistinct) { + invariantFailures.push( + `expected ${expectedDistinct} distinct ${label} values for ${managedCredentials ? "rotating managed credentials" : "warm process reuse"}; observed ${JSON.stringify(values)}`, + ); + } + } } if ( turnTimings?.length !== 3 ||