# Changelog ## Unreleased ## 0.31.0 - 2026-11-07 Proxied Port tunnels no longer drop on reordered guest frames or on messages larger than the Runner can credit, and the SDKs split Port writes themselves. Deployments from v0.14.0 onward update in place with no database migration; public API v1, Runner protocol 7 and standard Profile revisions are unchanged. The amd64 Firecracker bundle rotates to `secondbox-0.31.0` and the gVisor materialization changes; the arm64 bundle is unchanged. See the [v0.31.0 release notes](docs/releases/v0.31.0.md). ### Upgrade notes - Before updating, check that `SECONDBOX_DATA_PLANE_MAXIMUM_SESSION_BYTES` (`overrides.data_plane_maximum_session_bytes`) is unset or at least 65,536: the control plane and `secondbox-deploy` now refuse a smaller value. Profiles that declare ports with `execution.streamWindowBytes` below 65,536 now refuse proxied PortSessions. Publish a revision with a larger window; an existing Sandbox stays pinned to its revision, so switch it to the new revision (`:switch-profile`, stopped and Snapshot-free) or recreate it. Neither configuration could carry a working proxied tunnel before. - The guest agent changes. amd64 Firecracker Sandboxes get the fix from the new `secondbox-0.31.0` bundle, gVisor Sandboxes from the new materialization, and client-selected images only when rebuilt with this release's image builder. The arm64 bundle is unchanged and keeps the old guest agent. ### Security - The amd64 Firecracker bundle's guest root filesystem is built from the 2026-10-26 Debian and Debian security snapshots and installs `ca-certificates` `20250419~deb12u2`, so workloads on the default image that use the system trust store no longer trust the compromised eMudhra intermediates once their Sandbox restarts on the new bundle. The arm64 bundle still trusts them. ### Fixed - The guest agent sent Port frames out of sequence when it returned write credit while forwarding guest output, and the Runner then failed the PortSession. It now sends them in order. - A proxied Port message larger than 65,536 bytes waited for Runner credit until the session expired on Profiles with a larger stream window; the control plane now closes the tunnel at once with "message too big". The TypeScript `PortTunnel.send` and Go `PortTunnel.Write` split writes into messages within that bound, so callers no longer chunk, and a failed TypeScript `send` now also rejects every later one instead of continuing a partly written stream. - A guest-side close of a Port was reported as a failed read, and the cause of every other Port failure was discarded. A guest close is now reported as closed on both transports, a guest frame that breaks the stream contract is reported as "guest port protocol violation", and the Runner logs the cause of failed Port opens, writes and reads. ## 0.30.0 - 2026-10-30 Clients can read the answering control-plane process's release identity from `GET /healthz` before using a server feature. Deployments from v0.14.0 onward update in place with no database migration; public API v1, Runner protocol 7, the Firecracker bundles, gVisor materialization inputs and standard Profile revisions are unchanged. See the [v0.30.0 release notes](docs/releases/v0.30.0.md). ### Added - `GET /healthz` reports the answering control-plane process's release `version` and `sourceCommit`, so a client can check that an independently operated server supports a feature, such as `capacityWaitMilliseconds` (0.28.0), before it sends a request that uses it. Before, the API protocol string and the client's own version were the only signals, and an older server rejected the newer request member only at start. The value does not describe connected Runners. ## 0.29.0 - 2026-10-27 SecondBox now builds with Go 1.26.10 and ships refreshed trust stores in response to the eMudhra intermediate CA compromise. Deployments from v0.14.0 onward update in place with no database migration; public API v1, Runner protocol 7, the Firecracker bundles and standard Profile revisions are unchanged. The gVisor materialization changes. The Firecracker guest trust store is not yet replaced. Production deployments can publish the API on host loopback without TLS. See the [v0.29.0 release notes](docs/releases/v0.29.0.md). ### Upgrade notes - Update the control plane, every Runner, image fetchers and the `secondbox` and `secondbox-deploy` binaries: binaries from v0.28.0 and earlier were built with Go 1.25.12. Update gVisor Runners to the new `gvisor-artifacts` materialization while each Runner is stopped. ### Added - Production accepts `tls_termination = 'loopback'`, which publishes the API without TLS on host loopback only: `public_base_url` must be exactly `http://127.0.0.1:` and `api_bind_ip` must be `127.0.0.1`, and no TLS terminator or DNS name is needed. Returned WebSocket endpoints are then `ws://127.0.0.1` URLs, which a client inside a container must rebase onto its own route to the API. Remote Runners keep their mTLS listener and are still allowed. Before, production required external TLS on an HTTPS `public_base_url` even when nothing reached the API from off the host, and `secondbox-deploy` applied standard resources over that URL, so a same-host installation had to route its own resource apply through a TLS ingress. `tls_termination = 'external'` is unchanged. ### Security - SecondBox builds with Go 1.26.10, whose `crypto/x509` distrusts the eight compromised eMudhra intermediate CAs whatever the host trust store says. Go 1.25 is out of support and gets no fix: a 1.25.12 binary given a trust store older than Debian `ca-certificates` `20250419~deb12u2` accepted certificates forged under those intermediates for any domain. The control-plane, Runner, gVisor and installer images move to `debian:12.15-slim` and the Go builders to `golang:1.26.10-bookworm`; the Microsandbox probe and helper lock `rustls-webpki` 0.103.16 and `webpki-roots`/`webpki-root-certs` 1.0.11. Release hosts need a Go 1.26.10 `RELEASE_GOROOT`. - The gVisor flat root moves from Alpine 3.22.5 to 3.22.7, whose `ca-certificates-bundle` 20261020 no longer trusts the compromised intermediates; 3.22.5 accepted a forged `dv-g3b-emudhra` chain. The gVisor materialization digest changes. - The guest root filesystem image definition requires a dated `debian.securitySnapshot`, and the build applies its updates to every installed package. Both committed definitions move to the 2026-10-26 Debian and Debian security snapshots, so a guest built from them carries `ca-certificates` `20250419~deb12u2`, which no longer trusts the compromised eMudhra intermediates. Bookworm is in LTS, where security fixes publish only to the security archive; the previous definitions resolved `ca-certificates` `20230311` and no bookworm security update since 2025-07-01. The Firecracker bundles are unchanged until a release ships bundles built from these definitions. ## 0.28.0 - 2026-10-25 A start can bound how long it waits for its home Runner to place it, compute-only quota refusals carry a retry hint, and two read operations report RunnerPool capacity without naming Runners. Deployments from v0.14.0 onward update in place; migration `0038_start_capacity_wait_deadline` adds one nullable column, so back up PostgreSQL first. Runner protocol 7, the Firecracker bundles, gVisor materialization inputs and standard Profile revisions are unchanged, and the Runners need no update. See the [v0.28.0 release notes](docs/releases/v0.28.0.md). ### Upgrade notes - Migration `0038_start_capacity_wait_deadline` adds a nullable column to `operations`. Back up PostgreSQL before updating; rollback requires the backup. Stop every v0.27.x control-plane process before v0.28.0 starts: an older lifecycle worker ignores the start deadline and can still place an expired start. ### Added - `POST /v1/sandboxes/{sandboxId}:start` accepts an optional `capacityWaitMilliseconds` (1 to 3600000) that bounds how long the start may wait for its home Runner to place it. A start still unplaced when the bound ends fails its Operation with the new retryable `capacity_wait_expired` problem, releases the Workspace mutation slot, and leaves the Sandbox wanted `stopped`. Before, a start deferred on a full or unavailable home Runner stayed pending indefinitely, refused stop and delete with `workspace_mutation_conflict` meanwhile, and booted compute whenever the Runner freed up, whether or not its caller was still waiting. `POST /v1/sandboxes` accepts the same member for the start a Profile with a `running` initial state performs after creation; on expiry the create Operation fails and the Sandbox remains, stopped. The Go `StartSandboxRequest` and `CreateSandboxRequest` and the TypeScript `start` and `createSandbox` options carry the new member. - `GET /v1/pool-capacity` (`getPoolCapacity`, application authority with `sandbox:read` or the platform token) and `GET /v1/tenant-pool-capacity` (`getTenantPoolCapacity`, tenant controller) report, for each RunnerPool the caller can place into or has Sandboxes in, the placeable Runner count, the allocatable and reserved Instances, vCPU, memory, and concurrent-operation slots of those Runners, the number of Sandboxes wanted running and not yet placed, and the number of connected Runners whose storage pressure denies admission. Figures aggregate every Tenant in the pool and name no Runner. The Go and TypeScript SDKs add `GetPoolCapacity`/`getPoolCapacity` and `GetTenantPoolCapacity`/`getTenantPoolCapacity`. ### Changed - A `429 quota_exceeded` refused only on compute dimensions (active Instances, vCPU, memory, or concurrent operations at Subject or Tenant scope, or a Profile's per-Sandbox execution concurrency) is now `retryable` and carries `retryAfterMilliseconds: 1000` and `Retry-After: 1`, as `503 home_runner_unavailable` already did. A refusal on Sandboxes, Snapshots, or port sessions is unchanged: not retryable and without a hint. So is a request that exceeds a compute limit on its own, such as a start under `maxActiveInstances: 0` or a Profile larger than the vCPU or memory limit, because no work ending frees it. ## 0.27.1 - 2026-10-18 Stopping or fencing a Sandbox releases the host blocks behind files its guest deleted, and a restarted Firecracker Runner re-seats its host firewall jumps above Docker's. Deployments from v0.14.0 onward update in place with no database migration; public API v1, Runner protocol 7, the Firecracker bundles, gVisor materialization inputs and standard Profile revisions are unchanged. See the [v0.27.1 release notes](docs/releases/v0.27.1.md). ### Fixed - A restarted Firecracker Runner moves its host `INPUT` and `FORWARD` jumps back above Docker's. Before, a Docker daemon restart re-inserted Docker's jumps above the surviving Runner jumps, and on a same-host Runner whose gateway is on an internal Docker network, Docker's isolation rule then dropped all guest egress to the gateway until the jumps were moved by hand ([#228](https://github.com/SecondStack-AI/SecondBox/issues/228)). - Stopping or fencing a Sandbox releases the host blocks behind files its guest deleted, so a Workspace's `allocatedBytes` falls and its measured `exclusiveBytes` falls by the released unshared extents. Before, freed ext4 blocks stayed allocated in the Workspace image indefinitely on both Firecracker and gVisor, and storage meters built on `exclusiveBytes` kept charging them ([SecondStack-AI/SecondStack#1441](https://github.com/SecondStack-AI/SecondStack/issues/1441)). Blocks still shared with a Snapshot remain held by the Snapshot. ## 0.27.0 - 2026-10-14 A RunnerPool's compute-backend seal can be cleared, so an installation can switch its Runners between Firecracker and gVisor in place and keep its Sandboxes and Workspaces. Deployments from v0.14.0 onward update in place with no database migration; Runner protocol 7, the Firecracker bundles, gVisor materialization inputs and standard Profile revisions are unchanged, and public API v1 gains one platform operation. See the [v0.27.0 release notes](docs/releases/v0.27.0.md). ### Added - `POST /v1/runner-pools/{runnerPoolName}:unseal-backend` (`unsealRunnerPoolBackend`) clears a RunnerPool's private compute-backend seal under `If-Match`, so an operator can switch an installation's Runners between Firecracker and gVisor while keeping their identity, WorkspaceStore, Sandboxes, and Workspaces; the next Runner to register seals the pool again. It refuses with the new `409 runner_pool_backend_in_use` while any Runner of the pool is connected or owns a live Instance, and it is audited as `runner_pool.backend_unsealed`. The Go and TypeScript SDKs add `UnsealRunnerPoolBackend` and `unsealRunnerPoolBackend`, and the CLI adds `secondbox runner-pools unseal-backend`. ## 0.26.1 - 2026-10-14 The first published release of the v0.26.0 changes. v0.26.0 was tagged, but its release staging smoke still ran `secondbox-deploy runner-template` without the now-required `--backend` and failed on both architectures before anything was published; v0.26.0 is retracted. See the [v0.26.1 release notes](docs/releases/v0.26.1.md). ### Fixed - Release staging smokes the Firecracker and gVisor Runner templates. ## 0.26.0 - 2026-10-14 (retracted, never published) `secondbox-deploy` deploys gVisor Runners for Linux amd64 hosts without KVM, same-host in Compose or remote, and every `[[runners]]` declaration now states `compute_backend`. Deployments from v0.14.0 onward update in place with no database migration; public API v1, Runner protocol 7, the Firecracker bundles and standard Profile revisions are unchanged. Add `compute_backend = 'firecracker'` to each existing Runner declaration before updating. A same-host Compose Runner now honours `storage_admission_mode`. See the [v0.26.0 release notes](docs/releases/v0.26.0.md). ### Upgrade notes - **Every `[[runners]]` declaration must state `compute_backend`.** Add `compute_backend = 'firecracker'` to each existing declaration before updating; validation refuses a declaration without it. Guided installs regenerate their manifest during the update and need no edit. ### Added - `secondbox-deploy` deploys gVisor Runners. A `[[runners]]` declaration states `compute_backend = 'firecracker'` or `'gvisor'`; a gVisor declaration carries the `gvisor_*` settings and none of the Firecracker ones, and the deploy CLI takes its materialization digest from the verified artifact manifest. A same-host gVisor Runner renders from `deploy/compose.same-host-gvisor-runner.yml`: privileged with the host cgroup namespace and a `/dev` bind but no KVM or TUN, on the deployment network in its own network namespace, and in a `secondbox--gvisor.slice` of its own so several deployments share one host. `runner-init` renders the gVisor environment for a remote Runner. `secondbox-deploy runner-template` takes `--backend firecracker|gvisor`. - `secondbox-deploy install` accepts `--sandbox-profile NAME`, `--control-reserve-vcpus N`, and `--control-reserve-memory-mib N` to size a new single-host installation for a smaller standard Profile and control reserve, so `--sandbox-profile agent-compartment --control-reserve-vcpus 1 --control-reserve-memory-mib 1024` installs on a 2 CPU / 2 GiB host. Without them the installer sizes for `durable-coding` with a 2 vCPU / 4 GiB reserve exactly as before. The post-install smoke boots `agent-compartment` only where a Runner can place it: that Profile permits attributed execution, which needs an attributed gateway that a guided install never configures, so an `agent-compartment` or `agent-compartment-isolated` sizing smoke boots only `agent-compartment-isolated`. Release installer qualification now uses these options, and its guests default to 4096 MiB and 4 vCPUs with a 2 GiB host reserve. ### Changed - Releases are cut by pushing a `vVERSION` tag on `main`: `release-tag.yml` builds the architecture-neutral files (SDKs, CLI and deploy binaries, OpenAPI document, SBOM, install bootstrap) once on a GitHub-hosted runner, qualifies and stages the amd64 and arm64 artifact sets in parallel on two self-hosted release runners from those files, uploads both to the draft once both are staged, and dispatches the publisher. `just release VERSION SHARED_DIR` and `just release-arm64 VERSION SHARED_DIR` take the shared directory that `scripts/release-build-shared.sh` produces and verify it against its `shared-artifacts.json` record, require the pushed tag instead of creating it, and `just release` drops `--full` and `--resume`; a failed job is recovered with a re-run of that job. The bundled PostgreSQL image is pinned in `scripts/release-build-shared.sh`, so release runners no longer set `SECONDBOX_RELEASE_POSTGRES_IMAGE`, and the publisher refuses artifact sets that pin different shared files. ### Fixed - The same-host Compose Runner receives `storage_admission_mode`. Before, a manifest selecting `physical` admission ran the same-host Runner with logical admission. - Sandbox creation, Snapshot clone, and Workspace relocation home a Profile that permits attributed execution only on a Runner advertising `per-exec-attribution`. Before, such a Sandbox could be homed on a Runner without an attributed gateway, and its start then waited with no Assignment and no error; creation now refuses with the existing retryable `home_runner_unavailable` when no Runner in the pool has an attributed gateway ([#217](https://github.com/SecondStack-AI/SecondBox/issues/217)). - The installer post-install smoke allows 15 minutes for each smoke Sandbox to become ready instead of the CLI's 5-minute interactive default, and gives each smoke Profile its own budget instead of one shared 10 minutes, so the first cold preparation of the release image on a minimum host no longer fails installation. ## 0.25.0 - 2026-10-12 SecondBox no longer signs release microVM bundles or client-selected execution images; integrity rests on digest pinning, the registry allowlist, Tenant authorization and the Runner's bundle checks. Deployments from v0.14.0 onward update in place with no database migration, but the Runner protocol advances to generation 7 exactly, so update the control plane and every Runner together. Delete the retired signing keys from `secondbox.toml` before updating, and materialize snapshot-resume templates from the new bundle before the updated Runners start. The execution bundle is the `secondbox-0.17.0` bundle without its signature files, and standard Profile revisions are unchanged. See the [v0.25.0 release notes](docs/releases/v0.25.0.md). ### Upgrade notes - Delete `deployment.execution_image_public_key`, `deployment.execution_image_public_key_sha256`, and each Runner's `execution_image_public_key`, `execution_image_public_key_sha256`, `artifact_public_key`, and `artifact_public_key_sha256` from `secondbox.toml` before updating; the strict decoder rejects them as unknown keys. Remote Runner and image-fetcher environments drop their `*_PUBLIC_KEY` and `*_PUBLIC_KEY_SHA256` variables. - **Update the control plane and every Runner together.** The Runner protocol advances to generation 7 exactly, because a generation 6 control plane rejects image preparation results without a signature; generation 6 and 7 peers refuse each other at connect. - Re-materialize every snapshot-resume template before resuming service. The template key no longer carries a signing-key fingerprint, so a Runner ignores templates built by earlier releases and does not advertise `snapshot-resume` until a new template is in its cache. ### Removed - **Breaking:** SecondBox no longer signs release microVM bundles or client-selected execution images. Bundles carry no `manifest.sig` or `signing.pub`, `verify.sh` and `just -f runner/Justfile verify-microvm-images` take only the artifact directory, the client image builder and release staging take no signing or public key, `secondbox-deploy init` no longer generates `secrets/execution-image.{pub,key}`, and the control plane imports component identities from the manifest the Runner returns for the digest-resolved image instead of verifying a publisher signature. Integrity rests on immutable digest pinning, the registry allowlist, Tenant authorization, and the Runner's `SHA256SUMS`, manifest, component-digest, and rootfs-contract checks. The Runner no longer advertises the `signed-artifacts` capability. Guided installs from earlier releases keep their recorded `secondbox.toml` until the update regenerates it, so uninstall and purge still work on them. ### Changed - The release artifact manifest schema is `secondbox.release/artifact-manifest/v7`: `microvm.bundleManifestDigest` replaces `microvm.signedManifestDigest`, and `microvm.signingKeyFingerprint` is gone. Recorded v5 and v6 manifests still decode, and the guided installer still reads install plans that recorded a signing-key fingerprint. - Runner protocol generation 7 removes and reserves `PrepareImageResult.signature` (field 7). ## 0.24.0 - 2026-10-05 A stopped Sandbox can switch to another compatible Profile while keeping its Workspace, and one Subject Sandbox policy now names a set of Profiles so a switched Sandbox keeps the same lifecycle and connection limits. The Subject policy body changes from `profile` to `profiles`. Deployments from v0.14.0 onward update in place; back up PostgreSQL first, because migration `0037_subject_sandbox_policy_profiles` makes rollback a database restore, and update Subject policy clients with the control plane. The Runner protocol remains `[6,6]` and peers connect in either update order; update Runners to receive the direct File transport fix. The execution bundle, trust anchor, and standard Profile revisions are unchanged. See the [v0.24.0 release notes](docs/releases/v0.24.0.md). ### Added - A release can publish an arm64 artifact set beside the amd64 one under the same tag: `secondbox-VERSION-arm64-artifact-manifest.json` with its linux-arm64 scenario evidence and `standard-arm64` standard bundles, and the control-plane, Runner, installer-tools and microVM-artifact images under `vVERSION-arm64` tags. It shares the SDKs, binaries, OpenAPI document and install bootstrap with the amd64 set. `just release-arm64 VERSION AMD64_RELEASE_DIR` stages it on a Linux arm64 KVM host, which uploads it before the amd64 host uploads its own output and starts the publisher. A release without an arm64 set publishes amd64 alone. - `POST /v1/sandboxes/{sandboxId}:switch-profile` repins a stopped Sandbox to another Profile's current revision while keeping its ID, Workspace, home Runner, resources, and egress-context pin. The next start builds its Instance from the new revision, so an application can, for example, move a conversation's Workspace between an online and an offline network policy without losing its files. The Sandbox must be stopped with no retained Snapshot, the target must share its RunnerPool, architecture, startup mode, and egress-context requirement and admit its resources, and an application authority must hold grants for both Profiles. Lifecycle policy re-resolves against the target. Refusals use the new `profile_incompatible` (with the mismatching property as a detail) and `profile_switch_snapshots_present` codes. The Go and TypeScript SDKs add `SandboxHandle.SwitchProfile` and `switchProfile`, and the CLI adds `secondbox sandboxes switch-profile` ([#208](https://github.com/SecondStack-AI/SecondBox/pull/208)). ### Upgrade notes - Migration `0037_subject_sandbox_policy_profiles` rewrites every stored Subject Sandbox policy from `profile` to a one-element `profiles` set, without changing its values or the Subject revision. Rolling back requires the pre-update database backup. - Control planes before and after the migration do not share Subject Sandbox policies: an older control plane ignores a migrated policy and resolves Profile defaults, and a policy it writes with `profile` is ignored by an updated one. Stop every older control-plane process before the migration runs, pause Subject policy writes, update the control plane, then resume them only from clients that send `profiles`. Runners are unaffected by this migration. ### Changed - **Breaking:** a Subject Sandbox policy selects a set of Profiles. `PUT /v1/subjects/{subjectRef}/sandbox-policy` takes `profiles` (1 to 32 unique Profile names) instead of `profile`, and the observation's `desired` carries the same field. One lifecycle and attributed-connection selection applies to every named Profile at creation, at each attributed Assignment, and when a Profile switch re-resolves lifecycle, so a Sandbox switched between two named Profiles keeps the same limits. Every named Profile must be granted to the Tenant, enabled, and admit the selection; an unchanged block may still be preserved above a tightened ceiling only for a Profile the stored policy already named. The PUT response observes the first named Profile, and `GET ?profile=` observes any. The Go and TypeScript SDK `SubjectSandboxPolicy` types follow the new shape ([#208](https://github.com/SecondStack-AI/SecondBox/pull/208)). - `releasecontract.ArtifactManifestLocation` and `QualificationEvidenceLocation` take the guest architecture. Files of a non-amd64 artifact set, including its standard bundles, staging checksums and OCI archives, carry the architecture suffix; amd64 names are unchanged. ### Fixed - A File read over the direct data-plane transport intermittently failed with `500 internal_error` after the Runner had delivered the complete result. The Runner closed the connection right after the terminal, so the credit that the control plane granted for the last chunk failed to send, or the reset discarded the unread terminal. The Runner now keeps reading until the control plane closes, and a File request whose transport is lost while it sends chunks or credit waits for the terminal ([#203](https://github.com/SecondStack-AI/SecondBox/issues/203)). - A failed write to a direct data-plane connection now reports the retryable `execution_node_unavailable`, as on the proxied transport, instead of `internal_error`. - An application authority's Profile grant for exec, file, Terminal, and Port requests is now checked again inside admission, against the Sandbox's Profile as read under the admission lock. It was previously checked only on an earlier read, which a concurrent Profile switch could make stale. An idempotent replay of such a request now also requires grants for both the Profile its session was admitted under and the Sandbox's current Profile, so it cannot disclose a session another authority of the subject admitted under a Profile the caller lacks ([#208](https://github.com/SecondStack-AI/SecondBox/pull/208)). ## 0.23.0 - 2026-10-04 Linux arm64 hosts can run the Firecracker Runner from a locally staged arm64 release. The published release remains amd64: deployments from v0.14.0 onward update in place with no migration, and public API v1, Runner protocol `[6,6]`, the execution bundle, trust anchor, and standard Profile revisions are unchanged. Image build automation must now name its architecture. See the [v0.23.0 release notes](docs/releases/v0.23.0.md). ### Added - Linux arm64 hosts can run the Firecracker Runner. The guest kernel, microVM bundle, Runner image, and client execution-image builder take an explicit `amd64` or `arm64` architecture, an arm64 Debian definition builds the standard guest rootfs, and standard Profiles of an arm64 release bind the new `standard-arm64` RunnerPool. `init --mode development`, `just test-scenario`, and `just prepare-stress` follow the host architecture, and snapshot-resume templates load on arm64. `RELEASE_GUEST_ARCHITECTURE=arm64` stages an arm64 release qualified by `linux-arm64` scenario evidence; it carries no gVisor artifacts and no installer qualification evidence, because gVisor and the guided single-host installer remain amd64-only, and the installer refuses arm64 releases ([#201](https://github.com/SecondStack-AI/SecondBox/pull/201)). ### Changed - The image build scripts require an explicit architecture, also for amd64. `scripts/build-client-execution-image.sh` requires `SECONDBOX_CLIENT_IMAGE_ARCHITECTURE`, and the microVM kernel, rootfs-source and bundle builders require `SECONDBOX_RUNNER_MICROVM_ARCHITECTURE`. A prepared rootfs source built before this release records no architecture and must be rebuilt. The amd64 release manifest format and standard Profile revisions are unchanged, and amd64 releases keep the existing signed execution bundle. - The Go helpers `standardresources.Documents`, `ProfileLineage` and `DevelopmentProfileLineage`, and `releasecontract.QualificationEvidence.ValidateForRelease`, take the guest architecture. - `secondbox-deploy init --mode development` writes the standard pool of the machine that runs it, so an arm64 machine no longer generates an amd64 development topology. - Release verification requires scenario qualification evidence from a host of the release guest architecture. ### Fixed - `BUILD_KERNEL=true` microVM builds compiled the pinned kernel twice, the second time under `fakeroot`. - The development tenancy bootstrap wrote its controller and application request bodies over the CLI session files of the same name, so the controller CLI failed with `json: unknown field "expiresAt"`. ## 0.22.0 - 2026-10-02 Sandbox operations run concurrently, disconnected callers cancel their guest work, and cancellation releases operation quota even after a lost connection or an evicted terminal. Sandbox starts also spend less time waiting on control-plane quota bookkeeping. Deployments from v0.14.0 onward update in place; back up the database before updating the control plane and Runners. The Runner protocol remains `[6,6]`, and the execution bundle, trust anchor, and standard Profile revisions are unchanged. See the [v0.22.0 release notes](docs/releases/v0.22.0.md). ### Upgrade notes - Migrations `0035_live_sandbox_quota_index` and `0036_sandbox_quota_ledger_on_increase` add an index and replace the Sandbox quota-ledger trigger. Rolling back to v0.21.0 requires the pre-update database backup. ### Changed - Sandbox starts no longer queue behind each other on the control plane's quota bookkeeping. Each start used to take its Tenant's quota lock in about twenty separate transactions, so every start in a Tenant ran one at a time; now only admission and transitions that increase quota usage take it. On the lifecycle benchmark a burst of 32 starts finished at 1.8 s p50 and 2.7 s p95, down from 3.2 s and 4.4 s. Placement also locks only the Sandbox's home Runner rather than every Runner in the pool, runs without serializable isolation (which aborted and retried most placements in a burst), and releases the lifecycle claim in the same transaction. A starting Sandbox now waits for its Runner's evidence instead of being re-checked every 250 ms, and the lifecycle worker places the Sandboxes it claims concurrently. - Quota admission reads a partial index of live Sandboxes, so its cost no longer grows with the number of deleted Sandboxes a Tenant has retained. ### Fixed - Interrupted File uploads now discard their buffered content and complete cancellation before a backend worker starts. Delivered cancellation commands replay after the Runner reconnects, and a cancellation whose original terminal was evicted now confirms that no worker owns the fenced operation. Late Open frames cannot revive the cancelled operation ([#198](https://github.com/SecondStack-AI/SecondBox/pull/198)). - A stalled direct Exec, File, or PTY peer no longer blocks control-connection heartbeats or cancellation confirmation. Direct writes use their own connection lock and time out after five seconds, bounded by their delivery deadline ([#198](https://github.com/SecondStack-AI/SecondBox/pull/198)). - A cancelled exec, file, or Terminal operation could keep its session `cancelling`, and so its concurrent-operation slot, until the Sandbox stopped; on a Profile that allows one concurrent operation, every later exec and file operation failed with `429 quota_exceeded`. On Profiles with the direct data-plane transport the Runner closed the operation's connection as soon as it applied the cancellation, before the cancelled result was sent, and on either transport a result that arrived after the request had stopped waiting for it was dropped. The Runner now also reports a cancelled operation's result on its control connection, where the control plane completes the session, and a stopped direct-transport exec stream still receives its `cancelled` outcome. Control plane and Runners can be updated in either order; direct-transport sessions are confirmed once both are updated. - A client that disconnected from a buffered exec, such as a caller cancelling its HTTP request, left the guest command running to completion; its result was then discarded and the session ended only at its deadline. Disconnecting from a buffered exec or file operation now cancels the guest work as a streaming exec disconnect does, and the session records `cancelled`. - On Firecracker and gVisor Runners, a running exec or an open Terminal held up every other exec, file, and Terminal operation on the same Sandbox until it ended; a file operation behind a long command could fail with `409 operation_deadline_exceeded`. The Runner sent every operation over one shared guest stream and let only one use it at a time. Each operation now has its own guest stream, as Ports already did, so operations on one Sandbox run concurrently. - A proxied PortSession to an approved guest port with no listener dropped its home Runner's whole control-plane connection, cancelling every in-flight exec, file, terminal, and Port operation on that Runner across all tenants (clients saw `409 execution_node_unavailable`) ([#194](https://github.com/SecondStack-AI/SecondBox/pull/194)). The Runner refused the Open but kept no record of the stream, so the Credit frame the control plane sends behind every Open was treated as a protocol violation. The PortSession now ends by itself with a `guest port is unavailable` tunnel close, and the Runner connection stays up. The same fix applies to Exec and File Opens refused for capacity, File write chunks behind a refused Open (which could also start the refused write), and PTY input, resize, or credit that arrives after the terminal's process has exited. ## 0.21.0 - 2026-09-30 Attributed execution moves from a dedicated single-command generation to one exec inside an ordinary running Sandbox. This is a breaking API, runner protocol, and schema change. Deployments from v0.14.0 onward update in place; configure the attributed gateway route on every Runner first, drain attributed traffic, update the control plane and all Runners together, and keep the pre-update database backup for rollback. See the [v0.21.0 release notes](docs/releases/v0.21.0.md). ### Upgrade notes - **Update the control plane and every Runner together.** The runner protocol advances to generation 6 exactly; generation 5 Runners and control planes refuse each other. Runners replace the `attributed-execution` capability with `per-exec-attribution`. - **Drain attributed traffic first.** Let in-flight attributed starts and their commands finish before updating: migration `0034_per_exec_attribution` drops `assignments.execution_authorization_ref`, `execution_expires_at`, and `execution_session_id`, and adds `execution_authorization_ref` and `execution_expires_at` to `data_plane_sessions`. The migration is forward-only; rolling back requires the pre-update database backup. - **Route every permitting Profile's gateway.** Every Assignment of a Profile with `attributedExecution`, such as the standard `agent-compartment`, now carries the gateway permission and requires a home Runner that advertises `per-exec-attribution` and routes that gateway through `attributed_socket` in the Sandbox's pinned egress context. Configure the route on every Runner that hosts such Sandboxes; without it they no longer start. - **Move attribution from start to exec.** Clients start the Sandbox normally and pass `attributedExecution` on the buffered or streaming exec that needs it. Stopping the Sandbox before a credentialed command is no longer needed. ### Changed - **Breaking:** `StartSandboxRequest.attributedExecution` is removed, and `BufferedExecRequest` and `StreamingExecRequest` accept `attributedExecution {authorizationRef, expiresAt}` instead. An attributed exec runs in the Sandbox's ordinary generation with its ordinary network policy, Runner gateways, terminals, Ports, and file writes; the Instance is not stopped or fenced afterwards, and one generation admits any number of sequential or concurrent attributed execs. Admission requires the `sandbox:exec` scope and Profile grant, Profile permission, a pinned Tenant egress context, an expiry within the Profile execution deadline that the exec deadline does not outlive, and a home Runner advertising `per-exec-attribution` (otherwise `503 home_runner_unavailable`). The binding is stored with the exec session and covered by its idempotency key. Attributed exec streams use the proxied transport. The Go and TypeScript SDKs follow the new request shape ([#192](https://github.com/SecondStack-AI/SecondBox/pull/192)). - Each attributed exec gets its own Runner listener on the Instance interface, added to and removed from the Instance firewall policy for that exec only, and `SECONDBOX_EXECUTION_GATEWAY` is injected into that exec alone. The listener and its relays close when the exec ends, is cancelled, reaches its deadline or `expiresAt`, or loses its control-plane connection. While the window is open, any process in the Instance can reach the listener under that exec's identity; this is an accepted residual risk, because the sandbox never holds real credentials and the gateway and Integrations authorize every credentialed request. The `SBXATTR1` identity preface is unchanged ([#192](https://github.com/SecondStack-AI/SecondBox/pull/192)). ## 0.20.0 - 2026-09-29 Port sessions stream entirely in memory and live as long as their Lease is renewed, application data-plane requests enforce Profile grants, and grants can be extended in place. Deployments from v0.14.0 onward update in place; update the control plane and all Runners together, and audit application authority Profile grants first: missing grants can be added in place only after the update. Rolling back requires the pre-update database backup. See the [v0.20.0 release notes](docs/releases/v0.20.0.md). ### Security - Application data-plane requests (exec, terminals, files, directories, Port sessions) require the addressed Sandbox's Profile to be in the authority's `profileGrants` ([#182](https://github.com/SecondStack-AI/SecondBox/pull/182)). Previously grants gated only Profile reads and Sandbox creation, so an application could exec into a Sandbox that the platform or another authority had created in its subject on a Profile it was never granted. Reads, listing, Leases, and lifecycle remain subject-scoped. ### Added - Added add-only `extendTenantCeiling` (`POST /v1/tenants/{tenantRef}:extend-ceiling`) and `extendApplicationAuthority` (`POST /v1/application-authorities/{authorityId}:extend`), with `secondbox tenant extend-ceiling` and `secondbox application-authority extend`, so existing installations can grant a new Profile or scope such as `sandbox:ports` without re-creating authorities or rotating their credentials ([#181](https://github.com/SecondStack-AI/SecondBox/pull/181)). Both are revision-fenced and idempotent, and an authority extension must stay within its Tenant ceiling. ### Fixed - A proxied PortSession whose tunnel closed while the Runner waited for credit stayed `cancelling` forever and kept counting toward `concurrentOperations`, eventually refusing `exec` with `quota_exceeded` ([#180](https://github.com/SecondStack-AI/SecondBox/pull/180)). The Runner now reports a terminal on every Port pump exit; the data-plane sweep completes cancelling sessions whose Assignment has ended and closed PortSessions the Runner has not confirmed within 10 seconds, and an unconsumed proxied PortSession completes when it closes. Existing stuck sessions converge at the first sweep after upgrade. - A Runner could send a Port stream's bytes and credit frames out of sequence order when the guest and the client were both active ([#180](https://github.com/SecondStack-AI/SecondBox/pull/180)). The control plane then dropped the whole Runner connection, ending every data-plane session on that Runner. Port frames now leave in sequence order. - Proxied Port traffic no longer touches PostgreSQL while it streams ([#183](https://github.com/SecondStack-AI/SecondBox/pull/183), [#186](https://github.com/SecondStack-AI/SecondBox/pull/186)). Each chunk previously ran one or two transactions under the Tenant and Subject quota locks inside the serial Runner connection loop, and each bumped the Sandbox `revision` (65,490 revisions in 30 minutes for one viewer), which also starved lifecycle reconciliation with revision conflicts. Payload, credit, and sequence state now live in memory; PostgreSQL records only a session's admission, consumption, close, and terminal outcome, and the close stamps `lastActivityAt`. Data-plane traffic, including a direct Port connection, no longer changes the Sandbox `revision`. Migrations `0032_port_stream_accounting` and `0033_port_stream_in_memory` drop the retired `port_sessions` credit, acknowledgement, and byte-count columns. - A proxied Port client that wrote ahead of the Runner's credit slept one data-plane poll interval (250 ms by default) before retrying, which collapsed interactive streams near the stream window to seconds of latency ([#184](https://github.com/SecondStack-AI/SecondBox/pull/184)). The client-to-guest direction now waits for the credit frame itself. - The Firecracker and gVisor Port relay applies credit backpressure to the guest instead of granting credit on every read and closing the connection when 16 guest frames were queued ([#180](https://github.com/SecondStack-AI/SecondBox/pull/180)). A consumer slower than the guest application now slows it within a fixed 256 KiB receive window, and fast streams are no longer disconnected. - A Runner could send a terminal output frame through an attachment that had just been detached, after the replacement attachment's replay on the same control-plane stream ([#189](https://github.com/SecondStack-AI/SecondBox/pull/189)). The control plane rejected the out-of-sequence frame and dropped the Runner's connection. A detached attachment no longer sends; the replacement's replay already carries that frame. ### Changed - Port sessions no longer carry a per-session byte limit ([#186](https://github.com/SecondStack-AI/SecondBox/pull/186)). Proxied sessions were capped at the smaller of `execution.maximumTransferBytes` and `SECONDBOX_DATA_PLANE_MAXIMUM_SESSION_BYTES` (256 MiB by default), which ended a live browser view after about 12 minutes; direct sessions never had one. Both limits still apply to Exec and File sessions. - A PortSession lives while the Lease it was admitted under is renewed, bounded by its `durationSeconds` and the Port policy's `maximumSessionSeconds`, instead of being capped at creation by the Lease's remaining grant ([#185](https://github.com/SecondStack-AI/SecondBox/pull/185)). It still ends when the Lease is released, lapses, or is fenced. Long-lived viewers under a 60-second Lease no longer have to reconnect every minute. The Go SDK's `ForwardPort` and `secondbox ports forward` request the policy's full duration for each connection. - The client execution image builder requires `SECONDBOX_CLIENT_IMAGE_BROWSER_POLICY` (`forbid` or `allow`) instead of always forbidding browsers, so an application can ship a headless browser in its own signed userspace ([#179](https://github.com/SecondStack-AI/SecondBox/pull/179)). Set it to `forbid` to keep the previous behavior. - `invalid_request` problems name the refused input in `details`: the JSON field path (`spec.resources.memoryBytes`), the header (`If-Match`, `Idempotency-Key`, `X-SecondBox-Tenant-Ref`), or the path or query parameter (`limit`, `cursor`), with a short reason ([#190](https://github.com/SecondStack-AI/SecondBox/pull/190)). A problem with exactly one such detail uses it as the `title`, so SDK and CLI error messages show it; others keep `Request is invalid`. Previously only whole-MiB alignment refusals carried a detail. Profile alignment details now use the request's `spec.` prefix (`spec.resources.memoryBytes`). ## 0.19.0 - 2026-09-27 Sandboxes survive execution bundle upgrades. Profiles no longer pin runtime and toolchain digests; every Instance boots the signed bundle its home Runner has installed, so a release with a new bundle updates in place and existing Sandboxes keep their Workspaces. Deployments from v0.14.0 onward update in place; update the control plane and all Runners together. See the [v0.19.0 release notes](docs/releases/v0.19.0.md). ### Changed - Removed `runtimeBundleDigest` and `toolchainBundleDigest` from `ProfileRevisionSpec` ([#177](https://github.com/SecondStack-AI/SecondBox/pull/177)). Requests that still send them are rejected; migration `0031_profile_execution_assets_unpinned` removes them from recorded revisions. Client-selected images keep their signed component identities. - Default-image Assignments carry no asset references, and placement requires a verified materialization for the Runner's backend rather than exact bundle digests. The guided updater no longer refuses updates that change the Firecracker bundle; it refuses sources older than the v0.14.0 migration baseline before activation. - Standard bundle documents moved to `secondbox.standard-bundle/v4` without bundle digests. Standard Profile lineages keep their revision numbers, so installed histories from v0.14.0 onward converge after migration; their spec digests change. `secondbox resources check` reports the computed digest of a mismatched revision. Release verification binds bundle documents by digest, which lets the updater authenticate releases published under the older schema. ### Removed - Removed the control-plane signed-asset catalog: `SECONDBOX_SIGNED_ASSET_CATALOG_PATH`, the `deployment.signed_asset_catalog` manifest key, the Compose catalog mount, and the installer-written `signed-assets.json`. Delete the key from operator manifests; an existing installation's catalog file is left for purge. ## 0.18.1 - 2026-09-26 Firecracker and gVisor Runners can opt into physical Workspace storage admission. Logical Workspace limits can exceed the Runner's aggregate advertised disk capacity while individual Workspace ceilings, other capacity limits, and filesystem pressure checks remain active. This release retains protocol generation 5, the v0.15.0 migration baseline, standard Profile revisions, and the v0.17.0 signed Firecracker bundle; see the [v0.18.1 release notes](docs/releases/v0.18.1.md). ### Added - Added explicit `logical` or `physical` storage admission for Firecracker and gVisor Runners, including the reference Kubernetes pod. Physical mode admits against measured Workspace filesystem use with warning, denial, recovery, and probe-failure behavior, without charging aggregate logical Workspace reservations ([#175](https://github.com/SecondStack-AI/SecondBox/pull/175)). ### Fixed - Kept each backend's individual Workspace size ceiling during physical-mode placement, checked fresh relocation imports before staging storage, and preserved committed command and import replay under pressure ([#175](https://github.com/SecondStack-AI/SecondBox/pull/175)). - Made the scenario image fetcher's DNS server explicit so isolated Docker networks can resolve the signed selected-image registry on hosts whose local resolver is unreachable from those networks. ## 0.18.0 - 2026-09-26 gVisor Runners launch client-selected execution images, so applications that select a signed image on every create and start run without KVM. Every gVisor Runner now requires the image fetcher, a reflink-capable execution image cache, and the publisher key. The v0.17.0 Firecracker bundle, protocol generation 5, and the migration baseline are unchanged, so Firecracker deployments update in place; see the [v0.18.0 release notes](docs/releases/v0.18.0.md). ### Added - Added client-selected execution images to the gVisor backend. The Runner verifies the same signed OCI artifact Firecracker consumes through the shared verifier, reflinks its `rootfs.ext4` into an unnamed per-Instance clone, and mounts it read-only as the sandbox root ([#173](https://github.com/SecondStack-AI/SecondBox/pull/173)) while its pinned `runsc` and guest agent launch it. As under Firecracker, guests may write to that root; the writes stay in `runsc`'s in-memory overlay. gVisor Runners advertise `client-selected-image` readiness and serve `images:prepare`; the runner protocol is unchanged. - Added the unprivileged image fetcher and Skopeo to the `runner-gvisor` image, and an `image-fetcher` container with a node-local cache and per-Tenant registry Secret to the reference gVisor pod. ### Changed - gVisor Runners require `SECONDBOX_RUNNER_EXECUTION_IMAGE_CACHE_ROOT`, `SECONDBOX_RUNNER_IMAGE_FETCHER_SOCKET`, `SECONDBOX_RUNNER_EXECUTION_IMAGE_PUBLIC_KEY`, and `SECONDBOX_RUNNER_EXECUTION_IMAGE_PUBLIC_KEY_SHA256`. The cache must support reflink and be disjoint from `SECONDBOX_GVISOR_RUNTIME_DIR`; a Runner without them, or whose cache cannot reflink, does not start. Deploy the fetcher beside each gVisor Runner before upgrading. ## 0.17.0 - 2026-09-25 Delegated attributed connection limits and accurate full-Workspace file errors ship with a newly signed Firecracker bundle. The bundle changes the pinned runtime and toolchain identities, so existing deployments must reinstall and recreate resources; see the [v0.17.0 release notes](docs/releases/v0.17.0.md). ### Added - Added a finite attributed TCP connection default and ceiling to Profile revisions and optional Subject selection. The latest standard `agent-compartment` revision grants a default of 128 and a ceiling of 4096. The numeric policy resolves on each new Assignment; active connections and pinned gateway authority remain unchanged ([#169](https://github.com/SecondStack-AI/SecondBox/pull/169)). ### Fixed - Returned HTTP 507 `workspace_full` when a file mutation reaches Workspace capacity, including guest `ENOSPC` and `EDQUOT`, so clients can remove files and retry ([#168](https://github.com/SecondStack-AI/SecondBox/pull/168)). ## 0.16.0 - 2026-09-22 Firecracker and gVisor guest executions receive the resolved Runner gateway endpoints in `SECONDBOX_RUNNER_GATEWAYS`. This release keeps Runner protocol generation 5, the v0.15.0 database schema, and the signed v0.12.0 fixed-Profile bundle, so v0.15.0 deployments update in place; see the [v0.16.0 release notes](docs/releases/v0.16.0.md). ### Added - Added `SECONDBOX_RUNNER_GATEWAYS` to the Firecracker and gVisor guest execution environment, publishing each logical gateway the Sandbox's pinned egress context resolved as a sorted `logicalName=address:port` entry, so applications no longer configure a Runner-host address themselves. ### Removed - Removed the microVM rootfs secret scan and stopped emitting its `secretScanPolicySha256` provenance field; the golden rootfs is built only from Docker layers and never receives per-Sandbox runtime secrets. Existing signed bundles that still carry the field keep verifying. ## 0.15.0 - 2026-09-18 Applications can select and prepare signed execution images while retaining durable Workspaces. This release uses Runner protocol generation 5 and preserves the signed v0.12.0 fixed-Profile bundle. Upgrade the control plane, Runners, and image fetchers together; see the [v0.15.0 release notes](docs/releases/v0.15.0.md). ### Added - Added optional signed execution images on Sandbox create and start, tenant-scoped registry retrieval through an isolated image fetcher, and `images:prepare` to warm eligible Runners before first use. - Added `just release VERSION --resume`, which reuses a retained checksummed build and commit-exact scenario evidence after a gate-only failure, requalifies the gates, and continues from the installer candidate. ### Fixed - Preserved the exact published v0.14.0 migration baseline during forward upgrades without rewriting its recorded history. - Preserved the requested image reference and successful digest pin across image replacement, and corrected preparation progress and Runner selection. - Avoided repeated full-image hashing on warm starts, protected active image cache entries from eviction, bounded cache metadata, and validated reflink storage placement at startup. - Recovered lifecycle operations after terminal startup failure and retained revisions during unchanged effect waits. - Preserved release notes through draft upload, retries, and stable publication, with the install and SDK footer rendered as code. - Backfilled changelog sections for 0.4.5 through 0.14.0, release notes for v0.10.0, v0.10.1, v0.11.0, and v0.13.0, and the GitHub release descriptions that the publisher had overwritten. ## 0.14.0 - 2026-09-15 Added exclusive Workspace storage accounting, Sandbox inventory filters, and controller SDK helpers, and fixed unlimited Snapshot retention. Requires a fresh database and a separate Runner storage root: the unlimited Snapshot retention fix changes the initial migration checksum, so no existing installation, including v0.13.0, upgrades in place. The signed `secondbox-0.12.0` Firecracker bundle and its trust anchor are unchanged. See the [v0.14.0 release notes](docs/releases/v0.14.0.md). ### Added - Added bounded state and ID filters to Sandbox listings, atomic quota observations on Subject quota updates, and TypeScript tenant-controller management helpers ([#143](https://github.com/SecondStack-AI/SecondBox/pull/143)). - Added optional `exclusiveBytes` to Workspace storage observations, measuring image extents unshared with templates, Snapshots, or other Workspaces. Allocated bytes remain available when FIEMAP is unsupported or exceeds the bounded scan, with a separate exclusive-measurement reason ([#143](https://github.com/SecondStack-AI/SecondBox/pull/143)). ### Fixed - Preserved unlimited Snapshot retention through persistence and Snapshot reads/lists, without assigning an expiry. - Kept Workspace storage probes off the Runner heartbeat and command paths, bounded scans across reconnects, and omitted exclusive-byte measurements when unshared encoded extents prevent physical accounting ([#143](https://github.com/SecondStack-AI/SecondBox/pull/143)). - Distinguished missing Workspace files from missing Sandbox compute in data-plane errors, preserving generation fencing for stopped Sandboxes ([#143](https://github.com/SecondStack-AI/SecondBox/pull/143)). ## 0.13.0 - 2026-09-14 Added configurable finite-or-unlimited policy limits, delegated Subject lifecycle selection, Tenant quota updates, and retained Workspace storage observations. Schema changes require a clean initialization; existing deployments and their Workspaces remain separate, with no state adoption. The tag was cut at `1645f10` on the branch of [#141](https://github.com/SecondStack-AI/SecondBox/pull/141) before that branch was rebased onto `main`, so `git log v0.13.0..main` lists re-landed commits under different hashes; the tagged tree is identical to `main`'s `24ac255`. Release notes were written after the fact: see the [v0.13.0 release notes](docs/releases/v0.13.0.md). ### Added - Added application-scoped quota headroom and retained Workspace storage observations, including allocated image blocks and admission-pressure freshness without exposing host identities. Platform operators can raise existing Tenant quotas without recreating retained resources ([#141](https://github.com/SecondStack-AI/SecondBox/pull/141)). ### Changed - Changed policy ceilings and quota dimensions to explicit integer-or-null values, with complete-object validation and nullable admission headroom. Added delegated Subject lifecycle selection for future Sandboxes; fresh Agent Profiles use unlimited maximum runtime while finite workloads retain age-based termination ([#141](https://github.com/SecondStack-AI/SecondBox/pull/141)). ## 0.12.0 - 2026-09-13 Rotated the Firecracker microVM bundle and its signing authority so Firecracker guests receive the port-credit and streaming-exec fixes, and introduced the lean release tier. Existing deployments must be reinstalled and their resources recreated; the guided updater refuses bundle digest changes. See the [v0.12.0 release notes](docs/releases/v0.12.0.md). ### Changed - Rotated the Firecracker microVM bundle to `secondbox-0.12.0` with a new RSA-4096 trust anchor, shipping the guest port-credit and streaming-exec fixes from #127, #128, and #134. Existing deployments require reinstallation and resource recreation because the guided updater refuses bundle digest changes. See [v0.12.0 release notes](docs/releases/v0.12.0.md) for the fingerprint and bundle identity. - Added a lean release tier with sharded Firecracker and local gVisor qualification, amd64 images, and one installer guest. `just nightly` retains the full scenario matrix; `just release VERSION --full` also builds arm64 images and runs all installer modes. Manifests record built platforms and evidence remains commit-exact ([#135](https://github.com/SecondStack-AI/SecondBox/pull/135)). ### Fixed - Fixed queued streaming stdin blocking the guest connection after an exec terminates, preserved command exit status when a program stops reading stdin before exiting, and allowed deadline outcomes to reach the streaming client ([#134](https://github.com/SecondStack-AI/SecondBox/pull/134)). ## 0.11.0 - 2026-09-13 Added one command per Sandbox intent (friendly verbs, size presets, golden Snapshots, and a guided install that runs a hello-world microVM) and one-command qualification and release tooling. The Firecracker bundle shipped with this release still predates the guest-side port-credit fixes below; they reach Firecracker guests with the v0.12.0 bundle. Release notes were written after the fact: see the [v0.11.0 release notes](docs/releases/v0.11.0.md). ### Added - Added friendly Sandbox lifecycle, file, port-forwarding, and Snapshot commands. `run` and `create` now accept size presets, explicit CPU/memory/Workspace requests bounded by optional Profile ceilings, quota, and Runner admission, and `--from sandbox/snapshot-name` or Snapshot identifiers. Memory and disk requests, Profile defaults, and finite ceilings require whole MiB. Requested disk capacity rounds up to a power of two, capped at a finite ceiling when the request fits; resume Profiles require their fixed size. An operator flexible-size Profile example removes CPU and memory ceilings while bounding disk at 256 GiB. Resolved resources appear in human Sandbox views, and an operator Profile example documents preparing dependencies with registry HTTPS access for golden Snapshots ([#126](https://github.com/SecondStack-AI/SecondBox/pull/126)). - Added `just qualify` for concurrent PR and release gates and `just release VERSION` for local qualification, artifact building, memory-capped parallel installer guests, and final staging, with per-stage logs and timings. Releases retain commit-exact evidence and require explicit publication ([#131](https://github.com/SecondStack-AI/SecondBox/pull/131)). ### Changed - Required removal of duplicate ready Snapshot names per Sandbox before upgrading across migration `0024_snapshot_name_index.sql`. On the source release, list Snapshots with `snapshots list --path sandboxId=...` and delete unwanted duplicates by Snapshot identifier; duplicates block control-plane startup. See the deployment and guided-update procedures ([#126](https://github.com/SecondStack-AI/SecondBox/pull/126)). - Simplified deployment settings: removed the seven unused deployment-level Subject quota defaults and the unenforced RunnerPool `capacityPolicy` from the public API, SDKs, resource documents, the CLI `--capacity` option, and deployment pool declarations (`max_sandboxes`, `max_vcpu_count`, `max_memory_bytes`); a forward migration drops its column. Packaged listeners, catalog location, same-host Runner paths, and the jail policy are derived; polling intervals become optional `[overrides]`. Retired fields are rejected ([#130](https://github.com/SecondStack-AI/SecondBox/pull/130)). ### Fixed - Fixed port forwarding to bound every session by its granted Lease lifetime and keep individual connection failures from stopping other connections. Guided-install smoke runs now wait for Sandbox deletion to release capacity before starting the next run ([#126](https://github.com/SecondStack-AI/SecondBox/pull/126)). - Guest port forwarding now returns unused byte credit after short socket reads, bounds Runner adapter credit and read sizes, and keeps granting credit to guests that discard short-read remainders, preventing forwarding stalls ([#127](https://github.com/SecondStack-AI/SecondBox/pull/127), [#128](https://github.com/SecondStack-AI/SecondBox/pull/128), [#129](https://github.com/SecondStack-AI/SecondBox/pull/129)). ## 0.10.1 - 2026-09-11 Fixed attributed commands terminating after a single HTTP client connection closed. Preserves the v0.10.0 database, Runner protocol, and runtime/toolchain asset identities. See the [v0.10.1 release notes](docs/releases/v0.10.1.md). ### Fixed - Fixed attributed commands terminating after an individual HTTP client reset or closed its connection. Subsequent requests retain their command authority, while gateway failure, cancellation, and expiry still stop forwarding ([#125](https://github.com/SecondStack-AI/SecondBox/pull/125)). ## 0.10.0 - 2026-09-11 Added attributed command execution with Runner-owned egress attribution. The public API stays at version 1 and the Runner protocol window at `[4,4]`; attributed execution additionally requires the Runner's advertised `attributed-execution` capability and a configured gateway `attributed_socket`. Applying the release resources appends an attribution-enabled `agent-compartment` revision; existing Sandboxes keep their immutable older revision. Release notes were written after the fact: see the [v0.10.0 release notes](docs/releases/v0.10.0.md). ### Added - Added attributed command execution on Firecracker and gVisor: one command owns a fresh Sandbox generation, the Runner supplies authenticated connection identity to a local Unix gateway, and completion, cancellation or expiry retires compute. The `agent-compartment` Profile adds an opt-in attributed mode with two concurrent gateway connections while preserving ordinary execution and existing immutable revisions ([#123](https://github.com/SecondStack-AI/SecondBox/pull/123)). ## 0.9.2 - 2026-09-04 Repaired Workspace file reads that every release-owned Profile refused with `413 limit_exceeded`, and gave three data-plane refusals their own types. Guided Firecracker installations at v0.8.3 or newer upgrade in place; the bundle and trust anchor are unchanged from v0.6.0. See the [v0.9.2 release notes](docs/releases/v0.9.2.md). ### Fixed - Workspace file reads no longer fail with `413 limit_exceeded` whenever a Profile's `maximumTransferBytes` exceeds the control-plane `SECONDBOX_DATA_PLANE_MAXIMUM_SESSION_BYTES` cap. A read without a caller bound is now admitted with the smaller of the two ceilings instead of being refused before the Runner is contacted, which rejected every read (including 1 KB files) under the release-owned and gVisor Profiles. A file that is genuinely larger than the admitted bound now returns `413 limit_exceeded` from the Firecracker and gVisor backends instead of a `500` bridge failure ([#119](https://github.com/SecondStack-AI/SecondBox/issues/119)). - Data-plane requests whose `maximumOutputBytes` or streaming `windowBytes` exceed the pinned Profile execution policy now return `400 invalid_request` with a title naming the exceeded bound, and a payload larger than `maximumTransferBytes` returns `413 limit_exceeded`. Previously both surfaced as `429 quota_exceeded`, indistinguishable from concurrency refusals, and the bound was only checked after capacity, so a malformed request at a saturated Sandbox was still reported as a quota refusal ([#119](https://github.com/SecondStack-AI/SecondBox/issues/119)). ## 0.9.1 - 2026-09-02 Made the gVisor backend a supported backend for Linux amd64 hosts without KVM and distributed it with every release under the v6 artifact manifest. v0.9.0 was tagged but retracted before publication: its installer qualification driver still required the v5 artifact manifest ([#118](https://github.com/SecondStack-AI/SecondBox/pull/118)). See the [v0.9.1 release notes](docs/releases/v0.9.1.md). ### Changed - The gVisor backend is a supported backend for Linux amd64 hosts without KVM, Kubernetes nodes included. Every release now publishes `ghcr.io/secondstack-ai/secondbox/runner-gvisor` and `ghcr.io/secondstack-ai/secondbox/gvisor-artifacts` (the prepared flat root, `runsc`, the guest agent, and the backend materialization, built from the repository and digest-pinned bases) plus the `secondbox-VERSION-gvisor-materialization.json` release file; the artifact manifest schema is `secondbox.release/artifact-manifest/v6` with a `gvisor` section recording both image digests, the materialization digest, the flat-root digest, and the `runsc` release, and gVisor host scenario evidence (full releases also carry pod evidence) ([#117](https://github.com/SecondStack-AI/SecondBox/pull/117)). ## 0.8.3 - 2026-09-01 Added tenant-aware egress contexts so several SecondStack installations share one RunnerPool without sharing gateway PKI, interception authority, egress policy, or credential custody. This is a clean-recreation boundary from v0.7.2 and earlier; there is no in-place update. v0.8.0 through v0.8.2 were tagged but never published. See the [v0.8.3 release notes](docs/releases/v0.8.3.md). ### Added - Added tenant-aware egress contexts so several SecondStack installations can share one RunnerPool without sharing gateway PKI, interception authority, egress policy, or credential custody. Every Profile network policy now explicitly requires or declines a Tenant context through the required `requiresTenantEgressContext` field, and opaque context names use one bounded provider-neutral syntax across public contracts, Runner protocol, persistence, audit, and diagnostics ([#113](https://github.com/SecondStack-AI/SecondBox/pull/113)). ### Changed - Advanced the Runner protocol to generation 4 exactly for context-aware assignments. Control planes and Runners must be replaced together; generation 3 peers, mixed fleets, legacy global-gateway assignments, default contexts, and cross-context fallback are refused ([#113](https://github.com/SecondStack-AI/SecondBox/pull/113)). - Declared v0.7.2 to this release a clean-recreation boundary. Operators must quiesce applications, retire every old Sandbox, replace the deployment, and recreate resources under the new Profile and Runner contracts. A coordinated v0.7.2 backup is for complete rollback only; there is no historical Profile decoder, legacy assignment bridge, or Sandbox migration operation. - Experimental gVisor runners now require the generic network-policy bounds plus `SECONDBOX_RUNNER_EGRESS_CONTEXT_CONFIG`, an absolute path to the strict context-indexed Runner-local gateway document. The legacy global `SECONDBOX_RUNNER_NETWORK_POLICY_RUNNER_GATEWAYS` value is rejected. When upgrading an existing gVisor runner, stop it, prepare and repin its materialization, install the reviewed context file read-only, revise affected Profiles to the new execution-asset identity, and then restart the runner. Missing settings, unprepared roots, unsafe context files, and stale pins are rejected instead of falling back or failing later in `runsc` ([#113](https://github.com/SecondStack-AI/SecondBox/pull/113), [#114](https://github.com/SecondStack-AI/SecondBox/pull/114)). ### Fixed - Retracted `v0.8.0` before publication because its root Go module required the unpublished Runner submodule, leaving the tagged module graph unresolvable for downstream consumers. No v0.8.0 GitHub Release or coordinated artifacts were published. Also retracted `v0.8.1` and `v0.8.2`, tagged before the release qualification-driver fixes landed and likewise never published; use v0.8.3. ## 0.7.2 - 2026-08-28 Removed the two operator workarounds that blocked normal SecondStack use of the experimental gVisor backend. Guided Firecracker installations upgrade in place from v0.6.0, v0.7.0, or v0.7.1; the bundle and trust anchor are unchanged from v0.6.0. See the [v0.7.2 release notes](docs/releases/v0.7.2.md). ### Fixed - Experimental gVisor runners now consume the complete Runner network-policy contract, including explicit logical gateway authorization, and the Runner ships `secondbox-prepare-gvisor-flat-root`, which materializes and validates every required bind destination before the root identity is pinned. Existing gVisor deployments set all six network-policy variables, re-prepare and re-digest their flat root, repin, and revise affected Profiles ([#111](https://github.com/SecondStack-AI/SecondBox/pull/111)). ## 0.7.1 - 2026-08-27 Superseded v0.7.0 because the public Go module proxy cached the `v0.7.0` tag from an intermediate release-preparation commit. Every coordinated artifact is rebuilt from one immutable, qualified commit; the public API, migrations, protocol windows, deployment topology, standard resources, and signed bundle match the final v0.7.0 distribution. See the [v0.7.1 release notes](docs/releases/v0.7.1.md). ### Fixed - Retracted the Go module version `v0.7.0`, which the public Go proxy cached from an intermediate release-preparation commit before final qualification fixes merged. Use `v0.7.1`; it includes the exact v0.6.0 quota and recorded-manifest guided-update bridges missing from the cached module while preserving the final v0.7.0 distribution's API, protocol, migration, and signed microVM bundle contracts ([#110](https://github.com/SecondStack-AI/SecondBox/pull/110)). ## 0.7.0 - 2026-08-26 Made the Runner compute boundary concrete across three implementations, stated CPU in whole vCPUs, and advanced the Runner protocol to generation 3. Upgrading from v0.6.0 requires a verified PostgreSQL backup: migration `0019` is forward-only. Guided v0.6.0 installations update through the v0.7.x bootstrap; operator-managed fleets stop every Runner, upgrade the control plane, then start generation-3 Runners. The Go module coordinate `v0.7.0` is retracted; use v0.7.1. See the [v0.7.0 release notes](docs/releases/v0.7.0.md). ### Added - Added an experimental gVisor compute backend for Linux amd64 hosts without KVM. It runs each Sandbox under the `runsc` systrap platform with dedicated cgroup v2 limits, network and mount namespaces, fail-closed network policy, reflink-only Workspace durability, generation fencing, and the complete provider-neutral data plane. Operators must build and pin its launch artifacts locally, select it runner-wide, and use a dedicated homogeneous RunnerPool; it supports `cold_boot` only and does not replace or fall back from the supported Firecracker path ([#99](https://github.com/SecondStack-AI/SecondBox/pull/99)). - Added an experimental Microsandbox compute backend behind the provider-neutral runner compute port. A Runner selects exactly one backend at startup, RunnerPools seal privately to one backend kind, and backend identity never enters public resources. Firecracker remains the only supported production backend ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). ### Changed - Sandbox CPU is now stated in whole vCPUs everywhere: Profile resources use `vcpuCount` instead of `cpuMillis`, subject and tenant aggregate quotas use `maxVcpuCount` instead of `maxCpuMillis`, and the API, SDKs, and CLI carry the vCPU representation. Database migration `0019` converts stored quotas, Profile revisions, and runner capacity documents automatically on the first control-plane start, rounding milli-unit values up to the whole vCPU that already covered them, so no converted allowance shrinks below its recorded usage; a zero quota stays zero, and only Profile resources floor at one vCPU so revisions remain schedulable. Because rounding is per row, a subject whose fractional Profile allowances each round up can transiently sit at or above its converted quota: running work is never stopped, but new starts are refused until usage drains or the quota is widened, so review converted quotas after upgrading. Take a database backup before upgrading: this migration is forward-only, and rolling back to a pre-upgrade binary afterward requires restoring that backup ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). - Guided-deployment manifest keys follow the vCPU representation: `max_cpu_millis` is now `max_vcpu_count` and `default_subject_max_cpu_millis` is now `default_subject_max_vcpu_count`, both holding whole vCPUs. Regenerate or edit operator-maintained manifests when upgrading; the previous keys are rejected ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). - The signed-asset catalog no longer records a per-asset `signatureKeyId`: execution-asset identity is provider-neutral and Firecracker keeps its trust anchor in backend configuration. Guided updates accept both catalog forms from supported source deployments, but manually authored catalogs that still state `signatureKeyId` are rejected at control-plane startup and must be regenerated before activating this release ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). - The runner protocol advances to generation 3 exactly, carrying provider-neutral execution-asset identities and backend materialization evidence. Generation 1 and 2 runners are refused. Upgrade as one coordinated rollout: stop all Runners, upgrade and start the control plane (which applies migration `0019`), then deploy and start the upgraded Runners. A mixed-version fleet is not supported ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). ### Removed - Removed the Profile `processLimit` resource field. Guest process counts are not host PIDs; the Firecracker backend bounds its jailer and VMM workers with a backend-owned host PID ceiling (32 slots plus two per vCPU) and no guest PID limit is promised. Profile specs that state `processLimit` are rejected; remove the field before upgrading ([#96](https://github.com/SecondStack-AI/SecondBox/pull/96)). ### Fixed - Firecracker cold boots now install the runner-controlled DNS identity before readiness, including when using the unchanged v0.6.0 signed microVM bundle required by the v0.7.0 upgrade path ([#105](https://github.com/SecondStack-AI/SecondBox/pull/105)). - Release qualification now preserves and validates the qualified Linux amd64 host platform in both scenario and installer evidence ([#106](https://github.com/SecondStack-AI/SecondBox/pull/106)). - Recorded-release verification now recognizes the exact published v0.6.0 installer-qualification waiver only when authenticating that immutable release as a guided-update source. New scenario and installer evidence use v2 and require an explicit Linux amd64 host platform; valid v1 evidence remains readable without weakening current release staging ([#107](https://github.com/SecondStack-AI/SecondBox/pull/107)). - Guided updates from the exact v0.6.0 release now recover its recorded Compose transport through the strict v0.6.0 deployment-manifest schema, including the source-era `max_cpu_millis` fields. This bridge is version-gated to authenticated v0.6.0 installer state; ordinary and newer deployment manifests remain on the current strict schema and reject legacy CPU fields ([#109](https://github.com/SecondStack-AI/SecondBox/pull/109)). ## 0.6.0 - 2026-08-26 Added customer-shared tenancy: one customer-operated deployment serves multiple SecondStack installations in the same trust domain. This is a clean-install boundary: a v0.5.2 manifest or deployment carrying the retired static application-authority surface is refused. See the [v0.6.0 release notes](docs/releases/v0.6.0.md). ### Added - Added customer-shared tenancy: one customer-operated deployment serves multiple SecondStack installations in the same trust domain. The platform token delegates fixed-capability tenant-controller authorities; controllers manage Subjects and application authorities within tenant ceilings; PostgreSQL persists only public credential lookup identifiers and one-way verifiers, and revocation or expiry applies on the next request without a restart. Tenant aggregate and Subject quota reserve and release in one transaction under a single global lock order, Subject closure converges through one durable claim-fenced cleanup Operation with Runner-acknowledged workspace removal, and the `secondbox` CLI gains operator and controller management workflows with one-time bearer display ([#100](https://github.com/SecondStack-AI/SecondBox/pull/100)). - Added the release-owned `agent-compartment-isolated` standard Profile bundle: command, file, and workspace capabilities with `deny_all` networking. Runner DNS-forwarder admission is now keyed to compiled domain allow-lists, so network-disabled and CIDR-only Sandboxes receive no port-53 path ([#100](https://github.com/SecondStack-AI/SecondBox/pull/100)). - Added a resumable `secondbox-deploy update` workflow for completed v0.6.0 or newer guided single-host installations. v0.6.0 is a clean-install boundary that supersedes the earlier source minimum. The latest verified bootstrap authenticates immutable source Profile history without regenerating it from newer policy; admits staging only when Runner storage can retain its operational reserve; reclaims only the current update's validated interrupted extraction path; stages every compatible target release input before downtime; revalidates privileged Runner storage; requires every Tenant's Sandboxes and pending lifecycle intents to be stopped; fences control-plane admission before the forward-only boundary; keeps that admission fence closed when boundary persistence is ambiguous; shuts down through the source deployment's recorded Compose assets; rejects execution-bundle changes that would strand immutable Profile revisions; preserves PostgreSQL data, authority, Runner identity, Workspaces, Snapshots, topology, and Compose volumes; then advances the original plan and receipt with append-only release history after readiness and authenticated Runner qualification succeed ([#95](https://github.com/SecondStack-AI/SecondBox/pull/95)). ### Removed - Removed static application authorities: `SECONDBOX_APPLICATION_AUTHORITIES_JSON`, the `applications.application_authorities_file` manifest key, and every deployment, Compose, installer, diagnostics, and support-bundle surface that carried them. v0.6.0 is a clean-install boundary with no import, compatibility, fallback, or dual-source mode for v0.5.2 installations ([#100](https://github.com/SecondStack-AI/SecondBox/pull/100)). ### Fixed - Failed data-plane session setup (for example while a Runner reconnects after a control-plane restart) now releases its admitted concurrency reservation through the existing failure transitions instead of counting against tenant and Subject `maxConcurrentOperations` until retention cleanup ([#100](https://github.com/SecondStack-AI/SecondBox/pull/100)). - Management CLI commands now propagate API errors instead of rendering an empty result with exit success when a call fails ([#100](https://github.com/SecondStack-AI/SecondBox/pull/100)). ## 0.5.2 - 2026-08-20 Fixed large File API writes that exceeded gRPC's default 4 MiB receive limit between the Runner and guest. Compatible with v0.5.0 and v0.5.1. See the [v0.5.2 release notes](docs/releases/v0.5.2.md). ### Fixed - Runner-to-guest file writes now stream bounded 64 KiB frames instead of placing the complete file in one gRPC message. Files larger than gRPC's 4 MiB default receive limit now remain usable up to SecondBox's configured transfer limit, and a failed file operation no longer leaves subsequent file or Exec operations on that guest protocol stream unusable ([#97](https://github.com/SecondStack-AI/SecondBox/pull/97)). ## 0.5.1 - 2026-08-10 Fixed interactive CLI sessions that could forward terminal capability replies into a Sandbox shell as input. Compatible with v0.5.0. See the [v0.5.1 release notes](docs/releases/v0.5.1.md). ### Fixed - Interactive CLI progress no longer emits terminal capability queries or changes keyboard and paste modes. Terminal replies could remain unread on standard input and later be forwarded byte-for-byte into an interactive Sandbox shell, where `/bin/sh` interpreted their semicolon-delimited fragments as commands. The replacement activity renderer never reads standard input, emits only cursor-positioning output, remains bounded across terminal resizes, and preserves canonical Exec spawn-failure reasons in the Go SDK ([#93](https://github.com/SecondStack-AI/SecondBox/pull/93)). ## 0.5.0 - 2026-08-09 Removed the application Artifact resource and the control plane's S3-compatible object-store dependency, and fixed long-running HTTP operations that completed on the server after the client connection was cut. A deliberate clean break for the pre-production contract: recreate PostgreSQL from the v0.5.0 baseline and render a fresh `secondbox.toml`; there is no in-place upgrade from v0.4.7. The Runner protocol, guest protocol, bundle, and trust anchor are unchanged. See the [v0.5.0 release notes](docs/releases/v0.5.0.md). ### Removed - Removed the application Artifact resource, its API/SDK/CLI surfaces, S3-compatible storage integration, quotas, retention, garbage collection, and bundled object-store deployment ([#88](https://github.com/SecondStack-AI/SecondBox/pull/88)). ### Fixed - Buffered Exec and other long-running HTTP responses now remain connected for their operation-defined lifetime. The control plane previously applied its 30-second connection timeout as a server-wide response write deadline, so a valid command could complete and persist its result while the SDK received only `EOF` or `fetch failed` ([#90](https://github.com/SecondStack-AI/SecondBox/pull/90)). ## 0.4.7 - 2026-08-09 Made guided single-host installation independent of Docker's finite automatic address pools. See the [v0.4.7 release notes](docs/releases/v0.4.7.md). ### Fixed - Guided installation now inspects existing Docker IPAM allocations and chooses a separate, reviewed RFC1918 `/24` for the Compose backend network. Compose startup no longer depends on Docker's finite automatic address pools, and both the Firecracker guest network and Compose network are rejected when they overlap each other, a host route, or an allocated Docker network. A receipt-compatible v0.4.4 through v0.4.6 operation can use the v0.4.7 bootstrap to validate and stop its exact partial Compose project, journal a retry from the pre-Compose boundary, replace the conflicting network, and resume without downloading the release again ([#89](https://github.com/SecondStack-AI/SecondBox/pull/89)). ## 0.4.6 - 2026-08-09 Let the guided single-host installer safely upgrade an older SecondBox CLI installation. See the [v0.4.6 release notes](docs/releases/v0.4.6.md). ### Fixed - Guided installation now validates both reviewed CLI binary destinations and the CLI configuration destination before pulling images or extracting the microVM bundle. Existing executables are upgraded atomically only when their embedded Go build identities are exactly the corresponding SecondBox CLIs, and an existing configuration is upgraded only when it is a protected, strictly decoded SecondBox session document. Unrelated files remain untouched and stop the operation before expensive release materialization; interrupted binary upgrades adopt only the release's exact digest on resume ([#87](https://github.com/SecondStack-AI/SecondBox/pull/87)). ## 0.4.5 - 2026-08-09 Repaired automatic guest-network selection in the guided single-host installer. See the [v0.4.5 release notes](docs/releases/v0.4.5.md). ### Fixed - Guided installation now searches every RFC1918 `/24` for an unused guest bridge network while preserving its established preferred subnets. The former selector tried only `172.30.0.0/24` and `172.31.0.0/24`, so hosts with Docker, VPN, or other routes covering both failed before plan review even when other private networks were free ([#86](https://github.com/SecondStack-AI/SecondBox/pull/86)). ## 0.4.4 - 2026-08-08 Superseded the unpublished v0.4.3 tag after the first installer qualification VM exposed a host-firewall dependency in the libvirt bridge transport. ### Fixed - Installer qualification now gives each disposable guest a deterministic MAC address, an explicit NoCloud v2 DHCP configuration, and an isolated QEMU user network with a verified localhost-only SSH forward. This removes its dependency on host firewall rules for libvirt bridge DHCP and DNS. A bounded serial-console tail now accompanies future reachability failures before cleanup ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Filesystem-image installation now revalidates free capacity through the nearest existing safe ancestor before creating its operation-specific Runner directory. The former check addressed the not-yet-created image parent directly and rejected every new Btrfs-image installation with `no such file or directory` ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - The release-pinned installer-tools image now starts the Debian `btrfs-progs` formatter from its packaged `/usr/sbin/mkfs.btrfs` path. The former `/usr/bin` entrypoint made every filesystem-image installation fail after allocation when the container runtime could not start the formatter ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer resume now revalidates root-owned host resources through the private sudo helper and limits unprivileged postcondition checks to their exact plan-and-receipt identity. The former unprivileged `lstat` attempted to traverse the intentionally root-only Runner directory immediately after host apply and stopped every installation with `permission denied` ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - MicroVM artifact extraction now supplies an inert create-time entrypoint before copying files out of the release-pinned OCI image. The artifact-only image intentionally has no configured command, so bare `docker create` rejected every installation during release asset materialization ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Release materialization now preserves safe invoking-user directory parents that predate the installation, including a private `~/.local`. The previous exact create-time mode check rejected a normal pre-existing `0700` parent even though it was owner-controlled and the installer neither created nor claimed it ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Completed host apply now records the Workspace filesystem's stable Btrfs or XFS UUID instead of its transient Linux device number. Filesystem-image installs therefore resume after reboot even when systemd assigns the image a different loop device, while resume and purge still reject a different filesystem at the reviewed path ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - The privileged runtime qualification allocates an explicit isolated Compose backend subnet instead of depending on Docker's finite default address pools, so a host with many unrelated Docker networks can still qualify without deleting them ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer-owned Runner enrollment, Compose lifecycle, and diagnostics now consume the private host helper's accepted root-path verification instead of repeating same-host preflight as the unprivileged operator. Root-only Runner state remains inaccessible to the invoking user without blocking installation after deployment materialization ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer qualification now attributes every driver and guest-shell failure to its mode and source line, and includes the bounded guest serial tail for driver-side failures instead of silently cleaning the only diagnostic state ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer purge now validates every remaining user resource and the complete privileged mount boundary before deleting Compose volumes, artifacts, or host storage. Btrfs-image purge previously let systemd hide an unrecorded nested mount by unmounting its parent before recursive deletion reached the guard, and its Runner identity allowlist rejected the generated public CA certificate's intentional `0644` mode after earlier resources had already been removed ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Guided installation now gives Runner process logs and root-owned Firecracker instance logs separate reviewed directories. The generated manifest previously pointed both policies at `state/logs`; the container entrypoint consequently changed the receipt-owned Runner log directory from `10001:10001, 0750` to `root:root, 0700`, and an ordinary uninstall could not resume the deployment it had just preserved ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Guided installation now records and reviews the vendor-neutral Firecracker CPU template (`None`) in the accepted plan and materializes that exact choice into the Runner manifest. The former hidden `T2` literal was Intel-only, so an otherwise-qualified AMD host reached the first smoke Sandbox and then Firecracker rejected it with `CpuVendorMismatched` ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer-created Btrfs Runner storage now permits executable files and device nodes, which Firecracker's jail requires after it enters the per-Instance chroot. Existing XFS/Btrfs choices with `noexec` or `nodev` are no longer offered and are rejected again during privileged apply if their mount flags change; the Runner container also fails startup immediately instead of advertising readiness and timing out each Sandbox. The accepted plan now records the shortened jail directory itself so purge owns the path the Runner actually uses ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer-generated Runner manifests now keep the Firecracker jail root short enough for maximum-length Instance IDs and validate the resulting API socket path before deployment. The single-root storage correction had placed the jail beneath an extra `state` segment, so the first installed microVM reached a 112-byte Unix socket path and failed Linux's 108-byte limit even though Runner readiness was green ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - The Compose contract test now creates and removes an explicit isolated `/24` instead of consuming Docker's finite default-address pools, so a host with many unrelated networks can run the required gate without deleting them ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Same-host Compose now binds the reviewed Runner storage root once instead of overlaying its Workspace child with a second bind mount. Linux reports hard links across distinct bind mounts as `EXDEV`, so the former container topology still failed when the Firecracker jailer linked a Workspace image into its jail even after host-side artifact, state, and Workspace paths had been colocated. Deployment validation now requires one dedicated storage filesystem and the fixed `state` and `workspaces` child layout ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Guided installation now places verified microVM assets, Runner run/jail/cache state, and Workspace storage beneath one operation-specific root on the selected reflink-capable filesystem. Host apply proves an asset-to-run `FICLONE` and a matching Workspace filesystem identity before materializing the release; resume rechecks the topology. The former layout mounted only the Workspace on Btrfs/XFS, so every installed Sandbox reached Firecracker and then failed while cloning its rootfs across filesystems with `operation not supported`. Purge verifies and journals the colocated assets before removing the privileged storage root ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Installer readiness now accepts the public `pre_shared` credential state emitted for a ready, authenticated Runner instead of waiting for the nonexistent `active` projection until its five-minute deadline expired ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). - Single-host capacity planning now reserves all 16 concurrent operations required by each selected `durable-coding` Sandbox, and installer readiness verifies the exact pool, scheduling capabilities, and operation capacity before attempting its smoke Sandbox. The former plan advertised at most eight operations while the Profile required 16, then reported the incompatible Runner as ready and failed the first Sandbox with `home_runner_unavailable` ([#85](https://github.com/SecondStack-AI/SecondBox/pull/85)). ## 0.4.3 - 2026-08-08 Superseded the unpublished v0.4.2 tag after real-host release qualification reproduced a long-lived Runner control-session failure. ### Fixed - Runner control sessions now keep a bounded recent message-ID window and defer older replays to PostgreSQL's durable duplicate and sequence authority. The former 256-ID map was a fatal connection-lifetime cap: the release scenario crossed it during four-way snapshot-resume measurement, disconnected the Runner, and left otherwise-ready Sandboxes stuck in `starting` ([#84](https://github.com/SecondStack-AI/SecondBox/pull/84)). ## 0.4.2 - 2026-08-08 Superseded the unpublished v0.4.1 tag after real-host qualification reached a latent strict-shell failure in the new driver. ### Fixed - The installer qualification driver now assigns its guest mode before deriving the domain and workspace names from it. With `set -u`, the original combined `local` declaration expanded `mode` before assigning it and stopped before creating the first disposable VM ([#83](https://github.com/SecondStack-AI/SecondBox/pull/83)). ## 0.4.1 - 2026-08-08 Superseded the unpublished v0.4.0 tag with a qualification driver that can run on hosts whose Docker networks occupy its original private-address window. ### Fixed - Installer qualification now searches all RFC1918 `/24` networks for a subnet that does not overlap any host route. The initial driver searched only `192.168.240.0/24` through `192.168.250.0/24`, so a normal Docker `192.168.240.0/20` network blocked release qualification before any disposable VM started ([#82](https://github.com/SecondStack-AI/SecondBox/pull/82)). ### Added - Added a repository-owned QEMU/libvirt installer qualification driver that runs the candidate through isolated Btrfs-image and existing-Btrfs guests, real nested Firecracker execution, reboot and interrupted-stage recovery, retained-lineage uninstall/resume, and mount-confined purge. Candidate installation is explicit, verifies the staged manifest and embedded binary identity, and resolves exact OCI digests through a guest-local TLS registry without weakening the public installer path ([#81](https://github.com/SecondStack-AI/SecondBox/pull/81)). ## 0.4.0 - 2026-08-08 Added a qualified guided installer for one loopback-only Linux amd64 deployment and rebuilt both CLIs around explicit human, plain, JSON, accessible, and raw-output contracts. Existing v0.3.1 deployments need no database migration, public API change, Runner protocol change, or guest update. The v0.3.0 microVM bundle and trust anchor carry forward unchanged. Applying the release-owned standard resources creates a new `agent-compartment` revision whose command deadline ceiling matches the Sandbox's 900-second maximum lifetime. ### Added - Added `secondbox-deploy install`, a resumable single-host workflow that performs read-only preflight, verifies one immutable public release, reviews storage and capacity, records every accepted path and authority decision, prepares only bounded host resources, starts the deployment, logs in the CLI, and proves the result by running a command in a Firecracker microVM. Ordinary uninstall preserves durable state, explicit purge deletes only plan-and-receipt-matched resources, and a bounded support workflow excludes secrets and Workspace content ([#79](https://github.com/SecondStack-AI/SecondBox/pull/79)). - Added a release-pinned `install.sh`, digest-pinned bundled-service and installer-tools artifacts, and clean-VM installer qualification evidence to the release manifest. Release staging refuses to publish installer bytes that were not exercised through install, reboot, resume, uninstall, purge, and a real microVM run ([#79](https://github.com/SecondStack-AI/SecondBox/pull/79)). - Added capability-aware terminal presentation to both CLIs: width-aware tables, bounded summaries, lifecycle progress, accessible prompts, explicit `--output auto|json|plain` and `--color auto|always|never` modes, `NO_COLOR`, and help through no arguments, `help`, `--help`, or `-h`. Raw guest, file, artifact, log, and Compose streams remain byte-authoritative and never pass through the presentation layer ([#79](https://github.com/SecondStack-AI/SecondBox/pull/79)). ### Changed - Raised the release-owned `agent-compartment` command deadline ceiling from 120 seconds to 900 seconds, matching the Sandbox's own maximum lifetime so callers may request enough time for an install, build, or large search without the Profile rejecting the deadline ([#79](https://github.com/SecondStack-AI/SecondBox/pull/79)). ### Fixed - Kept ANSI presentation bytes out of error sanitization, detected systemd through `systemctl` on distributions that do not install a standalone `systemd` executable, and made local development installers reject their non-release identity before attempting a fabricated `v0.0.0-development` download ([#79](https://github.com/SecondStack-AI/SecondBox/pull/79)). ## 0.3.1 - 2026-08-07 Repaired the idle accounting that made a restarted Sandbox unusable, and let a manifest name its Compose project so a second deployment cannot seize the first. Upgrading from v0.3.0 needs no operator action. There is no migration, no contract change, and the v0.3.0 microVM bundle and its trust anchor carry forward unchanged, so no Profile is re-pinned. ### Fixed - A Sandbox restarted after its idle window elapsed is no longer drained as `idle_timeout` on its first reconciliation. Useful activity is generation-scoped everywhere it is recorded, but `sandboxes.last_activity_at` is one denormalized column that `finish_stop` never clears when it advances the generation, and `mark_ready` stamps it only through `COALESCE(last_activity_at, now)`, which is a no-op once it is set. A restarted Sandbox therefore came up holding the activity of the Instance that preceded it and was drained immediately — observed 244 ms after `ready_at` against a Profile with a 60 s idle timeout, leaving the Sandbox unusable for the rest of its life. `lifecycle.IdleSince` now bounds the idle window by readiness, because an Instance cannot have been idle for longer than it has been ready, and both the drain decision and the wake-up it schedules read that one reference so they cannot disagree. The public `lastActivityAt` is deliberately untouched: a stopped Sandbox still reports when it was last used ([#77](https://github.com/SecondStack-AI/SecondBox/pull/77)). - `secondbox-deploy` can deploy more than one stack on a host. `runCompose` passed the literal project name `secondbox` and `runDockerCompose` scrubs the environment to a Docker-client allowlist that excludes `COMPOSE_PROJECT_NAME`, so there was no override and a second deployment silently seized the first: Compose matched the running project by name, and `up --remove-orphans` bound the second deployment to the first's volumes — including `secondbox_postgres-data` — recreated its containers against the second's configuration, and deleted any container the second service set did not contain. An optional `deployment.compose_project_name`, validated against the Compose project grammar and threaded into `--project-name`, now names the project. Absent means the previous behavior exactly, so every manifest written before this key keeps deploying where it always did ([#76](https://github.com/SecondStack-AI/SecondBox/pull/76)). ## 0.3.0 - 2026-08-07 Added snapshot-resume Sandbox startup. A `snapshot_resume` Profile reaches `ready` in 125/130 ms p50/p95 on the unsaturated `start → ready` path, against 447/489 ms for the same deployment's cold boot, measured end to end through the real control plane. Teardown got its own attribution and then its own fix, and resting Sandboxes stopped moving their public revision. Upgrading requires operator action. Read the upgrade notes below before deploying. ### Upgrade notes - **Two migrations run automatically at control-plane start.** `0014_lifecycle_quiescent_schedule` redefines the lifecycle reconcile index and parks the resting Sandboxes an upgraded deployment already carries. `0015_profile_startup_mode` stamps every Profile revision that predates the `startup` field with `cold_boot`, which states the behavior those revisions already had rather than inventing a default for them. Both are idempotent and neither re-executes prior work. - **Breaking for clients that create Profiles.** `ProfileRevisionSpec` now requires an explicit `startup` policy whose `mode` is `cold_boot` or `snapshot_resume`. There is no application default: a blank mode is refused by validation and the contract lists `startup` as required. Both SDKs are regenerated with a typed `StartupMode` enum, and every caller that creates or revises a Profile must state a mode ([#63](https://github.com/SecondStack-AI/SecondBox/pull/63)). - **`snapshot_template_cache_root` is a new required key in every `[[runners]]` manifest declaration**, validated as an absolute Runner-host path and constrained to `/var/lib/secondbox-runner` for same-host Compose placement. It is required rather than optional because the golden memory file is hard-linked into every jail, so the cache must share a filesystem with the jail root ([#63](https://github.com/SecondStack-AI/SecondBox/pull/63)). - **A v0.3.0 Runner requires a v0.3.0 control plane.** Assignments now carry `ProfileRequirements.startup_mode` and a Runner refuses an Assignment that does not state one. A v0.3.0 control plane with an older Runner degrades cleanly: the older Runner never reports snapshot-resume readiness, so it simply never attracts a `snapshot_resume` Profile. - **This release ships a rebuilt microVM bundle under a new trust anchor.** The guest agent, `/init`, and the microVM entrypoint all live inside the rootfs, so template mode and the one-time assignment bind required a rebuilt, re-signed bundle. Operators must install the new bundle and pin the new public key through the runner init flow, exactly as documented in [release operator setup](docs/operations/release-operator-setup.md). Only snapshot-resume requires the new bundle; cold boot, the guest protocol, the Exec/File/PTY/Port data plane, runtime secrets, post-restore hardening, Workspaces, Snapshots, and generation fencing all work unchanged against an older already-verified bundle, and `contracts/guest/v1` is byte-identical to v0.2.3. What does change for every Profile is digest pinning: a Profile pins the runtime and toolchain component-manifest digests it was created against, and a Runner refuses any Assignment whose pinned digests differ from its own locally verified manifest. Installing a different bundle therefore re-pins every Profile, and the release-owned standard bundles are regenerated against the digests this release ships. ### Added - Added the provider-neutral `snapshot_resume` Profile class. `ProfileRevisionSpec` carries a required `startup` policy, the mode reaches `scheduler.Requirements.RequiredCapabilities`, `runnerPlacementCompatible`, and the Assignment's `ProfileRequirements.startup_mode`, and a `snapshot_resume` Profile is placeable only onto a Runner advertising the `snapshot-resume` capability. A Runner reports that capability only when its template cache root is configured, the jailer is required, and a published template matches its own verified signed bundle. A pool that never declares resume capacity refuses with the new non-retryable `startup_mode_unsupported` (409); a declared pool with no advertising Runner keeps the existing retryable refusals ([#63](https://github.com/SecondStack-AI/SecondBox/pull/63)). - Added the production snapshot-resume start path. `createAndStartResume` resolves the template, stages the jail, starts the jailer, loads the snapshot with the Instance's own TAP as the network override, hardens, and binds the assignment, sharing one prologue and one epilogue with cold start rather than a copy of each. A failed resume is a failed start: any stage failure is a typed retryable template-unavailable error with full teardown, and there is no cold-boot fallback anywhere on the path ([#65](https://github.com/SecondStack-AI/SecondBox/pull/65)). - Added the identity-neutral snapshot-resume template. The same signed rootfs now boots two ways: a tenant Instance boots with its full assignment identity, and a template boots with `secondbox.template_mode=1` and no identity at all, serving only the host-only vsock control endpoint, leaving its Workspace device unmounted, and refusing every guest-protocol connection. Identity arrives through one `POST /assignment/bind` control request that is refused before post-restore hardening succeeds and refused for every request after the first. The Workspace is mounted at bind rather than at boot, because a template that sealed a mounted Workspace superblock into its memory image could not be corrected by any drive override ([#60](https://github.com/SecondStack-AI/SecondBox/pull/60)). - Added the snapshot-resume template artifact, its runner-local cache, and the jailed resume composition. A template is not separately signed; its compatibility key carries the signing-key fingerprint and signed manifest digest of the bundle it was built from, so it is only ever admitted against a bundle the Runner has already verified. Admission verifies every recorded digest once, and every start after that proves stable file identity with a stat rather than rehashing the bundle ([#57](https://github.com/SecondStack-AI/SecondBox/pull/57)). - Added per-Instance network identity for a resumed guest. A resumed guest cannot be given an interface its template did not capture — only the host-side TAP is overridable and the guest MAC is not — so a template records the network device its Runner's configuration gives guests, and the guest installs its own hardware address, address, link state, and default route over rtnetlink as the last step of the assignment bind ([#64](https://github.com/SecondStack-AI/SecondBox/pull/64)). - Added a privileged jailed snapshot-resume qualification at concurrency 1, 2, 4, 8, and 16. Snapshot resume requires the jailer, because Firecracker opens every block device at the path the VM state recorded during the load itself, and only chroot-relative names give each Instance its own disks. The gate fails closed if resumed Instances stop sharing one golden memory inode, if their rootfs children or Workspace images stop being distinct, or if aggregate reads approach one memory image per Instance ([#61](https://github.com/SecondStack-AI/SecondBox/pull/61)). - Added stage attribution for the teardown orchestration path. Nine drain, stop, and delete milestones are persisted by the transactions that establish them, through the existing `operation_stage_timings` mechanism and the existing timings routes, with no new table and no new migration. The published `OperationStageTiming.stage` enum and `OperationTiming.orchestration` bound are corrected to the full vocabulary the placement transaction already wrote, so a timing response for a real create Operation now validates against its own schema ([#50](https://github.com/SecondStack-AI/SecondBox/pull/50)). ### Changed - Sandboxes at rest now park their lifecycle schedule and hold their public revision. A reconciliation pass that decides to wait changes no field a caller can observe, so it no longer advances `revision` or `updated_at` — previously any Sandbox with an outstanding transition moved its ETag about ten times a second while a Runner was being waited on, turning a caller's `If-Match` into a spurious 412. Quiescence now lives in the durable schedule rather than in a state predicate duplicated between the claim query and its partial index ([#58](https://github.com/SecondStack-AI/SecondBox/pull/58)). - Start deferrals now hold the public revision too. A `start_instance` decision that cannot be carried out — an unresolvable pinned Profile revision, or a home Runner outside the scheduler's candidate set — leaves the Sandbox exactly as the caller last saw it and now commits only its schedule. Both deferrals stay scheduled rather than parking, because these Sandboxes are wanted running and nothing writes a wakeup when a Profile is repaired or a Runner reconnects ([#70](https://github.com/SecondStack-AI/SecondBox/pull/70)). ### Fixed - Teardown transitions that decide their own successor now wake it immediately instead of waiting out a recovery poll interval that no external work occupied. A committed drain and a committed finish-stop each schedule their successor at their own clock, which both satisfies the notify predicate and leaves the Sandbox due for the worker's next claim. Client-visible `delete → deleted` p50 moves 506 → 299 ms and `stop → stopped` 339 → 229 ms, with create and start unchanged. The guard is deliberately narrow: effect actions keep the bounded poll, a wait is never immediate, a drain holding an active session keeps the poll, and every successor named is an effect action or a wait, so one state change advances at most one extra hop ([#55](https://github.com/SecondStack-AI/SecondBox/pull/55)). - Stopped reporting an answered peer close as a failed Terminal or Port session. When the peer closes first, the connection's default close handler answers it from the read goroutine and latches `ErrCloseSent`, which both handlers surfaced as a delivery failure even though the server's side of the handshake had completed. One shared helper now defines the rule for the Exec, Terminal, and Port paths ([#67](https://github.com/SecondStack-AI/SecondBox/pull/67)). - Release staging is deterministic under any operator locale. `SHA256SUMS`, the candidate allowlist, and the archived file lists were ordered by shell `sort` under `LC_COLLATE` while the verifier compared them against byte order, so a complete candidate failed staging's own final verification under a dictionary locale — it bit the v0.2.0, v0.2.1, and v0.2.2 stagings, each retried by hand. Staging now pins byte collation for the whole run, and candidate verification sorts the allowlist before comparing because the allowlist names a set of files rather than an order ([#68](https://github.com/SecondStack-AI/SecondBox/pull/68)). - The Compose test fixture's Runner now stays a placement candidate across both live halves. Its liveness was stamped once at seed time and nothing renewed it, so the Runner-loss reconciler marked it offline and the TypeScript half failed with `home_runner_unavailable`. The suite now restates the fixture's liveness immediately before each half and fails loudly if the statement does not match the fixture ([#69](https://github.com/SecondStack-AI/SecondBox/pull/69)). - The scenario suite now releases the host bridge and jailer cgroup parent it creates. The cgroup tree was never removed, and the bridge had exactly one working removal path — the one that needed a healthy Runner container — behind an unreachable fallback that gated itself on root-only state, so it always reported success and removed nothing. Teardown now stops the Runner before removing host networking, proves both resources are gone, and a narrowly scoped sweep in the preflight reclaims what killed runs left behind without ever touching a name a running container declares ([#71](https://github.com/SecondStack-AI/SecondBox/pull/71)). ## 0.2.3 - 2026-08-06 Repaired the colliding migration prefix that stopped v0.2.0 and v0.2.1 deployments from starting on v0.2.2, and restored TypeScript SDK terminal replay parity. Upgrade directly from v0.2.0 or v0.2.1 to v0.2.3 and skip v0.2.2. Deployments already running v0.2.2 are repaired automatically on first start. ### Fixed - Repaired the schema lineage collision that made v0.2.2 unreachable from v0.2.0 and v0.2.1: v0.2.2 shipped the lifecycle fence retag as `0010_lifecycle_fence_command_kind`, which sorts before the `0010_lifecycle_hot_path_indexes` already shipped in v0.2.0, so an upgraded ledger failed positional prefix validation and the control plane refused to start. The migration is renamed to `0013_lifecycle_fence_command_kind` with byte-identical content, and a checksum-verified ledger repair renames a row recorded under the v0.2.2 name in place, preserving `applied_at` and re-executing nothing. A row whose checksum is not the embedded fence content is refused, a ledger that recorded the fence ahead of the migrations preceding it is refused with an instruction to run the recording release to completion, and every 4-digit numeric prefix must now be unique across the embedded lineage so a colliding prefix cannot embed again ([#49](https://github.com/SecondStack-AI/SecondBox/pull/49)). - Restored terminal replay-resume parity in the TypeScript SDK: `connectTerminalAfter` threads the replay cursor through the connector descriptor, sends the `SecondBox-Terminal-After-Sequence` header, resumes output sequencing at `afterSequence + 1`, and surfaces a rejected WebSocket handshake — including `terminal_replay_evicted` — as the same typed Problem error the Go SDK returns. The SDK parity contract test now asserts the capability in both SDKs ([#48](https://github.com/SecondStack-AI/SecondBox/pull/48)). - Stopped the TypeScript `request` path from discarding the HTTP status when an error body is not a Problem document: the transport error carrying the status and a bounded body is surfaced instead of a `SyntaxError` ([#48](https://github.com/SecondStack-AI/SecondBox/pull/48)). - Made `scripts/test-firecracker.sh` export `SECONDBOX_RUNNER_QUALIFY_FIRECRACKER=1` so an unqualified machine fails loudly instead of skipping every targeted test and passing vacuously, and corrected six runner configuration errors to name the environment variables that actually exist (`SECONDBOX_RUNNER_ARTIFACT_PUBLIC_KEY`, `SECONDBOX_RUNNER_ARTIFACT_PUBLIC_KEY_SHA256`, `SECONDBOX_RUNNER_FIRECRACKER_SHARED_IMAGE_PATH`) ([#48](https://github.com/SecondStack-AI/SecondBox/pull/48)). ## 0.2.2 - 2026-08-06 Stopped single poison records from taking down the whole control plane or locking a runner into a reconnect loop. ### Fixed - Quarantined every known poison-record path in the reconcilers: a terminal Assignment failure that coincides with an in-flight Workspace stop, an Assignment whose durable command payload has an absent or mismatched correlation, an invalid Profile revision, a delete effect recorded as succeeded without a finalized Sandbox, and a lost generation race all now defer with backoff and a logged warning instead of ending the reconciler — previously any one such row shut down the control plane and kept it down across restarts ([#45](https://github.com/SecondStack-AI/SecondBox/pull/45)). - Separated lifecycle stop commands from the assignment reconciler's fence commands in `runner_commands` (`kind='lifecycle_fence'`, retagged by migration): the reconciler's bulk fence expiry could collaterally expire a lifecycle-owned command, after which the stop broker's delivery guard killed the process. The exhaustion path also tolerates an already-terminal command, and it now releases the Workspace stop mutation it holds — previously the slot leaked forever and blocked the restart that recovers a failed Sandbox ([#45](https://github.com/SecondStack-AI/SecondBox/pull/45)). - Stopped dropping the runner session over replayed messages: a result for an already-completed local-workspace effect is absorbed as a no-op even after the Workspace row has moved on, and `AssignmentProgress` stage timings are truncated to the microsecond precision PostgreSQL stores, so an exact replay no longer reads as different evidence. Genuine authority conflicts now name each divergent field, and telemetry disagreement is a logged anomaly that never costs the control connection ([#45](https://github.com/SecondStack-AI/SecondBox/pull/45)). ## 0.2.1 - 2026-08-06 Stopped the same-host runner deployment from leaking bind mounts into the host mount namespace. ### Fixed - The same-host runner Compose service now binds its state and workspace directories with `rslave` propagation instead of `rshared`. When `workspace_root` nested inside the state mount, `rshared` let each runner container start propagate the workspace bind back into the host mount namespace, doubling a stack of identical mounts on the workspace host directory every start; the leaked mounts survived container teardown, made the directory undeletable, blocked re-bootstrap, and grew the host mount table without bound. Nothing in the runner creates mounts intended for the host, so `rslave` preserves the needed host-to-container propagation while cutting the leaking direction ([#42](https://github.com/SecondStack-AI/SecondBox/pull/42)). ## 0.2.0 - 2026-08-05 Hardened microVM and data-plane isolation, unlocked placement and lifecycle database hot paths, brought the TypeScript SDK to parity with Go, and bound release staging to scenario qualification evidence. ### Added - Added `secondbox-deploy runner-template`, which emits a complete, commented `[[runners]]` declaration whose placeholder values cannot accidentally validate, plus a documented walkthrough kept byte-identical to the command output by test; the README same-host path now starts from the scaffold instead of asking the operator to supply every value from memory ([#32](https://github.com/SecondStack-AI/SecondBox/pull/32)). ### Changed - Renamed the proxied Port transport wire value from `relay` to `proxied` across the OpenAPI contract, SDKs, and stored session rows, matching the in-memory architecture that replaced the deleted PostgreSQL frame relay ([#34](https://github.com/SecondStack-AI/SecondBox/pull/34)). - Brought the TypeScript SDK to exact parity with Go: `readFile` now requires and enforces an explicit maximum-bytes bound during streaming, `exec` and `run` accept the full argv/shell command union with standard input, and the Flue adapter configuration gains a required `maximumFileBytes`. The Go `ExecStream` now rejects non-canonical base64 output like the Terminal already did ([#31](https://github.com/SecondStack-AI/SecondBox/pull/31)). - Hardened microVM isolation: every jailed Firecracker instance now runs under a unique UID allocated from an explicitly declared, validated manifest range with crash-safe lease reconciliation, and host-side cgroups now cap cpu and pids alongside memory; the assignment protocol now carries the exact cpu millis and guest process limit, which production previously delivered as zero ([#36](https://github.com/SecondStack-AI/SecondBox/pull/36)). - Removed the per-keystroke PostgreSQL write from terminal sessions: durable accounting now checkpoints on detach, close, terminal outcome, and a poll-interval cadence, and terminal reattachment adopts the Runner's authoritative input sequence through a new optional protocol and API field, with both SDKs updated. Reattaching a running session through a Runner that predates the field is refused with the typed replay-evicted result ([#35](https://github.com/SecondStack-AI/SecondBox/pull/35)). - Expired idempotency records are no longer replayed on any path and are swept periodically together with aged activity touches, with a validated retention override; per-stream Runner-connection dedupe state is released at stream termination and the message-ID dedupe set is capped with a typed error ([#35](https://github.com/SecondStack-AI/SecondBox/pull/35)). - Removed the fleet-wide Runner lock from Sandbox creation, Snapshot-clone, and relocation placement: candidates are now ranked unlocked, locked one at a time with `SKIP LOCKED` and a blocking fallback, and admission durably reserves homed Workspace and inbound-relocation disk so concurrent placements cannot oversubscribe a Runner's storage ([#30](https://github.com/SecondStack-AI/SecondBox/pull/30)). - Indexed the lifecycle reconciler claim scan and the active-Lease expiry sweep, moved the global Lease sweep out of the claim transaction as a bounded `SKIP LOCKED` statement, and scoped in-transaction Lease fencing to the claimed Sandboxes so lifecycle workers no longer block each other ([#30](https://github.com/SecondStack-AI/SecondBox/pull/30)). - Release staging now refuses to run without qualification evidence produced by a full scenario-suite pass at the exact staged commit on a clean checkout; the evidence document ships in the release manifest and `SHA256SUMS`, and hosted publishing remains publish-only with no qualification step in CI ([#33](https://github.com/SecondStack-AI/SecondBox/pull/33)). - Simplified releases to two operator commands: build every artifact locally with `just release-stage`, then publish the supplied artifacts directly to GitHub, GHCR, and npm with `just release-upload` ([#25](https://github.com/SecondStack-AI/SecondBox/pull/25)). - Removed the dead candidate, qualification-attestation, release-index, and publication release surface left behind by the local-publish refactor, and rewrote the deployment and downstream-integration runbooks around the artifact-manifest verification flow the release actually produces ([#28](https://github.com/SecondStack-AI/SecondBox/pull/28)). - Added pinned golangci-lint (govet, staticcheck, unused, ineffassign, errcheck) to CI for both Go modules, removed the dead code the relay deletion and earlier refactors left behind, and made deployment-config migration fail on malformed legacy values instead of silently treating them as unset ([#34](https://github.com/SecondStack-AI/SecondBox/pull/34)). ### Fixed - Stopped counting stopped Sandboxes against subject CPU and memory quotas: usage now sums only the same active states as instance counting, so accumulated stopped Sandboxes no longer wedge Sandbox creation into `quota_exceeded`; cleanly fenced assignments with stop authority are now released in the fencing transaction instead of parking unclaimably once the Sandbox generation advances ([#39](https://github.com/SecondStack-AI/SecondBox/pull/39)). - Hardened the in-memory data-plane broker: frames addressed to a closed or unknown session route are dropped and counted instead of tearing down the Runner control connection, route delivery no longer blocks the control-stream reader behind a slow consumer, per-session credit violations fail only the offending session, and File reads grant stream-window credit incrementally instead of the full transfer limit up front ([#29](https://github.com/SecondStack-AI/SecondBox/pull/29)). - Fixed audit events to record the resolved tenant and subject attribution in the service layer for operator and subject actions, instead of depending on store-side normalization of an empty tenant reference ([#27](https://github.com/SecondStack-AI/SecondBox/pull/27)). ## 0.1.4 - 2026-08-04 First stable public release of SecondBox. ### Added - Added the self-hosted SecondBox control plane with PostgreSQL-backed projects, service accounts, API keys, immutable Profiles, durable Sandboxes, quotas, Runner scheduling, lifecycle reconciliation, and end-to-end request correlation. - Added home-Runner-pinned reflink Workspaces and local Snapshots with generation fencing, crash-safe restore, exact-home scheduling, storage-pressure admission, and explicit stopped-Sandbox relocation. - Added the outbound authenticated Runner protocol and Firecracker backend with independently versioned guest assets, cgroup isolation, network policy, termination reporting, and durable generation receipts. - Added direct and proxied Exec, filesystem, PTY, and Profile-approved port sessions with bounded backpressure, reconnect, cancellation, idempotency, and Lease fencing. - Added immutable Artifact storage, quota enforcement, retention garbage collection, and fail-closed PostgreSQL and Artifact backup tooling. - Added generated Go and TypeScript transports, high-level lifecycle helpers, Node transports, the Flue 2 adapter, and the `secondbox` CLI. - Added the strict versioned `secondbox.toml` deployment manifest, `secondbox-deploy`, explicit RunnerPool and standard Profile reconciliation, and base, development, and privileged same-host Runner Compose topologies. - Published `secondbox` and `secondbox-deploy` binaries for Linux and macOS on amd64 and arm64, the Go and TypeScript SDKs, digest-addressed OCI images, microVM assets, OpenAPI, checksums, and the `agent-compartment` and `durable-coding` standard bundles. ### Changed - Defined `Sandbox` as the durable public resource and `Instance` as replaceable compute fenced to one Sandbox generation. - Removed the PostgreSQL data-plane frame relay; Exec, File, PTY, and Port payloads now travel directly to a Runner or through the bounded in-memory proxy. - Replaced implicit built-in Profile reconciliation and ambient deployment defaults with explicit release resources, identities, paths, authorities, and validated configuration. ### Fixed - Preserved distinct runtime and toolchain identities throughout the release manifest and standard bundles ([#23](https://github.com/SecondStack-AI/SecondBox/pull/23)). - Included the mandatory `compute` capability in generated RunnerPools and compared pool architectures and capabilities as unordered sets, preventing missing admission and false deployment drift ([#24](https://github.com/SecondStack-AI/SecondBox/pull/24)). - Made packaged lifecycle checks tolerate Runner enrollment races, wait for completed transitions, and resolve the exact Go SDK package ([#24](https://github.com/SecondStack-AI/SecondBox/pull/24)).