mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 23:37:53 +01:00
fix: pre-landing review fixes (round 2)
record-ci records the base policy that mapped the lane; the CI-first remote gate hint respects --no-ci-carry and reruns the whole pregate; changelog and docs say what the tool does. GSTA-258 piece A. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
02b2f2938a
commit
a102205e50
6 files changed
+23
-11
No files matched your search
+4
-2
@@ -122,6 +122,8 @@ interface EvidenceRecord {
|
||||
source?: "ci";
|
||||
runs?: string[];
|
||||
profiles?: string[];
|
||||
/** The base policy that mapped a carried lane (record-ci), as `origin/<base>@<sha>:.gstack/pregate.json`. */
|
||||
policy?: string;
|
||||
/** The runner's own test count (executed for bun/jest/vitest/pytest/rspec; ran with skips included for gstack's shard runner and the remote gate), 0 for ZERO-RUN, or "unknown" when no supported summary was seen. */
|
||||
tests_ran?: number | "unknown";
|
||||
}
|
||||
@@ -671,10 +673,10 @@ function cmdRecordCi(argv: string[]): number {
|
||||
const runs = v.runs.map((r) => `${r.run_id}/${r.run_attempt}`);
|
||||
const rec: EvidenceRecord = {
|
||||
ts: new Date(v.runs.map((r) => r.finished_at).sort()[0]!).toISOString(), label, command, cmd_sha256: sha256(command), exit: 0, duration_s: 0,
|
||||
commit: head, tree, dirty: false, wtree: tree, tests_ran: v.tests, source: "ci", runs, profiles: m.profiles,
|
||||
commit: head, tree, dirty: false, wtree: tree, tests_ran: v.tests, source: "ci", runs, profiles: m.profiles, policy: loaded.label,
|
||||
};
|
||||
appendJsonl(paths.file, rec, { mode: 0o600 });
|
||||
console.error(`gstack-evidence: recorded label=${label} exit=0 tests_ran=${v.tests} log=- source=ci runs=${runs.join(",")}`);
|
||||
console.error(`gstack-evidence: recorded label=${label} exit=0 tests_ran=${v.tests} log=- source=ci runs=${runs.join(",")} policy=${loaded.label}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ bump: minor
|
||||
- A lane that already passed at the same tree, command and runtime is reused instead of rerun (`gstack-evidence run --reuse`, the default from `gstack-ship-preflight` and `finish`): a newer failure vetoes an older pass, and the reused row keeps its original timestamp, so the 24-hour age limit still measures the real run.
|
||||
- /ship runs beside CI: on a repo that maps its lanes, Phase 2 pushes early (`gstack-ship-release push --early` runs the secrets scan and the sealed guard first), `finish` defers a mapped lane it cannot carry yet (`EVIDENCE: DEFERRED`), and the new `gstack-ship-release accept` carries it from the green run at the final pushed head and reruns the pregate there. `gstack-ci-carry find|wait` checks or waits for that run, and the ship receipt records `ci_carried`. See docs/pregate.md#ci-carry.
|
||||
- On a small machine, the remote gate obligation clears from the same carried run (through `remote_gate_runs` and its inventory check) when the files left over are only ones the gate skips, such as gstack's paid evals. Before, that remainder made the gate report `tests=0` and `clear --run` refuse it as `paid_only`, so nothing could clear it.
|
||||
- On gstack itself, a green Free Tests run at the same tree is now the default way the remote gate obligation clears, and the Ubicloud VM (`gstack-pregate --stage tests --remote`) is an opt-in. The uncleared obligation names the CI route first: push, `gstack-ci-carry wait --base main`, then rerun the tests stage. A branch behind main dispatches Free Tests on the branch, because a `pull_request` run tests the merge commit, another tree.
|
||||
- On gstack itself, a green Free Tests run at the same tree is now the default way the remote gate obligation clears, and the Ubicloud VM (`gstack-pregate --stage tests --remote`) is an opt-in. The uncleared obligation names the CI route first: push, `gstack-ci-carry wait --base main`, then rerun `gstack-pregate --allow-repo-commands`. On a branch behind main, dispatch Free Tests on the branch (`gh workflow run free-tests.yml --ref <branch>`), because a `pull_request` run tests the merge commit, another tree.
|
||||
- `gstack-evidence record-ci` now finds and verifies the CI run itself (`record-ci --label <L> --base <base> -- <command>`), so no caller can hand it run ids or a test count. `finish` writes its preflight-only pregate run to its own file, so the full `.gstack/tmp/pregate.json` stays in place until `accept` replaces it.
|
||||
+7
-4
@@ -499,8 +499,10 @@ the supervision above. Close the obligation one of four ways:
|
||||
- by default, when the base declares `remote_gate_runs`, let CI clear it: the
|
||||
tests stage clears it on its own from a verified run at this exact tree
|
||||
([CI carry](#ci-carry)), and the uncleared obligation's detail names that
|
||||
route first (`push, then gstack-ci-carry wait --base <base> and rerun --stage
|
||||
tests`) and the VM only as the opt-in; or
|
||||
route first (`push, then gstack-ci-carry wait --base <base> and rerun
|
||||
gstack-pregate --allow-repo-commands`, the whole pregate, so the record keeps
|
||||
its preflight checks) and the VM only as the opt-in (under `--no-ci-carry` it
|
||||
names the VM route); or
|
||||
- after running it, `gstack-pregate clear --remote pregate.json/remote_gate
|
||||
--receipt <file>` with the gate's output, validated as `--remote` validates it
|
||||
(a run URL is refused, `PREGATE_RECEIPT_INVALID` for a bad receipt; a receipt
|
||||
@@ -961,10 +963,11 @@ example a gate inventory file missing from the run's `tree.json`.
|
||||
Free Tests run at the same tree is how the remote gate obligation clears, and
|
||||
the Ubicloud VM (`gstack-pregate --stage tests --remote`) is an opt-in for when
|
||||
no such run exists. The order after a push: wait for Free Tests
|
||||
(`gstack-ci-carry wait --base main`), then `gstack-pregate --stage tests
|
||||
(`gstack-ci-carry wait --base main`), then `gstack-pregate
|
||||
--allow-repo-commands`. A `pull_request` run tests GitHub's merge commit, so on
|
||||
a branch that does not contain the base tip it tested another tree and
|
||||
`CI_CARRY` says `tree_mismatch`: dispatch Free Tests on the branch
|
||||
`CI_CARRY` says `none (no_verified_run: … tree_mismatch)`: dispatch Free Tests
|
||||
on the branch
|
||||
(`gh workflow run free-tests.yml --ref <branch>`), which tests HEAD's own tree,
|
||||
and wait again. The preflight stage (generated-file freshness, secrets, strays
|
||||
and the other mechanical checks) and the sealed guard are unchanged and always
|
||||
|
||||
@@ -533,8 +533,8 @@ async function runSelected(ctx: PregateContext, sel: Selection, base: CheckResul
|
||||
const smallMachine = ctx.cores < SMALL_MACHINE_CORES && sel.localTests.length > 0 && (ctx.config.remote_gate || ctx.testBackend !== 'local');
|
||||
if (smallMachine) {
|
||||
const obligation = remoteObligation(ctx, sel);
|
||||
const route = obligation.lane === REMOTE_GATE_LANE && ctx.config.remote_gate_runs
|
||||
? `a verified CI run at this tree clears it: push, then gstack-ci-carry wait --base ${ctx.base} and rerun --stage tests (a pull_request run of a branch behind its base tested the merge, another tree: dispatch the workflow on the branch); or opt in to the VM: gstack-pregate --remote --spend-cap <usd> --allow-repo-commands`
|
||||
const route = obligation.lane === REMOTE_GATE_LANE && ctx.config.remote_gate_runs && ctx.ciCarry !== false
|
||||
? `a verified CI run at this tree clears it: push, then gstack-ci-carry wait --base ${ctx.base} and rerun gstack-pregate --allow-repo-commands (a pull_request run of a branch behind its base tested the merge, another tree: dispatch the workflow on the branch); or opt in to the VM: gstack-pregate --remote --spend-cap <usd> --allow-repo-commands`
|
||||
: `run it, then ${clearHint(obligation.lane)}, or gstack-pregate --remote --spend-cap <usd> --allow-repo-commands`;
|
||||
return { ...base, status: 'requires-remote', remote: [...sel.remote, obligation], lines: [...lines, `requires-remote ${obligation.lane}: ${ctx.cores} cores (under ${SMALL_MACHINE_CORES}); ${sel.localTests.length} local test file(s) recorded for the remote gate, not run here`, `run: ${obligation.command}`], detail: `${sel.localTests.length} local test file(s) recorded as requires-remote ${obligation.lane} (${ctx.cores} cores, under ${SMALL_MACHINE_CORES}); ${route}` };
|
||||
}
|
||||
|
||||
@@ -88,7 +88,11 @@ describe.skipIf(process.platform === 'win32')('CI carry clears the remote gate o
|
||||
const file = await runPregate(loaded.ctx, { stage: 'tests' });
|
||||
expect(gate(file)?.receipt).toBeNull();
|
||||
const detail = file.checks.find(c => c.id === 'lanes')?.detail ?? '';
|
||||
expect(detail).toContain(`a verified CI run at this tree clears it: push, then gstack-ci-carry wait --base main and rerun --stage tests`);
|
||||
expect(detail).toContain(`a verified CI run at this tree clears it: push, then gstack-ci-carry wait --base main and rerun gstack-pregate --allow-repo-commands`);
|
||||
expect(detail.indexOf('gstack-ci-carry wait')).toBeLessThan(detail.indexOf('opt in to the VM: gstack-pregate --remote --spend-cap <usd> --allow-repo-commands'));
|
||||
loaded.ctx.ciCarry = false;
|
||||
const off = (await runPregate(loaded.ctx, { stage: 'tests' })).checks.find(c => c.id === 'lanes')?.detail ?? '';
|
||||
expect(off).not.toContain('gstack-ci-carry wait');
|
||||
expect(off).toContain('run it, then gstack-pregate clear --remote pregate.json/remote_gate');
|
||||
}, 120_000);
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import { afterEach, describe, expect, setDefaultTimeout, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { fakeGhScript, type FakeWorld } from './helpers/fake-gh-runs';
|
||||
import { findFilesBySuffix } from './helpers/scratch-repo';
|
||||
import { lineOf, PREFLIGHT, RELEASE, shipFixture, type ShipFx } from './helpers/ship-bin-fixture';
|
||||
|
||||
setDefaultTimeout(120_000);
|
||||
@@ -102,7 +103,9 @@ describe('Step 5 lanes: CI carry and the tree cache', () => {
|
||||
expect(ev(['check', '--label', 'free', '--expect-cmd', FREE]).stdout).not.toContain('EVIDENCE: FRESH');
|
||||
expect(JSON.parse(ev(['carried', '--json']).stdout)).toEqual([]);
|
||||
const ok = ev(['record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
expect([ok.status, ok.stderr]).toEqual([0, expect.stringContaining('tests_ran=12 log=- source=ci runs=7/1')]);
|
||||
expect([ok.status, ok.stderr]).toEqual([0, expect.stringContaining('tests_ran=12 log=- source=ci runs=7/1 policy=origin/main@')]);
|
||||
const row = JSON.parse(fs.readFileSync(findFilesBySuffix(path.join(f.state, 'projects'), '-evidence.jsonl')[0]!, 'utf8').trim().split('\n').at(-1)!);
|
||||
expect(row.policy).toMatch(/^origin\/main@[0-9a-f]{12}:\.gstack\/pregate\.json$/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user