fix(runner): accept native Rust question cancellation envelopes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-08 12:05:53 -05:00
1 parent d9e17db5ed
commit 75abb6a774
2 files changed
+25 -1

No files matched your search

@@ -318,6 +318,29 @@ describeEmbeddedPostgres("native question bridge", () => {
}
});
it("accepts the Rust ACPX cancellation payload without a redundant action field", async () => {
await seed();
const { interaction, cancelled } = await savedRuntimeQuestion();
// Both Rust expire_runtime_requests and RuntimeRequestOutcome emit this
// envelope. Cancellation authority is its committed event type.
cancelled.payload = { provider: "acpx", requestId: "request-1", requestKind: "runtime", requestType: "input",
turnId: cancelled.turnId, itemId: cancelled.itemId, reason: "session_interrupted", replayAllowed: false,
adapter: "acpx-runtime-sidecar", request: { ...(runtimeRequestEvent().payload.request as Record<string, unknown>),
turnId: cancelled.turnId, itemId: cancelled.itemId } };
await projectNativeRuntimeRequest({ db, binding: binding(), event: cancelled });
const [current] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interaction.id));
expect(current).toMatchObject({ status: "expired", result: { cancelled: true, answers: [] } });
});
it.each([{ action: "submit" }, { replayAllowed: true }, { response: { answers: {} } }])("rejects contradictory cancellation data %j", async (extra) => {
await seed();
const { interaction, cancelled } = await savedRuntimeQuestion();
Object.assign(cancelled.payload, extra);
await expect(projectNativeRuntimeRequest({ db, binding: binding(), event: cancelled })).rejects.toThrow("native_runtime_cancellation_invalid");
const [current] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interaction.id));
expect(current.status).toBe("pending");
});
it("commits and acknowledges an ACP permission, then queues only an admin's exact turn-bound decision", async () => {
await seed();
const event = permissionRequestEvent();
@@ -131,7 +131,8 @@ export async function projectNativeRuntimeRequest(input: {
// Permission cards have their own privileged resolver. A provider-loss
// expiry is a durable handoff, not cancellation of a historical question.
if (outcome.requestKind !== "runtime" || outcome.requestType !== "input") return null;
if (outcome.action !== "cancel" || typeof outcome.requestId !== "string"
if ((outcome.action !== undefined && outcome.action !== "cancel") || outcome.replayAllowed === true
|| typeof outcome.requestId !== "string"
|| !REQUEST_ID_PATTERN.test(outcome.requestId) || typeof input.event.turnId !== "string"
|| outcome.turnId !== input.event.turnId || (outcome.itemId ?? null) !== (input.event.itemId ?? null)
|| ["response", "answers", "answer", "replay"].some((key) => Object.hasOwn(outcome, key))) {