v1.92.35.0 fix(pregate): a timeout-only policy keeps lane verdicts, warn lanes runs the tests, [] keeps removed-test suites (GST-775) (#3792)

* fix(pregate): a lane-less policy keeps no-policy verdicts; warn lanes runs the tests; [] keeps removed-test suites (GST-775)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): warn lanes keeps a platform-only selection requires-remote (GST-775)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): a head policy that adds a lane over a lane-less base is strict (GST-775)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): no empty no-lane line on mapped runs; trace [] refusals; absent lane flag stays strict (GST-775 review)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): GST-775 pregate lane policy fragment

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: recommended-harness no-policy row matches pregate (GST-775 docs audit)

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>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-01 16:28:03 +00:00
1 parent 6954a8c42b
commit 4f5b043b3e
18 files changed
+336 -55

No files matched your search

+19 -5
View File
@@ -75,10 +75,10 @@ the byte-pinned `/ship` renders in `test/fixtures/golden/`).
| Key | Meaning |
|---|---|
| `runner` | the local test command with `{files}` (gstack: `bun test {files}`); unset, it is detected (bun, vitest, jest, pytest, `node --test`, and `flutter test`/`dart test` per Dart package root, through fvm when that package or an ancestor pins it with `.fvmrc` or `.fvm/fvm_config.json` and fvm is on PATH; `cargo test -p <name>` per Rust crate, run from the root of the workspace the crate is a member of (Cargo's `members` globs, `exclude` and `package.workspace`), else the crate directory; a JS/TS test whose nearest package.json is a nested package declaring vitest or jest (a dependency or its `test` script) runs from that package, with package-relative paths, through the package manager its `packageManager` field or the nearest lockfile up to the repo root names: `pnpm exec`, `yarn`, `bun x`, else `npx`; every other JS/TS test keeps the root detection); nothing detected, a Dart SDK, cargo or package manager missing from PATH, a `.dart` file with no `pubspec.yaml` above it, a `.rs` file outside any crate, or one in a crate whose name Cargo would reject (anything but letters, digits, `_` and `-`), is `incomplete` (the runnable groups still run, so a real failure still fails) |
| `dependencies` | glob → test files for subprocess targets, runtime reads and generated inputs the import graph cannot see; `[]` declares "no lane needed" (docs, licences) |
| `dependencies` | glob → test files for subprocess targets, runtime reads and generated inputs the import graph cannot see; `[]` declares "no lane needed" (docs, licences). A `[]` that matches a deleted test does not skip it: the deleted test still runs its owning suite, and a file naming it that `[]` covers (a durations list) counts as covered. With this key present, even empty, the policy [declares lanes](#lane-requirements) |
| `empty_lanes` | extra globs for the docs/no-tests empty lane. Any Markdown file already qualifies with no pin file when no test depends on it (the same import graph as other files: a quoted path, a `join()` of path segments, an import, or a `dependencies` entry). reStructuredText and AsciiDoc count as Markdown here. README, CHANGELOG, CONTRIBUTING, LICENSE, AGENTS and CLAUDE qualify even without `.md`, and so does licence text under any vendored name (`roboto-LICENSE.txt`, `LICENSE-MIT`, `licenses/*.txt`). Source code and files under a test directory never qualify. A test that reads the file still selects that test |
| `selection` | `workflow.yml/job` → a command printing the lane's file list, for matrices a planner computes (gstack: `scripts/test-free-shards.ts --list`) |
| `warn` | check ids downgraded to warn; `secrets` and required lanes never |
| `warn` | check ids downgraded to warn; `secrets` and required lanes never. `"lanes"` downgrades only a touched file with no lane: the selected tests still run, a red lane stays `fail` (`PREGATE_LANE_FAILED`) and an unfinished one `incomplete`; a green run is `warn` `PREGATE_NO_LANE` naming the unmapped files |
| `strays.allow` | intentional root files or new top-level directories |
| `local` | `workflow.yml/job` lanes a local run satisfies: a platform-agnostic toolchain (an `ubuntu-latest` suite that runs the same on a Mac), or a repo whose CI is off. Their tests run here instead of becoming `requires-remote`, except that on a machine under 8 cores with a `remote_gate` or a remote `test_backend` the small-machine rule still records the test tier as a `requires-remote` obligation. `--explain` notes an id that matches no workflow lane |
| `guards` | `[{ id, when: [globs], command, fix?, timeout_s? }]`: repo commands a lane must pass when a touched file matches `when` (manifest checks, subprocess goldens the import graph cannot see); see [guards](#guards) |
@@ -141,6 +141,19 @@ unmapped file is `PREGATE_NO_LANE` again: the warning is for the first /ship
in a new repository (three Captains lost their first pass to it), never a way
to hide missing coverage. `--policy-from head` with a head policy is strict too.
<a id="lane-requirements"></a>
**Lane requirements.** "Any policy" above means a policy that declares lanes:
one with a `dependencies`, `empty_lanes`, `selection` or `local` key, even an
empty one (`gstack-pregate init` writes the first three). A policy with none of
them, such as
`{"timeout_s": 1800}` to give long tests more time, keeps the no-policy lane
verdicts: an unmapped file is the `warn` `PREGATE_NO_POLICY`, the selected
tests run, a red lane is `fail`, and the starter says the active policy
declares no lanes instead of claiming there is none. So raising
`timeout_s` never needs a `"**": []` catch-all. Add `dependencies` when you
want unmapped files to fail. The same rule applies to the user-owned file and to
a `--policy-from head` bootstrap (gbrain-evals #272, GST-775).
`gstack-pregate init` writes a draft built from the repo, not a fixed example.
It prints the languages it found and the runner each language's tests would
get, then maps every file the import graph cannot place to the tests of the
@@ -266,9 +279,10 @@ Two stages with separate clocks, both printed:
`.cjs`, `.mts`, `.cts`, then `index.ts`, `index.tsx`, `index.js`, `index.jsx`
and `index.mjs` inside the directory; the first file that exists wins.
A touched source file with no lane is `FAIL lanes — <file> has no test lane`
once any policy exists (the base copy, the user-owned file or `--policy-from
head`); with no policy anywhere it is the `warn` `PREGATE_NO_POLICY` with a
starter printed ([no policy](#no-policy)); never a pass. Markdown that no
once a policy that [declares lanes](#lane-requirements) exists (the base copy,
the user-owned file or `--policy-from head`); with no policy anywhere, or one
that declares no lanes, it is the `warn` `PREGATE_NO_POLICY` with a starter
printed ([no policy](#no-policy)); never a pass. Markdown that no
test depends on is a docs/no-tests empty lane
instead (`docs/no-tests: N documentation file(s)`), including when the base
branch has no `.gstack/pregate.json`. The dependency is the same import graph
+1 -1
View File
@@ -82,7 +82,7 @@ a time; each is independent.
| File | Why | Without it |
|---|---|---|
| `.gstack/pregate.json` | Maps changed paths to test lanes, so `/ship` runs the tests that cover the diff and the remote gate selects correctly ([docs/pregate.md](pregate.md)) | `/ship` fails every touched file with `PREGATE_NO_LANE`. Write it once, from one owner, before the crew starts. |
| `.gstack/pregate.json` | Maps changed paths to test lanes, so `/ship` runs the tests that cover the diff and the remote gate selects correctly ([docs/pregate.md](pregate.md)) | `/ship` warns on every touched file with no test lane (`PREGATE_NO_POLICY`) and prints a starter policy; the selected tests still run. A policy that sets only `timeout_s` or other keys without `dependencies`, `empty_lanes`, `selection` or `local` behaves the same. Write it once, from one owner, before the crew starts. |
| `.gstack/ship-policy.json` | `queue_mode`, the `train` block (gate and free workflow names, the full-gate check, the train size and fill wait), the stamp files ([docs/ship-policy.md](ship-policy.md)) | Per-PR queue; `train …` refuses with `QUEUE_TRAIN_OFF`. |
| `CHANGELOG.md` + `VERSION` | A stamp per merge and a twice-weekly release with notes ([docs/releases.md](releases.md)) | Skip the version and changelog steps; the train still works on the tree receipt. |
| `AGENTS.md` validation discipline | Diagnose before fixing, focused checks during repair, one full suite at the end on a frozen tree | Threads retry blindly and report passes from older revisions. |
+10 -5
View File
@@ -3561,9 +3561,12 @@ never runs them: declare `"**/integration_test/**": []` (and
them. A `[]` declaration added only on the branch is refused under
`--policy-from head` ([`PREGATE_HEAD_POLICY_LOOSENS`](#pregate-head-policy-loosens)),
so land it in its own PR first.
With no policy anywhere (no base copy, no user-owned file) an unmapped file is
With no policy anywhere (no base copy, no user-owned file), or a policy with no
`dependencies`, `empty_lanes`, `selection` or `local` key (a timeout-only file), an unmapped file is
the warning [`PREGATE_NO_POLICY`](#pregate-no-policy) with a starter policy
printed, not this error; `gstack-pregate init` writes that draft and lists the
printed, not this error. Under `warn: ["lanes"]` it is a `warn` with this code,
given only after the selected tests ran green (a red lane is still
`PREGATE_LANE_FAILED`); `gstack-pregate init` writes that draft and lists the
files it could not place. `--explain` prints every mapping and why. For a file with no lane it also prints the import
specifiers in test files that resolved to no repo file (for example a
`tsconfig.json` `paths` alias such as `@/lib/x` whose target is missing) and any
@@ -3701,8 +3704,9 @@ differing path is a change.
### `PREGATE_NO_POLICY`
**Meaning.** The repository has no pregate policy yet (no `.gstack/pregate.json`
on the base branch and no user-owned `<state root>/projects/<slug>/pregate.json`)
and a touched file maps to no test lane. Under no policy that is a `warn`, not
on the base branch and no user-owned `<state root>/projects/<slug>/pregate.json`),
or its policy declares no lanes (no `dependencies`, `empty_lanes`, `selection` or `local` key, a
timeout-only file for example), and a touched file maps to no test lane. Under no policy that is a `warn`, not
`PREGATE_NO_LANE`: the selected tests still ran (a red lane is still `fail`),
and the table printed a starter policy drafted from the repository
(`PREGATE_STARTER:` lines: runner, test directories, CI lanes, the JSON between
@@ -3713,7 +3717,8 @@ Details: [docs/pregate.md](pregate.md#no-policy).
overwrite an existing file without `--force`), edit each `TODO` (a test that
names the file, a `dependencies` entry, or `[]` for a file with no behaviour),
run `gstack-pregate validate`, then commit `.gstack/pregate.json` on the base
branch. From then on an unmapped file is `PREGATE_NO_LANE`.
branch. When a policy already exists, add the starter's `dependencies` to it
instead. From then on an unmapped file is `PREGATE_NO_LANE`.
<a id="pregate-environment"></a>
### `PREGATE_ENVIRONMENT`