Files
Garry Tanandcapy-ai[bot] 45a678323b v1.91.70.0 feat(unattended): artifact contract + validator, unattended session kind, findings-derived review log, gate list (multi-agent wave B-core) (#3115)
* v1.91.70.0 feat(unattended): artifact contract, unattended session kind, findings-derived review log, gate list

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: register test/fixtures/multi-agent-wave as a free fixture (consumer: headless-artifacts.test.ts)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-10 23:26:08 +00:00

394 lines
112 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- /autoplan restore point: "/workspace/gstack/.gstack/tmp/autoplan/main-autoplan-restore-20261010-203029.md" -->
# gstack multi-agent wave (October 10, 2026): honest install, unattended autoplan, queue-aware ship, cheap-first gates
## URGENT, outside this plan
None. Six reviewer passes (CEO, DX, Eng; native Claude and outside gpt-6-astra each) raised no security or data-loss finding the plan does not own. The one live defect found during the run, the user-scope installer's `--check` certifying an absent install (`~/.capy/drive/user-garry-tan/skills/gstack/scripts/install.sh:7-16`, `if --check; then check_install; exit 0; fi`), is owned by A2/D3 and is worked around today by running the full install on every Capy machine (86 s).
## Implementation plan
### Context
Base: origin/main `91bbd9e` (v1.91.69.0). Input: feedback from 28 Capy threads that built gbrain and gbrain-evals over a month on cloud machines with no Claude Code harness, a coordinator thread merging PRs one at a time, full test gates on ephemeral Ubicloud VMs, and preregistered paid evals. 13 of the 28 threads ran no gstack workflow beyond the installer; several times the skills were not installed and the check did not say so. The others used each repo's own release tooling because `/ship` does not fit a shared serial merge queue.
Evidence gathered for this plan (2026-10-10, this machine, a fresh Capy cloud box):
- **The install check lies by omission (P0-1 reproduced).** The user-scope Capy installer's `--check` (`~/.capy/drive/user-garry-tan/skills/gstack/scripts/install.sh`, a bare `test -x` chain) printed nothing and exited 1 in 0.004 s with nothing installed. The cause is `if --check; then check_install; exit 0; fi` under `set -e`: a failed probe exits 1 with no message, and through a pipe (`... | tail`) the caller sees the pipe's status, 0, which is the "exited 0 with nothing installed" report. A full install then took 86 s, most of it Chromium (114.7 MiB headless shell plus fonts). After it, `bin/gstack-doctor` reported `7 ok, 2 warn, 3 not configured, 0 fail` and exited 0 with Bun 1.3.14 against the repo's tested 1.4.2 (`warn`, not `fail`), no Node row, no telemetry row.
- **What exists already is more than the feedback assumes.** `bin/gstack-doctor` (456 lines) has one `check_*` row per component with a fix line and exits 1 on `fail` (bin/gstack-doctor:3-16, :455-456). `bin/gstack-session-kind` already classifies `spawned | headless | interactive` (`GSTACK_HEADLESS`, `CI`, `GSTACK_SESSION_KIND=spawned`), and the preamble's AskUserQuestion rules branch on it (scripts/resolvers/preamble/generate-ask-user-format.ts:15-29). The snapshot tool wraps a raw plan in `## Implementation plan` automatically (bin/gstack-autoplan-snapshot.ts:415), so no literal heading is required of the user. `gstack-wtree` and `gstack-evidence` already compute reviewed-tree fingerprints (bin/gstack-evidence:13, :392). `bin/gstack-next-version` already reads the live PR queue and sibling worktrees (bin/gstack-next-version:80-105). `ship/sections/measure.md.tmpl:3-16` already holds a never-loosen rerun discipline. `codex_reviews` defaults to `enabled` (bin/gstack-config:251), so the outside voice is on by default.
- **Where the gaps actually are.** `headless` means BLOCK on any question (generate-ask-user-format.ts:28); only `spawned` auto-continues, and `spawned` would auto-approve autoplan's final gate. Upgrade, telemetry, lake and proactive prompts are suppressed for `spawned` only (bin/gstack-skill-start:394). Review-log `status`/`unresolved` are model-written free text; nothing derives them from findings (bin/gstack-review-log:92-97, lib/review-evidence.ts:226-243). `bin/gstack-version-bump write` stamps VERSION, package.json and npm lockfiles only, with one hard-coded post-write hook (`scripts/gen-agents-digest.ts`, :374-402); there is no `release:restamp` hook, no `--after <pr>`, no tree-equality receipt in the PR body, and no CHANGELOG re-heading tool. Paid evals in `/ship` (Step 6) run before Step 16's cheap checks. `/investigate` has no flake, polluter, race or backlog content (investigate/SKILL.md.tmpl, 278 lines). No user-facing eval workflow exists; preregistration, panels, admission budgets and dev/held-out splits are all gstack-internal (scripts/ship-measure.ts:22-28, scripts/judge-calibration.ts:42, test/helpers/periodic-exclude-data.ts:79-90). Codex outside voice from an API key works today (`printenv OPENAI_API_KEY | codex login --with-api-key` then `gstack-codex-probe role-ready exec` printed `CODEX_SEL: gpt-6-astra`, `AUTH_OK`, `MODEL_OK` in 3.7 s) but no helper, doctor fix line or doc names it (grep: only TODOS.md:1640 and CI tests).
- **Owner input received during review (binding).** Garry's second message adds: accept a subagent on another model family as a full outside voice and never report a missing Codex CLI as degraded; store learnings and telemetry durably on ephemeral machines or say plainly they are skipped; allow guarded tool calls when no publication guard is installed and say so; list the linked issue's deferred asks in the gate template; name the ship mode `--queue-mode stamp-at-merge`. These are folded into A3, B1, B3, C2 and D1 below.
- **Repo constraints that shape every item.** SKILL.md is generated from `.tmpl` (edit templates, run `bun run gen:skill-docs`). New logic goes in `lib/*.ts` behind thin `bin/` entrypoints; `test/module-size-ratchet.test.ts` caps *registered* owner modules at 800 lines and 150 per function (test/helpers/module-size.ts:202), so every new module this wave adds is registered in `test/fixtures/module-size-ratchet.json`, and `bin/gstack-next-version` (822 lines), `bin/gstack-autoplan-snapshot.ts` (865) and `setup` (4662), unregistered today, are not grown. Four skills have no parity headroom at all (ship 1.539/1.539, review 1.234/1.2345, autoplan 1.129/1.1305, qa 1.127/1.1272 union-byte ratios against `test/helpers/parity-harness.ts` guards, measured during this review), so prose added to them must be replaced byte for byte or printed by a bin instead. State paths resolve only through `lib/state-root.ts` / `bin/gstack-paths`. Prose in templates cannot be pinned by sentence (docs/test-value-bar.md); new rows get structural tests plus eval cases. Every template edit must clear `bun test test/parity-suite.test.ts` and the context-budget ratchets.
The wave has one thesis: **gstack must be a tool a parent agent can drive and verify without reading prose.** Every artifact below is machine-readable first (one grep-able line, a JSONL file, a fenced receipt) and human-readable second.
### Release promises (the finish line)
- **P1. The install check cannot lie.** On a machine with nothing installed, one command prints one PASS/FAIL line per component with the exact fix command, ends with `gstack: fail <components>` and exits non-zero. On a healthy machine it ends with `gstack: ok <revision>`. A parent agent confirms a subagent machine by grepping that one line. Planning-only machines skip Chromium.
- **P2. /autoplan, the plan reviews and /cso run unattended on a host with no Claude Code harness.** Under the new `unattended` session kind they need no hooks, no AskUserQuestion, no Skill or Agent tool, prompt for nothing, and write `plan.md`, `review-record.md`, `tasks.jsonl`, `decisions.jsonl`, `findings.jsonl`, `timing.json` and a `run.json` manifest, all under one versioned schema with a validator, that a parent agent consumes without reading prose. The approval gate is never auto-approved: it is written as a gate list and the run ends.
- **P3. "Ship, next in queue at <version>" works.** The stamp happens when the PR is next, from main's VERSION at that moment, through the repo's own release tool when it has one, across every stamp location at once. The PR carries a tree-equality receipt and a handoff record with identifiers first. Zero manual stamp edits; a pure restamp of a gated tree is reported as `gate-reuse: eligible` with the identity it rests on (tree modulo stamp and generated paths, runtime pins, lockfile, CI config), and the coordinator reuses the gate instead of paying for it again.
- **P4. Cheap checks run before any paid or remote gate.** A two-minute pre-gate replays every mechanical-drift incident in the feedback (stale goldens, scratch files, pipe-hidden failures, unrun platform lanes, old constants in nightly-only tests, dropped vendored hunks) and refuses to start a gate while any fails.
- **P5. Plans, reviews and investigations carry the gbrain lessons as required rows,** each one tied to the incident that earned it, each failing the review when missing.
- **P6. Measurement, coordination and the owner's view get first-class support:** a preregistered-eval workflow with a budget ledger, lane-ownership and evidence-bundle tools, and a one-page owner brief with numbered decisions.
- **P7. Adoption is measured, not assumed.** The durable carriers are the ones that already leave the machine: `run.json.consumed_by` in the parent-chosen `--out` directory and the ship receipt's `session_kind` and `artifacts_consumed: <n>/<m>` fields in merged PR bodies; `gstack-ship-receipt census --repo <r> --since <tag>` computes the number from merged PRs through `gh`, deduplicated by run and thread id, against an owner-maintained thread cohort (threads that never invoke gstack count in the denominator); state-root analytics stay a bonus labeled `durable=no`. The tier-3 items in PRs D and E start only after the first ten Capy threads on B-runner show at least six unattended runs whose artifacts a parent consumed (see UC1).
### Delivery shape
Seven pull requests, merged in order; each is shippable alone and each later one assumes the earlier one merged. Every item below carries a tier: **tier 1** ships in its PR; **tier 2** ships in the same PR only if green and otherwise moves to the next PR without blocking; **tier 3** waits for the P7 adoption number. The Eng review sized B and C at two review sittings each (3,500–6,000 LOC `[estimated]`), so each is split at its natural seam; the halves can be built in parallel on fresh machines, stacked. Queue cost: six extra full gates over a single PR, two of them made cheaper by the pre-gate they ship. The stack dogfoods itself: C-stamp's restamp tool stamps every PR behind it. (See decisions D1, D12 and UC1.)
| PR | Items | Theme | Depends on |
|---|---|---|---|
| A | A1–A3 | Honest install (small, lands first) | — |
| B-core | B0, B1, B7, B9, B10, B12 | Artifact contract, `unattended` kind, gate list, derived review log | A (`GSTACK_LAUNCH`) |
| B-runner | B2, B3, B4, B8, B11 | Unattended runner, outside-voice runner, checklists, spend ledger | B-core |
| C-stamp | C1, C2, C3, C5, C7 | Ship policy, restamp, receipt, handoff, history policy | B-core (schemas) |
| C-pregate | C4, C6, C8 | Regen registry, issue links, pre-gate | C-stamp (trust boundary) |
| D | D1–D3 | Plan rows, sideways review, investigate recipes | B-runner (tier 2 rows need `scope`) |
| E | E1–E3 | `/eval-plan`, coordination tools, owner brief | B-runner (ledger), C-stamp (bundle) |
Every PR's acceptance lists the union byte delta per skill it touches and the `CARVE_GUARDS`/`capture-context-budget` refresh commit, because ship, review, autoplan and qa have zero parity headroom today. Shared files have one owner each: `bin/gstack-skill-start` (B-core), `scripts/resolvers/outside-voice*.ts` (B-runner), `ship/SKILL.md.tmpl` and `ship/sections/*` (C-stamp, then C-pregate), the ratchet fixture (one commit per PR by that PR's author).
### PR A: honest install (P0-1)
**A1. `gstack-doctor --check`: the one-line verdict.** Add a `--check` mode to `bin/gstack-doctor` that prints one line per component as `PASS <component>` or `FAIL <component> — fix: <exact command>` and ends with `gstack: ok <revision>` or `gstack: fail <component,component>`, exit 1 on any FAIL, exit 0 only when every required component passes. Components: Claude skills, Codex skills, the Astra behavioral patch (grep of the generated patch heading, as the Capy installer does today), browse bundle, Chromium launch and render (reuse setup's launch probe plus a `setContent` smoke render through `bin/gstack-render.ts`), runtime pins, privacy state. Readiness is relative to a requested workflow: `--for <skill>` (or `--require <csv>`) names the components that skill needs (planning skills: both host profiles and the patch; browser skills: plus the browse bundle and Chromium; `/cso`: plus the native helper) and the trailer names the workflow it certified (`gstack: ok 1.91.70.0 for=autoplan`). Without either flag the required set is the installed hosts, and the trailer says so. Wrong-host, missing-authentication and incompatible-runtime fixtures are tested, not just missing files. One component table feeds the row ids, `--for` resolution, `--require`, the trailer and the `--json` keys, so a parent never maps between vocabularies. The doctor stays Bash 3.2 and reports without Bun (bin/gstack-doctor:22; the P1 scenario is a machine with Bun absent or below floor), so the table's source of truth is `lib/doctor-components.ts` and the file the doctor sources, `bin/gstack-doctor-components.sh`, is generated from it at build time with a freshness test, the way the agents digest is. `--check` is fixtured with `bun` removed from `PATH`, with an empty installed-host set (never `ok`), and with an unmet `--revision` (never `ok`). The `gstack runtime` row distinguishes gstack's security floor (1.3.3) from its supported minimum (1.4.2, bin/gstack-bun-version.sh:15); 1.3.14 is FAIL in `--check`. `--for <skill>` prints `required: claude codex patch` before the rows, and each row is `PASS | FAIL | SKIP <id>` where SKIP marks a component the selected workflow, host or runner does not need (`not_applicable`) or an optional one that is absent (`optional`). A healthy Capy machine with Claude subagents as the outside voice and no Codex CLI passes `--for autoplan`. `--json` emits the same rows as objects; `--check` never runs the paid Codex probe unless `--live` is also passed. Silent success becomes impossible: every path prints the trailer. `./setup --status` exits 1 when any install row reports broken section links instead of always 0 (setup:136-146).
- New rows: `gstack runtime` (Bun against gstack's own floor and tested version, today's `bun` row) and `project pins`, which reads the *target repository's* (named by `--project <path>`, default the git toplevel of the cwd, never the gstack checkout) `packageManager`, `engines`, `.tool-versions`, `.nvmrc`, `.bun-version` and the CI matrix (`bun-version:` / `node-version:` in `.github/workflows/*.yml`) through new `lib/runtime-pins.ts`, compares with the running Bun and Node, distinguishes a range (`>=1.4.2`) from an exact pin, keeps platform-specific CI pins with their lane (the Windows workflow pins Bun and Node separately, .github/workflows/windows-free-tests.yml:83, and must not block a Linux run), prints each effective constraint with its source, and reports `FAIL project pins — bun 1.3.14 outside engines.bun >=1.4.2 (package.json:80; CI windows-free-tests.yml:89 pins 1.4.2); fix: bun upgrade --version 1.4.2`. A mismatch against the project's pin is FAIL in `--check` (a gate on an unpinned runtime is the incident), `warn` in the dashboard. `privacy` prints `telemetry=<mode> artifacts_sync=<mode> codex_reviews=<v> update_check=<v>` from `gstack-config get`, values only (doctor's clamp rule). `cores` prints the CPU count and, under 8 cores, the remote-gate recommendation (consumed by E2).
- Tests: `test/gstack-doctor.test.ts` gains the `--check` fixtures (healthy, each missing component, runtime mismatch), pinned on the trailer tokens and exit codes, not prose.
**A2. The Capy installer lives in the repo.** New `bin/gstack-capy-install` (bash, Capy cloud Ubuntu only, under 200 lines): clone or reuse `~/.local/share/gstack` under the existing lock (never reset, never auto-pulled), `./setup --host claude --no-prefix`, `./setup --host codex --model gpt-6-astra`, then `gstack-doctor --check --for <skill>` (default `--require claude,codex,patch`). Chromium is lazy by default: the installer skips it (`GSTACK_SKIP_PLAYWRIGHT=1 GSTACK_SKIP_FONTS=1`), new `bin/gstack-browser-ensure` installs it on first use from the browse preflight in `scripts/resolvers/browse.ts`, and `--with-browser` installs it up front for machines that want the 86 s now; `--only <skill>` installs the host profiles and checks only that skill's declared dependencies. `./setup` gains the documented `--no-browser` flag (today only the undocumented env var, setup:2072). Runtime preflight runs before `./setup`, because `setup` exits at once when Bun is absent or below its floor (setup:296): the installer checks Bun, Node and `jq`, installs or upgrades Bun through `setup`'s own checksum-verified recipe (`BUN_VERSION=<v> bash install`, setup:296-305; `bun upgrade --version` does not exist on 1.3.14) when gstack's supported minimum or the project's pin requires it and the two agree, and stops with both constraints printed when they conflict. The default Capy image ships 1.3.14, so P1 is unmeetable there otherwise. `--revision <ref>` pins the checkout for a fleet; an existing checkout at a different revision is reused and reported as `gstack: ok <installed> (requested <ref>; run gstack-upgrade to move)`, never pulled. Chromium skip precedence is one sentence in `setup --help`: `--no-browser` sets `GSTACK_SKIP_PLAYWRIGHT=1` internally; the installer passes it unless `--with-browser`. Planning-only install time is measured separately from browser bootstrap and printed. The user-scope Capy skill becomes a thin wrapper: clone or reuse the repo under the same lock, then run `bin/gstack-capy-install` from the clone (no raw-content URL), and grep `gstack: ok`. Its SKILL.md tells a parent agent to confirm each subagent machine with that grep and never to pipe `--check` through `tail`.
- Acceptance: a fresh machine with nothing installed fails loudly, naming each missing component; the default install finishes without downloading Chromium and reports `SKIP browser (lazy; gstack-browser-ensure installs on first use)` rather than PASS; the first browser skill run installs it.
**A3. Durable state on ephemeral machines.** The state-root chain already exists (`GSTACK_STATE_ROOT` > `GSTACK_HOME` > `GSTACK_STATE_DIR` > `CLAUDE_PLUGIN_DATA` > `~/.gstack`, lib/state-root.ts), so learnings, decisions, review logs and timing are a defaults-and-honesty fix: `gstack-skill-start` prints `STATE_ROOT: <path> durable=<yes|no>` once per session, detecting an ephemeral root (a Capy cloud home, a tmpfs, or `GSTACK_EPHEMERAL=1`), and in unattended runs prints `learnings: skipped (state root is ephemeral; set GSTACK_STATE_ROOT)` instead of writing silently to a disk that will vanish. Ephemerality is detected from an environment marker the host sets (`GSTACK_EPHEMERAL=1`, which the Capy installer exports into the skill's launch configuration), not a path heuristic. A durable root is opt-in: the owner sets `GSTACK_STATE_ROOT` (the Capy drive is governed by Capy's own drive policy, which excludes memory and verification evidence, so the installer only prints the option and never writes there by default). Because a child shell's export cannot configure the parent's later commands (lib/state-root.ts:22 reads each process's own environment), the installer prints a machine-readable launch configuration (`GSTACK_LAUNCH: GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT=<path> GSTACK_EPHEMERAL=1`) that the parent supplies to every gstack command it launches; the doctor `state root` row shows the durability verdict.
### PR B (B-core, B-runner): unattended autoplan and the artifact contract (P0-2)
Item ids are stable across review phases, so B5 and B6 (folded into B2 and B0 during the CEO phase) are not reused.
**B0. One versioned artifact contract.** New `lib/headless-artifacts.ts` owns every schema this wave writes (`tasks`, `decisions`, `findings`, `timing`, `run` manifest, `pregate`, `ship-receipt`), each with `schema_version`, exported TypeScript types, and `bin/gstack-artifact validate <file>`, which `phase-close`, `gstack-ship-receipt` and `gstack-pregate` call before writing. `run.json` is the manifest: required phases, each reviewer's outcome and model, input hashes, decision and finding counts, artifact paths and hashes; `gstack-artifact validate run.json` is the deterministic completion check a parent runs first, and it fails on an interrupted run, a missing reviewer, a stale artifact or a findings total that disagrees with the file. `findings.jsonl` rows share the review-log row shape so B7's derivation is one function over one type, and every finding carries a run-bound id (`<run>-<phase>-F<n>`) that decisions, tasks and the URGENT block reference. `tasks.jsonl` rows carry run-bound ids, `depends_on`, `acceptance` (the command or check that proves the task), `findings` (ids) and `blocked_by` (decision ids); `validate` checks referential integrity across the files and binds each reviewer's immutable output file (path and hash) into `run.json`, so a count that merely matches cannot hide an omitted finding. One transport contract for every new command: `--json` means JSON only on stdout and diagnostics on stderr; without it, one terminal line `GSTACK_RESULT: skill=<name> status=<complete|gate_pending|incomplete|interrupted|refused> run=<dir>` where the run directory holds fixed file names; a shared exit-code table (0 ok, 1 fail, 2 usage, 3 refused or needs a flag) printed in every new bin's `--help`, with a `--help` coverage test; every error line carries a reason code with a `docs/troubleshooting.md` anchor and a `fix:` clause from a new sibling table `lib/result-codes.ts` (same `{anchor, summary, fix}` shape as `lib/gate-outcomes.ts`, whose `state` enum is for gate outcomes only; `test/troubleshooting-anchors.test.ts` covers both tables; the artifact errors are an enumerated list, not a wildcard): `GUARD_NOT_INSTALLED`, `REPO_COMMANDS_NOT_AUTHORIZED`, `POLICY_SOURCE_UNAVAILABLE`, `RESTAMP_PREDECESSOR_MOVED`, `GATE_REUSE_NOT_ELIGIBLE`, `QUEUE_STALE`, `REVIEW_STATUS_MISMATCH`, `EXECUTION_UNKNOWN`, and the `ARTIFACT_*` list. The terminal-line rule applies to workflow commands (`run`, `next`, `submit`, `answer`, `restamp`, `pregate`); pure query commands (`schema`, `validate --json`, the doctor trailer) keep their own documented output. Every reviewer returns canonical structured findings (the `findings.jsonl` row shape) beside its prose, and the aggregate reconciles their exact ids, keeping disagreements and resolutions as separate fields; a hash of the prose proves which file was kept, not completeness, so the reconciliation is the completeness check. `validate` also rejects duplicate ids, dependency cycles, malformed JSONL tails, and artifact paths that escape the run directory (symlinks included). Formats are versioned from day one with every reader and writer enumerated (`tasks` v2 adds `depends_on`, `acceptance`, `findings`, `blocked_by` over today's `source_finding` rows, scripts/resolvers/tasks-section.ts:77; v1 rows still aggregate), and `bindReview` validates a `--findings` file before it consumes the start token (lib/review-evidence.ts:188). `gstack-artifact schema <name>` prints the JSON Schema for the version it validates, so a parent on another machine can read the contract; `gstack-artifact ack <run.json> --consumer <thread-id>` is the idempotent consumption report behind P7 (it sets `run.json.consumed_by` and appends to `analytics/autoplan-timing.jsonl`); `validate --json` returns `{valid, errors:[{code, path, message}]}`.
**B1. A real unattended session kind.** A fourth kind, `unattended`, honored only as the explicit `GSTACK_SESSION_KIND=unattended` override (bin/gstack-session-kind:17-23). `headless` (set by `GSTACK_HEADLESS`, `CI`, `GITHUB_ACTIONS`, and by the eval harness) keeps today's BLOCK semantics, so no paid eval or E2E case changes meaning. `unattended` gets its own semantics in the preamble and `gstack-skill-start`:
- `unattended` is added to the kind whitelist in `gstack-skill-start` (bin/gstack-skill-start:93 coerces any unknown kind to `interactive`, the inverse of the intent), an unknown kind prints `SESSION_KIND: interactive (unknown kind '<x>')` so a mixed-version fleet is loud, the explicit override outranks `GSTACK_HEADLESS`, and the eval harness never sets it.
- No prompts of any kind: update check, upgrade offer, lake intro, telemetry, proactive and routing blocks are suppressed as they are for `spawned` (bin/gstack-skill-start:96, :394). Privacy is enforced at the writers and sync entrypoints, not only at the prompts: the analytics and artifact-sync calls that run outside the onboarding guard (bin/gstack-skill-start:179, :310) and the review-log sync enqueue (bin/gstack-review-log:99) all check the session kind and skip with an explicit line, inherited enabled settings and pending queue work included; the test instruments the egress sinks and asserts no subprocess or network call, not merely the absence of a prompt. The upgrade offer becomes one line at skill end (`UPGRADE_AVAILABLE: <version>; run /gstack-upgrade`). An unanswered consent is `off`; the preamble never records consent in headless.
- Questions: a `(recommended)` option is taken and the decision appended to `decisions.jsonl` with `kind: "auto"`; a question with no recommendation, a consent, a destructive option or an approval gate is appended with `kind: "gate"` and `status: "pending"`, and the skill stops at its next gate with the pending list printed. Unattended never approves and never publishes or syncs data. `spawned` and `headless` keep today's behavior.
- The Capy adaptation text in the preamble (no Skill tool, no slash commands, plain text is not delivered) moves from the user-scope skill into a `{{HOST_ADAPTATION:unattended}}` resolver so every host's render carries it.
- The autoplan frontmatter `PreToolUse` hooks deny every matched call with `Autoplan publication guard is unavailable` when `autoplan/bin/phase-publication-hook` is absent. A host that honors the frontmatter but lacks the file is blocked outright. The hook renders only for the Claude host (scripts/resolvers/composition.ts:11) and on Capy it is present but never executed, so "file absent" is the wrong condition: on Claude Code, deny-on-absent stays (a broken install must not pass as an absent one, autoplan/bin/phase-publication-hook:4-9); on every host that does not execute hooks, and whenever `SESSION_KIND=unattended`, `gstack-skill-start` prints `autoplan guard: not enforced by this host; publication order is unverified (GUARD_NOT_INSTALLED)` once, and B0's validator requires that line in `review-record.md` for an unattended run. That satisfies the owner's ask (guarded calls proceed and the run says so) without weakening the one host that enforces the guard.
- Tests: `test/gstack-session-kind.test.ts`, `test/gstack-skill-start.test.ts`, `test/spawned-consent-rule.test.ts` gain the unattended cases; negative controls prove unattended never emits the telemetry prompt and that `GSTACK_HEADLESS` still blocks.
**B2. autoplan unattended profile and the output files.** A new lazy section `autoplan/sections/unattended.md.tmpl` (loaded when `SESSION_KIND: unattended`) and a `bun gstack-autoplan-snapshot export` step implemented in new `lib/autoplan-export.ts` (the snapshot bin stays at 865 lines), writing through B0's schemas. Export writes, beside `ACTIVE_PLAN`: `plan.md` (the `## Implementation plan` section only; warn above 30 KB), `review-record.md` (the `## Review record` section and decision audit trail), `tasks.jsonl` (the aggregated per-phase tasks, same schema as today's `tasks-<phase>-*.jsonl`), `decisions.jsonl` (every auto, taste, user-challenge and gate item with `id`, `phase`, `kind`, `recommended`, `options`, `cost`, `status`), `findings.jsonl` (every finding with `phase`, `voice`, `model`, `severity`, `file`, `line`, `title`, `resolution`), `timing.json` (per-phase start/end, outside-voice wall time, total; see B10) and `run.json`. One executable owns the unattended loop as an explicit, durable state machine: `bin/gstack-autoplan` (thin, over `lib/autoplan-run.ts`). On a host where the parent dispatches the native reviewer itself (Capy: the native voice is a subagent the parent starts from the prompt file, exactly how this review ran), the loop is `next` and `submit`: `next --out <dir>` prepares the current phase (snapshot, methodology, prompt), persists the dispatch intent with an attempt id, prints the voice, the prompt path, the expected result path and the model family required, and exits with `GSTACK_RESULT: status=awaiting_result`; `submit --out <dir> --phase <p> --voice native|outside --result <file> --model <id> --attempt <id>` validates the result (full-read receipt, canonical findings, model family), binds its hash into `run.json` atomically, runs the B11 reconciliation and `phase-close`, and advances. `run --plan <file> --out <dir> [--runner ...] [--outside-model <id>] [--deadline <min>] [--spend-cap <usd>] [--plan-heading <text>]` is `next` in a loop and exists only when a runner can execute both voices itself. `status --out <dir>` prints the manifest. `answer --out <dir> --gate-rev <rev> --reply "<grammar>"` rejects a stale, conflicting or partially parsed reply atomically with no writes; applies the choices; and when a non-recommended choice changes reviewed content (scope, architecture, test plan), recomputes the affected review-input identities and reopens the dependent phases with Eng last, the same rule today's option D follows, instead of finalizing a plan no reviewer evaluated. Only a reply that changes nothing reviewed finalizes `plan.md` and `run.json.status` without another paid phase. `resume --out <dir>` takes exclusive ownership of the run (a second writer is refused), reconciles any attempt whose dispatch intent exists without a terminal record as `EXECUTION_UNKNOWN` (the provider may have finished and charged; snapshot hashes prove input identity, not completion), and never redispatches an ambiguous attempt automatically. `--deadline` cancels every child the runner started; spend by a host-dispatched subagent is reported as `unknown` in the ledger, not as zero. The minimal shared spend ledger (`lib/spend-ledger.ts`: atomic `spent + reserved + next_estimate <= cap` under a file lock, reservations retained for unsettled attempts after a crash) lands here, not in PR E, because `--spend-cap` needs it. `--out` must be a directory the parent chose (durable or not, reported per artifact class); nothing is written beside `ACTIVE_PLAN` by default. The final approval gate in unattended runs is written as a gate list (B9) into `decisions.jsonl` and printed; the run ends with `GSTACK_RESULT: skill=autoplan status=gate_pending run=<dir>`. Lanes read `tasks.jsonl`, not the plan. `docs/unattended.md` is the parent-agent quickstart: install, run, validate, answer, ack, five copy-paste commands, linked from the user-scope Capy skill. Each run also appends `session_kind` and, when the parent reports it, `consumer` to `analytics/autoplan-timing.jsonl` (P7).
- `cso` in unattended runs prints `GSTACK_RESULT: skill=cso status=<...> run=<dir>` at exit (the run directory holds `findings.jsonl` and the run id), and `gstack-doctor --check` lists the CSO native helper as an optional component (`SKIP cso (toolchain absent)`, never silent).
- A plan with any layout is accepted (already true: snapshot `init` wraps raw text; document it in the headless section) and `--plan-heading <text>` names a custom heading when a repo's plans use one.
- Acceptance: this plan, reviewed on this machine with no Claude Code harness, produces the files; a parent agent runs `gstack-artifact validate run.json`, lists pending decisions with `jq 'select(.status=="pending")' decisions.jsonl`, and never reads the record. Interrupted-run, missing-reviewer and omitted-finding fixtures fail validation; `test/autoplan-run-recovery.test.ts` kills the runner before and after result persistence and asserts no automatic duplicate charge; `test/autoplan-run-concurrency.test.ts` covers two writers, revision conflicts and exactly-once `ack`; `test/autoplan-answer-invalidation.test.ts` proves a scope-changing answer reopens only the affected phases and a stale reply writes nothing. New bash bins and their tests are classified for the Windows subset (`scripts/test-free-shards.ts --windows-only`).
**B3. A pluggable outside voice, with a model parameter.** The outside voice becomes a runtime choice, not a render-time one: `outsideVoiceFor(ctx)` picks the provider when `gen:skill-docs` renders (scripts/resolvers/outside-voice.ts:8-12) and the rendered prose shells out to `codex` directly (scripts/resolvers/outside-voice-steps.ts:177), so a per-run `--runner` cannot live there without rendering three branches into every zero-headroom skill. New `bin/gstack-outside-voice run --runner <r> --prompt <file> --out <file> [--model <id>] [--result <file>]` over `lib/outside-voice-runner.ts`; the resolver names the bin and the runner table once. Every runner implements one adapter interface: declared capabilities (repository access or supplied-input only), input digest, actual model metadata, cancellation, terminal result and usage; the `api` runner either runs a bounded repository tool loop or is labeled `supplied-input review` in the record, never silently equal to a repo-reading review. Runners: `codex-cli` (today's path), `api` (a direct call with `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` through new `lib/outside-voice-api.ts`, model from `--outside-model`), or `host-subagent` (the host runs a subagent on another model family from the paste-ready prompt and returns its output file; the parent passes `--outside-result <file> --outside-model <id>`). A completed `host-subagent` or `api` review is a full outside voice: `OUTSIDE_STATUS: completed provider=<runner> model=<id>`, never `[single-model]` or degraded, and the model id is recorded in `findings.jsonl` and the voices log row. A missing Codex CLI is reported as `codex-cli unavailable; using <runner>` only when another runner is configured; with none, today's unavailable text stands. New `bin/gstack-codex-login`: `printenv OPENAI_API_KEY | codex login --with-api-key`, then `codex login status`, exit non-zero with the probe's hint on failure; `outsideVoiceFailurePolicy()` (outside-voice.ts:217) and the doctor codex row (bin/gstack-doctor:186) name it as the unattended remedy. `--outside-model <id>` sets `GSTACK_CODEX_MODEL` for the CLI runner (bin/gstack-codex-probe:144-147 already honors it). The outside voice stays on by default (`codex_reviews: enabled`). The same-family rule holds for every runner: the outside model must differ in family from the native reviewer, and the record says which was which. Runner selection is decoupled from host identity (today `outsideVoiceFor` picks the provider from the harness, scripts/resolvers/outside-voice.ts:8-12), so a Codex-host render can still receive an outside voice from a Claude subagent and a Capy run can use either. The Capy dispatch path (a Capy subagent reading the native prompt file with its own file-reading tool) is the one this plan's own review ran on; the PR body records it.
**B4. Condensed operative checklists.** Each review skill gets `sections/checklist.md.tmpl`, at most 200 lines, holding the executable steps and required outputs of that phase; the deep methodology stays in `review-sections.md`. In unattended runs and under an explicit `light` mode the phase loads the checklist plus only the deep sections the detected scope needs (the manifest gains a `scope` field per section: `always`, `ui`, `dx`, `db`, `perf`, `security`, `incident`), and prints one index line: `Skipped sections: <ids> (scope: <detected>)`. Interactive runs are unchanged. `nativeDispatchPrompt` already exists in `snapshot.json`; `bun gstack-autoplan-snapshot prompt <phase>` prints it so a parent can paste it to a subagent. The manifest's `scope` field is optional with `always` as the default, its "passive, no machine predicate" note is updated with the schema, and the only consumer is the runner (interactive renders ignore it; `test/qa-lazy-sections.test.ts:127` keeps asserting passive resolution for them). A checklist is "replace, never add": the methodology bytes it summarizes leave the eager text, and an obligation-to-section map (every required output names the section that produces it) proves nothing required was dropped, with a conservative fallback that loads the deep section whenever scope detection is uncertain and behavioral negative controls for omitted obligations. A test caps every checklist at 200 lines and checks the obligation map, not just the section count.
**B7. Review-log status derived from findings.** `reviewFreshness` already downgrades a `clean` row with open issues to UNVERIFIED at read time (lib/review-evidence.ts:238-240); the gap is at write time. `bindReview` (lib/review-evidence.ts:178-186, the single choke point) accepts `--findings <path>`: it derives `status` (`clean` when no finding is open, else `issues_open`), `unresolved`, `issues_found` and `critical_gaps` from the file and rejects a row whose claimed values disagree. The row carries separate `findings_total`, `findings_open` and `findings_resolved`; `reviewFreshness` (lib/review-evidence.ts:238) is updated in the same change to read `findings_open`, since today it rejects any row with `issues_found > 0` even when every finding is resolved. A rejected row exits 1 with `review-log: status mismatch: claimed clean, findings.jsonl has 176 open (REVIEW_STATUS_MISMATCH)` on stderr (the wrapper drops the `2>/dev/null` at bin/gstack-review-log:95 and stops converting binding failures into `invalid JSON`); usage becomes 2 (today 1, :81-84); the legacy `critical` and Codex counters keep their conservative reading. Rows written without `--findings` are tagged `status_source: claimed`. Unattended autoplan always logs through `--findings`. Phase subagents that cannot write the JSONL return findings to the parent, who writes `findings.jsonl` and logs from it; the hand-written `status: clean` beside `issues_found: 176` is rejected at write time.
**B8. Spec-review loop at the cap.** The loop stops today on PASS, repeated issues or the third launch, and a missing score alone does not force another launch (scripts/resolvers/spec-review.ts:182-192); the 1-10 score is reported but not a stop rule. At the cap it now prints the unresolved gaps as a numbered list and takes one of two recorded paths, never a resample of unchanged input: a deterministic issue-verification pass that checks only the listed issues after their fixes are applied and is recorded as `issues verified: k of N`, never promoted to a full-review verdict and never a fourth model launch past the cap (unattended default), or `N fixes unconfirmed` with the approval gate blocked by a `spec not re-verified` decision. An interactive user may raise `--max-rounds`, and each extra launch requires a changed input hash. `DONE_WITH_CONCERNS` always names the missing points next to the score.
**B9. One gate list instead of four-option sequences.** New `lib/gate-list.ts` with `bin/gstack-gate` (`render`, `parse`): a gate list is numbered items each with a recommended default and lettered options, and one reply grammar: `all` (every item takes its recommendation), or `<id><option>` tokens (`d3b uc1a`), or `all except <tokens>` (every item takes its recommendation except the listed overrides; the two forms are the same operation, so there is no exclusion-vs-override ambiguity). Bare `yes`/`no` are rejected as ambiguous across multi-option items and reported as unparsed. Every item has a stable id that survives pagination (a list longer than seven items is rendered in pages with the same ids), items are marked `auto` (taken if unanswered) or `approval` (stays pending until answered; the owner's direction stands), and a reply binds to the gate revision it answers (`gate_rev` in `decisions.jsonl`); a reply against a stale revision is refused with the current one. The rendered list prints the exact reply shape it accepts and the resolved JSON it will produce; `parse` returns the resolved choices as JSON and lists unparsed tokens instead of guessing. autoplan's Phase 4 renders the gate through it (interactive and unattended); CEO Step 0 taste decisions join the same list instead of being asked mid-run. The owner replies quoted in the feedback (`d10 yes`, `1a 2a`, a 33-decision approve-all) are parse fixtures.
**B10. Wall time and a pre-run estimate.** `phase-close` records phase start and end, outside-voice wall time and native wall time to `timing.json` and to `$GSTACK_STATE_ROOT/analytics/autoplan-timing.jsonl`. Phase 0 prints an estimate from plan bytes and detected scope using the recorded history (median of the last 10 runs per phase, `no history` when empty), and the final report prints the actual whole-cycle time next to it.
**B11. Reconciliation when outside passes run beside native ones.** The outside Bash dispatch may start while the native reviewer runs (both bind to the same snapshot), and `phase-close` gains an explicit numbered operation: for each outside finding record `confirmed`, `disagree` or `new` against the native result, and for the Eng dispatch verify the prior-phase consensus summaries are present in the prompt before sending (a test fixture with a missing CEO summary is refused). The completion barrier and phase order are unchanged.
**B12. URGENT, outside this plan.** The final report and `plan.md` get a fixed first block `## URGENT, outside this plan` listing security or data-loss findings any voice raised that the plan does not own, each with a suggested owner and the finding id; `None` when empty. A test asserts the block precedes the summary.
### PR C (C-stamp, C-pregate): queue-aware ship and the pre-gate (P0-3, P0-4)
**C1. A repo ship policy, machine-readable.** New pin file `.gstack/ship-policy.json` (schema in `lib/ship-policy.ts`, containment-checked like `.gstack/version-path`): `bump` (`patch` | `ask`), `release_tool` (for example `bun run release:restamp`), `stamp_paths` (globs where `(was X)`, `since:` and version mentions are rewritten), `changelog` (`CHANGELOG.md`), `mirror` (path of a paired repo to restamp identically), `history` (`merge-only` | `squash-ok`), `preregistration_shas` (must be ancestors of HEAD), `pr_count` (`default_pr_count`, `split_requires_owner`; consumed by D1). `gstack-version-bump classify` returns `bumpPolicy` from it, and Step 12 skips the MINOR/MAJOR prompts under `bump: patch`. Without the file nothing changes.
- Trust boundary for every pin file that names a command (`release_tool`, `.gstack/generated.json`, `.gstack/pregate.json`): file presence never authorizes execution. `gstack-restamp`, `gstack-regen` and `gstack-pregate` run repo-declared commands only when invoked from `/ship` or `/land-and-deploy`, or with `--allow-repo-commands`; standalone they list the commands and exit 3 with `repo commands not executed (pass --allow-repo-commands)`. Commands are read from the committed file on `origin/<base>`, not the working tree, so a PR cannot smuggle a command into its own gate; `mirror` writes are confined to the declared path and refused outside it. This is the opt-in posture `gstack-version-bump --regen-digest` already takes (bin/gstack-version-bump:373-385). Pinning the command string does not freeze its implementation (`bun run release:restamp` still executes the PR's package script), so the record names the policy revision, the command, its working directory and the trust scope. Each tool has `init` (writes a commented example with `$schema` and every key at its default), `validate`, and `show-effective` (the resolved configuration with the source revision of each value); `docs/ship-policy.md` shows gstack's own files. A PR that introduces or changes a policy file runs under the reviewed bootstrap path: `--policy-from head` is accepted only with `--allow-repo-commands`, and the receipt records `policy: head (unreviewed)` so the coordinator sees it. A refusal happens before any write and says `nothing written`. When `origin/<base>` is unresolvable (shallow or detached gate checkouts), the tool exits 3 with `policy source unavailable — fix: git fetch origin <base> --depth=1 (POLICY_SOURCE_UNAVAILABLE)` and never falls back to the working tree silently. Every repo-controlled input (`mirror`, `stamp_paths`, `generated.json` commands, `pregate.json` guard patterns, `banned-terms.json`) has named containment fixtures: no `..`, no symlink escape, globs confined to the repo, regex size caps; `gstack-regen --check` is code execution even in a scratch worktree and sits behind the same opt-in. The policy schema includes `queue_mode`, and examples live in sibling `*.example.json` files or `init --explain`, since JSON carries no comments.
**C2. `gstack-restamp`: the whole stamp in one step.** New `bin/gstack-restamp` over `lib/restamp.ts` and `lib/changelog-check.ts`. Modes (`--queue-mode stamp-at-merge` is the umbrella: no claim at PR-open time, stamp only when told the PR is next): `--version X`, `--next` (under stamp-at-merge: the configured version source at the base revision through `lib/version-source.ts` (explicit version paths, pinned manifests, three- vs four-component versions and no-version repos are the resolver's existing cases, lib/version-source.ts:135) plus the policy bump, nothing else; the claim reader stays for the legacy claim-at-open mode and the output says `claims: ignored (stamp-at-merge)`), with `--expect-base <sha>` and `--expect-order <pr,pr,...>` so the allocation records the base SHA and queue order it assumed and re-validates both immediately before merge, returning a structured `QUEUE_STALE` result with the exact recomputation command when either moved; the allocation and the receipt bind to repository, base SHA, the ordered predecessor chain, the PR head and the synthetic integration tree, and the merge itself is serialized with server-enforced conditions (the GitHub merge API's expected head `sha`) so a check-to-merge race is rejected by the server rather than assumed away, and where the host cannot enforce the condition the coordinator recomputes after the merge and the receipt says so, `--after <pr>` (gate-ahead: build the merge of the branch and the predecessor's head in a throwaway worktree, stamp N+1 relative to the predecessor's version there, gate there, and record the predecessor head in `.gstack/tmp/gate-ahead.json`; the PR branch's history is never touched, so squash-history PRs stay clean; `gate-ahead.json` records the evidence branch key, because review and evidence ledgers are keyed by branch (bin/gstack-review-log:18-22, bin/gstack-evidence:13) and a synthetic worktree branch would otherwise grade MISSING on the real PR), `--base <ref>`. It runs `gstack-version-bump write`, the policy's `release_tool`, re-heads the CHANGELOG (the PR's section moves to the top under the new version, above its predecessor's; duplicate headings and a top entry not equal to VERSION are errors), rewrites `stamp_paths` mentions (exact `(was X)`, `since: X` and `vX` patterns only, within the declared globs, shown as a dry-run diff before writing), regenerates the generated-file registry (C4), repeats everything in `mirror`, and prints the diff limited to stamp files. Idempotent: a second run with the same version is a no-op; `--dry-run` prints the full diff and writes nothing. The release tool runs under a contract: in an isolated staging worktree, with an enumerated allowed-output set, recording preimages and postimages, applied to the branch only after validation; an output outside the allowed set is a refusal, and rollback refuses to overwrite a file whose current bytes differ from the recorded postimage (a later user edit) and stops with a conflict. Every restamp journals its intended inputs and completed writes (`.gstack/tmp/restamp-journal.json`) so a failure after any stage resumes or rolls back without touching unrelated edits (the version writer already has a half-write state, bin/gstack-version-bump:489; the journal extends it across the release tool, the CHANGELOG, `stamp_paths` and the mirror), with injected-failure tests after every stage. `--queue-mode` defaults from `ship-policy.json`, else legacy, so the flag is not required on every call. Tier 1: `--version`, `--next`, `--after` (the gate-ahead is what makes a reorder cheap, so the P3 promise needs it), `release_tool`, CHANGELOG re-heading, `stamp_paths`. Tier 2: `mirror`. Considered and rejected: stamping on main after merge with a release commit. The gate must run on the stamped tree (CHANGELOG and version live in the PR, and a post-merge commit is itself unreviewed), which is why the owner's requirement is stamp-at-merge.
**C3. The tree-equality receipt.** New `bin/gstack-tree-receipt` (lib/tree-receipt.ts): prints gated head, current head, both `HEAD^{tree}` hashes, both `gstack-wtree` fingerprints, the diff limited to stamp files, the predecessor head the gate assumed, and one verdict line. Inputs are explicit: `--gated <sha>` and `--predecessor <sha>`, or `--from .gstack/tmp/gate-ahead.json`; `--json` emits the receipt as an object. Gates compare committed heads only (a working-tree fingerprint can name objects unreachable once the VM is gone, bin/gstack-wtree:24). The diff is classified hunk by hunk, not path by path, with per-file-type rules: lockfile rules match only the root package's `version` keys (where `syncNpmLockfiles` writes, bin/gstack-version-bump:486); `stamp_paths` rules match `(was X)`, `since: X` and `vX` only when `X` equals the old version and the replacement equals the new one, so a dependency pin that happens to equal the old version classifies as `changed` (a named negative fixture); a CHANGELOG heading moving is permitted; any other changed byte counts as a change (bin/gstack-evidence:505 compares allowed filenames today, which would let a changed generated prompt pass; npm lockfile metadata changes on every stamp, bin/gstack-version-bump:486, which would otherwise fail every restamp). Equivalence is defined per reusable gate, not once: a gate that consumes VERSION or generated bytes (the agents-digest freshness test does, test/agents-digest.test.ts:22) is stamp-sensitive and always reruns on the final stamped tree; the receipt lists which gates are reusable and which reran, keeping both the raw and the normalized identity. The verdict names the residual hunks: `TREE: same-modulo-stamps` or `TREE: changed (<residual paths>)`. Tree identity alone is not gate evidence: the receipt also records the identity the earlier gate ran under (runtime pins, lockfile hash, CI workflow hashes, the gate command's `cmd_sha256` from `gstack-evidence`) plus the executed test selection and the container or runner image when known) and prints `gate-reuse: eligible` only when every one matches, else `gate-reuse: not-eligible (<what changed>) — fix: <rerun command> (GATE_REUSE_NOT_ELIGIBLE)`. The minimum portable evidence lands in this PR, not in E: `gstack-evidence bundle` exports the comparison material itself (the committed tree id and the stamped-file normalization inputs), the complete required-lane manifest, executed selections, source run URLs, outcomes, artifact hashes and producer-bound run metadata, distinguishes imported assertions from independently verified evidence, and refuses reuse when execution identity is unknown or comparison objects are missing; the receipt reads a bundle from a fresh coordinator machine after the gate VM is gone, without rerunning the gate (`gstack-evidence` is machine-local by design, bin/gstack-evidence:27-38, so the bundle is the portable form). It refuses `eligible` when the recorded predecessor head is no longer the predecessor PR's head (fetch and compare) and prints `PREDECESSOR MOVED <old> -> <new> — fix: gstack-restamp --after <pr> again (RESTAMP_PREDECESSOR_MOVED)`. `/ship` Step 16 and `/land-and-deploy` consult it; when the receipt says eligible, a repeated full gate needs `--force-gate <reason>`, and the coordinator makes the call.
**C4. Generated files regenerate, never hand-merge.** New pin `.gstack/generated.json` (`[{paths, command}]`) and `bin/gstack-regen` (lib/regen.ts) with two verbs: `--check` runs each command in a scratch copy and reports stale entries without touching the tree (the pre-gate's freshness check), and `--write` regenerates in place (what the restamp and the conflict rule use, where changed output is the point, not staleness). Step 3 conflict rule: for a path in the registry take either side, then `gstack-regen`; the agent never hand-merges a generated file. Used by C2, the pre-gate (C8) and Step 16 stage 1. gstack's own entry is `bun run gen:skill-docs --host all` plus the agents digest and llms files.
**C5. The handoff record, identifiers first.** New `bin/gstack-ship-receipt` renders a fenced ```` ```gstack-ship-receipt ```` block: `pr`, `head`, `version`, `base`, `gated_tree`, `gate` (lanes with pass/fail/skip counts), `ci` (check counts), `spend_usd`, `predecessor`. The block carries `schema_version` and is validated by `gstack-artifact`. It is the first section of the PR body and the first lines of the completion message to the coordinator, so truncation loses prose, not identifiers. `/ship` Step 18 writes it; `/land-and-deploy` reads it with `gstack-ship-receipt read --pr <n> --json`.
**C6. Issue links and flake issues.** Step 18 adds `Fixes #N` only on explicit closure intent: a `Fixes #N`/`Closes #N` trailer in a commit or an explicit `--issue N`, never a bare mention (a context reference, a partial fix or a deferred ask is `Refs #N`); it reads each through `gstack-issue-guard`, and maps failing CI run ids to bot-opened flake issues (search issues whose body names the run id) as `Flake: #N (run <id>)`. Spec-archive links keep today's rules.
**C7. History policy and preregistration ancestry.** Under `history: merge-only` `/ship` refuses a squash or rebase plan and says why; `gstack-evidence ancestor <sha>` checks each `preregistration_shas` entry is an ancestor of HEAD before push, and the receipt records the result.
**C8. `gstack-pregate`: cheap checks before anything paid.** New `bin/gstack-pregate` over `lib/pregate/*.ts` (one file per check, each under 150 lines, registry in `lib/pregate/index.ts`). It runs at the entry of ship Step 5, before Step 6's paid evals and before any remote gate, under `set -o pipefail`, aggregates exit codes itself, and prints a table plus `pregate.json`. A failing check blocks; a repo downgrades a named check to `warn` in `.gstack/pregate.json`, except `secrets` and required-lane obligations, which cannot be downgraded. Every check records the input identity it ran against (the tree, the generated outputs, the selection) with the same before/after fingerprinting `gstack-evidence` uses (bin/gstack-evidence:394), and publication requires results that are current after final generation and stamping; a timed-out or tool-unavailable check is `incomplete`, never `pass`. The lane list and platform tiers come from the repo's `.github/workflows/*.yml` (the same parser as `project pins`), never from a pre-gate-owned taxonomy, so a changed CI matrix changes the pre-gate. Two stages with separate clocks: the mechanical preflight (`regen --check`, `secrets`, `strays`, `literals`, `links`, `lint`, `patches`, `ratchets`) is the two-minute promise, measured on gstack and gbrain and printed per check; test execution (`lanes`, `guards`, `hermetic`) is the second stage with its own measured time, never folded into the first. Lane discovery reads what the workflows actually encode and defers to runner-owned selection manifests where the matrix is computed by a planner (gstack's Windows matrix comes from `scripts/test-free-shards.ts`, .github/workflows/windows-free-tests.yml:62, and exclusive classification lives in TypeScript, scripts/test-free-shards.ts:243); `--explain` prints each selected test's provenance and each uncertain mapping. A `requires-remote` entry names the workflow and job to dispatch and the receipt that clears it. Tier 1 (this PR, each with a named incident fixture): `regen`, `secrets`, `strays`, `literals`, `lanes` with the pipefail fix. Tier 2 (same PR if green, else a follow-up pre-gate PR): `guards`, `ratchets`, `lint`, `links`, `hermetic`, `patches`, `integrator`. Checks:
- `regen`: every `.gstack/generated.json` entry fresh (C4, `--check` in a `git worktree` scratch, never a copy of a tree with `node_modules`), including goldens outside the repo's regen script when listed there.
- `guards`: new exported interface members, CLI commands, error codes and migrations detected from the diff (patterns in `.gstack/pregate.json` `guards`), each mapped to the guard tests it must run (CLI contract rows, `--help` coverage, canonical-writers census, upgrade replay, serial-files manifest); runs them and reports which ran.
- `ratchets`: module-size and any repo-declared ratchet test file; on a failure it prints the exact fixture entry the repo's convention would need (an interface-member growth needs a registered `movedFrom`/`reason` line; the documented exception is for data tables and generated literals, test/helpers/module-size.ts:225) and leaves the append to the author, so the ratchet is never auto-exempted.
- `lint`: semgrep, gitleaks, actionlint and shellcheck when installed and when touched paths match; absent tools print `not installed`, never PASS. `secrets` runs `gstack-redact` over the branch's added lines regardless.
- `links`: relative links and anchors in changed markdown resolve to tracked files (lib/md-links.ts).
- `lanes`: maps touched source files to the test files that depend on them through `lib/pregate/import-graph.ts`, which unions static imports, repo-path string literals (`test/gstack-doctor.test.ts` spawns `bin/gstack-doctor`; no import names it), the `bin/<name>` → `test/<name>*.test.ts` convention and explicit `.gstack/pregate.json` dependency declarations for subprocess targets, runtime file reads and generated inputs; an uncertain mapping selects conservatively (more tests, never fewer) and a touched file with no lane is `FAIL lanes — <file> has no test lane`, not a pass (`test/helpers/touchfile-closure.ts` follows only helpers and fixtures and calls itself a lower bound, so it is the precedent, not the implementation). `requires-remote` obligations are persisted in `pregate.json` and the ship receipt, and publication requires their matching receipts; printing the line is not enforcement. It classifies them serial, E2E, platform or unit from the workflow files, runs the local ones, and prints `Tiers ran: unit serial; requires-remote: windows`. It blocks only on lanes that can run here (a serial file under the touched area that did not run); a platform lane this machine cannot run becomes a `requires-remote` entry the gate must include, so a Windows-affecting change on a Linux box has a forward path and never merges with that lane unrun.
- `literals`: for every changed exported constant, grep the whole test tree, nightly-only and Heavy-only files included, for the old literal; for every changed spawn, retry or timeout call, grep for spies and assertions on it; for every migration, grep SQL fixtures that DROP or ALTER the objects it touches.
- `hermetic`: new and changed test files run once through new `bin/gstack-hermetic-run` over `lib/hermetic-env.ts` (the builder moves out of `test/helpers/hermetic-env.ts:112`, which keeps a re-export, because a shipped bin must not import test code), which allocates an isolated `HOME`, sets `GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_NOSYSTEM=1` (today's helper keeps `HOME` and accepts overrides after scrubbing, test/helpers/hermetic-env.ts:49, :147), and distinguishes an isolation failure from a fixture that legitimately needs an identity; a planted home file and a global git identity are the fixtures.
- `strays`: files added at the repo root or outside the usual directories, warning on `zz-*`, `tmp*`, `scratch*`.
- `patches`: for `patchedDependencies`, the key matches the installed version and every hunk in the committed patch is present in a regenerated one; dropped hunks are listed and each needs explicit confirmation.
- `integrator` (`--after-merge`): doc size caps, ratchets, registry goldens and manifest freshness after each lane merge, with the ratchet-conflict rule printed: re-sum to the measured count and keep both notes.
- The Step 5 lane example in `ship/sections/tests.md.tmpl:26-30` collects PIDs and `wait $pid || fail` per lane; every recorded command carries `pipefail`; nothing judges success from `tail`.
- Acceptance: a fixture per incident in the feedback (stale goldens, scratch script, Heavy-only old constant, Windows-only change without its lane, dropped vendored hunk, `| tail`-hidden verify failure) is caught locally before any remote gate starts.
### PR D: plan rows, sideways review, investigate recipes (P1-5, P1-6, P1-7)
**D1. Reality rows in plan reviews.** New resolver `scripts/resolvers/plan-reality.ts` renders `{{PLAN_REALITY_ROWS}}` into plan-eng-review's Scope Challenge, plan-ceo-review 0A/0B, the autoplan CEO and Eng phase files and the Pre-Gate table. In unattended runs a missing applicable row sets the phase `incomplete` (not a warning); interactive runs keep the two-repair-then-warn rule. Tier 1 (no scope detection needed, demonstrated by this plan's own review): the premise table, already done, surface check, binding decisions, numbers, deferred asks, PR count. Tier 2 (need a scope signal the manifest's `scope` field (B4) supplies: `db`, `perf`, `search`, `incident`): transaction semantics, recall, units, perf table, falsification box, stop reasons, reporter environment, prior art, contract assumptions, dependency fallback, obligation tiers. Rows, each with a one-line incident note in the deep section:
- Premise table: `premise | claim | file:line receipt | verdict`; every factual claim in the draft gets a receipt or the tag `unverified`, and the count of unverified claims is a scored line in the Completion Summary.
- Prior art: for a vendored or patched dependency or a known library behavior, search the upstream tracker and open PRs, then record `adopt <link>` or `diverge because <reason>`.
- Transaction semantics (driver, pool or protocol change): what happens to the surrounding transaction on idle, pooled and reserved connections; crash robot reruns after the change.
- Binding decisions: read `gstack-decision-search --scope branch --recent 50` and a `## Binding decisions` section of the plan; they outrank project defaults and CLAUDE.md, and a reintroduced overruled default is a finding.
- Already done: `git fetch origin <base>` then `git log origin/<base> --since=<ask date> -- <named files>` and `--grep` for the named symbols; a hit is reported before any task is written.
- Surface check: every named CLI command or HTTP route verified against the registry or `--help`; a miss is a finding.
- Contract assumptions: a row per default or flag assumed from another repo, rechecked against merged code before the next stage.
- Numbers: every number tagged `[measured: <source>]` or `[estimated]`; cited baselines tagged `[re-measure before gate]`.
- Falsification box (incident or outage plans): top hypothesis, the evidence that kills it, a time box, and the plan's action in each branch.
- Stop reasons: every new stop reason, hold code and progress line names the test that triggers it.
- Reporter environment: a repro that does not match the reporter's environment is `class not confirmed` and may not say `ruled out`.
- Units: a diff that changes an input's units or accuracy lists every threshold that consumes it and shows the re-tune.
- Perf table: machine, engine, data size, N, p50/p95, statistics state, base and branch interleaved, pathological and steady-state cases, `reproduced on main?`.
- Recall: a search or query-plan change reports recall against exact search across scope buckets.
- PR count: read `pr_count` from `.gstack/ship-policy.json` (C1); default one PR per wave; a proposed split shows the queue cost per extra PR and checks user-facing references across the split.
- Dependency fallback: what ships without each predecessor and by when.
- Deferred asks: the linked issue's requests the plan consciously leaves out, each with its reason, listed in the gate template so a reviewer sees them.
- Obligation tiers: must-ship, next PR, deferred, under a stated size budget; the ledger status column carries the tier.
**D2. Review and cso look sideways.** In `review/checklist.md` and `review/SKILL.md.tmpl` Step 4: for every finding grep the same pattern at every other ingress, command and call site and rate severity on the worst sibling; a forced-CRITICAL list (data loss, trust-boundary bypass, uncapped spend) that no lane can downgrade; for a new cache in front of a guarded or metered call, name the policy checks a hit skips; when imports move from static to dynamic, list top-level side effects no longer loaded and who reads each registry; when a function changes, find modules that claim parity with it (docstrings, parity tests) and flag unchanged ones; flag pool queries awaited inside a caller's transaction on hot read paths. The plan-completion resolver (scripts/resolvers/plan-gates.ts:464-490) adds an `Undeclared behavior changes` line listing diff behavior the author's summary omits, and the approval step refuses `approved` without a recorded read of the full diff. Test-only flake fixes require a number: the probability the test still fails on a real regression, before and after (shared resolver with D3). `/cso` adopts the sibling sweep for its findings and nothing else.
**D3. Investigate recipes and backlog mode.** `investigate/SKILL.md.tmpl` gains lazy sections (new `investigate/sections/` with a manifest): `flake` (reproduce on main; failure rate over N runs; cause inside or outside the PR; compare the metric with the last N green runs of branch and main via `gh run list`; forced probe that fails before and passes after; never loosen; a rerun only when the cause is known and outside the PR; a required `## Flake evidence` PR-body section with the probe's before and after output and a routing note for the owner; reruns through `ship-measure`'s discipline, paid evals never retried), `polluter` (file order from the CI log, bisect the earlier files with `ship-measure free --files`, report the polluter and what it leaked), `race` (timing probes, forced interleaving, concurrent-writer template), `eval-bisect` (distinct revisions, a cached cheap scorer, detached runs), and `backlog` (`investigate --backlog`: each issue classified `still_open`, `already_fixed <commit>`, `partially_fixed` or `owned_elsewhere`, a reproduction required for `still_open`, contributor PRs counted as evidence only, read-only output table, no issue closing). Phase 1 adds: read call sites before calling two queries duplicates; `git update-index --refresh` before timing anything that spawns git in a benchmark worktree (`benchmark/SKILL.md.tmpl` too).
### PR E: `/eval-plan`, coordination tools, owner brief (P1-8, P2-9, P2-10)
**E1. `/eval-plan`: preregistered, paid evals for the user's product.** New skill `eval-plan/SKILL.md.tmpl` with sections, generalizing gstack's internal machinery:
- Preregistration checklist: bars, arms, decoys, stop rules, and a held-out exposure section listing every held-out set the plan touches, its prior exposure and reservations.
- Scorer dry run on dev data with measured accuracy per scorer and a stop when any is below its bar (lib generalization of `scripts/judge-calibration.ts` `compare()` over user JSONL).
- `$0` stub-model dry run over real input shapes, plus the checklist: price-table entry present, output budget sized for reasoning models, the retry prompt's actual text. `PRICING` moves to `lib/pricing.ts` (re-exported for tests).
- Priced pilot (n≈3) with extrapolation and an error band before the approval ask.
- Budget ledger `lib/spend-ledger.ts`, extracted from `scripts/ship-measure.ts`'s admission logic and made honest about what it can promise: admission is estimated (the price table excludes some billing categories, test/helpers/pricing.ts:4, and actual cost reconciles after each call), the ledger refuses a new call when `spent + reserved >= cap` under a file lock shared by concurrent workers, and the spend-vs-cap table regenerated at every gate shows `spent`, `reserved`, `unknown charges` and the worst-case overrun (one in-flight call per worker). `budget_exhausted` stops the run.
- Arms from the project's model rules (`gstack-models resolve --role eval-arms --json`, falling back to the repo's own policy doc) with per-arm cost shown.
- Instrument audit: read every failing transcript of one dev round before freezing the scorer; check generated worlds for duplicate identifiers, word-like codes and generic phrases; mutation tests in both directions.
- Steady state: background queues empty at snapshot time, queue depth recorded as a gate.
- Sealed paths declared in the plan; the skill refuses to quote from them, self-checks output for n-grams from them and adds a provenance line.
- Long runs persist off the machine: `gstack-detach --sync-cmd <cmd> --sync-every <min>`.
- Memory preflight: concurrency times measured per-process memory, refuse above 70% of RAM.
- Power and risk sentences cite the committed power output, with a sensitivity table over the obvious rule alternatives (lib generalization of `scripts/lib/measure-bar.ts`).
- QA soak: `qa/sections/soak.md.tmpl` runs hours of mixed ingest against a long-lived server with a watchdog for `CPU pinned, no progress`.
- Tiers: tier 1 is the preregistration checklist, the `$0` stub dry run and the priced pilot with the ledger (the three steps behind the spend incidents); tier 3 (after P7) is the instrument audit, steady state, sealed paths, off-machine persistence, memory preflight, power tables and QA soak.
**E2. Coordination tools.** `bin/gstack-lane-check` (lib/lane-ownership.ts): given in-flight branches and PRs, diff each and flag planned files that intersect, with the PR number and hunk count. `gstack-evidence bundle` (shipped in C-stamp, C3) gains `gstack-evidence verify <bundle>` here, which reruns the probes or marks each claim `verified` or `unverified`; E1's priced pilot consumes the `lib/spend-ledger.ts` that B-runner ships (B2), adding the pricing table extraction (`lib/pricing.ts` from test/helpers/pricing.ts:4, whose exclusions are documented) rather than a second ledger. `{{COORDINATOR_CONTRACT}}` resolver: a block in the plan template (merge conditions, behavior-change notices, spend caps, handoff format) that every lane's subagent prompt inherits word for word; the prompt printer (B4) includes it. Cloud VMs: the repo's `scripts/ubicloud/ubi-runner.sh` adopts the user-scope skill's owner tag, `list --mine`, `usage`, own-VMs-only `gc`, record-before-create teardown, plus a VM-id scratch file under `~/.capy/work` and a resume checklist in docs/TESTING_INTERNALS.md; long benchmarks and gates documented as watched background operations, one before/after pair per machine, with a warning before profiling very large imports. `test_backend` config (`local` | `ubicloud`) with the doctor `cores` row (A1) recommending `ubicloud` under 8 cores for full unit and E2E tiers. `ship/sections/handoff.md.tmpl`: the no-PR automation handoff (verify-only checks, branch, SHA, inputs, before/after metrics, escalation rule). The Capy skill says to store and use full thread ids in cross-thread messages.
**E3. What the owner sees.** autoplan Phase 4 defaults to a one-page brief: plain-language summary, the `URGENT, outside this plan` block (B12), and the numbered decisions rendered through the gate list (B9): seven per page with stable ids, `auto` items showing the default taken if unanswered, `approval` items staying pending until answered; the full review goes to `review-record.md`. After the final gate, every `draft direction stands until the owner decides` line in the plan is rewritten to the decided option and date (phase-close operation). Contributor-PR mode in `/review` (`--contributor`, auto-detected when the PR author is outside the maintainer set, read from an explicit `maintainers` list in `.gstack/ship-policy.json`, else repository push permission through the collaborators API, else off, with the source recorded; `CODEOWNERS` is file ownership, not maintenance authority, and is not used): outside PRs are evidence first; the output is a table (evidence value, risk surfaces, `accept`, `supersede` or `decline`), where `accept` keeps a direct path for a correct outside implementation judged by the same bar as a maintainer change, `supersede` rewrites from our own plan after a plain-language approval, and credit lines (`Contributed by @handle`, `Co-Authored-By:`, `Supersedes #N`) are emitted only when a diagnosis or test carried over. Banned terms: `.gstack/banned-terms.json` (`terms`, `allowed_paths`) checked by `bin/gstack-banned-terms` on every diff from `/review` and `/document-release`; findings name the term, file and the allowed locations.
### Not in this wave
- **A `capy` host target in `hosts/`.** No resolver branches on `capabilities.questions` (scripts/host-config.ts:178 only validates it, and every host declares it); a new host means regenerating every skill for one more render with no behavior behind it. Headless behavior keys on the session kind so every host gets it. Revisit if a host-specific tool rewrite is needed (decision D2).
- **Installing semgrep or gitleaks for the user.** The pre-gate runs them when present and says `not installed` when not.
- **Native Windows for `/cso` and the Windows lane itself.** The pre-gate reports a Windows lane as `not run here`; running it stays CI's job.
- **Closing issues from `investigate --backlog`.** Read-only by design.
- **Stamping on main after merge.** Considered under C2 and rejected: the gate must run on the stamped tree.
- **A pre-gate that runs platform lanes this machine lacks.** It records `requires-remote` and the gate includes the lane (C8).
### Validation
1. **Incident fixtures.** Each incident in the feedback becomes a fixture or scripted scenario under `test/fixtures/multi-agent-wave/`, named by section and incident (`p0-4-stale-goldens`, `p0-3-queue-reorder`, `p1-6-cache-skips-guard`, ...), and a test shows the tool catching it or producing the artifact. The incident-to-test map is committed as `test/fixtures/multi-agent-wave/INCIDENTS.md`, not left in a PR body.
2. **Unattended autoplan end to end.** PR B-runner is proven by running `/autoplan` on this plan from a Capy machine with no Claude Code harness, with the outside voice from either a Codex login made from `OPENAI_API_KEY` or a cross-family host subagent (the gate accepts either and records which ran); the PR body links the output files, the `gstack-artifact validate run.json` result, and a parent-agent transcript that consumes them with `jq` and `grep` alone. This is PR B-runner's merge gate.
3. **Queue replay.** `test/gstack-restamp-queue.test.ts` builds three PRs on a fixture repo with a `release:restamp` script, a mirror repo, a CHANGELOG, `(was X)` doc mentions and `since:` fields, reorders the queue twice, and asserts zero manual edits, correct `(was X)` text, CHANGELOG order, mirror parity, and tree receipts that say `same-modulo-stamps` / `gate-reuse: eligible` after a pure restamp and `changed` after a real edit, including the `PREDECESSOR MOVED` refusal, a squash-history PR under gate-ahead (branch history untouched), a `release_tool` that regenerates a path outside `stamp_paths` (classified through `generated.json`), a metadata-only lockfile change (eligible) versus a dependency change (not eligible), and an environment change under an identical tree (`gate-reuse: not-eligible`).
4. **Acceptance matrix.** `docs/designs/multi-agent-wave-2026-10-10/ACCEPTANCE.md` lists every feature with its PR, tier, commands, artifact capabilities and deferred asks; the owner's gate approves that matrix, each PR body quotes its rows, and `gstack-doctor --json` exposes which capabilities the installed revision shipped.
5. **Repo gates per AGENTS.md.** `bun run typecheck`, `bun run typecheck:test` (the test-code type ratchet CI runs, .github/workflows/free-tests.yml:223), `bun run build`, `bun run format:cso:check`, the credential scan, `bun run gen:skill-docs --host all` with a clean diff, `bun test test/parity-suite.test.ts` with each PR's byte delta and `CARVE_GUARDS` refresh in the same commit, the context-budget, skill-size-floor and module-size ratchets (every new module registered), `test/spawnsync-timeout-tripwire.test.ts`, the selected quality judges for changed prompts, then one `bun run test` on the frozen tree per PR, with incomplete platform and provider coverage reported separately; `bun run eval:bg:pr` for the changed prompts (autoplan, plan reviews, review, investigate, ship) with every trial reported.
### Effort
Estimated per component (`[estimated]`; the CEO review found the first table uncredible, the Eng review re-counted B and C from what the text implies rather than the table's bin count, and these numbers are re-measured after PR A ships, `[re-measure before gate]`). Byte-budget work on ship, review, autoplan and qa (zero parity headroom) is the long pole for D, not the code.
| PR | New bins / libs / fixtures | Est. LOC | Human team | CC+gstack | Compression |
|---|---|---|---|---|---|
| A | 3 / 2 / 10 | 800–1,200 | 1 week | 0.5 day | ~10x |
| B-core | 3 / 3 / 10 | 1,500–2,500 | 1.5 weeks | 1 day | ~8x |
| B-runner | 2 / 4 / 12 | 2,000–3,500 | 2 weeks | 1.5 days | ~8x |
| C-stamp | 3 / 5 / 10 (tier 1) | 2,000–3,000 | 2 weeks | 1.5 days | ~8x |
| C-pregate | 3 / 8 / 10 (tier 1) | 2,000–3,000 | 2 weeks | 1.5 days | ~8x |
| D | 0 / 1 / 12 (tier 1) | 600–900 | 1.5 weeks | 1 day | ~8x |
| E | 3 / 4 / 10 (tier 1) | 1,500–2,500 | 2 weeks | 1.5 days | ~7x |
### Gate decisions (2026-10-10; owner replied `all` at 21:30 UTC: every item approved on its recommended option)
Reply grammar: `all` approves every recommendation; `<id><option>` overrides one item (`d1b uc1a`); `all except d9b` combines them.
**User challenges (both reviewers recommended changing the stated direction; the owner accepted the recommendation):**
- **UC1. Scope of the committed wave.** You asked for every section, P0 first. Both the native (Claude) and outside (gpt-6-astra) CEO voices recommend committing P0 now and gating the breadth on adoption evidence, because the premise that tooling gaps (not low perceived value) kept 13 of 28 threads off gstack is unmeasured, and the first effort table was not credible. (a) **Recommended:** commit the whole stacked wave (seven PRs per D12) with the tiering above; tier 1 of every PR is funded now, tier 3 waits for the P7 number (six of the next ten Capy threads run an unattended workflow a parent consumed). Cost: the full breadth lands weeks later than a single push, and two PRs may ship thinner. (b) P0 only (A, B-core, B-runner, C-stamp, C-pregate), then decide. (c) Everything as first written, no tiers. If wrong about (a): tier-3 items the fleet needed wait one measurement cycle.
**Decisions (recommendation first):**
- **D1. Delivery shape.** (a) **Recommended:** a stacked wave merged in order, install alone first, restamp tool dogfooded on every PR behind it (count per D12) [cost: one full gate per PR]. (b) One PR [one gate, a diff nobody can review]. (c) One PR per section, ten [nine extra gates].
- **D2. Unattended as a session kind, not a host.** (a) **Recommended:** a fourth kind `unattended`, explicit override only; `headless` keeps BLOCK so no eval case changes meaning; the outside-voice runner is decoupled from host identity (B3) [cost: none beyond B1]. (b) A new `hosts/capy.ts` target [one more render of every skill, no behavior behind it today].
- **D3. The Capy installer moves into the repo** as `bin/gstack-capy-install`; the user-scope skill clones first, then runs it [cost: one drive-skill edit after PR A merges]. (b) Keep the logic in the drive skill [untested, invisible to CI].
- **D4. Repo policy lives in `.gstack/*.json` pin files**, with command execution opt-in (`--allow-repo-commands` or under `/ship`), read from the committed base-branch copy [cost: one flag in `/ship`]. (b) `gstack-config` keys [per machine, not per repo]. (c) CLAUDE.md prose [the current failure mode].
- **D5. The pre-gate blocks by default** for lanes it can run; platform lanes it cannot run become `requires-remote`; a repo downgrades a named check to `warn` [cost: a first-run surprise in repos with existing drift]. (b) Advisory by default [the incidents recur].
- **D6. Unattended never approves.** The gate is written and the run ends. (b) Auto-approve the recommended option as `spawned` does [a production brain was blocked on exactly this kind of unread decision].
- **D7. Eval skill name:** (a) **Recommended** `/eval-plan` [`measure` collides with `ship-measure`]. (b) `/measure`. (c) `/preregister`.
- **D8. Review severity vocabulary:** (a) **Recommended** keep CRITICAL/INFORMATIONAL and add a forced-CRITICAL list [no collision with Greptile reply tiers or `preamble-tier`]. (b) Introduce Tier A/B.
- **D9. Chromium:** (a) **Recommended** lazy by default through `gstack-browser-ensure`, `--with-browser` to front-load, `--only <skill>` kept [cost: one helper; first browser skill run pays 86 s]. (b) Eager by default with `--light` to skip [every planning machine pays 86 s unless it remembers the flag].
- **D10. Contributor-PR mode is on for outside authors by default**, emits the table with a direct `accept` path, maintainer set from the policy's `maintainers` list, else repository push permission (Eng amendment: `CODEOWNERS` is file ownership, not maintenance authority). (b) Opt-in per repo.
- **D11. Gate reuse rests on identity, not tree equality alone** (tree modulo stamps and generated paths, plus runtime pins, lockfile, CI config, gate command hash); the receipt says `eligible` or names what changed, and the coordinator decides [cost: a slightly longer receipt]. (b) Tree equality alone refuses a second gate [unsound: dependencies and runtime can change under an identical tree].
- **D12. PR shape after the Eng sizing.** (a) **Recommended:** seven PRs: A, B-core, B-runner, C-stamp, C-pregate, D, E; B and C split at their natural seams, halves built in parallel and stacked [cost: two more gates than five PRs, each diff one review sitting]. (b) Keep five PRs and accept two review sittings for B and C.
### Accepted review amendments
Recorded by the autoplan run in the Review record below; the Implementation plan above already reflects accepted ones.
## Review record
### Autoplan run log (2026-10-10, base `91bbd9e`, Capy cloud machine, no Claude Code harness)
Host: Capy (Claude model, no Skill tool, no AskUserQuestion, no hooks). Session kind: unattended in practice (every question auto-decided on the recommended option and logged; the final gate is written, not approved). Outside voice: Codex CLI, logged in from `OPENAI_API_KEY` with `codex login --with-api-key` (6 s), `gstack-codex-probe role-ready exec` selected `gpt-6-astra` (3.7 s). Native voice: a Capy subagent on the Claude family reading `native-prompt.md` with its own file-reading tool. Snapshot tool: `~/.claude/skills/gstack/bin/gstack-autoplan-snapshot.ts` (`init`, `methodology`, `create`); restore point `.gstack/tmp/autoplan/main-autoplan-restore-20261010-203029.md`. Scope: UI no; DX yes (52 term matches, threshold 2). Methodology loaded for CEO: 2,484 lines / 160,290 bytes (the P0-2 incident, measured). Deviations from the Claude Code driver: the native and outside passes ran in parallel against the same snapshot (`sha256 2937ab4e…`), reconciled below (the plan's B11); the `phase-publication-hook` guard is not installed on this host, so publication order is unverified (the plan's B1 hook note); review-log rows were not written because this machine's state root is ephemeral (the plan's A3). Owner input arrived mid-phase (Garry's second message) and was folded into the plan before the DX phase as binding decisions.
Timing (UTC, from artifact mtimes): CEO phase 20:30 → 20:45 wall (outside voice 169 s; native 6 min 24 s; amendments 8 min).
### Phase 1 CEO review: Step 0
Premise table: the native reviewer checked 27 premises against `91bbd9e`: 23 VALID, 1 FALSE as worded (`bin/gstack-hermetic-run` described as reused; it is new over the existing `buildHermeticEnv()`), 3 UNVERIFIED (the causal reading of "13 of 28 threads", the effort table, and range-vs-pin on `engines.bun`). The outside reviewer checked 11: 6 VALID, 4 FALSE (Codex auth supplies every host's outside reviewer; the spec loop requires 7/10; `touchfile-closure.ts` is a general source-to-test map; tree equality alone is gate evidence), 1 UNVERIFIED (two-minute pre-gate and effort). Every FALSE premise is corrected in the plan above (B3, B8, C8 `lanes`, C3/D11). Full tables: `docs/designs/multi-agent-wave-2026-10-10/ceo-native.md`, `ceo-outside.md`.
What already exists: see Context. Mode: SELECTIVE EXPANSION. Spec Review Loop: not run (no `office-hours` spec; the plan is its own spec; the CEO voices served as the review).
### Phase 1 CEO review: dual voices
```
CEO DUAL VOICES — CONSENSUS TABLE:
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Premises valid? 8 5 DISAGREE → taste (repo facts hold; wave premise unmeasured)
2. Right problem to solve? 8 6 CONFIRMED with caveat (artifact contract is the product)
3. Scope calibration correct? 4 2 CONFIRMED: too wide → UC1 + tiers
4. Alternatives sufficiently explored? 6 2 CONFIRMED: trim-and-measure, unattended kind, post-merge stamping, single schema now weighed
5. Competitive/market risks covered? 5 3 CONFIRMED: contract/schema is the hedge (B0)
6. 6-month trajectory sound? 6 3 DISAGREE → resolved by B0 + P7
CONFIRMED = completed subagent + outside. Both completed. Provider codex, model gpt-6-astra, status completed.
```
Reconciliation of the parallel passes (each outside finding against the native result):
| # | Outside finding (gpt-6-astra) | Native | Disposition |
|---|---|---|---|
| 1 | One environment treated as a market; adoption unmeasured | 1.1 same | confirmed → UC1, P7 |
| 2 | "Never re-gate an equal tree" unsound (Critical) | 6.3 partial | confirmed → C3 rewritten, D11 |
| 3 | Machine-readable ≠ verifiable; needs run manifest + validator | 2.1 same | confirmed → B0 |
| 4 | Session mode doesn't solve host compatibility; outside reviewer is host-bound | not raised | new → B3 decouples runner from host; Capy dispatch proven by this run |
| 5 | PR A hostage to twelve initiatives | 3.1/4.1 same | confirmed → A split from B; tiers |
| 6 | Queue design manages churn instead of eliminating it; alternatives unweighed | not raised | disagree in part: post-merge stamping rejected (gate must run on the stamped tree, owner requirement); accepted: constrain `stamp_paths` rewriting, mirror tier 2 |
| 7 | Pre-gate can block the gate it needs (Windows lane) | 3.2 adjacent | confirmed → `requires-remote`, lanes from workflow files |
| 8 | Pin files acquire undefined authority | 6.2 same | confirmed → trust boundary in C1 |
| 9 | `/eval-plan` second product; ledger not a hard cap | 4.4 same | confirmed → tiers; ledger honesty |
| 10 | Row compliance ≠ review quality; spec loop 7+ misread | 3.3 adjacent | confirmed → tiers on rows; B8 rewritten (no resampling) |
| 11 | Install verdict lacks intended-readiness definition | 2.3 adjacent | confirmed → `--for <skill>`, two runtime rows, Bun upgrade |
| 12 | Contributor rewriting undermines maintenance | 6.5 adjacent | confirmed → `accept` path, maintainer set defined |
| — | (native only) `headless` redefinition changes eval semantics (Critical) | 6.1 | accepted → `unattended` fourth kind (B1) |
| — | (native only) gate-ahead merge pollutes squash PRs | 6.4 | accepted → throwaway worktree (C2) |
| — | (native only) lazy Chromium by default; one gate grammar; wrapper clones first; INCIDENTS.md committed | 4.2, 4.3, 6.6, 9 | accepted (A2, B9, A2, Validation) |
Single-voice criticals: native 6.1 (accepted, mechanical: the eval harness sets `GSTACK_HEADLESS`); outside #2 (accepted, D11).
### Phase 1 CEO review: sections 1-11
1. Right problem: both voices agree the thesis (machine-readable first) is right and that the artifact contract, not the tool count, is the product. Accepted: B0. 2. Premises: table above; four corrected. 3. Six-month regret: effort table replaced with per-binary estimates tagged `[estimated]`; tiers added. 4. Alternatives: four alternatives now recorded (trim-and-measure → UC1; `unattended` kind → B1; post-merge stamping → rejected with reason; single schema → B0). 5. Competitive: schema module is the hedge. 6. Uncovered risks: `headless` collision, repo-command trust, receipt residuals, squash history, maintainer set, wrapper bootstrap: all accepted. 7. Scope: tiers; exclusions extended. 8. Promises: P2 reworded for `unattended`, P3 for identity-based reuse, P7 added. 9. Validation: INCIDENTS.md, squash and residual-path replay cases, lockfile-change case. 10. Gate decisions: D1-D10 re-stated, D11 and UC1 added. 11. Design: skipped (no UI scope).
### Phase 1 CEO review: required outputs
NOT in scope, What already exists, Error & Rescue registry (11 rows) and Failure Modes registry (9 rows), dream state delta and Completion Summary: in `docs/designs/multi-agent-wave-2026-10-10/ceo-native.md` (native) and `ceo-outside.md` (outside); the plan's "Not in this wave" absorbs the additions. Completion: premises 27+11 checked; findings 2 Critical, 10 High, 9 Medium, 4 Low across both voices; 2 unverified claims remain in the plan (adoption cause, effort), both tagged.
<!-- autoplan-accepted:ceo -->
- B0 versioned artifact contract with `run.json` manifest and `gstack-artifact validate`; validation fixtures for interrupted run, missing reviewer, omitted finding.
- B1 fourth session kind `unattended` (explicit override only); `headless` semantics unchanged; negative control that `GSTACK_HEADLESS` still blocks.
- B3 pluggable outside-voice runner (`codex-cli` | `api` | `host-subagent`), decoupled from host identity; cross-family subagent is a full voice; model recorded.
- B8 spec loop at cap: no resample of unchanged input; cheap pass on listed issues or `spec not re-verified` block; score shown with missing points.
- B9 one gate-list reply grammar; quoted owner replies are parse fixtures.
- C1 trust boundary: repo-declared commands run only under `/ship`/`/land-and-deploy` or `--allow-repo-commands`, read from the committed base-branch copy; standalone exit 3.
- C2 gate-ahead in a throwaway worktree; `stamp_paths` rewriting limited to exact patterns with a dry-run diff; `--after` and `mirror` tier 2.
- C3 receipt classifies against `stamp_paths` + `generated.json`; `gate-reuse: eligible` requires runtime pins, lockfile, CI config and gate command identity; coordinator decides.
- C8 pre-gate tiers; lanes and platform tiers derived from workflow files; `requires-remote` for lanes this machine cannot run; per-check time budget printed.
- A1 `--for <skill>` readiness; `gstack runtime` and `project pins` rows; range vs exact pins; wrong-host/auth/runtime fixtures. A2 lazy Chromium default, `--with-browser`, Bun upgrade when satisfiable, wrapper clones then runs.
- D1 rows tiered (7 tier 1, 11 tier 2 behind manifest scope). E1 `/eval-plan` tiered; ledger states estimated admission, lock-shared refusal, worst-case overrun. E3 contributor table includes `accept`; maintainer set from CODEOWNERS → collaborators API → off.
- P7 adoption metric; effort table per binary, `[estimated]` and `[re-measure before gate]`; `test/fixtures/multi-agent-wave/INCIDENTS.md` committed.
<!-- /autoplan-accepted:ceo -->
### Phase 1 close and Phase 2
Phase 1 closed with the amendments above applied to the Implementation plan before the DX snapshot was taken. Phase 2 skipped: no UI scope detected (the plan's "component" means an install component, not a view).
### Phase 2.5 DX review (snapshot `ffe83107…f11753`, product type: developer tool, primary user: an AI agent)
Timing (UTC, from artifact mtimes): 20:45 → 20:54 wall (outside voice 173 s; native 6 min 7 s; amendments 3 min). Both voices ran in parallel against the same snapshot.
Personas traced by both voices: (A) a parent Capy agent installing gstack on a subagent machine, running autoplan unattended and consuming the outputs; (B) a coordinator running "ship, next in queue" for three PRs that reorder twice. Native TTHW: current unbounded (the check cannot be trusted) and nonexistent (needs the harness); target ≈ 25 s to `gstack: ok … for=autoplan` plus one run and one `jq`, closed only with an answer step. Outside TTHW estimate for the plan as snapshotted: 45–90 min of manual glue for persona A, 30–60 min plus a gate for persona B.
```
DX DUAL VOICES — CONSENSUS TABLE (0-10, plan as snapshotted → with amendments):
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Onboarding / TTHW 2 → 8 3 CONFIRMED: Bun preflight before setup, lazy Chromium, quickstart
2. Discoverability 3 → 7 3 CONFIRMED: one component table, init scaffolds, show-effective
3. Error messages 4 → 7 5 CONFIRMED: reason codes + anchors + fix clauses
4. CLI / flag consistency 3 → 7 3 CONFIRMED: one result grammar, exit table, --json everywhere
5. Machine-readability 5 → 9 4 CONFIRMED: run-bound ids, referential integrity, envelope
6. Docs / examples 2 → 6 3 CONFIRMED: docs/unattended.md, docs/ship-policy.md, ACCEPTANCE.md
7. Owner gate experience 4 → 8 3 CONFIRMED: stable ids, pagination, auto vs approval, gate_rev
8. Recoverability 2 → 7 2 CONFIRMED: answer/resume, restamp journal, all-or-nothing refusal
Native initial 3.1 → target 7.6. Outside recommendation: revise before implementation. Both completed; provider codex, model gpt-6-astra.
```
Reconciliation (outside finding → native → disposition): #1 gate reuse can certify changed generated behavior / lockfile metadata (Critical) → not raised natively → accepted, C3 now classifies hunks and permits only version transforms; #2 installer fails before its repair step (setup:296), no `jq` check, no revision pin → native F8 adjacent → accepted (A2); #3 readiness contradicts runners → native F2 (Critical, two vocabularies) → accepted, component table with required/optional/not_applicable (A1); #4 pins can repair the wrong environment → accepted, `--project`, per-lane pins, no Bun change on conflict (A1/A2); #5 no unattended invocation or continuation contract → native F1 (Critical, no answer step) → accepted, `bin/gstack-autoplan run|status|answer|resume` (B2); #6 task file cannot replace the plan → accepted, run-bound ids, deps, acceptance, finding refs (B0); #7 no common transport → native F7/F12/F16 → accepted, `GSTACK_RESULT`, exit table, `--json` (B0); #8 state root does not cross the parent–child boundary → native F11 (drive conflicts with policy) → accepted, launch configuration line, durable `--out`, drive opt-in, `ack` (A3, B0, B2); #9 no authoritative version-selection rule under reorder → native F3 → accepted, claims ignored, `--expect-base/--expect-order`, `QUEUE_STALE` (C2); #10 cross-machine reuse before portable evidence → accepted, minimal `gstack-evidence bundle` moves into PR C (C3); #11 restamp check/apply semantics and recovery → native F14 → accepted, `regen --check|--write`, journal, all-or-nothing (C2, C4); #12 base-only pin files have no first-use path → native F13 → accepted, `init|validate|show-effective`, reviewed bootstrap path (C1); #13 two-minute pre-gate assumes CI info YAML lacks → accepted, two stages, runner-owned selection manifests, `--explain` (C8); #14 gate can conceal decisions or misread approval → accepted, pagination with stable ids, `auto` vs `approval`, `gate_rev`, no bare yes/no (B9, E3); #15 findings-derived status collides with the freshness reader and the wrapper → native F16 → accepted (B7); #16 tiers do not define merge gates → native F4 → accepted, `--after` tier 1, ACCEPTANCE.md matrix (C2, Validation 4). Native-only: F5 reason codes with anchors (accepted, B0), F6 `ack` (accepted), F9 `--for` over `--require` (accepted), F10 hook-absent vs hook-failed (accepted, B1), F15 capability sentence (corrected). No disagreements remained after reconciliation.
Required outputs: developer journey map, DX Implementation Checklist (15 items), magical moments (4), competitive benchmark (Claude Code `--output-format json`, Codex `exec` JSONL, Capy subagent summaries: gstack's `run.json` + validator is the only deterministic completion check of the four), empathy narratives and both persona traces are in `docs/designs/multi-agent-wave-2026-10-10/dx-native.md` and `dx-outside.md`.
<!-- autoplan-accepted:dx -->
- B2 `bin/gstack-autoplan run|status|answer|resume` with `--out <dir>`; answer validates against `gate_rev`, finalizes `plan.md` and `run.json.status`, never repeats a completed paid phase; acceptance test covers answer and resume.
- A1 one component table (`lib/doctor-components.ts`); rows `PASS|FAIL|SKIP <id>`; `--for <skill>` prints the required set; `--check` never runs the paid probe without `--live`; healthy Capy + Claude-subagent + no-Codex fixture passes `--for autoplan`; `--project <path>`, per-lane CI pins with sources.
- A2 runtime preflight before `./setup` (Bun, Node, jq); Bun upgrade only when gstack floor and project pin agree; `--revision`; stale-checkout line; Chromium precedence sentence; planning-only install time measured.
- A3 `GSTACK_EPHEMERAL` marker; durable root opt-in; `GSTACK_LAUNCH:` configuration line the parent supplies to every command.
- B0 run-bound ids across findings/tasks/decisions; `depends_on`, `acceptance`, `blocked_by`; referential integrity and reviewer-output binding in `validate`; `GSTACK_RESULT:` grammar; exit-code table with `--help` coverage test; reason codes with anchors and `fix:` for every new error line; `gstack-artifact schema|ack|validate --json`.
- B1 hook-absent renders an inline allow command (`GUARD_NOT_INSTALLED`); hook-present-but-broken keeps denying.
- B7 `findings_total/open/resolved`; `reviewFreshness` updated with the writer; rejected row exits 1 with a typed reason.
- B9 one grammar (`all`, `<id><option>`, `all except <tokens>`), no bare yes/no, stable ids across pages, `auto` vs `approval`, `gate_rev` binding.
- C1 `init|validate|show-effective`; reviewed bootstrap path for new policies (`policy: head (unreviewed)` in the receipt); refusal before any write, `nothing written`.
- C2 `--next` ignores claims under stamp-at-merge; `--expect-base/--expect-order` with `QUEUE_STALE`; restamp journal with resume/rollback and injected-failure tests; `--dry-run`; `--queue-mode` default from policy; `--after` tier 1, `mirror` tier 2.
- C3 explicit inputs and `--json`; hunk-level classification with permitted version transforms only; executed selection and runner image in the identity; minimal `gstack-evidence bundle` in PR C; fix clauses on refusals.
- C4 `regen --check` vs `--write`. C5 `schema_version` and `read --pr`. C8 two stages with separate clocks; runner-owned selection manifests; `--explain`; `requires-remote` names workflow, job and clearing receipt.
- E3 pagination, `auto` vs `approval` semantics. Validation 4 `ACCEPTANCE.md` matrix. `docs/unattended.md` quickstart and `docs/ship-policy.md`.
<!-- /autoplan-accepted:dx -->
### Phase 3 Eng review (snapshot `d7365d92…fd14b17`, methodology 2223 lines)
Timing (UTC, from artifact mtimes): 20:54 → 21:18 wall (outside voice 227 s, Codex `gpt-6-astra`, RC=3 `OUTSIDE_STATUS completed`; native Claude subagent 11 min 22 s with a verified full read, 230 lines / 67384 bytes / SHA-256 `77ee24a4…3511f`; reconciliation and amendments 12 min). Both voices ran in parallel against the same snapshot, taken from the plan as amended by the DX phase. Phase order held: Eng ran last.
```
ENG DUAL VOICES — CONSENSUS TABLE (0-10, plan as snapshotted):
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Architecture soundness 6 5 CONFIRMED: right layering; runner, outside voice and doctor table misplaced (F1/F2/F5 ↔ #5/#8/#11)
2. Edge-case coverage 6 4 CONFIRMED: queue/receipt edges strong; recovery, race, privacy, shallow-clone, lane edges missing
3. Test plan 7 5 CONFIRMED: incident fixtures right; add recovery, concurrency, invalidation, portability, stamp-sensitive tests
4. Right-sized diff 4 3 CONFIRMED: B and C are each two review sittings; split at natural seams (D12)
5. Reversibility 8 4 DISAGREE → native position kept with the outside caveat: new kinds/flags/pins are additive, but
journaling alone cannot make arbitrary release commands reversible (#4 accepted into C2)
6. Maintainability under ratchets 5 4 CONFIRMED: ratchet premise wrong (unregistered bins), four zero-headroom skills unbudgeted
Native: revise-then-proceed. Outside: revise before approval. Both completed; provider codex, model gpt-6-astra.
```
Reconciliation (outside finding → native → disposition): #1 gate reuse confuses permitted edits with equivalent behavior (High; test/agents-digest.test.ts:22, bin/gstack-version-bump:486) → native F14 (per-file-type rules, negative fixture) → accepted, C3 defines equivalence per reusable gate, reruns stamp-sensitive gates, keeps raw and normalized identity, replaces the impossible "changed lockfile, identical tree" fixture; #2 portable bundle lacks data and provenance (bin/gstack-wtree:24, bin/gstack-evidence:23) → native F13 adjacent (branch key) → accepted, C3 compares committed heads only, bundle carries comparison material and producer-bound metadata, refuses reuse on unknown identity; #3 check-to-merge race and integration-tree identity → not raised natively → accepted, C2 binds allocation to base SHA, ordered predecessor chain, PR head and synthetic tree, merge serialized with server-enforced head condition; #4 journaling cannot make arbitrary release commands reversible (bin/gstack-version-bump:475) → not raised natively → accepted, C2 release-tool contract (isolated staging worktree, allowed outputs, pre/postimages, conflict-aware rollback); #5 durable state machine, attempt ids, `execution_unknown` (bin/gstack-autoplan-snapshot.ts:659) → native F1 (Critical: no verb for the host-dispatched reviewer) → accepted together, B2 is `next`/`submit` over `lib/autoplan-run.ts` with attempt ids, exclusive ownership, `EXECUTION_UNKNOWN` reconciliation, no automatic redispatch; #6 gate answers can finalize an unreviewed plan (bin/gstack-autoplan-snapshot.ts:777) → not raised natively → accepted, `answer --gate-rev` rejects stale replies atomically and reopens dependent phases, Eng last, when a choice changes reviewed content; #7 hashing prose cannot detect omitted findings (:677) → not raised natively → accepted, canonical structured findings per reviewer reconciled by id, validator rejects duplicates, cycles, malformed tails, path escapes; #8 runner adapters not equivalent (lib/outside-review-result.ts:4) → native F2 (runtime choice in a render-time resolver; scripts/resolvers/outside-voice.ts:8-12) → accepted together, `bin/gstack-outside-voice` with one adapter interface, `api` labeled `supplied-input review` unless it runs a repo tool loop; #9 spend controls promised in B, implemented in E (scripts/ship-measure.ts:409, test/helpers/pricing.ts:4) → native dependency map agrees (E1 ledger) → accepted, minimal `lib/spend-ledger.ts` moves to B-runner, host-subagent spend reported `unknown`, `--deadline` cancels children; #10 suppressing prompts does not enforce privacy (bin/gstack-skill-start:179, :310; bin/gstack-review-log:99) → native F7 adjacent (kind whitelist, :93) → both accepted, B1 whitelists `unattended`, enforces at writers and sync entrypoints, tests egress sinks; #11 installer bootstrap and false-success paths (bin/gstack-doctor:22, setup:296, bin/gstack-bun-version.sh:15) → native F5 (table language) and F11 (`bun upgrade --version` absent on 1.3.14) → all accepted, A1 table generated into sourced bash, floor vs supported minimum, empty host set and unmet `--revision` never `ok`; A2 uses setup's checksum recipe; #12 CI parsing and import closure cannot establish lane coverage (test/gstack-doctor.test.ts:25, scripts/test-free-shards.ts:235) → native F15 (bash bins unmapped) → accepted, `lanes` unions imports, path literals, the bin→test convention and declared dependencies, conservative on uncertainty, persisted `requires-remote` obligations; #13 pre-gate lacks late-change invalidation (bin/gstack-evidence:394) → not raised natively → accepted, every check records input identity, results must be current after stamping, `secrets` and required lanes cannot be downgraded, timeout is `incomplete`; #14 ratchet premise inaccurate, `--add-rationale` weakens the convention (test/helpers/module-size.ts:202, :225) → native premise correction agrees → accepted, Context corrected, every new module registered, `--add-rationale` removed (pre-gate prints the entry, author appends); #15 artifact and review-log migration (scripts/resolvers/tasks-section.ts:77, lib/review-evidence.ts:188) → native F8 (exit codes contradict) and F9 (codes do not fit `gate-outcomes`) → all accepted, B0 `tasks` v2 enumerated, `bindReview` validates before consuming the token, `lib/result-codes.ts` sibling table, B7 exit matrix fixed (usage 2, rejected row 1); #16 `--next` bypasses the version-source contract (lib/version-source.ts:135) → not raised natively → accepted, C2 reads the configured source at the base revision; #17 hermetic runner does not isolate (test/helpers/hermetic-env.ts:49, :118, :147) → native F16 (bin would import test code) → both accepted, `lib/hermetic-env.ts`, isolated `HOME`, `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`; #18 checklist reduction and fourth spec pass lack an acceptance criterion (scripts/resolvers/spec-review.ts:188) → native F10 (`scope` reverses the passive manifest, test/qa-lazy-sections.test.ts:127) → both accepted, obligation-to-section map with conservative fallback, B8 pass is deterministic issue verification never promoted to a verdict; #19 P7 unmeasurable (lib/state-root.ts:22) → native F3 (Critical, same diagnosis) → accepted, P7 rides `run.json.consumed_by` and the ship receipt in merged PR bodies, `gstack-ship-receipt census` over `gh` against an owner-maintained cohort; "assign a tier to every item" → already true after the CEO phase, rechecked; #20 automatic issue closure exceeds evidence; CODEOWNERS is not a maintainer list → not raised natively → accepted, C6 closes only on explicit intent (`Refs #N` otherwise), E3 reads `maintainers` from policy, else push permission; #21 command contract inconsistent (terminal-line rule vs query commands, exit 1 vs 2, comments in JSON, missing `queue_mode` key) → native F8 → accepted, B0 scopes the terminal-line rule to workflow commands, C1 schema gains `queue_mode` with `*.example.json`; #22 delivery estimate omits work, validation omits `typecheck:test` (.github/workflows/free-tests.yml:223) → native Scope Challenge and complexity table (B and C two sittings each) → accepted, seven-PR shape (D12), Effort re-counted with LOC, Validation 5 adds `typecheck:test`, `build`, `format:cso:check`, credential scan, quality judges.
Native-only findings: F4 guard clause targets the wrong host (scripts/resolvers/composition.ts:11; Claude Code deny-on-absent must stay) → accepted, B1 rewritten, `GUARD_NOT_INSTALLED` printed by `gstack-skill-start` under hosts that do not execute hooks and under `unattended`, validator requires the line; F6 zero parity headroom on ship/review/autoplan/qa → accepted, Context and Delivery shape carry the measured ratios and each PR's byte-delta acceptance; F12 shallow-clone policy path → accepted (`POLICY_SOURCE_UNAVAILABLE`, exit 3); F17 Validation 2 tied to Codex → accepted (either runner); F18 pin-file containment fixtures → accepted (C1); F19 Windows classification → accepted (B2 acceptance). Rejected: none. Deferred: the outside architecture's `gstack-capy-install → dependency-free bootstrap` box is already A2's preflight and needs no new component.
Required outputs: Scope Challenge, architecture diagrams, failure-mode registries (14 outside, 19 native rows), test-plan maps (20 named new or extended test files from the outside voice; folded into B2, C2, C3, C8 acceptance above), size analysis and the per-PR complexity table are in `docs/designs/multi-agent-wave-2026-10-10/eng-native.md` and `eng-outside.md`. New decision D12 (seven PRs) is recorded in Gate decisions with the recommended option.
<!-- autoplan-accepted:eng -->
- Context: ratchet premise corrected (registered modules only; new modules registered; parity headroom ratios).
- Delivery shape: seven PRs (A, B-core, B-runner, C-stamp, C-pregate, D, E) with dependency column, byte-delta acceptance, one owner per shared file; Effort re-counted with LOC; D12 added; D1 reworded.
- A1 table generated into sourced bash; no-Bun, empty-host and unmet-revision fixtures; floor vs supported minimum. A2 Bun via setup's checksum recipe.
- B0 `lib/result-codes.ts`; terminal-line rule scoped to workflow commands; canonical findings reconciled by id; validator rejects duplicates, cycles, malformed tails, path escapes; versioned `tasks` v2; `bindReview` validates before consuming the token.
- B1 guard line printed by `gstack-skill-start` on non-executing hosts and under `unattended`; `unattended` whitelisted at skill-start:93; privacy enforced at writers and sync entrypoints with egress-sink tests.
- B2 `next`/`submit` state machine with attempt ids, exclusive ownership, `EXECUTION_UNKNOWN`, `answer --gate-rev` with phase reopening; `lib/spend-ledger.ts` in B-runner; recovery, concurrency and invalidation tests; Windows classification.
- B3 `bin/gstack-outside-voice` runtime runner with one adapter interface; `api` labeled when supplied-input only. B4 `scope` optional/`always`, obligation map, replace-never-add. B7 exit matrix (usage 2, rejected 1). B8 deterministic issue verification, no fourth launch.
- C1 `POLICY_SOURCE_UNAVAILABLE`, containment fixtures, `queue_mode` in schema, example files. C2 version source via resolver; release-tool contract; race binding and conditional merge; evidence branch key. C3 committed heads only; per-gate equivalence; per-file-type rules; bundle provenance; split lockfile fixtures. C6 explicit closure intent. C8 non-downgradable checks, input identity and invalidation, ratchet entry printed not appended, lanes union and conservative selection, persisted remote obligations, `lib/hermetic-env.ts`, regen in a `git worktree`.
- E1 consumes B-runner's ledger; E2 `verify` over C3's bundle; E3 maintainers from policy or push permission. P7 measured through `run.json.consumed_by`, the ship receipt and `gstack-ship-receipt census`. Validation 2 accepts either outside runner; Validation 5 adds `typecheck:test`, `build`, `format:cso:check`, credential scan, quality judges.
<!-- /autoplan-accepted:eng -->
### Phase 4 close (21:18 UTC; gate written, approved by the owner at 21:30 UTC)
Whole cycle: 20:30 → 21:18 UTC, 48 min wall for three dual-voice phases on a 4-core Capy machine, no Claude Code harness. Outside voice total 569 s of Codex `gpt-6-astra` (three runs, spend not metered by the CLI); native reviewers were Claude subagents on this thread's model, spend unmetered. 65 reviewer findings reconciled (64 accepted, 1 partially accepted, 0 rejected); 13 gate items (UC1, D1–D12), all pending the owner.
Files beside this plan in `docs/designs/multi-agent-wave-2026-10-10/`: `decisions.jsonl` (13 rows, `kind` approval or auto, `recommended`, `gate_rev` 1), `findings.jsonl` (65 rows: phase, voice, model, severity, disposition, plan items, native counterpart), `tasks.jsonl` (28 rows, schema v2: pr, tier, `depends_on`, `findings`, `blocked_by` the gate), `timing.json`, `run.json` (manifest with artifact hashes, `status: gate_pending`), and the six reviewer reports. These are the B0 file set, written by hand this once because the tooling that writes and validates them is what PR B-core builds; their shapes are the v1 contract B0 freezes.
Deviations from the skill as written, stated rather than implied: the Spec Review Loop (B8's subject) was not run on this plan; no `review-log` JSONL rows were written because the state root on this machine is ephemeral (A3's subject) and nothing durable was configured; the phase-publication-hook guard was not enforced because this host executes no hooks (B1's subject), so publication order rests on the snapshot hashes recorded per phase; every mid-run question was auto-decided on the recommended option and logged in the phase records. The final gate below is the one decision the run does not take.
Gate: the owner replied `all` ("Take recommendations and approve"), so UC1 and D1–D12 are decided on option (a). Nothing reviewed changed, so no phase reopens. Implementation starts with PR A and PR B-core in parallel on fresh machines (disjoint files; A owns `bin/gstack-doctor`, `setup`, `bin/gstack-capy-install`; B-core owns `lib/headless-artifacts.ts`, `bin/gstack-skill-start`, `bin/gstack-session-kind`, `bin/gstack-review-log`), B-runner and C-stamp stacked on B-core.