Skip to content

v0.3.1 (2026-06-25)

v0.3.1 is an architecture-consolidation release. Where v0.3.0 re-architected server-side execution into a per-run-Job pipeline (backend run queue → dispatcher → driver), it left the CLI and the Tauri desktop app running test cases locally, through an in-process core runner backed by a host Docker/Podman. v0.3.1 removes that local execution path entirely: the CLI and the desktop app are now thin enqueue + watch clients of the backend run queue — exactly like the web console — so there is now a single way a run executes everywhere. The only remaining runner is the driver.

This is still pre-1.0 software intended primarily for project developers. v0.3.1 trades a capability — launching a run on your own machine with nothing but a container runtime — for a simpler, uniform topology: one execution path, one set of manifests, no second runner to maintain.

The CLI and desktop app no longer execute runs locally

Section titled “The CLI and desktop app no longer execute runs locally”

tcab and the Tauri app used to drive a run through the core on the host, against a local Docker/Podman. That path is gone. Both now POST the run to the backend’s /jobs queue, a dispatcher claims it and creates a per-run Kubernetes Job running the driver, and the launcher watches the run over GET /jobs/{id}/live. The practical consequences:

  • Neither needs a container runtime anymore. What they need instead is a reachable backend (TCAB_BACKEND_URL) and a logged-in account. For local development this means the k3d service stack must be uptcab run and the desktop app target it just as the web console does (see Running and the deployments/local/Makefile flow).
  • tcab run is now enqueue + watch. It prints the queued job id, streams the live event stream until the run finishes, and reads the produced run record back to print its summary. --auth-mode was added; --out-dir is now optional (the backend holds the artifacts — passing it only writes the fetched record JSON locally).
  • Removed flags and variables. --work-dir / TCAB_WORK_DIR and --orchestrator-dir are gone from tcab run; there is no host staging directory to point at and no local orchestrator-dir override (the run executes in the cluster). The CLI/desktop runner-side overrides (TCAB_CONTAINER_RUNTIME, TCAB_WORK_DIR, and the publishing release credentials) no longer apply to these clients.
  • push is a read-back confirmation. A backend-driven run’s record and artifacts are released by the driver during the run, so by the time an operator sees a produced run it is already stored. tcab push <run-id>… reads each run back by run id and reports whether the backend holds it and where its source and playable build resolve. review and publish likewise operate by backend run id. (This mirrors the web console, whose push is a near no-op.)

The local-only commands — seed, prompt, validate, catalog, harnesses, orchestrators, and the account commands login / register / logout — are unchanged.

The Tauri app’s local execution commands and its tcab-proof://, tcab-asset://, and playable-build URI schemes were removed. A run’s loadable media — proof artifacts and an asset-generation run’s images and action log — now loads over HTTP from the artifact service, exactly as in the browser (the backend reports its public URL via GET /config). The desktop CSP was opened to reach the backend, auth, and artifact origins.

The desktop UI now uses the same HTTP transport as the web console, promoted out of apps/web into the UI library and exported as @test-cabinet/ui/transport. The two consoles are now the same console over the same BackendClient, differing only in delivery (a desktop binary vs. a browser bundle).

A shared job-queue client (launch_run / job_status / list_active_jobs / watch_job / subscribe_notifications) and its wire types were added to the core, so the CLI and any other Rust client enqueue and watch runs through one contract.

The one thing the desktop app still runs locally, in-process is the adversarial arena — quick matches and tournaments are CPU-bound wasm the desktop plays itself — and its tcab-tournament:// scheme, which serves a tournament’s host-produced replay media, is kept. Everything else the desktop does is enqueue + watch + report over HTTP. See Arena.

Playable builds now load in the console iframe

Section titled “Playable builds now load in the console iframe”

The standalone artifact service was missing the trailing-slash /runs/{id}/build/ route — the exact URL the driver emits as a run’s playable-build link (it doubles as the build’s <base href>) and the one the console loads into its iframe. The worker served it, but the standalone service had only carried over /runs/{id}/build and /runs/{id}/build/{*path}, and axum’s {*path} capture rejects an empty path, so the build 404’d despite uploading fine. The route now maps to the build root.

Completed adversarial runs now appear in the arena

Section titled “Completed adversarial runs now appear in the arena”

A backend-driven adversarial run’s controller wasm is mirrored into the backend store at finalize so the run is pittable in the arena (Quick Match / tournaments) without being published. That upload was silently failing: the backend’s run-media upload routes inherited Axum’s default 2 MiB request-body limit, but a full-length proof replay serializes to tens of MiB (a time-limit Pac-Man match is ~20 MiB), so the canonical replay’s POST was rejected with 413 — and because the controller was uploaded after the replays, that rejection aborted the upload before the controller ever reached the store. The run completed and produced its replays, but never showed up alongside the baselines in the arena.

The run-media and tournament-replay upload routes now accept a generous body size, and the controller wasm is uploaded ahead of the replays in both the driver and the CLI push, so a run’s arena visibility no longer depends on its (much larger) replays uploading first. Runs produced before this fix need to be re-run to mirror their controller into the store.