mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 07:18:03 +01:00
v1.91.374.0 stamp: train gate.train-395971298ca4-0cd2b4a9f944-22db37 S1 (#3611)
This commit is contained in:
commit
4bc8d9c73f
61 files changed
+3091
-147
No files matched your search
@@ -197,7 +197,15 @@ shards get duration-aware walls (`max(base, predicted × 3, files × 5s)`). The
|
||||
legacy `--shards N --shard i` path keeps stable hash indices. Required CI uses
|
||||
one duration-packed `--ci-plan`, 20 ordinary `--ci-run` shards plus a separate
|
||||
exclusive-fixture shard, and a `--ci-verify` aggregate. CI shards still run on
|
||||
independent machines without ordering unrelated jobs. The public
|
||||
independent machines without ordering unrelated jobs. Two workflow-level
|
||||
constants in `free-tests.yml` set that size for every event: `FREE_SUITE_RUNNER`
|
||||
(`ubicloud-standard-2`) and `FREE_SUITE_SHARDS` (`'20'`), which `free-plan`
|
||||
resolves into the shards' runner and shard count. A `workflow_dispatch` run may
|
||||
override them with `free_runner` (`ubicloud-standard-2`, `-4` or `-8`) and
|
||||
`free_shards` (1–40) for runner-sizing experiments only; its job summary says
|
||||
so, and it is never cited as a PR or train Free Tests result. Each shard prints
|
||||
`RESOURCES: peak_used_mib=… min_free_disk_mib=… samples=…` from a memory and
|
||||
disk sample every 2 seconds, even when the suite fails. The public
|
||||
`TREE_MUTATING` map now classifies exclusive host-state fixtures; its sole
|
||||
entry is `test/bootstrap-retention.test.ts`, whose same-UID nondumpable actors
|
||||
affect host-wide procfs permission checks. Locally, this file runs only after
|
||||
@@ -1274,7 +1282,10 @@ re-run keeps the creation time, so it does not wait out the window again). A
|
||||
head that is no longer the PR's head reports "superseded by <sha>" and plans
|
||||
nothing. Selection, trials and verdict rules do not change. Version Gate is not
|
||||
an input, so a stamp push waits only for its own Free Tests and always gets a
|
||||
gate. Dispatches (`eval:bg`, the census) never wait.
|
||||
gate. Dispatches (`eval:bg`, the census) never wait. Neither does a
|
||||
`pr-summary` run (a repository whose policy declares a train): it plans no paid
|
||||
slices, so `readiness` is skipped, the run ends with the screening summary, and
|
||||
a red Free Tests shows on Free Tests alone.
|
||||
|
||||
**Verdict reuse (R2a restamp-only, R2 main moved; a gate edit).** A PR head
|
||||
reuses the verdict of the newest executed gate of the same PR instead of
|
||||
@@ -1424,6 +1435,13 @@ prints a run's ledgers as a table. A flaky file is fixed at source; the retry
|
||||
never masks it. `bun run eval:pass-rates` lists only the local ledger
|
||||
(`flakeLedgerPath()`, overridden by `GSTACK_FLAKE_LEDGER`).
|
||||
|
||||
Other repositories get the same rule per test with `bin/gstack-flaky run`
|
||||
([docs/flaky.md](flaky.md)): it reads a JUnit report from any runner, reruns
|
||||
the command once when tests failed (counting only the runner's full green
|
||||
verdict), and writes `flaky-pass` and `rerun-fail` lines to its
|
||||
own ledger (`flake-ledger-junit.jsonl`, or `GSTACK_FLAKE_LEDGER_JUNIT`), kept apart
|
||||
from this file-level ledger.
|
||||
|
||||
The aggregate job of `free-tests.yml` downloads every `flake-ledger-*` artifact
|
||||
of the run and appends `bun run scripts/test-health-report.ts flake-summary <dir>`
|
||||
to its job summary. The Windows lane uploads `flake-ledger-windows-<shard>`.
|
||||
@@ -1504,7 +1522,9 @@ refresh to a maintainer.
|
||||
- `free-durations-record.yml` (Mondays 04:00 UTC, before test-health, and on
|
||||
dispatch) records the Linux seed on `ubicloud-standard-8` with
|
||||
`scripts/ubicloud/setup-free-suite.sh` and the Windows seed through
|
||||
`windows-free-tests.yml`'s `record_durations` path. When either recording adds
|
||||
`windows-free-tests.yml`'s `record_durations` path. The recorder stays on
|
||||
`ubicloud-standard-8` while the free-suite shards run on `FREE_SUITE_RUNNER`
|
||||
(`ubicloud-standard-2`); both are Ubicloud standard runners. When either recording adds
|
||||
or drops a test file, it force-updates the `bot/free-duration-seeds` branch,
|
||||
opens or refreshes one seed PR whose body states the ratchet result, and
|
||||
dispatches Free Tests, Windows Free Tests and Quality gate on it. Those
|
||||
@@ -1624,6 +1644,51 @@ lower-case words in path components under `test/fixtures/`; a substring match
|
||||
separate change with its own touchfile updates; the list is here so that change
|
||||
can be scoped.
|
||||
|
||||
## No-gstack arm
|
||||
|
||||
Garry, 2026-10-31: "all of our head to head tests have to test no gstack from
|
||||
here so that that case is tested obviously Do it for science". A variant that
|
||||
beats another variant can still lose to the plain model, and only a no-gstack
|
||||
arm shows that.
|
||||
|
||||
**Scope.** Every preregistered comparison where any arm loads gstack or another
|
||||
skill stack: review variants, preamble loadouts, skill diets scored by behavior
|
||||
evals, and model panels run under gstack or under another stack. The
|
||||
preregistration says which kind it is in `## Comparison`: `stack: loaded`, or
|
||||
`stack: none` with a `why:` line (a scorer calibration, for example, where no
|
||||
arm loads any skill text). `gstack-eval-plan prereg --check` refuses a loaded
|
||||
comparison without the arm (`PREREG_NO_GSTACK_ARM`).
|
||||
|
||||
**How the arm is built.** One arm per model in the comparison, named
|
||||
`<model>#no-gstack` in the Arms section and listed on the `arms:` line of
|
||||
`## No-gstack arm`. Arm identities are `<model>[#<loadout>]` and are priced by
|
||||
the model, so the pilot reserves the no-gstack arm beside its variants under its
|
||||
own attempts. The arm gets:
|
||||
|
||||
- the same model, settings, inputs, fixture and tools as the variants;
|
||||
- a plain prompt: the task text only, with no SKILL.md, preamble, hooks,
|
||||
installed skills or routing lines from gstack or any other stack;
|
||||
- the same panel: the same `E2E_KINDS` kind and trial count, the same judge and
|
||||
rubric, and the same blinding labels (the judge reads it as `A` or `B` like
|
||||
any other arm, per [Candidate-side blinding](#candidate-side-blinding));
|
||||
- the same budget treatment: it is priced and reserved in the pilot like every
|
||||
other arm.
|
||||
|
||||
**Reporting.** The no-gstack result is one row beside the variants in every
|
||||
results table and every verdict, including when it wins. A comparison that
|
||||
drops the row has not reported its result. The prereg check cannot see the
|
||||
report, so the `report:` line states where the row goes and the reviewer of
|
||||
the result holds the author to it.
|
||||
|
||||
**Precedent and its gaps.** `test/helpers/arm-benchmark-harness.ts` already has
|
||||
a `without-skill` arm beside `with-skill`, and it is the closest existing
|
||||
control. It falls short of this rule in three ways: each arm and task runs
|
||||
once rather than as a panel; the judge (`armJudge` in
|
||||
`test/helpers/llm-judge.ts`) scores each diff on its own, without `A`/`B`
|
||||
labels; and the candidate's temporary folder is named after the arm
|
||||
(`arm-<fixture>-<arm>-*`). Existing paid cases are not retrofitted by the
|
||||
change that added this rule; an audit of them is tracked separately.
|
||||
|
||||
## Running evals as an agent: detach
|
||||
|
||||
Moved verbatim from CLAUDE.md (#2096 size limit). The short rule stays in
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Flaky tests: rerun once and record it (`gstack-flaky`)
|
||||
|
||||
`gstack-flaky run` runs your test command, reads the JUnit report it writes,
|
||||
and when tests failed, runs the command once more. A test that passes on its
|
||||
rerun is a **flaky pass**: the job stays green and the flake is recorded on a ledger, so
|
||||
the daily flaky pass can fix it at the source. A test that fails twice is red.
|
||||
This is CREW rule 16 ("run the failed test again and note that it is flaky")
|
||||
for any runner that writes JUnit: Playwright, `bun test`, jest, vitest and
|
||||
pytest. gstack's own free suite keeps its file-level retry
|
||||
(`GSTACK_FREE_RETRY_FLAKY`, [Flake ledger](TESTING_INTERNALS.md#flake-ledger)).
|
||||
|
||||
## Get started in GitHub Actions
|
||||
|
||||
```yaml
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with: { bun-version: 1.4.4 }
|
||||
- name: Install gstack-flaky (pinned)
|
||||
env: { GSTACK_REF: <a gstack commit sha you have tested> }
|
||||
run: |
|
||||
git clone --quiet --filter=blob:none https://github.com/garrytan/gstack.git "$RUNNER_TEMP/gstack"
|
||||
git -C "$RUNNER_TEMP/gstack" checkout --quiet "$GSTACK_REF"
|
||||
- name: E2E
|
||||
env:
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_NAME: results/junit.xml
|
||||
GSTACK_FLAKE_LEDGER_JUNIT: ${{ runner.temp }}/flaky-junit-ledger.jsonl
|
||||
run: |
|
||||
"$RUNNER_TEMP/gstack/bin/gstack-flaky" run --junit results/junit.xml -- npx playwright test --reporter=line,junit
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with: { name: flaky-junit-ledger-e2e, path: "${{ runner.temp }}/flaky-junit-ledger.jsonl", if-no-files-found: ignore, retention-days: 90 }
|
||||
```
|
||||
|
||||
Replace `GSTACK_REF` with a gstack commit you have tested; the bin needs only
|
||||
Bun and git. In Actions it also writes a step-summary table of the flaky
|
||||
passes and one `::warning` annotation per flaky test, so they are visible on
|
||||
the run before anyone reads the ledger.
|
||||
|
||||
## Runners
|
||||
|
||||
| Runner | Command after `--` | Notes |
|
||||
|---|---|---|
|
||||
| Playwright | `npx playwright test --reporter=line,junit` with `PLAYWRIGHT_JUNIT_OUTPUT_NAME=<file>` | `gstack-flaky` sets `PLAYWRIGHT_JUNIT_INCLUDE_RETRIES=1` so Playwright's own `retries` are reported; with retries configured, those attempts stand and nothing is rerun again. |
|
||||
| bun | `bun test --reporter=junit --reporter-outfile=<file>` | |
|
||||
| jest, vitest, pytest (`--runner generic`) | your command, writing JUnit | Always reruns the whole command, even with `--rerun-scope test`. |
|
||||
|
||||
The command after `--` runs without a shell. When it needs one, wrap it as
|
||||
`bash -c '<script> "$@"' _` so a targeted rerun's selection arguments reach
|
||||
the runner.
|
||||
|
||||
## Rerun scope
|
||||
|
||||
**Default: the whole command.** The rerun repeats your command exactly, and
|
||||
counts only on the runner's own full green verdict: it exits 0, no test fails,
|
||||
every test of the first run is in its report, and the runner counts no failure
|
||||
outside the report. That verdict covers every reason a run can fail, including
|
||||
the ones a narrowed rerun cannot see: a test the first run never reached
|
||||
(`--bail`, `--max-failures`, `-x`), a coverage threshold (however coverage is
|
||||
configured, `bunfig.toml` and lcov included), a fail-on-flaky policy, a
|
||||
global setup error. A test that failed in the first run and passes once in this
|
||||
rerun is a flaky pass. It costs one more run of the suite, which is still far
|
||||
cheaper than rerunning the CI job by hand.
|
||||
|
||||
**Opt-in: `--rerun-scope test`.** Reruns the failed test alone (Playwright:
|
||||
`<file> --grep <title> --project <project>`; bun: `<file> --test-name-pattern <title>`)
|
||||
and clears it when that rerun exits 0. Use it only when the first run's exit
|
||||
status can come from failed tests alone: no coverage threshold in any form, no
|
||||
`--fail-on-flaky-tests`, no other step inside the command that can fail. As a
|
||||
safety net, `gstack-flaky` still reruns the whole command
|
||||
(`FLAKY_RERUN: scope=command (<why>)`) when the runner exited anything but
|
||||
1, its summary is missing, it stopped early (bun `Bailed out`, Playwright
|
||||
`did not run`, `interrupted`, `Testing stopped early`, a global timeout), it
|
||||
printed a coverage table or got `--coverage`, or it retried flaky tests itself.
|
||||
It cannot see a coverage threshold configured without console output (for
|
||||
example `coverageReporter = ["lcov"]` in `bunfig.toml`), which is why this
|
||||
scope is opt-in.
|
||||
|
||||
## Rules
|
||||
|
||||
- **One rerun, small failures only.** A failed test gets one rerun when the
|
||||
runner made no retries and at most `--max-reruns` (3) tests failed. More
|
||||
failures than that is a real red, and nothing is rerun. When the run is
|
||||
already red for a test that cannot be rerun, a whole-command rerun is
|
||||
skipped.
|
||||
- **A rerun clears a test only cleanly.** The rerun's command must exit 0 and
|
||||
the test must run once and pass on its first attempt; a rerun the runner
|
||||
retried again does not count.
|
||||
- **Never green by accident.** The report is deleted before every attempt, and
|
||||
a report that attempt did not write counts as missing. A missing, empty,
|
||||
zero-test or malformed report fails, as does a non-zero exit with no failed
|
||||
test (a crash or a non-test step) and a runner killed by a signal. Cancelling
|
||||
`gstack-flaky` (SIGINT, SIGTERM) is passed to the runner and latched: nothing
|
||||
is rerun after it, whatever exit the runner chooses. When the runner's
|
||||
summary counts failures its JUnit omits (a bun file that fails to load, a
|
||||
Playwright error outside any test), the run is red and nothing is rerun.
|
||||
- **Reports.** After the run, `--junit` holds the first run's report, so a
|
||||
later upload or test reporter sees what failed. Each rerun's report is kept
|
||||
beside it as `<name>.rerun-<n>.xml`. Other reporters' outputs (Playwright's
|
||||
`test-results/` and HTML report) are rewritten by a rerun; upload the JUnit
|
||||
report as the record. An existing `--junit` file that is not a JUnit report,
|
||||
or cannot be read, is refused (usage, exit 2), never deleted.
|
||||
- **Ids.** A test is `<file>::<project>::<describe > title>`, with the project
|
||||
empty outside Playwright, so a test in two browser projects is two tests.
|
||||
`gstack-flaky ids --junit <file>` prints them.
|
||||
- **Ledger.** One JSON line per flaky pass (`kind: flaky-pass`) and per test
|
||||
that failed its rerun (`rerun-fail`), with the tree, commit, branch, run URL,
|
||||
attempts, the rerun's scope (`test` or `command`) and the first lines of
|
||||
the error. Ids, paths and failure text go through gstack's redaction engine
|
||||
first; when the scan cannot run, they are withheld. The path is `--ledger`,
|
||||
else `GSTACK_FLAKE_LEDGER_JUNIT`, else
|
||||
`<state root>/flake-ledger-junit.jsonl`, and `FLAKY_RESULT` names it. It is
|
||||
kept apart from the free suite's file-level ledger (`GSTACK_FLAKE_LEDGER`).
|
||||
A ledger write error is a warning and never changes the verdict.
|
||||
|
||||
## Output and exit codes
|
||||
|
||||
One `FLAKY: <flaky-pass|fail|rerun-fail> <id> attempts=<…> (<reason>)` line per
|
||||
test that failed at least once, `FLAKY_RERUN: scope=command (<why>)` when the
|
||||
rerun repeated the whole command, then
|
||||
`FLAKY_RESULT: tests=<n> failed=<n> flaky=<n> reruns=<n> command_exit=<n> verdict=pass|fail ledger=<file|none>`.
|
||||
|
||||
| Exit | Meaning |
|
||||
|---|---|
|
||||
| 0 | Every test passed, flaky passes included |
|
||||
| 1 | A test failed (after its rerun, or with no rerun), including `FLAKY_UNREPORTED_FAILURES` |
|
||||
| 2 | Usage error, including a `--junit` path that is not a `.xml` file, or an existing file there that is not a readable JUnit report |
|
||||
| 3 | No usable report, a crash, or a killed runner (a code below on stderr) |
|
||||
|
||||
| Code | Exit | Cause | Fix |
|
||||
|---|---|---|---|
|
||||
| `FLAKY_NO_RESULTS` | 3 | No report at `--junit`, an empty one, or one with no testcase | Make the command write JUnit to that path |
|
||||
| `FLAKY_JUNIT_INVALID` | 3 | The report is not well-formed XML | Make sure one process writes it and nothing appends to it |
|
||||
| `FLAKY_RETRIES_HIDDEN` | 3 | Playwright retried but its report omits the retries | Set `PLAYWRIGHT_JUNIT_INCLUDE_RETRIES=1` where the report is written |
|
||||
| `FLAKY_COMMAND_FAILED` | 3 | Non-zero exit with no failed test | Read the command output: a real red |
|
||||
| `FLAKY_COMMAND_KILLED` | 3 | The runner was killed by a signal (a cancelled job, an OOM kill) | Nothing was rerun; rerun the job |
|
||||
| `FLAKY_COMMAND_NOT_STARTED` | 3 | The command could not be started | Check the argv after `--` |
|
||||
| `FLAKY_UNREPORTED_FAILURES` | 1 | The runner counted failures or errors its JUnit omits | Fix the load error or the error outside any test: a real red |
|
||||
| `FLAKY_LEDGER_WRITE_FAILED` | — | The ledger could not be appended (warning) | Check the path; the verdict stands |
|
||||
+9
-3
@@ -85,7 +85,7 @@ the byte-pinned `/ship` renders in `test/fixtures/golden/`).
|
||||
| `serial_patterns` | test-file globs the `lanes` check runs one process per file, apart from the unit files (default `**/*.serial.test.*`) |
|
||||
| `sealed` | sealed or eval output that must never reach a public destination; read by the pre-push hook and `gstack-pregate sealed` (see [sealed output](#sealed)) |
|
||||
| `price_file` | repo-relative path of the paid gate's call-plan data file (`GSTACK_EVAL_CALLS v1`); `gstack-eval-plan price-gate` prices from it and never executes anything ([docs/eval-calls.md](eval-calls.md)) |
|
||||
| `stall_timeout_s` | seconds a test command may print nothing before its process group is killed and the check is `incomplete` (`PREGATE_STALLED`); default 300, `0` disables; `--stall-timeout` overrides (see [supervision](#supervision)) |
|
||||
| `stall_timeout_s` | seconds a test command may print nothing before its process group is killed and the check is `incomplete` (`PREGATE_STALLED`); default 300, `0` disables; `--stall-timeout` overrides; the `--remote` gate is exempt (see [supervision](#supervision)) |
|
||||
| `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)) |
|
||||
| `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)) |
|
||||
@@ -411,7 +411,9 @@ test-tier command:
|
||||
- is killed as **stalled** when its log does not grow for the stall timeout
|
||||
(`--stall-timeout <s>`, else `stall_timeout_s`, else 300; `0` disables): the
|
||||
check is `incomplete` with `PREGATE_STALLED` and names the last file
|
||||
started; the next serial file still runs;
|
||||
started; the next serial file still runs. The `--remote` gate is exempt: a
|
||||
gate that waits on CI is quiet by design, so only its timeout bounds it, and
|
||||
its heartbeats add `; last output: <the gate's last line>`;
|
||||
- is killed as **timed out** when that runner process reaches its timeout
|
||||
(`--timeout <s>`, else `timeout_s`, else 600; per runner process, so many
|
||||
serial files that each fit still pass): the check is `incomplete`, its detail
|
||||
@@ -452,7 +454,11 @@ the supervision above. Close the obligation one of two ways:
|
||||
`PREGATE_REMOTE: tree=<tree> tests=<n> failed=<n> [spent=<usd>]`, reporting the
|
||||
tree it actually tested. The obligation clears only when that tree is
|
||||
`PREGATE_TREE`, `tests` > 0, `failed` = 0 and the gate exits 0; anything else
|
||||
leaves it uncleared and the check `incomplete`. The cap defaults to
|
||||
leaves it uncleared and the check `incomplete`. The gate runs until it exits
|
||||
or reaches the timeout (`--timeout`, else `timeout_s`, else 600 s); the stall
|
||||
timer does not apply, and each 60 s heartbeat names the gate's last output
|
||||
line, so a gate that prints what it is waiting on (a CI run id and its
|
||||
status) shows it. The cap defaults to
|
||||
`$GSTACK_SPEND_CAP_USD`; there is no default spend. Spend prints as
|
||||
`spent, reserved, unknown`: a gate that prints no `spent=` is `unknown`,
|
||||
counted at the cap.
|
||||
|
||||
+16
-5
@@ -473,7 +473,14 @@ ones, and the arm lost blockers. `bin/gstack-review-dispatch` closes both ends:
|
||||
second line for the same field starts another: `Severity` raises its label,
|
||||
`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). A phantom finding costs a fold; a lost one costs the gate.
|
||||
adversarial voice's FIXABLE collects as CRITICAL (normalizer v2, GSTA-167). 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 phantom finding costs a fold; a lost one costs the gate.
|
||||
A reply with no finding counts as a review only with the clean marker its own brief asks for: `NO FINDINGS` alone on a
|
||||
line for the specialists, the batched specialist, the targeted reader, the Red Team and fix-check, the `Recommendation:`
|
||||
line for the outside voice, either for the native adversarial voice. Without one (a refusal, an empty reply, a reader
|
||||
that never saw the diff) collect records `review: false`, prints `COLLECT_NOT_REVIEWED` and exits 1.
|
||||
4. Lead judgment writes `judged.jsonl`; `judge` refuses a set that drops a merged
|
||||
finding, lowers its severity, folds it into a less severe duplicate, or adds or
|
||||
promotes one without a reason (`JUDGMENT_REJECTED`). A merged file missing any id
|
||||
@@ -481,10 +488,14 @@ ones, and the arm lost blockers. `bin/gstack-review-dispatch` closes both ends:
|
||||
5. `verify --ledger <f>` replays all of it from the run's ledger: rendered against
|
||||
dispatched hashes per render (each render is one dispatch of its reader, so a
|
||||
two-pass ledger checks pass-1 sends against pass-1 briefs; a later render prints as
|
||||
`<reader>@2`), a collected output for every sent reader (`NOT_COLLECTED`
|
||||
otherwise; a reader that failed is collected from its failure text, where any
|
||||
label word becomes a finding the judge folds, and its coverage is logged
|
||||
`unavailable` in the review-log row). A mismatched send re-sent
|
||||
`<reader>@2`), a collected output that is a review for every sent reader (`NOT_COLLECTED`
|
||||
otherwise, and the output row `NOT_REVIEWED`; the marker is checked again in the output file when it is unchanged, so an
|
||||
older ledger meets the same rule; a non-review the same dispatch later replaced with a review, by re-sending the brief, is `VOIDED`
|
||||
and counted in `voided=N`). The outside voice may fail without blocking: `collect --unavailable '<reason>'` records its failure text
|
||||
as missing coverage (`COLLECT_UNAVAILABLE`); an empty reason or an output that holds findings is refused (exit 2), so a partial
|
||||
answer is collected normally and loses nothing. Verify prints its brief and output `UNAVAILABLE` and `unavailable=N` on the
|
||||
verdict line, and its coverage is logged `unavailable` in the review-log row; any other reader is refused (exit 2), because its
|
||||
missing coverage leaves the review incomplete. A mismatched send re-sent
|
||||
byte-exact before anything was collected is void and counted (`voided=N` on the
|
||||
verdict line); one that produced an output stays `MISMATCH`. Then output hashes
|
||||
against the merged lines they produced, and the judgment rerun against the
|
||||
|
||||
@@ -18,6 +18,7 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that
|
||||
| `gstack-review-log` | **Review-pass receipts** — `--start <skill>` captures the working-tree fingerprint before a diff review; `'<JSON>' --finish <token>` consumes that single-use, repository/branch/skill-scoped receipt. Binding requires matching start/end content and reviewer-reported `completed:true` and `converged:true`; it is not independent proof that a model read the source. `--check-shared-libs <token>` reads a current finding as JSON on stdin and checks prior Skip decisions against the actual branch, capture and eligible raw source blobs. It returns `reusable:false` when proof is missing or unsafe, including older records without logger-versioned coverage. Final review logging computes shared-code fingerprints and coverage rather than trusting supplied proof. |
|
||||
| `gstack-review-read` | **Review freshness** — emits review records with computed `review_freshness.status` and `reason`: CURRENT, STALE, or UNVERIFIED for diff reviews. `/ship` and `/land-and-deploy` use the same grade; a matching commit alone never certifies a diff review. [Dashboard rules](skills.md#review-readiness-dashboard). |
|
||||
| `gstack-evidence` | **Verification-evidence ledger** — `run --label <lane> -- <cmd>` transparently wraps any test command (the child's exit code always passes through) and records what ran against which working-tree fingerprint (tracked and untracked files that are not gitignored, minus an untracked `.bun/` folder at the repo root, which Bun can create during a run); a run whose files changed while it ran is recorded without a fingerprint and with up to five of the changed paths, which `check` names in its STALE reason. `check` grades each label FRESH/STALE/MISSING with `--expect-cmd`, `--max-age`, and `--allow-paths` binding. /ship and /land-and-deploy cite fresh evidence instead of re-running suites. Per-run logs are 0600, capped at 2MB, pruned after 30 days; the ledger and logs stay machine-local by design. |
|
||||
| `gstack-flaky` | **Flaky-test rerun for any JUnit runner** — `run --junit <file.xml> -- <command>` runs your test command (Playwright, `bun test`, jest, vitest, pytest) and, when at most `--max-reruns` (3) tests failed and the runner made no retries of its own, reruns the whole command once (`--rerun-scope test` opts in to rerunning only the failed tests). A test that fails and then passes on that rerun is a flaky pass: the run stays green and one redacted line per flaky pass or failed rerun goes on a ledger (`GSTACK_FLAKE_LEDGER_JUNIT`, default `<state root>/flake-ledger-junit.jsonl`). A missing or malformed report, a crash or a killed runner never passes. `ids --junit <file>` prints a report's test ids. Exit 0 pass, 1 a test failed, 2 usage, 3 no usable report. [Guide](flaky.md). |
|
||||
| `gstack-pr-wait` | **PR event watcher** — `gstack-pr-wait <pr> [--repo owner/name] [--until green\|ready\|any-change] [--interval 20] [--timeout 60] [--exclude CHECK,...]` polls one pull request through `gh` and prints one stdout line per real event (a check failed with its link, all checks finished, a comment or review from someone other than the author, a review bot finished on the head, the base moved and its new commits touch this PR's files, BEHIND or DIRTY, merged or closed), with progress on stderr, so a monitor wakes the agent once per event instead of on a timer. `green` gates on every check on the head (an advisory bot's review is never waited for, its checks gate like any other), as `gstack-ci-gate` does; a check the base branch requires (rulesets and classic branch protection) is pending until it appears, never green by omission; two identical polls confirm a check set is complete; `--exclude` leaves a named non-required check out of the verdict as `gstack-ci-gate --exclude` does (a required name is refused and gates). Exit 0 when the condition is met, 1 when it needs action or `--timeout` passes (`PR_WAIT_BLOCKED`), 2 on bad input, a second watcher for the same PR or a local error (`PR_WAIT_REFUSED`). State under `<state root>/pr-wait/`, baseline discarded after 6 hours. Review bots are configuration: `gstack-config set pr_wait_gating_bots greptile,macroscope`, `pr_wait_advisory_bots coderabbit` (advisory bots are reported, never waited on). `/land-and-deploy` Step 3 is one call of it. Design ported from tstack's pr-wait (MIT, Theo Browne; NOTICE.md). [Exit codes](troubleshooting.md#gstack-pr-wait). |
|
||||
| `gstack-issue-guard` | **Tracker-text trust envelope** — fetches GitHub issue/PR text (`issue <n>`, `pr-body`, `pr-comments`, or `--stdin`) and wraps it in a labeled envelope so agents treat it as data: injection-shaped lines get labeled even through fullwidth and invisible-character evasion, and forged envelope banners are defused. Every tracker-text ingress in gstack routes through it, enforced by a CI scanner. |
|
||||
| `gstack-ios-qa-daemon` | **iOS QA daemon** — Mac-side broker between an agent and a connected iPhone or iPad over USB CoreDevice. Loopback by default; `--tailnet` opens a Tailscale-facing listener with identity-gated capability tiers. Single-instance via flock on `~/.gstack/ios-qa-daemon.pid`. See [docs/howto-ios-testing-with-gstack.md](howto-ios-testing-with-gstack.md). |
|
||||
|
||||
+46
-12
@@ -2522,12 +2522,15 @@ same-titled section (title compared case-insensitively), the
|
||||
````gstack-ship-receipt```` block is a section of its own so a receipt
|
||||
rewrite replaces only the receipt, sections the file does not rewrite stay in
|
||||
place, and new sections are appended after them. The text above the first
|
||||
`## ` heading belongs to the file whenever the file has a `## ` section: the
|
||||
current body's text there (the opening lines, and any lines between the receipt
|
||||
and the first heading, or the whole body when it has no `## ` heading) is
|
||||
replaced by the file's own text above its first heading, or dropped when the
|
||||
file has none. A file with no `## ` section (a receipt-only or text-only post)
|
||||
leaves that text alone. One stderr line names what was replaced, kept, added
|
||||
`## ` heading belongs to the file when the file is a full post: a
|
||||
receipt block above its first `## ` section, as /ship composes it. Then the current body's text there (the opening lines, and
|
||||
any lines between the receipt and the first heading, or the whole body when it
|
||||
has no `## ` heading) is replaced by the file's own text above its first
|
||||
heading, or dropped when the file has none. A partial post (`## ` sections
|
||||
with no receipt above them, such as a lane posting `## Screenshots`) and a
|
||||
file with no `## ` section (a receipt-only or text-only post) leave that text alone, so a human
|
||||
intro or a `Review-loop exception:` line under the receipt survives; only
|
||||
opening lines the file brings of its own replace the PR's opening lines. One stderr line names what was replaced, kept, added
|
||||
and dropped, a dropped piece with its first line, or with that line withheld
|
||||
when the redaction scan flags it (`gstack-post: pr-body merged
|
||||
by section: replaced What changed; kept ship receipt, Why, Approvals; added
|
||||
@@ -3464,8 +3467,7 @@ the lane log, then rerun or split the selection. A tool missing from PATH is
|
||||
<a id="pregate-stalled"></a>
|
||||
### `PREGATE_STALLED`
|
||||
|
||||
**Meaning.** A test command the lanes check (or a guard, or the `--remote`
|
||||
gate) launched wrote nothing to its log for the stall timeout (default 300 s,
|
||||
**Meaning.** A test command the lanes check (or a guard) launched wrote nothing to its log for the stall timeout (default 300 s,
|
||||
`stall_timeout_s` in the base branch's `.gstack/pregate.json` or the user-owned file, or
|
||||
`--stall-timeout`). pregate killed its process group and names the last test
|
||||
file started. The check is incomplete, never pass.
|
||||
@@ -3474,7 +3476,9 @@ file started. The check is incomplete, never pass.
|
||||
child, a server that never exits, a synchronous wait), fix it and rerun
|
||||
`gstack-pregate --stage tests`. A test that is legitimately quiet for longer
|
||||
needs a larger `--stall-timeout`; `0` disables the stall kill and leaves only
|
||||
`--timeout`.
|
||||
`--timeout`. The `--remote` gate never stalls: it is bounded by `--timeout`
|
||||
(else `timeout_s`), so a gate that waits on CI needs a timeout long enough for
|
||||
the CI run.
|
||||
|
||||
<a id="pregate-receipt-invalid"></a>
|
||||
### `PREGATE_RECEIPT_INVALID`
|
||||
@@ -3640,14 +3644,44 @@ parentheses; grep the code.
|
||||
### `PREREG_INCOMPLETE`
|
||||
|
||||
**Meaning.** `gstack-eval-plan prereg --check <file>` found a required
|
||||
preregistration section missing or empty: `## Bars`, `## Arms`, `## Decoys`,
|
||||
`## Stop rules` or `## Held-out exposure` (every held-out set the plan
|
||||
touches, its prior exposure and the reservation on it).
|
||||
preregistration section missing or empty: `## Bars`, `## Arms`,
|
||||
`## Comparison`, `## Decoys`, `## Stop rules` or `## Held-out exposure`
|
||||
(every held-out set the plan touches, its prior exposure and the reservation
|
||||
on it). It also fires when `## Comparison` has no `stack:` line, a value other
|
||||
than `loaded` or `none`, or `stack: none` without a `why:` line, and when a
|
||||
`## Diagnostics` declaration is malformed.
|
||||
|
||||
**Fix.** Fill the named sections before any priced call. A plan that cannot
|
||||
name its stop rule or its held-out exposure is not preregistered; the pilot
|
||||
refuses to start without a passing check.
|
||||
|
||||
<a id="prereg-no-gstack-arm"></a>
|
||||
### `PREREG_NO_GSTACK_ARM`
|
||||
|
||||
**Meaning.** The preregistration says `stack: loaded` (some arm loads gstack or
|
||||
another skill stack) and its `## No-gstack arm` section is missing, lacks one
|
||||
of `arms:`, `inputs:`, `prompt:`, `panel:` or `report:`, lists an arm that
|
||||
is not a `<model>#no-gstack` Arms line, or leaves a model in the comparison
|
||||
without its own no-gstack arm. Arms match on whole identities, so
|
||||
`claude-opus-5#no-gstack` does not cover `claude-opus-5-5`.
|
||||
|
||||
**Fix.** Add one `<model>#no-gstack` line per model under `## Arms`, list them
|
||||
on the `arms:` line, and fill the other four keys: the same inputs, a plain
|
||||
prompt with no skill text, the same panel and judge, and where the result row
|
||||
goes beside the variants. When no arm loads a skill stack, write
|
||||
`stack: none` with a `why:` line instead. The rule is in
|
||||
[docs/TESTING_INTERNALS.md](TESTING_INTERNALS.md#no-gstack-arm).
|
||||
|
||||
<a id="eval-arm-duplicate"></a>
|
||||
### `EVAL_ARM_DUPLICATE`
|
||||
|
||||
**Meaning.** `gstack-eval-plan pilot start --arms` named the same arm identity
|
||||
twice, so two arms would share one set of pilot attempts.
|
||||
|
||||
**Fix.** Give each arm its own identity: `<model>` or `<model>#<loadout>`, for
|
||||
example `claude-opus-5-5` and `claude-opus-5-5#no-gstack`. Nothing was
|
||||
reserved.
|
||||
|
||||
<a id="eval-price-missing"></a>
|
||||
### `EVAL_PRICE_MISSING`
|
||||
|
||||
|
||||
Reference in new issue
Block a user