fix: preserve agent-owned native result recovery

Keep settled and foreign-owner failures fenced while allowing audit-only malformed-result outcomes to materialize their existing bounded recovery action.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-27 18:26:54 -05:00
1 parent c5b2a16a28
commit d02e0f45e7
2 files changed
+39 -1

No files matched your search

@@ -553,6 +553,36 @@ describeEmbeddedPostgres("native run finalizer / status decision committer — a
expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, fixture.issueId))).toEqual([]);
});
it("materializes recovery for an agent-owned invalid-result outcome", async () => {
const fixture = await seedNativeRun();
await db.insert(nativeRunFinalizations).values({
runId: fixture.runId, companyId, issueId: fixture.issueId,
phase: "terminal_failure", failureCode: "native_finalization_invalid",
failureDetail: { recoveryOwner: { kind: "agent", agentId } },
});
const outcome = await recordNativeFinalizationFailure({
db, runId: fixture.runId, error: new Error("native_finalization_invalid"),
});
expect(outcome.phase).toBe("retryable_failure");
expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, fixture.issueId)))
.toEqual([expect.objectContaining({ status: "active", ownerType: "agent", cause: "native_finalization_invalid" })]);
});
it("does not reopen a board-owned terminal workspace repair for a late generic failure", async () => {
const fixture = await seedNativeRun();
await db.insert(nativeRunFinalizations).values({
runId: fixture.runId, companyId, issueId: fixture.issueId, phase: "workspace_finalizing",
});
await recordNativeFinalizationFailure({ db, runId: fixture.runId,
error: new Error("native_workspace_sync_out_unrecoverable"), failureScope: "workspace", permanent: true });
const [before] = await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, fixture.runId));
const recoveryBefore = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, fixture.issueId));
await recordNativeFinalizationFailure({ db, runId: fixture.runId, error: new Error("native_finalization_invalid") });
const [after] = await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, fixture.runId));
expect(after).toEqual(before);
expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, fixture.issueId))).toEqual(recoveryBefore);
});
it("emits zero events when a retryable-failure write's conditional status spread is omitted", async () => {
const fixture = await seedNativeRun();
// recordNativeFinalizationFailure only needs the run and coordinator rows
@@ -291,7 +291,15 @@ async function recordRetryableFailure(input: {
eq(nativeRunFinalizations.companyId, input.run.companyId),
)).for("update").limit(1).then((rows) => rows[0] ?? null);
if (!current) throw new Error("native_finalization_missing");
if (current.phase === "committed" || current.phase === "terminal_failure"
// Audit-only attention first records an agent-owned invalid-result outcome;
// its caller still needs to materialize the normal bounded recovery action.
// Board-owned terminal repairs and late snapshots remain settled.
const recoveryOwner = record(record(current.failureDetail).recoveryOwner);
const pendingAgentRecovery = current.phase === "terminal_failure"
&& input.coordinator.phase === "terminal_failure"
&& recoveryOwner.kind === "agent" && recoveryOwner.agentId === input.run.agentId;
if (current.phase === "committed"
|| (current.phase === "terminal_failure" && !pendingAgentRecovery)
|| current.leaseOwner !== input.coordinator.leaseOwner) return current;
const [currentRun] = await tx.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, input.run.id)).limit(1);
if (!currentRun) throw new Error("native_finalization_run_missing");