From a58cf98d01744dddc78199c36ca8a1a71ad6b978 Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 2 Nov 2026 17:31:36 +0000 Subject: [PATCH] pregate: gstack declares remote_gate_runs, so a green Free Tests run at the pregate tree clears remote_gate (GST-1024) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- .gstack/pregate.json | 5 + changelog.d/gst-1024-remote-gate-runs.md | 8 ++ docs/pregate.md | 31 ++++- lib/pregate/config.ts | 10 +- lib/pregate/index.ts | 15 ++- lib/pregate/run-check.ts | 22 +++- lib/run-evidence.ts | 13 ++- test/pregate-remote-gate-runs.test.ts | 142 +++++++++++++++++++++++ 8 files changed, 223 insertions(+), 23 deletions(-) create mode 100644 changelog.d/gst-1024-remote-gate-runs.md create mode 100644 test/pregate-remote-gate-runs.test.ts diff --git a/.gstack/pregate.json b/.gstack/pregate.json index 6be73a295..5d1caa4f3 100644 --- a/.gstack/pregate.json +++ b/.gstack/pregate.json @@ -22,6 +22,11 @@ "ci-reuse/free-tests": { "workflow": "free-tests.yml", "events": ["pull_request", "workflow_dispatch"], "jobs": ["free-plan", "typecheck", "quick-bun-default", "cso-macos-launcher", "macos-named-regressions", "cso-windows-launcher", "cso-docker-integration", "free-tests"], "artifact": "tested-tree", "os": "Linux" }, "ci-reuse/windows-free-tests": { "workflow": "windows-free-tests.yml", "events": ["pull_request", "workflow_dispatch"], "jobs": ["windows-plan", "windows-free-tests"], "artifact": "tested-tree", "os": "Windows" } }, + "remote_gate_runs": { + "profiles": ["ci-reuse/free-tests"], + "inventory": "bun --no-env-file --config=/dev/null run scripts/test-free-shards.ts --list | sed -n 's/^ //p'", + "skipped": "bun --no-env-file --config=/dev/null -e 'import { isPaidTestFile } from \"./test/helpers/paid-test-set.ts\"; import * as fs from \"node:fs\"; for (const f of fs.readFileSync(process.env.PREGATE_FILES_FILE, \"utf8\").split(/\\r?\\n/)) if (f && isPaidTestFile(f)) console.log(f);'" + }, "warn": [], "strays": { "allow": ["modules/loadouts.json", "modules/loadouts.schema.json"] } } diff --git a/changelog.d/gst-1024-remote-gate-runs.md b/changelog.d/gst-1024-remote-gate-runs.md new file mode 100644 index 000000000..2c75bb63a --- /dev/null +++ b/changelog.d/gst-1024-remote-gate-runs.md @@ -0,0 +1,8 @@ +**A green Free Tests run at the pregate tree now clears gstack's remote gate, so a slow Ubicloud VM no longer blocks a PR whose CI already passed (GST-1024).** docs/pregate.md described `gstack-pregate clear --remote pregate.json/remote_gate --run --allow-repo-commands`, but gstack's own `.gstack/pregate.json` declared no `remote_gate_runs`, so the route always refused. On #3869, every check passed at 3f2bcf3 (Free Tests run 41484634592), while two remote gate runs at the same tree failed on bun timeouts in files the PR doesn't touch. + +### Added +- **`.gstack/pregate.json` `remote_gate_runs`:** the `ci-reuse/free-tests` profile, with `scripts/test-free-shards.ts --list` as the inventory (the complete free suite the Free Tests plan shards). +- **`remote_gate_runs.skipped`** (optional, `lib/run-evidence.ts`, `lib/pregate/index.ts`, `lib/pregate/run-check.ts`): a command that reads the selected files from `PREGATE_FILES_FILE` and prints the ones the remote gate itself never runs. Those files need no run, and the receipt records them as `skipped=`. An inventory file stays required even when `skipped` names it, and a failing command refuses with `skipped_failed`. gstack's command prints the selected paid test files, the same split `scripts/ubicloud/pregate-remote.sh` makes. Without it, run 41484634592 still refused: all 71 of the selected files it didn't cover are paid. + +### Tests +- `test/pregate-remote-gate-runs.test.ts`: gstack's inventory equals the Free Tests plan's file set, and its skipped command prints exactly the paid files of a mixed list. In a fixture with gstack's real profile, a green Free Tests `pull_request` run clears the gate. A run at another tree, a run of another workflow, a failed or cancelled run, a fork run, a merge commit behind its base, an uncovered inventory file, a skipped list naming an inventory file, a failing skipped command, and a declaration without `skipped` all refuse. On main, all three tests fail. diff --git a/docs/pregate.md b/docs/pregate.md index 4e223e0cf..d3504c213 100644 --- a/docs/pregate.md +++ b/docs/pregate.md @@ -89,7 +89,7 @@ the byte-pinned `/ship` renders in `test/fixtures/golden/`). | `timeout_s` | seconds each runner process may take (the unit-file group, each serial file, each guard without its own `timeout_s`, and the remote gate's deadline) when `--timeout` is not passed; 1 to 3600, default 600. `gstack-ship-preflight` and the `finish` fallback run pregate without `--timeout`, so this is how a repo with long lanes sets theirs | | `remote_gate` | the repo's remote test gate command; under 8 cores the local test tier becomes a `requires-remote` obligation naming it, and `--remote` runs it (see [remote gate](#remote-gate)) | | `run_profiles` | name → `{ workflow, events, jobs, artifact, max_age_hours?, os? }`: the GitHub Actions runs `clear --run` verifies as evidence on the identical tree; a profile named after a lane (`free-tests.yml/macos-named-regressions`) clears that lane (see [run evidence](#run-evidence)). `--from-run` and the main-push reuse job verify runs against them too ([from-run](#from-run), [CI reuse](#ci-reuse)) | -| `remote_gate_runs` | `{ profiles, inventory }`: the run profiles that together clear `pregate.json/remote_gate`, and the command that prints the remote gate's test files, one per line, which their runs must cover (see [run evidence](#run-evidence)) | +| `remote_gate_runs` | `{ profiles, inventory, skipped? }`: the run profiles that together clear `pregate.json/remote_gate`, the command that prints the remote gate's test files, one per line, which their runs must cover, and an optional command that prints which selected files the gate itself never runs (see [run evidence](#run-evidence)) | | `allowlist` | `[{ finding, paths?, pattern?, reason }]`: benign findings the `secrets` check accepts and names as allowlisted, by gstack-redact finding id or non-secret category, path globs and a pattern anchored at the finding; never `env.value` (see [allowlist](#allowlist)) | Only the `origin/` copy or the user-owned file below is honoured, so a @@ -734,6 +734,14 @@ Then, for the lane: while the working tree is pregate.json's tree (checked before and after it runs); an empty or failing inventory refuses, and it runs under `pipefail`, so a pipeline whose first command fails refuses too. +- When `remote_gate_runs` declares `skipped`, that command runs the same way, + with `PREGATE_FILES_FILE` naming a file of the obligation's selected test + files, one per line, and prints the ones the remote gate itself never runs + (gstack's gate leaves paid test files to the paid PR gate). Those files need + no run, and the receipt counts them as `skipped=`. A file the inventory + prints stays required even when `skipped` names it, and a failing `skipped` + command refuses (`skipped_failed`). Without `skipped`, every selected file + must appear in a run's `files`. - Every run is read once more after the inventory: a run that moved to a new attempt or stopped being a success meanwhile refuses (`run_changed`). @@ -745,13 +753,14 @@ verification is named with its own reason (`fork_run`, `event_mismatch`, `os_mismatch`, `api_error`, …); the clear-level codes are `base_mismatch`, `no_verified_run`, `tree_mismatch` (runs on different trees), `tree_not_stamp_only`, `no_tests`, `inventory_failed`, `inventory_empty`, -`inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own +`skipped_failed`, `inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own selected tests). A wrong run count, a repeated run or a lane with no profile is a usage error (exit 2), not a REUSE refusal. Accepted, `clear` prints `REUSE: accepted `, and the receipt reads `remote-gate tree= tests= runs=/,… profiles=

,… -heads= verified=