diff --git a/CHANGELOG.md b/CHANGELOG.md index 488f8fa9c..ba6297e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.91.341.0] - 2026-10-30 + +**gstack no longer leaves Ubicloud VMs running after a lane dies (GST-118).** A runner killed mid-run, a copy of the runner without a TTL, or a VM with no owner could keep billing until someone noticed. Now every VM names its owner and its lifetime, the next `up` anywhere destroys anything past its lifetime, and every run says in one line whether its VM is gone. + +### Fixed +- **`scripts/ubicloud/ubi-runner.sh`**: `up`, `run` and `verify` refuse without `UBI_OWNER` (`UBI_OWNER_REQUIRED`), so no VM is created that nobody can name. The default TTL drops from 4 hours to 3 (`UBI_TTL_HOURS`, 1-999), checked only when a VM is created, so an old setting never blocks `down` or `reap`. `reap`, which every `up` runs first, now also destroys the caller's own `ubirun--*` VMs that carry no TTL (made by older runner copies) once they are 4 hours old (`UBI_LEGACY_HOURS`). Another owner's TTL-less VM, an untagged one and names outside the `ubirun-` prefix are never touched, because only a TTL lets reap judge another lane's VM. +- **`UBICLOUD: left=<0|1|unknown> owner= vm= others=`**: `run` and `verify` end with this line, checked against the project's VM list after teardown. Anything but `left=0` turns a passing command into exit 1 and prints the `down` command to run; `run --keep` prints `left=1 ... kept=1` and keeps its exit status. +- **`scripts/ubicloud/pregate-remote.sh`**: the remote pregate refuses without `UBI_OWNER` (`PREGATE_REMOTE_OWNER_REQUIRED`), names its VM with a TTL taken from its own stop deadline plus 15 minutes (`-t1` at today's 2,700-second timeout), and fails with `PREGATE_REMOTE_INFRA` unless the runner reports `left=0`. It reads that line from the file named by `UBI_LEFT_FILE`, never from the remote command's output. +- docs/TESTING_INTERNALS.md, docs/pregate.md, docs/skills.md and the `/ubicloud` skill describe the owner rule, the new TTL default, the legacy sweep and the `left=` line. + +### Tests +- `test/ubicloud-runner.test.ts`: refusal without an owner, the TTL bounds, the legacy sweep at the default and at `UBI_LEGACY_HOURS=2` (never another owner's, an untagged or an owner-less caller's), `left=0`, `left=1` on a failed destroy (with a failing command keeping exit 7), `run --keep`, and read-only commands accepting an old `UBI_TTL_HOURS=0`. +- `test/ubicloud-pregate-remote.test.ts`: the owner refusal, the `-t1` TTL at today's timeout, and `left=1`, `left=unknown` and a missing line each failing closed with the `down` hint. + ## [1.91.340.0] - 2026-10-30 ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2c84855a..22d3b3eb3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,8 +190,8 @@ the new defaults. | Check an ordinary edit quickly | `bun run test:quick` | Free, about a minute | Bun 1.4.2 | | Run one free test file while repairing | `bun test ` | Free, seconds | Never bare `bun test` for the suite | | Run full free acceptance before publishing | `bun run test` | Free, a few minutes | Bun 1.4.2 | -| Run the full free suite from a small machine | `bun run test:ubicloud` | Free suite on a billed 16-vCPU VM, about 5 minutes | `UBICLOUD_API_KEY` | -| Clear pregate's test tier on a machine under 8 cores ([remote gate](docs/pregate.md#remote-gate)) | `gstack-pregate --stage tests --remote --spend-cap 2 --timeout 2700 --allow-repo-commands` (same command keyed or keyless) | Keyed: one billed 16-vCPU VM, about 7 minutes and $0.06 for the whole free tier, worst case $0.38; keyless: free, local parallel shards, practical for small selections only (98 files took 103 s on 4 cores) | Keyed: `UBICLOUD_API_KEY`; keyless: nothing | +| Run the full free suite from a small machine | `bun run test:ubicloud` | Free suite on a billed 16-vCPU VM, about 5 minutes | `UBICLOUD_API_KEY`, `UBI_OWNER` | +| Clear pregate's test tier on a machine under 8 cores ([remote gate](docs/pregate.md#remote-gate)) | `gstack-pregate --stage tests --remote --spend-cap 2 --timeout 2700 --allow-repo-commands` (same command keyed or keyless) | Keyed: one billed 16-vCPU VM, about 7 minutes and $0.06 for the whole free tier, worst case $0.38; keyless: free, local parallel shards, practical for small selections only (98 files took 103 s on 4 cores) | Keyed: `UBICLOUD_API_KEY`, `UBI_OWNER`; keyless: nothing | | Run the curated Windows-safe subset | `bun run test:windows` (CI: six `windows-latest` jobs) | Free | Windows, Git Bash | | Refresh the Windows duration seed | `gh workflow run windows-free-tests.yml --ref -f record_durations=true`, then commit the `free-test-durations-windows` artifact | Free CI runners | Pushed branch, `gh` with workflow rights | | Run a native Windows qualification campaign | `gh workflow run native-qualification.yml --ref -f mode=` ([modes](docs/TESTING_INTERNALS.md#windows-free-lane)) | Free CI runners | Pushed branch, `gh` with workflow rights | diff --git a/VERSION b/VERSION index b2a0c2857..eac00fd3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.340.0 +1.91.341.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 53be166c2..db5ee4f78 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.340.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.341.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/docs/TESTING_INTERNALS.md b/docs/TESTING_INTERNALS.md index 2a7c30633..cb477888d 100644 --- a/docs/TESTING_INTERNALS.md +++ b/docs/TESTING_INTERNALS.md @@ -979,28 +979,34 @@ namespaces Chromium's sandbox needs, and `python3-venv` is absent (required by the Python runner tests). 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 +(below); `` is `UBI_OWNER` (the thread id on a Capy machine; lowercase +letters and digits, at most 12), which `up`, `run` and `verify` require. 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. 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 +destroy, so a killed runner leaves a name to clean up. `run` and `verify` end +with `UBICLOUD: left=<0|1|unknown> owner= vm= others=`, checked +against the project's VM list after teardown; anything but `left=0` turns a +passing command into exit 1. pregate-remote.sh reads that line from the file +named by `UBI_LEFT_FILE`, never from the command's output. No owner-age sweep runs by default (the TTL reaper +below covers every owner); 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