lib/openagents/roles/catalog.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Roles.Catalog do
  @moduledoc "Versioned catalog of admitted and explicitly inactive Sarah role programs."

  alias OpenAgents.Provenance.Canonical
  alias OpenAgents.Roles.{CodingLieutenant, GeneralCollaborator, Role}

  @inactive_programs [
    {"sarah.role.sales_discovery.v1", "sales_discovery", ["text", "voice"], ["crm.read"]},
    {"sarah.role.company_operations.v1", "company_operations", ["text", "voice"],
     ["operations.read", "operations.write"]},
    {"sarah.role.public_broadcast.v1", "public_broadcast", ["public"], ["broadcast.publish"]}
  ]

  @spec all() :: [Role.t()]
  def all do
    [GeneralCollaborator.artifact(), CodingLieutenant.artifact()] ++
      Enum.map(@inactive_programs, &inactive_role/1)
  end

  @spec fetch(String.t()) :: {:ok, Role.t()} | {:error, :unknown_role}
  def fetch(role_id) when is_binary(role_id) do
    case Enum.find(all(), &(&1.id == role_id)) do
      nil -> {:error, :unknown_role}
      role -> {:ok, role}
    end
  end

  @spec digest() :: String.t()
  def digest do
    all()
    |> Enum.sort_by(& &1.id)
    |> Enum.map(&Map.take(&1, [:id, :version, :status, :digest]))
    |> Canonical.digest!()
  end

  defp inactive_role({id, register, surfaces, required_capabilities}) do
    content =
      "Inactive role placeholder. It cannot enter runtime composition until its surface, " <>
        "authority, product facts, tools, and containment evidence are admitted."

    source_digest = Canonical.sha256(content)

    attributes = %{
      "schema" => "sarah.role_program.v1",
      "id" => id,
      "version" => 1,
      "status" => "inactive",
      "content" => content,
      "source_ref" => "roadmap-role:" <> id,
      "source_digest" => source_digest,
      "compatibility_min" => 1,
      "compatibility_max" => 1,
      "surfaces" => surfaces,
      "required_capabilities" => required_capabilities,
      "register" => register
    }

    %Role{
      id: id,
      version: 1,
      status: "inactive",
      content: content,
      digest: Canonical.digest!(attributes),
      source_ref: attributes["source_ref"],
      source_digest: source_digest,
      compatibility_min: 1,
      compatibility_max: 1,
      surfaces: surfaces,
      required_capabilities: required_capabilities,
      register: register
    }
  end
end