lib/openagents/deployments/principal.ex

58e6347eeb72 · 3 KB

defmodule OpenAgents.Deployments.Principal do
  @moduledoc """
  The authority a caller holds when it reaches the deployment control plane.

  A principal is built once, at the edge, from a credential. It is not derived
  from anything in the request body, so a caller cannot claim a repository, an
  environment, a commit, or an operator role by asserting it. Four kinds exist:

    * `:user` — a human with a repository membership and the `deployments:write`
      token scope.
    * `:workflow` — a short-lived grant bound to one repository, one source ref,
      one workflow, and one workflow run.
    * `:operator` — a platform operator, which can recover stuck control-plane
      runs but holds no tenant deployment authority.
    * `:system` — the control plane acting on itself, for worker transitions.
  """

  alias OpenAgents.Accounts.User
  alias OpenAgents.Deployments.WorkflowGrant

  @kinds [:user, :workflow, :operator, :system]

  @type kind :: :user | :workflow | :operator | :system
  @type t :: %__MODULE__{
          kind: kind(),
          user: User.t() | nil,
          grant: WorkflowGrant.t() | nil,
          worker: String.t() | nil
        }

  @enforce_keys [:kind]
  defstruct [:kind, :user, :grant, :worker]

  @doc "A human principal acting under its own repository membership."
  @spec user(User.t()) :: t()
  def user(%User{} = user), do: %__MODULE__{kind: :user, user: user}

  @doc "A workflow principal acting under a bound, short-lived grant."
  @spec workflow(WorkflowGrant.t()) :: t()
  def workflow(%WorkflowGrant{} = grant), do: %__MODULE__{kind: :workflow, grant: grant}

  @doc "A platform operator principal, holding control-plane recovery authority only."
  @spec operator(User.t()) :: t()
  def operator(%User{} = user), do: %__MODULE__{kind: :operator, user: user}

  @doc "The control plane's own principal, used for worker-driven transitions."
  @spec system(String.t()) :: t()
  def system(worker) when is_binary(worker), do: %__MODULE__{kind: :system, worker: worker}

  @doc "The principal kinds the control plane recognizes."
  @spec kinds() :: [kind()]
  def kinds, do: @kinds

  @doc """
  The principal type recorded on a durable request.

  `:system` never holds an intent, so it has no request principal type.
  """
  @spec request_principal_type(t()) :: {:ok, String.t()} | {:error, :unsupported_principal}
  def request_principal_type(%__MODULE__{kind: :user}), do: {:ok, "user"}
  def request_principal_type(%__MODULE__{kind: :workflow}), do: {:ok, "workflow"}
  def request_principal_type(%__MODULE__{kind: :operator}), do: {:ok, "operator"}
  def request_principal_type(%__MODULE__{kind: :system}), do: {:error, :unsupported_principal}

  @doc "The actor kind recorded on an event."
  @spec actor_type(t()) :: String.t()
  def actor_type(%__MODULE__{kind: kind}), do: Atom.to_string(kind)

  @doc """
  The bounded actor identifier recorded on an event.

  A grant's identifier is the grant, never its token, and a user's identifier is
  the user id, never a login that can be renamed.
  """
  @spec actor_id(t()) :: String.t() | nil
  def actor_id(%__MODULE__{kind: :workflow, grant: %WorkflowGrant{id: id}}), do: id
  def actor_id(%__MODULE__{kind: :system, worker: worker}), do: worker
  def actor_id(%__MODULE__{user: %User{id: id}}), do: id
  def actor_id(%__MODULE__{}), do: nil
end