diff --git a/doc/native-workspace-finalization-recovery.md b/doc/native-workspace-finalization-recovery.md index c888ce1a34f..3d16ff3aada 100644 --- a/doc/native-workspace-finalization-recovery.md +++ b/doc/native-workspace-finalization-recovery.md @@ -19,6 +19,11 @@ cleanup run. This includes descriptor reads that fail inside runtime selection. preparation failure; another owner or a stale claim prevents cleanup. Settling that claim and recording file retention share a transaction. +The reconciliation candidate scan selects the complete workspace-sync record and +the native status wake identifier from each saved run. It keeps their JSON types +and reads full evidence later where finalization or recovery needs it. Saved +execution input, runner profiles, and logs stay in the stored run. + The recovery card names the original run and requires evidence that its exact allocation stopped and its files were recovered. Recording that evidence does not retry the old turn, certify unknown external actions, change the current task diff --git a/server/src/services/native-runtime/native-finalization-reconciler.ts b/server/src/services/native-runtime/native-finalization-reconciler.ts index ec391457b07..283aac76b2d 100644 --- a/server/src/services/native-runtime/native-finalization-reconciler.ts +++ b/server/src/services/native-runtime/native-finalization-reconciler.ts @@ -742,8 +742,8 @@ async function reconcileNativeFinalizationsPass( resultId: nativeRunFinalizations.resultId, assessmentId: nativeRunFinalizations.assessmentId, decisionId: nativeRunFinalizations.decisionId, - runnerProfileJson: heartbeatRuns.runnerProfileJson, - contextSnapshot: heartbeatRuns.contextSnapshot, + nativeWorkspaceSync: sql`${heartbeatRuns.runnerProfileJson}->'nativeWorkspaceSync'`, + nativeStatusWakeIntentId: sql`${heartbeatRuns.contextSnapshot}->'nativeStatusWakeIntentId'`, }) .from(heartbeatRuns) .innerJoin(nativeRunFinalizations, eq(nativeRunFinalizations.runId, heartbeatRuns.id)) @@ -955,8 +955,7 @@ async function reconcileNativeFinalizationsPass( if (reviewBarrier && reviewBarrier.status !== "succeeded") continue; const childCompletionRecipient = { companyId: row.companyId, issueId: row.issueId, agentId: row.agentId, runId: row.runId, - sourceIntentId: typeof record(row.contextSnapshot).nativeStatusWakeIntentId === "string" - ? record(row.contextSnapshot).nativeStatusWakeIntentId as string : null, + sourceIntentId: typeof row.nativeStatusWakeIntentId === "string" ? row.nativeStatusWakeIntentId : null, }; const hasPendingChildCompletion = await hasPendingNativeChildCompletion(db, childCompletionRecipient); const reassessmentRow = await recordNativeWorkAssessment({ @@ -1095,7 +1094,7 @@ async function reconcileNativeFinalizationsPass( projectRunStatus: true, preserveProviderAttempt: Boolean( readNativeWorkspaceSyncReference( - record(row.runnerProfileJson).nativeWorkspaceSync, + row.nativeWorkspaceSync, ), ), }); @@ -1153,7 +1152,7 @@ async function reconcileNativeFinalizationsPass( projectRunStatus: true, preserveProviderAttempt: Boolean( readNativeWorkspaceSyncReference( - record(row.runnerProfileJson).nativeWorkspaceSync, + row.nativeWorkspaceSync, ), ), }); diff --git a/server/src/services/native-runtime/native-run-finalizer-telemetry.test.ts b/server/src/services/native-runtime/native-run-finalizer-telemetry.test.ts index 2c2e534081b..5d1ed177987 100644 --- a/server/src/services/native-runtime/native-run-finalizer-telemetry.test.ts +++ b/server/src/services/native-runtime/native-run-finalizer-telemetry.test.ts @@ -279,6 +279,11 @@ describeEmbeddedPostgres("native run finalizer / status decision committer — a }); expect(agentTaskRunCalls(callsBefore)).toHaveLength(0); expect(reconcileMuseAssignments).toHaveBeenCalledExactlyOnceWith(companyId, agentId, fixture.runId); + await db.insert(workspaceOperations).values({ companyId, issueId: fixture.issueId, + heartbeatRunId: fixture.runId, phase: "workspace_finalize", status: "succeeded" }); + const reconciled = await reconcileNativeFinalizations(db, [fixture.runId]); + expect(reconciled).toContainEqual(expect.objectContaining({ runId: fixture.runId, phase: "committed" })); + expect(agentTaskRunCalls(callsBefore)).toHaveLength(0); }); });