From 737ba3af405431db94cb4c088f79cc3c974bb233 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 26 Oct 2026 16:45:45 -0700 Subject: [PATCH] v1.91.259.0 feat(ubicloud): /ubicloud skill with TTL-named VMs and a reaper, quota preflight, hang watchdog and one committed verify entry point (#3482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ubicloud): /ubicloud skill with TTL-named VMs and a reaper, quota preflight, hang watchdog and one committed verify entry point Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: CLAUDE.md names the ratcheted catalog ceiling Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: UBI_OWNER example is a literal value, not a free-text placeholder (INV-3 fence lint) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: the UBI_OWNER example is a neutral value (no thread codes in product prose) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: verify trusts the checkout only, watched commands keep stdin and end the remote session, quota and reap outcomes verify: .gstack/ubicloud.json is repository content, so its setup script and pull destinations must resolve inside --src (absolute, ~ and .. refused before any API call) and a name it lists under pass is forwarded only when the command line grants it (--pass NAME or --trust-config); --pass never forwards UBICLOUD_* and refuses non-names. watched: the caller's stdin reaches the remote command, a command that finished while the clock was read is not a timeout, a hung setup exits 124, INT/TERM take the ssh session down, and the watchdog ends the command's session on the VM (setsid + ~/.ubirun-.sid + pkill -s). Quota: a cached refusal maximum is trusted for a day, the refusal names its source and uses the 'vCPU quota exhausted' marker pregate-remote.sh retries on. reap: returns 1 on a failed list or destroy; TTL suffixes read in base 10 and UBI_TTL_HOURS takes 0-999. Tests: a fake API that creates plus a fake ssh run 'run' and 'verify' end to end. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: the remote session file is per runner invocation and setsid is optional on the image Two watched commands on one VM wrote the same ~/.ubirun-.sid, so a cancel could end the other's session; the file now carries the runner's pid and is removed when the command ends. An image without setsid runs the command through plain bash instead of failing with exit 127. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: pull destinations are rechecked at pull time, verify parses options by arity, a finished command keeps its status, the remote sid outlives the wrapper; docs audit Pass-2 (Codex adversarial on the delta): an earlier pull could plant a symlink where a later config pull lands, so under verify every destination is resolved again (symlinks followed) right before its pull and a refused pull exits 1; '--dest --trust-config' made an option value act as the trust flag, so verify parses by arity; a command that finished in the instant before the watchdog's kill exited 0 and was still reported as 124; the wrapper removed the sid file while a background child kept the channel open, so the file now stays until remote_cancel reads it. Docs audit (document-release, edit mode): TTL suffix and owner rule in TESTING_INTERNALS, the reaper as the exception to 'no sweep', the docs/skills.md transcript uses a neutral owner; CHANGELOG and the runner header corrected on the two points it deferred. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: a refused pull destination creates no directory, inside or outside the checkout inside_dir checked containment after mkdir -p, so a destination under a planted symlink (escape/new/sub) created the outside directories before being refused. The nearest existing ancestor is checked first; only an accepted destination is created, and it is checked again after. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: bin/gstack-ubi has a test lane (it execs the runner and forwards arguments) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: pack ships a linked worktree with a self-contained .git A worktree's .git is a gitdir pointer into the main repository; on the VM git could not run, so the remote pregate refused with PREGATE_VM_TREE_MISMATCH vm=unavailable. pack now replaces it with a bare --single-branch clone of the checked-out branch (core.bare false, the worktree's index copied in), ~45 MB for gstack; a detached worktree is refused with the fix named. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: take main's host-conformance C3 fix (#3479) so the remote gate can run on this tree Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ubicloud: pack skips a split index (its shared half stays in the original git dir) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * CHANGELOG: restore main's released history under the [Unreleased] block (the merge resolution kept only the conflicted span) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * RESTAMP: 1.91.258.0 -> 1.91.259.0 (stamped; queue_mode=stamp-at-merge base=origin/main@7dc2f23192b7 policy=base origin/main@7dc2f23192b7:.gstack/ship-policy.json) ship, next in queue at 1.91.259.0 STAMP_VERSION: 1.91.259.0 claims: ignored (stamp-at-merge) stage version: done (wrote 1.91.259.0) stage release_tool: done (bun scripts/gen-agents-digest.ts) stage changelog: done (Unreleased -> 1.91.259.0) stage stamp_paths: skipped (no stamp_paths in policy) stage mirror: skipped (no mirror in policy) stage apply: done (4 file(s)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c91fdaf8..e4f4d63ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [1.91.259.0] - 2026-10-26 **New `/ubicloud` skill: the Ubicloud runner moves into gstack with the discipline a shared vCPU quota needs.** Crews ran heavy suites on Ubicloud through a user-scope skill and per-lane wrappers, and paid for it in stray VMs after a machine slept, a project quota at 528 of 512 vCPUs when lanes verified in parallel, verifies that hung for an hour before anyone noticed, and a stale wrapper that skipped a service start. `scripts/ubicloud/ubi-runner.sh` now carries the fixes and `ubicloud/SKILL.md` (entry point `bin/gstack-ubi`) teaches the watched launch; `bun run test:ubicloud` and the remote pregate are unchanged. diff --git a/VERSION b/VERSION index c67e9e442..08528ff87 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.258.0 +1.91.259.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 4bd43c922..203312cf5 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.258.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.259.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/node_modules b/node_modules new file mode 120000 index 000000000..c7990ad12 --- /dev/null +++ b/node_modules @@ -0,0 +1 @@ +/workspace/gstack-ubi/node_modules \ No newline at end of file diff --git a/package.json b/package.json index 5e6d56827..0eca459a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.258", + "version": "1.91.259", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json MERGE_CONDITION: sha=76e86e08a851fb1a19bef9a947bdf0abeb87956d next: NEW_VERSION=1.91.259.0; commit and push only the stamp diff above (FILES), with no edit of your own: the train's verify accepts the CHANGELOG only as the base's plus this PR's entry re-headed; merge with the expected head 76e86e08a851fb1a19bef9a947bdf0abeb87956d and recompute on a stale queue. GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack-ubi/.gstack/tmp/restamp-journal.json 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> --- AGENTS.md | 1 + CHANGELOG.md | 18 ++ CLAUDE.md | 2 +- README.md | 1 + SKILL.md | 1 + SKILL.md.tmpl | 1 + VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- bin/gstack-doctor-components.sh | 6 +- bin/gstack-ubi | 9 + docs/PROJECT_STRUCTURE.md | 1 + docs/TESTING_INTERNALS.md | 42 ++- docs/skills.md | 40 +++ gstack/llms.txt | 1 + lib/doctor-components.ts | 1 + package.json | 2 +- scripts/ubicloud/ubi-runner.sh | 404 +++++++++++++++++++++-- test/catalog-budget.test.ts | 10 +- test/fixtures/context-budget.json | 1 + test/ubicloud-runner.test.ts | 383 +++++++++++++++++++++- ubicloud/SKILL.md | 514 ++++++++++++++++++++++++++++++ ubicloud/SKILL.md.tmpl | 190 +++++++++++ 22 files changed, 1590 insertions(+), 42 deletions(-) create mode 100755 bin/gstack-ubi create mode 100644 ubicloud/SKILL.md create mode 100644 ubicloud/SKILL.md.tmpl diff --git a/AGENTS.md b/AGENTS.md index b49cc15a4..1196d4482 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,6 +66,7 @@ Invoke them by name (e.g., `/office-hours`). | `/health` | Code quality dashboard (type checker, linter, tests, dead code). | | `/captain` | Run a crew from one controller: READY contract, merge trains, digest, approvals shapes, daily flaky pass. Also `/crew`. Templates in `captain/templates/`, team guide in `docs/captain.md`. | | `/benchmark` | Performance regression detection (page load, Core Web Vitals). | +| `/ubicloud` | Run a heavy suite, build or eval on an ephemeral 16-vCPU Ubicloud VM: TTL-named VMs with a reaper, quota preflight, hang watchdog, one committed verify per repository. | | `/benchmark-models` | Cross-model benchmark for skills (Claude, GPT, Gemini side-by-side). | | `/eval-plan` | Preregister a paid eval (bars, arms, decoys, stop rules), dry-run it for $0, pilot three priced items, then approve the spend. | | `/cso` | Supported security findings with explicit coverage. Static assessment remains available without catalog profiles. Contained runtime and scanner execution needs Docker and a matching qualified profile. The runtime catalog qualifies ten images: Node 24.4.0, Bun 1.3.10, Python 3.13.4 with uv 0.8.0, Rails on Ruby 3.4.4, and PostgreSQL 17.2, each for linux/amd64 and linux/arm64. The scanner catalog qualifies twelve: gitleaks, OSV-Scanner, Schemathesis, Semgrep, Trivy and zizmor on both platforms. Without Docker, or where no profile matches (native Windows Docker, for example), `/cso` runs static assessment only. Runtime-tested bundles authenticate separate external assertions. Project-test completion remains `self_reported` because target code controls the test process; `tested` is reserved for a future target-independent completion witness. | diff --git a/CHANGELOG.md b/CHANGELOG.md index e915716c3..e4f4d63ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [1.91.259.0] - 2026-10-26 + +**New `/ubicloud` skill: the Ubicloud runner moves into gstack with the discipline a shared vCPU quota needs.** Crews ran heavy suites on Ubicloud through a user-scope skill and per-lane wrappers, and paid for it in stray VMs after a machine slept, a project quota at 528 of 512 vCPUs when lanes verified in parallel, verifies that hung for an hour before anyone noticed, and a stale wrapper that skipped a service start. `scripts/ubicloud/ubi-runner.sh` now carries the fixes and `ubicloud/SKILL.md` (entry point `bin/gstack-ubi`) teaches the watched launch; `bun run test:ubicloud` and the remote pregate are unchanged. + +### Added +- **`ubicloud/SKILL.md.tmpl`, `bin/gstack-ubi`**: the skill: set/unset prerequisite checks (never `env`), `UBI_OWNER` on every machine, a watched launch (a waited-on background operation or `gstack-detach`), the quoted recovery line, one committed verify per repository, how to read exit 124 and a quota refusal, and the resume checklist. Classified `CLAUDE_ONLY` in `lib/doctor-components.ts`; routed from the root `SKILL.md`; listed in README, AGENTS.md, docs/skills.md and docs/PROJECT_STRUCTURE.md. +- **TTL names and a reaper**: a generated VM name ends in `-t` (`UBI_TTL_HOURS`, default 4; 0 omits it). `reap` destroys every VM in the project whose TTL ended more than ten minutes ago, whichever owner created it, and every `up` runs it first (`UBI_REAP=0` skips it). Names without a TTL, including the ones `pregate-remote.sh` builds, are never reaped. +- **Quota preflight**: `up` adds the size's vCPUs to the project's running VMs and refuses before the create request when that exceeds the known ceiling (`UBI_QUOTA_VCPUS`, or the maximum the last API refusal printed, remembered in the state root); `UBI_QUOTA_WAIT=` polls for room instead of refusing. Both refusals (the preflight's and the API's) print the per-owner `usage` table. +- **Watchdog and heartbeat**: `run`, `verify` and `ssh NAME COMMAND` stream the remote output (both streams kept in the VM's state dir) through a watchdog: a heartbeat every `UBI_HEARTBEAT` seconds (300), and a command silent for `UBI_IDLE_TIMEOUT` seconds (1800) or older than `UBI_RUN_TIMEOUT` (0, unlimited) is killed in its own session with exit 124 and its last 40 lines on stderr. `run` also prints the VM's TTL and its `down` recovery command as soon as the VM is ready. +- **`verify`**: runs the repository's committed `.gstack/ubicloud.json` (setup, command, size, location, storage, image, env, pass, pull; flags and a trailing `-- COMMAND` override it) and refuses without the file or a command, so every lane runs one definition instead of its own wrapper. The file is repository content and gets no reach beyond the checkout: `setup` and each `pull` destination must be relative paths that resolve inside `--src` (absolute, `~` and `..` paths are refused before any API call, and under `verify` every pull destination is resolved again, symlinks followed, right before its pull, so a symlink an earlier pull planted cannot redirect a later one; a refused pull makes the run exit 1), and a name under `pass` is forwarded only when the command line grants it (`--pass NAME`, or every listed name with `--trust-config`; `verify` parses its options by arity, so an option value can never act as that flag). `--pass` never forwards `UBICLOUD_*`, from the file or the command line. +- **Watched commands keep their stdin and exit codes**: `ssh NAME COMMAND` hands the caller's stdin to the remote command (`printf payload | gstack-ubi ssh NAME 'cat > file'` wrote an empty file once the watchdog backgrounded the client), a command that finished while the watchdog read the clock, or in the instant before its kill, keeps its own exit status instead of 124, a hung setup script exits 124 like a hung command instead of 1, Ctrl-C on a watched command takes the ssh session down with it, and when the watchdog fires the runner also ends the command's session on the VM (`setsid` there, leader pid in `~/.ubirun--.sid`, kept until the cancel reads it so a background child the command left behind is still reachable, `pkill -s` bounded to 20 s), since killing the local client alone leaves a silent remote command running until it next writes. +- **`pack` and `sync` from a linked worktree**: a worktree's `.git` is a one-line pointer into the main repository, so the VM got a checkout whose git did not work (`gstack-pregate --remote` from a Capy worktree failed with `PREGATE_VM_TREE_MISMATCH vm=unavailable`). The tarball now carries a self-contained clone of the checked-out branch with the worktree's own index in its place (about 45 MB for gstack instead of the main repository's full `.git`); a detached worktree is refused with the fix named. +- **Quota ceiling and reaper outcomes**: the maximum a refusal cached is trusted for a day, then the API's own answer decides again; the refusal line says where the ceiling came from and names `UBI_QUOTA_VCPUS`, and its `vCPU quota exhausted` wording is the one `pregate-remote.sh` retries once on. `reap` returns 1 when the list fails (nothing destroyed) or an expired VM could not be destroyed, instead of 0 either way; a TTL suffix is read in base 10 (`UBI_TTL_HOURS` takes 0 to 999 with no leading zero). + +### Tests +- `test/ubicloud-runner.test.ts`: against the fake API, `up` reaps an expired other-owner VM, names its own VM `-t4`, and the preflight refuses with no create call; `UBI_REAP=0` and `UBI_TTL_HOURS=0`; `reap` leaves unexpired and TTL-less names alone. Against a fake `ssh`, the watchdog returns 124 with the last output and the heartbeat line appears. `verify` refuses a missing file, a missing command, a missing setup script (resolved against `--src`) and invalid JSON, all before any API call. A second fixture whose fake API can create runs `run` end to end: a hung setup exits 124 and still destroys the VM, `UBI_RUN_TIMEOUT` kills a chatty command, `--pass` writes the named variable (and never `UBICLOUD_*` or a non-name) into the VM's env file without printing it, `ssh NAME COMMAND` passes stdin through, `verify` refuses ungranted `pass` names and setup or pull paths outside `--src` and runs once granted, a quota refusal caches its maximum which drives the preflight until it is a day old, `UBI_QUOTA_WAIT` waits before refusing, and `reap` reports a failed list or destroy. +- `test/catalog-budget.test.ts` ratchet 1,251 → 1,289 token-equivalents (ubicloud adds 150 bytes on top of /captain's 1,251); `test/fixtures/context-budget.json` gains the `ubicloud` ceiling only. + ## [1.91.258.0] - 2026-10-26 **The session check stops failing for things that are not failures, and a consuming repository can name the gstack it wants without broadcasting to every thread.** Five Captains running gstack on Capy hit the same install friction this week: eight crew-wide "upgrade to " messages since Oct 16, `gstack: fail not-installed` on coordinator machines that run no workflow, `fail pins` from the image's Node while the tests run on the pinned one, reviewers told to "reinstall" when asked to run a workflow, and an environment dump from a broken `/cso` shell line. Each of these is now a product behaviour instead of a crew rule. diff --git a/CLAUDE.md b/CLAUDE.md index 3b874a2ab..37dcd42a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,7 +146,7 @@ a skill's own bash. **Size budgets:** generated SKILL.md files warn above 160KB (~40K tokens); `test/catalog-budget.test.ts` caps the always-loaded skill catalog at -`CATALOG_BUDGET_TOKEN_EQUIVALENTS` (1,251 today; each new skill ratchets it); and +`CATALOG_BUDGET_TOKEN_EQUIVALENTS` (1,289 today; each new skill ratchets it); and `test/context-budget-ratchet.test.ts` pins per-skill token ceilings against `test/fixtures/context-budget.json` (for legitimate growth or a landed reduction, re-run `bun test/helpers/capture-context-budget.ts` and commit the diff --git a/README.md b/README.md index 75f85a9d0..94bfae2a7 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ Each skill feeds into the next. `/office-hours` writes a design doc that `/plan- | `/land-and-deploy` | **Release Engineer** | Merge the PR, wait for CI and deploy, verify production health. One command from "approved" to "verified in production." | | `/canary` | **SRE** | Post-deploy monitoring loop. Watches for console errors, performance regressions, and page failures. | | `/benchmark` | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. | +| `/ubicloud` | **Remote Runner** | Run a heavy suite, build or eval on an ephemeral 16-vCPU Ubicloud VM: owner- and TTL-named VMs with a reaper, quota preflight, a hang watchdog, and one committed verify per repository. | | `/eval-plan` | **Eval Planner** | Preregister a paid eval before the first priced call: bars, arms, decoys and stop rules, a $0 stub-model dry run over the real inputs, and a three-item priced pilot whose extrapolation and spend table gate the approval. | | `/document-release` | **Technical Writer** | Audit changed behavior against project docs on every ship, before final verification and publication. Also runs standalone. Shows updated, reviewed/current or blocked docs and any remaining gaps. | | `/document-generate` | **Documentation Author** | Generate missing docs from scratch using the Diataxis framework. Researches the codebase first, then writes reference / how-to / tutorial / explanation docs that actually match the code. Invokable standalone or chained from `/document-release` when the coverage map finds gaps. Learn more: [tutorial](docs/tutorial-document-generate.md) • [how-to](docs/howto-document-a-shipped-feature.md) • [why Diataxis](docs/explanation-diataxis-in-gstack.md). | diff --git a/SKILL.md b/SKILL.md index a03f92690..55a69a56a 100644 --- a/SKILL.md +++ b/SKILL.md @@ -230,6 +230,7 @@ Generic “second opinion”, “outside review”, or “cross-model review” - User asks to codify or save the last `/scrape` as a reusable skill → invoke `/skillify` (fallback browser) - User asks about page speed, web performance regression → invoke `/benchmark`; model or skill benchmarks → invoke `/benchmark-models` - User asks to plan, preregister, price or pilot a paid eval → invoke `/eval-plan` +- User asks to run a suite, build or eval on Ubicloud → invoke `/ubicloud` - User asks what gstack has learned, "show learnings" → invoke `/learn` - User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune` - User asks for code quality dashboard, "health check" → invoke `/health` diff --git a/SKILL.md.tmpl b/SKILL.md.tmpl index 0c7baa07f..1c1ca97f5 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -106,6 +106,7 @@ quality gates that produce better results than answering inline. - User asks to codify or save the last `/scrape` as a reusable skill → invoke `/skillify` (fallback browser) - User asks about page speed, web performance regression → invoke `/benchmark`; model or skill benchmarks → invoke `/benchmark-models` - User asks to plan, preregister, price or pilot a paid eval → invoke `/eval-plan` +- User asks to run a suite, build or eval on Ubicloud → invoke `/ubicloud` - User asks what gstack has learned, "show learnings" → invoke `/learn` - User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune` - User asks for code quality dashboard, "health check" → invoke `/health`{{VOICE_TRIGGER_ROUTING}} diff --git a/VERSION b/VERSION index c67e9e442..08528ff87 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.258.0 +1.91.259.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 4bd43c922..203312cf5 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.258.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.259.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/bin/gstack-doctor-components.sh b/bin/gstack-doctor-components.sh index d0bfaefef..bc9fe7537 100644 --- a/bin/gstack-doctor-components.sh +++ b/bin/gstack-doctor-components.sh @@ -8,7 +8,7 @@ GSTACK_DOCTOR_COMPONENTS="claude codex patch codex-cli browse-bundle browser cso GSTACK_DOCTOR_CORE="runtime pins state-root privacy cores revision floor wrapper" GSTACK_DOCTOR_HOSTS="claude codex" GSTACK_DOCTOR_OPTIONAL="codex-cli cso" -GSTACK_DOCTOR_SKILLS="autoplan plan-ceo-review plan-eng-review plan-design-review plan-devex-review plan-tune office-hours spec review ship investigate codex cso browse qa qa-only design-review design-consultation design-html devex-review scrape skillify canary benchmark make-pdf diagram pair-agent open-gstack-browser setup-browser-cookies connect-chrome land-and-deploy benchmark-models captain careful claude-code context-restore context-save design-shotgun deslop-shared-libs document-generate document-release eval-plan freeze gstack-upgrade guard health ios-clean ios-design-review ios-fix ios-qa ios-sync landing-report learn retro setup-deploy setup-gbrain sync-gbrain test-audit unfreeze review-voice" +GSTACK_DOCTOR_SKILLS="autoplan plan-ceo-review plan-eng-review plan-design-review plan-devex-review plan-tune office-hours spec review ship investigate codex cso browse qa qa-only design-review design-consultation design-html devex-review scrape skillify canary benchmark make-pdf diagram pair-agent open-gstack-browser setup-browser-cookies connect-chrome land-and-deploy benchmark-models captain careful claude-code context-restore context-save design-shotgun deslop-shared-libs document-generate document-release eval-plan freeze gstack-upgrade guard health ios-clean ios-design-review ios-fix ios-qa ios-sync landing-report learn retro setup-deploy setup-gbrain sync-gbrain test-audit ubicloud unfreeze review-voice" GSTACK_BROWSER_LAZY_REASON="lazy; gstack-browser-ensure installs on first use" GSTACK_DOCTOR_HELPERS="gstack-pregate gstack-plan-reality gstack-artifact gstack-autoplan" GSTACK_DOCTOR_NOT_NEEDED="none" @@ -69,7 +69,7 @@ gstack_doctor_skill_requires() { codex) printf '%s' 'claude codex patch codex-cli' ;; cso) printf '%s' 'claude cso' ;; browse|qa|qa-only|design-review|design-consultation|design-html|devex-review|scrape|skillify|canary|benchmark|make-pdf|diagram|pair-agent|open-gstack-browser|setup-browser-cookies|connect-chrome|land-and-deploy) printf '%s' 'claude codex patch browse-bundle browser' ;; - benchmark-models|captain|careful|claude-code|context-restore|context-save|design-shotgun|deslop-shared-libs|document-generate|document-release|eval-plan|freeze|gstack-upgrade|guard|health|ios-clean|ios-design-review|ios-fix|ios-qa|ios-sync|landing-report|learn|retro|setup-deploy|setup-gbrain|sync-gbrain|test-audit|unfreeze) printf '%s' 'claude' ;; + benchmark-models|captain|careful|claude-code|context-restore|context-save|design-shotgun|deslop-shared-libs|document-generate|document-release|eval-plan|freeze|gstack-upgrade|guard|health|ios-clean|ios-design-review|ios-fix|ios-qa|ios-sync|landing-report|learn|retro|setup-deploy|setup-gbrain|sync-gbrain|test-audit|ubicloud|unfreeze) printf '%s' 'claude' ;; review-voice) printf '%s' 'helpers' ;; *) return 1 ;; esac @@ -82,7 +82,7 @@ gstack_doctor_skill_optional() { codex) printf '%s' '' ;; cso) printf '%s' '' ;; browse|qa|qa-only|design-review|design-consultation|design-html|devex-review|scrape|skillify|canary|benchmark|make-pdf|diagram|pair-agent|open-gstack-browser|setup-browser-cookies|connect-chrome|land-and-deploy) printf '%s' 'codex-cli' ;; - benchmark-models|captain|careful|claude-code|context-restore|context-save|design-shotgun|deslop-shared-libs|document-generate|document-release|eval-plan|freeze|gstack-upgrade|guard|health|ios-clean|ios-design-review|ios-fix|ios-qa|ios-sync|landing-report|learn|retro|setup-deploy|setup-gbrain|sync-gbrain|test-audit|unfreeze) printf '%s' '' ;; + benchmark-models|captain|careful|claude-code|context-restore|context-save|design-shotgun|deslop-shared-libs|document-generate|document-release|eval-plan|freeze|gstack-upgrade|guard|health|ios-clean|ios-design-review|ios-fix|ios-qa|ios-sync|landing-report|learn|retro|setup-deploy|setup-gbrain|sync-gbrain|test-audit|ubicloud|unfreeze) printf '%s' '' ;; review-voice) printf '%s' '' ;; *) return 1 ;; esac diff --git a/bin/gstack-ubi b/bin/gstack-ubi new file mode 100755 index 000000000..39070627f --- /dev/null +++ b/bin/gstack-ubi @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# gstack-ubi — the /ubicloud skill's entry point: ephemeral Ubicloud VMs for +# test suites, builds and evals that outgrow the local machine. Everything is +# scripts/ubicloud/ubi-runner.sh (run, verify, up, ssh, sync, pull, down, list, +# usage, records, gc, reap); this wrapper only resolves the checkout so skill +# prose has one stable path. +set -euo pipefail +here=$(cd "$(dirname "$0")" && pwd) +exec bash "$here/../scripts/ubicloud/ubi-runner.sh" "$@" diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index a2fbf04ee..c637ad2d3 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -60,6 +60,7 @@ gstack/ ├── plan-eng-review/ # /plan-eng-review skill ├── autoplan/ # /autoplan skill (auto-review pipeline: CEO → design → DX → eng, eng always last) ├── benchmark/ # /benchmark skill (performance regression detection) +├── ubicloud/ # /ubicloud skill (ephemeral Ubicloud VMs: TTL + reaper, quota preflight, watchdog, verify; bin/gstack-ubi) ├── canary/ # /canary skill (post-deploy monitoring loop) ├── codex/ # /codex skill (multi-AI second opinion via OpenAI Codex CLI) ├── land-and-deploy/ # /land-and-deploy skill (merge → deploy → canary verify) diff --git a/docs/TESTING_INTERNALS.md b/docs/TESTING_INTERNALS.md index ed8b885c4..aac8bef44 100644 --- a/docs/TESTING_INTERNALS.md +++ b/docs/TESTING_INTERNALS.md @@ -970,21 +970,48 @@ the CSO private-state checks), AppArmor blocks the unprivileged user namespaces Chromium's sandbox needs, and `clang` and `python3-venv` are absent (required by the Dia readiness and Python runner tests). -VMs are named `ubirun---`; `` is `UBI_OWNER` (set it -to the thread id on a Capy machine), else a stable per-machine id. Every exit +VMs are named `ubirun---`, plus a `-t` TTL suffix +(below); `` is `UBI_OWNER` (set it to the thread id on a Capy machine; +lowercase letters and digits, at most 12), else a stable per-machine id. Every exit path destroys the client's own VM (`trap` on EXIT, INT, TERM, HUP and QUIT), and `down` fails loudly (`FAILED to destroy / (HTTP )`) unless the VM is confirmed gone. `list --mine` shows this owner's VMs, `usage` the project's, and `records` the VM-id scratch file (`UBI_VM_RECORD`, default `~/.capy/work/ubi-runner/vms.tsv`, else `/vms.tsv`): every VM is recorded there before the create call and forgotten only after a confirmed -destroy, so a killed runner leaves a name to clean up. Nothing sweeps stale VMs -by default, because the project quota is shared with other agents' runners; set +destroy, so a killed runner leaves a name to clean up. No age-based sweep runs +by default, because the project quota is shared with other agents' runners (the +TTL reaper below destroys only a VM whose own `-t` TTL has ended); set `UBI_GC_HOURS` to a positive number (or run `gc HOURS`; `gc 0` is a no-op) to sweep only this owner's VMs older than that. For other commands, use the runner directly: `scripts/ubicloud/ubi-runner.sh run --setup