Managed runtime manifest v2
BranchBox's in-guest runtime accepts an orchestrator-owned, versioned assignment. The manifest
describes identities and materializations without teaching BranchBox about a particular platform,
coding provider, source host, tunnel vendor, or secret manager.
The version 2 fields below remain supported for assignment parsing and legacy recovery. Managed
Dev Containers launches now require version 3 with a distinct non-root workspace consumer and a
complete immutable service_images map. Version 1/2 and no-consumer launches fail before CLI
preflight. Legacy provider state without the signed-run-derived Compose project name cannot be
automatically torn down and requires outer-VM/operator cleanup.
Version 3 workspace delegation preserves existing file and directory modes, while new worktree
descendants inherit owner and consumer-group access with no other access, even below a previously
world-readable directory. After delegation the consumer can also edit shared Git metadata, so
managed in-guest teardown requires --force. BranchBox does not run a dirty-worktree status check
on that metadata; callers must commit changes they want to retain before teardown. Managed launches
also refuse --reuse and implicit stale-branch reuse: a later launch must use a fresh repository
snapshot because a prior consumer could have changed Git remotes or executable config. Before any
managed checkout, BranchBox rejects shared Git metadata that is not runtime-owned or has
group/other-writable paths, including a tree delegated by a previous run. It also rejects an
ancestor that another UID could replace, apart from sticky directories such as /tmp.
Version 2 separates three identities:
consumeris an arbitrary name used to bind related leases.executableis the exact provider entrypoint BranchBox may start for that consumer.environment_nameis the exact environment target for one owner-only, digest-bound value file.
For example:
{
"version": "2",
"leases": [
{
"lease_id": "lease_model",
"scope": "model-identity",
"consumer": "coding-agent",
"executable": "provider-cli",
"inherited_environment": ["MODEL_ACCESS_TOKEN"],
"expires_at": "2099-01-01T00:00:00Z",
"materializations": []
},
{
"lease_id": "lease_delivery",
"scope": "provider-environment",
"consumer": "coding-agent",
"expires_at": "2099-01-01T00:00:00Z",
"materializations": [
{
"source_path": "/run/branchbox/managed/run-id/materializations/delivery-token",
"environment_name": "SOURCE_DELIVERY_TOKEN",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}
]
}
The complete manifest also carries the run, outer-runtime, repository, branch, workspace, and published-port fields required by version 1.
Preloaded service images
A version 2 assignment can opt into build-free startup by binding Compose service names to exact preloaded image references:
{
"version": "2",
"published_ports": [{"host": 3000, "runtime": 3000}],
"service_images": {
"app": "registry.example/team/app@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"database": "registry.example/team/database@sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
},
"port_proxy_image": "registry.example/runtime/tcp-proxy@sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
When service_images is present and non-empty, it must bind every runnable service declared by the
repository Compose files. Services that BranchBox disables as outer tunnel connectors are excluded.
Unknown, disabled, missing, duplicate map entries, indirect Compose includes, interpolated,
URL-shaped, tag-only, and non-SHA-256 image values are rejected before startup. A valid value is a
literal lowercase registry/repository@sha256:<64 lowercase hexadecimal characters> reference; an
optional tag and registry port may precede the digest. A local image content digest in the exact
form sha256:<64 lowercase hexadecimal characters> is also accepted.
When preloaded service mode publishes one or more ports, port_proxy_image is required and follows
the same immutable-reference rules. BranchBox inspects that exact image locally before startup and
passes it as an argument to the out-of-Compose loopback proxy launch with Docker --pull=never.
This closes the only image path outside the generated Compose facade. A version 2 assignment may
also set port_proxy_image independently of service_images for legacy parsing, but cannot launch
a managed devcontainer.
BranchBox writes the assigned image, resets the repository build, and sets pull_policy: never
for each bound service. It also removes devcontainer build and features, disables remote-user UID
image rewriting, and verifies every assigned reference with a local Docker image inspection before
running the Dev Containers CLI. A missing image therefore fails the assignment instead of building
or pulling during the task. Managed launches with omitted or incomplete service_images fail
closed before the CLI can read writable generated files or resolve relative build paths.
Shared directory, tool endpoint, and request-spool leases
Version 2 can also expose provider-neutral, per-run channels to the primary devcontainer. A
shared-directory lets an outer helper update finalized evidence while the container sees a
read-only bind. A standalone tool-endpoint can expose an owner-only directory or socket as a
read-only compatibility bind. A linked socket endpoint is different: it remains wholly outside the
coding container and is paired with a tool-request spool. The spool is a run-and-lease-derived
Docker volume, not a bind to the private endpoint.
{
"version": "2",
"run_id": "run-id",
"leases": [
{
"lease_id": "shared_exchange",
"scope": "shared-directory",
"consumer": "primary-tool",
"expires_at": "2099-01-01T00:00:00Z",
"materializations": [
{
"source_path": "/run/branchbox/managed/run-id/shared/exchange",
"target_path": "/run/branchbox/leases/shared/exchange"
}
]
},
{
"lease_id": "delivery_endpoint",
"scope": "tool-endpoint",
"consumer": "coding-agent",
"expires_at": "2099-01-01T00:00:00Z",
"materializations": [
{
"source_path": "/run/branchbox/managed/run-id/tool-endpoints/delivery.sock",
"target_path": "/run/branchbox/leases/tool-endpoints/delivery.sock"
}
]
},
{
"lease_id": "delivery_requests",
"scope": "tool-request",
"consumer": "coding-agent",
"consumer_uid": 1000,
"endpoint_lease_id": "delivery_endpoint",
"request_spool_target": "/run/branchbox/leases/tool-requests/delivery",
"replay_policy": "exact-digest-replay-v1",
"expires_at": "2099-01-01T00:00:00Z",
"materializations": [
{
"source_path": "/run/branchbox/managed/run-id/materializations/delivery-capability",
"target_path": "/run/branchbox/leases/tool-requests/delivery/.capability",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
]
}
]
}
The assignment must be stored directly in /run/branchbox/managed/run-id/. The run directory and
every source-directory ancestor use mode 0700 and the assignment owner's UID. A shared-directory
leaf uses mode 0755 so arbitrary non-root container users can inspect its files through the exact
read-only bind; its private ancestors still prevent ambient host traversal. Tool-endpoint
directories remain 0700, and socket permissions remain owner-only.
Directly binding that socket does not translate UID 10001 in the guest to UID 1000 in a typical
container. Docker Compose has no portable per-bind idmapped-mount contract, and changing the socket
owner, group, ACL, or mode would weaken the trusted boundary. A linked tool-request therefore
suppresses the endpoint bind. Only its exact named request volume may be writable.
BranchBox initializes the volume with these paths:
.binding.json: root-owned mode0444, containing the non-secret run, lease, consumer, request and response paths, filename convention, quotas, and optional replay policy from the signed assignment..capability: root-owned mode0444, containing a random endpoint-only capability. This token authorizes requests to the linked broker endpoint; it is not the broker's source-host, cloud, model, or other underlying credential.requests/: mode0700and owned byconsumer_uid. The consumer writes a temporary file, uses mode0600, then atomically renames it to<request_id>.json.responses/: root-owned mode0755. The trusted dispatcher atomically creates a correlated<request_id>.jsonowned byconsumer_uidat mode0400, so the consumer can read but cannot replace it..processing/: root-owned mode0700and invisible to the consumer. BranchBox atomically moves one finalized request here before reading it, then re-checks its type, owner, mode, size, and link count. It retains the exact staged file until the replay claim is durable, closing the read-before-claim process-crash gap, and then removes it exactly.
The request envelope has only these top-level fields:
{
"version": "1",
"run_id": "run-id",
"lease_id": "delivery_requests",
"consumer": "coding-agent",
"request_id": "artifact-delivery",
"capability": "endpoint-only-random-value",
"payload": {}
}
The payload is opaque to BranchBox. The trusted endpoint must return the same version, run, lease,
consumer, and request IDs plus an opaque payload. BranchBox first moves the request into the
root-only staging directory and keeps it there until an owner-only replay record containing the
capability-stripped canonical request and SHA-256 fingerprint is durable. It serializes each request
ID with a process-crash-safe advisory lock, then relays one newline-delimited JSON frame followed by
write-side EOF. It validates and durably caches the correlated response before idempotently writing
the consumer response file.
Replay is denied by default, preserving the original fail-closed behavior for existing manifests.
A trusted endpoint that itself guarantees exact-request idempotency can explicitly opt in on its
provider-neutral tool-request lease:
"replay_policy": "exact-digest-replay-v1"
With that signed policy, an interrupted relay may retry only the persisted exact request. A newly spooled request must pass the capability check and match the original request and fingerprint; changed or malformed same-ID requests remain terminal. Once a correlated response is cached, subsequent exact dispatches return it without contacting or re-executing the endpoint. Without the signed policy, a relay failure leaves the claim permanently blocked because the external side effect may already have occurred. An authenticated correlated response is a successful transport even when its opaque payload reports a tool-level decline (for example, an optional upload declined in favor of a durable artifact link). Only transport, authentication, framing, or correlation failures fail dispatch. With signed exact replay enabled, transport failures are retryable only within the dispatcher's bounded wait window; authentication, malformed framing, changed fingerprints, and correlation failures remain terminal.
Run the dispatcher concurrently with the coding provider:
branchbox feature dispatch-tool coding-demo \
--lease delivery_requests \
--request-id artifact-delivery \
--wait-seconds 300 \
--json
Absence of the atomic final request is retryable. An exhausted wait returns exit status 75 and
JSON with status: "not-pending" and retryable: true. A transport-indeterminate exact replay is
also retried inside the same bounded wait only when its signed lease opts in. Success returns status
dispatched. Malformed paths, modes, symlinks, quotas, bindings, capabilities, changed requests,
unsigned replay claims, and response mismatches are terminal and never share the retryable exit
status.
Security contract
- The assignment and file materializations must be owner-only regular files. File materializations
are siblings below the assignment's
materializationsdirectory and match their declared SHA-256. - Shared-directory and tool-endpoint leases are live, exact-run bindings. BranchBox rejects sources
outside the manifest's
run_iddirectory, source ownership or type changes, overlapping sources, targets outside the scope-specific BranchBox lease namespace, and ambient supervisor-authority or secret paths. - The generated configuration mounts only manifest-declared source/target pairs. Final Docker inspection must show each bind-capable lease exactly once as a read-only bind on the primary container; a missing, writable, mistargeted, duplicate, or unsigned managed mount fails startup.
- A linked tool endpoint is never mounted. Final inspection permits one exact writable named volume
per signed
tool-requestlease and rejects writable binds, read-only request volumes, wrong volume names, or any other writable entry under the managed namespace. Docker'sMounts[].Nameis the signed volume identity; its engine-private physicalSourcepath is never treated as a consumer mount or exposed to the container. - Request spools enforce a non-root consumer UID, one same-consumer live socket endpoint, immutable
binding/capability files, regular non-symlink request files, mode
0600, at most 16 pending files, 256 KiB per request, and 1 MiB total. BranchBox resolves the actual provider user fromremoteUser/containerUseror Docker's inspectedConfig.User, runsid -uin the container, and rejects root or any mismatch with the signedconsumer_uidbefore spool initialization and again before dispatch. Docker exec and Unix relay operations are time-bounded. - The primary devcontainer is forced onto Docker's built-in seccomp profile, and final inspection
rejects a missing or unconfined profile. This preserves normal Unix and Internet socket families
while denying direct
AF_VSOCKcreation even when no/dev/vsockpath is mounted. - A provider starts only when the requested executable and inherited environment exactly match one
live
model-identitylease. Provider-environment values are selected only for that lease's exact consumer. - Environment names use bounded uppercase syntax and reject shell, loader, Git-control, container supervisor, and BranchBox control variables. Value files are bounded, UTF-8, and control-free.
- Provider-environment files are never mounted into the devcontainer and their names may not persist in the container configuration. BranchBox reads them immediately before provider execution, recomputes the digest from the bytes it will use, and passes values only to that provider process tree.
- Provider execution starts from an empty process environment, adding only a fixed safe
PATH, the exact signed inherited names, and the exact digest-bound provider-environment bindings. - Teardown removes every recorded materialization, including non-empty shared directories and Unix sockets, every exact request volume, and the replay ledger. Any source that cannot be removed is reported as residue and prevents a residue-free teardown receipt.
Version 1 remains accepted for existing integrations and retains its original fixed provider
allowlist. Orchestrators launching managed Dev Containers must emit version 3 and place managed
files below a run-specific directory such as /run/branchbox/managed/<run-id>/.