<p align="center">
<img src="assets/external_runtime_transport.svg" alt="External Runtime Transport logo" width="200" height="200" />
</p>
# ExternalRuntimeTransport
<p align="center">
<a href="https://hex.pm/packages/external_runtime_transport"><img src="https://img.shields.io/hexpm/v/external_runtime_transport.svg" alt="Hex version" /></a>
<a href="https://hexdocs.pm/external_runtime_transport"><img src="https://img.shields.io/badge/hexdocs-docs-264FD7.svg" alt="HexDocs" /></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-111111.svg" alt="MIT License" /></a>
</p>
`external_runtime_transport` is the shared execution-surface substrate for the
Jido runtime stack. It owns the generic `execution_surface` contract, adapter
registry, transport facade, and the built-in `:local_subprocess`,
`:ssh_exec`, and `:guest_bridge` families.
The package is intentionally transport-focused. Provider planning, session
parsing, approval policy, workspace policy, and other application concerns stay
in downstream repos such as `cli_subprocess_core`.
## What This Package Owns
- `ExternalRuntimeTransport.ExecutionSurface` defines the public placement seam.
- `ExternalRuntimeTransport.Transport` is the generic run/start facade.
- `ExternalRuntimeTransport.ExecutionSurface.Adapter` and the internal registry
own built-in adapter dispatch.
- the built-in local subprocess, SSH exec, and guest bridge adapters implement
the landed transport families.
- `ExternalRuntimeTransport.Transport.Options`,
`ExternalRuntimeTransport.Transport.RunOptions`,
`ExternalRuntimeTransport.Transport.RunResult`, and
`ExternalRuntimeTransport.Transport.Info` own normalized transport contracts.
- `ExternalRuntimeTransport.Command`, `ExternalRuntimeTransport.ProcessExit`,
`ExternalRuntimeTransport.LineFraming`, and `ExternalRuntimeTransport.TaskSupport`
support the substrate itself.
## Installation
```elixir
def deps do
[
{:external_runtime_transport, "~> 0.1.0"}
]
end
```
## Quick Start
Run a local command through the generic facade:
```elixir
alias ExternalRuntimeTransport.Command
alias ExternalRuntimeTransport.Transport
{:ok, result} =
Transport.run(
Command.new("sh", ["-c", "printf ready"])
)
IO.inspect(result.stdout)
```
Move the same command onto SSH without naming an adapter module:
```elixir
{:ok, result} =
Transport.run(
Command.new("hostname"),
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [
destination: "buildbox.example",
ssh_user: "deploy",
port: 22
]
]
)
```
Start a long-lived transport with the same public seam:
```elixir
ref = make_ref()
{:ok, transport} =
Transport.start(
command: Command.new("sh", ["-c", "cat"]),
subscriber: {self(), ref},
stdout_mode: :raw,
stdin_mode: :raw,
execution_surface: [surface_kind: :local_subprocess]
)
```
## Execution Surface
The public placement contract is one `execution_surface` value with these
fields:
- `contract_version`
- `surface_kind`
- `transport_options`
- `target_id`
- `lease_ref`
- `surface_ref`
- `boundary_class`
- `observability`
The contract does not expose adapter module names. Callers choose placement by
describing the surface, and the substrate resolves the built-in adapter
internally.
Use `ExternalRuntimeTransport.ExecutionSurface.to_map/1` when you need the
versioned map projection for JSON-safe boundaries.
Supported built-in surface kinds today are:
- `:local_subprocess`
- `:ssh_exec`
- `:guest_bridge`
Use `ExternalRuntimeTransport.ExecutionSurface.capabilities/1`,
`path_semantics/1`, `remote_surface?/1`, and `nonlocal_path_surface?/1` when a
higher layer needs to reason about the surface generically.
## Documentation
- `guides/getting-started.md` covers the public facade.
- `guides/execution-surface-contract.md` defines the stable placement seam.
- `guides/guest-bridge-contract.md` covers the attach contract for
`:guest_bridge`.
- `examples/README.md` points at runnable placement examples.
Published HexDocs:
<https://hexdocs.pm/external_runtime_transport>
## Oversize Line Recovery
The transport now treats oversized stdout lines as a bounded recovery problem instead of an
unbounded buffer-growth bug.
- `max_buffer_size` remains the steady-state in-memory line buffer and defaults to `1_048_576`
bytes.
- `oversize_line_chunk_bytes` defaults to `131_072` bytes and is used to incrementally recover a
large line without letting the framer grow without bound.
- `max_recoverable_line_bytes` defaults to `16_777_216` bytes and is the hard ceiling for a single
recoverable line.
- `oversize_line_mode` is now `:chunk_then_fail` and `buffer_overflow_mode` is intentionally
`:fatal`.
The operational rule is simple: recover the full line while it remains within the configured
ceiling, then fail fast with a structured `buffer_overflow` error once the data-loss boundary is
crossed. The transport no longer pretends that silently dropped bytes are a healthy recovery path.