From 923574f3eeb8ae6c1b8fe47dccfce1492925857b Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 3 Nov 2026 09:53:12 -0600 Subject: [PATCH] test(activity): preserve explicit pause chronology Co-Authored-By: Paperclip --- .../internal-timeline-narrative.ts | 13 +++++++++++-- .../internal-timeline-native-milestone.test.ts | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/runner-e2e/chat-catalog-domains/internal-timeline-narrative.ts b/tests/runner-e2e/chat-catalog-domains/internal-timeline-narrative.ts index d05677def82..e4ce1292c8a 100644 --- a/tests/runner-e2e/chat-catalog-domains/internal-timeline-narrative.ts +++ b/tests/runner-e2e/chat-catalog-domains/internal-timeline-narrative.ts @@ -15,7 +15,7 @@ export function truthfulTimelineAccount(input: { const failureOutcome = /\b(?:fail(?:ed|ure|ing)?|exit(?:ed)?(?:\s+with)?(?:\s+(?:code|status))?\s*[:=]?\s*1)\b/gi; const failures = clauses.flatMap(({ text, at }) => mentions(text, firstWorker) && /\b(?:verification|verify_launch_html|check|test|command)\b/i.test(text) - ? [...text.matchAll(failureOutcome)].map(match => ({ at: at + match.index, clauseAt: at })) : []); + ? [...text.matchAll(failureOutcome)].map(match => ({ at: at + match.index, end: at + match.index + match[0].length, clauseAt: at })) : []); const humanActors = ["you", "human operator", "board member", "human", "person", "user", "operator", "board", input.personName].filter(Boolean).map(name => escape(name!)).join("|"); const humanActor = `\\b(?:${humanActors})\\b`; const assignment = "\\b(?:reassign(?:ed|ment)?|assign(?:ed|ment)?|hand(?:ed)?(?:\\s+\\w+){0,4}\\s+(?:off|over)|handed|handoff)\\b"; @@ -76,8 +76,17 @@ export function truthfulTimelineAccount(input: { commandLead.test(prefix.slice(0, success.index)) && successIsOwned(text, success.index) && causedByRepair.test(prefix.slice(success.index + success[0].length))); }).map(match => ({ at: at + match.index }))); + const pauseCausedByFailure = new RegExp(`\\b(?:after|because)\\s+(?:(?:the|his|her|their|its|${escape(firstWorker)}['’]s)\\s+)?${commandTail}`, "i"); + const failureAfterPause = new RegExp(`\\bafter\\s+(?:(?:the|his|her|their|its|a)\\s+)?(?:task|work|it|${escape(firstWorker)})(?:\\s+(?:was|got|had been|has been))?\\s*$`, "i"); const ordered = failures.some(failure => handoffs.some(handoff => handoff.at > failure.at - && /\b(?:paused|blocked|stopped)\b/i.test(body.slice(failure.clauseAt, handoff.at)) + && [...body.slice(failure.clauseAt, handoff.at).matchAll(/\b(?:paused|blocked|stopped)\b/gi)].some(pause => { + const pauseAt = failure.clauseAt + pause.index; + // Wording order can differ from event order, but an earlier pause needs + // an explicit causal link to this failure, not just the same clause. + return pauseAt < failure.at + ? pauseCausedByFailure.test(body.slice(pauseAt + pause[0].length, failure.at)) + : !failureAfterPause.test(body.slice(failure.end, pauseAt)); + }) && repairs.some(repair => repair.at > handoff.at))); const nativeFailure = /\b(?:native|runner|execution)\s+runs?\s+(?:(?:themselves|were|was|had|also|both)\s+){0,3}(?:failed|failures?|unsuccessful)\b|\b(?:native|runner|execution)\s+runs?\s+(?:ended|finished)\s+(?:in|with)\s+(?:a\s+)?failure\b|\bfailed\s+(?:native|runner|execution)\s+runs?\b/i; const falseNativeFailure = clauses.some(({ text }) => { diff --git a/tests/runner-e2e/chat-catalog-domains/internal-timeline-native-milestone.test.ts b/tests/runner-e2e/chat-catalog-domains/internal-timeline-native-milestone.test.ts index 6018f60f80c..50808a74cf7 100644 --- a/tests/runner-e2e/chat-catalog-domains/internal-timeline-native-milestone.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/internal-timeline-native-milestone.test.ts @@ -196,3 +196,18 @@ it("admits the selected live timeline metadata without invoking fixture callback for (const spy of spies) expect(spy).not.toHaveBeenCalled(); } finally { for (const spy of spies) spy.mockRestore(); } }); + +it.each([ + "Today UTC: Morgan's verification failed before the task paused. You reassigned the task to Riley. Riley fixed the label and the check passed.", + "Today UTC: Morgan's command exited with code 1 before the task was blocked. You reassigned the task to Riley. Riley added the missing label and the verification passed.", +])("keeps explicit failure-before-pause chronology valid: %s", reply => { + const o = fixture(); o.reply = reply; expect(gradeWorkTimeline(o).passed).toBe(true); +}); +it.each([ + "Today UTC: Morgan paused the task before verification failed. You reassigned the task to Riley. Riley fixed the label and the check passed.", + "Today UTC: Morgan was blocked before verification failed. You reassigned the task to Riley. Riley fixed the label and the check passed.", + "Today UTC: Morgan's verification failed after the task paused. You reassigned the task to Riley. Riley fixed the label and the check passed.", + "Today UTC: Morgan paused an unrelated task, then verification failed. You reassigned the task to Riley. Riley fixed the label and the check passed.", +])("rejects reversed or unrelated pause chronology: %s", reply => { + const o = fixture(); o.reply = reply; expect(gradeWorkTimeline(o).passed).toBe(false); +});