From 6e10ca3c3250d17eaf818dd0699ecfec0a2e6010 Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 13 Oct 2026 01:05:42 +0000 Subject: [PATCH] docs: Capy install layers, lazy install, snapshot bake, forward-only recovery, raising the minimum docs/capy-install.md describes the three layers (Drive bootstrapper, full wrapper, installer), the lazy install, the GSTACK_LAUNCH line, install admission, baking gstack into a Capy snapshot, and that recovery after an interrupted fast-forward completes or refuses the pending install stages (checking out an older commit is not a rollback). docs/CHANGELOG_STYLE.md (the release doc; there is no docs/RELEASING.md) says when GSTACK_MIN_REVISION may be raised. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- docs/CHANGELOG_STYLE.md | 9 ++ docs/capy-install.md | 176 +++++++++++++++++++++++++++++++++------- 2 files changed, 155 insertions(+), 30 deletions(-) diff --git a/docs/CHANGELOG_STYLE.md b/docs/CHANGELOG_STYLE.md index aeabb2e09..6dc733e93 100644 --- a/docs/CHANGELOG_STYLE.md +++ b/docs/CHANGELOG_STYLE.md @@ -125,6 +125,15 @@ sentence: "Version bump for branch-ahead discipline. No user-facing changes yet. there. Do not pad. Do not explain the plan that will ship eventually. Do not narrate the branch's history. When real work lands, the entry will replace this at /ship time. +### Raising the supported minimum (`capy/support.env`) + +Release stamping can also raise `GSTACK_MIN_REVISION` in `capy/support.env`. +Every Capy machine below it is fast-forwarded at its next session start, so +raise it only for install-honesty or security fixes, and set it at stamp time +to the VERSION being released. It never exceeds VERSION +(test/capy-skill-wrapper.test.ts pins this). Details: +[capy-install.md](capy-install.md#raising-the-supported-minimum). + ## Entry format ### Release-summary format (every `## [X.Y.Z]` entry) diff --git a/docs/capy-install.md b/docs/capy-install.md index 208e7ab9f..1d74a9c86 100644 --- a/docs/capy-install.md +++ b/docs/capy-install.md @@ -1,47 +1,110 @@ # Installing gstack on a Capy cloud machine -`bin/gstack-capy-install` installs gstack on a fresh Capy cloud machine -(Ubuntu) and ends with one line a parent agent greps. It is the repo-owned -installer the user-scope Capy skill runs after cloning; nothing in the skill -decides what "installed" means. +Three layers, each owned by gstack: + +| Layer | Where | Job | +|---|---|---| +| Bootstrapper | `capy/skill/gstack/scripts/install.sh` (copied once into the owner's Drive skill folder; frozen) | clone when absent; fetch origin's default branch and read the supported minimum from `FETCH_HEAD:capy/support.env`; in install mode fast-forward a clean, unpinned checkout below it; exec the full wrapper | +| Full wrapper | `bin/gstack-capy-session` (moves with the checkout) | map the session start onto the installer: `--check` certifies, otherwise install or repair | +| Installer | `bin/gstack-capy-install` | Bun, both host profiles, the doctor, the install verdict, the launch line, the trailer | + +The vendored skill (`capy/skill/gstack/SKILL.md`) tells a Capy agent how to use +them. The Drive folder is a mirror: the owner copies `capy/skill/gstack/` there +when asked; nothing writes the Drive automatically. + +Direct use, without the skill: ```bash git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.local/share/gstack ~/.local/share/gstack/bin/gstack-capy-install --for autoplan ``` -What it does, in order: +## Session start -1. Reuses the checkout it lives in (or clones into `--root `), under a - lock. It never resets or pulls an existing checkout. `--revision ` - pins a fresh clone; an existing checkout at another revision is reused and - the trailer says `(requested ; run gstack-upgrade to move)`. -2. Preflight: `git`, `curl`, `node`, `jq`, then Bun. The default Capy image - ships Bun 1.3.14, below gstack's supported minimum (1.4.2), so the installer - downloads the exact release archive, verifies its SHA-256 against - `bin/gstack-bun-version.sh` and installs it to `$BUN_INSTALL` (default - `~/.bun`). It does that only when gstack's minimum and the target project's - Bun pin (`--project `, default the git toplevel of the current - directory) agree; a conflict prints both constraints and stops - (`RUNTIME_PIN_CONFLICT`, exit 3) before anything is installed. +1. `bash /scripts/install.sh --check --for ` (under a second when + a checkout exists). It fetches refs only and never moves the checkout. With + no checkout it prints `gstack: fail not-installed — ...` and the session + records that line and continues when it runs no gstack workflow; install + right before the first workflow. +2. When the trailer is `gstack: fail revision ...` or `gstack: fail runtime ...` + and a checkout exists, run the same command without `--check`. Below the + supported minimum, a clean checkout on the remote default branch is + fast-forwarded (`gstack-capy-skill: upgraded -> `), reinstalled + and rechecked as one transaction under `.gstack-capy-install.lock` (the + bootstrapper holds it and hands it over with `GSTACK_CAPY_LOCK_HELD=1`). + A pinned (`--revision`, detached HEAD, another branch), dirty or diverged + checkout is never moved; the trailer names the cause and the exact commands + ([REVISION_BELOW_MINIMUM](troubleshooting.md#revision-below-minimum)). + `GSTACK_CAPY_AUTO_UPGRADE=0` disables the fast-forward. +3. When the fetch fails (offline), the bootstrapper says `remote minimum + unknown (fetch failed)` and checks the local install only; it never claims + the minimum. + +The upgrade target is always the tip of the default branch: every commit there +is a stamped release that passed CI. Recovery runs forward, never back: an +install interrupted after a fast-forward leaves the trailer `fail`, and the +next install or `bin/gstack-upgrade` run completes the pending install stages +(setup is never skipped after a fast-forward) or refuses and names the cause. +Checking out an older commit is not a supported rollback; it does not undo +setup migrations that already ran. + +Between minimums, `bin/gstack-upgrade --check` says `current ` or `behind + -> ( commits)`, and `bin/gstack-upgrade` fast-forwards +and reinstalls (`bin/gstack-capy-install` on a Capy install, else `./setup +--refresh-registered`). It refuses a dirty, diverged or pinned checkout with +[UPGRADE_REFUSED](troubleshooting.md#upgrade-refused) and never stashes or +resets. + +## What the installer does + +1. Reuses the checkout it lives in (or clones into `--root `), under the + lock. It never resets or pulls; `--revision ` pins a fresh clone, and an + existing checkout at another revision is reused with the trailer note + `(requested ; run /bin/gstack-upgrade to move)`. +2. Preflight: `git`, `curl`, `node`, `jq`, then Bun. A supported Bun already in + `$BUN_INSTALL/bin` (default `~/.bun/bin`) is reused. The project's pins + (`--project `, default the git toplevel of the current directory) are + read with the doctor's own parser (`bin/gstack-runtime-pins.ts`: engines, + packageManager, `.tool-versions`, `.bun-version` and CI `bun-version:` lanes + for this platform). When neither the Bun on PATH nor the installed one + satisfies gstack's minimum and the pins, and every exact pin equals the + version gstack carries a verified archive for (1.4.2), the installer + downloads that exact release archive, verifies its SHA-256 against + `bin/gstack-bun-version.sh` and installs it into `$BUN_INSTALL/bin`, beside + any different system Bun (no symlink into a shared bin directory). Any other + exact pin, or a range gstack's minimum does not satisfy, stops with both + constraints ([RUNTIME_PIN_CONFLICT](troubleshooting.md#capy-install-runtime-conflict), + exit 3) before `./setup`. With no Bun at or above the security floor, the + verified one is installed first so the pins can be read. 3. `./setup --host claude --no-prefix`, then `./setup --host codex --model - gpt-6-astra`, both with `--no-browser`: Chromium is lazy by default. + gpt-6-astra`, both with `--no-browser`. Skipped when this exact HEAD was set + up with the same hosts, model and browser flag (recorded in the git dir + after a successful setup) and `gstack-doctor --check` passes; a warm rerun + takes about a second. 4. `gstack-doctor --check` for the requested workflow (`--for `, - `--only `, or `--require `; default `claude,codex,patch`). + `--only `, or `--require `; default `claude,codex,patch`), + run with the launch PATH, so the trailer certifies the Bun workflows run. +5. The install verdict, written atomically to + `/install-verdicts/.json`: + checkout, HEAD, the workflow and required components it certified, whether + pins were skipped as planning-only, the trailer, the remote minimum, the + launch PATH and the time. + +`--check` runs only steps 4 and 5 (no clone, Bun install or setup). ## The one line to grep -The last line is the doctor's trailer: +The last line is the trailer: ``` -gstack: ok 1.91.70.0 for=autoplan +gstack: ok 1.91.96.0 for=autoplan gstack: fail runtime,pins ``` Exit 0 only with `gstack: ok`. Read the exit status and the trailer directly: ```bash -out=$(~/.local/share/gstack/bin/gstack-capy-install --for autoplan 2>&1); rc=$? +out=$(bash /scripts/install.sh --check --for autoplan 2>&1); rc=$? printf '%s\n' "$out" | grep '^gstack: ' [ "$rc" -eq 0 ] ``` @@ -52,7 +115,13 @@ the incident this installer exists to end. Every `FAIL ` row carries a `fix:` clause and a reason code with its [troubleshooting anchor](troubleshooting.md#install-check-gstack-doctor---check-and-the-capy-installer). A parent agent confirms each subagent machine with this grep; one machine's -install never covers another. +install never covers another, and a subagent's first report carries its own +`gstack:` line. + +`SKIP pins (planning-only; repo pins differ: ..., launch bun )` is not a +failure: planning workflows (`--for autoplan`, the plan reviews, +`office-hours`) never run the target repo's tests. Workflows that do (`review`, +`ship`, `qa`) and `--require` keep `pins` required. When a lane reports to another thread (a handoff, a `gstack-lane-check` result, a coordinator contract), store and use the full thread id (`jam_…`) @@ -73,23 +142,70 @@ time separately. ## The launch line -The installer's last lines include: +Every successful run, `--check` included, prints before the trailer: ``` -GSTACK_LAUNCH: GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT= GSTACK_EPHEMERAL=1 +GSTACK_LAUNCH: PATH=:$PATH GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT= GSTACK_EPHEMERAL=1 GSTACK_CAPY=1 GSTACK_INSTALL_VERDICT= +GSTACK_LAUNCH_PATH: ``` -A child shell's `export` cannot configure the parent's later commands, so the -parent supplies these assignments to every gstack command it launches (prefix -the command, or export them once in the shell that runs it). +`PATH=:$PATH` and `GSTACK_LAUNCH_PATH` appear only when the certified Bun +is not first on PATH (the Capy image's `/usr/local/bin/bun` is 1.3.14; 57 gstack +commands start with `#!/usr/bin/env bun`). A child shell's `export` cannot +configure the parent's later commands, so the parent supplies these +assignments to every gstack command it launches (prefix the command, or +export them once in the shell that runs it). A fresh shell without them keeps +reporting the Bun it would really run: `FAIL runtime — bun 1.3.14 at +/usr/local/bin/bun ...; verified bun 1.4.2 found at ~/.bun/bin (not on PATH); +fix: export PATH="$HOME/.bun/bin:$PATH" or prefix commands with the +GSTACK_LAUNCH line`. + `GSTACK_EPHEMERAL=1` tells gstack the disk vanishes with the machine, so the doctor's `state root` row reads `durable=no` and unattended runs say `learnings: skipped` instead of writing silently. A durable root is opt-in: set `GSTACK_STATE_ROOT` to a directory that outlives the machine. The -installer only prints the option and never writes to the Capy drive (its -policy excludes memory and verification evidence). See +installer never writes to the Capy drive. See [docs/state-root.md](state-root.md#ephemeral-machines). +`GSTACK_CAPY=1` turns on install admission. An unattended session (any host) +refuses to start a workflow on a Bun below the supported minimum; with +`GSTACK_CAPY=1`, an unattended or `GSTACK_SESSION_KIND=spawned` session also +needs a passing verdict for this checkout, its current HEAD and a workflow at +least as wide as the one starting. A refusal prints `INSTALL: fail ...` and +`GSTACK_RESULT: skill= status=refused run=-` and exits 3 +([INSTALL_UNVERIFIED](troubleshooting.md#install-unverified)). Ambient +spawned sessions (OpenClaw) only warn. `GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` +admits anyway and says so. + +## Bake into a Capy snapshot + +Machines that never run a workflow should not pay the install (about 30 s +cold). A project that runs gstack on most machines can bake it instead: the +project Setup's initialize step runs + +```bash +git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.local/share/gstack +~/.local/share/gstack/bin/gstack-capy-install --require claude,codex,patch +``` + +and snapshots capture `$HOME` (the checkout, `~/.bun/bin`, both host +profiles). Each session then pays only the `--check`. A baked revision still +moves forward at session start when it falls below the supported minimum, and +the baked `~/.bun/bin` reaches workflows through the `GSTACK_LAUNCH` line. The +project Setup is shared by every thread in the project, so changing it is the +project's decision. + +## Raising the supported minimum + +`capy/support.env` holds `GSTACK_MIN_REVISION` and `GSTACK_CAPY_MIN_WRAPPER`. +Raise `GSTACK_MIN_REVISION` only for install-honesty or security fixes, at +stamp time, to the VERSION being released: every machine below it is +fast-forwarded at its next session start. It never exceeds VERSION +(test/capy-skill-wrapper.test.ts). Raise `GSTACK_CAPY_MIN_WRAPPER` together with +`GSTACK_CAPY_WRAPPER` in the bootstrapper when the bootstrapper itself +changes; older Drive copies keep working and print an owner action +([CAPY_WRAPPER_STALE](troubleshooting.md#capy-wrapper-stale)). + ## Exit codes | Exit | Meaning |