mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
fix(native): scope governed waits to one run
Clear settled wait authority when rebinding a provider session to a new run. Reject duplicate approval requests that reference a resolved action through an unknown completion criterion. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7ad54e8305
commit
ba9b0b4610
5 files changed
+19
-2
No files matched your search
@@ -1373,6 +1373,8 @@ settling their usage and terminal evidence before detaching. Recovery verifies
|
||||
the trigger against the retained event ledger and settles that same turn, even
|
||||
if the user has already answered the interaction. It replays retained accounting
|
||||
through the ordinary control-plane observers without starting another model turn.
|
||||
When a later heartbeat run reuses the provider session, it clears that prior
|
||||
run's wait marker along with its terminal, result and pending-request authority.
|
||||
A timeout, missing accounting, or an unproven provider terminal remains a failure;
|
||||
the saved wait does not certify successful provider execution.
|
||||
|
||||
|
||||
@@ -77,6 +77,11 @@ describe("native final-response feedback", () => {
|
||||
completionClaim: { ...report.completionClaim, criteria: [{ criterionId: "objective", status: "not_satisfied", evidenceRefs: [`approval:${actionRequestId}`] }] },
|
||||
};
|
||||
await expect(nativeCompletionFeedback(db, value.runId, criterionOnly)).rejects.toThrow("already resolved");
|
||||
// A provider can mark the approval criterion unknown while citing the exact
|
||||
// declined interaction; that still is not a new review target.
|
||||
await expect(nativeCompletionFeedback(db, value.runId, {
|
||||
...criterionOnly, completionClaim: { ...criterionOnly.completionClaim, criteria: [{ criterionId: "objective", status: "unknown", evidenceRefs: [`interaction:${interactionId}`] }] },
|
||||
})).rejects.toThrow("already resolved");
|
||||
// Satisfied historical evidence does not bind the independent review target.
|
||||
if (disposition !== "yielded") await expect(nativeCompletionFeedback(db, value.runId, {
|
||||
...criterionOnly, completionClaim: { ...criterionOnly.completionClaim, criteria: [{ criterionId: "past-action", status: "satisfied", evidenceRefs: [`approval:${actionRequestId}`] }] },
|
||||
|
||||
@@ -186,7 +186,7 @@ export async function nativeCompletionFeedback(
|
||||
blocker: result.blocker,
|
||||
// An unmet criterion can name the action still being requested. Completed
|
||||
// evidence elsewhere in the report is not authority to suppress a review.
|
||||
unmetCriteria: result.completionClaim.criteria.filter(criterion => criterion.status === "not_satisfied"),
|
||||
unmetCriteria: result.completionClaim.criteria.filter(criterion => criterion.status !== "satisfied"),
|
||||
continuation: result.continuation,
|
||||
// The existing response-wake contract also names its card through evidence.
|
||||
evidence: result.reportedWorkDisposition === "yielded" && result.continuation?.kind === "response_wake" ? result.evidence : [],
|
||||
|
||||
@@ -2125,10 +2125,17 @@ describe("rebindNativeSessionCheckpoint", () => {
|
||||
});
|
||||
|
||||
it("retains provider identity but clears prior turn and event state", () => {
|
||||
const prior = previousRun();
|
||||
const profile = prior.runnerProfileJson as Record<string, unknown>;
|
||||
const checkpoint = profile.sessionCheckpoint as Record<string, unknown>;
|
||||
checkpoint.governedWait = { sourceEvent: { runId: previousRunId, turnId: "old-turn" }, result: { reportedWorkDisposition: "yielded" } };
|
||||
const before = structuredClone(prior);
|
||||
const rebound = rebindNativeSessionCheckpoint({
|
||||
previousRun: previousRun(),
|
||||
previousRun: prior,
|
||||
currentExecution: execution(currentRunId),
|
||||
});
|
||||
expect(rebound?.governedWait).toBeUndefined();
|
||||
expect(prior).toEqual(before);
|
||||
expect(rebound).toMatchObject({
|
||||
sessionId: "provider-thread-123",
|
||||
providerSessionId: "provider-thread-123",
|
||||
|
||||
@@ -568,6 +568,9 @@ export function rebindNativeSessionCheckpoint(input: {
|
||||
activeTurnId: null,
|
||||
terminalTurns: [],
|
||||
pendingRuntimeRequests: [],
|
||||
// Settlement evidence belongs to the prior heartbeat run, even when the
|
||||
// provider session is reused for the user's response.
|
||||
governedWait: undefined,
|
||||
providerRecoveryPolicy,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user