mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 10:40:32 +01:00
* Load the Capy crew skills only on Capy machines (GST-1833) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Add changelog entry for Capy-only crew skills (GST-1833) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Move the Capy-only crew tooling into crew/ and run its tests only when needed (GST-1833) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Test that the Capy launch file puts crew/bin on PATH Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Count any ~/.capy as a Capy machine, so a Mac running Capy keeps /captain Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Fix the tests and fixtures that broke when crew tooling moved into crew/ Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Plan every crew test when a pull request's merge-base can't be read Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Add a test that checks each crew-only module left the root tree for crew/ Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Run crew tests on pull requests that change bin/ commands or workflows they drive Review-Finding: p1/specialist-batched#1 reader=testing Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep running every crew test on every pull request; the skip comes in a later change Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep copied installs and older release bisects working after the crew/ move Setup now copies crew/lib into every host's runtime root beside bin and lib, so Windows installs (which copy instead of linking) can still start gstack-ship-receipt, gstack-pregate and the other commands that import it. The release bisect runs the train-gate script from wherever the checked-out commit has it, so a bisect over commits from before the move still gets a verdict. Review-Finding: p1/outside-voice#1 reader=outside-voice Review-Finding: p1/outside-voice#2 reader=outside-voice Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Say in the changelog that Windows installs keep working after the crew/ move Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Point the harness guide's merge-queue rows at crew/lib/ where those modules moved Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Point the plain-title test at the wave module's new home in crew/ (GST-1833) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
53 lines
3.9 KiB
TypeScript
53 lines
3.9 KiB
TypeScript
/**
|
|
* `verify --post-merge <sha> [--timeout <s>]`: the coordinator's follow
|
|
* command after a train merged a PR onto the base branch. The push workflows
|
|
* run on the merged commit (free tests, the Windows subset, the main backstop
|
|
* that supersedes itself when main moves on), and a background `gh run
|
|
* watch` chain dies with the machine that started it. This command keeps no
|
|
* state: it reads every check run attached to the sha, waits (polling gh)
|
|
* until none is still running or the timeout passes, prints one `CHECK:`
|
|
* line per check, and a `POST_MERGE:` summary that is green only when at
|
|
* least one check completed and none failed; a neutral or skipped check is
|
|
* skipped, never red. Commit statuses count too: a pending status keeps the
|
|
* commit pending, a failed one makes it red (named as a commit status when no
|
|
* check run failed). Rerun it after an interruption and it continues.
|
|
*/
|
|
import { pollDelayMs } from '../../lib/gh-poll';
|
|
import { ciAtHead, type QueueContext } from '../../lib/ship-queue';
|
|
import { outFailed, outOk, outPending, type TrainOut } from './ship-queue-train';
|
|
|
|
const Q = 'gstack-ship-queue';
|
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
export interface PostMergeOptions { sha: string; timeoutSec: number; pollMs: number; checks?: string[] }
|
|
|
|
export async function verifyPostMerge(ctx: QueueContext, o: PostMergeOptions): Promise<TrainOut> {
|
|
const deadline = Date.now() + o.timeoutSec * 1000;
|
|
const at = o.sha.slice(0, 12);
|
|
const named = o.checks ?? [];
|
|
const again = `${Q} verify --post-merge ${o.sha} --timeout ${o.timeoutSec}${named.length ? ` --checks ${JSON.stringify(named.join(','))}` : ''}`;
|
|
for (;;) {
|
|
const ci = ciAtHead(ctx, o.sha);
|
|
const lines = [`POST_MERGE: ${ci.line.replace(/^(\S+) at ([0-9a-f]+)/, '$2 $1')}`, ...ci.checks.map(c => `CHECK: ${c.name} ${c.status === 'completed' ? c.conclusion ?? 'unknown' : c.status}`)];
|
|
const settled = !ci.none && !/^(pending|unknown) /.test(ci.line) && !ci.checks.some(c => c.status !== 'completed');
|
|
const runs = (n: string) => ci.checks.filter(c => c.name === n || c.name.startsWith(`${n} (`));
|
|
const absent = named.filter(n => !runs(n).length);
|
|
const unsuccessful = named.filter(n => runs(n).some(c => c.status === 'completed' && c.conclusion !== 'success'));
|
|
if (settled && ci.green && unsuccessful.length) return outFailed({ code: 'QUEUE_POST_MERGE_RED', message: `the post-merge checks the train gate names did not succeed at ${at}: ${unsuccessful.join(', ')}` }, lines,
|
|
`read the run, repair on a new PR or roll back; ${Q} train status names the freeze if the backstop filed one`, 'none');
|
|
if (settled && ci.green && !absent.length) return outOk(lines, 'none', 'none');
|
|
if (settled && !ci.green) {
|
|
const red = ci.checks.filter(c => c.status === 'completed' && !['success', 'skipped', 'neutral'].includes(c.conclusion ?? '')).map(c => c.name);
|
|
return outFailed({ code: 'QUEUE_POST_MERGE_RED', message: `main's push workflows at ${at} are red: ${red.join(', ') || 'a commit status (no check run failed)'}` }, lines,
|
|
`read the red run, repair on a new PR or roll back; ${Q} train status names the freeze if the backstop filed one`, 'none');
|
|
}
|
|
if (Date.now() >= deadline) {
|
|
const pending = ci.checks.filter(c => c.status !== 'completed').map(c => c.name);
|
|
const message = ci.line.startsWith('unknown') ? `the check runs at ${at} are unreadable (${ci.line.replace(/^unknown at [0-9a-f]+ /, '')})`
|
|
: ci.none ? `no check run has attached to ${at} yet` : settled ? `the post-merge checks the train gate names never ran at ${at}: ${absent.join(', ')}` : `main's push workflows at ${at} have not settled at the timeout: ${pending.join(', ') || 'a commit status'} still running`;
|
|
return outPending({ code: 'QUEUE_TRAIN_PENDING', message }, lines, again, 'none');
|
|
}
|
|
await sleep(pollDelayMs(o.pollMs));
|
|
}
|
|
}
|