v1.91.257.0 feat(pregate): clear --receipt accepts a remote_gate receipt from a stamp-only predecessor tree (gbrain gate-ahead) (#3486)

* feat(pregate): clear --receipt accepts a remote_gate receipt from a stamp-only predecessor tree (gbrain gate-ahead)

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

* fix: pre-landing review fixes (hex object ids, stamp_sensitive_gates and base-branch refusals, one substitution type, docs)

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

* fix(pregate): harden the stamp-only diff read (fixed presentation, diff-tree reconciliation), refuse an unreadable base policy, check the receipt before git work, bind verify to the receipt tree; docs audit corrections

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

* test(pregate): the stamp-only suite expects the Linux-host lanes verdict; skip itself on win32 like test/pregate-remote.test.ts

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-10-26 22:09:32 +00:00
1 parent 576839cb8c
commit 7f53ddb1f3
13 files changed
+612 -28

No files matched your search

+89 -1
View File
@@ -357,7 +357,9 @@ the supervision above. Close the obligation one of two ways:
- after running it, `gstack-pregate clear --remote pregate.json/remote_gate
--receipt <file>` with the gate's output, validated as `--remote` validates it
(a run URL is refused, `PREGATE_RECEIPT_INVALID` for a bad receipt), or
(a run URL is refused, `PREGATE_RECEIPT_INVALID` for a bad receipt; a receipt
for a tree that differs from this one only by the repo's stamp rule is
accepted and recorded, see [below](#remote-gate-stamp-only)), or
`gstack-pregate clear --remote test_backend/<backend> --run-url <url>`, which
has no machine-checkable receipt and is recorded as `attested:<url>`; or
- `gstack-pregate --stage tests --remote --spend-cap <usd> --allow-repo-commands`,
@@ -374,6 +376,92 @@ the supervision above. Close the obligation one of two ways:
`spent, reserved, unknown`: a gate that prints no `spent=` is `unknown`,
counted at the cap.
<a id="remote-gate-stamp-only"></a>
### A receipt from a stamp-only predecessor tree (gate ahead of the stamp)
The receipt `clear --receipt` validates names the tree the gate tested, and
pregate.json names the tree being published. A repo whose full gate runs before
its turn in the queue ("gate ahead": the gate tests commit G, then /ship's
restamp moves the version files, so the head H it publishes is G plus the
stamp) could never clear the obligation with that run, and paid a second full
gate on H for a diff that was nothing but stamps. `clear` now accepts that
receipt for H when the two trees differ only as a stamp does:
- gstack diffs the gated tree against pregate.json's tree
(`git diff --unified=0 <gated> <head>`, both trees as objects in this
repository: the gated commit's tree once it is fetched, and the head tree
`bin/gstack-wtree` wrote) and classifies every differing file with the tree
receipt's own rule, `lib/tree-receipt.ts` under `.gstack/ship-policy.json` on
`origin/<base>` ([ship-policy](ship-policy.md#tree-receipt)). That is the rule
`gstack-tree-receipt`, `gstack-pregate regate` and the train's
`verify --train` apply: with `stamp_files` set only those files can be
stamps, each hunk in them must be the exact old → new rewrite under
`stamp_patterns` (literal `{version}` templates) or the built-in forms
(`(was X)`, `since: X`, `vX`), and the version file, `package.json`'s
`version`, a lockfile's root version keys and a CHANGELOG re-heading have
their own built-in rules. There is no second list and no looser rule here:
the file verdicts `clear` reaches on a pair are the verdicts
`gstack-tree-receipt --gated G --current H` reaches, and its gate rule holds
too: a policy whose `stamp_sensitive_gates` names the test lane (`tests`, as
gstack's own does because its tests read VERSION and the agents digest) is
refused outright, since the tree receipt never marks such a lane reusable.
The policy is the one on this repository's base branch (`origin/HEAD`, as
`regate` reads it) at its current tip, never a branch pregate.json names: a
pregate.json recorded against another base is refused. What the receipt does
not compare, exactly as a same-tree receipt does not, is the gate's execution
identity (Bun, OS, lockfiles); that stays the evidence bundle's job.
- Accepted, the obligation's receipt reads `remote-gate tree=<gated>
tests=<n> accepted-for=<head> stamp-only=<file count> receipt=<file>
sha256=<digest>`, and pregate.json records the substitution beside it:
`substitution: { gated_tree, accepted_for, policy, files: [{ path, rule }] }`,
the policy label (`origin/<base>@<sha>:.gstack/ship-policy.json`) and every
differing path with the rule that classed it (`version-file`,
`version-manifest`, `manifest-version`, `lockfile-root-version`,
`changelog-heading`, `stamp_files`, `stamp_paths`, `release_outputs`). `clear` prints the same
as `PREGATE_RECEIPT_SUBSTITUTED: …`, `verify` repeats it on its ok line, and
the ship receipt carries the receipt string. The receipt's own tests and
failed checks apply as before (its exit status is checked only when
`--remote` runs the gate itself), and `verify` refuses a recorded
substitution whose `gated_tree` is not the receipt string's `tree=`.
- Refused, with `PREGATE_RECEIPT_INVALID` and a fix naming the offending
path: any differing path outside the rule (`lib/x.ts (stamp_files: not in the
repo's stamp_files allowlist, line 1)`), a stamp path whose hunk is not the
stamp rewrite (`plugin.json (stamp_files: "…description…" -> "…", line 4)`),
a gated tree that is not a 40- or 64-hex object id or not an object here
(fetch the gated commit first), a base branch whose policy cannot be read
or is invalid, a pregate.json recorded with `--base` against another branch
than this repository's base (`origin/HEAD`), a policy whose
`stamp_sensitive_gates` names the test lane (`tests` or `remote_gate`;
gstack's own policy does, so gstack itself never substitutes), or a patch
that does not account for every path `git diff-tree` lists between the two
trees. The version rule is the tree
receipt's: a `stamp_files`, `stamp_paths` or `release_outputs` hunk is a stamp
only as the rewrite from the gated tree's version to the head's (so two trees
at the same version refuse such a hunk as `other`), while the built-in
version-file, manifest, lockfile and CHANGELOG rules compare content and do
not check the direction of the version move. The obligation stays uncleared; rerun
the gate on the head, or make the extra change a stamp under the repo's
rule.
A consuming repo declares its stamp paths once, in `.gstack/ship-policy.json`
on its base branch (`stamp_files`, `stamp_patterns`, `changelog`), the same
file the tree receipt and restamp already read; nothing is declared in
`.gstack/pregate.json` for this. gbrain's is in
[gbrain worked configs](#gbrain-worked-configs).
The gate must have run on a clean, committed checkout: `PREGATE_TREE` is the
`bin/gstack-wtree` fingerprint, which equals the commit's tree only when nothing
was uncommitted or untracked there, and `clear` can compare only trees that are
objects in this repository. A receipt whose tree is not here is refused, never
guessed. The sequence that reuses a gate-ahead run, on a machine under 8 cores:
```bash
git fetch origin <gated sha> # the gated commit's tree must be here
gstack-pregate --allow-repo-commands # on the stamped head: records pregate.json/remote_gate uncleared
gstack-pregate clear --remote pregate.json/remote_gate --receipt <the gate's output for the gated tree>
gstack-pregate verify # PREGATE_VERIFY: ok …; pregate.json/remote_gate: gated tree … accepted for tree …
```
gstack's own example: `.gstack/pregate.json` declares
`"remote_gate": "bash scripts/ubicloud/pregate-remote.sh"`. With
`UBICLOUD_API_KEY` set the script runs the selected files on one Ubicloud VM
+35 -4
View File
@@ -3122,9 +3122,32 @@ reports failures. The obligation stays uncleared. A run URL alone never clears
this obligation; a `test_backend/<backend>` obligation takes a run URL, recorded
as attested rather than validated.
Another tree is accepted in exactly one case: it differs from `pregate.json`'s
tree only in the repo's stamp rule (`.gstack/ship-policy.json` on the base
branch: `stamp_files`, `stamp_patterns`, the built-in version, manifest,
lockfile and CHANGELOG forms), judged by the tree receipt's own classifier on a
`git diff` of the two trees ([the gate ran ahead of the
stamp](pregate.md#remote-gate-stamp-only)). The refusal line then says `and the
trees differ beyond the stamp rule of <policy>: <path> (<rule>: <why>, line
<n>)`, and the path it names is the one that is not a stamp: a file outside
`stamp_files`, or a stamp file with a hunk that is not the old → new rewrite.
`the gated tree <t> is not an object in this repository` means the gated
commit is not fetched here, so the two trees cannot be compared. Three more
refusals close the substitution without classifying anything: `lists the test
lane as stamp-sensitive (stamp_sensitive_gates: tests)` (the base policy says
the test tier reads the stamps, as gstack's own does, so no run on another tree
covers this one; rerun the gate on the head), `pregate.json was recorded against
origin/<a>, not this repository's base origin/<b>` (rerun `gstack-pregate --base
<b>`, or `git remote set-head origin <a>` when `origin/HEAD` was unset), and
`.gstack/ship-policy.json on origin/<base>@<sha> is invalid` or `could not be
read` (fix the policy on the base, or fetch it).
**Fix.** Run the remote gate on the tree pregate tested and pass its output
file with `--receipt`, or let `gstack-pregate --stage tests --remote --spend-cap
<usd> --allow-repo-commands` run and validate it.
<usd> --allow-repo-commands` run and validate it. For a gate-ahead receipt:
fetch the gated commit (`git fetch origin <sha>`) and clear again, or make the
named path a stamp under the repo's rule; a receipt is never accepted while any
differing path is a change.
<a id="pregate-no-policy"></a>
### `PREGATE_NO_POLICY`
@@ -3190,12 +3213,20 @@ refuses to write the ship receipt otherwise.
### `PREGATE_REMOTE_UNCLEARED`
**Meaning.** A `requires-remote` obligation (a platform lane this machine cannot
run, named by `workflow.yml/job`) has no receipt.
run, named by `workflow.yml/job`, or the small-machine `pregate.json/remote_gate`
tier) has no receipt; or a `remote_gate` receipt accepted from a stamp-only
predecessor tree records `accepted_for` as a tree other than `pregate.json`'s,
or a `gated_tree` other than its receipt string's `tree=` (the file was edited
after `clear`), so it clears nothing here.
**Fix.** Dispatch that job, then
`gstack-pregate clear --remote <workflow.yml/job> --run-url <url>` (or
`--bundle-lane <label>` from an evidence bundle). The ship receipt carries each
obligation with its receipt.
`--bundle-lane <label>` from an evidence bundle). For `pregate.json/remote_gate`,
`clear --remote pregate.json/remote_gate --receipt <gate output>`: the gate's
own run on this tree, or a run on a predecessor tree that differs only by the
repo's stamp rule ([gate ahead of the stamp](pregate.md#remote-gate-stamp-only));
`verify` then prints the accepted substitution on its ok line. The ship receipt
carries each obligation with its receipt.
<a id="issue-unreadable"></a>
### `ISSUE_UNREADABLE`