Files
PaperClipAI/tests/runner-e2e/everyday-reliability.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

327 lines
21 KiB
TypeScript

import { RELIABILITY_BOUNDS } from "./everyday-reliability-cases.js";
import { isStoryWorkspaceDeferral, type StoryCheck, type StoryComment, type StoryRun } from "./everyday-observations.js";
/**
* Pure graders for the staging-soak reliability scenarios. Product rule:
* work interrupted by a restart, Stop, reassignment or lost runner resumes
* automatically, or offers a Retry that works on first use. "Send a message
* to continue", a dead Retry, a silent stall and duplicate execution all fail.
*/
const check = (id: string, passed: boolean, detail: string): StoryCheck => ({ id, passed, detail });
const ms = (value: string | null | undefined) => Date.parse(value ?? "");
/** Clock granularity between separately written run timestamps. */
const OVERLAP_TOLERANCE_MS = 2_000;
export function runIssueId(run: StoryRun): string | undefined {
const context = run.contextSnapshot ?? {};
return run.nativeIssueId ?? (typeof context.issueId === "string" ? context.issueId
: typeof context.taskId === "string" ? context.taskId : undefined);
}
/** Serialized native execution input; a delivered message appears here by ID and body. */
export function runNativeInput(run: StoryRun): string {
return JSON.stringify(run.runnerProfileJson?.nativeExecutionInput ?? null);
}
/** Runs that reached provider execution (workspace deferrals never did). */
export function executedRuns(runs: readonly StoryRun[]): StoryRun[] {
return runs.filter(run => !isStoryWorkspaceDeferral(run) && Boolean(run.startedAt));
}
/**
* Comments this run was woken to deliver. The saved prompt also holds the whole
* conversation history, so it cannot attribute delivery; the wake context can.
*/
export function runWakeCommentIds(run: StoryRun): string[] {
const context = run.contextSnapshot ?? {};
const ids = Array.isArray(context.wakeCommentIds) ? context.wakeCommentIds : [];
return [...new Set([...ids, context.wakeCommentId, context.commentId].filter((id): id is string => typeof id === "string"))];
}
/** Executed runs woken to deliver this comment (a Retry of a failed delivery inherits it). */
export function runsCarrying(runs: readonly StoryRun[], commentId: string): StoryRun[] {
return executedRuns(runs).filter(run => runWakeCommentIds(run).includes(commentId));
}
/** Successful deliveries of one comment; more than one is duplicate execution. */
export const successfulDeliveries = (runs: readonly StoryRun[], commentId: string) =>
runsCarrying(runs, commentId).filter(run => run.status === "succeeded");
/**
* Failed or interrupted runs whose task a later run completed successfully: the
* product rule's "resumed automatically or through a working Retry". The
* resumption checks separately fail send-a-message blockers and dead Retries.
*/
export function recoveredRunIds(runs: readonly StoryRun[]): string[] {
return executedRuns(runs).filter(run => ["failed", "timed_out", "cancelled", "interrupted"].includes(run.status)
&& runs.some(later => later.status === "succeeded" && runIssueId(later) === runIssueId(run)
&& ms(later.startedAt) >= ms(run.finishedAt))).map(run => run.id);
}
const earliest = (runs: readonly StoryRun[]) => [...runs].sort((a, b) => ms(a.startedAt) - ms(b.startedAt))[0];
/** Pairs of runs that executed on the same task at the same time. */
export function concurrentExecutors(runs: readonly StoryRun[], issueId: string, now = Date.now()): Array<[string, string]> {
const spans = executedRuns(runs).filter(run => runIssueId(run) === issueId)
.map(run => ({ id: run.id, start: ms(run.startedAt), end: Number.isFinite(ms(run.finishedAt)) ? ms(run.finishedAt) : now }))
.sort((left, right) => left.start - right.start);
const pairs: Array<[string, string]> = [];
for (let index = 0; index < spans.length; index += 1)
for (let other = index + 1; other < spans.length; other += 1)
if (spans[other]!.start + OVERLAP_TOLERANCE_MS < spans[index]!.end) pairs.push([spans[index]!.id, spans[other]!.id]);
return pairs;
}
/** Exact "step-1".."step-N" lines, each once and in order. */
export function completeStepLines(content: string | undefined, steps: number, prefix = "step"): boolean {
if (content === undefined) return false;
const lines = content.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
return lines.length === steps && lines.every((line, index) => line === `${prefix}-${index + 1}`);
}
export interface ResumptionObservation {
at: string;
issueId: string;
blocker?: { cause?: string | null; nextAction?: string | null; canRetry?: boolean; canContinue?: boolean } | null;
interruption?: { state?: string; canRetry?: boolean; userAction?: string | null; responsibility?: string } | null;
execution?: { phase?: string; label?: string; cause?: string | null } | null;
}
export interface RetryClick { at: string; issueId: string; startedRunId?: string }
/** The only way forward is another user message: the S47/S49 failure mode. */
export function requiresUserMessage(observation: ResumptionObservation): boolean {
const blocker = observation.blocker;
if (blocker && !blocker.canRetry && /send (?:a |another |a new )?(?:new )?(?:chat )?message/i.test(blocker.nextAction ?? ""))
return true;
const interruption = observation.interruption;
return Boolean(interruption && interruption.state === "waiting_for_user" && !interruption.canRetry && !interruption.userAction);
}
export function gradeResumptionRule(input: {
observations: readonly ResumptionObservation[];
retryClicks: readonly RetryClick[];
stalled?: string;
}): StoryCheck[] {
const blocked = input.observations.find(requiresUserMessage);
const dead = input.retryClicks.filter(click => !click.startedRunId);
const repeated = new Set(input.retryClicks.map(click => click.issueId)).size !== input.retryClicks.length;
return [
check("resume.no-send-message-blocker", !blocked,
blocked ? `Blocked at ${blocked.at}: ${blocked.blocker?.nextAction ?? blocked.interruption?.state}` : "Interrupted work never required another user message to continue."),
check("resume.retry-works-first-use", dead.length === 0 && !repeated,
input.retryClicks.length === 0 ? "No Retry was needed; work resumed automatically."
: dead.length ? `Retry clicked without starting a run at ${dead.map(click => click.at).join(", ")}.`
: repeated ? "Retry had to be used more than once for the same task." : "Each offered Retry started work on first use."),
check("resume.no-silent-stall", !input.stalled, input.stalled ?? "Work resumed or finished within the scenario bound."),
];
}
const userCommentsBetween = (comments: readonly StoryComment[], from: number, to: number, except: readonly string[]) =>
comments.filter(comment => !comment.authorAgentId && !except.includes(comment.id ?? "")
&& ms(comment.createdAt) > from && ms(comment.createdAt) < to);
export function gradeInterruptRedirect(input: {
runs: readonly StoryRun[];
comments: readonly StoryComment[];
interruptedRunId: string;
interruptCommentId: string;
interruptedAt: number;
stopSettle?: { stoppedRunId: string; commentId: string; sentAt: number };
}): StoryCheck[] {
const interrupted = input.runs.find(run => run.id === input.interruptedRunId);
const carriers = successfulDeliveries(input.runs, input.interruptCommentId);
const continuation = earliest(runsCarrying(input.runs, input.interruptCommentId));
const delay = ms(continuation?.startedAt) - input.interruptedAt;
const extra = continuation ? userCommentsBetween(input.comments, input.interruptedAt, ms(continuation.startedAt), [input.interruptCommentId]) : [];
const checks = [
check("interrupt.operator-interrupt", interrupted?.status === "cancelled"
&& (interrupted.errorCode === "operator_interrupted" || interrupted.resultJson?.operatorInterrupted === true),
`The interrupted run ends as an operator interrupt (observed ${interrupted?.status ?? "missing"}/${interrupted?.errorCode ?? "none"}).`),
check("interrupt.exactly-one-continuation", carriers.length === 1,
`Exactly one successful continuation delivers the interrupt message (observed ${carriers.length}).`),
check("interrupt.continuation-bounded", Number.isFinite(delay) && delay <= RELIABILITY_BOUNDS.continuationStartMs,
`The continuation starts within ${RELIABILITY_BOUNDS.continuationStartMs / 1000}s of the interrupt (observed ${Number.isFinite(delay) ? Math.round(delay / 1000) + "s" : "never"}).`),
check("interrupt.no-second-message", extra.length === 0, "No second user message was needed before the continuation started."),
];
if (input.stopSettle) {
const stopped = input.runs.find(run => run.id === input.stopSettle!.stoppedRunId);
const settleCarriers = successfulDeliveries(input.runs, input.stopSettle.commentId);
const settleDelay = ms(earliest(runsCarrying(input.runs, input.stopSettle.commentId))?.startedAt) - input.stopSettle.sentAt;
checks.push(
check("stop-settle.stopped", stopped?.status === "cancelled", "The stopped run is terminal as cancelled."),
check("stop-settle.consumed-once", settleCarriers.length === 1,
`A message sent while Stop was settling is consumed by exactly one successful run (observed ${settleCarriers.length}).`),
check("stop-settle.bounded", Number.isFinite(settleDelay) && settleDelay <= RELIABILITY_BOUNDS.continuationStartMs,
`The workspace_busy retry dispatches within ${RELIABILITY_BOUNDS.continuationStartMs / 1000}s (observed ${Number.isFinite(settleDelay) ? Math.round(settleDelay / 1000) + "s" : "never"}).`),
);
}
return checks;
}
export function gradeReassignMidrun(input: {
runs: readonly StoryRun[];
issueId: string;
oldRunId: string;
newAgentId: string;
request: { status: number; latencyMs: number; requestedAt: number };
stepFile?: string;
steps: number;
}): StoryCheck[] {
const old = input.runs.find(run => run.id === input.oldRunId);
// A failed first attempt followed by a working Retry is allowed: require any success.
const successor = executedRuns(input.runs).find(run => run.agentId === input.newAgentId && runIssueId(run) === input.issueId
&& ms(run.startedAt) >= input.request.requestedAt && run.status === "succeeded");
const overlaps = concurrentExecutors(input.runs, input.issueId);
return [
check("reassign.request-prompt", input.request.status >= 200 && input.request.status < 300
&& input.request.latencyMs <= RELIABILITY_BOUNDS.reassignRequestMs,
`Reassignment returns ${input.request.status} in ${input.request.latencyMs}ms (bound ${RELIABILITY_BOUNDS.reassignRequestMs}ms, no 409).`),
check("reassign.old-run-ended", old?.status === "cancelled" && old.errorCode === "issue_reassigned",
`The original run ends issue_reassigned (observed ${old?.status ?? "missing"}/${old?.errorCode ?? "none"}).`),
check("reassign.new-assignee-finished", successor?.status === "succeeded", "The new assignee's run starts after the request and succeeds."),
check("reassign.no-concurrent-executors", overlaps.length === 0,
overlaps.length ? `Concurrent executions: ${overlaps.map(pair => pair.join("+")).join(", ")}.` : "Never two executions on the task at once."),
check("reassign.continued-from-saved-files", completeStepLines(input.stepFile, input.steps),
`The saved step file holds step-1..step-${input.steps} exactly once, so work continued from saved files.`),
];
}
export function gradeThreeTurnRevise(input: {
turnRuns: ReadonlyArray<StoryRun | undefined>;
content?: string;
nonce: string;
}): StoryCheck[] {
const items = ["alpha", "beta", "gamma", "delta", "epsilon"].map(word => `${word} ${input.nonce}`);
const rows = (input.content ?? "").split(/\r?\n/).map(line => line.trim()).filter(line => line.startsWith("|"));
const header = rows[0]?.toLowerCase() ?? "";
const body = rows.slice(2);
const table = /number/.test(header) && /item/.test(header) && /^\|[\s:|-]+\|$/.test(rows[1] ?? "")
&& body.length === items.length && body.every((row, index) => row.includes(items[index]!));
const bullets = /^\s*[-*]\s+/m.test(input.content ?? "");
return [
...input.turnRuns.map((run, index) => check(`three-turn.turn-${index + 1}-succeeded`, run?.status === "succeeded",
`Turn ${index + 1} completes in a successful run without a reconciliation stop (observed ${run?.status ?? "no run"}).`)),
check("three-turn.final-table", table && !bullets, "items.md is a Number/Item table with all five items in order and no bullet list."),
];
}
export function gradeLongSilentCommand(input: {
run?: StoryRun;
content?: string;
iterations: number;
iterationSeconds: number;
stopSamples: ReadonlyArray<{ at: string; canStop: boolean; source: "api" | "browser" }>;
reply?: string;
nonce: string;
}): StoryCheck[] {
const commandMs = input.iterations * input.iterationSeconds * 1000;
const elapsed = ms(input.run?.finishedAt) - ms(input.run?.startedAt);
const named = input.run?.status === "succeeded" || Boolean(input.run?.errorCode);
return [
check("silent.completed", input.run?.status === "succeeded",
`The run completes after the silent command (observed ${input.run?.status ?? "no run"}${input.run?.errorCode ? `/${input.run.errorCode}` : ""}).`),
check("silent.not-shortened", Number.isFinite(elapsed) && elapsed >= commandMs * 0.95,
`The run lasted at least the ${input.iterations}-minute command (observed ${Number.isFinite(elapsed) ? Math.round(elapsed / 1000) + "s" : "unknown"}).`),
check("silent.no-hang", Number.isFinite(elapsed) && elapsed <= commandMs + RELIABILITY_BOUNDS.silentCompletionAfterCommandMs,
`The run is terminal within ${RELIABILITY_BOUNDS.silentCompletionAfterCommandMs / 60_000} minutes after the command ends.`),
check("silent.named-failure", named, named ? "Any failure carries a named cause." : "The run ended without a named cause."),
check("silent.file-complete", completeStepLines(input.content, input.iterations, "tick"),
`silent.txt holds tick-1..tick-${input.iterations} exactly once.`),
check("silent.stop-available", input.stopSamples.length >= 3 && input.stopSamples.every(sample => sample.canStop)
&& input.stopSamples.some(sample => sample.source === "browser"),
`Stop stays available throughout the silent interval (${input.stopSamples.filter(sample => sample.canStop).length}/${input.stopSamples.length} samples).`),
check("silent.reported", new RegExp(`SILENT_DONE_${input.nonce}\\s+${input.iterations}\\b`).test(input.reply ?? ""),
"The agent reports the completed line count after the command."),
];
}
export function gradeTreePauseRelease(input: {
runs: readonly StoryRun[];
childIds: readonly string[];
holdCreatedAt: number;
releasedAt: number;
interruptedRuns: ReadonlyArray<{ id: string; issueId: string }>;
}): StoryCheck[] {
const executed = executedRuns(input.runs);
// A run that started while the hold request was in flight is one the hold interrupted.
const interruptedIds = new Set(input.interruptedRuns.map(run => run.id));
const startedDuringHold = executed.filter(run => input.childIds.includes(runIssueId(run) ?? "") && !interruptedIds.has(run.id)
&& ms(run.startedAt) > input.holdCreatedAt && ms(run.startedAt) < input.releasedAt);
const interruptedIssues = [...new Set(input.interruptedRuns.map(run => run.issueId))];
const resumed = (issueId: string) => input.runs.filter(run => runIssueId(run) === issueId
&& run.contextSnapshot?.wakeReason === "issue_tree_resumed" && !isStoryWorkspaceDeferral(run));
const resumeCounts = interruptedIssues.map(issueId => ({ issueId, count: resumed(issueId).length }));
const stray = input.runs.filter(run => run.contextSnapshot?.wakeReason === "issue_tree_resumed"
&& !interruptedIssues.includes(runIssueId(run) ?? ""));
return [
check("tree.hold-interrupted-work", input.interruptedRuns.length > 0,
input.interruptedRuns.length ? `The hold interrupted ${input.interruptedRuns.length} run(s).` : "Boundary unexercised: no child work was active or queued when the hold was created."),
check("tree.no-start-during-hold", startedDuringHold.length === 0,
startedDuringHold.length ? `Child runs started during the hold: ${startedDuringHold.map(run => run.id).join(", ")}.` : "No child run started while the tree was paused."),
check("tree.one-resume-per-interrupted", resumeCounts.every(entry => entry.count === 1) && stray.length === 0,
`Each interrupted task gets exactly one issue_tree_resumed run (observed ${resumeCounts.map(entry => entry.count).join(", ") || "none"}; stray ${stray.length}).`),
];
}
export function gradeQueueSteer(input: {
runs: readonly StoryRun[];
activeRunId: string;
edited: { commentId: string; originalMarker: string; editedMarker: string };
discarded: { commentId: string; marker: string };
steered: { commentId: string; marker: string };
steerAttempts: ReadonlyArray<{ status: number; code?: string }>;
files: { edited?: string; discarded?: string; steered?: string };
}): StoryCheck[] {
const executed = executedRuns(input.runs);
const anyInput = (needle: string) => executed.some(run => runNativeInput(run).includes(needle));
const active = input.runs.find(run => run.id === input.activeRunId);
const acks = (active?.resultJson?.queuedSteeringAcknowledgements ?? {}) as Record<string, { status?: string }>;
const editedCarriers = successfulDeliveries(input.runs, input.edited.commentId);
const steerReplays = runsCarrying(input.runs, input.steered.commentId);
const finalAttempt = input.steerAttempts.at(-1);
const retriesValid = input.steerAttempts.slice(0, -1).every(attempt => attempt.code === "steering_temporarily_unavailable");
return [
check("queue.edit-used", anyInput(input.edited.editedMarker) && !anyInput(input.edited.originalMarker)
&& (input.files.edited ?? "").includes(input.edited.editedMarker),
"The edited text reached the agent; the original text never did."),
check("queue.remaining-handled-once", editedCarriers.length === 1,
`The remaining queued message is handled by exactly one successful run (observed ${editedCarriers.length}).`),
// History may retain a deleted comment's ID, but never its text or a delivery wake.
check("queue.discard-never-delivered", !anyInput(input.discarded.marker) && runsCarrying(input.runs, input.discarded.commentId).length === 0
&& !acks[input.discarded.commentId] && input.files.discarded === undefined,
"The discarded message never reached the agent."),
check("queue.steer-consumed-in-active-run", acks[input.steered.commentId]?.status === "acknowledged" && steerReplays.length === 0
&& (input.files.steered ?? "").includes(input.steered.marker),
"The steered message is acknowledged by the active turn, acted on there, and never replayed as a new run."),
check("queue.steer-retry-bounded", Boolean(finalAttempt && finalAttempt.status >= 200 && finalAttempt.status < 300) && retriesValid,
`Steering succeeded after ${input.steerAttempts.length} attempt(s); only steering_temporarily_unavailable was retried.`),
];
}
export function gradeQueuedAcrossRestart(input: {
runs: readonly StoryRun[];
commentId: string;
/** When the controller stop was requested; the old controller cannot dispatch after it. */
restartStartedAt: number;
/** Whether a delivery already existed when the restart was requested. */
dispatchedBeforeRestart: boolean;
waitObservations: ReadonlyArray<{ at: string; phase?: string; label?: string; cause?: string | null }>;
}): StoryCheck[] {
const carriers = successfulDeliveries(input.runs, input.commentId);
const start = ms(earliest(runsCarrying(input.runs, input.commentId))?.startedAt);
const bare = input.waitObservations.filter(observation => observation.phase === "queued"
&& (!observation.cause || observation.label === "Queued"));
return [
check("restart.boundary-exercised", !input.dispatchedBeforeRestart && (!Number.isFinite(start) || start >= input.restartStartedAt),
"The queued follow-up had not dispatched when the controller restart was requested."),
check("restart.dispatched-bounded", Number.isFinite(start) && start - input.restartStartedAt <= RELIABILITY_BOUNDS.restartDispatchMs,
`The follow-up dispatches automatically within ${RELIABILITY_BOUNDS.restartDispatchMs / 60_000} minutes of the restart (observed ${Number.isFinite(start) ? Math.round((start - input.restartStartedAt) / 1000) + "s" : "never"}).`),
check("restart.exactly-once", carriers.length === 1,
`The follow-up is consumed by exactly one successful run (observed ${carriers.length}).`),
check("restart.named-wait", bare.length === 0,
bare.length ? `Bare Queued without a cause at ${bare.map(observation => observation.at).join(", ")}.`
: `Every observed wait names its cause (${input.waitObservations.length} observations).`),
];
}