mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
v1.91.101.0 fix: close the field wave's residual gaps (#3173)
The field wave's residual gaps are closed. v1.91.96.0 through v1.91.100.0 shipped the install-honesty and unattended-`/autoplan` wave (`docs/designs/GSTACK_FIELD_WAVE_2026_10_12.md`); running that release on a Capy machine left six gaps, each reproduced here with a test that failed first.
This commit is contained in:
1 parent
15ebd85cdd
commit
47d639abff
49 files changed
+1111
-132
No files matched your search
+30
-6
@@ -5,7 +5,7 @@ Three layers, each owned by gstack:
|
||||
| Layer | Where | Job |
|
||||
|---|---|---|
|
||||
| Bootstrapper | `capy/skill/gstack/scripts/install.sh` (copied once into the owner's Drive skill folder; frozen) | clone when absent; fetch origin's default branch and read the supported minimum from `FETCH_HEAD:capy/support.env`; in install mode fast-forward a clean, unpinned checkout below it; exec the full wrapper |
|
||||
| Full wrapper | `bin/gstack-capy-session` (moves with the checkout) | map the session start onto the installer: `--check` certifies, otherwise install or repair |
|
||||
| Full wrapper | `bin/gstack-capy-session` (moves with the checkout) | map the session start onto the installer: `--check` certifies (and reports a checkout behind origin), otherwise fast-forward a checkout behind origin (`bin/gstack-upgrade --ff-only-no-reinstall`), then install or repair |
|
||||
| Installer | `bin/gstack-capy-install` | Bun, both host profiles, the doctor, the install verdict, the launch line, the trailer |
|
||||
|
||||
The vendored skill (`capy/skill/gstack/SKILL.md`) tells a Capy agent how to use
|
||||
@@ -36,9 +36,23 @@ git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.lo
|
||||
checkout is never moved; the trailer names the cause and the exact commands
|
||||
([REVISION_BELOW_MINIMUM](troubleshooting.md#revision-below-minimum)).
|
||||
`GSTACK_CAPY_AUTO_UPGRADE=0` disables the fast-forward.
|
||||
3. When the fetch fails (offline), the bootstrapper says `remote minimum
|
||||
3. The minimum is the floor, not the trigger. Above it, a checkout behind
|
||||
origin's default branch still moves at install-mode session start: the
|
||||
full wrapper runs `bin/gstack-upgrade --ff-only-no-reinstall` (every
|
||||
refusal of `gstack-upgrade` applies), prints `SELF_UPGRADE: done <old> ->
|
||||
<new>` and re-execs the new checkout's wrapper, which installs from the
|
||||
new revision (`GSTACK_CAPY_UPGRADED_FROM` set, so setup is never skipped).
|
||||
A refusal prints `SELF_UPGRADE: skipped (<reason>)` and the session
|
||||
continues on the installed revision; the trailer stays the doctor's.
|
||||
`SELF_UPGRADE: none (current)` when nothing is ahead;
|
||||
`GSTACK_CAPY_SELF_UPGRADE=off` (or `GSTACK_CAPY_AUTO_UPGRADE=0`) prints
|
||||
`SELF_UPGRADE: disabled (<variable>)`. `--check` never moves the checkout;
|
||||
behind origin it prints `gstack-capy-session: behind origin/<branch>:
|
||||
<installed> -> <remote> (<n> commits); fix: bash <skill>/scripts/install.sh
|
||||
[--for <skill>] (fast-forwards)` and the agent runs that fix.
|
||||
4. When the fetch fails (offline), the bootstrapper says `remote minimum
|
||||
unknown (fetch failed)` and checks the local install only; it never claims
|
||||
the minimum.
|
||||
the minimum, and nothing is fast-forwarded.
|
||||
|
||||
The upgrade target is always the tip of the default branch: every commit there
|
||||
is a stamped release that passed CI. Recovery runs forward, never back: an
|
||||
@@ -55,6 +69,14 @@ and reinstalls (`bin/gstack-capy-install` on a Capy install, else `./setup
|
||||
[UPGRADE_REFUSED](troubleshooting.md#upgrade-refused) and never stashes or
|
||||
resets.
|
||||
|
||||
Several agents sharing one checkout (eight Capy subagents on a machine) hold
|
||||
workflow leases on it: every `bin/gstack-skill-start` touches
|
||||
`<git dir>/gstack-leases/<harness pid>` (`bin/gstack-lease touch`; pruned after
|
||||
120 minutes). `bin/gstack-upgrade`, and so the session-start self-upgrade,
|
||||
refuses with [UPGRADE_BUSY](troubleshooting.md#upgrade-busy) while a lease
|
||||
whose pid is alive and outside its own process chain exists;
|
||||
`bin/gstack-lease list` shows them and `GSTACK_UPGRADE_FORCE=1` overrides.
|
||||
|
||||
## What the installer does
|
||||
|
||||
1. Reuses the checkout it lives in (or clones into `--root <dir>`), under the
|
||||
@@ -194,7 +216,7 @@ git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.lo
|
||||
|
||||
and snapshots capture `$HOME` (the checkout, `~/.bun/bin`, both host
|
||||
profiles). Each session then pays only the `--check`. A baked revision still
|
||||
moves forward at session start when it falls below the supported minimum, and
|
||||
moves forward at install-mode session start whenever it is behind origin, and
|
||||
the baked `~/.bun/bin` reaches workflows through the `GSTACK_LAUNCH` line. The
|
||||
project Setup is shared by every thread in the project, so changing it is the
|
||||
project's decision.
|
||||
@@ -203,8 +225,10 @@ project's decision.
|
||||
|
||||
`capy/support.env` holds `GSTACK_MIN_REVISION` and `GSTACK_CAPY_MIN_WRAPPER`.
|
||||
Raise `GSTACK_MIN_REVISION` only for install-honesty or security fixes, at
|
||||
stamp time, to the VERSION being released: every machine below it is
|
||||
fast-forwarded at its next session start. It never exceeds VERSION
|
||||
stamp time, to the VERSION being released: it is the floor below which the
|
||||
trailer fails and the bootstrapper itself fast-forwards (a checkout above it
|
||||
that is behind origin is fast-forwarded by the checkout's own wrapper, so the
|
||||
floor is not what keeps machines current). It never exceeds VERSION
|
||||
(test/capy-skill-wrapper.test.ts). Raise `GSTACK_CAPY_MIN_WRAPPER` together with
|
||||
`GSTACK_CAPY_WRAPPER` in the bootstrapper when the bootstrapper itself
|
||||
changes; older Drive copies keep working and print an owner action
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# gstack fix wave (October 12, 2026): install honesty, unattended autoplan on Capy, plan-to-gate, queue-aware shipping
|
||||
|
||||
## URGENT, outside this plan
|
||||
|
||||
- `fixwave-run-20261012204516-ceo-native-6` [High]: D16 scans outgoing diffs; sealed eval transcripts and a quoted sealed answer are **already committed** in gbrain's public repository, and nothing in this wave rewrites history. Owner: the gbrain repo owner (Garry). Options: scrub history and force-push the affected refs, or accept the disclosure in writing. This wave only stops the next one.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
### Context
|
||||
|
||||
Base: origin/main `70fab3e` (v1.91.94.0). Input: field evidence from the GBRA threads, which ran gstack on Capy cloud machines during gbrain fix waves 15–16 (2026-10-10..12, gstack 1.91.22–1.91.94), plus reproductions on this machine (a Capy cloud box, Ubuntu 24.04, Bun 1.3.14 at `/usr/local/bin/bun`, no Codex CLI harness). Plan revision 3: revision 1 was reviewed by six voices (CEO, DX, Eng; Claude and GPT each), every gate item was answered `b` (take the reviewers' change), revision 2 folded those in and was reviewed again by six voices; this revision folds in the revision-2 findings (eight gate items plus the in-item fixes). The Review record lists what each phase accepted at each revision.
|
||||
|
||||
Reproduced here before writing a line (2026-10-12, this machine):
|
||||
|
||||
- **A1/A2.** `~/.local/share/gstack` sat at 1.91.69.0, 25 releases behind. The drive skill's `install.sh --check` printed `gstack: fail installer — checkout … predates gstack-doctor --check (installed 1.91.69.0); fix: … or run /home/user/.local/share/gstack/bin/gstack-upgrade to move it` and exited 1. `bin/gstack-upgrade` does not exist at any revision (`git ls-tree origin/main bin/ | grep -c gstack-upgrade` → 0); `gstack-upgrade/` is a Claude-only skill directory (`lib/doctor-components.ts:117`, `CLAUDE_ONLY`). The doctor's own fix text names it too (`bin/gstack-doctor-check.sh:338`: `…, or run /gstack-upgrade`). Rerunning the installer with a sidecar root installed 1.91.94.0 in 32 s `[measured: this machine 2026-10-12]` but ended `gstack: fail claude`, because `./setup` left `~/.claude/skills/gstack` pointing at the stale checkout. Only `git merge --ff-only origin/main` in the stale checkout plus `./setup` recovered the machine, exactly the field report.
|
||||
- **A3.** After that recovery the installer printed `gstack: ok 1.91.94.0 for=autoplan` and exited 0. The very next `install.sh --check` in a fresh shell printed `FAIL runtime — bun 1.3.14 at /usr/local/bin/bun is below the supported minimum 1.4.2` and `FAIL pins`, trailer `gstack: fail runtime,pins`, exit 1; with `PATH=$HOME/.bun/bin:$PATH` the same check printed `gstack: ok`. The installer exports the verified Bun only inside its own shell (`bin/gstack-capy-install:108`) and the `GSTACK_LAUNCH` line it prints (`:145`) carries no `PATH`. The runtime FAIL row's `fix:` says to reinstall Bun although `~/.bun/bin/bun` is already 1.4.2 (`lib/result-codes.ts:126`). `lib/autoplan-prompts.ts:33` and `lib/restamp.ts:177,314` spawn `bun` by PATH name.
|
||||
- **B5.** `lib/autoplan-gate.ts:107-120` `finishGate` writes decision rows whose ids come from `lib/gate-list.ts:148-150` as `${list.run}-${item.id}`; `gate_rev` is a separate column, so a second gate revision re-emits the same ids and `gstack-artifact validate` reports `ARTIFACT_DUPLICATE_ID`. (`finishGate` already retains earlier revisions' rows; the ids are what collide.) Reproduced by this run itself: after gate rev 2, `gstack-artifact validate run.json` reports 9 `ARTIFACT_DUPLICATE_ID` errors (`d1`..`d8`, `p1` at `decisions.jsonl:19-27`), so the run cannot validate until B5 ships.
|
||||
- **B6/B8.** `bin/gstack-autoplan --help`: no `revise` subcommand. Reopened phases snapshot the run's `plan-active.md` (`lib/autoplan-run.ts:119`), so a reopen does review a revised plan when that file is edited; the field reruns reviewed the old plan because the source document was edited instead, and nothing told the agent which file the run reads. The snapshot tool (`bin/gstack-autoplan-snapshot.ts:90`) requires exactly one `## Implementation plan` followed by one `## Review record` (`lib/autoplan-export.ts:54-57` is tolerant); `init` already scaffolds a heading-less plan (`:415`), and the error names neither the missing heading nor a fix. After a `b` answer, `answer` prints `NEXT: next` (`bin/gstack-autoplan:184`) and `next` snapshots and dispatches before anyone is told to revise, which is how the field reruns reviewed the old plan.
|
||||
- **B9.** `lib/autoplan-gate.ts:81-83`: every user-challenge item is built with `recommended: 'a'` = "keep your direction" even when both voices recommend the change. Live example: this plan's own gate rev 1 rendered 17 items as "keep your direction (recommended)" while the reviewers argued the opposite on every one. `lib/gate-list.ts:138` resolves `all` to `recommended`, and `answer` reopens on `how === 'override'`, not on the option chosen.
|
||||
- **B10.** `lib/autoplan-prompts.ts:23-27`: UI scope is two or more hits of `component|screen|form|button|modal|layout|dashboard|sidebar|nav|dialog` anywhere in the plan text, Review record included; `detectScope` (`:39-46`) records `source: 'flags'` and nothing persists flags across runs. `openPhase` passes prior consensus only to Eng (`lib/autoplan-run.ts:121`), so DX never sees the CEO consensus (this run: both rev-2 DX prompts contained zero consensus blocks). `bin/gstack-autoplan` never passes through `gstack-skill-start` or the doctor; this run's `run.json` carries no `gstack_revision`. Gate answers record `chosen`, `answered_at` and `reply` but not who answered (`lib/gate-list.ts:155`).
|
||||
- **D15/D16.** `lib/ship-policy.ts:34,58` has `release_tool` (one command run inside `gstack-restamp`'s staging worktree after the stamp) but no way for a repo whose own script is what CI checks to own the whole stamp; `bin/gstack-pregate --help` has no stamp or sealed check.
|
||||
|
||||
Items not reproducible on this machine are taken from the GBRA threads as stated and marked `[field]`: A4 (52 s install on automation machines), B7 (gate churn across three revisions), B11 (rules claimed "written first", unreceipted numbers), C13 (lanes passing `bun run verify` but missing manifest rows, pinned contracts, stale goldens), C14 ($723 unpriced gate, hidden synopsis builds), D15 (gbrain's `release-restamp.ts`, `since:` rows, migration v240 collision), D16 (transcripts and a quoted sealed answer pushed to a public repo).
|
||||
|
||||
**What already exists (reused, not rebuilt).** `bin/gstack-session-update:270-300` upgrades the checkout in-session: receipted fetch, `gstack_bun_incoming_hold` (`bin/gstack-bun-version.sh:82`), hook hold, `merge --ff-only --autostash` (a bare `--ff-only` wedged permanently on patched tracked files, #2566), setup, migrations. `gstack-doctor --check --revision <ref>` and `REVISION_UNMET` exist (`bin/gstack-doctor:55`, `bin/gstack-doctor-check.sh:338`). The installer already exits non-zero on every skipped-Bun path. `gstack-autoplan submit --result` binds a host-subagent markdown result by receipt and fence (this plan's own run closed all six attempts through it). `lib/tree-receipt.ts` (`buildReceipt`, `classifyFile:69`) already classifies stamp-only hunks, `since:` rows, changelog headings and gate reuse, printed by `bin/gstack-tree-receipt`. `scripts/test-paid-shards.ts --list` prints a free paid-gate plan (human-readable; no `--json`). `docs/unattended.md:135-243` is the Capy driver loop. `lib/claude-code-migration.ts:159` spawns through `process.execPath`. `bin/gstack-redact-prepush:604` already parses pre-push stdin refs for the managed pre-push wrapper (`bin/gstack-redact`), single-guard, spawning `bun` by name. `test/lane-coordination.test.ts:117` is the byte-pin pattern. `setup:309-327` resolves Bun with `command -v bun` and accepts 1.3.14 as merely untested.
|
||||
|
||||
What worked and must not regress (each gets a pinned test if it lacks one): `install.sh --check` FAIL rows with exact `fix:` lines; `gstack-autoplan next` phase order plus the snapshot hash on every attempt; accepted-obligation blocks that replace earlier ones; exact-token gate answers with `REOPENED:` reruns; `PLAN_REALITY … unreceipted=` flags; the pluggable outside voice on another model family.
|
||||
|
||||
Repo constraints: SKILL.md is generated from `.tmpl`; new logic in `lib/*.ts` behind thin `bin/` entrypoints, every module registered in `test/fixtures/module-size-ratchet.json` (800 lines, 150 per function); state paths through `lib/state-root.ts`; result codes appended to `lib/result-codes.ts` with `docs/troubleshooting.md` anchors; every new outbound call wrapped in `_receipted_git` / `lib/egress-receipt.ts` (`test/egress-receipt-wiring.test.ts` fails on an unreceipted sink); ship is on `stamp-at-merge` with `bump: patch`, so every PR opens with a tentative base+PATCH stamp and the restamp runs when it is next. Zero-headroom skills: autoplan union 1.149, review 1.296, ship 1.645, qa ~9 B; prose added there is replaced byte for byte or printed by a bin.
|
||||
|
||||
### Release promises (the finish line)
|
||||
|
||||
- **P1. A stale machine cannot run a workflow silently.** At session start, when the receipted fetch shows `origin/main` moved, the checkout fast-forwards itself (with autostash, never a reset) and refreshes setup, reruns `--check`, and refuses to run a workflow while the trailer is `fail`; below the contract floor it refuses even offline, above it an offline machine runs with a visible stale note. The Capy autoplan driver itself runs the doctor on its first `next` and refuses a `fail` trailer. A 1.91.12 checkout reaches current from one `install.sh --check`, on every machine a thread uses, and a subagent's first report carries its `gstack:` trailer.
|
||||
- **P2. The installer and the check agree.** `gstack-capy-install` never prints `gstack: ok` on a machine where the next fresh-shell `--check` fails; `GSTACK_LAUNCH` carries the verified Bun on `PATH`; lib code never spawns `bun` by name.
|
||||
- **P3. A three-revision unattended autoplan run validates and closes.** Rev-scoped decision ids, `submit --result --extract` and `--outcome` for phases run as Capy subagents, `revise --plan v2` in the same run before any rerun can dispatch, reruns that dedupe only explicitly linked repeats, user-challenge defaults that follow mechanically agreeing voices without letting `all` flip the owner's direction, answers with provenance, scope from changed files, every phase fed its predecessors' consensus with transitive invalidation, and a compact Capy driver doc.
|
||||
- **P4. Plan obligations reach the mechanical gates before a lane reports done,** and every paid gate a plan names is priced from a declared adapter's structured plan before approval; a gate without an adapter is an owner-answerable `GATE_UNPRICED` item, never silently accepted. Release one ships the gstack adapter only; other runners' manifests report `not configured`.
|
||||
- **P5. Stamp-at-merge delegates to the repo's own script where that is what CI checks,** with the stamp-only proof coming from the tree receipt, migration-collision detection, and a sealed-output guard that runs before push and inside the pre-push hook.
|
||||
|
||||
### Delivery shape
|
||||
|
||||
Three PRs in a dependency chain, not four independent lanes (gate rev 1, d1/d4): every lane appends to `lib/result-codes.ts`, `docs/troubleshooting.md`, `test/fixtures/module-size-ratchet.json` and `scripts/free-test-durations.json`; A and B share `lib/coordinator-contract.ts` and `lib/headless-artifacts.ts`; C13, D15 and D16 share `bin/gstack-pregate` and `lib/pregate/`. Each PR opens with a tentative base+PATCH stamp, is registered with the merge queue before it opens, merges `origin/main` in (never rebases) when the predecessor lands, and ships with a failing test first on the simulated Capy host.
|
||||
|
||||
| PR | Items | Base | Owns (no other PR edits these) |
|
||||
|---|---|---|---|
|
||||
| A | A1–A4, host simulator | main | `bin/gstack-upgrade`, `bin/gstack-upgrade-lib.sh`, `bin/gstack-session-update` pull stage, `bin/gstack-capy-install`, `bin/gstack-doctor-check.sh`, `scripts/capy/**`, `test/helpers/capy-host-sim.ts` |
|
||||
| B | B5–B12, the trailer rule in `lib/coordinator-contract.ts` and `lib/headless-artifacts.ts` | A's branch | `lib/autoplan-*.ts`, `lib/gate-list.ts`, `lib/plan-reality.ts`, `bin/gstack-autoplan*`, `autoplan/sections/**`, `docs/unattended.md` |
|
||||
| C | C13, C14, D15, D16 (one pregate PR) | A's branch (merged after B) | `bin/gstack-pregate`, `lib/pregate/**`, `lib/ship-policy.ts`, `lib/restamp.ts`, `lib/tree-receipt.ts`, `bin/gstack-eval-plan`, `scripts/test-paid-shards.ts` (`--list --json`), `bin/gstack-redact*`, `lib/prepush-refs.ts`, `docs/pregate.md`; **after B lands**: `lib/plan-reality.ts` (GATE_PRICE clause), `lib/headless-artifacts.ts:41,145` (tasks v3), `lib/autoplan-export.ts:31` (checks), `lib/coordinator-contract.ts:22` (lane-done and sealed sentences) |
|
||||
|
||||
Stacked stamps: each successor's tentative stamp is computed from `origin/main`, not the predecessor's branch; when a successor merges `origin/main` after its predecessor squash-lands, it takes main's `VERSION` and changelog heading and runs `gstack-restamp --next --base main` to re-mint rather than hand-resolving; `classifyFile` treats a merge commit whose second parent is already-gated `origin/main` as `gate-reuse: eligible`, so the chain costs B one regate and C two at most (test: a restamp fixture with a squash-merged predecessor resolves without a manual conflict and prints `STAMP_ONLY: yes`). Shared append-only files get pre-assigned blocks so merges are mechanical: result codes and troubleshooting anchors are listed per PR below; each PR adds its own module-size-ratchet rows and durations rows under its own file names; CHANGELOG sections stay separate.
|
||||
|
||||
**Result codes, per PR** (each with a `docs/troubleshooting.md` anchor before any line prints; every failure line ends `(<CODE>; <anchor>)` like the doctor rows). A: `UPGRADE_NOT_FAST_FORWARD`, `UPGRADE_BUSY`, `UPGRADE_FETCH_FAILED`, `UPGRADE_PINNED_ROOT`, `UPGRADE_TARGET_UNAVAILABLE`, `GSTACK_STALE_REFUSED`, `RUNTIME_OFF_PATH`. B: `PLAN_SHAPE_PARTIAL`, `PLAN_SHAPE_AMBIGUOUS`, `RESULT_FINDINGS_MISSING`, `REVISE_OBLIGATIONS_DROPPED`, `REVISE_ATTEMPTS_OPEN`, `REVISE_SCOPE_CHANGED`, `PLAN_UNCHANGED_SINCE_GATE`, `PLAN_REALITY_INCOMPLETE`. C: `MANIFEST_ROW_MISSING`, `GATE_OVER_CAP`, `GATE_UNPRICED`, `STAMP_NOT_ONLY`, `STAMP_EVIDENCE_MISSING`, `MIGRATION_COLLISION`, `MIGRATION_CENSUS_INCOMPLETE`, `SEALED_CONTENT_REFUSED`, `SEALED_RESULT_MISSING`.
|
||||
|
||||
**The simulated Capy host** (`test/helpers/capy-host-sim.ts`, lands in PR A, used by every PR). A scratch root with `usr/local/bin/bun` as a shim that reports `1.3.14` (and otherwise execs the real Bun), no `codex` on PATH, isolated `HOME`, `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_ALLOW_PROTOCOL=file`, `http_proxy=http://127.0.0.1:9`. Subprocess environments are built from an allowlist (never inherited), every state/install/runtime/temp root is set explicitly, credentials are stripped, and a test plants sentinel files outside the root and asserts they are unchanged after install, upgrade and cleanup, including through an escaping symlink. The stale checkout is **synthesised**, because this checkout, CI's (`.github/workflows/quality-gate.yml:40`) and the installer's own clone (`bin/gstack-capy-install:65`) are all shallow and tagless: a local bare origin with two commits (stale = minimal tree with `VERSION 1.91.12` and no `bin/gstack-doctor-check.sh`; current = the install fixture's source files), cloned `--depth 1 --single-branch` like the installer; divergence is one extra local commit. One Docker smoke (`.github/docker/Dockerfile.capy-sim`, Ubuntu 24.04 + Bun 1.3.14 at `/usr/local/bin`) runs the same scenarios in a `workflow_dispatch` lane; it is a `requires-remote` obligation for PR A in the ship receipt, its evidence label declared outside `stamp_sensitive_gates` (`lib/ship-policy.ts:48`) so the tree receipt's `gate-reuse: eligible` carries it across a stamp-only restamp; only a successful job receipt bound to the gated SHA, container digest and selection clears it (a green unit-only rerun cannot erase it). Sim fixtures that run the real `./setup` cost about 30 s each `[estimated]`, so the suite shares one upgraded checkout per file where the scenario allows.
|
||||
|
||||
### PR A: install and upgrade honesty
|
||||
|
||||
**A1. `bin/gstack-upgrade` exists and is the one upgrade path.** Bash, not Bun (it must run on a checkout whose Bun it is about to verify). `bin/gstack-session-update`'s pull stage is extracted into a sourced `bin/gstack-upgrade-lib.sh` (receipted fetch labelled `gstack-upgrade-fetch`, `gstack_bun_incoming_hold`, hook hold, `merge --ff-only --autostash`, setup, migrations, pending-stage file) and both callers use it. Locking protects the **installation**, not a state root: workflow leases live under the installation (`<install>/.gstack-leases/<pid>`, written by `gstack-skill-start` and `gstack-autoplan next`, so eight subagents with distinct state roots are all visible); `gstack-upgrade` takes the installation flock (the installer's `.gstack-capy-install.lock`), refuses `UPGRADE_BUSY` (exit 3) while a lease whose pid is alive and outside the caller's process ancestry exists, and after acquiring the lock re-reads HEAD against `origin/main` so a second concurrent caller prints `UPGRADE: none (current)`. It refuses only local commits or a non-ancestor upstream with `UPGRADE_NOT_FAST_FORWARD` and the exact `git` commands; on an autostash conflict it keeps the stash and the original bytes and refuses rather than dropping anything (test: genuine user edits conflict → bytes and stash survive; a generated-overlay edit fast-forwards). `--to <tag|sha>` distinguishes a tag (`git fetch origin tag <t>`) from a commit (`git fetch origin <sha>`), verifies the commit (`UPGRADE_TARGET_UNAVAILABLE` otherwise), applies the same fast-forward rule, and records the pin in the state root; a `CLAUDE_PLUGIN_DATA`-managed root prints `claude plugin update gstack` and exits 3 (`UPGRADE_PINNED_ROOT`) without touching the tree. Prints `UPGRADE: <old> -> <new> ff=yes migrations=<n> setup=ok` then the doctor trailer, all on stdout. The doctor's `REVISION_UNMET` fix text names this bin; the `gstack-upgrade` Claude skill's Step 4/4.75 delegates to it and its `git reset --hard` divergence fallback is removed (listed under `## Behavior changes` in PR A; the fallback was reachable only when local commits existed, which the rule forbids). Decision rule, written first and stated here: the upgrade path never discards local commits, dirty files or a stash; when it cannot fast-forward it prints the exact commands and stops. Tests: `upgrade-refuses-divergence-keeps-bytes` (including diverged-with-clean-tree, two concurrent `--check` calls on one stale sim producing exactly one `UPGRADE:` line and two `gstack: ok` trailers, no upgrade while a live lease exists, and a dead-pid lease not blocking).
|
||||
|
||||
**A2. The authority on "stale" lives outside the checkout; the first hop has an owner; the drive carries no logic.** The drive skill's `install.sh` becomes a ~20-line **bootstrap** with no version number, bin name or flag in it (pinned by a test over the in-repo copy `scripts/capy/bootstrap.sh`): clone if absent; `git fetch origin main` wrapped in `_receipted_git` when the checkout's `bin/gstack-egress-lib.sh` exists, else a plain fetch printing `receipt: unavailable (pre-upgrade bootstrap)` on stdout (the one exemption named in `test/egress-receipt-wiring.test.ts`); then `git show origin/main:scripts/capy/install.sh > $tmp && bash $tmp --check "$@"`, falling back to the checkout's own `scripts/capy/install.sh` only when the fetch fails. All logic is in-repo in `scripts/capy/install.sh`, tested in the sim; the drive `SKILL.md` is generated from `scripts/capy/SKILL.md.tmpl` by `gen:skill-docs`, listed as a release output, with a test that every `bin/<name>` and flag the skill names exists. The in-repo wrapper's `--check` sources the fetched tree's `bin/gstack-bun-version.sh` and, when `$BUN_INSTALL/bin/bun` reports `gstack_bun_status ok`, prepends its directory to PATH **before** the merge, `./setup --refresh-registered` and the exec of `bin/gstack-doctor --check` (otherwise the upgrade ends `fail runtime` under the 1.3.14 Bun first on PATH; `setup:309` resolves Bun with `command -v bun`). It then applies one ordered policy, stated here and in the wrapper: (1) below `CONTRACT_FLOOR_REVISION` refuse, online or offline (`GSTACK_STALE_REFUSED`, exit 3, trailer `fail revision`); (2) a recorded `--to` pin skips with `SELF_UPGRADE: skipped (pinned <ref>)` and the stale note; (3) the off switch (`GSTACK_SELF_UPGRADE=off` or `auto_upgrade=false` via the checkout's `gstack-config`, `bin/gstack-session-update:153`) skips with `SELF_UPGRADE: disabled (<source>)`; (4) otherwise fetch under a deadline (`--fetch-timeout`, default 20 s; a timeout prints `UPGRADE_FETCH_FAILED (timeout <s>)`, distinct from a connection refusal) and, when `origin/main` moved, fast-forward and refresh setup unconditionally through `bin/gstack-upgrade` (G8 option (c): always current when online, so there is no version zoo and no knob to forget; the cost is setup time at session start on release days, bounded because A4 defers the full install to first workflow use); when the fetch fails above the floor, run with `gstack: ok <rev> (stale; upgrade unavailable: UPGRADE_FETCH_FAILED)`. `CONTRACT_FLOOR_REVISION` lives in `lib/doctor-components.ts`, is read by bash from the generated `bin/gstack-doctor-components.sh`, and is bumped deliberately when the CLI or artifact contract changes. Every trailer and `UPGRADE:`/`SELF_UPGRADE:` line goes to stdout; a test pins the wrapper's result-code anchors against `lib/result-codes.ts` as `bin/gstack-capy-install:50` does. In `gstack-skill-start` the upgrade is a mechanical pre-step printing `SELF_UPGRADE: none|done <old>-><new>|skipped …|disabled …|refused <code>`, and the unattended sentence at `:616` becomes "the tool already applied any required upgrade; never run an upgrade yourself". `gstack-skill-start` prints `GSTACK_TRAILER: gstack: ok <rev>` as the first line of every workflow so a subagent's first report carries it; `lib/coordinator-contract.ts` gains the sentence and the handoff-format order gains the trailer, and `gstack-artifact validate` requires the trailer line in an unattended `review-record.md` only when `run.json.gstack_revision` is at or above this release (reference fixtures keep validating). Session-check latency: the in-checkout check measured 0.38 s `[measured: this machine 2026-10-12]`; the fetch adds network time bounded by the deadline. Fixtures: `install-stale-1-91-12-self-upgrades` (synthesised stale origin → current from one bootstrap `--check`, runtime row PASS and trailer `gstack: ok` with the 1.3.14 shim first on the parent PATH and a supported Bun planted in `$BUN_INSTALL`); the 2×2×2×2 matrix floor × pinned × switch × offline, each cell asserting the `SELF_UPGRADE:` line and the trailer, the hanging-fetch cell using a listener that accepts and never answers; a diverged checkout refuses with the commands and an untouched tree.
|
||||
|
||||
**A3. One Bun, agreed on by installer, check and launch.** The runtime row stays honest to `command -v bun` (gate rev 1, d2/d6). When `$BUN_INSTALL/bin/bun` is already the supported version (read from `bin/gstack-bun-version.sh`, never hardcoded) and only PATH is wrong, the row reads `verified bun <v> at ~/.bun/bin/bun is not on PATH` with fix `source <state root>/launch.env before each gstack command (or prepend the GSTACK_LAUNCH line)` (`RUNTIME_OFF_PATH`; `RUNTIME_BELOW_MINIMUM.fix` gains the PATH clause). `gstack-doctor --check` writes `<state root>/launch.env` (plain `KEY=value` lines, no shell syntax, `PATH_PREPEND=<abs dir>`) and prints `GSTACK_LAUNCH_FILE: <path>` plus the display line `GSTACK_LAUNCH: PATH="<dir>:$PATH" GSTACK_SESSION_KIND=…` with every literal shell-quoted; `--json` carries `path_prepend` and the assignments as a map (paths with spaces, quotes and `$` round-trip; nothing is `eval`ed and the file is parsed, not sourced). The installer's final `--check` runs under `env -i PATH=<prepend>:<system PATH> HOME=<home>` plus the launch assignments, so `ok` means the next fresh shell says `ok`; a host with only 1.3.14 and no network ends `gstack: fail runtime` from both (the installer's existing non-zero skip exits are pinned by this negative test, not re-implemented). `lib/autoplan-prompts.ts:33` and `lib/restamp.ts:177,314` spawn `process.execPath`; `test/bun-spawn-ratchet.test.ts` greps `lib/**/*.ts`, `bin/*.ts` and generated hook templates for a bare `bun` spawn with a reasoned exemption list. Fixture: `install-ok-then-check-ok` records `Bun.version` from the snapshot tool inside the sim and asserts the supported version with the 1.3.14 shim first on the parent's PATH.
|
||||
|
||||
**A4. No 52 s install on machines that never run a workflow.** `[field]` The bootstrap's session-start step is `--check` only; the install runs at first workflow use from the wrapper (`install.sh --for <skill>`), printing `INSTALL: lazy (first workflow use) <seconds>s`. `docs/capy-install.md` gains "Baking gstack into a Capy snapshot" (what the Setup runs, what `--check` must print after boot, why `~/.bun/bin` must be on the Setup's PATH) and states the relation between the self-upgrade and `auto_upgrade`. Tier 2: `gstack-capy-install --prefetch`.
|
||||
|
||||
### PR B: unattended `/autoplan` on Capy
|
||||
|
||||
**B5. Rev-scoped decision ids; attempt-qualified references.** `lib/gate-list.ts:148` emits `${run}-g<gate_rev>-<item>` through one revision-aware reference constructor shared by decisions, `tasks.jsonl` `blocked_by` links and `applyReply` (which today derives the item with `row.id.slice(run.length + 1)`); both id shapes parse; `label` stays the primary key. Finding ids keep their shape (`lib/headless-artifacts.ts:127`), every canonical finding row and `tasks.findings` ref carries `attempt` (`${run}-a<n>`), and every cross-revision reference (`superseded_by`, the `DEDUPED:` line, the reopen journal) is attempt-qualified `<attempt>:<id>`; `gstack-artifact validate` checks each referenced attempt exists in the journal. Fixture: `autoplan-3-rev-validates` with v1 and v2 rows in one `decisions.jsonl`, a rev-2 finding reusing a rev-1 id, actionable findings, tasks, a partial answer and a stale-answer replay; zero `ARTIFACT_DUPLICATE_ID`.
|
||||
|
||||
**B6. `submit --result` closes a run whose phases ran as Capy subagents; failures have a terminal outcome.** No `--record` and no `--attempt new` (gate rev 1 d7/d12; gate rev 2 d5: `next` already mints the attempt and reserves against the cap at `lib/autoplan-run.ts:168-172`, and this run closed twelve attempts through `next`/`submit`). `submit --result` gains `--extract` with the parsed grammar `### F<n> [<Severity>] <title> — <file>:<line>`; a heading without an anchor refuses `RESULT_FINDINGS_MISSING` naming it and printing the example, or with `--anchor-optional` the row carries `anchor: none` and `DEDUPED:` prints `by=title`. `submit --outcome failed|unavailable --attempt <id>` is the terminal-outcome command for a host subagent that never produced a result (`markOutside` is reachable only from `run` today, `bin/gstack-autoplan:141`); terminal transitions are centralised across `submit`, `next` and `resume`: ordinary `next` never redispatches failed or unavailable work, usage settles once, a native failure leaves the phase `incomplete`, and outside-only degradation stays explicit. Every `--json` path returns one versioned JSON object, refusals included. The recovery triad (`resume` → `submit --attempt <id>`; `next --redispatch <attempt>`; `submit --outcome`) is one table in the driver doc. Fixture: each failure before and after its sibling result, asserting stable attempts, reservations and coverage under repeated `next`/`resume`.
|
||||
|
||||
**B7. Reruns dedupe only explicit repeats; nothing is suppressed silently.** `[field]` A reopened phase's prompt on `gate_rev > 1` carries the previous gate's answered items with their attempt-qualified ids. `lib/autoplan-gate.ts` dedupes a rerun challenge only when the finding links a prior item explicitly (`supersedes: <attempt>:<id>`) and its issue and resolution are compatible with that item's and the consumed inputs are unchanged; location and title similarity only **propose** a match (`DEDUPE_CANDIDATE:` line), never apply one. Packaging and other reversible choices render as `kind: auto` items with `basis: packaging` (visible, defaultable, never reopening a phase) rather than dropped (gate rev 1, d8). Fixture: a rerun whose only new challenge is packaging produces one auto item and no reopen; a paraphrased repeat with a valid prior-item link is deduped; two distinct defects at one answered line are both kept.
|
||||
|
||||
**B8. `revise --plan <v2>` in the same run, before any rerun can dispatch; transitive invalidation.** After a content-changing answer, `answer` prints `REVISE: gstack-autoplan revise --out <dir> --plan <v2> (before next)` and `EDIT: <plan-active path>` ahead of `NEXT:`, exposes the same next action in `--json`, and `next` refuses `PLAN_UNCHANGED_SINCE_GATE` (exit 3) when the Implementation-plan hash equals the one snapshotted at the answered gate revision unless `--same-plan` is passed and journaled as a deviation (`bin/gstack-autoplan:184`). `revise` replaces only the Implementation plan bytes of `plan-active.md`, refuses a v2 carrying `## Review record` or an empty body (`PLAN_SHAPE_AMBIGUOUS`), refuses while any attempt is `dispatched`/`execution_unknown` (`REVISE_ATTEMPTS_OPEN`), runs the snapshot tool's obligation check and refuses `REVISE_OBLIGATIONS_DROPPED` with the block names unless `--drop-accepted <ids>`, re-runs `detectScope` over the new bytes and prints `SCOPE: ui <old>-><new> (source=…)`, refusing `REVISE_SCOPE_CHANGED` when the phase order would change unless `--phase-order <list>` is passed (the added phase opens before Eng with every closed predecessor's consensus; `required_phases` is rewritten and journaled), then performs a journaled generation transition: `plan_revisions[]` in `run.json` (generation, both hashes, dropped ids, time), hash-named immutable archives of bound files, `restore.r<rev>.md`, an atomic manifest write, and idempotent `resume` across a crash after any step (one `failAfter` test, as `lib/restamp.ts` does). Every phase records the identities of its consumed inputs (plan snapshot hash plus each predecessor consensus hash); `revise`, `answer` and a changed consensus invalidate the transitive consumers of what changed (a CEO change reopens DX, design and Eng, not only Eng), recompute the phase order, and invalidate the previous gate approval; the reopen line states the dependent reruns and their estimate (gate rev 2, d6). The `PLAN_SHAPE` error at `bin/gstack-autoplan-snapshot.ts:90` gets result code `PLAN_SHAPE_PARTIAL` naming the missing or duplicated heading and the one-line fix; no `scaffold` subcommand (init already scaffolds). Fixtures: the 3-rev run revises with a v2 containing a Review record (refused), with a clean v2 (asserting `review-record.md` still carries rev-1 consensus blocks), with an early `next` before `revise` (refused, zero attempts dispatched); a four-phase CEO-change regression, an Eng-only control, and a CLI-to-TSX revision that inserts design.
|
||||
|
||||
**B9. User-challenge defaults follow mechanically agreeing voices; `all` never flips the owner's direction; answers compose and carry provenance.** In `buildGate` (`lib/autoplan-gate.ts:81-83`) the agreement rule is mechanical: a `confirmed` reconciliation row whose two findings are both `user_challenge` **and** whose resolutions share token overlap at or above the reconciler's half-words heuristic (`lib/autoplan-reconcile.ts:116`) renders `recommended: b, basis: both voices agree` with the reconciled change in the option text; a `confirmed` row with differing resolutions renders `recommended: a, basis: both challenge; resolutions differ` with both resolutions printed; one voice → `recommended: a, basis: one voice; the plan as reviewed stands`; an `agreement: true|false` field on the ReconciliationOverride schema (`:130`) overrides the heuristic. Items recommended `b` are excluded from `all` (`answerable_by_all: false` in `gate.json`) and stay `gate_pending` until answered by token; `GATE_PENDING` prints the exact tokens per pending item and `all does not answer items recommended b` (`:141`); reopen keys on the chosen option changing reviewed content, not on `how`. Incremental answers compose: persisted choices for the current revision merge with new explicit tokens, only unanswered eligible items default, and pending approval plus the reopen set derive from the complete effective decision set, reopening once (`uc1b` then `p1b`; `all` then individual tokens). `answer --by <human|agent:<id>>` is required in unattended runs; `answered_by` lands on every DecisionRow and in the review-record gate line, and `gstack-owner-brief` flags a revision whose user-challenge `b` answers were all agent-given as `agent-accepted; owner has not decided`. Fixture: `gate-defaults-follow-agreement` (the 15-item field gate; opposite fixes at one location → `a` with the basis line; a paraphrased same fix → `b`; `all` leaves agreed items pending; `d1b` reopens; composed answers reopen once).
|
||||
|
||||
**B10. Scope from changed files and rendered surfaces, flags sticky, every phase informed.** `Scope.ui` becomes `yes|no|unknown` (`lib/autoplan-prompts.ts:47` `phaseOrder` and the run-log rendering follow). `detectScope` (`:39`) runs over the Implementation plan bytes only: `ui=yes` when the plan names files under rendered-surface globs (default `**/*.tsx`, `**/*.vue`, `**/*.svelte`, `**/*.css`, `**/views/**`; `**/templates/**` is not a default and gstack ships its own `.gstack/autoplan.json`, with `init`/schema, excluding `qa/templates` and friends) or a fenced `surface:` block lists a screen; `ui=unknown` when no paths match but two or more prose UI terms appear (design runs light, never skipped silently); one prose hit is a `hint:` line and nothing more. Flags persist in `run.json.scope.flags` with provenance and apply to reopened phases (`source=flags (sticky from rev 1)`); `revise` may replace or reset them. `openPhase` passes every closed predecessor's consensus to DX and design, not only to Eng (`lib/autoplan-run.ts:121`), and refuses `CONSENSUS_MISSING` for a required predecessor. Fixtures: a plan with "doctor", "status", "description" and a Review record containing "dashboard" twice → `ui=no`; `qa/templates/x.md` → `ui=no`; `app/components/Foo.tsx` → `ui=yes`; prose-only "modal" and "sidebar" → `ui=unknown`; the DX prompt file contains the CEO consensus block.
|
||||
|
||||
**B11. PLAN_REALITY requires the rule text and receipts for decision-driving figures, before dispatch.** `lib/plan-reality.ts`: the `binding decisions` row fails when a plan claims a rule was written first but no rule text appears in the Implementation plan. The preserved `unreceipted=<row ids>` metric is unchanged; a new `figures_unreceipted=<n>` (with the first three excerpts and lines) scopes to unit-bearing figures (`$`, s/ms/min, %, KB/MB, calls, lines) inside the Implementation plan, accepts the existing tags `[measured: <src>]` / `[estimated]` / `[re-measure before gate]` (`lib/plan-reality.ts:67`) plus `[target]` and `[fixture]` for caps and fixture parameters, requires a receipt to be a `file:line` match on the same line (a bare sha is not a receipt), and ignores versions, exit codes, item ids and `g<n>`/`a<n>`/`uc<n>` tokens. The plan-bytes scan runs at `init` and the first `next` as a pre-dispatch refusal (`PLAN_REALITY_INCOMPLETE`, exit 3) with `--allow-unreceipted` recorded as a deviation; review-text rows still run at phase close as a persisted, input-bound validation boundary, without redispatching completed reviewers. Fixture: the scanner over this plan's Implementation plan pins an enumerated unreceipted set (zero once caps are tagged); the $723 arm list fails, `$723` with a sha on the line still fails; `32 s [measured: this machine 2026-10-12]` passes; `exit 3` and `v1.91.94.0` are ignored.
|
||||
|
||||
**B12. A compact Capy driver doc, one template, outside the parity union.** `[field]` `autoplan/sections/capy-driver.md.tmpl` renders to **`docs/capy-driver.md`** as a whole-file output registered in `.gstack/generated.json` (the parity harness unions every `sections/*.md`, `test/helpers/parity-harness.ts:78-82`, and the autoplan union sits at 117,121/101,979 = 1.1485 against the 1.1490 cap, ~50 bytes of headroom `[measured: Eng rev 2]`); `docs/unattended.md` links it (no fenced-region mechanism, which `gen-skill-docs` lacks). Content: phase order, artifact contract, the `next`/`submit`/`answer`/`revise` loop, the gate reply grammar, the recovery triad table, the RESULT FORMAT, no hook or guard prose; ≤ 200 lines `[target]`. The drive skill reads that path. A test pins the line cap, that no hook token (`PreToolUse`, `Stop hook`, `guard:`) appears, and that `bun test test/parity-suite.test.ts` shows the autoplan ratio unchanged. `review-record.md` links each bound record by name and sha256 instead of inlining it (byte cap pinned in the 3-rev fixture).
|
||||
|
||||
**B13. The Capy driver is honest about the checkout it runs on.** The first `gstack-autoplan next` of a run invokes `gstack-doctor --check --for autoplan` (through `process.execPath` and the bin path, receipted), writes `gstack_revision` and the `gstack:` trailer into `run.json` and the review-record run log, prints `GSTACK_TRAILER:` as the first line of its output, writes the installation lease (A1), and refuses `GSTACK_STALE_REFUSED` (exit 3) when the trailer is `fail`; the `gstack-skill-start` path stays for Claude Code. Fixture: a stale checkout driven by `gstack-autoplan next` in the sim refuses rather than runs.
|
||||
|
||||
### PR C: pregate (plan to gate, queue-aware shipping)
|
||||
|
||||
One flag grammar for every check: `gstack-pregate run [--only <checks>] --range <a>..<b>` (`--staged` as sugar; `--range a..b` implies pin files from `a` unless `--base` is given; the table header prints `POLICY_FROM: <ref>` and `TIER: unit (test tier)` so check tiers and lane platform tiers are not confused); `--only` stays literal; `install-hook` becomes `hook install|status|remove`. The pre-push ref parsing (zero remote sha → merge-base with the remote default; deletes skipped) is extracted from `bin/gstack-redact-prepush:604` into `lib/prepush-refs.ts` used by both guards; the managed pre-push wrapper becomes a guard list so `hook install|status|remove` adds or removes one guard while `pre-push.local` and the other guard survive, and the wrapper embeds the absolute Bun path (`process.execPath` at install time, `command -v bun` fallback) instead of `bun`. `bin/gstack-redact*` and `lib/prepush-refs.ts` are in C's owner column.
|
||||
|
||||
**C13. Lane-done runs the pre-gate on changed files.** `[field]` `gstack-pregate run --tier unit --range origin/<base>..HEAD --allow-repo-commands` is the lane-done preflight: stage 1, `lanes` restricted to the unit tier, `regen` (no duplicate `goldens` check), and the new `manifests` check; the receipt lists every promised preflight row, and a stale-generated-output fixture whose lane and manifest checks pass still fails on `regen`. `manifests` is the **gstack adapter** in release one (gate rev 2, d8): it imports `unseededFreeFiles` (`scripts/test-free-shards.ts:396`) and `TREE_MUTATING` (`:243`) and fails `MANIFEST_ROW_MISSING` with the exact row to add; an ordinary new test needs only its durations row, a renamed exclusive test its serial row, a platform-excluded test neither; a repository without a declared runner adapter reports `manifests: not configured` (never `pass`), and P4 is narrowed accordingly, with the adapter contract (census, durations, exclusivity, platform data) documented in `docs/pregate.md`. Receipts carry purpose, selection, required inventory and unexecuted obligations, so a green unit-only rerun cannot overwrite a publication receipt or clear a remote obligation. After B lands: `tasks.jsonl` gains `checks: string[]` (schema v3, `lib/headless-artifacts.ts:41,145`; `acceptance` stays prose), `lib/autoplan-export.ts:31` renders them, and `lib/coordinator-contract.ts:22` gains the exact sentence "before reporting done, run `gstack-pregate run --tier unit --range origin/<base>..HEAD --allow-repo-commands` and paste its table after `GSTACK_RESULT`". Fixtures: `pregate-lane-done-manifest-row` (adds an unseeded test file and renames a `TREE_MUTATING` key; both FAIL rows name the row to add); a fixture with a different exclusive-test map and one without duration manifests both report `not configured`.
|
||||
|
||||
**C14. Paid gates are priced from a declared adapter's structured plan before approval.** `[field]` `gstack-eval-plan dry-run --gate <cmd>` (under `--allow-repo-commands`) accepts only declared adapters emitting a complete structured plan; PR C ships the first one: `scripts/test-paid-shards.ts --list --json` (steps, calls, model, max tokens, trial multiplicity, preflight work, excluded charges, hidden-step rule), and the synopsis builders gain the same `--list --json`. It prices Σ(calls × model) through `lib/pricing.ts` and the ledger's reservation rule, persists a structured quote bound to adapter, resolved command, consumed-input hashes, models, limits and pricing revision, and prints `GATE_PRICE: <cmd> calls=<n> est=$<x> cap=$<y> evidence=list|smoke preconditions=<list|none>`. `--smoke 2` spends two real questions only with explicit consent and reserves at dispatch. A hidden step over the cap is `GATE_OVER_CAP` (exit 1) naming it; a quote whose inputs changed is invalid at spend time; an unknown model is refused. **Consequence stated (gate rev 2, d2):** a paid gate in a repository without a declared adapter is `GATE_UNPRICED`; in release one that renders as an owner-answerable gate item (`accept unpriced <cmd> under cap $x` / `block`) rather than an unconditional blocker, so the wave does not freeze gbrain shipping on day one; the gbrain synopsis-build adapter (the $723 field case) is a named deliverable with an owner outside this plan, and the adapter contract is published in `docs/pregate.md` so a repo can comply in one PR. After B lands, the plan `numbers` row requires a `GATE_PRICE:` line for every paid gate the plan names, printing `GATE_PRICE: unavailable (adapter not installed)` without blocking while the repo has no adapter. No `GSTACK_EVAL_DRY_RUN`. Fixtures: `gate-price-hidden-synopsis` exercises the real `--list --json` planner with a stubbed paid boundary (100-call plan under cap 10 → `GATE_OVER_CAP` naming the hidden step); changed-input refusal; an undeclared command → `GATE_UNPRICED` item.
|
||||
|
||||
**D15. Ship policy lets the repo's script own the stamp; the tree receipt proves stamp-only; failures never pass.** `[field]` `release_tool` stays the one key and gains `release_tool_owns: 'post-stamp' | 'whole-stamp'` (default `post-stamp`); `GSTACK_NEW_VERSION`, `GSTACK_OLD_VERSION` and `GSTACK_BASE_SHA` are exported in both modes (`lib/restamp.ts:310`). Whole-stamp skips the version/changelog/`stamp_paths` stages and keeps the journal, the allowed-output set, pre/postimage and `MERGE_CONDITION`; every post-command output is diffed against an immutable pre-command tree, index or HEAD mutation by the tool is refused, and the authoritative version, declared mirrors and changelog heading are validated independently afterwards (exit-zero no-op at the old version fails; already-at-target is a legitimate no-op). `--dry-run` prints `STAGES: version=skipped(release_tool owns) …`. A pregate `stamp` check (`run --only stamp --range <gated-head>..HEAD --evidence <dir>|--gate-ahead <record>`) calls `buildReceipt` with an explicit evidence source and prints `STAMP_ONLY: yes|no` and `REGATE: needed|not needed <lanes>` from the receipt (`STAMP_NOT_ONLY` on `no`; `STAMP_EVIDENCE_MISSING` with the recovery when the bundle is absent); stamp-only equality and per-lane evidence reuse are separate rows. `classifyFile` (`lib/tree-receipt.ts:69`) gains one `migration-number` rule backed by raw tree-entry comparison, so mode and object-type changes are refused rather than read as empty hunks, and a failed or overflowed `git diff`, enumeration or blob read propagates as `incomplete`/non-reusable instead of an empty (passing) comparison (`lib/tree-receipt.ts:140`). `MIGRATION_COLLISION` compares candidate ids against the target base's current migrations plus a complete, paginated open-PR census (`gh pr list --json number,files` pages past 100 files and 30 PRs, bound to head SHAs, receipted as `pregate-migration-collision`), rechecks at the serialised merge position, and refuses `MIGRATION_CENSUS_INCOMPLETE` (never `pass`) on a failed page or without `gh`; semantic renumbering requires a regate. Fixtures: extend the tree-receipt tests with a gbrain-shaped fixture (`release-restamp.ts`, `since:` rows, two PRs naming v240, a collision in PR 31, an open-to-merged predecessor, different filenames sharing an id, a chmod-only change on a stamp path, a failed diff that may not print `STAMP_ONLY: yes`).
|
||||
|
||||
**D16. Sealed-output guard before push and in the hook, bound to the destination.** `[field]` Sealed rules live under `checks.sealed` in `.gstack/pregate.json` (`public`, `deny` rules for transcript paths, answer-text fences, seed files and machine-local prefixes, each with narrow per-rule `except` globs so documented placeholders pass while a sealed answer in the same file still fails; `allow` globs; `max_bytes`, default 2 MB `[target]`), reusing its schema, containment check and `gstack-pregate init`; an unconfigured check reports `not configured` and the ship receipt treats that as not-required, never passed. The rule grammar is anchored literals, globs and a fixed set of named detectors, or a regex compiled under a per-rule time budget that yields `SEALED: incomplete <file> rule=<name> (timeout)`; the sealed check reads its policy from head without `--allow-repo-commands` since it executes no command. `run --only sealed --range <a>..<b> | --staged` applies path denies to every outgoing blob before decoding, inspects actual bytes independent of diff attributes (NUL-bearing, `-diff`, UTF-16 content included), returns `incomplete` for non-exempt uninspectable content, and reports over-cap files as `SEALED: incomplete <file> (<bytes>)` with the fix line (`add "<path>" to allow, or raise max_bytes`); allow-globbed files print `SEALED: skipped <file> rule=allow`. Receipts are bound to the receiving remote, the outgoing refs and object ids, the policy hash and the scanner version; the scanned history excludes only commits established to exist at that destination, so a push to a new or unknown destination scans the full reachable history or is `incomplete`; staged-only evidence never authorises publication, and a receipt is stale once leaking history is added even when the final tree is identical. Refusals print `SEALED: <ref> <commit>:<blob> <path>:<line> rule=<name> status=current|historical (SEALED_CONTENT_REFUSED)` with no sealed snippet, and already-published history routes to the owner with non-destructive inspection guidance rather than an automatic force-push. `hook` mode reads the pre-push stdin refs through `lib/prepush-refs.ts`. `/ship` runs `--range origin/<base>..HEAD` before `git push`; `gstack-ship-receipt write` refuses `SEALED_RESULT_MISSING` without a current result when the check is configured. After B lands, `lib/coordinator-contract.ts` gains the sentence every lane inherits: "Before any push, run `gstack-pregate run --only sealed --range origin/<base>..HEAD`; a lane never pushes without a current `SEALED:` pass or `not configured` result, and pastes that line in its handoff after `GSTACK_RESULT`" (the field leak was a lane's direct push, not `/ship`; G6 is judged against that path). Fixtures: `pregate-sealed-refuses-transcript` drives the hook through stdin for a new branch carrying a per-cell transcript, a quoted sealed answer, a 20 MB `[fixture]` generated file and a `public: false` negative; a private origin with denied base-history content pushed to an empty public destination is refused; a pathological rule (`(a+)+$`) over a 1 MB `[fixture]` blob completes as `incomplete`; a clean receipt followed by add-then-delete transcript commits with an identical final tree is stale.
|
||||
|
||||
### Not in this wave
|
||||
|
||||
Scrubbing gbrain's history (URGENT above, owner's call); the gbrain paid-gate adapter (named deliverable, owner outside this plan); runner adapters for `manifests` beyond gstack's; snapshot baking itself (documented, not automated); Windows paths for the simulated host; the `mirror` restamp tier; `/eval-plan` tier 3; CODEOWNERS-driven anything; a Capy-native hook mechanism (the driver doc says the guard is not enforced, as today); crash injection after every revise step beyond the one `failAfter` test; migration-identity adapters beyond the policy glob.
|
||||
|
||||
### Validation
|
||||
|
||||
Every item: a failing test first on the simulated Capy host, then the fix, then the test passes; never a loosened assertion. Cheap gates before paid: `bun run typecheck`, `typecheck:test`, `gen:skill-docs --host all` clean, `gstack-regen check`, parity suite, every ratchet (module-size, state-root, spawnsync, troubleshooting-anchors, headless-artifacts, free-fixtures, egress-receipt-wiring, bun-spawn, harness-version), `format:cso:check`, credential scan; `bun run test:quick`; `gstack-pregate run --tier unit --range` once C13 lands (dogfood). Paid: the autoplan/ship/review workflow judges on changed prompts only. Acceptance, each a named test: `install-stale-1-91-12-self-upgrades`, `install-ok-then-check-ok` (positive and the no-network negative), `upgrade-refuses-divergence-keeps-bytes`, `self-upgrade-policy-matrix`, `autoplan-3-rev-validates` (B5, B6, B8 together), `gate-defaults-follow-agreement` (B9), `scope-doctor-status-description-is-not-ui` (B10), `driver-refuses-stale-checkout` (B13), `pregate-lane-done-manifest-row` (C13), `gate-price-hidden-synopsis` (C14), `tree-receipt-gbrain-shape` (D15), `pregate-sealed-refuses-transcript` (D16). Integrated acceptance after review fixes, generation and release metadata: freeze the tree, `bun run test` once on it, PR A's Docker smoke receipt on the gated SHA (reused through the tree receipt on the stamped tree), then the selected judges. Done means: all twelve pass on the simulated host; a 3-rev unattended autoplan run validates; a synthesised 1.91.12 checkout upgrades itself to current at session start; and the drive skill is regenerated from `scripts/capy/SKILL.md.tmpl` with the bootstrap from `scripts/capy/bootstrap.sh` (no promise of a command that does not exist; `--check` at session start, lazy install, `GSTACK_LAUNCH_FILE`, the trailer in a subagent's first report).
|
||||
|
||||
### Effort
|
||||
|
||||
| PR | New bins / libs / fixtures | Est. LOC `[estimated]` | Notes |
|
||||
|---|---|---|---|
|
||||
| A | 2 / 0 / 6 | 1,100–1,500 | bash upgrade lib shared with session-update; installation leases; host sim with synthesised origin; bootstrap, in-repo wrapper, drive skill template and pin tests |
|
||||
| B | 0 / 3 / 8 | 1,800–2,400 | `lib/autoplan-gate.ts` (215 lines), `lib/autoplan-run.ts`, `lib/autoplan-prompts.ts`, `lib/gate-list.ts`, `lib/autoplan-reconcile.ts`; new `lib/autoplan-revise.ts`, `lib/decision-ref.ts`, `lib/phase-inputs.ts` |
|
||||
| C | 0 / 4 / 7 | 1,800–2,400 | `lib/pregate/checks/{manifests,stamp,sealed}.ts`, `lib/prepush-refs.ts`, `release_tool_owns`, `--list --json`, eval-plan `--gate`, one tree-receipt rule, guard-list hook |
|
||||
|
||||
### Gate decisions
|
||||
|
||||
Resolved at gate rev 1 (all 17 reviewer challenges answered `b`): G1 → dependency chain A → B → C with one owner per shared file; G2 → bash `bin/gstack-upgrade` sharing session-update's stages; G3 → (b) plus the PATH-bearing launch line; G5 → one template rendering the Capy driver doc. Resolved at gate rev 2 (owner answered `d1b d2b d3b d4a d5b d6b d7b d8b`): the drive carries a logic-free bootstrap; `GATE_UNPRICED` is owner-answerable in release one with the adapter contract published; G8 → option (c), always current when online, floor refuses, stale note offline; `--attempt new` is dropped (d4 moot); transitive invalidation of dependent phases; installation-scoped leases; `manifests` is the gstack adapter only. Still the owner's, each a draft direction that stands until decided:
|
||||
|
||||
- **G4. A4.** (a) **Recommended:** lazy install at first workflow use from the wrapper plus the snapshot-baking doc. (b) Snapshot-baking doc only. Draft direction stands until the owner decides.
|
||||
- **G6. D16 enforcement.** (a) **Recommended:** the coordinator contract makes every lane run the sealed check before any push, `/ship` runs it too and the receipt refuses without it; the pre-push hook is opt-in via `hook install`. (b) Hook only. Draft direction stands until the owner decides.
|
||||
- **G7. The simulated host.** (a) **Recommended:** hermetic simulation in the free suite plus one Docker smoke in a dispatch lane, cleared only by a matching job receipt reused through the tree receipt. (b) Docker only. Draft direction stands until the owner decides.
|
||||
|
||||
## Review record
|
||||
|
||||
### Gate rev 1 (run `fixwave-run-20261012204516`, snapshot `6f25ac67…`, order ceo, dx, eng; scope ui=no dx=yes source=flags)
|
||||
|
||||
CEO: native 18 findings (1 Critical, 8 High, 8 Medium, 1 Low), outside 14 (`new=14 native_only=18`). DX: native 15, outside 8. Eng: native 17 (5 High, 10 Medium, 2 Low), outside 12 (10 High, 2 Medium). Seventeen user challenges rendered as gate items d1–d17; the owner answered every one `b` and `p1b` (`REOPENED: ceo,dx,eng`); the Eng-stated reconciliations were adopted: `all` leaves agreed `uc` items pending and reopen keys on the chosen option (CEO native-5 + outside-6 against B9), and the single upgrade implementation is bash sharing session-update's stages (Eng native-1 over DX native-1's `lib/upgrade.ts`).
|
||||
|
||||
### Gate rev 2 (plan revision 2, snapshot `472342d2…`, same order and scope)
|
||||
|
||||
CEO: native 10 findings (4 High, 5 Medium, 1 Low), outside 4 (3 High, 1 Medium). DX: native 17 (4 High, 8 Medium, 5 Low), outside 7 (3 High, 4 Medium); the driver omitted the CEO consensus from both DX prompts (B10 defect, reproduced). Eng: native 22 (3 High, 10 Medium, 8 Low, 1 Informational), outside 9 (6 High, 3 Medium). Eight gate items d1–d8; the owner answered `d1b d2b d3b d4a d5b d6b d7b d8b p1b` (d4 moot once d5 dropped `--attempt new`). All non-challenge High and Medium findings were folded into revision 3 as the author's own decisions; they are listed in the accepted blocks below. The run itself reproduced B5: `gstack-artifact validate run.json` reports 9 `ARTIFACT_DUPLICATE_ID` errors after this revision.
|
||||
|
||||
<!-- autoplan-accepted:ceo -->
|
||||
- Lanes form a dependency chain A → B → C with one owner per shared file, pre-assigned result codes and anchors, and C's "after B lands" shared-file list; verification: the ownership table and no two PRs touching an owned file.
|
||||
- The staleness authority lives outside the checkout; the drive carries a logic-free bootstrap that fetches and executes the fetched tree's in-repo wrapper; policy order floor → pin → off switch → fetch-under-deadline → always current when online, stale note offline; verification: `install-stale-1-91-12-self-upgrades` and `self-upgrade-policy-matrix`.
|
||||
- The runtime row is honest to `command -v bun`; `GSTACK_LAUNCH` carries PATH and a parsed `launch.env`; the installer's final check runs in a fresh environment; verification: `install-ok-then-check-ok` and its negative.
|
||||
- `all` never flips the owner's stated direction; agreed items stay pending until answered by token; reopen keys on the chosen option; answers carry `answered_by`; verification: `gate-defaults-follow-agreement`.
|
||||
- No `--record` and no `--attempt new`; `submit --result` is extended with `--extract` and `--outcome`; packaging choices are visible auto items; dedupe requires an explicit prior-item link; verification: the 3-rev fixture.
|
||||
- Gate pricing accepts only declared adapters with complete structured plans; `GATE_UNPRICED` is owner-answerable in release one and the adapter contract is published; figures are classified and only decision-driving measurements need receipts; verification: `gate-price-hidden-synopsis` and the plan-reality fixtures.
|
||||
- The Capy driver runs the doctor on its first `next` and refuses a `fail` trailer; the coordinator contract makes every lane run the sealed check before any push; verification: `driver-refuses-stale-checkout` and the contract pin test.
|
||||
- Sealed scanning covers every newly reachable blob of the outgoing refs, bound to the destination; the Docker obligation is reused through the tree receipt; the existing disclosure is triaged separately (URGENT block).
|
||||
- The Claude upgrade skill delegates to `bin/gstack-upgrade` and loses its reset fallback; sealed rules live under `checks.sealed` in `pregate.json`.
|
||||
- Integrated acceptance: frozen tree, one final `bun run test`, PR A's Docker receipt on the gated SHA, selected judges.
|
||||
<!-- /autoplan-accepted:ceo -->
|
||||
|
||||
<!-- autoplan-accepted:dx -->
|
||||
- `scripts/capy/bootstrap.sh`, `scripts/capy/install.sh` and `scripts/capy/SKILL.md.tmpl` are in-repo with pin tests; the wrapper owns the first hop and lazy install, prints every trailer on stdout, and pins its anchors; `gstack-skill-start` prints `SELF_UPGRADE:` and its unattended prose matches; a `--to` pin skips rather than refuses and one off switch exists.
|
||||
- Every failure line carries a result code and anchor; the per-PR code lists above are the enumeration.
|
||||
- The runtime FAIL row names the off-PATH verified Bun with the `launch.env` fix; `gstack-doctor --check` prints `GSTACK_LAUNCH_FILE:` itself; JSON carries `path_prepend` without shell syntax.
|
||||
- `answer` prints `REVISE:` before `NEXT:` and `next` refuses `PLAN_UNCHANGED_SINCE_GATE`; `GATE_PENDING` prints exact tokens; incremental answers compose; `--extract` has a heading grammar; the plan-side numbers scan runs before dispatch and keeps `unreceipted=<row ids>`.
|
||||
- `release_tool` stays the one key with `release_tool_owns`; one `--range` grammar with `POLICY_FROM`/`TIER` headers; `hook install|status|remove` composed through the guard-list wrapper; `autoplan.json` has init and schema; the sealed cap has a stated default, a `skipped` verdict and a fix line; the stacked-stamp regate count is stated.
|
||||
- One Capy driver template renders `docs/capy-driver.md`; every later phase receives predecessor consensus; `--json` returns one versioned object on every path; a host-subagent failure has a terminal outcome command; revise refuses while attempts are open and can replace or reset scope flags.
|
||||
<!-- /autoplan-accepted:dx -->
|
||||
|
||||
<!-- autoplan-accepted:eng -->
|
||||
- `bin/gstack-upgrade` is bash over a sourced lib extracted from session-update; conflict recovery preserves bytes and stash; `--to` distinguishes tags from commits and verifies the target; the busy predicate is a live installation lease outside the caller's ancestry plus the installer flock; the wrapper prepends the supported Bun before merge, setup and check.
|
||||
- The stale checkout is synthesised from a local bare origin cloned depth-1 under `GIT_ALLOW_PROTOCOL=file` and a dead proxy; simulator environments are allowlisted and sentinel-tested; fixtures share one upgraded checkout where the scenario allows.
|
||||
- lib spawns and generated hooks use `process.execPath`; `test/bun-spawn-ratchet.test.ts` guards it.
|
||||
- Finding rows carry `attempt`; cross-revision references are attempt-qualified; decision references go through one revision-aware constructor; `Scope.ui` is `yes|no|unknown` and revise re-detects scope with `REVISE_SCOPE_CHANGED`.
|
||||
- `revise` replaces Implementation bytes only, checks obligations, performs a journaled generation transition with idempotent resume, and invalidates transitive consumers of changed inputs; `PLAN_SHAPE_PARTIAL` replaces the scaffold idea.
|
||||
- The B9 agreement rule is mechanical (both `user_challenge` and resolution token overlap, or an `agreement` override).
|
||||
- The numbers row scopes to unit-bearing figures with `[measured]`/`[estimated]`/`[target]`/`[fixture]` tags and `file:line` receipts; reality and price checks run as a persisted validation boundary.
|
||||
- `manifests` imports the shard runner's own inventories as the gstack adapter and reports `not configured` elsewhere; `goldens` is dropped for the existing `regen`; tasks gain `checks[]`; receipts are versioned by purpose and selection; the Docker obligation clears only on a matching job receipt.
|
||||
- `--gate` consumes the new `--list --json` plans; quotes are input-bound; no `GSTACK_EVAL_DRY_RUN`.
|
||||
- The stamp-only proof is the tree receipt plus one `migration-number` rule with raw tree-entry comparison; failed comparisons are `incomplete`, never stamp-only; the stamp check takes an explicit evidence source; delegated stamps are diffed against an immutable pre-tree with independent postconditions; the migration census is paginated, SHA-bound and refuses when incomplete.
|
||||
- The sealed check inspects every outgoing blob's bytes before decoding, restricts its rule grammar or budgets regex time, reads policy without `--allow-repo-commands`, binds receipts to destination, refs, objects, policy and scanner version, and prints object identities without snippets; the hook shares `lib/prepush-refs.ts`.
|
||||
- Egress sites are receipted under named labels with the single bootstrap exemption; `review-record.md` links bound records by sha; the bootstrap is fail-open on the egress lib exactly like session-update.
|
||||
<!-- /autoplan-accepted:eng -->
|
||||
@@ -1193,6 +1193,26 @@ listed changes, check out the default branch, move local commits to another
|
||||
branch), then re-run `bin/gstack-upgrade`. A reinstall that failed after the
|
||||
fast-forward is completed by the next run (`reinstall pending from <version>`).
|
||||
|
||||
<a id="upgrade-busy"></a>
|
||||
### `gstack-upgrade: live gstack sessions hold this installation: pid <p> (<age>m), ...; nothing was changed; fix: ... (UPGRADE_BUSY)`
|
||||
|
||||
**Meaning.** Several agents share this checkout (Capy subagents on one
|
||||
machine, say). Each session start writes a workflow lease,
|
||||
`<git dir>/gstack-leases/<harness pid>` (`bin/gstack-lease touch`, from
|
||||
`bin/gstack-skill-start`), and `bin/gstack-upgrade` refused before changing
|
||||
anything because a lease whose pid is alive and not in its own process
|
||||
ancestry exists: fast-forwarding would change gstack under a session
|
||||
mid-workflow. Leases older than 120 minutes are pruned; a lease whose
|
||||
process has exited never blocks. The upgrader's own session (the pid chain it
|
||||
runs under) never counts.
|
||||
|
||||
**Fix.** Wait for those sessions to finish (`bin/gstack-lease list` shows
|
||||
every lease with its age and `alive|dead`), then re-run `bin/gstack-upgrade`.
|
||||
When you know they are idle, `GSTACK_UPGRADE_FORCE=1 bin/gstack-upgrade`
|
||||
proceeds. At a Capy session start the same refusal appears as
|
||||
`SELF_UPGRADE: skipped (live gstack sessions hold this installation: ...)`
|
||||
and the session continues on the installed revision.
|
||||
|
||||
<a id="upgrade-fetch-failed"></a>
|
||||
### `gstack-upgrade: could not reach origin (...) (UPGRADE_FETCH_FAILED)`
|
||||
|
||||
@@ -1838,6 +1858,20 @@ applied; the unparsed tokens are listed.
|
||||
`all except <tokens>`. A bare `yes` or `no` is ambiguous across multi-option
|
||||
items and is never guessed.
|
||||
|
||||
<a id="answer-by-required"></a>
|
||||
### `ANSWER_BY_REQUIRED`
|
||||
|
||||
**Meaning.** `gstack-autoplan answer` ran in an unattended run without
|
||||
`--by`, or with a value that is neither `human` nor `agent:<id>`. The
|
||||
artifacts must say who answered a gate: an owner typing tokens and an agent
|
||||
relaying or deciding them look identical in `decisions.jsonl` otherwise.
|
||||
Nothing was applied.
|
||||
|
||||
**Fix.** Re-run with `--by human` when the owner typed the tokens, or
|
||||
`--by agent:<thread or id>` when an agent relays or decides. The value is
|
||||
recorded as `answered_by` on every decision row the reply answers and in the
|
||||
review record's gate line.
|
||||
|
||||
## Queue-aware ship: policy, restamp, tree receipt, ship receipt
|
||||
|
||||
`gstack-ship-policy`, `gstack-restamp`, `gstack-tree-receipt`,
|
||||
|
||||
+16
-6
@@ -186,22 +186,32 @@ the plan.
|
||||
`PHASE_NOT_AWAITING`, exit 3). Both voices in closes the phase: the
|
||||
reconciliation (`confirmed` / `disagree` / `new` per outside finding, with
|
||||
disagreement and resolution kept separate) is written to
|
||||
`<phase>-consensus.{json,md}`, and the Eng prompt refuses to open without
|
||||
every closed prior phase's consensus (`CONSENSUS_MISSING`).
|
||||
`<phase>-consensus.{json,md}`, and every later phase's prompts (DX, design
|
||||
and Eng alike) carry each closed predecessor's consensus as data; a phase
|
||||
refuses to open without one (`CONSENSUS_MISSING`).
|
||||
3. **Loop `next` until `status=gate_pending`.** Eng always runs last. The gate
|
||||
(`gate.json`, `gate_rev`) lists user challenges as approval items,
|
||||
disagreements as auto items and `p1` plan approval; nothing is approved.
|
||||
4. **Answer the gate** with the reply grammar and the revision you read:
|
||||
```bash
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all" # every recommendation; agreeing plan changes print APPLY:
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all as-is" # APPROVED: keeps every direction, no new paid phase
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all except d1b" # APPLY: <id> ...; REOPENED: ceo,eng; NEXT: gstack-autoplan revise ...
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all" --by human # every recommendation the owner may delegate; agreeing plan changes stay pending
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all as-is" --by human # APPROVED: keeps every direction, no new paid phase
|
||||
$AP answer --out <dir> --gate-rev 1 --reply "all except d1b" --by human # APPLY: <id> ...; REOPENED: ceo,eng; NEXT: gstack-autoplan revise ...
|
||||
```
|
||||
`--by human` means the owner typed the tokens; `--by agent:<thread or id>`
|
||||
means an agent relayed or decided them. An unattended run refuses an answer
|
||||
without it (`ANSWER_BY_REQUIRED`, exit 3) and records the value as
|
||||
`answered_by` on every decision row and in the review record's gate line,
|
||||
so a gate an agent closed is distinguishable from one the owner closed.
|
||||
A user challenge both voices raised on a confirmed finding with the same
|
||||
`changes` value is recommended as `b`, and option b shows both voices'
|
||||
remedies (`recommended: b (both voices; remedies shown)`); a `--reconcile`
|
||||
record naming one shared `proposal` shows that instead. Disagreeing or
|
||||
missing remedies keep `a`. Every accepted plan change prints
|
||||
missing remedies keep `a`. `all` never answers an item recommended `b`:
|
||||
that would change the owner's stated direction on their behalf, so those
|
||||
items stay pending (`GATE_PENDING: uc1 unanswered (reply with uc1a|uc1b; all
|
||||
does not answer items recommended b)`) until their own token arrives, and
|
||||
the rendered gate names them under the reply grammar. Every accepted plan change prints
|
||||
`APPLY: <decision id> <title> -> <chosen text>` and becomes a pending
|
||||
amendment: `next` refuses with `PLAN_AMEND_PENDING` and `export` refuses a
|
||||
`complete` run until `revise --incorporates` names it (see below).
|
||||
|
||||
Reference in new issue
Block a user