fix(e2e): include verified transport recovery causes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-30 06:30:26 -05:00
1 parent bc4b3564cd
commit b17ecd1946
3 files changed
+20 -8

No files matched your search

+5 -2
View File
@@ -105,8 +105,11 @@ server proves that no provider work started. A scheduled replacement remains
pending and consumes the run limit. It cannot satisfy completion; the child must
still finish through native execution and pass the independent artifact checks.
A native run with a saved automatic same-run recovery remains pending within the
original campaign deadline. The authenticated run projection must identify an
A native run can remain pending for saved automatic same-run recovery with code
`native_session_interrupted`, `provider_transport_failed`,
`provider_request_timeout`, or `native_runner_process_exited`.
The wait stays within the original campaign deadline.
The authenticated run projection must identify a matching source code and an
agent-owned retryable native interruption, a bounded attempt and retry schedule,
and no successor. An overdue retry can still be reconnecting; it does not extend
the deadline or authorize a new story attempt. Ordinary failures, terminal or
@@ -301,7 +301,8 @@ describe("Cloud canonical onboarding first task", () => {
expect(result.artifact.originatingRunId).toBe("child-run");
expect(connectionDelay).toHaveBeenCalledOnce();
});
it.each([false, true])("observes the same native recovery through success with workspace predecessor=%s", async predecessor => {
it.each(["native_session_interrupted", "provider_transport_failed", "provider_request_timeout", "native_runner_process_exited"]
.flatMap(cause => [false, true].map(predecessor => ({ cause, predecessor }))))("observes $cause recovery through success with workspace predecessor=$predecessor", async ({ cause, predecessor }) => {
let now = Date.now(), stage = 0;
const failedAt = now - 1000, retryAt = now + 30_000;
vi.spyOn(Date, "now").mockImplementation(() => now);
@@ -311,7 +312,7 @@ describe("Cloud canonical onboarding first task", () => {
const child = state.runs.find(run => run.id === "child-run")!;
if (predecessor) { state.runs.splice(state.runs.indexOf(child), 0, workspaceDeferral()); linkWorkspaceRetry(child); }
if (stage < 2) {
withPendingNativeRecovery(child, failedAt, retryAt);
withPendingNativeRecovery(child, failedAt, retryAt, cause);
child.execution.phase = stage === 0 ? "retry_scheduled" : "reconnecting";
child.scheduledRetryAt = new Date(failedAt - 60_000).toISOString();
} else if (stage === 2) Object.assign(child, { status: "running", nativePhase: "observed", finishedAt: null });
@@ -365,6 +366,11 @@ describe("Cloud canonical onboarding first task", () => {
["mismatched issue binding", (run: Row) => { run.contextSnapshot.issueId = "other-issue"; }],
["provider failure", (run: Row) => { run.errorCode = run.execution.cause = "native_provider_terminal_failed"; }],
["mismatched cause", (run: Row) => { run.execution.cause = "provider_transport_failed"; }],
["unknown matching cause", (run: Row) => { run.errorCode = run.execution.cause = "unclassified_provider_error"; }],
["ambiguous transport failure", (run: Row) => {
run.errorCode = run.execution.cause = "provider_transport_failed"; run.nativePhase = "terminal_failure";
Object.assign(run.execution, { phase: "recovery_needed", recoveryOwner: "board", retryAt: null });
}],
["generic recovery", (run: Row) => { run.execution.phase = "recovery_needed"; }],
["board owner", (run: Row) => { run.execution.recoveryOwner = "board"; }],
["missing schedule", (run: Row) => { run.execution.retryAt = null; }],
@@ -526,10 +532,10 @@ function workspaceDeferral(): Row {
} };
}
function withPendingNativeRecovery(run: Row, failedAt: number, retryAt: number) {
Object.assign(run, { status: "failed", nativePhase: "retryable_failure", errorCode: "native_session_interrupted",
function withPendingNativeRecovery(run: Row, failedAt: number, retryAt: number, cause = "native_session_interrupted") {
Object.assign(run, { status: "failed", nativePhase: "retryable_failure", errorCode: cause,
finishedAt: new Date(failedAt).toISOString(), execution: {
phase: "retry_scheduled", cause: "native_session_interrupted", retryAt: new Date(retryAt).toISOString(),
phase: "retry_scheduled", cause, retryAt: new Date(retryAt).toISOString(),
attempt: 1, maxAttempts: 3, recoveryOwner: "agent", successorRunId: null,
} });
}
@@ -1,6 +1,9 @@
type RecordValue = Record<string, unknown>;
const object = (value: unknown): RecordValue => value !== null && typeof value === "object" && !Array.isArray(value)
? value as RecordValue : {};
const nativeRecoveryCauses = new Set([
"native_session_interrupted", "provider_transport_failed", "provider_request_timeout", "native_runner_process_exited",
]);
function hasProviderUsage(value: unknown): boolean {
if (value == null) return false;
@@ -20,7 +23,7 @@ export function isPendingNativeRecovery(run: RecordValue, deadlineAt: number): b
const finishedAt = typeof run.finishedAt === "string" ? Date.parse(run.finishedAt) : NaN;
const retryAt = typeof execution.retryAt === "string" ? Date.parse(execution.retryAt) : NaN;
return run.runtimeMode === "native" && run.status === "failed" && run.nativePhase === "retryable_failure" &&
run.errorCode === "native_session_interrupted" && execution.cause === "native_session_interrupted" &&
typeof run.errorCode === "string" && nativeRecoveryCauses.has(run.errorCode) && execution.cause === run.errorCode &&
typeof run.nativeIssueId === "string" && run.nativeIssueId === object(run.contextSnapshot).issueId &&
(run.issueId == null || run.issueId === run.nativeIssueId) &&
(execution.phase === "retry_scheduled" || execution.phase === "reconnecting") && execution.recoveryOwner === "agent" &&