mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
Verify warm continuity with run-scoped credential rotation
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3fa1a22914
commit
3da3d0c307
3 files changed
+23
-11
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -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":"<returned interaction id>"}. 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.`,
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
Reference in new issue
Block a user