fix(server): include restored monitor proof in assessment identity (#17665)

Include validated preserved monitor proof in the immutable assessment input so restored receipts or completion times can produce a new status decision at the same deadline. Preserve accepted results, earlier decisions, provider attempts, monitor deadlines, and idempotent replay.

Validate two baseline regressions, 201 targeted tests on current master, workspace typecheck, server build, full CI, and Apex 5/5.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-25 16:06:36 -07:00
1 parent 9f3a5456ac
commit 5a28f7acf8
4 files changed
+30 -5

No files matched your search

+5
View File
@@ -175,6 +175,11 @@ launch context does not travel with each policy or parent-report check. JSON
types, task-key precedence, and company and agent identity checks are preserved.
Each decision still reads current data; this is not an authorization cache.
Restoring a saved monitor's receipt or completion proof creates a new native
assessment identity even when its scheduled deadline is unchanged. The saved
proof is part of the assessment input. Prior decisions and accepted results stay
intact, and an unchanged replay reuses the same assessment and decision.
Native tool authority lookups select current run identity, status, invocation,
responsible user, context, and receipt data. Saved profiles and output excerpts
stay in storage and are omitted from that read. Task and agent binding, current
@@ -126,21 +126,38 @@ describe.skipIf(!support.supported)("released native monitor results", () => {
},
);
it("supersedes an earlier authority error without replaying the accepted result", async () => {
it.each(["schedule", "receipt", "finished_time"] as const)("supersedes an earlier authority error after restoring %s without replaying the result", async state => {
const f = await fixture(), scheduled = await f.readIssue();
await db.update(issues).set({ executionPolicy: {}, monitorNextCheckAt: null }).where(eq(issues.id, f.issueId));
const [source] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, f.runId));
await db.update(nativeRunFinalizations).set({ controllerBootId: randomUUID(), attempt: 1 })
.where(eq(nativeRunFinalizations.runId, f.runId));
if (state === "schedule") await db.update(issues).set({ executionPolicy: {}, monitorNextCheckAt: null }).where(eq(issues.id, f.issueId));
if (state === "receipt") await db.update(heartbeatRuns).set({ resultJson: {} }).where(eq(heartbeatRuns.id, f.runId));
if (state === "finished_time") await db.update(heartbeatRuns).set({ finishedAt: null }).where(eq(heartbeatRuns.id, f.runId));
await f.finalize();
const old = await db.select().from(statusDecisions).where(eq(statusDecisions.runId, f.runId));
expect(old.some(decision => decision.reasonCode === "monitor_wait_authority_lost")).toBe(true);
await db.update(issues).set({ executionPolicy: scheduled.executionPolicy, monitorNextCheckAt: scheduled.monitorNextCheckAt })
if (state === "schedule") await db.update(issues).set({ executionPolicy: scheduled.executionPolicy, monitorNextCheckAt: scheduled.monitorNextCheckAt })
.where(eq(issues.id, f.issueId));
if (state === "receipt") {
const [current] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, f.runId));
await db.update(heartbeatRuns).set({ resultJson: { ...current.resultJson,
semanticToolReceipts: source.resultJson?.semanticToolReceipts } }).where(eq(heartbeatRuns.id, f.runId));
}
if (state === "finished_time") await db.update(heartbeatRuns).set({ finishedAt: f.finishedAt }).where(eq(heartbeatRuns.id, f.runId));
await f.finalize();
expect((await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.runId)))[0].phase).toBe("committed");
const [settled] = await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.runId));
expect(settled).toMatchObject({ phase: "committed", nextAttemptAt: null, attempt: 1 });
const after = await db.select().from(statusDecisions).where(eq(statusDecisions.runId, f.runId));
for (const before of old) expect(after.find(decision => decision.id === before.id)).toEqual(before);
expect(after.some(decision => decision.reasonCode === "scheduled_monitor_preserved")).toBe(true);
expect((await db.select().from(nativeRunResults).where(eq(nativeRunResults.id, f.resultId)))[0].resultJson).toEqual(f.result);
expect(await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.companyId, f.companyId))).toEqual([]);
expect(await f.readIssue()).toMatchObject({ monitorNextCheckAt: scheduled.monitorNextCheckAt,
executionRunId: null, checkoutRunId: null });
await f.finalize();
expect(await db.select().from(statusDecisions).where(eq(statusDecisions.runId, f.runId))).toEqual(after);
expect((await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, f.runId)))[0].finishedAt).toEqual(f.finishedAt);
});
it("notifies the existing monitor scheduler once when finalization stops blocking it", async () => {
@@ -1488,6 +1488,7 @@ export async function finalizeNativeRun(input: {
workspaceFinalizeStatus: input.workspaceFinalizeStatus,
monitorDelivery,
monitorWaitAt: assessment.continuation?.kind === "monitor" ? monitorWaitAt : null,
preservedMonitorWait,
});
await input.db
.update(nativeRunFinalizations)
@@ -3,7 +3,7 @@ import type { Db } from "@paperclipai/db";
import { workAssessments } from "@paperclipai/db";
import type { NativeEvidenceAssessment } from "./evidence-classifier.js";
import { nativeSha256 } from "./canonical.js";
import type { NativeMonitorDelivery } from "./native-monitor-delivery.js";
import type { NativeMonitorDelivery, NativePreservedMonitorWait } from "./native-monitor-delivery.js";
export async function recordNativeWorkAssessment(input: {
db: Db;
@@ -26,12 +26,14 @@ export async function recordNativeWorkAssessment(input: {
monitorDelivery?: NativeMonitorDelivery | null;
/** A persisted schedule can regain settlement authority after a policy repair. */
monitorWaitAt?: string | null;
preservedMonitorWait?: NativePreservedMonitorWait | null;
supersedesAssessmentId?: string | null;
}) {
const assessmentJson = {
...input.assessment,
...(input.monitorDelivery ? { monitorDelivery: input.monitorDelivery } : {}),
...(input.monitorWaitAt ? { monitorWaitAt: input.monitorWaitAt } : {}),
...(input.preservedMonitorWait ? { preservedMonitorWait: input.preservedMonitorWait } : {}),
} as unknown as Record<string, unknown>;
const inputDigest = nativeSha256({
contractId: input.contractId,