Files
devin-ai-integration[bot] 885c1486bb test(ci): single-retry policy for enumerated contention-flaky files (timeouts only) (#1448)
* test(ci): single-retry policy for enumerated contention-flaky files

* fix: satisfy fallow

* test(ci): read failures through a lane reporter so timeouts stay distinguishable

* test(ci): cover the lane reporter and drop its duplicated boilerplate

* chore(fallow): own the retry lane's tool-loaded export seams

* test(ci): block retries on non-test failures and classify timeouts structurally

* test(ci): decide retry eligibility from runner metadata and route gate verdicts through blockers

* refactor(ci): name the retry policy's rules in code instead of comments

* test(ci): mark runner-aborted timeouts inside the runner instead of inferring them

* test(ci): make timeout provenance a per-run secret, not a writable flag

Cover direct task.meta mutation in the real child-Vitest fixture gate.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(ci): retry the failed files in the first run's project and coverage modes

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: drop deleted repo-health file from the retry list after #1480

Rebase onto main post-#1480: the SkillGym/repo-health descope deleted
scripts/repo-health/run.test.ts, whose CONTENTION_RETRY_FILES entry
would now fail this PR's own missing-file check, and inlined the
slow-test budgets into the reporter, resolving the budgets-module
import. Envelope comments now point at scripts/lib/lane-envelope.ts
instead of the closed #1430.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-29 12:53:33 +02:00

70 lines
2.3 KiB
TypeScript

// Standard artifact envelope for scheduled lanes.
//
// A scheduled lane can go dark or drift for weeks while PR CI stays green, so
// every artifact it uploads must say — without a human reading logs — which
// commit produced it, which tool/config version measured it, how long it took,
// and whether it passed. Freshness monitoring reads `finishedAt`/`result`; drift
// analysis reads `tool`/`configHash`.
//
// Lanes that adopt the envelope later should import this module rather than
// re-deriving the field names.
export const LANE_ENVELOPE_SCHEMA_VERSION = 1;
export type LaneResult = 'pass' | 'fail';
export type LaneEnvelope<T> = {
schemaVersion: number;
/** Stable lane id — the metric key freshness/health jobs group by. */
lane: string;
commit: string;
ref: string | undefined;
runUrl: string | undefined;
/** Tool versions, or content hashes where a version is not enough. */
tool: Record<string, string>;
configHash: string;
/** Only lanes with randomized input record a seed; `null` states "not applicable". */
seed: string | null;
startedAt: string;
finishedAt: string;
durationMs: number;
result: LaneResult;
/** Lane-specific payload; kept separate so the envelope shape stays stable. */
data: T;
};
export type LaneEnvelopeInput<T> = {
lane: string;
commit: string;
tool: Record<string, string>;
configHash: string;
startedAtMs: number;
result: LaneResult;
data: T;
seed?: string | null;
now?: number;
};
export function laneEnvelope<T>(input: LaneEnvelopeInput<T>): LaneEnvelope<T> {
const finished = input.now ?? Date.now();
const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_REF_NAME } = process.env;
return {
schemaVersion: LANE_ENVELOPE_SCHEMA_VERSION,
lane: input.lane,
commit: input.commit,
ref: GITHUB_REF_NAME,
runUrl:
GITHUB_SERVER_URL && GITHUB_REPOSITORY && GITHUB_RUN_ID
? `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`
: undefined,
tool: input.tool,
configHash: input.configHash,
seed: input.seed ?? null,
startedAt: new Date(input.startedAtMs).toISOString(),
finishedAt: new Date(finished).toISOString(),
durationMs: Math.max(0, finished - input.startedAtMs),
result: input.result,
data: input.data,
};
}