Current section

Files

Jump to
agent_session_manager lib asm adapter_selection_policy.ex
Raw

lib/asm/adapter_selection_policy.ex

defmodule ASM.AdapterSelectionPolicy do
@moduledoc """
Phase 6 adapter selection policy declaration for ASM provider resolution.
ASM consumes CLI core runtime profile configuration as a provider-registry
rule. It does not expose request-time simulation selectors.
"""
@contract_version "ASM.AdapterSelectionPolicy.v1"
@owner_repo "agent_session_manager"
@selection_surfaces [
"application_config",
"adapter_registry",
"transport_registry",
"provider_registry",
"backend_manifest",
"profile_registry_install"
]
defstruct [
:contract_version,
:selection_surface,
:owner_repo,
:config_key,
:default_value_when_unset,
:fail_closed_action_when_misconfigured
]
@type t :: %__MODULE__{
contract_version: String.t(),
selection_surface: String.t(),
owner_repo: String.t(),
config_key: String.t(),
default_value_when_unset: String.t(),
fail_closed_action_when_misconfigured: String.t()
}
@spec contract_version() :: String.t()
def contract_version, do: @contract_version
@spec owner_repo() :: String.t()
def owner_repo, do: @owner_repo
@spec new(map() | keyword() | t()) :: {:ok, t()} | {:error, Exception.t()}
def new(%__MODULE__{} = value), do: {:ok, value}
def new(attrs) do
{:ok, build(attrs)}
rescue
error in ArgumentError -> {:error, error}
end
@spec new!(map() | keyword() | t()) :: t()
def new!(%__MODULE__{} = value), do: value
def new!(attrs) do
case new(attrs) do
{:ok, value} -> value
{:error, error} -> raise error
end
end
@spec dump(t()) :: map()
def dump(%__MODULE__{} = policy) do
policy
|> Map.from_struct()
|> stringify_contract()
end
defp build(attrs) do
attrs = normalize_attrs(attrs)
reject_public_simulation_selector!(attrs)
config_key = fetch_required_stringish!(attrs, :config_key)
reject_public_simulation_config_key!(config_key)
%__MODULE__{
contract_version: validate_contract_version!(attrs),
selection_surface:
attrs
|> fetch_required_stringish!(:selection_surface)
|> validate_selection_surface!(),
owner_repo: validate_owner_repo!(fetch_required_stringish!(attrs, :owner_repo)),
config_key: config_key,
default_value_when_unset: fetch_required_stringish!(attrs, :default_value_when_unset),
fail_closed_action_when_misconfigured:
fetch_required_stringish!(attrs, :fail_closed_action_when_misconfigured)
}
end
defp normalize_attrs(attrs) when is_map(attrs), do: attrs
defp normalize_attrs(attrs) when is_list(attrs) do
if Keyword.keyword?(attrs) do
Map.new(attrs)
else
raise ArgumentError, "expected keyword attrs, got: #{inspect(attrs)}"
end
end
defp normalize_attrs(attrs) do
raise ArgumentError, "expected map or keyword attrs, got: #{inspect(attrs)}"
end
defp fetch_required_stringish!(attrs, key) do
attrs
|> fetch_value(key)
|> validate_non_empty_string!(to_string(key))
end
defp fetch_value(attrs, key), do: Map.get(attrs, key, Map.get(attrs, Atom.to_string(key)))
defp validate_non_empty_string!(value, _field_name)
when is_binary(value) and byte_size(value) > 0,
do: value
defp validate_non_empty_string!(value, field_name) when is_atom(value) do
value
|> Atom.to_string()
|> validate_non_empty_string!(field_name)
end
defp validate_non_empty_string!(value, field_name) do
raise ArgumentError, "#{field_name} must be a non-empty string, got: #{inspect(value)}"
end
defp validate_contract_version!(attrs) do
version = fetch_value(attrs, :contract_version) || @contract_version
if version == @contract_version do
version
else
raise ArgumentError,
"expected contract_version #{inspect(@contract_version)}, got: #{inspect(version)}"
end
end
defp reject_public_simulation_selector!(attrs) do
if Map.has_key?(attrs, :simulation) or Map.has_key?(attrs, "simulation") do
raise ArgumentError,
"public simulation selector is forbidden; use owner registry configuration"
end
end
defp reject_public_simulation_config_key!(config_key) do
if config_key == "simulation" or String.contains?(config_key, ".simulation") do
raise ArgumentError, "config_key must not be a public simulation selector"
end
end
defp validate_selection_surface!(selection_surface) do
if selection_surface in @selection_surfaces do
selection_surface
else
raise ArgumentError, "selection_surface unsupported value: #{inspect(selection_surface)}"
end
end
defp validate_owner_repo!(@owner_repo), do: @owner_repo
defp validate_owner_repo!(owner_repo) do
raise ArgumentError, "owner_repo must be #{@owner_repo}, got: #{inspect(owner_repo)}"
end
defp stringify_contract(map) do
Enum.into(map, %{}, fn {key, value} -> {to_string(key), value} end)
end
end