Extend and embed

Author a backend

Implement the ABI 10 lifecycle, capability vocabulary, filesystem semantics and descriptor contract.

A backend implements maelys_executor_backend_ops_t and is registered with maelys_executor_register_backend().

ABI 10 lifecycle, target compatibility and budgets

CODE
probe -> probe_target(TargetSpec) -> prepare(request, plan) -> artifact -> spawn
                                                               -> instance
                                      |
                                      +-> artifact_release exactly once
  • probe reports only guarantees enforced on the running host.
  • probe_target evaluates target kind/platform separately from MIR capability bits and returns stable evidence bound into selection and the artifact.
  • prepare starts no workload and copies, validates or pins everything the artifact needs. Failure leaves no resource behind.
  • after successful prepare, the core calls artifact_release exactly once, whether spawn succeeds or fails.
  • spawn consumes the immutable artifact and either returns one complete instance or leaves no process, descriptor or handle behind.
  • returned stream descriptors belong to Executor immediately.
  • wait honors its timeout. status.exited means the lifecycle domain tracked by that backend is empty.
  • stop is non-blocking and idempotent.
  • release is local-only: it neither signals nor waits.

The ops table must outlive the executor. Context ownership transfers only after successful registration and only when destroy_context is supplied.

abi_version is checked before any ops field is used. Target-aware probing remains mandatory in ABI 10 and every backend must implement it. ABI 10 also carries the normalized root mode, exact ephemeral scratch ceiling and terminal cleanup outcome. An ABI 2 backend is therefore rejected with a diagnostic instead of being interpreted through the wrong layout.

Capability vocabulary

Capabilities describe guarantees, not implementation technologies:

  • PROCESS_CONFINEMENT: the isolation policy follows descendants;
  • PROCESS_TREE_LIFECYCLE: wait/stop cover descendants even after session or process-group escape;
  • FS_ACCESS_RULES: READ and WRITE rule lowering;
  • FS_DENY_READ: explicit DENY lowering where DENY wins over every allow;
  • FS_VIEW_MAPPING and FS_TMPFS: view construction;
  • NETWORK_NONE: no ambient external network path;
  • NETWORK_IP_NONE: no ambient IP path; pathname-addressed Unix sockets deliberately exposed by a filesystem mapping may remain reachable;
  • NETWORK_EXTERNAL_CHANNEL: ambient networking is denied and the borrowed Unix datagram channel from maelys_execution_request_external_channel_fd() is inherited as child fd 4;
  • NETWORK_MEDIATED_PROXY: the backend admits only the execution-scoped proxy endpoint supplied by the request and prevents workload-created sockets from reaching another host channel. Bubblewrap realizes this with an internal loopback relay in a fresh network namespace; Seatbelt lowers the endpoint to one exact loopback-port rule. The capability does not imply that the backend authenticates or evaluates destinations: those guarantees belong to Egress;
  • FS_PROTECT_CREATE: an absent denied path cannot be created.

Never advertise a capability merely because a primitive exists. Selection is fail-closed, but prepare may still reject an individual plan that cannot be represented safely on the current host.

The exact built-in masks and the additional per-plan restrictions are listed in backend-capabilities.md. In particular, apple-container and apple-container-bubblewrap accept OCI targets only; the latter advertises recursive READ/WRITE rules but deliberately omits DENY, PROTECT_CREATE and an inner syscall filter.

Filesystem semantics

READ grants read. WRITE grants read and write. Allow rules are additive; DENY subtracts both read and write and always wins, independent of insertion order. LITERAL matches one vnode; RECURSIVE matches the path and descendants.

MISSING_ERROR rejects an absent path. MISSING_SKIP omits it. MISSING_PROTECT_CREATE is valid only for DENY and requires a separate backend capability. No built-in backend currently advertises that capability.

The canonical plan sort is for hashing and deterministic compilation. It never defines authorization precedence.

Returned descriptors

Every active stream descriptor returned by spawn is blocking, close-on-exec and distinct from every other returned descriptor; inactive entries are -1. The protocol stream is a SOCK_STREAM socket. It has SO_NOSIGPIPE on systems that provide the option; other systems must be compatible with a MSG_NOSIGNAL write path. The core validates these properties before exposing an instance. An invalid instance is forcibly stopped and boundedly waited; a cleanup failure is reported as a containment failure, never hidden behind a layout diagnostic.

For NETWORK_EXTERNAL_CHANNEL, prepare must duplicate the borrowed request fd into its immutable artifact and spawn must map it to MAELYS_EXECUTOR_EXTERNAL_CHANNEL_FD (4). The fd is blocking, close-on-exec, connected AF_UNIX/SOCK_DGRAM; it is not an ambient network capability. The backend must prevent ordinary network creation while preserving fd 4.

Launcher acceptance

For direct POSIX execution, a successful spawn means the target image was accepted by posix_spawn. For wrapper backends such as Bubblewrap and sandbox-exec, it means the trusted launcher image was accepted; a later launcher setup or target-exec error is observed through wait and diagnostics. spawn_timeout_ms is retained for remote/helper backends but is not a hard interruptible deadline for the local posix_spawn substrate in 0.18.0.

Local process backends own their child wait status. Embedders must not install SA_NOCLDWAIT or reap Executor children from an application-wide SIGCHLD handler; doing so makes the backend return an explicit wait error.