Files
PaperClipAI/tests/runner-e2e/everyday-reliability.test.ts
T
DottaandClaude Opus 5.5 d87f6fa4c2 test(runner-e2e): add staging-soak reliability cases to everyday workflows (#19455)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The paid runner E2E suite (`tests/runner-e2e`) drives the real
browser, server, native runner, provider and Daytona
> - A manual staging soak found about nine reliability bugs (S47–S55,
H2, U9). Each fix got unit or integration tests
> - The end-to-end journeys that exposed the bugs existed only as ad-hoc
browser scripts, so a regression could return without a signal
> - These journeys are expensive, so they must not run per PR or in
`--all`. They must run on demand before a release
> - This pull request adds the journeys to the existing
`everyday-workflows` suite as an explicit `reliability` group
> - The benefit is one command that runs the whole reliability set
before a release or after a risky runner change

## Linked Issues or Issue Description

**What is the problem?**
The staging-soak scenarios that found S47–S55, H2 and U9 are not
repeatable. They were ad-hoc browser scripts on staging.

**Why does it matter?**
Without a repeatable end-to-end case, a reliability regression (stalled
follow-ups, duplicate execution, dead Retry, hangs after silent
commands) can return without a signal.

**What is the proposed solution?**
Add the scenarios to the paid `everyday-workflows` suite as
explicit-only cases with independent graders and a selectable
`reliability` group.

Refs #19370, #19355, #19354, #19407, #19419, #19394, #19421, #19431 (the
related fixes).

## What Changed

- `everyday-reliability-cases.ts`: seven new cases
(`interrupt-redirect`, `reassign-midrun`, `three-turn-revise`,
`long-silent-command`, `tree-pause-release`, `queue-steer`,
`queued-across-restart`), their soak signatures, prompts, bounds and the
explicit matrix.
- `everyday-reliability-flow.ts`: browser and public-API drivers for
each journey. A shared watcher records `executionBlocker`,
`interruption` and execution projections while it waits. It uses an
offered Retry at most once (never while an automatic retry is
scheduled).
- `everyday-reliability.ts`: pure graders. They encode the product rule:
interrupted work resumes automatically or offers a Retry that works on
first use. A send-a-message blocker, a dead or repeated Retry, a silent
stall, or duplicate execution fails.
- `types.ts` / `selectors.ts`: new optional `explicitGroup` on a task.
Such a task is selected only by its group, `--case`, or an exact `--id`.
`--all`, the plain suite and generic selectors skip it. `--list` still
shows it.
- `catalog.ts`: the suite gets the reliability tasks, the released
Grok/Kimi/Pi facades (only for `long-silent-command`), a per-turn
Daytona override for `three-turn-revise`, the `reliability` and `slow`
groups, definition version 11, and a computed `expectedMatrixSize` (52 →
62).
- `everyday-flow.ts`: a narrow hook calls the scenario driver after task
creation. Riley is created for the reassign and tree cases. The
reliability checks are added after settlement.
- `everyday-observations.ts`: an allowed interrupted run may end
`operator_interrupted` or `issue_reassigned`. Lifecycle checks accept a
declared reassigned parent owner.
- `live-fixtures.ts`: reliability cells get the existing 1,000-cent
company and lead-agent hard stop.
- Tests: new `everyday-reliability.test.ts` (selection exclusion,
matrix, credentials, and calibrated pass/fail cases for every grader).
Updated counts in `catalog.test.ts`, `everyday.test.ts` and
`installed-cli.test.ts`.
- Docs: new "Staging-soak reliability group" section in
`EVERYDAY-WORKFLOWS.md` (cases, signatures, run commands, cadence,
coverage boundary) and a link from `doc/evals.md`.

`everyday-flow.ts` was already over 1,000 lines. This PR adds only about
20 lines of wiring there. The scenario logic is in the new modules.

Matrix (10 cells, all Daytona, single attempt): one `runner-codex`
(gpt-5.6-sol) cell per case, plus `runner-acpx-grok`, `runner-acpx-kimi`
and `runner-acpx-pi` for `long-silent-command`.

Run command:

```sh
pnpm test:e2e:runner -- --suite everyday-workflows --group reliability --max-parallel 2
```

## Verification

- `pnpm test:e2e:runner:typecheck` passes.
- `node node_modules/vitest/vitest.mjs run --config
tests/runner-e2e/vitest.config.ts everyday catalog installed-cli
selectors support.test`: 15 files, 362 tests pass.
- The full runner-e2e Vitest run has the same 14 failures with and
without this change (`controller-outage`, `operating-receipts`,
`pi-controls-evidence`, `pi-file-evidence`, `workflow-security`). These
fail on the base commit in this local environment too.
- `pnpm test:e2e:runner -- --list --suite everyday-workflows --group
reliability` lists exactly the ten cells.
- `--all`, `--suite everyday-workflows` (still 52 cells),
profile/environment selectors and the daily/weekly scheduled selections
contain no reliability cell. A test asserts this.
- Not run: the paid cells. They need provider and Daytona credentials.
The first paid run of the group is the acceptance step.

## Risks

- The drivers have not run against a live stack. A first paid run can
expose harness timing or selector issues (for example, how fast the
queued Interrupt control appears). The failure then names the stalled
step and keeps `reliability-observations.json`.
- `long-silent-command` does not inject a runner connection loss. The
named-cause contract is checked only if a loss happens naturally.
- The Grok/Kimi/Pi facades run as ordinary released profiles. If one
returns to pending qualification, it drops out of the group and the
matrix size follows.
- No product code changes. Existing everyday cells keep their selection
and checks. The suite definition hash changes because the suite
definition changed.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edit, code execution). Standard context window.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 08:37:22 -06:00

254 lines
20 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildMatrixJobs, parseRunnerSelectors, selectRunnerExecutions } from "./selectors.js";
import { everydayReliabilityFacadeProfileIds, runnerMatrix } from "./catalog.js";
import { scheduledSelection } from "./scheduled-operations.js";
import {
EVERYDAY_RELIABILITY_GROUP, everydayReliabilityCaseIds, everydayReliabilityExecutionIds, everydayReliabilitySignatures,
everydayReliabilityTasks, isEverydayReliabilityCase,
} from "./everyday-reliability-cases.js";
import {
completeStepLines, concurrentExecutors, recoveredRunIds, gradeInterruptRedirect, gradeLongSilentCommand, gradeQueueSteer,
gradeQueuedAcrossRestart, gradeReassignMidrun, gradeResumptionRule, gradeThreeTurnRevise, gradeTreePauseRelease,
requiresUserMessage,
} from "./everyday-reliability.js";
import { isExpectedStoryInterruption, storyLifecycleChecks, type StoryCheck, type StoryRun } from "./everyday-observations.js";
const T0 = Date.parse("2026-11-06T10:00:00.000Z");
const at = (seconds: number) => new Date(T0 + seconds * 1000).toISOString();
const run = (id: string, overrides: Partial<StoryRun> = {}): StoryRun => ({
id, companyId: "company", agentId: "lead", status: "succeeded", runtimeMode: "native", runnerInstanceId: "runner",
nativeIssueId: "parent", startedAt: at(0), finishedAt: at(60), ...overrides,
});
/** A run woken to deliver `wake`; its prompt holds `history` (the whole conversation so far). */
const carrying = (id: string, wake: string, overrides: Partial<StoryRun> = {}, history = "") =>
run(id, { contextSnapshot: { issueId: "parent", wakeCommentIds: [wake] },
runnerProfileJson: { nativeExecutionInput: { task: { prompt: `${history} ${wake}` } } }, ...overrides });
const reliabilityCellCount = everydayReliabilityCaseIds.length + everydayReliabilityFacadeProfileIds.length;
const failed = (checks: StoryCheck[]) => checks.filter(check => !check.passed).map(check => check.id);
describe("reliability selection stays explicit", () => {
const reliable = (executions: { groups: readonly string[] }[]) => executions.filter(execution => execution.groups.includes(EVERYDAY_RELIABILITY_GROUP));
it("selects the whole reliability set only through its group", () => {
const selected = selectRunnerExecutions(parseRunnerSelectors(["--suite", "everyday-workflows", "--group", "reliability"]));
expect(selected.map(execution => execution.id).sort())
.toEqual(everydayReliabilityExecutionIds("everyday-workflows", everydayReliabilityFacadeProfileIds).sort());
expect(selected).toHaveLength(reliabilityCellCount);
expect(selected.every(execution => execution.environment.id === "daytona")).toBe(true);
});
it("is excluded from --all, the plain suite, generic selectors and scheduled campaigns", () => {
for (const args of [["--all"], ["--suite", "everyday-workflows"], ["--suite", "everyday-workflows", "--environment", "daytona"],
["--suite", "everyday-workflows", "--profile", "runner-codex"], ["--suite", "everyday-workflows", "--group", "native"],
["--profile", "runner-codex"], ["--environment", "daytona"]]) {
expect(reliable(selectRunnerExecutions(parseRunnerSelectors(args))), args.join(" ")).toEqual([]);
}
expect(selectRunnerExecutions(parseRunnerSelectors(["--suite", "everyday-workflows"]))).toHaveLength(52);
for (const cadence of ["daily", "weekly"] as const)
expect(scheduledSelection(cadence).filter(id => id.startsWith("everyday-workflows."))).toEqual([]);
});
it("allows a single case or exact ID and keeps the cells discoverable in --list", () => {
expect(selectRunnerExecutions(parseRunnerSelectors(["--suite", "everyday-workflows", "--case", "long-silent-command"]))
.map(execution => execution.profile.id).sort()).toEqual(["runner-codex", ...everydayReliabilityFacadeProfileIds].sort());
expect(selectRunnerExecutions(parseRunnerSelectors(["--id", "everyday-workflows.runner-codex.daytona.queue-steer"]))).toHaveLength(1);
expect(reliable(selectRunnerExecutions(parseRunnerSelectors(["--list"])))).toHaveLength(reliabilityCellCount);
expect(selectRunnerExecutions(parseRunnerSelectors(["--suite", "everyday-workflows", "--group", "slow"]))
.every(execution => execution.task.id === "long-silent-command")).toBe(true);
});
it("uses one native Daytona profile per case and facades only for the silent command", () => {
const cells = runnerMatrix.filter(execution => isEverydayReliabilityCase(execution.task.id));
expect(cells).toHaveLength(reliabilityCellCount);
for (const caseId of everydayReliabilityCaseIds) {
const profiles = cells.filter(execution => execution.task.id === caseId).map(execution => execution.profile.id);
expect(profiles).toContain("runner-codex");
expect(profiles.length).toBe(caseId === "long-silent-command" ? 1 + everydayReliabilityFacadeProfileIds.length : 1);
}
expect(runnerMatrix.filter(execution => execution.suite.id === "everyday-workflows"
&& ["runner-acpx-grok", "runner-acpx-kimi", "runner-acpx-pi"].includes(execution.profile.id)
&& execution.task.id !== "long-silent-command")).toEqual([]);
const turnThree = cells.find(execution => execution.task.id === "three-turn-revise")!;
expect(turnThree.environment.configurationKey).toBeUndefined(); // Per-turn ephemeral lease, as in S49.
expect(cells.find(execution => execution.task.id === "long-silent-command")!.groups).toContain("slow");
});
it("gives the slow case its own deadline and single attempts", () => {
const silent = everydayReliabilityTasks.find(task => task.id === "long-silent-command")!;
expect(silent.attemptTimeoutMs.daytona).toBeGreaterThanOrEqual(45 * 60_000);
expect(everydayReliabilityTasks.every(task => task.automaticRetryPolicy === "single_attempt")).toBe(true);
const [job] = buildMatrixJobs(runnerMatrix.filter(execution => execution.id === "everyday-workflows.runner-codex.daytona.long-silent-command"));
expect(job!.timeoutMinutes).toBeGreaterThan(100);
});
it("requires the facade credentials before provider spend and records each soak signature", () => {
const credentials = (profile: string) => runnerMatrix.find(execution =>
execution.id === `everyday-workflows.${profile}.daytona.long-silent-command`)!.requiredCredentials;
const facadeCredentials: Record<string, string> = { "runner-acpx-grok": "XAI_API_KEY", "runner-acpx-kimi": "KIMI_API_KEY", "runner-acpx-pi": "OPENROUTER_API_KEY" };
for (const profile of everydayReliabilityFacadeProfileIds)
expect(credentials(profile)).toEqual([facadeCredentials[profile], "DAYTONA_API_KEY"]);
expect(Object.keys(everydayReliabilitySignatures).sort()).toEqual([...everydayReliabilityCaseIds].sort());
for (const task of everydayReliabilityTasks)
expect(task.buildPrompt("sample")).not.toMatch(/finish_task|paperclip_finish|PATCH|mark .*done|api\/issues/i);
});
});
describe("reliability interruption bookkeeping", () => {
it("exempts only deliberate interrupt and reassignment stops", () => {
expect(isExpectedStoryInterruption(run("a", { status: "cancelled", errorCode: "operator_interrupted" }), ["a"])).toBe(true);
expect(isExpectedStoryInterruption(run("a", { status: "cancelled", errorCode: "issue_reassigned" }), ["a"])).toBe(true);
expect(isExpectedStoryInterruption(run("a", { status: "cancelled", errorCode: "issue_reassigned" }), [])).toBe(false);
expect(isExpectedStoryInterruption(run("a", { status: "cancelled", errorCode: "workspace_lost" }), ["a"])).toBe(false);
});
it("lets a reassigned owner execute on the parent only when declared", () => {
const input = { issues: [{ id: "parent", companyId: "company", title: "t", status: "done" }], parentId: "parent", leadId: "lead",
runs: [run("a", { agentId: "riley", contextSnapshot: { issueId: "parent" } })] };
expect(failed(storyLifecycleChecks(input))).toContain("parent-owned-by-lead");
expect(failed(storyLifecycleChecks({ ...input, parentAgentIds: ["lead", "riley"] }))).toEqual([]);
});
});
describe("product rule: resume automatically or offer a Retry that works", () => {
it("treats a failure as recovered only when a later run on the same task succeeds", () => {
const lost = run("lost", { status: "failed", errorCode: "runner_lost", finishedAt: at(60) });
expect(recoveredRunIds([lost, run("retry", { startedAt: at(70), finishedAt: at(120) })])).toEqual(["lost"]);
expect(recoveredRunIds([lost])).toEqual([]);
expect(recoveredRunIds([lost, run("other", { nativeIssueId: "child", startedAt: at(70) })])).toEqual([]);
const lifecycle = { issues: [{ id: "parent", companyId: "company", title: "t", status: "done" }], parentId: "parent", leadId: "lead",
runs: [lost, run("retry", { startedAt: at(70), finishedAt: at(120) })] };
expect(failed(storyLifecycleChecks(lifecycle))).toEqual(["successful-runs"]);
expect(failed(storyLifecycleChecks({ ...lifecycle, recoveredRuns: ["lost"] }))).toEqual([]);
});
it("passes automatic resumption", () => expect(failed(gradeResumptionRule({ observations: [], retryClicks: [] }))).toEqual([]));
it("fails a send-a-message blocker, a dead Retry, a repeated Retry, and a stall", () => {
const blocker = { at: at(1), issueId: "parent", blocker: { cause: "x", nextAction: "Saved work recovered. Send a message to continue.", canRetry: false } };
expect(requiresUserMessage(blocker)).toBe(true);
expect(requiresUserMessage({ ...blocker, blocker: { ...blocker.blocker, canRetry: true } })).toBe(false);
expect(requiresUserMessage({ at: at(1), issueId: "parent", interruption: { state: "waiting_for_user", canRetry: false, userAction: null } })).toBe(true);
expect(failed(gradeResumptionRule({ observations: [blocker], retryClicks: [] }))).toEqual(["resume.no-send-message-blocker"]);
expect(failed(gradeResumptionRule({ observations: [], retryClicks: [{ at: at(1), issueId: "parent" }] }))).toEqual(["resume.retry-works-first-use"]);
expect(failed(gradeResumptionRule({ observations: [], retryClicks: [
{ at: at(1), issueId: "parent", startedRunId: "r1" }, { at: at(2), issueId: "parent", startedRunId: "r2" }] }))).toEqual(["resume.retry-works-first-use"]);
expect(failed(gradeResumptionRule({ observations: [], retryClicks: [], stalled: "Stalled" }))).toEqual(["resume.no-silent-stall"]);
});
});
describe("scenario graders", () => {
it("detects concurrent executors and exact step files", () => {
expect(concurrentExecutors([run("a", { finishedAt: at(60) }), run("b", { startedAt: at(61), finishedAt: at(90) })], "parent")).toEqual([]);
expect(concurrentExecutors([run("a", { finishedAt: at(60) }), run("b", { startedAt: at(30) })], "parent")).toEqual([["a", "b"]]);
expect(completeStepLines("step-1\nstep-2\n", 2)).toBe(true);
expect(completeStepLines("step-1\nstep-1\nstep-2\n", 2)).toBe(false);
expect(completeStepLines(undefined, 2)).toBe(false);
});
it("interrupt-redirect: one bounded continuation, and Stop-settlement consumption", () => {
const interrupted = run("old", { status: "cancelled", errorCode: "operator_interrupted", finishedAt: at(10) });
const good = [interrupted, carrying("next", "comment-1", { startedAt: at(40), finishedAt: at(90) }),
run("stopped", { status: "cancelled", errorCode: "cancelled", startedAt: at(100), finishedAt: at(130) }),
carrying("settle", "comment-2", { startedAt: at(150), finishedAt: at(200) })];
const input = { runs: good, comments: [], interruptedRunId: "old", interruptCommentId: "comment-1", interruptedAt: T0 + 10_000,
stopSettle: { stoppedRunId: "stopped", commentId: "comment-2", sentAt: T0 + 120_000 } };
expect(failed(gradeInterruptRedirect(input))).toEqual([]);
expect(failed(gradeInterruptRedirect({ ...input, runs: [...good, carrying("dup", "comment-1", { startedAt: at(300) })] })))
.toContain("interrupt.exactly-one-continuation");
// Later turns keep the interrupt message in their prompt history; that is not a second delivery.
expect(failed(gradeInterruptRedirect({ ...input, runs: [...good, run("later", { startedAt: at(300),
runnerProfileJson: { nativeExecutionInput: { task: { prompt: "comment-1 comment-2 history" } } } })] }))).toEqual([]);
expect(failed(gradeInterruptRedirect({ ...input, runs: [interrupted, carrying("late", "comment-1", { startedAt: at(250) }), ...good.slice(2)] })))
.toEqual(["interrupt.continuation-bounded"]);
expect(failed(gradeInterruptRedirect({ ...input, comments: [{ id: "nudge", createdAt: at(20) }] }))).toEqual(["interrupt.no-second-message"]);
expect(failed(gradeInterruptRedirect({ ...input, runs: [{ ...interrupted, errorCode: "cancelled" }, ...good.slice(1)] })))
.toEqual(["interrupt.operator-interrupt"]);
});
it("reassign-midrun: prompt request, old run reassigned, no overlap, saved files continued", () => {
const steps = Array.from({ length: 8 }, (_, index) => `step-${index + 1}`).join("\n");
const input = { issueId: "parent", oldRunId: "old", newAgentId: "riley", steps: 8, stepFile: steps as string | undefined,
request: { status: 200, latencyMs: 800, requestedAt: T0 + 30_000 },
runs: [run("old", { status: "cancelled", errorCode: "issue_reassigned", finishedAt: at(32) }),
run("new", { agentId: "riley", startedAt: at(40), finishedAt: at(200) })] };
expect(failed(gradeReassignMidrun(input))).toEqual([]);
expect(failed(gradeReassignMidrun({ ...input, request: { status: 409, latencyMs: 20_000, requestedAt: T0 } }))).toContain("reassign.request-prompt");
expect(failed(gradeReassignMidrun({ ...input, runs: [run("old", { status: "cancelled", errorCode: "issue_reassigned", finishedAt: at(120) }), input.runs[1]!] })))
.toEqual(["reassign.no-concurrent-executors"]);
expect(failed(gradeReassignMidrun({ ...input, stepFile: `${steps}\nstep-3` }))).toEqual(["reassign.continued-from-saved-files"]);
});
it("three-turn-revise: every turn succeeds and the table is complete", () => {
const nonce = "N1";
const table = `| Number | Item |\n| --- | --- |\n${["alpha", "beta", "gamma", "delta", "epsilon"].map((word, index) => `| ${index + 1} | ${word} ${nonce} |`).join("\n")}\n`;
const turns = [run("t1"), run("t2"), run("t3")];
expect(failed(gradeThreeTurnRevise({ turnRuns: turns, content: table, nonce }))).toEqual([]);
expect(failed(gradeThreeTurnRevise({ turnRuns: [turns[0], turns[1], undefined], content: table, nonce }))).toEqual(["three-turn.turn-3-succeeded"]);
expect(failed(gradeThreeTurnRevise({ turnRuns: turns, content: `${table}- alpha ${nonce}\n`, nonce }))).toEqual(["three-turn.final-table"]);
expect(failed(gradeThreeTurnRevise({ turnRuns: turns, content: table.replace(`epsilon ${nonce}`, "missing"), nonce }))).toEqual(["three-turn.final-table"]);
});
it("long-silent-command: completes after the command, Stop stays available, no hang", () => {
const content = Array.from({ length: 13 }, (_, index) => `tick-${index + 1}`).join("\n");
const samples = [{ at: at(60), canStop: true, source: "api" as const }, { at: at(61), canStop: true, source: "browser" as const },
{ at: at(400), canStop: true, source: "api" as const }];
const input = { run: run("silent", { startedAt: at(0), finishedAt: at(13 * 60 + 90) }), content, iterations: 13, iterationSeconds: 60,
stopSamples: samples, reply: "SILENT_DONE_N1 13", nonce: "N1" };
expect(failed(gradeLongSilentCommand(input))).toEqual([]);
expect(failed(gradeLongSilentCommand({ ...input, run: run("silent", { status: "failed", errorCode: null, startedAt: at(0), finishedAt: at(13 * 60 + 60 * 30) }) })))
.toEqual(expect.arrayContaining(["silent.completed", "silent.no-hang", "silent.named-failure"]));
expect(failed(gradeLongSilentCommand({ ...input, run: run("silent", { startedAt: at(0), finishedAt: at(120) }) }))).toEqual(["silent.not-shortened"]);
expect(failed(gradeLongSilentCommand({ ...input, stopSamples: [...samples, { at: at(500), canStop: false, source: "api" }] }))).toEqual(["silent.stop-available"]);
});
it("tree-pause-release: no start during hold and exactly one resume per interrupted task", () => {
const input = { childIds: ["a", "b"], holdCreatedAt: T0 + 100_000, releasedAt: T0 + 200_000,
interruptedRuns: [{ id: "ra", issueId: "a" }, { id: "rb", issueId: "b" }],
runs: [run("ra", { nativeIssueId: "a", status: "cancelled", finishedAt: at(101) }),
run("rb", { nativeIssueId: "b", status: "cancelled", startedAt: null, finishedAt: at(101) }),
run("ra2", { nativeIssueId: "a", startedAt: at(210), contextSnapshot: { wakeReason: "issue_tree_resumed" } }),
run("rb2", { nativeIssueId: "b", startedAt: at(260), contextSnapshot: { wakeReason: "issue_tree_resumed" } })] };
expect(failed(gradeTreePauseRelease(input))).toEqual([]);
expect(failed(gradeTreePauseRelease({ ...input, runs: [...input.runs, run("early", { nativeIssueId: "b", startedAt: at(150) })] })))
.toEqual(["tree.no-start-during-hold"]);
expect(failed(gradeTreePauseRelease({ ...input, runs: [...input.runs, run("rb3", { nativeIssueId: "b", startedAt: at(300), contextSnapshot: { wakeReason: "issue_tree_resumed" } })] })))
.toEqual(["tree.one-resume-per-interrupted"]);
expect(failed(gradeTreePauseRelease({ ...input, runs: input.runs.slice(0, 3) }))).toEqual(["tree.one-resume-per-interrupted"]);
expect(failed(gradeTreePauseRelease({ ...input, interruptedRuns: [] }))).toContain("tree.hold-interrupted-work");
// A run that started while the hold request was in flight was interrupted by it, not started during it.
expect(failed(gradeTreePauseRelease({ ...input, runs: input.runs.map(entry => entry.id === "ra" ? { ...entry, startedAt: at(100.5) } : entry) }))).toEqual([]);
});
it("queue-steer: edit used, discard never delivered, steer consumed once in the active turn", () => {
const active = run("active", { resultJson: { queuedSteeringAcknowledgements: { steer: { status: "acknowledged" } } } });
const input = { activeRunId: "active", runs: [active, carrying("next", "edit", {}, "EDITED_N1 history: steer drop")],
edited: { commentId: "edit", originalMarker: "ORIGINAL_N1", editedMarker: "EDITED_N1" },
discarded: { commentId: "drop", marker: "DISCARDED_N1" }, steered: { commentId: "steer", marker: "STEERED_N1" },
steerAttempts: [{ status: 409, code: "steering_temporarily_unavailable" }, { status: 200 }],
files: { edited: "EDITED_N1", steered: "STEERED_N1" } };
expect(failed(gradeQueueSteer(input))).toEqual([]);
expect(failed(gradeQueueSteer({ ...input, runs: [active, carrying("next", "edit", {}, "EDITED_N1 DISCARDED_N1")] })))
.toEqual(["queue.discard-never-delivered"]);
expect(failed(gradeQueueSteer({ ...input, runs: [active, carrying("next", "edit", {}, "ORIGINAL_N1")], files: { ...input.files, edited: "ORIGINAL_N1" } })))
.toEqual(["queue.edit-used"]);
expect(failed(gradeQueueSteer({ ...input, runs: [...input.runs, carrying("replay", "steer", { startedAt: at(200) }, "EDITED_N1")] })))
.toEqual(["queue.steer-consumed-in-active-run"]);
expect(failed(gradeQueueSteer({ ...input, steerAttempts: [{ status: 409, code: "queued_comment_stale_target" }, { status: 200 }] })))
.toEqual(["queue.steer-retry-bounded"]);
expect(failed(gradeQueueSteer({ ...input, runs: [...input.runs, carrying("again", "edit", { startedAt: at(300) }, "EDITED_N1")] })))
.toEqual(["queue.remaining-handled-once"]);
});
it("queued-across-restart: bounded automatic dispatch with a named wait", () => {
const input = { commentId: "c1", restartStartedAt: T0 + 60_000, dispatchedBeforeRestart: false,
runs: [carrying("next", "c1", { startedAt: at(120), finishedAt: at(200) })],
waitObservations: [{ at: at(65), phase: "queued", label: "Waiting for server restart", cause: "server_restarting" }] };
expect(failed(gradeQueuedAcrossRestart(input))).toEqual([]);
expect(failed(gradeQueuedAcrossRestart({ ...input, waitObservations: [{ at: at(65), phase: "queued", label: "Queued", cause: null }] })))
.toEqual(["restart.named-wait"]);
expect(failed(gradeQueuedAcrossRestart({ ...input, runs: [] }))).toEqual(["restart.dispatched-bounded", "restart.exactly-once"]);
expect(failed(gradeQueuedAcrossRestart({ ...input, runs: [carrying("next", "c1", { startedAt: at(60 + 7 * 60) })] })))
.toEqual(["restart.dispatched-bounded"]);
expect(failed(gradeQueuedAcrossRestart({ ...input, runs: [carrying("early", "c1", { startedAt: at(30) })] })))
.toEqual(["restart.boundary-exercised"]);
expect(failed(gradeQueuedAcrossRestart({ ...input, dispatchedBeforeRestart: true }))).toEqual(["restart.boundary-exercised"]);
});
});