From 36f63548ec9341ab8ad585f33fcb67d16ea361ff Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 26 Oct 2026 15:02:58 +0000 Subject: [PATCH] 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> --- AGENTS.md | 1 + CHANGELOG.md | 15 + README.md | 1 + SKILL.md | 1 + SKILL.md.tmpl | 1 + bin/gstack-doctor-components.sh | 6 +- bin/gstack-ubi | 9 + docs/PROJECT_STRUCTURE.md | 1 + docs/TESTING_INTERNALS.md | 21 ++ docs/skills.md | 37 +++ gstack/llms.txt | 1 + lib/doctor-components.ts | 1 + scripts/ubicloud/ubi-runner.sh | 230 ++++++++++++-- test/catalog-budget.test.ts | 10 +- test/fixtures/context-budget.json | 1 + test/ubicloud-runner.test.ts | 81 +++++ ubicloud/SKILL.md | 498 ++++++++++++++++++++++++++++++ ubicloud/SKILL.md.tmpl | 174 +++++++++++ 18 files changed, 1066 insertions(+), 23 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 ac84585d1..1cc9e9d7d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,6 +65,7 @@ Invoke them by name (e.g., `/office-hours`). | `/retro` | Weekly retro with per-person breakdowns and shipping streaks. | | `/health` | Code quality dashboard (type checker, linter, tests, dead code). | | `/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 bcc901d87..db5a4c308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [Unreleased] + +**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. The per-owner `usage` table is printed either way. +- **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; a relative setup path resolves against `--src`) and refuses without the file or a command, so every lane runs one definition instead of its own wrapper. + +### 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. +- `test/catalog-budget.test.ts` ratchet 1,197 → 1,235 token-equivalents (ubicloud adds 150 bytes); `test/fixtures/context-budget.json` gains the `ubicloud` ceiling only. + ## [1.91.248.0] - 2026-10-26 **`$B` in direct mode no longer lets Chromium pick a proxy from the environment (#3440 item 8).** `BROWSE_PROXY_URL=direct` (and an unparseable `HTTPS_PROXY`) only stopped gstack from configuring a proxy. The daemon inherits the whole environment and the launch passed no proxy switch, so Chromium read `HTTPS_PROXY`, `HTTP_PROXY` or `ALL_PROXY` itself and every `goto` still went through that proxy while `$B` reported a direct connection. That made the BROWSER.md precedence list false and any certificate check on a proxied machine unreliable. Reproduced on Linux with Playwright 1.62.1: a logging CONNECT proxy in `HTTPS_PROXY` received `CONNECT :443` with `BROWSE_PROXY_URL=direct` set. diff --git a/README.md b/README.md index eea86d521..390120bc9 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 1e879554f..4375bd0d6 100644 --- a/SKILL.md +++ b/SKILL.md @@ -229,6 +229,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 f2885faf5..02be8d3eb 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -105,6 +105,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/bin/gstack-doctor-components.sh b/bin/gstack-doctor-components.sh index 50d515473..0540b82dd 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 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 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 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" @@ -63,7 +63,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|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|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 @@ -76,7 +76,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|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|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 e84f7cd36..a7c2c42d9 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -59,6 +59,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 8528a1609..0a9e1597c 100644 --- a/docs/TESTING_INTERNALS.md +++ b/docs/TESTING_INTERNALS.md @@ -977,6 +977,27 @@ sweep only this owner's VMs older than that. For other commands, use the runner directly: `scripts/ubicloud/ubi-runner.sh run --setup