Merge main e85005482 (v1.93.27.0, train 94) into wave/review-53cfb5bfe011 (rule 19 grant)

Conflicts resolved keeping both sides: bin/gstack-review-dispatch (main's targetedRead record plus the wave's exclude digest on the rendered event); bin/gstack-ship-receipt (main's receiptTargetedAtHead and targetedClassesSince plus the wave's receiptReviewAtHead and receiptExclude; both help paragraphs); docs/troubleshooting.md (main's targeted-refusal fix plus the wave's review_exclude, REVIEW_DIFF_TOO_LARGE, REVIEW_DIFF_UNREADABLE and REVIEW_EXCLUDE_REFERENCE entries); ship/DRIVER.md.tmpl (main's line, DRIVER.md 12,241 B); test/review-packets-removed.test.ts (both replay lists, the wave's placeholder brief-body hashes under main's GST-986 test). test/review-targeted.test.ts follows main's DRIVER wording ([--review-targeted @<f>]), which main's own copy of that check in test/review-targeted-record.test.ts pins.
This commit is contained in:
garrytan committed 2026-11-02 23:50:02 +00:00
commit feeec2e760
74 files changed
+1618 -316

No files matched your search

+46 -42
View File
@@ -1,7 +1,9 @@
# CHANGELOG + VERSION style
Moved verbatim from CLAUDE.md (token-load reduction). Read this BEFORE
writing any `## [X.Y.Z]` CHANGELOG entry.
writing a CHANGELOG entry. In gstack the entry is a fragment,
`changelog.d/<branch-slug>.md`, and the merge step gives it its `## [X.Y.Z.W]`
heading ([changelog fragments](ship-policy.md#changelog-fragments)).
## Versioning and branch-scoped entries
@@ -9,15 +11,15 @@ Moved verbatim from CLAUDE.md (#2096 size limit). The autonomous version
policy (choose versions without asking; default to PATCH) stays in CLAUDE.md
as the single source.
**Versioning invariant (workspace-aware ship).** VERSION is a monotonic ordered
release identifier, not a strict semver commitment. The bump level
(major/minor/patch/micro) expresses intent at ship time. Queue-advancing past a
claimed version within the same bump level is explicitly permitted — if branch A
claims v1.7.0.0 as a MINOR and branch B is also a MINOR, B lands at v1.8.0.0
(still a MINOR relative to main). Downstream consumers must NOT rely on
"MINOR = feature-only, PATCH = fix-only" as a strict contract. This is why
`bin/gstack-next-version` advances within the chosen bump level rather than
repicking the level when collisions happen.
**Versioning invariant.** VERSION is a monotonic ordered release identifier,
not a strict semver commitment. The bump level (major/minor/patch/micro)
expresses intent. Versions are assigned at merge, in merge order: each stamp
takes the version before it plus the effective bump, which is the policy's
PATCH raised by any folded fragment's `bump:` front matter. If PR A and PR B
both ask for MINOR and land back to back on v1.6.0.0, A lands at v1.7.0.0 and
B at v1.8.0.0 (still a MINOR relative to the version before it). Downstream
consumers must NOT rely on "MINOR = feature-only, PATCH = fix-only" as a
strict contract.
**package.json carries the npm-valid translation, not VERSION verbatim.**
VERSION stays the 4-digit source of truth (e.g. `1.67.0.0`); package.json and
@@ -26,18 +28,25 @@ translation (`1.67.0`), and lockfile `version` fields sync only when the
lockfile already exists. `bin/gstack-version-bump` (via `lib/version-source.ts`)
owns the translation and judges drift on translated forms — do NOT "fix" the
apparent mismatch by hand, and do not write a 4-digit version into
package.json (npm rejects it). Rationale and translation rules live in the
package.json (npm rejects it). A branch never writes either one: the merge
stamp does, through `bin/gstack-version-bump`. Rationale and translation rules live in the
`lib/version-source.ts` header; `test/gstack-version-bump.test.ts` pins the
contract.
**VERSION and CHANGELOG are branch-scoped.** Every feature branch that ships gets its
own version bump and CHANGELOG entry. The entry describes what THIS branch adds —
not what was already on main.
**The CHANGELOG entry is branch-scoped; the version is not.** Every feature
branch that ships writes one entry as `changelog.d/<branch-slug>.md`: the entry
text only, with no `## [x]` heading, and `bump: minor` or `bump: major` front
matter only when the change warrants more than a PATCH. A branch never edits
VERSION, package.json's version or a released CHANGELOG.md section. At merge,
`train land` (or `gstack-ship-queue land`) assigns the version, folds the
fragment into one `## [<version>] - <date>` section at the top of CHANGELOG.md
and deletes the file. The entry describes what THIS branch adds — not what was
already on main.
**The CHANGELOG entry is the diff between main and the shipping branch — what users
get when they upgrade. NOT how the branch got there.** A reader landing on the entry
should learn what they can do now that they couldn't before; they should not learn
about the branch's internal version bumps, the bugs we caught and fixed mid-branch,
about versions the branch carried during development, the bugs we caught and fixed mid-branch,
the plan reviews we ran, or the commits we squashed. That is branch development
narrative. It belongs in PR descriptions and commit messages, not CHANGELOG.
@@ -62,29 +71,28 @@ the user gets; the path to that system is invisible to them.
**When to write the CHANGELOG entry:**
- At `/ship` time (Step 13), not during development or mid-branch.
- The entry covers ALL commits on this branch vs the base branch.
- `/ship` lints the lines the branch added with `bin/gstack-prose-lint CHANGELOG.md --base origin/<base> --gate`,
and the Quality gate workflow gates the `## [Unreleased]` block the same way. Rewrite each `PROSE:` line
it names ([docs/troubleshooting.md](troubleshooting.md#prose-lint-found)).
- Never fold new work into an existing CHANGELOG entry from a prior version that
already landed on main. If main has v0.10.0.0 and your branch adds features,
bump to v0.10.1.0 with a new entry — don't edit the v0.10.0.0 entry.
- `/ship` lints the fragment with `bin/gstack-prose-lint changelog.d/<branch-slug>.md --gate`.
Rewrite each `PROSE:` line it names ([docs/troubleshooting.md](troubleshooting.md#prose-lint-found)).
- Never fold new work into a CHANGELOG.md section that already landed on main.
New work goes in this branch's fragment, and the merge stamp gives it its own
section at the next version.
**Key questions before writing:**
1. What branch am I on? What did THIS branch change?
2. Is the base branch version already released? (If yes, bump and create new entry.)
3. Does an existing entry on this branch already cover earlier work? (If yes, replace
it with one unified entry for the final version.)
2. Does this change warrant more than a PATCH? (If yes, open the fragment with
`bump: minor` or `bump: major` front matter; otherwise add none.)
3. Does this branch's fragment already cover earlier work? (If yes, rewrite it as
one unified entry.)
**Merging main does NOT mean adopting main's version.** When you merge origin/main into
a feature branch, main may bring new CHANGELOG entries and a higher VERSION. Your branch
still needs its OWN version bump on top. If main is at v0.13.8.0 and your branch adds
features, bump to v0.13.9.0 with a new entry. Never jam your changes into an entry that
already landed on main. Your entry goes on top because your branch lands next.
**Merging main never gives your branch a version.** When you merge origin/main into
a feature branch, main may bring new CHANGELOG sections and a higher VERSION. Take them
exactly as main has them. Your branch still carries only its fragment, and never jams its
changes into a section that already landed on main; the merge stamp puts its section on top.
**After merging main, always check:**
- Does CHANGELOG have your branch's own entry separate from main's entries?
- Is VERSION higher than main's VERSION?
- Is your entry the topmost entry in CHANGELOG (above main's latest)?
- Is your fragment still in `changelog.d/`, with its own text, separate from main's sections?
- Are VERSION, package.json's version and every released CHANGELOG.md section exactly
main's? (The Version Gate check requires this of a head the merge step did not stamp.)
If any answer is no, fix it before continuing.
**After any CHANGELOG edit that moves, adds, or removes entries,** immediately run
@@ -93,13 +101,9 @@ order. Gaps between version numbers are fine. A branch that ships at v1.6.4.0 wi
a prior v1.5.2.0 or v1.5.3.0 entry on main is correct — those were branch-internal
version numbers that never landed. Do not back-fill gaps with placeholder entries.
**Never orphan branch-internal versions.** If your branch bumped VERSION several times
during development (v1.5.1.0 → v1.5.2.0 → v1.6.4.0, say) and those earlier entries were
never released to main, the final ship consolidates ALL of them into a single entry at
the final version (v1.6.4.0). Collapse them — delete the old entries and move their
content into the final entry, re-version table columns accordingly. Readers see one
release, not a branch diary. Gaps are fine (v1.6.3.0 → v1.6.4.0 with no v1.5.x
in between on main is correct).
**Keep one entry per branch.** When the branch's work grows after its fragment was
written, rewrite that fragment as one unified entry instead of adding a second file
beside it. Readers see one release, not a branch diary.
CHANGELOG.md is **for users**, not contributors. Write it like product release notes:
@@ -122,9 +126,9 @@ we got here. Keep out of the CHANGELOG, always:
documents what DID ship, not what MIGHT ship.
- Version-bump housekeeping when no user-facing work actually landed.
If the diff between the base branch version and this version has no user-facing change
(only merges, only CHANGELOG edits, only placeholder work), the honest entry is one
sentence: "Version bump for branch-ahead discipline. No user-facing changes yet." Stop
If the diff between the base branch and this branch has no user-facing change
(only merges, only CHANGELOG edits, only placeholder work), the honest fragment is one
sentence: "No user-facing changes yet." Stop
there. Do not pad. Do not explain the plan that will ship eventually. Do not narrate
the branch's history. When real work lands, the entry will replace this at /ship time.
+41 -4
View File
@@ -89,7 +89,7 @@ the byte-pinned `/ship` renders in `test/fixtures/golden/`).
| `timeout_s` | seconds each runner process may take (the unit-file group, each serial file, each guard without its own `timeout_s`, and the remote gate's deadline) when `--timeout` is not passed; 1 to 3600, default 600. `gstack-ship-preflight` and the `finish` fallback run pregate without `--timeout`, so this is how a repo with long lanes sets theirs |
| `remote_gate` | the repo's remote test gate command; under 8 cores the local test tier becomes a `requires-remote` obligation naming it, and `--remote` runs it (see [remote gate](#remote-gate)) |
| `run_profiles` | name → `{ workflow, events, jobs, artifact, max_age_hours?, os? }`: the GitHub Actions runs `clear --run` verifies as evidence on the identical tree; a profile named after a lane (`free-tests.yml/macos-named-regressions`) clears that lane (see [run evidence](#run-evidence)). `--from-run` and the main-push reuse job verify runs against them too ([from-run](#from-run), [CI reuse](#ci-reuse)) |
| `remote_gate_runs` | `{ profiles, inventory }`: the run profiles that together clear `pregate.json/remote_gate`, and the command that prints the remote gate's test files, one per line, which their runs must cover (see [run evidence](#run-evidence)) |
| `remote_gate_runs` | `{ profiles, inventory, skipped? }`: the run profiles that together clear `pregate.json/remote_gate`, the command that prints the remote gate's test files, one per line, which their runs must cover, and an optional command that prints which selected files the gate itself never runs (see [run evidence](#run-evidence)) |
| `allowlist` | `[{ finding, paths?, pattern?, reason }]`: benign findings the `secrets` check accepts and names as allowlisted, by gstack-redact finding id or non-secret category, path globs and a pattern anchored at the finding; never `env.value` (see [allowlist](#allowlist)) |
Only the `origin/<base>` copy or the user-owned file below is honoured, so a
@@ -228,6 +228,18 @@ Two stages with separate clocks, both printed:
elsewhere in the file is not a consumer. A call-site name such as `attempts`
or `limit` is an ordinary word, so for it the file must also name the changed
module.
A test that keeps the old value on purpose, such as the refused input after a
minimum version goes up, marks that one line with a comment on it or on the
comment line directly above it:
`// pregate-literal: MIN_RUNTIME='1.4.0' refused input below the minimum`
(`#` and `/*` comments work too). The marker waives only that line, and only
when it names the changed constant and its old value and gives a reason (a
reason cannot start with `*`, so a bare `*/`, `**/` or `* */` closer is not
one); text inside a string is not a marker. Every other line in the file is still
checked. The check lists each waived line as
`waived: <file>:<line>: <NAME> <old> (<reason>)`, adding `marker added on
this branch` when the merge-base file lacks the marker. The policy has no
waiver key, so neither a base nor a pushed `pregate.json` can widen it.
- `timing`: added or changed test lines that compare an elapsed wall-clock or
CPU time against a bound, or sleep and then assert, print
@@ -742,6 +754,16 @@ Then, for the lane:
while the working tree is pregate.json's tree (checked before and after it
runs); an empty or failing inventory refuses, and it runs under `pipefail`,
so a pipeline whose first command fails refuses too.
- When `remote_gate_runs` declares `skipped`, that command runs the same way,
with `PREGATE_FILES_FILE` naming a file of the obligation's selected test
files, one per line, and prints the ones the remote gate itself never runs
(gstack's gate leaves paid test files to the paid PR gate). Those files need
no run, and the receipt counts them as `skipped=<n>`. A file the inventory
prints stays required even when `skipped` names it, and a failing `skipped`
command refuses (`skipped_failed`). When `skipped` names every selected file,
no selected test ran, so the clear refuses (`paid_only`), just as the gate's
own `tests=0` report never clears. Without `skipped`, every selected file
must appear in a run's `files`.
- Every run is read once more after the inventory: a run that moved to a new
attempt or stopped being a success meanwhile refuses (`run_changed`).
@@ -753,13 +775,14 @@ verification is named with its own reason (`fork_run`, `event_mismatch`,
`os_mismatch`, `api_error`, …); the clear-level codes are `base_mismatch`,
`no_verified_run`, `tree_mismatch` (runs on different trees),
`tree_not_stamp_only`, `no_tests`, `inventory_failed`, `inventory_empty`,
`inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own
`skipped_failed`, `paid_only`, `inventory_uncovered` (the remote gate) and `tests_uncovered` (a lane's own
selected tests). A wrong run count, a repeated run or a lane with no
profile is a usage error (exit 2), not a REUSE refusal.
Accepted, `clear` prints `REUSE: accepted <lane> <receipt>`, and the receipt
reads `remote-gate tree=<t12> tests=<n> runs=<id>/<attempt>,… profiles=<p>,…
heads=<h12> verified=<time>` (with `accepted-for=` and the substitution for a
heads=<h12> verified=<time>` (with `skipped=<n>` after `tests=` when the
`skipped` command excused selected files, and `accepted-for=` and the substitution for a
stamp-only predecessor). A lane with its own profile gets `run tree=… runs=…`
the same way; for such a lane `--run-url` is verified as `--run` is, and
`--bundle-lane` is refused. A lane with no profile still clears by
@@ -804,7 +827,21 @@ every non-matrix job and the `free-tests` aggregate, `os` Linux) and
aggregate, `os` Windows). Each accepts `pull_request` and `workflow_dispatch`
runs and reads the `tested-tree` artifact, which both aggregates upload only
after their strict verification passes. The main-push reuse job and
`--from-run` read them. No lane has a profile, so every macOS and Windows lane
`--from-run` read them, and `remote_gate_runs` names `ci-reuse/free-tests`,
so a green Free Tests run at pregate.json's tree clears
`pregate.json/remote_gate` without a Ubicloud VM:
```bash
gstack-pregate clear --remote pregate.json/remote_gate --run <free-tests run id or url> --allow-repo-commands
```
Its `inventory` is `scripts/test-free-shards.ts --list` (the complete free
suite, the same set the Free Tests plan shards), and its `skipped` command
prints the selected files `test/helpers/paid-test-set.ts` marks paid, the
same split `scripts/ubicloud/pregate-remote.sh` makes before it runs. A
`pull_request` run counts only when the branch already contains its base
(the merge commit's tree is the head's), so a branch behind main still needs
the gate or a fresh run. No lane has a profile, so every macOS and Windows lane
still clears by `--run-url`, checked for its workflow file and a green
conclusion. On main the free tier's Windows smoke lane is
`windows-smoke.yml/windows-smoke`, and a `windows-smoke.yml` run clears it.
+2 -2
View File
@@ -263,7 +263,7 @@ line whose input could not be read says `unavailable (<reason>)` rather than 0.
gstack-factory-scorecard --repo owner/name --since-days 7 \
[--crew login,login] [--gate-workflow evals.yml] [--free-workflow free-tests.yml] \
[--train-prefix train/] [--ready-marker 'READY #\d+|SHIP_RECEIPT:'] [--pass-marker '"passes": (\d+)|passes=(\d+)'] \
[--pass-cap 3] [--out-dir .context/factory-scorecard] [--now <iso>]
[--pass-cap 2] [--out-dir .context/factory-scorecard] [--now <iso>]
```
`--repo` defaults to the origin remote's owner/name on github.com (or
@@ -280,7 +280,7 @@ measured later does not count anything that happened after that week.
| Merged PRs | `merged_at` in the window, base = default branch; crew by `--crew` logins, else author association OWNER/MEMBER/COLLABORATOR | several merges a day, every day; outside contributions land in the same week |
| Lead time, created → merged | PR `created_at` to `merged_at`, median and p90 | median under 24h, p90 under 3 days (DORA "elite" change lead time is under a day) |
| Lead time, READY → merged | first PR comment matching `--ready-marker` at or before `merged_at`; PRs with no such comment are left out and counted | under 2h; every merged PR carries a READY comment |
| Review passes per PR | the largest count `--pass-marker` captures in the PR body (the ship receipt's `"passes": N` once /ship writes it) or a PR comment (`passes=N`, `REVIEW_PASSES: N`), median and p90, plus how many PRs sat at `--pass-cap` (3: pass 1 reviews the whole diff, later passes only the delta, a pass that finds nothing new is the exit) | median 2 passes; none at the cap |
| Review passes per PR | the largest count `--pass-marker` captures in the PR body (the ship receipt's `"passes": N` once /ship writes it) or a PR comment (`passes=N`, `REVIEW_PASSES: N`), median and p90, plus how many PRs reached `--pass-cap` (2, the [review policy](review-policy.md)'s cap of two rounds, a fix-check included: pass 1 reviews the whole diff, pass 2 only the range since, and a pass that finds nothing blocking is the exit) | median 1 pass; few at the cap |
| Gate runs green / red / cancelled | completed runs of the gate workflow (`workflow_dispatch` by default; `--gate-event any`); cost from the run's `report-verdict-a*` artifact (`collector-outcomes.json` `.verdict.costUsd`), else the `**$N** rule/judge cost` or `cost=$N` line of the PR's verdict comment | green on 95%+ of runs, cancelled near 0, cost known for every run |
| Known gate cost per merged PR | known cost / merged PRs; unknown-cost runs make it a floor | known, stable or falling week over week |
| Red-train rate | runs whose head branch starts with `--train-prefix`, cancelled excluded; red = conclusion other than success; red backstops and screens shown beside it | under 5% (each red train is a bisect plus a rebuild, about two gates) |
+22 -6
View File
@@ -333,7 +333,8 @@ fix-checks run at `full` effort (the lane's reasoning); specialists run at
batched specialist, which carries the Red Team prompt and stays `full`. The
outside voice and the native adversarial pass carry the floor sentence and end
each finding with `Severity:` and `Blocks: yes|no`; a finding is CRITICAL when
its label rule says so (native FIXABLE, outside Critical or High) or it says
its label rule says so (native FIXABLE, except as the collect step below
relaxes it on a runtime-only code or docs plan; outside Critical or High) or it says
`Blocks: yes`, and `Blocks: no` never lowers one (`gstack-review-dispatch collect`). Work that cannot block (the Step 7 coverage audit, the Step 8
plan-completion audit, the Step 14.5 docs audit, decision-trail reviewers,
watchers) runs at `light`: the same model at a lower reasoning effort, never a
@@ -406,8 +407,19 @@ The brief states the blocking floor in one sentence, and its money block cites
the checklist's Forced-CRITICAL rule on uncapped spend, so a money finding can
block without new floor words. The reader is read-only, and `NO FINDINGS` is a
result. `gstack-review-dispatch render --role targeted --classes <classes>`
renders its brief; `verify --targeted-out <file>` writes the record that
`gstack-ship-receipt write --review-targeted @<file>` puts in the receipt.
renders its brief; `verify --targeted-out .gstack/tmp/targeted.json` writes the record that
`gstack-ship-receipt write --review-targeted @.gstack/tmp/targeted.json` puts in the receipt
(Step 19, the handoff and the driver pass it when the file exists). The record is `done` when
the reader got its brief verbatim and its reply was collected on a committed diff, whatever
happened to HEAD after, and it names the commit it read (`head`). `write` binds it only while
that commit is an ancestor of HEAD, its product patch is the receipt's, and nothing changed
after it carries a targeted class, with the gate scan run over that range: any product change
after the read refuses with `RECEIPT_INVALID`, docs and TODOS.md commits included, and so
does a test edit the gate scan tags (a skipped case). Every verify
removes `--targeted-out` before it writes a record, so an earlier run's record never reaches
Step 19. `write` and `carry` (which rescans the gate at the new head) also
refuse a receipt whose head needs a class no `done` record covers (`RECEIPT_INVALID`,
`RECEIPT_CARRY_REFUSED`). The queue refuses the same receipts at `ready` (#3859).
`gstack-ship-queue ready`, `train build` and `land` print
`REVIEW_TARGETED: required=<classes|none> receipt=<classes|none|legacy>
verdict=ok|missing|failed` and refuse
@@ -454,11 +466,11 @@ puts in the receipt:
| `review_loop.noted` | below-floor findings listed in the PR body |
| `review_loop.fixed` | findings fixed in this PR |
| `review_loop.scope` | the `review_scope` the loop ran under |
| `review_loop.fix_checks` | fix-checks this ship ran (never passes) |
| `review_loop.fix_checks` | fix-checks this ship ran (never passes): the larger of the `review-fix-check` rows and the ledgers holding a fix-check reader's review. When fix-checks ran without their rows, a blocker no row disposes counts as fixed once a later fix-check ledger followed it (decision 3.5 runs one only after the fixes) |
| `review_loop.rounds` | passes plus fix-checks (R51 caps it at 2; the queue's `REVIEW_DEPTH:` line prints it) |
| `review_loop.dispatches` | reviewer dispatches by role: `specialist`, `red-team`, `adversarial-native`, `outside-voice`, `fix-check`, `targeted` |
| `review_loop.est_input_tokens` | prompt plus diff bytes the dispatches read, divided by 4: a relative estimate for comparing runs, not a bill |
| `review_targeted` | the [targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code): the `classes` it covered, `rules`, `brief_sha`, the receipt's own `product_patch_id`, `status` (`done` only with its output collected and the committed diff it was rendered on still the one at verify, else `failed`; absent when write got no record, which the queue reports as `legacy`), `findings` and `raised_by`; a carry keeps it while the product patch is unchanged |
| `review_targeted` | the [targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code): the `classes` it covered, `rules`, `brief_sha`, the receipt's own `product_patch_id`, `status` (`done` only with its brief sent verbatim and its output collected on a committed diff, else `failed`; `write` refuses a record when a change after its read commit carries a targeted class or the read commit is not an ancestor, and refuses to write without a record when the head needs a class), `findings` and `raised_by`; a carry keeps it while the product patch is unchanged |
Each review row may carry `dispatches: [{role, effort, prompt_bytes,
diff_bytes, status}]`, and each finding `raised_by` (the reviewer that raised it,
@@ -589,7 +601,11 @@ ones, and the arm lost blockers. `bin/gstack-review-dispatch` closes both ends:
second line for the same field starts another: `Severity` sets its label (a label leading the finding stays its floor),
`Blocks: yes` makes it CRITICAL and `Blocks: no` never lowers it; an inline
`Blocks: yes` is CRITICAL too, and with no finding above it is one. The native
adversarial voice's FIXABLE collects as CRITICAL (normalizer v2, GSTA-167). An unnumbered, unlabeled bullet with no file:line
adversarial voice's FIXABLE collects as CRITICAL (normalizer v2, GSTA-167) unless collect `--manifest` names this
ledger's pass and its plan is the code or docs class with only `runtime=<n>` depth reasons (no `risk:`, `error:`,
`history:` or `floor:` reason). On such a plan a native finding's own `Severity` field decides and FIXABLE beside it
raises nothing once it also states `Blocks: no`; a native FIXABLE finding without `Blocks: no`, with no `Severity` field,
or whose field names only FIXABLE or INVESTIGATE still collects as CRITICAL (GST-987). An unnumbered, unlabeled bullet with no file:line
under a paragraph finding whose Severity or Blocks line has not come yet is that finding's detail, so the field line after it
lands on the finding; a numbered item is its own finding.
A negated severity word still collects at its label ("found nothing CRITICAL", "No P0/P1 issues"), so a reader's
+20 -10
View File
@@ -113,10 +113,16 @@ refuses the file by name (`POLICY_GSTACK_TOO_OLD`).
`.gstack/ship-policy.example.json` shows every key set the way a serial-merge
repo with a release script, docs mentions and a mirror would set them.
### Queue-side procedure for gstack
<a id="queue-side-procedure-for-gstack"></a>
### Queue-side procedure under `stamp-at-merge` without fragments
Who does what under `stamp-at-merge`, for the PR author and for the thread
running the merge queue:
gstack itself no longer follows these steps. Since #3654 (v1.91.342.0) its
policy sets [`changelog_fragments`](#changelog-fragments), so a gstack PR adds
a fragment and never a version, and trains merge through
[`train land`](#train-land). The steps
below are for a repo on `stamp-at-merge` whose policy has no
`changelog_fragments`. Who does what there, for the PR author and for the
thread running the merge queue:
1. **Open.** The branch carries a tentative stamp of the base's version plus
PATCH (`gstack-version-bump write --version <base+patch> --regen-digest`)
@@ -1234,13 +1240,17 @@ both values; prose that quotes no counter is not a claim. The `## Approvals`
block `gstack-approvals` renders (its heading, the Date | Asked | Exact words
table and the `Source: gstack-approvals record` line) quotes the owner's
words, not this PR's numbers, so it is skipped: "one Step 11 pass" in that table
is not a `pass` claim. Text you write under the heading after that block, and
a `## Approvals` example inside a code fence, are still checked. The
`Review-loop exception: …` line the receipt's own exception requires is
skipped the same way (GST-977): it quotes the owner's reason, so "outside 1 and
2 skip detector" is not a `2 skip` claim. Only a line that matches the rendered
line exactly is skipped, inside a code fence too, as the receipt check accepts
a fenced copy; the same words inside a longer line are still checked. Data PRs (evals,
is not a `pass` claim. Text you write under the heading after that block is
still checked. The `Review-loop exception: …` line (GST-977) and the
`Documentation exception: …` line (GST-983) the receipt's own exceptions
require are skipped the same way: they quote the owner's reason, so "outside 1
and 2 skip detector" is not a `2 skip` claim. Only a line that matches the
rendered line exactly is skipped; the same words inside a longer line are still
checked. A closed code fence is skipped too (GST-984): pasted test output
("113 pass, 0 fail") and quoted code are evidence, not this PR's counters, so
put a count you mean as a claim outside the fence. A line like ```` ```npm test``` ran ````
opens no fence, and a fence that never closes stays prose, along with the rest
of the body. Data PRs (evals,
measurements, sweeps) run it before READY so the body's numbers are the run's.
<a id="review-exclude-paths"></a>
+25 -7
View File
@@ -3320,10 +3320,19 @@ for an older PR, write it once by hand with the same command.
**Meaning.** The block exists but does not validate against the `ship-receipt`
schema (`gstack-artifact schema ship-receipt`). `gstack-ship-receipt write`
also refuses with this code, and writes nothing, when `--head` is not the
checked-out HEAD: the receipt binds the tree the review read.
checked-out HEAD: the receipt binds the tree the review read. It refuses the
same way when the head touches money, gate, auth or secret code and no done
targeted record covers those classes (`REVIEW_TARGETED: … verdict=missing|failed`),
or when `--review-targeted` names a read commit whose product patch is not
the receipt's and a change after it carries a targeted class (money, gate,
auth or secret code changed after the targeted reader read it, a test edit the
gate scan tags included), or that is not an ancestor of HEAD.
**Fix.** Regenerate it with `gstack-ship-receipt write`; never hand-edit the
block. For the `--head` refusal, check out that commit and write there.
block. 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`.
**`field=review_exclude`.** `gstack-ship-receipt write` and the queue's receipt check also
use this code when the receipt's review_exclude digest (or the
@@ -3767,7 +3776,12 @@ directory, or under a scratch name (`zz-*`, `tmp*`, `scratch*`).
appears with its old value in a test file the branch did not update — nightly-
and Heavy-only files included.
**Fix.** Update every listed `file:line`, or keep the old value.
**Fix.** Update every listed `file:line`, or keep the old value. When a test
keeps the old value on purpose (the refused input after a minimum version goes
up), mark that line: `// pregate-literal: NAME=<old value> <reason>` on the line
or on the comment line directly above it. The marker waives only that line, and
the check lists it as `waived:`. Don't use `warn: ["literals"]` for this, since
that turns the check off for every constant.
<a id="pregate-no-lane"></a>
### `PREGATE_NO_LANE`
@@ -5910,7 +5924,10 @@ working tree's status. It also refuses when an owner exception the receipt recor
no longer stands, and, for a receipt from a gstack that records review
depth, when HEAD needs a deeper review than the receipt records, `--head` is
not the checked-out HEAD, or the receipt records `gstack_version` without
`review_depth` ([ship-policy.md](ship-policy.md#ship-receipt)). The receipt in
`review_depth` ([ship-policy.md](ship-policy.md#ship-receipt)). It refuses, too, when
HEAD needs a targeted class (money, gate, auth, secret) that the receipt's
`review_targeted` does not cover with a done pass, since the queue would refuse
that receipt. The receipt in
the body is unchanged and still names the old head, so `gstack-ship-queue
ready` refuses `QUEUE_RECEIPT_STALE`.
@@ -5918,7 +5935,7 @@ ready` refuses `QUEUE_RECEIPT_STALE`.
`gstack-ship-receipt carry --pr <n>` at the new head. When the history is
already rewritten and the receipt predates `head_tree`, run /ship on the new
head; next time merge the base in instead of rebasing. For a retargeted stacked
PR whose older receipt lost its parent branch, or a depth or exception
PR whose older receipt lost its parent branch, or a depth, targeted or exception
refusal, run /ship on the new head. When `--head` is not the checked-out HEAD,
check it out and carry there.
@@ -6013,8 +6030,9 @@ 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, and the `Review-loop exception: …`
line the receipt's exception requires, are never read as claims.
`## 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.
**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