From 0ade4b5f5aad5b28d0be5e49373e0d8f4426d76d Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 24 Oct 2026 23:48:52 +0000 Subject: [PATCH] docs(pregate): CHANGELOG entry for #3286 and the link-chain and dangling-parent rules Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 19 +++++++++++++++++++ docs/pregate.md | 9 ++++++--- docs/troubleshooting.md | 10 ++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c82c04d..598597c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,25 @@ - No threshold, budget, timeout, trial count or case kind changed. Pregate's lanes now map and run tests for `.rs` files that previously failed closed with "no test lane", and nested vitest/jest packages run under their own runner instead of `bun test`. A `.rs` file outside any crate, a crate with no tests, and a missing cargo or package manager still fail closed or stay incomplete; the tests above cover each. - No new dependency (Cargo.toml is parsed with Bun's built-in TOML parser), no network call in pregate itself, no workflow change. As with the root runner detection on main (`npx vitest run`), a nested package's detected runner (`npx`, `bun x`) may fetch a missing tool when it runs under `--allow-repo-commands`. +**A repo that keeps `.gstack/` out of git can give pregate a policy, and name the CI lanes a local run satisfies (#3286).** On a shared repo where gstack is one contributor's tool, pregate fell back to defaults, so every platform lane the machine could not run (an `ubuntu-latest` suite on a Mac, say) became a `requires-remote` obligation that never cleared, even when that suite runs the same here. Ported from #3286 by arlophoenix, whose 13 commits carry the feature (merged with their history kept); our commits on top close two findings from a security-focused review of the port. + +### Added +- **`lib/pregate/context.ts`, `lib/pregate/config.ts`** (arlophoenix): when the base branch carries no `.gstack/pregate.json`, `gstack-pregate run` reads `/projects//pregate.json`. Never under `--policy-from head`, never a relative path or one inside the checkout, and never chosen by a checkout's `.env` (the bin ignores it, and a bare `bun gstack-pregate` beside one refuses). An unreadable file, a dangling link on its path, `sealed` or `price_file` refuses with `PREGATE_CONFIG_INVALID`; a base copy git cannot read refuses with `POLICY_SOURCE_UNAVAILABLE` unless `git ls-tree` confirms it is absent. +- **`local`** (arlophoenix): `/` lanes a local run satisfies run here instead of becoming `requires-remote`. Under 8 cores with a `remote_gate` or remote `test_backend`, the small-machine obligation still stands. +- **`lib/pregate/index.ts`** (arlophoenix): the record names the user file and its digest, and `verify` goes `PREGATE_STALE` when it is edited, deleted, or appears after a run under the defaults. Fix texts for missing lanes, strays, stalls and `remote_gate` name the active policy file. + +### Fixed +- **`lib/pregate/context.ts`**: a user policy whose path reaches the checkout through any symlink is ignored. Before, only the lexical path and the final target were checked, so a state root linked into the checkout, holding a link back out to another existing policy, let a branch choose a more permissive file without changing its bytes. +- **`lib/pregate/index.ts`**: `verify` goes `PREGATE_STALE` when a link on the way to the absent user path starts pointing nowhere. A fresh run refuses that state, but verify looked only at the leaf, so an old defaults pass stayed publishable. The loader and verify now share one absence check. + +### Tests +- `test/pregate-user-policy.test.ts` (new, arlophoenix) covers the user file, `local`, the refusals, `.env` isolation and stale receipts. New cases: a link chain through the checkout is refused for both a linked state root and a single in-repo hop, while a direct outside link is still read; and verify goes stale on a dangling parent, with the plain-absent path as the control. +- `test/pregate-rust.test.ts`: the two cargo-lane cases skipped on Windows carry a `TODO(#3380)` naming the gap. + +### Gate integrity +- No threshold, budget, timeout, trial count, case kind or skip changed. The user file can relax lanes only where the base has no policy at all, and only from outside the repository; the two fixes make that containment stricter. +- No new dependency, no network call, no workflow change. + ## [1.91.186.0] - 2026-10-24 **Approving an autoplan run no longer fails one time in ten because its run id looks like a card number.** Today's daily flaky-fix pass found `test/capy-captain-flow.test.ts` failing once and passing on the serial retry in 7 of the 57 Free Tests runs from 2026-10-21 to 2026-10-23, one of them a push to main (39878113535). Every failure had the same assertion: `gstack-approvals import` refused the approval row with "field ask: pii.cc". The default autoplan run id is `run-YYYYMMDDHHMMSS`, the import writes it into the row's ask and source, and the credit-card rule saw those 14 digits as a card number whenever they passed the Luhn check, which a timestamp does about one time in ten. A real user's approval import failed the same way. diff --git a/docs/pregate.md b/docs/pregate.md index f12f0eb56..173096b3d 100644 --- a/docs/pregate.md +++ b/docs/pregate.md @@ -85,9 +85,12 @@ else a gstack plugin's data directory, else `~/.gstack` ([docs/state-root.md](st (never under `--policy-from head`), it lives outside the repository so a PR still cannot reach it, and the record names it (`policy user `) and fingerprints its bytes (`policy_digest`, and each check's `inputs.policy`); `verify` and the -ship receipt treat an edited or deleted user file, or one that appears after a -run under the defaults, as `PREGATE_STALE`. A path that is relative or resolves -inside the repository is ignored, since a PR could commit it; `--explain` says +ship receipt treat an edited or deleted user file, or one that appears (or a +link on its path that starts pointing nowhere) after a run under the defaults, +as `PREGATE_STALE`. A path that is relative, resolves inside the repository, or +reaches it through any symlink on the way (a state root linked into the +checkout, or a link hop inside it, even when the last hop points back outside) +is ignored, since a PR could commit it or choose the file; `--explain` says so, or says `(absent)` when no file is there. The slug and state root come from the caller's environment, never the checkout's `.env`: the bin's shebang passes `--no-env-file`, and a bare `bun gstack-pregate` launched beside any `.env` file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 64de9b6be..4241cfdb7 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2947,8 +2947,9 @@ or exists but cannot be read (a dangling symlink on its path counts). **Fix.** `gstack-pregate validate`. `secrets` and required-lane obligations cannot be downgraded; every other check id in `warn` is honoured only from the base branch's copy or the user-owned file. For the user-owned file, edit it -directly at the path the message names. A user file inside the repository is -ignored rather than read (`--explain` names it). +directly at the path the message names. A user file inside the repository, or +reached through a symlink into it, is ignored rather than read (`--explain` +names it). ### `PREGATE_SECRETS` @@ -3061,8 +3062,9 @@ file with `--receipt`, or let `gstack-pregate --stage tests --remote --spend-cap **Meaning.** `pregate.json` was recorded against a different working-tree fingerprint than the one being published (the final stamp, a regen, any edit), -or the user-owned policy it ran under was edited or deleted (or one appeared -after a run under the defaults). +or the user-owned policy it ran under was edited or deleted (or one appeared, +or a symlink on its path started pointing nowhere, after a run under the +defaults). **Fix.** Rerun `gstack-pregate` after the final generation and stamp; Step 18 refuses to write the ship receipt otherwise.