mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 00:44:07 +01:00
Merge origin/main (v1.91.278.0) into eval-select-review-floor-followups; stamp v1.91.279.0
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json MERGE_CONDITION: sha=64684de144e5c5b8934408514b44434559487b18 NEW_VERSION=1.91.279.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
commit
c7f6daa7a4
30 files changed
+654
-85
No files matched your search
@@ -5,7 +5,8 @@
|
||||
* head's paid slices start only after (1) the PR has been quiet for the
|
||||
* window (no newer push since this run was created; `vars.EVALS_QUIET_MINUTES`,
|
||||
* default 10; the `evals-no-debounce` label sets it to 0) and (2) Free Tests
|
||||
* (free-tests.yml) has concluded success on the same head SHA. Selection,
|
||||
* (free-tests.yml) has concluded success on the same head SHA, as GitHub's
|
||||
* pull_request run or as a workflow_dispatch run on the branch. Selection,
|
||||
* trials and verdict rules are unchanged: only the start time moves.
|
||||
*
|
||||
* A newer push cancels the run through the workflow's cancel-in-progress
|
||||
@@ -40,9 +41,25 @@ export type ReadinessDecision =
|
||||
| { kind: 'superseded'; by: string }
|
||||
| { kind: 'blocked'; reason: string };
|
||||
|
||||
/** The newest Free Tests pull_request run on this exact head decides; a rerun updates that run in place. */
|
||||
/**
|
||||
* The Free Tests run events that count as this head's CI, in order of
|
||||
* precedence: GitHub's own pull_request run (it builds the merge ref, the tree
|
||||
* a merge would test), then a workflow_dispatch run on the branch (the
|
||||
* dispatch route in docs/ship-policy.md for a head whose only conflict with
|
||||
* the base is the CHANGELOG, which GitHub builds no merge ref for and so never
|
||||
* runs pull_request CI on; #3508). A dispatched run builds the head alone, so
|
||||
* it decides only for a head with no pull_request run: it never overrides a red
|
||||
* pull_request run (rerun that run), and a lane's extra dispatch never holds a
|
||||
* head its pull_request CI cleared. A push run is a branch build, not this
|
||||
* head's gate.
|
||||
*/
|
||||
export const FREE_TESTS_EVENTS: readonly string[] = ['pull_request', 'workflow_dispatch'];
|
||||
|
||||
/** The newest Free Tests run on this exact head under the first event present decides; a rerun updates that run in place. */
|
||||
export function freeTestsState(runs: FreeTestsRun[], headSha: string): FreeTestsState {
|
||||
const newest = runs.filter(run => run.head_sha === headSha && run.event === 'pull_request').sort((a, b) => b.id - a.id)[0];
|
||||
const atHead = runs.filter(run => run.head_sha === headSha);
|
||||
const event = FREE_TESTS_EVENTS.find(e => atHead.some(run => run.event === e));
|
||||
const newest = atHead.filter(run => run.event === event).sort((a, b) => b.id - a.id)[0];
|
||||
if (!newest) return { state: 'missing' };
|
||||
if (newest.status !== 'completed') return { state: 'running', runId: newest.id };
|
||||
if (newest.conclusion === 'success') return { state: 'success', runId: newest.id };
|
||||
@@ -111,8 +128,9 @@ export function waitForReadiness(o: WaitOptions): { decision: ReadinessDecision;
|
||||
if (quietUntil > now()) sleep(quietUntil - now());
|
||||
let lastError = '';
|
||||
for (;;) {
|
||||
// Every event at the head; freeTestsState keeps the pull_request and workflow_dispatch runs.
|
||||
const listed = ghJson<{ workflow_runs?: FreeTestsRun[] }>(gh, ['api', '-X', 'GET', `repos/${o.repo}/actions/workflows/free-tests.yml/runs`,
|
||||
'-f', `head_sha=${o.headSha}`, '-f', 'event=pull_request', '-f', 'per_page=20']);
|
||||
'-f', `head_sha=${o.headSha}`, '-f', 'per_page=20']);
|
||||
const pr = ghJson<{ head?: { sha?: string } }>(gh, ['api', `repos/${o.repo}/pulls/${o.pr}`]);
|
||||
lastError = !listed.ok ? listed.error : !pr.ok ? pr.error : '';
|
||||
if (!lastError && listed.ok && pr.ok) {
|
||||
|
||||
+23
-5
@@ -26,7 +26,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { normalizeChangelog } from '../lib/changelog-check';
|
||||
import { normalizeLockfile } from '../lib/evidence-bundle';
|
||||
import { rewriteStamps, versionAtRef } from '../lib/restamp';
|
||||
import { stampMentions, rewriteStamps, versionAtRef } from '../lib/restamp';
|
||||
import { loadPolicyFromHead, matchesAny, type ShipPolicy } from '../lib/ship-policy';
|
||||
import type { BisectPrefixRecord, TrainGateRecord } from '../lib/ship-queue-train-verdict';
|
||||
import type { CasePanel } from '../lib/ship-queue-train-record';
|
||||
@@ -132,20 +132,38 @@ export function fullGateCases(rootDir = ROOT): Map<string, string> {
|
||||
return plannedCases(buildRunManifest({ tier: 'gate', profile: 'full', sliceCount: 1, evalsAll: true, env: { EVALS_ALL: '1' }, rootDir }), rootDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* A release output or stamp path without its stamps: every version mentioned
|
||||
* in a stamp form (the built-in forms and the policy's `stamp_patterns`
|
||||
* templates) collapses to one placeholder. Normalising only the commit's own
|
||||
* version left a tentative `*_KEY_VERSION = '<next minor>'` as written on the
|
||||
* unstamped prefix and normalised once the slot stamp moved it to the released
|
||||
* version, so the two digests differed and train-backstop found no covering
|
||||
* record for main after that train (#3512), and a base-version mention on a
|
||||
* line the base already carried was normalised on the prefix and kept on the
|
||||
* stamped main. Every stamp-form mention collapsing makes the digest blind to
|
||||
* which version a stamp names; the receipt (`stampOnlyLine`) is what judges a
|
||||
* stamp commit's content.
|
||||
*/
|
||||
export function stampFreeText(text: string, templates: readonly string[]): string {
|
||||
let out = text;
|
||||
for (const v of [...stampMentions(text, templates)].sort((a, b) => b.length - a.length)) out = rewriteStamps(out, v, '\0VERSION', templates).text;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* A commit's tree modulo stamps: every path with its mode and blob, except the
|
||||
* files a restamp rewrites, which contribute their content without the stamp
|
||||
* (the version file nothing, package.json without its version, lockfiles
|
||||
* without their root version, the CHANGELOG with its headings normalized,
|
||||
* release outputs and stamp paths with their own version replaced). Two
|
||||
* commits that differ only by a restamp have the same digest.
|
||||
* release outputs and stamp paths with every stamp-form version mention
|
||||
* replaced). Two commits that differ only by a restamp have the same digest.
|
||||
*/
|
||||
export function stampFreeTree(git: Git, commit: string, repoRoot: string, policy: ShipPolicy): string | { error: string } {
|
||||
const listed = git(['ls-tree', '-r', '-z', '--full-tree', commit]);
|
||||
if (!listed.ok) return { error: `cannot list ${commit.slice(0, 12)} (${listed.out.split('\n')[0]})` };
|
||||
const at = versionAtRef(repoRoot, commit);
|
||||
const versionRel = 'rel' in at ? at.rel : 'VERSION';
|
||||
const version = 'version' in at ? at.version : null;
|
||||
const lines: string[] = [];
|
||||
for (const item of listed.out.split('\0').filter(Boolean)) {
|
||||
const [meta, rel] = [item.slice(0, item.indexOf('\t')), item.slice(item.indexOf('\t') + 1)];
|
||||
@@ -161,7 +179,7 @@ export function stampFreeTree(git: Git, commit: string, repoRoot: string, policy
|
||||
if (stamp === 'manifest') { try { const parsed = JSON.parse(text); delete parsed.version; text = JSON.stringify(parsed); } catch { /* not JSON: kept verbatim */ } }
|
||||
else if (stamp === 'lockfile') text = normalizeLockfile(base, text);
|
||||
else if (stamp === 'changelog') text = normalizeChangelog(text);
|
||||
else if (version) text = rewriteStamps(text, version, '\0VERSION', policy.stamp_patterns).text;
|
||||
else text = stampFreeText(text, policy.stamp_patterns);
|
||||
lines.push(`${rel}\0${mode}\0${createHash('sha256').update(text).digest('hex')}`);
|
||||
}
|
||||
return createHash('sha256').update(lines.sort().join('\n')).digest('hex');
|
||||
|
||||
Reference in new issue
Block a user