Skip to content

v1.91.244.0 fix(pregate): waive version-stamp-only manifests from the lanes check (GBRA-95) - #3461

Merged
capy-ai[bot] merged 2 commits into
mainfrom
fix/pregate-version-stamp-only-waiver
Oct 26, 2026
Merged

capy-ai[bot] merged 2 commits into
mainfrom
fix/pregate-version-stamp-only-waiver

Conversation

@garrytan

@garrytan garrytan commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner
{
  "schema_version": 1,
  "pr": 3461,
  "head": "adc85bdb86207c36731777481753bfdcc398351b",
  "version": "1.91.231.0",
  "base": "main",
  "gate": {
    "pregate.json/remote_gate (requires-remote, Ubicloud)": {
      "pass": 15591,
      "fail": 0,
      "skip": 0
    },
    "free-tests.yml (workflow_dispatch run 40289961928)": {
      "pass": 1,
      "fail": 0,
      "skip": 0
    },
    "windows-free-tests.yml (workflow_dispatch run 40289964968)": {
      "pass": 1,
      "fail": 0,
      "skip": 0
    },
    "typecheck": {
      "pass": 1,
      "fail": 0,
      "skip": 0
    }
  },
  "session_kind": "unattended",
  "pregate": "requires-remote cleared @311570169ef2 (1 checks, current for adc85bdb8620)",
  "requires_remote": [
    "free-tests.yml/cso-macos-launcher: https://github.com/garrytan/gstack/actions/runs/40289961928",
    "free-tests.yml/macos-named-regressions: https://github.com/garrytan/gstack/actions/runs/40289961928",
    "windows-free-tests.yml/windows-free-shard: https://github.com/garrytan/gstack/actions/runs/40289964968",
    "pregate.json/remote_gate: remote-gate tree=311570169ef2 tests=15591"
  ],
  "issues": {
    "fixes": [],
    "refs": [],
    "flakes": []
  }
}

What changed

gbrain's bun run release:restamp rewrites its plugin manifests and VERSION. Its base's .gstack/pregate.json declares no lane for them, and pregate reads policy from the base, so every restamp PR failed lanes with PREGATE_NO_LANE and could not fix itself (gbrain #7625 at f8ef55c3b, #7606).

gstack-pregate lanes on a restamp PR whose manifests have no declared lane: before, four manifests with only a version bump were PREGATE_NO_LANE and the PR could not fix itself; after, they are waived as version-stamp-only on a line that names them and why, and a manifest whose description also changed is still refused with the reason under --explain
gstack-pregate on a restamp fixture. Before: 4 touched file(s) with no test lane, exit 1, and no way for the PR to declare the lane (policy comes from the base). After: version-stamp-only (no lane needed): …, exit 0, with the per-file verdict under --explain; the same stamp plus a reworded description is still PREGATE_NO_LANE, with not version-stamp-only (.description changed and its key is not \version`)`.

The rule (lib/pregate/stamp-only.ts)

versionStampOnly(rel, before, after) says a touched file changed as a stamp and nothing else. Three file kinds, each exact; anything else is not a stamp:

  • JSON (*.json): both sides parse to objects of the same shape (the same own keys in the same order in every object, the same length in every array, the same type at every leaf); the only differing leaf is the document's top-level version, a bare version on both sides (1.2.3, v1.2.3.4, 1.2.3-rc.1; never a range); and the texts are line-for-line identical once that one line has its old quoted version replaced by the new one (one recorded change consumed per differing line). Nothing nested qualifies, so a dependency pin, a lockfile entry, a catalog, a devcontainer feature or a package named version can never be a stamp; nor can a changed description, a new, removed or reordered key (conditional exports resolve in key order), a floor such as minHostVersion or schemaVersion, a reflow, or anything the parser erases (a big integer, a shadowed duplicate key, a number or escape spelling). A document nested too deep to walk is "could not compare", not a crash.
  • plist: same line count; every differing line is a <string> version on both sides whose previous line is <key>CFBundleShortVersionString</key> or <key>CFBundleVersion</key>. MinimumOSVersion and every other key are not stamps.
  • VERSION / VERSION.txt (any case): one bare version with at most a trailing LF on both sides. A BOM, a space, a CR or a second line is content.

Texts are compared as they are: no CRLF normalisation, BOM kept (TextDecoder with ignoreBOM), strict UTF-8 (two different invalid bytes must not both become U+FFFD), 1 MiB cap checked from lstat before reading.

Where it applies (lib/pregate/lane-selection.ts)

Only after the import graph, the declarations, the regen registry and both empty-lane classes found nothing, so a manifest a test reads still selects that test. Only to a modified, unrenamed file whose merge-base entry is a regular blob of the same mode (git ls-tree; a symlink or exec-bit flip is not a stamp). Never under a test, fixture, golden, snapshot, baseline, .gstack/ or CI-workflow tree (isProtectedTree, exported from docs-lane.ts), whose JSON a test may compare against by a path the graph cannot see. Selection.stampOnly carries the waived files; lanes prints them on their own line (version-stamp-only (no lane needed): <files> — only version values changed against the merge base; a manifest whose other content changed still needs a lane) and names them in the no-tests detail; --explain prints the per-file verdict.

Docs

docs/pregate.md states the rule in full; the PREGATE_NO_LANE entry in docs/troubleshooting.md says why a stamp PR is not that error and links the rule; lib/result-codes.ts's fix text mentions the waiver; CHANGELOG ## [Unreleased] carries the entry (VERSION, package.json and the digest stay at main's: stamp-at-merge).

Why

A restamp PR has nothing a test could observe, yet it was the one PR shape gbrain's lanes could never pass, and the fix was on the wrong side of the policy boundary (the base's). The waiver is as narrow as the restamp it serves: gbrain's six manifests all stamp a top-level version, and the reviewers' three fix cycles (below) spent their effort making sure nothing else slips through it.

Approvals

Date Asked Garry's words Covers Pending
2026-10-26 GStack Minder scoped this PR (GBRA-95: PREGATE_NO_LANE on stamp-only manifests; touch only pregate, tests, docs) — (Captain-directed under the 2026-10-25 "we should really be using the gstack ship skill before merging PRs" rule) this PR READY --no-screen to GStack Minder

Summary

  • gstack-pregate lanes waive version-stamp-only manifests (JSON top-level version, plist bundle versions, VERSION files) with a stated line and per-file --explain verdicts.
  • Everything else keeps PREGATE_NO_LANE: nested versions, dependency pins, lockfiles, reorders, reflows, parser-erased changes, BOM/CRLF, oversize or invalid UTF-8, mode changes, symlinks, renames, adds, deletes, protected trees.

Test Coverage

Step 7 audit (read-only, against tree b5ef0a79; the final commit touched only scripts/lib/windows-curation.ts): 36 decision paths across stamp-only.ts and the lane-selection integration, 31 tested (86%), ★★★ 27, ★★ 4, gaps 5, none weak: the plist no line changed return (unreachable once the texts differ), the non-RangeError wording of the could not compare catch, the text check's changed in the document but on no line of its own return (every fixture that reaches it is caught by the per-line check first), a working-tree symlink (the isFile() guard; only the merge-base symlink is exercised), and the lstat catch for a file that vanished mid-run. 0 E2E gaps. bun test test/pregate-stamp-only.test.ts: 21 pass, 0 fail, 173 assertions; pure rows for every rule and bin fixtures for the positive stamp, the reworded manifest, the mixed diff (orphan source refused, mapped file still runs its test), a manifest a test reads, merge-base semantics with main re-stamping after the fork, uncommitted stamp vs uncommitted reword, oversize / invalid-UTF-8 / BOM manifests, protected trees, exec-bit and symlink, rename/add/delete, and the detail line beside docs and assets.

Pre-Landing Review

/ship invocation 5, unattended: five-specialist review army plus the Claude adversarial pass (codex unavailable here), four passes over three fix cycles. Pass 1 found three CRITICAL holes (prototype-named keys passing the key-set check via in; sorted keys hiding reorders; any key containing "version" qualifying, dependency names and MinimumOSVersion included) — fixed. Pass 2: 0 CRITICAL; informational fixes taken (top-level version only instead of a deny-list; one change per differing line; bounded recursion; strict decoding and the 1 MiB cap). Pass 3: one CRITICAL (JSON under protected trees could be waived) — fixed, plus BOM, VERSION trim, mode/symlink and lstat-first fixes. Pass 4 (final, re-bound on the shipped tree): 0 CRITICAL, 11 informational, all recorded as skipped: whitespace around a stamped plist <string> line is ignored (XML-insignificant; the line is still a bare version under a bundle key), the protected-tree denylist does not name testdata/, __fixtures__/, __mocks__/, e2e/, and each manifest costs two serial git spawns (about nine files on a restamp). Review binding verified on the shipped tree 31157016 (records at adc85bd; the only change after pass 4 is the Windows-curation entry for the new test file, which has no code paths of its own). Gate edits: none (GATE_SUMMARY listed=1 eligible=0 tagged={} against origin/main).

Exploratory QA

Fixture probes through the branch's bin/gstack-pregate (the image above): the stamp-only PR passes with the waiver line; the reworded manifest is refused with the reason; the reviewers' /tmp mutation fuzzes (300k and 400k JSON mutations) found no waived document whose parsed content differed beyond the top-level version.

Eval Results

No prompt or skill template changed; eval:select maps the diff to no skill-owned paid case. Not run.

Greptile Review

Skipped: GREPTILE_API_KEY unset.

Issues

No gstack issue closed. GBRA-95 (gbrain) is the requester; GStack Minder relays the PR number and version.

Verification Results

  • Remote pregate lane on the shipped tree 31157016: PREGATE_REMOTE: tree=311570169ef2… tests=15591 failed=0 spent=0.05 (gstack-evidence FRESH, exit 0). Earlier trees 7fe96eff (15568/0), e5cf2efa (15569/0) and b5ef0a79 (15571/0) were green too; the first lane attempt died before running because a git worktree's .git file confused the VM tree check (PREGATE_VM_TREE_MISMATCH vm=unavailable), so lanes run from a plain clone.
  • bun run typecheck exit 0; bun run typecheck:test within the ratchet; troubleshooting anchors, module-size ratchet, spawnSync tripwire green.
  • GitHub ran no pull_request workflow on this head because it conflicts with main on CHANGELOG.md alone (the train merges that by section); Free Tests (run 40289961928) and Windows Free Tests (run 40289964968) were dispatched on this head by workflow_dispatch and both succeeded. The first Windows dispatch, on 78f436b, failed in shard 2 because the new test file's bin fixtures assert the Linux-host lanes verdict and its mode-change case needs a POSIX exec bit; like every other pregate bin test, test/pregate-stamp-only.test.ts is now on the Windows-curation exclusion list in scripts/lib/windows-curation.ts (the only edit after review pass 4).

TODOS

No TODOS.md entry matches; none added.

Documentation

Audit gsta102-inv5-docs (read-only snapshot against origin/main, compare clean): docs/pregate.md (lanes section) and the PREGATE_NO_LANE entry in docs/troubleshooting.md were updated in the commit; README, CONTRIBUTOR_REFERENCE and CLAUDE.md do not describe lane classes and need no change.

Test plan

  • bun test test/pregate-stamp-only.test.ts (21 pass) on the shipped tree
  • Remote free tier on the shipped tree: 15591 tests, 0 failed
  • Fixture probes: stamp-only PR passes, reworded manifest refused (image above)
  • Train: stamp at the slot with gstack-restamp --next; CHANGELOG merges by section

Evidence

Head pinned at READY: adc85bdb86207c36731777481753bfdcc398351b; tree 311570169ef24030c80387603905173d137f0625.

@trunk-io

trunk-io Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

@garrytan
garrytan force-pushed the fix/pregate-version-stamp-only-waiver branch 4 times, most recently from 78f436b to b245f3b Compare October 26, 2026 07:49
… (GBRA-95)

gbrain's release:restamp rewrites its plugin manifests and VERSION; its
base's .gstack/pregate.json declares no lane for them, and pregate reads
policy from the base, so every restamp PR failed lanes with
PREGATE_NO_LANE and could not fix itself (gbrain PRs 7625 and 7606).

lib/pregate/stamp-only.ts defines version-stamp-only per file kind: a JSON
document of the same shape whose every changed leaf is a bare version under
a key named for a version; plist <string> versions under a ...Version...
key; a VERSION file that is one bare version on both sides. lane-selection
applies it only after the import graph, the declarations, the regen
registry and both empty lanes found nothing; lanes prints the waived files
on their own line and --explain the per-file verdict. Anything else (a
changed description, a new key, a dependency pin, an added or deleted
manifest) keeps its PREGATE_NO_LANE.

Tests: pure rows for every rule, and bin fixtures for a stamp-only PR
(passes), a manifest with a changed description (refused), a mixed diff
(stamped files waived, the orphan source file still refused, a mapped file
still runs its test) and an added/deleted manifest.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@garrytan
garrytan force-pushed the fix/pregate-version-stamp-only-waiver branch from b245f3b to adc85bd Compare October 26, 2026 08:06
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@github-actions github-actions Bot changed the title fix(pregate): waive version-stamp-only manifests from the lanes check (GBRA-95) v1.91.244.0 fix(pregate): waive version-stamp-only manifests from the lanes check (GBRA-95) Oct 26, 2026
@capy-ai
capy-ai Bot merged commit d62eb91 into main Oct 26, 2026
63 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant