ADR 0001: Domain model and compute backend SPI¶
- Status: Accepted and implemented; remaining OpenShell behavior gaps are tracked in the compatibility matrix
- Date: 2026-10-16
- Decision owners: Quarshell maintainers
- Scope: compute backends and Gateway orchestration
Context¶
The driver package defines a product-owned request and Backend contract,
typed backend identities and capabilities, and optional extension interfaces.
All built-in local implementations register this contract directly; the Gateway
resolves only native Backends. Remote ComputeDriver plugins implement the same
Backend contract over the pinned OpenShell protocol. The VM backend currently
fails closed at Gateway runtime validation because its host-supervisor and
isolation lifecycle is incomplete. The former flat Spec/ComputeDriver
compatibility API has been removed.
Decision¶
- The product owns the domain contract. Orchestration uses
BackendCreateRequest,Backend,BackendInfo, and typed domain values. Transport messages, upstream protobuf types and container-engine request types stay outside this contract. - Keep the required lifecycle small.
Backendowns identity, capability discovery, create validation, create, start, stop, delete, list and inspect. Optional behavior is expressed by narrow extension interfaces, such asInteractiveExec,FileTransfer,ServiceForward,LogAccessand runtime extensions. A backend may implement only the behavior it can support. - Capabilities and implementations must agree. Callers obtain an
extension through
driver.RequireExtension[T]. The helper checks both the advertised capability and the method implementation and returns a typedUnsupportedErrorwhen either is absent. A caller must not silently downgrade a requested security or lifecycle behavior. - Validate before committing state. Gateway resolves and validates a backend create request before writing durable lifecycle state or consuming a one-use upload token. Secrets are short-lived operation inputs and are not copied into durable create records.
- Use direct modular backend contracts. Every built-in implementation
registers
Backenddirectly and owns its typed create model. Gateway resolves native factories only and returns factory errors without fallback. The earlier migration-adapter proposal is superseded by the 2026-10-21 direct-port decision; the unused flat API cleanup remains tracked under B0.6. - Keep the pinned wire contract. The generated OpenShell schema and its Gateway implementation keep the exact upstream service, field and package identifiers. Gateway methods map those requests directly to product domain operations. Product-owned API and domain names do not inherit upstream wire identifiers. Unsupported operations return typed unsupported results at the protocol boundary.
- Keep clients independent. CLI and browser UI are separate API clients. The CLI may contain terminal presentation code and call the SDK; browser UI uses a typed browser client and Gateway API. Gateway does not import CLI or UI packages, and browser UI does not launch the CLI.
Contract example¶
This excerpt uses the implemented Go SPI. A backend-specific configuration type belongs to that backend package; the shared request does not need a generic serialized driver configuration field:
type Backend interface {
ID() BackendID
Capabilities(context.Context) (BackendInfo, error)
ValidateCreate(context.Context, BackendCreateRequest) error
Create(context.Context, BackendCreateRequest) (Handle, error)
Start(context.Context, core.ID) error
Stop(context.Context, core.ID) error
Delete(context.Context, core.ID) error
List(context.Context) ([]Info, error)
Inspect(context.Context, string) (Info, error)
}
type BackendCreateRequest struct {
SandboxID core.ID
Name string
Workspace WorkspaceTarget
Workload WorkloadSpec
Resources ResourceLimits
Mounts []Mount
Network NetworkSpec
Runtime RuntimeAssets
Config any // typed by the selected backend, not a wire-format map
}
transfer, err := driver.RequireExtension[driver.FileTransfer](
ctx, backend, driver.CapabilityFileTransfer, "copy into sandbox",
)
if err != nil {
return err // preserve Unsupported; do not claim the operation succeeded
}
return transfer.CopyTo(ctx, sandboxID, source, destination)
The real BackendCreateRequest also includes the sandbox name and unsafe-mount
admission flag. Runtime and network values are ephemeral. Implementations call
ValidateCreate before allocating backend resources and reject inputs they
cannot safely represent.
Module and adapter layout¶
The target ownership boundaries are:
core/ domain values, policy, validation and typed errors
proto/ product API schemas and generated product clients
driver/ Backend SPI and Docker, Podman and MicroVM adapters
runtime/ guest init, supervisor and guest protocol
proxy/ egress enforcement and credential mediation
providers/ provider profiles and credential refresh validation
gateway/ auth, orchestration, durable state and API adapters
sdk/ Go and Python API clients
cli/ commands, SDK calls and terminal presentation
ui/ browser application and typed browser API client
These are ownership boundaries, not a requirement that every repository be split immediately. Current repository and Go module paths still use Cautem names; their staged rename and release order is documented in the rename and release plan.
The Gateway resolver in quarshell-gateway/internal/httpapi/compute.go selects
registered native backends. The external OpenShell protocol handler maps once
to the modular create request. A backend owns its typed config and implements
the domain SPI without importing CLI packages or transport schemas.
Consequences¶
- A backend can be added behind the SPI without adding backend conditionals to API clients or making its configuration part of a shared wire schema.
- Optional features have explicit capability negotiation and can fail closed.
- Native Backend registration is used for Docker, Podman, Kubernetes and VM;
the former flat
Spec/ComputeDrivercompatibility surface is removed. - VM Gateway provisioning remains fail-closed pending host-supervisor and isolation lifecycle parity. Current build evidence does not establish live backend acceptance, full feature parity or production readiness.
- At the gRPC compatibility boundary, unsupported behavior maps to the protocol's unsupported status. Existing wire identifiers remain stable while product-owned APIs can use their own namespace.
- Module, package and binary renames wait for configured target remotes, published replacement dependencies and a consumer build without local workspace overrides. See the release plan for the cutover and rollback order.
Verification¶
The decision is reflected in driver/backend.go, the Gateway backend resolver,
the compatibility RPC adapter, the RPC inventory and backend contract tests.
Those tests verify the adapter and the explicit unsupported path. Backend
production acceptance remains governed by the feature matrix and open-gap
register; this ADR does not change their status.
