mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
v1.92.62.0 stamp: merge main dd9834d44d into #3832 (land)
This commit is contained in:
commit
ef17b85a81
103 files changed
+4033
-455
No files matched your search
+69
-13
@@ -1002,7 +1002,7 @@ with `UBICLOUD: left=<0|1|unknown> owner=<o> vm=<name> others=<n>`, checked
|
||||
against the project's VM list after teardown; anything but `left=0` turns a
|
||||
passing command into exit 1. pregate-remote.sh reads that line from the file
|
||||
named by `UBI_LEFT_FILE`, never from the command's output. No owner-age sweep runs by default (the TTL reaper
|
||||
below covers every owner); set
|
||||
below covers this owner's expired VMs); set
|
||||
`UBI_GC_HOURS` to a positive number (or run `gc HOURS`; `gc 0` is a no-op) to
|
||||
sweep only this owner's VMs older than that. For other commands, use the runner
|
||||
directly: `scripts/ubicloud/ubi-runner.sh run --setup <script> -- '<command>'`,
|
||||
@@ -1011,12 +1011,12 @@ or its `up` / `ssh` / `sync` / `pull` / `down` steps (`--help` lists them).
|
||||
The runner is also the `/ubicloud` skill's engine (`bin/gstack-ubi` execs it).
|
||||
A generated name ends in `-t<hours>` (`UBI_TTL_HOURS`, 1-999, default 3; pregate-remote.sh
|
||||
uses its stop deadline plus 15 minutes, rounded up: `-t1` today), the creator's own statement of how long the VM may live: `reap`
|
||||
destroys every VM in the project whose TTL ended more than ten minutes ago,
|
||||
whichever owner made it, and the caller's own `ubirun-<UBI_OWNER>-*` names
|
||||
without a TTL (older runner copies) once they are `UBI_LEGACY_HOURS` old
|
||||
(default 4). Another owner's TTL-less VM and an untagged one are never reaped,
|
||||
because only a TTL lets reap judge another lane's VM. Every `up` runs it
|
||||
first (`UBI_REAP=0` skips it). Names outside the `ubirun-` prefix are never reaped. The
|
||||
destroys only the caller's own VMs: its `ubirun-<UBI_OWNER>-*` names whose TTL
|
||||
ended more than ten minutes ago, and those without a TTL (older runner copies)
|
||||
once they are `UBI_LEGACY_HOURS` old (default 4). Another owner's VM and an
|
||||
untagged one are never reaped, even after their TTL ends; reap only counts them
|
||||
in its log, because cloud deletes stay with the owner that created the VM
|
||||
(GST-573). Every `up` runs it first (`UBI_REAP=0` skips it). Names outside the `ubirun-` prefix are never reaped. The
|
||||
quota preflight adds the size's vCPUs to the project's running VMs and refuses
|
||||
before the create request when that exceeds the known ceiling
|
||||
(`UBI_QUOTA_VCPUS`, or the maximum the last refusal printed, remembered in the
|
||||
@@ -1070,7 +1070,7 @@ machine swap, in this order:
|
||||
one side local and the other remote.
|
||||
4. Profiling very large imports (a whole-repo ingest, the full free suite under
|
||||
a profiler) is warned about before it starts: it holds the VM for hours, and
|
||||
any other lane's `up` reaps it once its TTL (`UBI_TTL_HOURS`, default 3)
|
||||
the same owner's next `up` or `reap` destroys it once its TTL (`UBI_TTL_HOURS`, default 3)
|
||||
plus ten minutes has passed, so set `UBI_TTL_HOURS` above the expected
|
||||
duration and name that duration in the thread before launching.
|
||||
|
||||
@@ -1428,10 +1428,13 @@ No free test may write the developer's real home. `scripts/lib/free-home-guard.t
|
||||
snapshots `~/.gstack`, `~/.claude`, `~/.codex`, `~/.agents` and `~/.config/gstack`
|
||||
(live session logs excluded) and fails the run when an entry changes.
|
||||
|
||||
- A shard that runs alone (`--shard`, CI, the exclusive host-state shard, the
|
||||
flaky retry) owns its window, so the failure names its files.
|
||||
- Full-suite shards run concurrently on one HOME and cannot tell whose write a
|
||||
change was. The runner guards that whole phase once and names no shard.
|
||||
- A shard that runs alone (`--shard`, a CI shard at `--jobs 1` or its trailing
|
||||
serial sub-shard, the exclusive host-state shard, the flaky retry) owns its
|
||||
window, so the failure names its files.
|
||||
- Full-suite shards, and the parallel sub-shards of a CI shard at `--jobs k > 1`
|
||||
([sub-shards](#sub-shards-inside-one-ci-job)), run concurrently on one HOME and
|
||||
cannot tell whose write a change was. The runner guards that whole phase once
|
||||
and names no shard.
|
||||
- `bun run scripts/test-free-shards.ts --attribute-home` names the writer: it
|
||||
runs every file alone with a private HOME (browser cache and git identity
|
||||
still come from the real home) and reports each file that wrote a watched
|
||||
@@ -1450,7 +1453,13 @@ Monday "Weekly test health" workflow (`bun run test:health --enforce`) reads
|
||||
main's ledgers through `scripts/lib/ci-history.ts` and fails when a file flakes
|
||||
in more than 5% of main free-tests runs over a window of at least 20 runs; a
|
||||
window thinner than 20 runs in the last 7 days widens to the newest 20 runs, and
|
||||
fewer than 20 runs is report-only. `bun run test:health flake-summary <dir>`
|
||||
fewer than 20 runs is report-only. A main push that reused its pull request's
|
||||
run ([CI reuse](pregate.md#ci-reuse)) ran no shards, so it stays out of the
|
||||
window and its denominator, and older runs take its place. Such a run has no
|
||||
artifacts and a green `reuse` job; checking that costs one job listing per
|
||||
run with no artifacts, at most 40 per read. Past that cap, or when a listing
|
||||
fails, the run counts as before and the report says so.
|
||||
`bun run test:health flake-summary <dir>`
|
||||
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`).
|
||||
@@ -1559,6 +1568,53 @@ refresh to a maintainer.
|
||||
besides the bot's one refresh commit, later runs leave it alone. Only runs on
|
||||
`main` push the branch; a dispatch from another ref reports only.
|
||||
|
||||
### Sub-shards inside one CI job
|
||||
|
||||
`--ci-run <plan> --shard i --result <file> --jobs k` runs one planned CI shard as
|
||||
k sub-shards side by side on the same machine (`scripts/lib/shard-jobs.ts`). Each
|
||||
sub-shard is an ordinary free shard child: one `bun test` process, serial
|
||||
inside, with its own sandbox, process-group kill and log. Bun's worker
|
||||
`--parallel` mode is still not used (decision V3/D6 in
|
||||
`scripts/test-free-shards.ts`). The default is `k = 1`, which runs today's one
|
||||
child, so no workflow changes behavior until it passes `--jobs`.
|
||||
|
||||
- `--jobs` applies only to `--ci-run`. It takes a positive whole number up to the
|
||||
shard's file count (an empty shard takes 1); anything else stops with
|
||||
`FREE_JOBS_INVALID`. `GSTACK_FREE_JOBS` sets the local full-suite process count
|
||||
and never changes `--ci-run`. The exclusive host-state shard has one file, so
|
||||
a workflow passes it `--jobs 1` or no `--jobs`.
|
||||
- Files are split into disjoint sub-shards packed by the duration seed. Each
|
||||
sub-shard has an id (`<plan id prefix>:<shard>.<n>`) bound to its file list.
|
||||
Files in `TREE_MUTATING` or `SLOW_UNDER_LOAD` run after the parallel
|
||||
sub-shards settle, serially, in one trailing sub-shard.
|
||||
- The parallel phase uses the concurrent-phase real-home guard, the same one
|
||||
full-suite mode uses; the serial sub-shard guards itself and names its files.
|
||||
- The CI shard keeps its k = 1 wall deadline: the parallel sub-shards get it,
|
||||
and the serial sub-shard gets whatever is left of it, so k never extends a
|
||||
deadline. The parent waits for every child to settle.
|
||||
- The parent merges every sub-shard result into the shard's one `outcome`
|
||||
before the single flaky-retry pass, so the five-file retry cap stays per CI
|
||||
shard. A missing, duplicate or incomplete sub-shard result fails the shard
|
||||
with `FREE_SUBSHARD_REJECTED` and vetoes the retry. `--ci-verify` re-checks
|
||||
the `jobs` and `subShards` records a `k > 1` result carries.
|
||||
- Sub-shard runs never write `scripts/free-test-durations.json`; only
|
||||
`--record-durations` does, and only from one-file children.
|
||||
|
||||
`FREE_SUBSHARD_TIMEOUT` means a sub-shard hit its wall at `k > 1`. The message
|
||||
names the file in flight, k, the runner and the CI run id, and prints a serial
|
||||
reproduce command. If the file passes serially, add it to `SLOW_UNDER_LOAD` in
|
||||
`scripts/lib/shard-jobs.ts` with that run id and a reason. Never raise the
|
||||
timeout. `SLOW_UNDER_LOAD` is empty today.
|
||||
|
||||
Every `--ci-run` result, at any k, records per-file test and `expect()` counts
|
||||
in `outcome.counts` (`tests`, `pass`, `fail`, `skip`, `todo`, `expects` per
|
||||
file, plus shard totals). They come from bun's JUnit reporter, which the runner
|
||||
writes into the shard sandbox; on the pinned Bun 1.4.2 it attributes both counts
|
||||
per file inside a multi-file child and leaves the console stream unchanged.
|
||||
`complete: false` (with a `problem`) means the report was missing or did not add
|
||||
up to bun's own test count. Counts are evidence for comparing k values; they
|
||||
never decide a verdict.
|
||||
|
||||
### Windows free lane
|
||||
|
||||
Windows has minimal support ([support tiers](support-tiers.md)). The required
|
||||
|
||||
+3
-2
@@ -317,8 +317,9 @@ stamp on the bare pin leaves the PR conflicting on the CHANGELOG. The lane merge
|
||||
into its pin first (expect a CHANGELOG-only conflict; resolve it with `mergeChangelogs`
|
||||
from `lib/changelog-check.ts`, the `bun -e` one-liner in
|
||||
[ship-policy.md](ship-policy.md#run-a-merge-train), never by hand, and grep for conflict
|
||||
markers before stamping), then restamps, then commits only the merge and the stamp
|
||||
files. In practice slot 1 needs the same merge whenever the base moved after the pin was
|
||||
markers before stamping), then restamps (the restamp stages the stamp files it wrote,
|
||||
so a plain `git commit` after a `--no-commit` merge keeps them), then commits only the
|
||||
merge and the stamp files. In practice slot 1 needs the same merge whenever the base moved after the pin was
|
||||
taken. A stamp commit with one extra byte freezes the queue for every
|
||||
lane behind it.
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ measured later does not count anything that happened after that week.
|
||||
| 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) |
|
||||
| Flaky events | `flake-records-a*` records of the newest gate runs (count, known rerun spend) and `flake-ledger-*` entries of free runs on the default branch; a run whose artifacts cannot be read is counted and named, never scored 0 | 0 repeat cases; under 1 flaky case per 10 gates; free retries under 1% of runs |
|
||||
| Flaky events | `flake-records-a*` records of the newest gate runs (count, known rerun spend) and `flake-ledger-*` entries of free runs on the default branch; a run whose artifacts cannot be read is counted and named, never scored 0, and a main push that reused its pull request's run (no artifacts, `reuse` job green) is left out and named | 0 repeat cases; under 1 flaky case per 10 gates; free retries under 1% of runs |
|
||||
| Issues opened / closed | GitHub search, created and closed in the window | closed ≥ opened; nothing severe older than a week |
|
||||
| Releases cut | releases published in the window, drafts excluded | at least one tagged release a week, on schedule |
|
||||
|
||||
|
||||
+28
-7
@@ -201,17 +201,37 @@ in a data file still schedules the outside voice.
|
||||
added and deleted lines; any class makes the depth full (`risk:<class>` in
|
||||
`reasons=`, `review_depth.risk` in the receipt). money, auth and secret are the
|
||||
[targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code)'s matchers.
|
||||
**security** is a trust-boundary path (trust, redact, sanitize, neutralize,
|
||||
consent, escape) or line (`shell: true`, `execSync(`, `eval`, `new Function(`,
|
||||
raw HTML sinks, a shell run through argv, recursive deletes). **data** is user data: a path naming pii, gdpr, ccpa, privacy,
|
||||
**money** also reads metering and charge words in a path or on a line (meter,
|
||||
metering, metered, charge, charged, billable, billed, credits) and three
|
||||
pairs: usage beside a token or cost word, proxy beside billing, and a client
|
||||
beside a cost or charge word, so a CLI's usage text and a plain proxy stay out
|
||||
(GST-850, from gbrain-evals #289, whose metering proxy and billing clients read
|
||||
`required=none`). **security** is a trust-boundary path (trust, redact, sanitize, neutralize,
|
||||
consent, escape) or line (`shell: true`, `exec(` and `execSync(`, `eval`, `new Function(`,
|
||||
raw HTML sinks, a shell run through argv, recursive deletes), any call to an
|
||||
outbound HTTP client (`fetch`, `http(s).get` or `.request`, axios, got, undici,
|
||||
`requests`, `httpx`, urllib, with no exemption for a literal URL), `new URL` from
|
||||
input, redirect toggles, proxy settings and host allowlists, archive extraction
|
||||
and path traversal (`tar.x`, adm-zip, yauzl, extract-zip, `extractall`, `tar -x`,
|
||||
`unzip`, `path.join` or `resolve` on request or entry input, a `'..'` check added
|
||||
or removed), and any changed line that names the `child_process` or
|
||||
`node:child_process` module, in any import or require spelling. The GST-850
|
||||
rules are coarse on purpose and parse no import clause or call arguments;
|
||||
`test/review-risk-linear.test.ts` holds each of them under 100 ms on
|
||||
200,000-character hostile lines, its anchor followed by a long run of the
|
||||
characters it repeats included. That claim covers only the rules GST-850 adds,
|
||||
not R51's older ones. **data** is user data: a path naming pii, gdpr, ccpa, privacy,
|
||||
personal, account, customer, cookie, persistence, storage or database, or a line
|
||||
that drops a table or column, deletes rows or bulk-deletes records. Auth scope,
|
||||
migrations and deleted code paths were already gate class. `RISK_RULES` hashes
|
||||
the patterns and `DEPTH_RULES` includes it.
|
||||
|
||||
**UI text and SVGs** stay code (light unless a risk class applies). A docs path
|
||||
for UI copy, locale values and chart SVGs waits for an allowlist classifier: three
|
||||
rounds of R51 review found bypasses in line-pattern versions of both.
|
||||
for UI copy, locale values and chart SVGs was built as an allowlist in GST-850 and
|
||||
dropped for review cost: its first review round found a new bypass class in each
|
||||
version, the same pattern as three rounds of R51. When a diff publishes numbers,
|
||||
the batched specialist's diff keeps the write-up's text files instead of leaving
|
||||
docs out.
|
||||
|
||||
**Batched checklists (the lean ensemble, GSTA-167 part 2).** One specialist
|
||||
subagent reads Testing and Security on every code or gate diff, plus
|
||||
@@ -545,8 +565,9 @@ ones, and the arm lost blockers. `bin/gstack-review-dispatch` closes both ends:
|
||||
prompts prints `VERIFY: no brief records` (exit 3), never clean.
|
||||
6. `pass --base <b> --out <dir> --pass <id> -- <plan flags>` runs one pass from a
|
||||
staged manifest (`<dir>/pass-manifest.json`): audit readers marked first, then
|
||||
the specialists with QA and the other audits, then the Red Team, then Step 11's
|
||||
voices. Each reviewer keeps its plan entry and is rendered by the same code as
|
||||
the specialists with the other audit readers, then the Red Team, then Step 11's
|
||||
voices. A QA extra is refused before any reader is created: Step 9.2.1's probes
|
||||
are the parent's. Each reviewer keeps its plan entry and is rendered by the same code as
|
||||
`render`, once its inputs exist; the Red Team's findings file is the specialists'
|
||||
`merged.jsonl` lines byte for byte. A brief leaves the bin in indexed chunks of at
|
||||
most 65,536 characters (`chunk`), so no tool output line carries a whole brief and a
|
||||
|
||||
+48
-8
@@ -401,6 +401,28 @@ release paths (version files, CHANGELOG, fragments, `release_outputs`,
|
||||
train head and branch are S_k, and the one full gate runs on S_k, the exact
|
||||
tree the last merge publishes.
|
||||
|
||||
**One land lock.** `train land` takes the same repository land lock as
|
||||
[`land`](#land), `refs/notes/gstack-land`, as `train <nonce> slot <i> (#<pr>)`,
|
||||
before its first read of main, so a standalone land and a train slot never
|
||||
both publish onto one main: whichever starts second refuses with
|
||||
`QUEUE_LAND_LOCKED`, naming the holder. Once a slot has merged, the train keeps
|
||||
the lock in phase `between`, which only the same train's next slot takes over,
|
||||
until its last landable slot lands; a refusal before a later slot's push (a
|
||||
failed `gh` or `git fetch` read, say) keeps it too, so no standalone land slips
|
||||
in while the train waits on a rerun. Before any slot merged, a refusal before
|
||||
the push releases it. After a push, a failure keeps the lock in its phase and a
|
||||
rerun of the same slot takes it back. A stop that freezes the queue
|
||||
leaves the lock in phase `freeze`, which `--break-lock` never breaks: only the
|
||||
repair train the freeze admits takes it over, and it is released when that
|
||||
train clears the freeze. `--break-lock` prints the holder (`LOCK_HOLDER:`),
|
||||
keeps land's staleness, mid-push and drift rules, refuses a `between` lock
|
||||
younger than 10 minutes, and refuses a live train run's lock unless
|
||||
`--break-train-lock` is also given. `train watch --land` goes through `train
|
||||
land`, so it stops with `NEXT:` on `QUEUE_LAND_LOCKED` like any refusal. A train
|
||||
`train land` refuses (one built without stamped prefixes or a gate) still merges
|
||||
through `verify --train` and `gh pr merge`, which neither take nor check this
|
||||
lock.
|
||||
|
||||
Slots merge with `train land --train <nonce> --pr <n>`, never a slot call:
|
||||
|
||||
1. The record must be the gated head's own chain. Walking first parents from
|
||||
@@ -851,8 +873,8 @@ git fetch origin main
|
||||
git merge --no-ff --no-commit origin/main # expect a CHANGELOG.md conflict only
|
||||
bun --no-env-file --config=/dev/null -e "import { mergeChangelogsWithNotes } from './lib/changelog-check'; import { spawnSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; const s = (n) => spawnSync('git', ['show', ':' + n + ':CHANGELOG.md'], { encoding: 'utf8', maxBuffer: 1 << 26, timeout: 30000 }).stdout; const m = mergeChangelogsWithNotes(s(2), s(3), s(1)); for (const n of m.notes) console.error('CHANGELOG: carried as a Gate integrity correction: ' + n); if (!m.text) { console.error('CHANGELOG needs a hand merge'); process.exit(1); } writeFileSync('CHANGELOG.md', m.text);"
|
||||
git add CHANGELOG.md && git commit --no-edit
|
||||
gstack-restamp --next --base main --pr 42 --expect-base <main sha> --expect-order <order> --allow-repo-commands
|
||||
git add CHANGELOG.md VERSION package.json <release outputs> && git commit -m "v<X> stamp" && git push
|
||||
gstack-restamp --next --base main --pr 42 --expect-base <main sha> --expect-order <order> --allow-repo-commands # stages what it writes (STAGED:)
|
||||
git commit -m "v<X> stamp" && git push
|
||||
```
|
||||
|
||||
A conflict in any file other than CHANGELOG.md means the merge isn't the train's prefix. Stop and report it. When the helper says the CHANGELOG needs a hand merge, take main's CHANGELOG unchanged and put your own entry above it. `verify` accepts the resulting head because it contains main and its tree equals T<i> modulo stamps, with its CHANGELOG judged as main's plus lines your pin added, however the conflict was resolved; the `head:` line says "T<i> with main merged in".
|
||||
@@ -865,8 +887,12 @@ introduced). A line the base added since the pin's fork point is the base's,
|
||||
whether the base merge is committed or still staged (`stage stamp_paths` says
|
||||
"N line(s) the base added since the fork point are the base's"; train 24,
|
||||
2026-10-27, moved main's `DOCS_CAPS_KEY_VERSION` in both slots before this
|
||||
rule). A file the restamp lists that the slot call did not name is not a stamp:
|
||||
leave it out and report it. The tree receipt refuses such a move after the fact
|
||||
rule). Restamp stages what it writes and prints `STAGED:`; commit what restamp
|
||||
staged. A file the slot call does not name is not a stamp: `git restore --staged
|
||||
--worktree` it and report it. A stamp file holding unstaged edits of your own refuses before
|
||||
anything is written (`RESTAMP_UNSTAGED_EDITS`), and a staging failure leaves the
|
||||
files written for the noop rerun to stage (`RESTAMP_NOT_STAGED`), so a bare
|
||||
commit after a `--no-commit` base merge can no longer drop the stamp (#3527). The tree receipt refuses such a move after the fact
|
||||
only while the moved version is older than the base's at the build, so the
|
||||
restamp, not the receipt, is what keeps the base's key in place.
|
||||
|
||||
@@ -937,7 +963,18 @@ saw, itself the prefix before modulo stamps) plus the PR's own entry under the
|
||||
same rule. One CHANGELOG rule applies before and after, so a head the pre-merge
|
||||
verify accepted confirms after its squash merge with the same CHANGELOG, and a
|
||||
stacked child whose one `[Unreleased]` became two released sections is not a
|
||||
mismatch (train 24, 2026-10-27, froze on exactly that). Anything else is
|
||||
mismatch (train 24, 2026-10-27, froze on exactly that). Before
|
||||
`MERGE_CONDITION`, the head must also carry its own stamp: its `VERSION` above
|
||||
main's, and its CHANGELOG headed with that version directly above main's top
|
||||
release, with no `[Unreleased]` section main does not have; otherwise
|
||||
`QUEUE_TRAIN_UNSTAMPED` names the rule and both versions (train 33 merged #3527
|
||||
under #3526's version without it). verify prints `STAMP_VERSION:` from the head
|
||||
itself. A post-merge confirmation needs GitHub to report the PR MERGED: an open
|
||||
PR whose stamp-only diff already matches main (a release-only PR, #3530) takes
|
||||
the pre-merge path, and any other open or unreadable PR refuses
|
||||
`QUEUE_PROOF_MISSING` without freezing, as does a verify that could not read
|
||||
the PR's current head (`--no-fetch` or a failed fetch) instead of judging the
|
||||
unstamped pin. Anything else is
|
||||
`QUEUE_TRAIN_TREE_MISMATCH` (or `QUEUE_TRAIN_MOVED`). When nothing of the train
|
||||
has merged, that is a plain refusal, and a new train is built on current main. When
|
||||
earlier slots already merged, main holds a prefix no gate covered on its own,
|
||||
@@ -1629,9 +1666,12 @@ command:
|
||||
2. **Lock.** Take the repository land lock, `refs/notes/gstack-land` (its own
|
||||
ref, never the trains notes ref), by exact lease. It records the PR, holder,
|
||||
phase and journal. A held lock is `QUEUE_LAND_LOCKED` naming the holder;
|
||||
`--break-lock` takes over a crashed holder's lock and is refused while that
|
||||
holder entered a push or a merge less than 10 minutes ago, and for a drift
|
||||
lock. A refusal before the push releases the lock.
|
||||
`--break-lock` prints the holder (`LOCK_HOLDER:`), takes over a crashed
|
||||
holder's lock and is refused while that holder entered a push or a merge
|
||||
less than 10 minutes ago, and for a drift lock. `train land` shares this
|
||||
lock ([one land lock](#train-land)); a train slot's lock needs
|
||||
`--break-train-lock` as well while its train still lands, and a `freeze`
|
||||
lock is never broken. A refusal before the push releases the lock.
|
||||
3. **Merge and stamp.** In a scratch worktree, merge the base into the pin.
|
||||
Conflicts in release fields only (the version file and `release_outputs`
|
||||
take the base's side, the CHANGELOG merges by section, a manifest or npm
|
||||
|
||||
+3
-3
@@ -1006,8 +1006,8 @@ VM. `/ubicloud` runs it there through one entry point, `bin/gstack-ubi`
|
||||
(`scripts/ubicloud/ubi-runner.sh`), with the discipline a shared quota needs:
|
||||
every VM is named `ubirun-<owner>-<epoch>-<suffix>-t<hours>`, so its owner
|
||||
(`UBI_OWNER`, required to create one) and TTL (`UBI_TTL_HOURS`, default 3) are
|
||||
visible to everyone; every `up` first reaps VMs whose TTL ended, whichever owner
|
||||
made them, and its own owner's VMs with no TTL once they are 4 hours old; every run
|
||||
visible to everyone; every `up` first reaps its own owner's VMs whose TTL ended,
|
||||
and those with no TTL once they are 4 hours old, never another owner's; every run
|
||||
ends with `UBICLOUD: left=0` or fails; the
|
||||
quota preflight refuses before the create request when the project's running
|
||||
VMs plus the new size exceed the known ceiling (`UBI_QUOTA_VCPUS`, or the
|
||||
@@ -1030,7 +1030,7 @@ Claude: UBICLOUD_API_KEY: set; UBI_OWNER=lane7
|
||||
gstack-ubi verify (.gstack/ubicloud.json: bootstrap.sh, "bash scripts/verify.sh", standard-16)
|
||||
ubi-runner: reap: no expired VMs
|
||||
ubi-runner: creating eu-central-h1/ubirun-lane7-1761500000-9f3a2c10-t3 (standard-16)
|
||||
ubi-runner: ubirun-lane7-1761500000-9f3a2c10-t3 carries a 3h TTL (any later run of this script reaps it); if this process dies first: gstack-ubi down ubirun-lane7-1761500000-9f3a2c10-t3
|
||||
ubi-runner: ubirun-lane7-1761500000-9f3a2c10-t3 carries a 3h TTL (this owner's next up or reap destroys it once it expires); if this process dies first: gstack-ubi down ubirun-lane7-1761500000-9f3a2c10-t3
|
||||
ubi-runner: heartbeat: command 300s elapsed, last output 2s ago
|
||||
ubi-runner: command exited 0 after 1140s
|
||||
ubi-runner: destroyed eu-central-h1/ubirun-lane7-1761500000-9f3a2c10-t3
|
||||
|
||||
+97
-8
@@ -880,7 +880,11 @@ pick a new `--output`), then copy the recovery file to where you want it.
|
||||
**Meaning.** On Codex, Factory, OpenCode, Cursor, Copilot and Kiro every bash
|
||||
block finds gstack on its own: an exported `GSTACK_ROOT` (with `bin/` and
|
||||
`lib/`), then a repo-local install, then the host's global install
|
||||
(`$CODEX_HOME/skills/gstack` on Codex). None existed.
|
||||
(`$CODEX_HOME/skills/gstack` on Codex). None existed. A skill installed for one
|
||||
folder (Hermes, or the Codex install `./setup` writes) is the exception for its
|
||||
first block: that block runs the folder's own `bin/gstack-skill-start` and
|
||||
ignores an exported `GSTACK_ROOT`. When the folder is missing, the block prints
|
||||
this line and its start command then fails.
|
||||
|
||||
**Fix.**
|
||||
|
||||
@@ -3277,6 +3281,42 @@ result.
|
||||
**Fix.** Run `gstack-autoplan status --out <dir>` and submit for the attempt
|
||||
the last `next` printed (`ATTEMPT: ... phase=<p> voice=<v>`).
|
||||
|
||||
<a id="ceo-only-conflict"></a>
|
||||
### `CEO_ONLY_CONFLICT`
|
||||
|
||||
**Meaning.** `gstack-autoplan next --ceo-only` was combined with `--ui`,
|
||||
`--developer-tool` or `--agent-primary` (on the command line or in the plan's
|
||||
`gstack-autoplan-scope` marker), each of which adds a phase a CEO-only run
|
||||
never opens; or the marker names `--ceo-only` (the mode is a command-line
|
||||
choice for one run, so a plan can't pin it); or the plan is `<out>/ceo-review.md`
|
||||
(directly, through a symlink or a hard link), the report the run would write
|
||||
over it. The finish checks that last case again before it writes the report.
|
||||
|
||||
**Fix.** Drop the conflicting flag (or the marker token), move the plan out of
|
||||
the report path, or run the full review without `--ceo-only`.
|
||||
|
||||
<a id="ceo-only-after-init"></a>
|
||||
### `CEO_ONLY_AFTER_INIT`
|
||||
|
||||
**Meaning.** `next --ceo-only` named a run directory that already holds a full
|
||||
review run. A run keeps the mode it started with.
|
||||
|
||||
**Fix.** Start the CEO-only run in a new `--out` directory, passing
|
||||
`--ceo-only` on its first `next`.
|
||||
|
||||
<a id="ceo-only-no-gate"></a>
|
||||
### `CEO_ONLY_NO_GATE`
|
||||
|
||||
**Meaning.** `answer`, `recommend` or `revise` was run on a CEO-only run, or
|
||||
`gstack-approvals import` was pointed at one. A CEO-only
|
||||
run writes no gate, ends `status=reviewed` and is never approved, so there is
|
||||
nothing to answer, change or import. (`budget` and `close --deferred` still
|
||||
work: they are how a budget-stopped CEO-only run recovers.)
|
||||
|
||||
**Fix.** Read `<run dir>/ceo-review.md`. For a gate and an approval, start a
|
||||
full review in a new directory: `gstack-autoplan next --out <new dir> --plan
|
||||
<file>` without `--ceo-only`.
|
||||
|
||||
<a id="attempt-mismatch"></a>
|
||||
### `ATTEMPT_MISMATCH`
|
||||
|
||||
@@ -4604,7 +4644,10 @@ train still building, running or merging, `train watch` on an unknown record,
|
||||
unreadable run), `verify --pr <n> --train <nonce>` when main's tree mismatches
|
||||
and whether a slot merged on GitHub cannot be read (so whether the queue must
|
||||
freeze is unknown; it never answers "nothing merged" from an unreadable
|
||||
state), `train land` when the base or the train branch cannot be fetched or
|
||||
state), `verify --pr <n> --train <nonce>` when GitHub does not report the PR
|
||||
MERGED and it is not a release-only PR whose stamp-only diff already matches
|
||||
main, or when it could not read the PR's current head (`--no-fetch` or a failed
|
||||
fetch), without freezing the queue, `train land` when the base or the train branch cannot be fetched or
|
||||
the record's T0 and S_1..S_k are not the gated head's first-parent chain
|
||||
(each S_i's second parent slot i's pin), `train land` when main's tree is the
|
||||
slot's S_i but GitHub does not report the PR as MERGED (a record whose slot
|
||||
@@ -4710,14 +4753,60 @@ the name already landed. Then rerun.
|
||||
<a id="queue-land-locked"></a>
|
||||
### `QUEUE_LAND_LOCKED`
|
||||
|
||||
**Meaning.** Another `gstack-ship-queue land` holds the repository land lock
|
||||
(`refs/notes/gstack-land`), or took it from under this run, so this one would
|
||||
race it for the next version. The line names the holder, its PR and phase.
|
||||
**Meaning.** Another `gstack-ship-queue land`, or a `train land` slot, holds the
|
||||
repository land lock (`refs/notes/gstack-land`), or took it from under this run,
|
||||
so this one would race it onto main. The line names the holder: `#<pr>` for a
|
||||
land, `train <nonce> slot <i> (#<pr>)` for a train, with its holder and phase. A
|
||||
train keeps the lock in phase `between` from one merged slot to the next, so no
|
||||
standalone land slips in between slots.
|
||||
|
||||
**Fix.** Wait for the holder to finish. If it crashed, rerun with
|
||||
`--break-lock`; that is refused while the holder entered a push or a merge
|
||||
less than 10 minutes ago. A lock in phase `drift` is released by landing its
|
||||
repair PR ([QUEUE_LAND_DRIFT](#queue-land-drift)).
|
||||
`--break-lock`, which prints the holder (`LOCK_HOLDER:`). That is refused while
|
||||
the holder entered a push or a merge less than 10 minutes ago, while a train is
|
||||
between slots for less than 10 minutes, and for a live train run unless you also
|
||||
pass `--break-train-lock`. A lock in phase `drift` is released by landing its
|
||||
repair PR ([QUEUE_LAND_DRIFT](#queue-land-drift)); a lock in phase `freeze` is
|
||||
released by the verify that clears the freeze
|
||||
([QUEUE_TRAIN_FROZEN](#queue-train-frozen)). If that release push failed, the
|
||||
next `land` or `train land` takes the lock over once the freeze record it fetches
|
||||
shows the freeze cleared after the lock was written; `--break-lock` never does.
|
||||
|
||||
<a id="queue-train-unstamped"></a>
|
||||
### `QUEUE_TRAIN_UNSTAMPED`
|
||||
|
||||
**Meaning.** `verify --pr <n> --train <nonce>` found the slot head it would let
|
||||
merge without its own stamp: the head's `VERSION` is not above main's, or its
|
||||
CHANGELOG's top section is not headed with that version directly above main's
|
||||
top release (or keeps an `[Unreleased]` section main does not have). The line
|
||||
names the rule, the head's version and main's. Train 33 merged #3527 this way
|
||||
(a no-commit base merge, a restamp whose files were never staged, a bare
|
||||
commit) and shipped it under #3526's version. Nothing was merged.
|
||||
|
||||
**Fix.** On the lane, run `gstack-restamp --next` on the head, commit what
|
||||
restamp staged, push, and rerun the verify.
|
||||
|
||||
<a id="restamp-not-staged"></a>
|
||||
### `RESTAMP_NOT_STAGED`
|
||||
|
||||
**Meaning.** `gstack-restamp` wrote the stamp but `git add` failed, so a bare
|
||||
`git commit` would leave the stamp out. The files stay written, and
|
||||
`.gstack/tmp/restamp-staging.json` records what is still to stage.
|
||||
|
||||
**Fix.** Clear what blocked `git add` (a stale `.git/index.lock`, for
|
||||
example) and rerun `gstack-restamp`; the rerun is a noop that stages the files
|
||||
it wrote. When any of them changed since restamp wrote it (you committed by
|
||||
hand, switched branches or merged the base), the marker is stale: the noop
|
||||
rerun drops it and stages nothing, and a stamp still owed is restamped as usual.
|
||||
|
||||
<a id="restamp-unstaged-edits"></a>
|
||||
### `RESTAMP_UNSTAGED_EDITS`
|
||||
|
||||
**Meaning.** A file `gstack-restamp` would stamp holds unstaged edits of yours.
|
||||
Restamp stages what it writes, so those edits would ride along in the stamp
|
||||
commit. Nothing was written.
|
||||
|
||||
**Fix.** Stage (`git add`) or stash your own edits to the named files, then
|
||||
rerun `gstack-restamp`.
|
||||
|
||||
<a id="queue-land-conflict"></a>
|
||||
### `QUEUE_LAND_CONFLICT`
|
||||
|
||||
+31
-7
@@ -259,7 +259,7 @@ tree no review saw, so /ship runs again;
|
||||
| File | Schema | Contents |
|
||||
|------|--------|----------|
|
||||
| `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. |
|
||||
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`. |
|
||||
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`. |
|
||||
| `findings.jsonl` | `findings` v1 | One row per reviewer finding: `id`, `phase`, `voice` (`native`/`outside`), `model`, `severity`, `title`, `disposition`, `plan_items`, `native_counterpart` (string or null), `source`. Rows share the review-log row shape, so `gstack-review-log --findings` derives review status from this file. |
|
||||
| `tasks.jsonl` | `tasks` v2 | Implementation tasks with `id`, `title`, `status`, `depends_on`, `findings` (ids in `findings.jsonl`), `blocked_by`, `acceptance`, optional `pr`/`item`/`tier`. |
|
||||
| `timing.json` | `timing` v1 | Per-phase `started_at`, `ended_at`, `wall_s`, `outside_s`, `native_s`; `started_at` and `total_wall_s` for the run; the Phase 0 `estimate`. |
|
||||
@@ -596,12 +596,12 @@ without being shown to the owner.
|
||||
## CEO-only runs
|
||||
|
||||
`/autoplan --ceo-only` runs the CEO review by itself and asks nothing. It runs
|
||||
in the session that invokes the skill; `gstack-autoplan`, the driver a parent
|
||||
agent uses, has no CEO-only mode yet, and `run.json` does not accept the
|
||||
`reviewed` status. It needs Claude Code: other hosts inline autoplan's sections
|
||||
and do not carry this one yet. It is for a harness or a crew that wants the strategy pass
|
||||
without the full gauntlet, such as an eval comparing gstack's CEO review with a
|
||||
plain prompt.
|
||||
in the session that invokes the skill and needs Claude Code: other hosts inline
|
||||
autoplan's sections and do not carry this one yet. A parent agent on any host
|
||||
gets the same run from the driver with `gstack-autoplan next --ceo-only` (see
|
||||
below). It is for a harness or a crew that wants the strategy pass without the
|
||||
full gauntlet, such as an eval comparing gstack's CEO review with a plain
|
||||
prompt.
|
||||
|
||||
- **What runs:** Phase 0, the outside-reviewer preflight and the CEO phase.
|
||||
Design, DX, Eng and the final gate do not run.
|
||||
@@ -627,6 +627,30 @@ plain prompt.
|
||||
`test/skill-e2e-autoplan-ceo-only.test.ts` runs it on a plan that makes the
|
||||
interactive CEO review ask a question and fails if this mode asks anything.
|
||||
|
||||
**From the driver.** `gstack-autoplan next --out <dir> --plan <file>
|
||||
--ceo-only` opens only the CEO phase; the parent dispatches and submits its
|
||||
voices as usual. When CEO closes, the run prints `REVIEWED:` and ends
|
||||
`GSTACK_RESULT: skill=autoplan status=reviewed run=<dir>`, with
|
||||
`<dir>/ceo-review.md` in the same sections, hashed in `run.json`. Each item a
|
||||
gate would have asked becomes a `decisions.jsonl` row of kind `auto` and status
|
||||
`auto_kept` (nobody answered: the plan's direction is kept, and a round-cap item
|
||||
only the owner can accept, such as a Critical left open, stays undecided with
|
||||
no chosen option), and tasks are blocked on no decision. An open Critical or
|
||||
High finding, which a full gate asks about, gets no row: nobody decides it, and
|
||||
it stays in the report's Findings and in `tasks.jsonl` for the owner. The driver never edits the plan, writes no `gate.json` and never
|
||||
approves, so `gstack-artifact validate` accepts the run as it stands.
|
||||
|
||||
- The flag goes on the first `next` only. It refuses beside `--ui`,
|
||||
`--developer-tool` or `--agent-primary` on the command line or in the plan's
|
||||
scope marker, when the marker names it, and on a plan at the report path
|
||||
(`CEO_ONLY_CONFLICT`); a directory that holds a full run refuses it
|
||||
(`CEO_ONLY_AFTER_INIT`).
|
||||
- `answer`, `recommend` and `revise` refuse a CEO-only run, and so does
|
||||
`gstack-approvals import` (`CEO_ONLY_NO_GATE`): there is no gate to answer and
|
||||
nothing was approved. A run stopped by `--spend-cap` recovers as a full run
|
||||
does: `budget` then `resume`, or `close --phase ceo --deferred`, which ends
|
||||
it `reviewed`.
|
||||
|
||||
## Owner brief
|
||||
|
||||
`gstack-owner-brief render --out <dir>` is what the owner sees at the final
|
||||
|
||||
Reference in new issue
Block a user