fix(qa): ignore rejected cron mutations (#163191)

This commit is contained in:
Josh Avant 2026-10-01 22:07:51 -05:00 • committed by GitHub
parent 3e7270438e
commit 8866daeed3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 28 additions and 3 deletions

View file

@ -25,7 +25,11 @@ type Job = {
};
type ToolCall = { action: string; job?: Job; jobId?: string; includeDisabled?: boolean };
function historyMessages(calls: ToolCall[], shape: HistoryShape) {
function historyMessages(
calls: ToolCall[],
shape: HistoryShape,
errorCallIndexes: readonly number[] = [],
) {
const messages = calls.flatMap<Record<string, unknown>>((input, index) => {
const id = `automation-${index}`;
if (shape === "nested") {
@ -35,6 +39,7 @@ function historyMessages(calls: ToolCall[], shape: HistoryShape) {
toolCallId: id,
input,
text: JSON.stringify(input.job ? { id: input.job.id } : { ok: true }),
isError: errorCallIndexes.includes(index),
}),
];
}
@ -78,6 +83,7 @@ async function runSchedulingFixture(
onHistoryRead?: () => void;
initialHistoryCallCount?: number;
mutateCalls?: (calls: ToolCall[]) => void;
errorCallIndexes?: readonly number[];
mutateJobs?: (jobs: Job[]) => void;
mutateRestartedJobs?: (jobs: Job[]) => void;
oneShotRunCount?: number;
@ -117,7 +123,7 @@ async function runSchedulingFixture(
});
options.mutateCalls?.(calls);
options.mutateJobs?.(jobs);
const history = historyMessages(calls, shape);
const history = historyMessages(calls, shape, options.errorCallIndexes);
let restarted = false;
let recurringReads = 0;
let historyReads = 0;
@ -289,6 +295,24 @@ describe("scheduling YAML canonical tool proof", () => {
expect(removed).toEqual(["job-0", "job-1", "job-2", "job-3"]);
});
it.each(["authority", "recurring"] as const)(
"ignores a rejected %s add attempt before successful mutations",
async (kind) => {
const { result, restarted, removed } = await runSchedulingFixture(kind, "nested", {
mutateCalls: (calls) => {
assert.ok(calls[0]);
calls.unshift(structuredClone(calls[0]));
},
errorCallIndexes: [0],
});
expect(result.status).toBe("pass");
expect(restarted).toBe(true);
expect(removed).toEqual(
kind === "authority" ? ["job-0", "job-1", "job-2", "job-3"] : ["job-1"],
);
},
);
it.each(["direct", "nested"] as const)(
"accepts one-shot/recurring evidence alongside other owners in %s history",
async (shape) => {

View file

@ -243,7 +243,7 @@ flow:
- ref: config.sessionKey
- lambda:
params: [history]
expr: "(() => { const calls = projectQaToolMessages(history.messages ?? []).flatMap((message) => message.role === 'assistant' && Array.isArray(message.content) ? message.content : []).filter((item) => item.type === 'toolCall' && item.name === 'automations'); return calls.length >= 4 ? calls : undefined; })()"
expr: "(() => { const calls = projectQaToolMessages(history.messages ?? []).flatMap((message) => message.role === 'assistant' && Array.isArray(message.content) ? message.content : []).filter((item) => item.type === 'toolCall' && item.name === 'automations' && item.isError !== true); return calls.length >= 4 ? calls : undefined; })()"
- expr: liveTurnTimeoutMs(env, 240000)
- 250
- assert:

View file

@ -120,6 +120,7 @@ flow:
const mutations = projectQaToolMessages(history.messages ?? [])
.flatMap((message) => message.role === 'assistant' && Array.isArray(message.content) ? message.content : [])
.filter((item) => item.type === 'toolCall' && item.name === 'automations'
&& item.isError !== true
&& !['list', 'status', 'get', 'runs'].includes(item.arguments?.action));
return mutations.filter((call) => call.arguments?.action === 'add').length >= 2
|| mutations.some((call) => call.arguments?.action !== 'add')