From 8af121fd4c8ed0c74856ed5809bfa0449f23822f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 2 Nov 2026 13:04:44 -0800 Subject: [PATCH] v1.93.18.0 fix(pregate): a green Free Tests run at the pregate tree clears gstack's remote gate (GST-1024) (#3883) * 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> * fix: pre-landing review fixes (GST-1024: refuse a paid-only selection; validate skipped) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): GST-1024 entry names all four tests 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> --- .gstack/pregate.json | 5 + CHANGELOG.md | 11 ++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- docs/pregate.md | 33 +++++- lib/pregate/config.ts | 10 +- lib/pregate/index.ts | 16 ++- lib/pregate/run-check.ts | 22 +++- lib/run-evidence.ts | 13 ++- package.json | 2 +- test/pregate-remote-gate-runs.test.ts | 154 ++++++++++++++++++++++++++ 11 files changed, 244 insertions(+), 26 deletions(-) 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.md b/CHANGELOG.md index c97693ba1..6bbcc79ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [1.93.18.0] - 2026-11-02 + +**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`. When every selected file is skipped, the clear refuses with `paid_only`, just as the gate's own `tests=0` report never clears. 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, a paid-only selection, and a declaration without `skipped` all refuse. Malformed `skipped` values fail config validation. On main, all four tests fail. + ## [1.93.17.0] - 2026-11-02 - `gstack-ship-queue wave plan` now leaves out a READY head that does not merge onto main. It prints `NOT_MERGEABLE: # (...)` with the conflicting files, the same way `NOT_GREEN` works for red CI. A conflict only in the CHANGELOG doesn't count, since the train merges those. Before, such a head was planned into a wave and the wave build stopped on it. diff --git a/VERSION b/VERSION index a194d2ff7..f876ca51e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.93.17.0 +1.93.18.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 12980b593..6bf1df188 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.93.17.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.93.18.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/docs/pregate.md b/docs/pregate.md index 09d1afe7d..9128fa093 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 @@ -742,6 +742,16 @@ 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`). When `skipped` names every selected file, + no selected test ran, so the clear refuses (`paid_only`), just as the gate's + own `tests=0` report never clears. 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`). @@ -753,13 +763,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`, `paid_only`, `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=