Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0fc9b15
ci: onboard Blacksmith Testbox
lawrencecchen Aug 13, 2026
e407046
ci: provision cmux-tui Rust Testbox lane
lawrencecchen Aug 13, 2026
67f398f
ci: serialize Testbox benchmark stages
lawrencecchen Aug 13, 2026
5e31e66
ci: harden trusted Blacksmith Testbox lane
lawrencecchen Aug 13, 2026
33b0dfe
docs: tighten Testbox source preflight
lawrencecchen Aug 13, 2026
22800c1
ci: serialize and recover trusted Testbox runs
lawrencecchen Aug 13, 2026
42ab3da
ci: bind Testbox cleanup to setup receipts
lawrencecchen Aug 13, 2026
5735ee5
ci: pin Testbox hydration failure reporting
lawrencecchen Aug 13, 2026
bc7ff77
ci: bound and verify remote Testbox builds
lawrencecchen Aug 13, 2026
5cdb5fc
ci: verify Testbox ready phone-home
lawrencecchen Aug 13, 2026
94c6187
ci: fence setup markers and preserve evidence
lawrencecchen Aug 13, 2026
5da1401
docs: state Testbox trust configuration limits
lawrencecchen Aug 13, 2026
bd89ec7
ci: preview owned Testbox cleanup
lawrencecchen Aug 13, 2026
c1999b8
ci: assert trusted runner identity
lawrencecchen Aug 13, 2026
ba2fd40
ci: finalize Testbox readiness and cleanup states
lawrencecchen Aug 13, 2026
dd3765c
ci: make Testbox readiness and cleanup explicit
lawrencecchen Aug 13, 2026
b0c5b80
ci: gate Testbox readiness and destructive cleanup
lawrencecchen Aug 13, 2026
4a05ae6
ci: verify Testbox ownership before cleanup
lawrencecchen Aug 13, 2026
1c95667
ci: restrict Testbox token setup to main
lawrencecchen Aug 13, 2026
1dd0e2a
ci: validate Testbox inputs before cleanup
lawrencecchen Aug 13, 2026
7e1e7cc
ci: align Testbox cleanup with CLI inventory
lawrencecchen Aug 13, 2026
3c283ab
ci: make Testbox cleanup status-specific
lawrencecchen Aug 13, 2026
4465525
ci: parse Testbox status formats safely
lawrencecchen Aug 13, 2026
9fc1b55
ci: require a current Testbox cleanup preview
lawrencecchen Aug 13, 2026
63d74f3
ci: close final Testbox trust gaps
lawrencecchen Aug 13, 2026
d0e7108
ci: bound Testbox control-plane operations
lawrencecchen Aug 13, 2026
905939f
ci: use portable bounded Testbox commands
lawrencecchen Aug 13, 2026
ad1206b
ci: require preview-bound Testbox cleanup
lawrencecchen Aug 13, 2026
53fbe79
docs: bound Testbox downloads and cleanup hashing
lawrencecchen Aug 13, 2026
d210746
ci: handle Testbox metadata and CLI schema drift
lawrencecchen Aug 13, 2026
229119d
ci: tighten Testbox ownership parsing
lawrencecchen Aug 13, 2026
07c1856
ci: enforce hydrated toolchain identity
lawrencecchen Aug 13, 2026
2af052b
ci: bound warmup and preserve interrupted stages
lawrencecchen Aug 13, 2026
89bd10d
ci: compare toolchain identity in cmux-tui
lawrencecchen Aug 13, 2026
5c529ba
ci: own bounded Testbox keepalive
lawrencecchen Aug 13, 2026
6042481
ci: fix keepalive SSH activity detection
lawrencecchen Aug 13, 2026
00634a2
docs: require independently reviewed cleanup preview
lawrencecchen Aug 13, 2026
1f21d21
ci: pin reviewed Testbox branch and SHA
lawrencecchen Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ self-hosted-runner:
# Linux: Blacksmith primary (LINUX_RUNNER), WarpBuild overflow fallback.
- blacksmith-4vcpu-ubuntu-2404
- blacksmith-8vcpu-ubuntu-2404
- blacksmith-32vcpu-ubuntu-2404
- warp-ubuntu-latest-x64-4x
298 changes: 298 additions & 0 deletions .github/workflows/ci-workflow-guard-tests-testbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
name: cmux-tui Rust Testbox setup

on:
workflow_dispatch:
inputs:
testbox_id:
description: "Testbox session ID supplied by blacksmith testbox warmup"
required: true
type: string
source_sha:
description: "Optional full source SHA; if set, it must equal the pushed branch head"
required: false
default: ""
type: string

# The Blacksmith CLI supplies testbox_id and dispatches the selected pushed
# branch. It does not expose arbitrary workflow inputs, so source_sha is an
# optional manual-dispatch assertion; the benchmark's remote guard is required
# for the CLI path below.
permissions: {}

concurrency:
# A Testbox is a mutable shared workspace. Queue every request that names the
# same box/source instead of allowing two syncs or stage commands to race.
group: cmux-tui-testbox-${{ inputs.testbox_id }}-${{ inputs.source_sha || github.sha }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize all workflows sharing a Testbox ID

When the same Testbox ID is dispatched for two different revisions—for example, a retry after the source branch advances—the source component makes the concurrency groups different, so both jobs may attach to and hydrate the same mutable VM concurrently. This can race checkouts and dependency state despite the comment's serialization intent; key the group solely by inputs.testbox_id.

Useful? React with 👍 / 👎.

cancel-in-progress: false

jobs:
cmux-tui-rust:
name: cmux-tui Rust setup
runs-on: blacksmith-32vcpu-ubuntu-2404
environment:
# Configure this environment with required reviewers and no secrets.
# Approval must happen before begin-testbox exposes its auth token.
name: blacksmith-testbox-trusted
permissions:
contents: read
timeout-minutes: 45
steps:
# begin-testbox must be the first step. It attaches the VM requested by
# `blacksmith testbox warmup` and leaves it alive after this setup job.
- name: Begin Testbox
uses: useblacksmith/begin-testbox@233448af4bfdc6fca509a7f0974411ac6d8a8043 # v2
Comment on lines +110 to +111

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate the repository before beginning the Testbox

When this workflow is dispatched from another repository on its main branch, the ref guard passes and Begin Testbox runs before REPOSITORY is checked at lines 69–72. Because the begin action installs the Testbox authentication token, the declared manaflow-ai/cmux restriction is enforced too late for the token-bearing step; include the repository predicate in the pre-begin guard.

Useful? React with 👍 / 👎.

with:
testbox_id: ${{ inputs.testbox_id }}

- name: Validate trusted branch dispatch and source identity
env:
EXPECTED_INPUT_SHA: ${{ inputs.source_sha }}
DISPATCH_SHA: ${{ github.sha }}
DISPATCH_REF: ${{ github.ref }}
TESTBOX_ID: ${{ inputs.testbox_id }}
REPOSITORY: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
[[ "$REPOSITORY" == "manaflow-ai/cmux" ]] || {
echo "::error::this Testbox lane is only valid for manaflow-ai/cmux" >&2
exit 1
}
[[ "$TESTBOX_ID" =~ ^tbx_[A-Za-z0-9_-]+$ ]] || {
echo "::error::malformed Testbox ID" >&2
exit 1
}
[[ "$DISPATCH_SHA" =~ ^[0-9a-f]{40}$ ]] || {
echo "::error::github.sha must be a lowercase full commit SHA" >&2
exit 1
}
if [[ ! "$DISPATCH_REF" =~ ^refs/heads/[A-Za-z0-9._/-]+$ || "$DISPATCH_REF" == *..* || "$DISPATCH_REF" == */ || "$DISPATCH_REF" == *//* ]]; then
echo "::error::Testbox warmup must dispatch a pushed branch ref, not a raw SHA, tag, or malformed ref" >&2
exit 1
fi
if [[ -n "$EXPECTED_INPUT_SHA" ]]; then
[[ "$EXPECTED_INPUT_SHA" =~ ^[0-9a-f]{40}$ ]] || {
echo "::error::source_sha must be a lowercase full commit SHA" >&2
exit 1
}
[[ "$EXPECTED_INPUT_SHA" == "$DISPATCH_SHA" ]] || {
echo "::error::source_sha does not equal the workflow dispatch SHA" >&2
exit 1
}
fi
remote_sha="$(git ls-remote --exit-code "https://github.com/${REPOSITORY}.git" "$DISPATCH_REF" | awk 'NR == 1 { print $1 }')"
[[ "$remote_sha" == "$DISPATCH_SHA" ]] || {
echo "::error::the pushed branch moved during dispatch: remote=$remote_sha workflow=$DISPATCH_SHA" >&2
exit 1
}
printf 'trusted source ref: %s\ntrusted source SHA: %s\n' "$DISPATCH_REF" "$DISPATCH_SHA"

- name: Checkout exact dispatch commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}

- name: Require exact checkout and clean source
env:
EXPECTED_SHA: ${{ github.sha }}
shell: bash
run: |
set -euo pipefail
actual_sha="$(git rev-parse HEAD)"
[[ "$actual_sha" == "$EXPECTED_SHA" ]] || {
echo "::error::checked out $actual_sha, expected dispatch SHA $EXPECTED_SHA" >&2
exit 1
}
[[ -z "$(git status --porcelain=v1 --untracked-files=normal)" ]] || {
echo "::error::source checkout is dirty before hydration" >&2
git status --short >&2
exit 1
}
source_tree_sha="$(git rev-parse 'HEAD^{tree}')"
ghostty_gitlink_sha="$(git rev-parse 'HEAD:ghostty')"
[[ "$ghostty_gitlink_sha" =~ ^[0-9a-f]{40}$ ]] || {
echo "::error::HEAD:ghostty is not a gitlink SHA" >&2
exit 1
}
printf 'source_sha=%s\nsource_tree_sha=%s\nghostty_gitlink_sha=%s\n' \
"$actual_sha" "$source_tree_sha" "$ghostty_gitlink_sha"

- name: Initialize Ghostty source submodule
shell: bash
run: |
set -euo pipefail
git submodule update --init --depth 1 ghostty
[[ "$(git -C ghostty rev-parse --show-toplevel)" == "$GITHUB_WORKSPACE/ghostty" ]] || {
echo "::error::ghostty did not initialize as its own submodule checkout" >&2
exit 1
}
expected_ghostty_sha="$(git rev-parse HEAD:ghostty)"
actual_ghostty_sha="$(git -C ghostty rev-parse HEAD)"
[[ "$actual_ghostty_sha" == "$expected_ghostty_sha" ]] || {
echo "::error::Ghostty checkout $actual_ghostty_sha does not match gitlink $expected_ghostty_sha" >&2
exit 1
}
[[ -z "$(git -C ghostty status --porcelain=v1 --untracked-files=normal)" ]] || {
echo "::error::Ghostty submodule is dirty after initialization" >&2
git -C ghostty status --short >&2
exit 1
}
test -f ghostty/build.zig.zon

- name: Install Linux build dependencies
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y clang libclang-dev pkg-config

- name: Cache Zig package downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/zig
key: cmux-tui-zig-${{ hashFiles('ghostty/build.zig.zon', 'ghostty/build.zig.zon.json') }}
restore-keys: |
cmux-tui-zig-

- name: Install repository-pinned Zig
shell: bash
run: ./scripts/install-zig-ci.sh

- name: Fetch Ghostty Zig dependencies without compiling
working-directory: ghostty
shell: bash
run: |
set -euo pipefail
# `--fetch` hydrates the package cache and exits before a build.
"$CMUX_ZIG" build --fetch

- name: Cache Cargo registry and git dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cmux-tui-cargo-${{ runner.os }}-${{ hashFiles('cmux-tui/Cargo.lock', 'cmux-tui/rust-toolchain.toml') }}
restore-keys: |
cmux-tui-cargo-${{ runner.os }}-

- name: Set up repository-pinned cmux-tui Rust
uses: ./.github/actions/setup-cmux-tui-rust

- name: Fetch Cargo dependencies without compiling
working-directory: cmux-tui
shell: bash
run: |
set -euo pipefail
cargo fetch --locked

- name: Record runner, toolchain, and Ghostty identity
env:
SOURCE_SHA: ${{ github.sha }}
SOURCE_REF: ${{ github.ref }}
TESTBOX_ID: ${{ inputs.testbox_id }}
RUNNER_LABEL: blacksmith-32vcpu-ubuntu-2404
shell: bash
run: |
set -euo pipefail
source_tree_sha="$(git rev-parse 'HEAD^{tree}')"
ghostty_gitlink_sha="$(git rev-parse 'HEAD:ghostty')"
ghostty_head_sha="$(git -C ghostty rev-parse HEAD)"
[[ "$SOURCE_SHA" == "$(git rev-parse HEAD)" ]] || exit 1
[[ "$ghostty_gitlink_sha" == "$ghostty_head_sha" ]] || exit 1
[[ -z "$(git status --porcelain=v1 --untracked-files=normal)" ]] || exit 1
[[ -z "$(git -C ghostty status --porcelain=v1 --untracked-files=normal)" ]] || exit 1
pushd cmux-tui >/dev/null
cargo metadata --locked --no-deps --format-version 1 > "$RUNNER_TEMP/cmux-tui-cargo-metadata.json"
test -s "$RUNNER_TEMP/cmux-tui-cargo-metadata.json"
RUST_TOOLCHAIN="$(rustup show active-toolchain)"
RUSTC_VERSION="$(rustc --version)"
CARGO_VERSION="$(cargo --version)"
popd >/dev/null
mkdir -p testbox-benchmark
SOURCE_TREE_SHA="$source_tree_sha"
GHOSTTY_GITLINK_SHA="$ghostty_gitlink_sha"
GHOSTTY_HEAD_SHA="$ghostty_head_sha"
ZIG_VERSION="$("$CMUX_ZIG" version)"
ZIG_PATH="$CMUX_ZIG"
RUNNER_UNAME="$(uname -a)"
RUNNER_CPU_COUNT="$(nproc)"
CARGO_METADATA_SHA256="$(sha256sum "$RUNNER_TEMP/cmux-tui-cargo-metadata.json" | cut -d ' ' -f 1)"
RUST_TOOLCHAIN_FILE_SHA256="$(sha256sum cmux-tui/rust-toolchain.toml | cut -d ' ' -f 1)"
CARGO_LOCK_SHA256="$(sha256sum cmux-tui/Cargo.lock | cut -d ' ' -f 1)"
GHOSTTY_ZON_SHA256="$(sha256sum ghostty/build.zig.zon | cut -d ' ' -f 1)"
export SOURCE_TREE_SHA GHOSTTY_GITLINK_SHA GHOSTTY_HEAD_SHA RUST_TOOLCHAIN RUSTC_VERSION CARGO_VERSION ZIG_VERSION ZIG_PATH RUNNER_UNAME RUNNER_CPU_COUNT CARGO_METADATA_SHA256 RUST_TOOLCHAIN_FILE_SHA256 CARGO_LOCK_SHA256 GHOSTTY_ZON_SHA256
python3 - <<'PY' > testbox-benchmark/setup-identity.json
import json
import os
import platform

print(json.dumps({
"schema": 2,
"source": {
"ref": os.environ["SOURCE_REF"],
"commit_sha": os.environ["SOURCE_SHA"],
"tree_sha": os.environ["SOURCE_TREE_SHA"],
"ghostty_gitlink_sha": os.environ["GHOSTTY_GITLINK_SHA"],
"ghostty_head_sha": os.environ["GHOSTTY_HEAD_SHA"],
},
"testbox": {
"id": os.environ["TESTBOX_ID"],
"setup_workflow_run_id": os.environ["GITHUB_RUN_ID"],
},
"runner": {
"label": os.environ["RUNNER_LABEL"],
"name": os.environ.get("RUNNER_NAME"),
"os": os.environ.get("RUNNER_OS"),
"arch": os.environ.get("RUNNER_ARCH"),
"hostname": platform.node(),
"uname": os.environ["RUNNER_UNAME"],
"cpu_count": int(os.environ["RUNNER_CPU_COUNT"]),
},
"toolchain": {
"rust_toolchain": os.environ["RUST_TOOLCHAIN"],
"rustc": os.environ["RUSTC_VERSION"],
"cargo": os.environ["CARGO_VERSION"],
"rust_toolchain_file_sha256": os.environ["RUST_TOOLCHAIN_FILE_SHA256"],
"cargo_lock_sha256": os.environ["CARGO_LOCK_SHA256"],
"cargo_metadata_sha256": os.environ["CARGO_METADATA_SHA256"],
"zig_path": os.environ["ZIG_PATH"],
"zig": os.environ["ZIG_VERSION"],
"ghostty_build_zig_zon_sha256": os.environ["GHOSTTY_ZON_SHA256"],
},
}, sort_keys=True, indent=2))
PY
test -s testbox-benchmark/setup-identity.json
cat testbox-benchmark/setup-identity.json

- name: Require clean hydrated source
shell: bash
run: |
set -euo pipefail
[[ -z "$(git status --porcelain=v1 --untracked-files=normal)" ]] || {
echo "::error::source became dirty during hydration" >&2
git status --short >&2
exit 1
}
[[ -z "$(git -C ghostty status --porcelain=v1 --untracked-files=normal)" ]] || {
echo "::error::Ghostty became dirty during hydration" >&2
git -C ghostty status --short >&2
exit 1
}

- name: Upload setup identity JSON
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cmux-tui-testbox-setup-${{ github.run_id }}
path: testbox-benchmark/setup-identity.json
if-no-files-found: warn
retention-days: 14

# Always hand control back to Testbox. Warmup is setup-only: all actual
# Rust builds are issued later with `blacksmith testbox run`.
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc # v2
if: always()
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ artifacts/

# tmux verbose debug logs (tmux -v) that land in the cwd
tmux-*.log

# Remote-only Blacksmith cmux-tui benchmark output
/testbox-benchmark/
/.cmux-scratch/
Loading
Loading