feat: add Docker sbx microVM runtime support#6101
Conversation
- Activate sbx in RUNTIME_REGISTRY (executionModel: microvm) - Gate agent service in compose-generator: skip when !runtimeUsesComposeAgent - New src/sbx-manager.ts: create/exec/wait/rm sandbox lifecycle - Wire sbx path in main-action.ts: infra-only compose, then sbx exec - Add sbx cleanup to buildCleanupFn - Update JSON schemas, spec doc, CLI help with sbx option - Add 4 sbx tests to container-runtime.test.ts (14 total) - Postprocess script injects --container-runtime sbx into lock file Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.32% | 📉 -0.76% |
| Statements | 99.04% | 98.29% | 📉 -0.75% |
| Functions | 99.46% | 98.66% | 📉 -0.80% |
| Branches | 95.77% | 95.05% | 📉 -0.72% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 90.9% (-9.10%) | 100.0% → 90.2% (-9.83%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 18.2% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
There was a problem hiding this comment.
Pull request overview
Adds experimental Docker sbx microVM support while retaining host-side infrastructure services.
Changes:
- Registers and documents the
sbxruntime. - Adds sandbox lifecycle and orchestration paths.
- Enables sbx in the smoke-workflow postprocessor.
Show a summary per file
| File | Description |
|---|---|
src/sbx-manager.ts |
Implements sbx lifecycle operations. |
src/container-runtime.ts |
Registers sbx capabilities. |
src/container-runtime.test.ts |
Tests runtime capability resolution. |
src/compose-generator.ts |
Omits the Compose agent for microVMs. |
src/commands/main-action.ts |
Routes execution through sbx. |
src/cli-options.ts |
Documents the new CLI value. |
src/awf-config-schema.json |
Adds sbx to the source schema. |
scripts/ci/postprocess-smoke-workflows.ts |
Injects runtime flags into smoke workflows. |
docs/awf-config.schema.json |
Adds sbx to the published schema. |
docs/awf-config-spec.md |
Documents sbx configuration. |
.github/workflows/smoke-gvisor.lock.yml |
Refreshes generated metadata. |
.github/workflows/smoke-docker-sbx.lock.yml |
Enables sbx in the generated smoke workflow. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 12/12 changed files
- Comments generated: 6
- Review effort level: Medium
| const services: Record<string, any> = { | ||
| 'squid-proxy': squidService, | ||
| 'agent': agentService, | ||
| ...(includeAgent ? { 'agent': agentService } : {}), |
| const args = [ | ||
| 'create', | ||
| '--name', name, | ||
| 'bash', // minimal template — we exec our own command |
| // Create the sandbox with workspace mounted, proxy chaining through Squid | ||
| const workspaceDir = process.env.GITHUB_WORKSPACE || process.cwd(); | ||
| sbxName = await createSandbox({ | ||
| workspaceDir, | ||
| squidIp: '172.30.0.10', | ||
| }); |
| await execa('sbx', args, { | ||
| env: { | ||
| ...process.env, | ||
| DOCKER_SANDBOXES_PROXY: proxyUrl, | ||
| }, |
| await execa('sbx', ['rm', '--force', name], { | ||
| stdio: ['ignore', 'pipe', 'pipe'], | ||
| reject: false, | ||
| }); | ||
| logger.info(`Sandbox "${name}" removed`); | ||
| } catch (error: any) { | ||
| logger.warn(`Failed to remove sandbox "${name}": ${error.message}`); |
| fi | ||
| # shellcheck disable=SC1003,SC2016,SC2086 | ||
| awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --build-local \ | ||
| awf --container-runtime sbx --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --build-local \ |
Adds Docker sbx CLI installation via apt (docker-sbx package) and KVM availability check before the AWF invocation step. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
✅ Copilot review passed with no inline comments. @lpcox Add the |
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.32% | 📉 -0.76% |
| Statements | 99.04% | 98.29% | 📉 -0.75% |
| Functions | 99.46% | 98.66% | 📉 -0.80% |
| Branches | 95.77% | 95.05% | 📉 -0.72% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 90.9% (-9.10%) | 100.0% → 90.2% (-9.83%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 18.2% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
|
@copilot address review feedback |
The sbx CLI uses subcommand style (sbx version) not flag style (sbx --version). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.32% | 📉 -0.76% |
| Statements | 99.04% | 98.29% | 📉 -0.75% |
| Functions | 99.46% | 98.66% | 📉 -0.80% |
| Branches | 95.77% | 95.05% | 📉 -0.72% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 90.9% (-9.10%) | 100.0% → 90.2% (-9.83%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 18.2% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
sbx requires Docker authentication even for basic sandbox creation. Uses DOCKER_PAT and DOCKER_USERNAME repo secrets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.32% | 📉 -0.76% |
| Statements | 99.04% | 98.29% | 📉 -0.75% |
| Functions | 99.46% | 98.66% | 📉 -0.80% |
| Branches | 95.77% | 95.05% | 📉 -0.72% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 90.9% (-9.10%) | 100.0% → 90.2% (-9.83%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 18.2% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
createSandbox() and execInSandbox() were spreading process.env into sbx CLI invocations, which could leak credential-bearing env vars into the sbx sandbox. Add sanitizeEnvForSbx() that strips env vars matching secret patterns (TOKEN, SECRET, PASSWORD, KEY, CREDENTIAL, PAT) before passing to sbx. Also removes redundant DOCKER_PAT env from the lock.yml login step. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.32% | 📉 -0.76% |
| Statements | 99.04% | 98.30% | 📉 -0.74% |
| Functions | 99.46% | 98.66% | 📉 -0.80% |
| Branches | 95.77% | 95.06% | 📉 -0.71% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 90.9% (-9.10%) | 100.0% → 90.2% (-9.83%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 29.4% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
Addressed in This update fixes the sbx review items: compose now skips agent-only helper services for microVM runtimes, sbx sandbox creation uses the |
sbx create is a host-side management operation that needs Docker auth credentials (stored on disk by sbx login). Only sbx exec (which runs commands inside the sandbox) gets the sanitized environment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.47% | 📉 -0.61% |
| Statements | 99.04% | 98.44% | 📉 -0.60% |
| Functions | 99.46% | 99.20% | 📉 -0.26% |
| Branches | 95.77% | 94.10% | 📉 -1.67% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 97.4% (-2.64%) | 100.0% → 96.5% (-3.45%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 56.8% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
Run a diagnostic check inside the sandbox after creation to verify: - Network interfaces and DNS config - Proxy connectivity to Squid - Direct connectivity (should fail if iptables are working) - Environment variables (sans secrets) Also adds logging around the agent command execution to help identify where hangs occur. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.47% | 📉 -0.61% |
| Statements | 99.04% | 98.44% | 📉 -0.60% |
| Functions | 99.46% | 99.20% | 📉 -0.26% |
| Branches | 95.77% | 94.07% | 📉 -1.70% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 97.5% (-2.48%) | 100.0% → 96.7% (-3.26%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 56.8% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
The previous run hung for 14 minutes with zero output after 'Configuring sbx daemon proxy'. This adds: - 30s timeouts on all sbx daemon operations (status/stop/start) - 2min timeout on sbx create - Per-step [sbx-daemon] log messages with stdout/stderr capture - Error output capture on sbx create failure - Connectivity diagnostics inside sandbox before agent launch Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.39% | 📉 -0.69% |
| Statements | 99.04% | 98.36% | 📉 -0.68% |
| Functions | 99.46% | 99.20% | 📉 -0.26% |
| Branches | 95.77% | 93.73% | 📉 -2.04% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 97.5% (-2.48%) | 100.0% → 96.7% (-3.26%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 58.3% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
sbx daemon start runs in foreground (blocks forever), not as a background service launcher. The previous approach of stopping and restarting the daemon was hanging the CI run for 14 minutes. Instead, pass DOCKER_SANDBOXES_PROXY as an environment variable directly to the sbx create command. The daemon is already running (started by sbx login/install), so we just need the proxy env var set when creating the sandbox. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
sbx auth requires a running daemon. The daemon is a foreground process that must be backgrounded with nohup+disown to survive across GitHub Actions steps. Added daemon startup with polling wait, then login, then auth verification. Also replaced diagnostic dumps with a proper auth pre-check that fails fast with an actionable error message. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
🔬 Smoke Test Results
Overall: PASS — sandbox is functional. @lpcox
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
🔬 OTEL Smoke Test Results
All 5 scenarios pass. OTEL tracing integration is fully operational. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
Thanks for the detailed description and accompanying tests/documentation. One contribution-guideline item is still missing: please reference any related issue(s), or state explicitly that there are none, per CONTRIBUTING.md → Pull Request Process → Pull request requirements (items 1–2). This will make the PR context complete for reviewers. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test Results: Gemini
Overall status: PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
|
@copilot fix the failing ci check https://github.com/github/gh-aw-firewall/pull/6101/checks?check_run_id=86573862557 |
Fixed in I removed the shell-based
|
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.35% | 📉 -0.73% |
| Statements | 99.04% | 98.28% | 📉 -0.76% |
| Functions | 99.46% | 99.07% | 📉 -0.39% |
| Branches | 95.78% | 93.80% | 📉 -1.98% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 93.2% (-6.85%) | 100.0% → 92.6% (-7.44%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 62.1% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
Use a redactSecret() helper that only reports whether a secret is set and its length, without passing the secret value into the log template literal. This avoids the js/clear-text-logging CodeQL alert. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
| Metric | Base | PR | Delta |
|---|---|---|---|
| Lines | 99.08% | 98.33% | 📉 -0.75% |
| Statements | 99.04% | 98.26% | 📉 -0.78% |
| Functions | 99.46% | 99.07% | 📉 -0.39% |
| Branches | 95.78% | 93.85% | 📉 -1.93% |
📁 Per-file Coverage Changes (2 files)
| File | Lines (Before → After) | Statements (Before → After) |
|---|---|---|
src/commands/main-action.ts |
100.0% → 92.6% (-7.44%) | 100.0% → 92.0% (-7.95%) |
src/log-directory-setup.ts |
96.2% → 100.0% (+3.78%) | 96.3% → 100.0% (+3.71%) |
✨ New Files (1 files)
src/sbx-manager.ts: 62.1% lines
Coverage comparison generated by scripts/ci/compare-coverage.ts
Revert to the original behavior from PR #6101: delete DOCKER_SANDBOXES_PROXY from the env during sbx create. Setting it forces the sbx daemon to route Docker Hub registry auth through Squid, which blocks the pull and causes 'user is not authenticated to Docker' errors. The proxy is set at exec time instead, which is after Squid is fully configured and ready. Also reverts the process.env change (security issue: would have leaked secrets like GITHUB_TOKEN, DOCKER_PAT etc to the sbx CLI). The sanitized env approach (sanitizeEnvForSbx) is the correct security boundary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…6153) * Set runtime to docker-sbx in smoke-docker-sbx workflow frontmatter Add sandbox.agent block with runtime: docker-sbx to all four smoke-docker-sbx workflow .md files, matching the pattern used by the gvisor smoke tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add sudo: true to sandbox.agent in smoke-docker-sbx workflows Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * compiled workflows * Fix sbx create crash: use pipe stdin instead of ignore execa throws 'Cannot read properties of null (reading end)' when input is provided but stdio[0] is 'ignore'. Change stdin to 'pipe' so the confirmation input can be written to the sbx create process. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Build AWF from source in smoke-docker-sbx-build-test to test sbx fix Replace the released binary install step with a source build so the stdin pipe fix in sbx-manager.ts is exercised in CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix sbx auth: preserve XDG_CONFIG_HOME for credential lookup Removing XDG_CONFIG_HOME from the environment passed to sbx create prevents the sbx CLI from finding Docker Hub credentials stored by sbx login, causing 'user is not authenticated' errors even though the daemon pre-pulled the image successfully. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * compiled workflows * compiled workflows * chore: build AWF from source in docker-sbx lock files (stdin pipe fix) The released AWF binary (v0.27.30) has a bug where execa crashes with 'Cannot read properties of null (reading end)' because stdio[0] is set to 'ignore' while input is provided. Build from source to include the fix (stdio[0] changed to 'pipe'). This is a temporary workaround until a new AWF version is released. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: include containers/ dir in source build install The compiled dist references ../containers/api-proxy/provider-env-constants.json relative to the dist/ directory. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(sbx): use process.env instead of custom env for sbx create Passing a custom sanitized env to execa replaces the entire process environment, which strips variables the sbx CLI needs to locate its daemon socket and credential store. This causes 'user is not authenticated to Docker' errors even when sbx login was run moments before. Instead, temporarily set DOCKER_SANDBOXES_PROXY in process.env and restore it after the call, letting sbx inherit the full environment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(sbx): don't set DOCKER_SANDBOXES_PROXY during sbx create Revert to the original behavior from PR #6101: delete DOCKER_SANDBOXES_PROXY from the env during sbx create. Setting it forces the sbx daemon to route Docker Hub registry auth through Squid, which blocks the pull and causes 'user is not authenticated to Docker' errors. The proxy is set at exec time instead, which is after Squid is fully configured and ready. Also reverts the process.env change (security issue: would have leaked secrets like GITHUB_TOKEN, DOCKER_PAT etc to the sbx CLI). The sanitized env approach (sanitizeEnvForSbx) is the correct security boundary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(sbx): inherit process.env for sbx create (management command) sbx create is a management command that talks to the local daemon — it does not run user code inside the sandbox. Passing a custom sanitized env strips variables the sbx CLI needs to locate the daemon's credential store, causing 'user is not authenticated to Docker' errors. The sandbox interior's env is controlled separately by execInSandbox() which still uses sanitizeEnvForSbx() to strip secrets. DOCKER_SANDBOXES_PROXY is temporarily removed during create to prevent routing Docker Hub registry auth through Squid (which isn't ready yet). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(sbx): remove XDG_CONFIG_HOME during sbx create The Copilot harness sets XDG_CONFIG_HOME=$HOME before running AWF. This makes the sbx CLI look for credentials in $HOME/ instead of the default $HOME/.config/ where 'sbx login' stored them (sbx login runs in a step before the harness, with XDG_CONFIG_HOME unset). Temporarily unset XDG_CONFIG_HOME during sbx create so the sbx CLI finds the daemon credentials at the correct default path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * chore: update smoke-docker-sbx.lock.yml to build AWF from source Add source build + credential refresh step to match the other lock files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * compiled workflows * compiled workflows * chore: build AWF from source in smoke-docker-sbx.lock.yml Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds
--container-runtime sbxsupport so the agent runs inside a Docker sbx microVM with hypervisor isolation, while infrastructure containers (Squid, api-proxy) stay in Docker Compose on the host.Architecture
Changes
Runtime registry & schema
sbxinRUNTIME_REGISTRYwithexecutionModel: 'microvm'src/anddocs/) with"sbx"enum valuedocs/awf-config-spec.mdand CLI help textCompose generator gating
compose-generator.tschecksruntimeUsesComposeAgent()— skips agent service for microVM backendssbx lifecycle manager (
src/sbx-manager.ts)createSandbox()—sbx createwith workspace mounts,DOCKER_SANDBOXES_PROXYset to SquidexecInSandbox()—sbx execto run agent command, collect exit coderemoveSandbox()—sbx stop+sbx rmcleanupisSbxAvailable()— preflight checkOrchestration wiring (
main-action.ts)containerRuntime === 'sbx': wrapsstartContainersto start infra-only compose then create sandboxrunAgentCommandto usesbx execinstead ofdocker logs/waitCI workflow
--container-runtime sbxintosmoke-docker-sbx.lock.ymlTesting
container-runtime.test.ts)Status
🧪 Experimental — sbx CLI installation and CI runner support TBD. This PR establishes the AWF code paths; the smoke workflow will be iterated on in follow-up commits.