Packages
execution_plane_process
0.1.0
Execution Plane process launch, stdio, PTY, and process-session runtime.
Current section
Files
Jump to
Current section
Files
lib/execution_plane/process/transport/surface/capabilities.ex
defmodule ExecutionPlane.Process.Transport.Surface.Capabilities do
@moduledoc """
Typed transport-family and per-session execution-surface capabilities.
"""
defstruct remote?: false,
startup_kind: :spawn,
path_semantics: :local,
supports_run?: true,
supports_streaming_stdio?: true,
supports_pty?: true,
supports_user?: true,
supports_env?: true,
supports_cwd?: true,
interrupt_kind: :signal
@type startup_kind :: :spawn | :attach | :bridge
@type path_semantics :: :local | :remote | :guest
@type interrupt_kind :: :signal | :stdin | :rpc | :none
@type t :: %__MODULE__{
remote?: boolean(),
startup_kind: startup_kind(),
path_semantics: path_semantics(),
supports_run?: boolean(),
supports_streaming_stdio?: boolean(),
supports_pty?: boolean(),
supports_user?: boolean(),
supports_env?: boolean(),
supports_cwd?: boolean(),
interrupt_kind: interrupt_kind()
}
@type validation_error ::
{:invalid_capabilities, term()}
| {:invalid_remote, term()}
| {:invalid_startup_kind, term()}
| {:invalid_path_semantics, term()}
| {:invalid_supports_run, term()}
| {:invalid_supports_streaming_stdio, term()}
| {:invalid_supports_pty, term()}
| {:invalid_supports_user, term()}
| {:invalid_supports_env, term()}
| {:invalid_supports_cwd, term()}
| {:invalid_interrupt_kind, term()}
@capability_keys [
:remote?,
:startup_kind,
:path_semantics,
:supports_run?,
:supports_streaming_stdio?,
:supports_pty?,
:supports_user?,
:supports_env?,
:supports_cwd?,
:interrupt_kind
]
@atomish_values %{
"attach" => :attach,
"bridge" => :bridge,
"guest" => :guest,
"local" => :local,
"none" => :none,
"remote" => :remote,
"rpc" => :rpc,
"signal" => :signal,
"spawn" => :spawn,
"stdin" => :stdin
}
@type key ::
:remote?
| :startup_kind
| :path_semantics
| :supports_run?
| :supports_streaming_stdio?
| :supports_pty?
| :supports_user?
| :supports_env?
| :supports_cwd?
| :interrupt_kind
@spec keys() :: [key(), ...]
def keys, do: @capability_keys
@spec new(t() | keyword() | map()) :: {:ok, t()} | {:error, validation_error()}
def new(%__MODULE__{} = capabilities), do: {:ok, capabilities}
def new(attrs) when is_list(attrs) do
if Keyword.keyword?(attrs) do
attrs |> Map.new() |> new()
else
{:error, {:invalid_capabilities, attrs}}
end
end
def new(attrs) when is_map(attrs) do
with {:ok, remote?} <- validate_boolean(fetch(attrs, :remote?), :invalid_remote),
{:ok, startup_kind} <-
validate_member(
fetch(attrs, :startup_kind),
[:spawn, :attach, :bridge],
:invalid_startup_kind
),
{:ok, path_semantics} <-
validate_member(
fetch(attrs, :path_semantics),
[:local, :remote, :guest],
:invalid_path_semantics
),
{:ok, supports_run?} <-
validate_boolean(fetch(attrs, :supports_run?), :invalid_supports_run),
{:ok, supports_streaming_stdio?} <-
validate_boolean(
fetch(attrs, :supports_streaming_stdio?),
:invalid_supports_streaming_stdio
),
{:ok, supports_pty?} <-
validate_boolean(fetch(attrs, :supports_pty?), :invalid_supports_pty),
{:ok, supports_user?} <-
validate_boolean(fetch(attrs, :supports_user?), :invalid_supports_user),
{:ok, supports_env?} <-
validate_boolean(fetch(attrs, :supports_env?), :invalid_supports_env),
{:ok, supports_cwd?} <-
validate_boolean(fetch(attrs, :supports_cwd?), :invalid_supports_cwd),
{:ok, interrupt_kind} <-
validate_member(
fetch(attrs, :interrupt_kind),
[:signal, :stdin, :rpc, :none],
:invalid_interrupt_kind
) do
{:ok,
%__MODULE__{
remote?: remote?,
startup_kind: startup_kind,
path_semantics: path_semantics,
supports_run?: supports_run?,
supports_streaming_stdio?: supports_streaming_stdio?,
supports_pty?: supports_pty?,
supports_user?: supports_user?,
supports_env?: supports_env?,
supports_cwd?: supports_cwd?,
interrupt_kind: interrupt_kind
}}
end
end
def new(other), do: {:error, {:invalid_capabilities, other}}
@spec new!(t() | keyword() | map()) :: t()
def new!(attrs) do
case new(attrs) do
{:ok, %__MODULE__{} = capabilities} ->
capabilities
{:error, reason} ->
raise ArgumentError, "invalid execution-surface capabilities: #{inspect(reason)}"
end
end
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = capabilities) do
Map.take(capabilities, @capability_keys)
end
@spec subset?(t(), t()) :: boolean()
def subset?(%__MODULE__{} = effective, %__MODULE__{} = maximum) do
[
effective.remote? == maximum.remote?,
effective.startup_kind == maximum.startup_kind,
effective.path_semantics == maximum.path_semantics,
weaker_boolean?(effective.supports_run?, maximum.supports_run?),
weaker_boolean?(effective.supports_streaming_stdio?, maximum.supports_streaming_stdio?),
weaker_boolean?(effective.supports_pty?, maximum.supports_pty?),
weaker_boolean?(effective.supports_user?, maximum.supports_user?),
weaker_boolean?(effective.supports_env?, maximum.supports_env?),
weaker_boolean?(effective.supports_cwd?, maximum.supports_cwd?),
weaker_interrupt_kind?(effective.interrupt_kind, maximum.interrupt_kind)
]
|> Enum.all?()
end
@spec satisfies_requirements?(t(), map() | keyword() | t()) :: boolean()
def satisfies_requirements?(%__MODULE__{} = effective, %__MODULE__{} = required) do
satisfies_requirements?(effective, to_map(required))
end
def satisfies_requirements?(%__MODULE__{} = effective, required) when is_list(required) do
if Keyword.keyword?(required) do
satisfies_requirements?(effective, Map.new(required))
else
false
end
end
def satisfies_requirements?(%__MODULE__{} = effective, required) when is_map(required) do
Enum.all?(required, fn
{:remote?, value} -> effective.remote? == value
{"remote?", value} -> effective.remote? == value
{:startup_kind, value} -> effective.startup_kind == normalize_atomish(value)
{"startup_kind", value} -> effective.startup_kind == normalize_atomish(value)
{:path_semantics, value} -> effective.path_semantics == normalize_atomish(value)
{"path_semantics", value} -> effective.path_semantics == normalize_atomish(value)
{:supports_run?, value} -> effective.supports_run? == value
{"supports_run?", value} -> effective.supports_run? == value
{:supports_streaming_stdio?, value} -> effective.supports_streaming_stdio? == value
{"supports_streaming_stdio?", value} -> effective.supports_streaming_stdio? == value
{:supports_pty?, value} -> effective.supports_pty? == value
{"supports_pty?", value} -> effective.supports_pty? == value
{:supports_user?, value} -> effective.supports_user? == value
{"supports_user?", value} -> effective.supports_user? == value
{:supports_env?, value} -> effective.supports_env? == value
{"supports_env?", value} -> effective.supports_env? == value
{:supports_cwd?, value} -> effective.supports_cwd? == value
{"supports_cwd?", value} -> effective.supports_cwd? == value
{:interrupt_kind, value} -> effective.interrupt_kind == normalize_atomish(value)
{"interrupt_kind", value} -> effective.interrupt_kind == normalize_atomish(value)
_other -> false
end)
end
def satisfies_requirements?(_effective, _required), do: false
defp fetch(attrs, key) when is_map(attrs) do
default = Map.get(%__MODULE__{}, key)
Map.get(attrs, key, Map.get(attrs, Atom.to_string(key), default))
end
defp validate_boolean(value, _reason) when is_boolean(value), do: {:ok, value}
defp validate_boolean(value, reason), do: {:error, {reason, value}}
defp validate_member(value, allowed, reason) when is_list(allowed) do
if value in allowed do
{:ok, value}
else
{:error, {reason, value}}
end
end
defp weaker_boolean?(false, _maximum), do: true
defp weaker_boolean?(true, true), do: true
defp weaker_boolean?(_effective, _maximum), do: false
defp weaker_interrupt_kind?(kind, kind), do: true
defp weaker_interrupt_kind?(:none, _maximum), do: true
defp weaker_interrupt_kind?(_effective, _maximum), do: false
defp normalize_atomish(value) when is_atom(value), do: value
defp normalize_atomish(value) when is_binary(value) do
Map.get(@atomish_values, value)
end
defp normalize_atomish(_other), do: nil
end