Files
gstack/docs/pregate.md
T
Garry Tanandcapy-ai[bot] 8584d6ff49 v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen generated-file registry, intent-based issue links (multi-agent wave C-pregate) (#3121)
* v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen registry, intent-based issue links (multi-agent wave C-pregate)

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

* test(pregate): keep the bin-driving pregate tests off the Windows subset; pin files are free-only for eval selection

The three C-pregate tests that spawn bin/gstack-regen, bin/gstack-pregate,
bin/gstack-issue-links and bin/gstack-ship-receipt do so through
test/helpers/pregate-fixture.ts runBin, so the Windows curation's source
scan of the test file never saw a bin/ spawn and shipped them to
windows-latest, where CreateProcess cannot exec a shebang (14 failures in
windows-free-shard 2 and 5). Each is now a KNOWN_WINDOWS_INCOMPATIBLE entry
with the precise reason. .gstack/generated.json and .gstack/pregate.json
join FREE_ONLY_PR_FILES: no paid case reads gstack's own pin files (the
tools read origin/<base> of the repo they run in, and paid cases run in
fixture repos), so touching them no longer restores every gate case.

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

* test(harness-version): record the FREE_ONLY_PR_FILES pin-file entries as non-behavioral

scripts/test-pr-profile.ts is a pinned shared-harness file; the two
.gstack pin-file entries change selection only, never a case verdict.

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>
2026-10-11 01:57:16 +00:00

141 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# The pre-gate: generated files, cheap checks, issue links
`/ship` Step 5 opens with `gstack-pregate`: the free checks that catch what the
multi-agent wave kept paying remote gates to find (stale generated files, a
scratch script at the repo root, a nightly-only test still asserting an old
constant, a Windows-only change merged with its lane unrun, a `| tail`-hidden
failure). Plan: `docs/designs/MULTI_AGENT_WAVE_2026_10_10.md` C4, C6, C8.
Result codes: [docs/troubleshooting.md](troubleshooting.md#pre-gate-generated-file-registry-gstack-pregate-issue-links).
Three bins, all over `lib/`:
| Bin | Pin file (read from `origin/<base>`) | Does |
|---|---|---|
| `gstack-regen` | `.gstack/generated.json` | generated files regenerate, never hand-merge |
| `gstack-pregate` | `.gstack/pregate.json` | two-stage cheap gate, `pregate.json` record, publication check |
| `gstack-issue-links` | — | `Fixes #N` only on explicit closure intent |
## Trust boundary
A pin file names commands. Its presence never authorizes execution: both tools
read the committed copy on `origin/<base>` (a PR cannot smuggle a command into
its own gate) and run commands only with `--allow-repo-commands`, which `/ship`
passes. Standalone, `gstack-regen check` lists the commands and exits 3
(`REPO_COMMANDS_NOT_ALLOWED`); `gstack-pregate` marks `regen` and `lanes`
`incomplete`, never `pass`. `--policy-from head` (a PR that introduces or
changes a pin file) is accepted only with `--allow-repo-commands` and the record
says `policy head (unreviewed)`. An unresolvable base exits 3 with
`POLICY_SOURCE_UNAVAILABLE`. Globs are repo-relative (no `..`, no absolute
paths, at most 64 per list); commands are one line under 512 bytes.
<a id="generated"></a>
## `.gstack/generated.json` (generated-v1)
```json
{ "$schema": "https://github.com/garrytan/gstack/docs/pregate.md#generated-v1",
"entries": [ { "id": "skill-docs", "command": "bun run gen:skill-docs --host all",
"outputs": ["*/SKILL.md", "*/sections/*.md"], "inputs": ["*/SKILL.md.tmpl", "hosts/**"] } ] }
```
- `gstack-regen check` regenerates every entry (or `--only <id>`, or `--touched
<paths>` for entries whose inputs or outputs intersect) in a scratch
`git worktree` seeded from the working tree, with `node_modules` linked rather
than copied, and reports stale outputs (`REGEN_STALE`, exit 1) without touching
the tree. An entry with no `command` is tracked only (goldens regenerated by
another entry).
- `gstack-regen write` regenerates in place: what `gstack-restamp` and the
Step 3 conflict rule use. On a conflict in a registered output, take either
side, then `gstack-regen write`; the agent never hand-merges a generated file.
- `list`, `validate`, `init [--explain] [--force]`.
gstack's entries: `skill-docs` (every host render, the agents digest, llms.txt,
the doctor component table) and `ship-goldens` (`bun scripts/gen-ship-goldens.ts`,
the byte-pinned `/ship` renders in `test/fixtures/golden/`).
<a id="pregate-v1"></a>
## `.gstack/pregate.json` (pregate-v1)
| Key | Meaning |
|---|---|
| `runner` | the local test command with `{files}` (gstack: `bun test {files}`); without it `lanes` is `incomplete` |
| `dependencies` | glob → test files for subprocess targets, runtime reads and generated inputs the import graph cannot see; `[]` declares "no lane needed" (docs, licences) |
| `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 |
| `strays.allow` | intentional root files or new top-level directories |
Only the `origin/<base>` copy is honoured, so a downgrade is reviewed before it
takes effect. `gstack-pregate validate` / `init` cover the working-tree file.
## `gstack-pregate`
```
gstack-pregate [--stage preflight|tests|all] [--only <check,..>] [--allow-repo-commands] [--policy-from base|head] [--explain] [--json]
gstack-pregate verify | clear --remote <workflow.yml/job> --run-url <url> | lanes | validate | init
```
Two stages with separate clocks, both printed:
**Stage 1, mechanical preflight** (the two-minute promise; gstack measured
~2.5 s for `regen` and under 200 ms for each of the others):
- `regen`: every registry entry whose inputs or outputs are touched is fresh
(`gstack-regen check` in a scratch worktree).
- `secrets`: `gstack-redact` over the branch's added lines (merge-base to the
working tree, untracked files included). HIGH, or MEDIUM in the `secret`
category, fails; other MEDIUM findings warn. Cannot be downgraded.
- `strays`: a new file at the repository root, a new top-level directory, or a
scratch name (`zz-*`, `tmp*`, `scratch*`) anywhere.
- `literals`: for every changed exported constant or named timeout/retry
literal, the whole test tree (nightly- and Heavy-only files included) is
searched for the old value; a consumer the branch did not update fails with
`file:line`.
**Stage 2, test execution:**
- `lanes`: touched files map to tests through `lib/pregate/import-graph.ts`
(static imports ∪ repo-path string literals ∪ the `bin/<name>` →
`test/<name>*.test.ts` convention ∪ `dependencies`), transitively through
non-test files; deleted and renamed files select the tests that named them.
A touched file with no lane is `FAIL lanes — <file> has no test lane`, never
a pass. Lanes and platform tiers come from `.github/workflows/*.yml`
(`gstack-pregate lanes` prints them): local ones run through the runner under
`set -o pipefail`, judged on the runner's summary line (zero tests ran is a
failure, a `| tail`-hidden exit is still an exit); a platform lane this
machine cannot run becomes a `requires-remote` obligation naming
`workflow.yml/job`. `--explain` prints each test's provenance and every
uncertain mapping (a planner-computed matrix without a `selection` manifest
selects conservatively).
Every check records the input identity it ran against (the working-tree
fingerprint before and after, the selection, the command hash); a tree that
moved during a check, a timeout or a missing tool is `incomplete`, never `pass`.
The record is `.gstack/tmp/pregate.json` (`gstack-artifact validate --as pregate`).
**Publication.** `gstack-ship-receipt write --pregate .gstack/tmp/pregate.json`
(Step 18) refuses unless the record is current for the tree being published
(`PREGATE_STALE`: rerun after the final stamp), green (`PREGATE_LANE_FAILED`,
`PREGATE_INCOMPLETE`) and every `requires-remote` obligation carries a receipt
(`PREGATE_REMOTE_UNCLEARED`: dispatch the job, then
`gstack-pregate clear --remote <workflow.yml/job> --run-url <url>`). The ship
receipt then carries `pregate` and `requires_remote`. `gstack-pregate verify`
runs the same check standalone.
Tier 2 checks (`guards`, `ratchets`, `lint`, `links`, `hermetic`, `patches`,
`integrator`) are named in `--only`'s help and land in a follow-up pre-gate PR.
<a id="issue-links"></a>
## `gstack-issue-links`
```
gstack-issue-links [--base <ref>] [--issue <n>]... [--ci-run <id>]... [--out issues.json] [--json]
```
Reads `origin/<base>..HEAD`. `Fixes #N` only on explicit closure intent: a
`Fixes`/`Closes`/`Resolves #N` trailer line or `--issue N`. A bare mention, a
partial fix, a deferred ask and every cross-repo `owner/repo#N` are `Refs`. Each
closure issue is read through `gstack-issue-guard`; one that cannot be read is
`Refs #N (unreadable …)` with `ISSUE_UNREADABLE` (exit 1), never `Fixes`. Each
`--ci-run <id>` is searched among open issues whose body names it and rendered
`Flake: #N (run <id>)`. The printed lines go under `## Issues` in the PR body;
`--out` writes the object `gstack-ship-receipt write --issues @file` embeds.