mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-13 20:07:08 +01:00
ship accept: a lanes-only head policy gates the final tree; owner-granted test-lane exception (GST-1595) (#4061)
* ship accept: a lanes-only head policy gates the final tree; name bun-hidden JUnit suites; owner-granted test-lane exception (GST-1595) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Each excepted file is preflighted on its own and must run a counted test; the preflighted files count toward the lane's tests_ran; grants are kept per head and file set; lanesOnlyHeadPolicy compares .gstack/generated.json with the fork copy. Review-Finding: v1/outside-voice#1 reader=outside-voice Review-Finding: v1/outside-voice#2 reader=outside-voice Review-Finding: v1/outside-voice#3 reader=outside-voice Review-Finding: v1/adversarial-native#2 reader=adversarial-native Review-Finding: v1/adversarial-native#4 reader=adversarial-native Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: lanesOnlyHeadPolicy compares generated.json with the base tip again A --policy-from head run loads the regen registry from the branch (lib/pregate/context.ts), so a branch whose .gstack/generated.json differs from main's tip keeps the base policy; comparing with the fork copy would gate under a stale registry. Review-Finding: p2/red-team#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: troubleshooting entries for the accept head policy and test-lane exceptions (GST-1595) 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:
1 parent
3c7ff030b2
commit
05114c8049
20 files changed
+624
-48
No files matched your search
+40
-1
@@ -39,7 +39,24 @@ read from the branch under `--policy-from head`. So an allowlist entry or a
|
||||
`[]` declaration a branch adds counts only once it is on the base, and a lane it
|
||||
adds counts on the base policy only once it lands; `PREGATE_NO_LANE`'s fix line
|
||||
says so when the branch edits the file
|
||||
([troubleshooting](troubleshooting.md#pregate-no-lane-own-policy)). An unresolvable base exits 3 with
|
||||
([troubleshooting](troubleshooting.md#pregate-no-lane-own-policy)).
|
||||
<a id="accept-head-policy"></a>/ship's reruns on the final tree (`gstack-ship-release accept`, and `finish`
|
||||
when it reruns the whole pregate on the stamped tree) choose the policy themselves
|
||||
(GST-1595). When the branch's `pregate.json` differs from the copy it forked from
|
||||
(the base tip's when the fork has none) in nothing but added test lanes, and
|
||||
`.gstack/generated.json` matches the base tip's copy (a head-policy run loads the
|
||||
regen registry from the branch, so a copy that main changed after the fork keeps
|
||||
the base policy), they pass `--policy-from head` and print
|
||||
`POLICY: head (unreviewed) .gstack/pregate.json: adds <n> test lane(s) over
|
||||
origin/<base> and changes nothing else, so the gate is only stricter`. So a PR
|
||||
that adds a runtime file and its own lane line passes `accept` without a
|
||||
lanes-only PR first (gstack-evals #240). Any other difference (a removed or
|
||||
shrunk lane, a `[]` declaration, `warn`, `timeout_s`, `ship_lanes_from`, any
|
||||
other key, a changed `generated.json`, a file that does not parse) keeps the
|
||||
base policy as before, with `POLICY: base origin/<base>:.gstack/pregate.json (the
|
||||
branch's copy <what differs>, so it does not count here; land it on <base>
|
||||
first)`. A branch that leaves the file alone prints no `POLICY:` line.
|
||||
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.
|
||||
|
||||
@@ -1202,6 +1219,28 @@ the same run. `gstack-ship-receipt write` records the carried runs as
|
||||
`ci_carried: [{ lane, run, attempt, tree }]`, and the `SHIP_RECEIPT:` line names
|
||||
them (`ci_carried=<lane>:<run>/<attempt>`).
|
||||
|
||||
<a id="carry-imported-tests"></a>**An inventory file CI's JUnit tree never lists (GST-1595).** bun registers
|
||||
the tests of a test file another test file imports under the importer, so the
|
||||
imported file's own JUnit suite never appears in `tree.json` and the carry
|
||||
refuses with `inventory_uncovered`. For each such file another inventory file
|
||||
imports by a relative path (`import`, `import()`, `require`), the refusal adds
|
||||
`CARRY_DIAGNOSIS: bun registers <imported> under <importer>; its JUnit suite
|
||||
won't appear` (`lib/ci-carry.ts` `importedTestLines`), printed by `gstack-evidence
|
||||
record-ci`, `gstack-ship-preflight` and `accept`; the remote gate's
|
||||
`inventory_uncovered` reason carries the same words. The fix is usually to move
|
||||
the shared code out of the `.test.` file. When that can't happen in this PR,
|
||||
the owner can grant a [test-lane exception](ship-policy.md#test-lane-exceptions)
|
||||
for exactly those files at the pushed head: `record-ci` then runs each of them
|
||||
here, one file at a time, with the entry's `per_file` runner (label
|
||||
`<lane>.excepted`) and carries the lane only when every file's run passes with a
|
||||
counted test; the lane's `tests_ran` counts those files beside the ones CI
|
||||
covered. A failed or zero-test run of any file refuses with
|
||||
`exception_preflight_failed`, and a grant at another head or for another file
|
||||
set binds nothing. The CI row and the receipt's `ci_carried` entry
|
||||
keep the grant and the preflight (`exception`), and `accept` prints
|
||||
`TEST_LANE_EXCEPTION: <files> at <head12>: preflight <label> ran <n> test(s)
|
||||
here; granted by <who> at <time>: <reason> (approval <id>)`.
|
||||
|
||||
**`gstack-ci-carry`.** `find [--profile <name>]… [--command <exact lane command>
|
||||
--allow-repo-commands]` prints the verdict for HEAD; `wait [--timeout <s>]`
|
||||
polls the newest run at HEAD until it completes (run it in the background) and
|
||||
|
||||
@@ -1645,6 +1645,38 @@ Pre-Landing Review and the PR body, `gstack-ship-receipt write` records them as
|
||||
`ruled_notes` (each with its `head`) and refuses a `--body` that does not carry each
|
||||
line word for word, and the READY block prints each as `RULED_NOTE:`.
|
||||
|
||||
<a id="test-lane-exceptions"></a>
|
||||
**Test-lane exceptions (GST-1595).** A lane `accept` carries from CI refuses with
|
||||
`inventory_uncovered` when an inventory file is in no run's `tree.json`; when bun
|
||||
hid it under an importing test file, the refusal says so
|
||||
([pregate.md](pregate.md#carry-imported-tests)). The owner, never the lane, can
|
||||
grant that the lane counts anyway at one pushed head, for exactly those files,
|
||||
through the same approval record as a ruling:
|
||||
|
||||
```bash
|
||||
gstack-approvals lane-exception --head <pushed head> --files <f1,f2> \
|
||||
--reason '<why, one line>' --words '<the owner's exact words>' --by '<who granted>'
|
||||
```
|
||||
|
||||
The row is an approved `gstack-approvals` row with scope `test_lane_exception <head>
|
||||
<files>` and ask `test_lane_exception at <head>: <files>: <reason>`
|
||||
(`lib/test-lane-exception.ts`). `lane-exception` refuses a head that is not a
|
||||
commit, a file with whitespace, a multi-line reason, a `--by` naming gstack and
|
||||
any grant while this checkout holds an open /ship run's marker, and `add` refuses
|
||||
the scope, so the lane's own runner cannot write one; a row by gstack is ignored
|
||||
wherever it came from. `gstack-evidence record-ci` binds a grant only when its
|
||||
head is HEAD and its files are exactly the uncovered set, and only after a local
|
||||
preflight of each of those files, one at a time, with the lane's `per_file` runner
|
||||
passes with a counted test at that head (a lane with no `per_file` runner cannot
|
||||
use one). Grants are kept per head and file set (the newest wins), so two lanes at
|
||||
one head each bind their own. Nothing about it is
|
||||
hidden: `accept` prints `TEST_LANE_EXCEPTION: …` and names the body line in its
|
||||
`NEXT:`, the receipt's `ci_carried` entry records `exception` (head, files, by,
|
||||
reason, time, approval id and the preflight's label and test count),
|
||||
`gstack-ship-receipt write --body` refuses a body that does not carry
|
||||
`Test-lane exception: …` word for word, and the READY block prints each as
|
||||
`TEST_LANE_EXCEPTION:`. A new push needs a new grant at the new head.
|
||||
|
||||
`gstack-review-scope summary --since <REVIEW_SINCE> --run <run dir>` reads the branch's review-log
|
||||
rows for this invocation and its dispatch ledgers, and writes the counters the ship receipt carries
|
||||
(`gstack-ship-receipt write … --review-loop @<file>`): `review_loop: { passes,
|
||||
|
||||
+31
-6
@@ -1940,8 +1940,17 @@ The guide is `capy/skill/gstack/captain.md`.
|
||||
`--by`), holds a value outside its enum (`--outcome approved|rejected|pending|standing`,
|
||||
a `--date` that is not a real `YYYY-MM-DD` day) or exceeds a length cap (ask, scope and
|
||||
pending 300 characters, words 2,000). Exit 2. Nothing was written.
|
||||
`rule` and `lane-exception` refuse with this code too: a `--head` that is not
|
||||
a commit in the repository, a reason that is missing or holds a newline or
|
||||
control character, a `--by` naming gstack, any record while this checkout
|
||||
holds an open /ship run, and for `lane-exception` a `--files` list that is
|
||||
empty or holds whitespace. `add` refuses a `ruled_note` or
|
||||
`test_lane_exception` scope ([ruled notes](ship-policy.md#ruled-notes),
|
||||
[test-lane exceptions](ship-policy.md#test-lane-exceptions)).
|
||||
|
||||
**Fix.** Rerun with every required flag inside the caps the line names.
|
||||
**Fix.** Rerun with every required flag inside the caps the line names. A
|
||||
ruling or a test-lane exception is the owner's: record it from the owner's
|
||||
session, outside the lane's /ship run.
|
||||
|
||||
<a id="approval-record-secret"></a>
|
||||
### `gstack-approvals: ... (APPROVAL_RECORD_SECRET)`
|
||||
@@ -3496,10 +3505,17 @@ scan tags included), or whose product patch is not the receipt's and the change
|
||||
since it is more than plain docs. A docs-only change after the read carries the
|
||||
record only when it changes no binary file, adds no numeric claim, touches no
|
||||
results, prereg or eval path, and names no money, security, secret or auth topic; any other docs change
|
||||
needs the reader again (GST-1195).
|
||||
needs the reader again (GST-1195). With `--body`, write also refuses, writing
|
||||
nothing, a body that does not carry each line the receipt requires as one line,
|
||||
word for word: every `Ruled note: …` line `gstack-approvals rulings` prints,
|
||||
finish's `MECHANICAL:` lines as `Mechanical retake: …`, and accept's
|
||||
`TEST_LANE_EXCEPTION:` lines as `Test-lane exception: …`
|
||||
([ruled notes](ship-policy.md#ruled-notes),
|
||||
[test-lane exceptions](ship-policy.md#test-lane-exceptions)).
|
||||
|
||||
**Fix.** Regenerate it with `gstack-ship-receipt write`; never hand-edit the
|
||||
block. For the `--head` refusal, check out that commit and write there. For a
|
||||
block. For a missing body line, add the line the refusal quotes to the body and
|
||||
write again. For the `--head` refusal, check out that commit and write there. For a
|
||||
targeted refusal, run the targeted reader on HEAD, `gstack-review-dispatch verify
|
||||
--targeted-out .gstack/tmp/targeted.json`, and write again with
|
||||
`--review-targeted @.gstack/tmp/targeted.json`.
|
||||
@@ -4146,6 +4162,14 @@ two routes:
|
||||
An allowlist entry or a `[]` declaration the branch adds to its own
|
||||
`pregate.json` counts only once it is on the base.
|
||||
|
||||
/ship's own reruns on the final tree (`gstack-ship-release accept`, and
|
||||
`finish` when it reruns the whole pregate) pass `--policy-from head`
|
||||
themselves when the branch's copy differs from the one it forked from in
|
||||
nothing but added test lanes and `.gstack/generated.json` matches the base
|
||||
tip's, and print `POLICY: head (unreviewed) …`; any other difference keeps the
|
||||
base policy and prints `POLICY: base … (the branch's copy <what differs> …)`
|
||||
([accept's policy choice](pregate.md#accept-head-policy), GST-1595).
|
||||
|
||||
<a id="pregate-head-policy-loosens"></a>
|
||||
### `PREGATE_HEAD_POLICY_LOOSENS`
|
||||
|
||||
@@ -6536,9 +6560,10 @@ body that names a counter the receipt records (`passes=3` against
|
||||
against `gate.passed: 41`) with a different value. The receipt is written from
|
||||
the run; the prose was typed, so a reader of the body would learn a number the
|
||||
run did not produce. The `PROSE:` line names each field with both values. The
|
||||
`## Approvals` table `gstack-approvals` renders, the `Review-loop exception: …`
|
||||
and `Documentation exception: …` lines the receipt's exceptions require, and
|
||||
the inside of a closed code fence are never read as claims.
|
||||
`## Approvals` table `gstack-approvals` renders, the `Review-loop exception: …`,
|
||||
`Documentation exception: …`, `Ruled note: …`, `Mechanical retake: …` and
|
||||
`Test-lane exception: …` lines the receipt requires, and the inside of a
|
||||
closed code fence are never read as claims.
|
||||
|
||||
**Fix.** Correct the body to the receipt's number. When the receipt is the
|
||||
stale one (the run was repeated), rerun `/ship` so the receipt is rewritten
|
||||
|
||||
Reference in new issue
Block a user