mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 00:03:15 +01:00
* docs(designs): gstack field wave plan (2026-10-12) and the shared simulated Capy host fixture Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(doctor): --min-revision, a launch Bun found off PATH, planning-only pins SKIP gstack-doctor --check gains --min-revision <v>|unknown: below it the revision row FAILs with REVISION_BELOW_MINIMUM naming the Capy wrapper or bin/gstack-upgrade; unknown says so and never claims the minimum. The runtime and pins rows name a supported Bun found in $BUN_INSTALL/bin (not on PATH) with the export/GSTACK_LAUNCH fix (gstack_bun_launch_dir, one helper in bin/gstack-bun-version.sh). --for a planning-only workflow (autoplan, the plan reviews, office-hours) reports a repo test-pin mismatch as SKIP pins (planning-only; ...). New result codes UPGRADE_REFUSED, UPGRADE_FETCH_FAILED, REVISION_BELOW_MINIMUM, CAPY_WRAPPER_STALE, INSTALL_UNVERIFIED with troubleshooting anchors; REVISION_UNMET's fix names bin/gstack-upgrade. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(install): install verdict and install admission at skill start bin/gstack-install-verdict.sh (Bash) and lib/install-verdict.ts (TypeScript) are the one reader of the install verdict gstack-capy-install writes to <state root>/install-verdicts/<sha256(checkout realpath)>.json (checkout, HEAD, workflow and required components, planning-only pins, trailer). The source checkout and capy/support.env resolve through gstack_source_root / sourceRoot, which follow an installed root's .source-path. gstack-skill-start, after SESSION_KIND: an unattended session (any host) refuses on a PATH Bun below the supported minimum; with GSTACK_CAPY=1 an unattended or explicit spawned session also refuses without a passing verdict for this checkout, HEAD and workflow (INSTALL_UNVERIFIED). A refusal prints the INSTALL: row, an install-refused instruction block bound to its SESSION_ID, GSTACK_RESULT ... status=refused run=- and exits 3. Ambient spawned sessions warn; GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1 admits and says so. capy/support.env declares GSTACK_MIN_REVISION and GSTACK_CAPY_MIN_WRAPPER. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(capy-install): launch PATH, Bun reuse and CI pins, setup skip on HEAD, verdict, --check The GSTACK_LAUNCH line carries PATH=<dir>:$PATH when the certified Bun is not first on PATH, plus GSTACK_CAPY=1 and GSTACK_INSTALL_VERDICT, and GSTACK_LAUNCH_PATH: <dir>; the doctor runs with that PATH. A supported Bun in $BUN_INSTALL/bin is reused instead of downloaded again. Project pins are read with bin/gstack-runtime-pins.ts (CI lanes included): when every exact pin is gstack's verified version the archive is installed alongside a different PATH Bun; any other exact pin is RUNTIME_PIN_CONFLICT. Both ./setup runs are skipped when this HEAD was set up with the same hosts, model and browser flag and the check passes (never after a fast-forward); a failed setup ends with gstack: fail setup and is retried next run. --check certifies only; GSTACK_CAPY_LOCK_HELD=1 accepts the bootstrapper's held lock; result anchors come from the generated components table; REV_NOTE names bin/gstack-upgrade. Intentional pin change: test/gstack-capy-install.test.ts --revision trailer now names <root>/bin/gstack-upgrade (the old text named a command that never existed). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(upgrade): bin/gstack-upgrade, the unattended fast-forward every fix line names --check fetches and prints current|behind <installed> -> <remote> (<n> commits); the default fast-forwards to the remote default branch tip and reinstalls (bin/gstack-capy-install on a Capy install, else ./setup --refresh-registered), completing a pending reinstall on the next run. It refuses (UPGRADE_REFUSED, exit 3, nothing changed) a dirty tracked byte, a diverged commit, a pinned checkout, the incoming Bun floor and a held .setup-lock; never stashes or resets; an unreachable remote is UPGRADE_FETCH_FAILED. Fetches are receipted (fail-open). The unattended skill-end UPGRADE_AVAILABLE line is followed by UPGRADE_COMMAND naming it. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(capy): vendored Capy skill, frozen bootstrapper and full wrapper bin/gstack-capy-session capy/skill/gstack/ is the Drive skill's source of truth. scripts/install.sh is a frozen bootstrapper: it prints gstack-capy-skill: wrapper <n> (<path>), clones or fetches under .gstack-capy-install.lock, reads the remote capy/support.env (FETCH_HEAD), fast-forwards a clean unpinned checkout below GSTACK_MIN_REVISION (refusing dirty, diverged or pinned checkouts with the exact commands; GSTACK_CAPY_AUTO_UPGRADE=0 disables), and execs the checkout's own bin/gstack-capy-session with GSTACK_CAPY_LOCK_HELD=1 so the held lock is never taken twice. Offline it says the remote minimum is unknown. A Drive copy older than GSTACK_CAPY_MIN_WRAPPER prints an owner action (CAPY_WRAPPER_STALE). SKILL.md: check at session start, install lazily before the first workflow, the GSTACK_LAUNCH line, the trailer rule, the subagent first-report line, gstack-pregate sealed before any push, and the autoplan/DRIVER.md pointer. The context-budget fixture gains the skill's entry. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * 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> * refactor(capy): the stale-copy owner action moves from the frozen bootstrapper to bin/gstack-capy-session The checkout-owned full wrapper reads GSTACK_CAPY_MIN_WRAPPER through gstack_support_env_get(gstack_source_root) and compares it with the bootstrapper revision it was handed, so the rule can change without recopying the Drive skill. The bootstrapper keeps only what a stale checkout cannot do: clone, fetch, read the remote minimum, fast-forward, exec. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * v1.91.96.0: honest install and self-upgrade on Capy; supported minimum 1.91.96.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: drop the helper-only Capy host fixture test (test-of-test ratchet); lane tests exercise the helper Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: bound the new sync spawns in the install-verdict and wrapper tests (spawnSync tripwire) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ci(paid-pr-profile): the vendored Capy skill is free-only; free-only files are not prompts needing paid coverage Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(harness): non-behavioral HARNESS_VERSION decision for the PR-profile free-only rule Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(doctor): lane A runtime and pins cases are platform-neutral (bash paths; a lane-free .bun-version pin) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(designs): field wave plan with amendments and the gate rev 2-3 review record (ARTIFACT_VALID, 3 gate revisions) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(capy-install): follow a project's exact Bun pin above the minimum when gstack has its verified archive (adds Bun 1.4.3) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: the installer follows a verified exact project Bun pin (capy-install, troubleshooting, changelog) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(capy-install): the install reason names the project's pin and gstack's real minimum 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>
221 lines
12 KiB
Markdown
221 lines
12 KiB
Markdown
# Installing gstack on a Capy cloud machine
|
|
|
|
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
|
|
```
|
|
|
|
## Session start
|
|
|
|
1. `bash <skill>/scripts/install.sh --check --for <skill>` (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 <old> -> <new>`), 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 <v>` or `behind
|
|
<installed> -> <remote> (<n> 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 <dir>`), under the
|
|
lock. It never resets or pulls; `--revision <ref>` pins a fresh clone, and an
|
|
existing checkout at another revision is reused with the trailer note
|
|
`(requested <ref>; run <checkout>/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 <path>`, 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, the installer downloads one 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). That archive is the supported minimum
|
|
(1.4.2), or the project's exact pin when it is above the minimum and gstack
|
|
carries a verified archive for it (`GSTACK_BUN_VERIFIED`: 1.4.2, 1.4.3), so a
|
|
repo whose CI pins `bun-version: 1.4.3` gets 1.4.3. With an exact pin, only
|
|
that version satisfies it. Any other exact pin (below the minimum, two
|
|
different ones, or one without a verified archive), 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`. 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 <skill>`,
|
|
`--only <skill>`, or `--require <id,id>`; default `claude,codex,patch`),
|
|
run with the launch PATH, so the trailer certifies the Bun workflows run.
|
|
5. The install verdict, written atomically to
|
|
`<state root>/install-verdicts/<sha256 of the checkout realpath>.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 trailer:
|
|
|
|
```
|
|
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=$(bash <skill>/scripts/install.sh --check --for autoplan 2>&1); rc=$?
|
|
printf '%s\n' "$out" | grep '^gstack: '
|
|
[ "$rc" -eq 0 ]
|
|
```
|
|
|
|
Never pipe `--check` (or the installer) through `tail`, `head` or `grep`
|
|
alone: the pipe's exit status replaces the doctor's, and a silent failure is
|
|
the incident this installer exists to end. Every `FAIL <id>` 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, and a subagent's first report carries its own
|
|
`gstack:` line.
|
|
|
|
`SKIP pins (planning-only; repo pins differ: ..., launch bun <v>)` 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_…`)
|
|
in the message, never the short display code: the code is a label the app
|
|
renders, the id is what another agent can address and what the evidence bundle
|
|
records. `UBI_OWNER` and lane ownership records take the same full id.
|
|
|
|
## Browser
|
|
|
|
The default install prints `SKIP browser (lazy; gstack-browser-ensure
|
|
installs on first use)` and downloads nothing. The first browser skill run
|
|
(`/browse`, `/qa`, `/design-review`, ...) calls `bin/gstack-browser-ensure`
|
|
from its preflight, which installs Chromium (~115 MiB, about 86 s on a Capy
|
|
machine) under setup's lock and prints `BROWSER_OK` or `BROWSER_UNAVAILABLE
|
|
<reason>`. `--with-browser` installs it during the install instead, and the
|
|
installer then prints the planning-only install time and the browser bootstrap
|
|
time separately.
|
|
|
|
## The launch line
|
|
|
|
Every successful run, `--check` included, prints before the trailer:
|
|
|
|
```
|
|
GSTACK_LAUNCH: PATH=<dir>:$PATH GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT=<path> GSTACK_EPHEMERAL=1 GSTACK_CAPY=1 GSTACK_INSTALL_VERDICT=<file>
|
|
GSTACK_LAUNCH_PATH: <dir>
|
|
```
|
|
|
|
`PATH=<dir>:$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 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=<s> 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 |
|
|
|-----:|---------|
|
|
| 0 | `gstack: ok` — every required component passed |
|
|
| 1 | `gstack: fail <ids>` — a required component failed; each row has a fix |
|
|
| 2 | usage error |
|
|
| 3 | preflight stopped before `./setup` (`INSTALL_PREFLIGHT_FAILED`, `RUNTIME_PIN_CONFLICT`); nothing was installed |
|