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.
60 KiB
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/gstacksat at 1.91.69.0, 25 releases behind. The drive skill'sinstall.sh --checkprintedgstack: 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 itand exited 1.bin/gstack-upgradedoes 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 endedgstack: fail claude, because./setupleft~/.claude/skills/gstackpointing at the stale checkout. Onlygit merge --ff-only origin/mainin the stale checkout plus./setuprecovered the machine, exactly the field report. - A3. After that recovery the installer printed
gstack: ok 1.91.94.0 for=autoplanand exited 0. The very nextinstall.sh --checkin a fresh shell printedFAIL runtime — bun 1.3.14 at /usr/local/bin/bun is below the supported minimum 1.4.2andFAIL pins, trailergstack: fail runtime,pins, exit 1; withPATH=$HOME/.bun/bin:$PATHthe same check printedgstack: ok. The installer exports the verified Bun only inside its own shell (bin/gstack-capy-install:108) and theGSTACK_LAUNCHline it prints (:145) carries noPATH. The runtime FAIL row'sfix:says to reinstall Bun although~/.bun/bin/bunis already 1.4.2 (lib/result-codes.ts:126).lib/autoplan-prompts.ts:33andlib/restamp.ts:177,314spawnbunby PATH name. - B5.
lib/autoplan-gate.ts:107-120finishGatewrites decision rows whose ids come fromlib/gate-list.ts:148-150as${list.run}-${item.id};gate_revis a separate column, so a second gate revision re-emits the same ids andgstack-artifact validatereportsARTIFACT_DUPLICATE_ID. (finishGatealready retains earlier revisions' rows; the ids are what collide.) Reproduced by this run itself: after gate rev 2,gstack-artifact validate run.jsonreports 9ARTIFACT_DUPLICATE_IDerrors (d1..d8,p1atdecisions.jsonl:19-27), so the run cannot validate until B5 ships. - B6/B8.
bin/gstack-autoplan --help: norevisesubcommand. Reopened phases snapshot the run'splan-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 planfollowed by one## Review record(lib/autoplan-export.ts:54-57is tolerant);initalready scaffolds a heading-less plan (:415), and the error names neither the missing heading nor a fix. After abanswer,answerprintsNEXT: next(bin/gstack-autoplan:184) andnextsnapshots 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 withrecommended: '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:138resolvesalltorecommended, andanswerreopens onhow === 'override', not on the option chosen. - B10.
lib/autoplan-prompts.ts:23-27: UI scope is two or more hits ofcomponent|screen|form|button|modal|layout|dashboard|sidebar|nav|dialoganywhere in the plan text, Review record included;detectScope(:39-46) recordssource: 'flags'and nothing persists flags across runs.openPhasepasses 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-autoplannever passes throughgstack-skill-startor the doctor; this run'srun.jsoncarries nogstack_revision. Gate answers recordchosen,answered_atandreplybut not who answered (lib/gate-list.ts:155). - D15/D16.
lib/ship-policy.ts:34,58hasrelease_tool(one command run insidegstack-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 --helphas 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/mainmoved, the checkout fast-forwards itself (with autostash, never a reset) and refreshes setup, reruns--check, and refuses to run a workflow while the trailer isfail; 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 firstnextand refuses afailtrailer. A 1.91.12 checkout reaches current from oneinstall.sh --check, on every machine a thread uses, and a subagent's first report carries itsgstack:trailer. - P2. The installer and the check agree.
gstack-capy-installnever printsgstack: okon a machine where the next fresh-shell--checkfails;GSTACK_LAUNCHcarries the verified Bun onPATH; lib code never spawnsbunby name. - P3. A three-revision unattended autoplan run validates and closes. Rev-scoped decision ids,
submit --result --extractand--outcomefor phases run as Capy subagents,revise --plan v2in the same run before any rerun can dispatch, reruns that dedupe only explicitly linked repeats, user-challenge defaults that follow mechanically agreeing voices without lettingallflip 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_UNPRICEDitem, never silently accepted. Release one ships the gstack adapter only; other runners' manifests reportnot 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 evaled 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,
/shipruns it too and the receipt refuses without it; the pre-push hook is opt-in viahook 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.
- 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-upgradesandself-upgrade-policy-matrix. - The runtime row is honest to
command -v bun;GSTACK_LAUNCHcarries PATH and a parsedlaunch.env; the installer's final check runs in a fresh environment; verification:install-ok-then-check-okand its negative. allnever flips the owner's stated direction; agreed items stay pending until answered by token; reopen keys on the chosen option; answers carryanswered_by; verification:gate-defaults-follow-agreement.- No
--recordand no--attempt new;submit --resultis extended with--extractand--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_UNPRICEDis 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-synopsisand the plan-reality fixtures. - The Capy driver runs the doctor on its first
nextand refuses afailtrailer; the coordinator contract makes every lane run the sealed check before any push; verification:driver-refuses-stale-checkoutand 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-upgradeand loses its reset fallback; sealed rules live underchecks.sealedinpregate.json. - Integrated acceptance: frozen tree, one final
bun run test, PR A's Docker receipt on the gated SHA, selected judges.
scripts/capy/bootstrap.sh,scripts/capy/install.shandscripts/capy/SKILL.md.tmplare 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-startprintsSELF_UPGRADE:and its unattended prose matches; a--topin 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.envfix;gstack-doctor --checkprintsGSTACK_LAUNCH_FILE:itself; JSON carriespath_prependwithout shell syntax. answerprintsREVISE:beforeNEXT:andnextrefusesPLAN_UNCHANGED_SINCE_GATE;GATE_PENDINGprints exact tokens; incremental answers compose;--extracthas a heading grammar; the plan-side numbers scan runs before dispatch and keepsunreceipted=<row ids>.release_toolstays the one key withrelease_tool_owns; one--rangegrammar withPOLICY_FROM/TIERheaders;hook install|status|removecomposed through the guard-list wrapper;autoplan.jsonhas init and schema; the sealed cap has a stated default, askippedverdict 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;--jsonreturns 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.
bin/gstack-upgradeis bash over a sourced lib extracted from session-update; conflict recovery preserves bytes and stash;--todistinguishes 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=fileand 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.tsguards it. - Finding rows carry
attempt; cross-revision references are attempt-qualified; decision references go through one revision-aware constructor;Scope.uiisyes|no|unknownand revise re-detects scope withREVISE_SCOPE_CHANGED. revisereplaces Implementation bytes only, checks obligations, performs a journaled generation transition with idempotent resume, and invalidates transitive consumers of changed inputs;PLAN_SHAPE_PARTIALreplaces the scaffold idea.- The B9 agreement rule is mechanical (both
user_challengeand resolution token overlap, or anagreementoverride). - The numbers row scopes to unit-bearing figures with
[measured]/[estimated]/[target]/[fixture]tags andfile:linereceipts; reality and price checks run as a persisted validation boundary. manifestsimports the shard runner's own inventories as the gstack adapter and reportsnot configuredelsewhere;goldensis dropped for the existingregen; tasks gainchecks[]; receipts are versioned by purpose and selection; the Docker obligation clears only on a matching job receipt.--gateconsumes the new--list --jsonplans; quotes are input-bound; noGSTACK_EVAL_DRY_RUN.- The stamp-only proof is the tree receipt plus one
migration-numberrule with raw tree-entry comparison; failed comparisons areincomplete, 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 shareslib/prepush-refs.ts. - Egress sites are receipted under named labels with the single bootstrap exemption;
review-record.mdlinks bound records by sha; the bootstrap is fail-open on the egress lib exactly like session-update.