lib/openagents/modules/routing_receipts.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Modules.RoutingReceipts do
  @moduledoc "Durably records a routing decision before any selected module dispatch."

  alias OpenAgents.Modules.{RouteDecision, RouteReceipt}
  alias OpenAgents.Repo

  @spec persist(Ecto.UUID.t(), String.t(), RouteDecision.t()) ::
          {:ok, RouteReceipt.t()} | {:error, Ecto.Changeset.t() | atom()}
  def persist(turn_receipt_id, provider_call_id, %RouteDecision{} = decision) do
    attributes = %{
      turn_receipt_id: turn_receipt_id,
      provider_call_id: provider_call_id,
      status: decision.status,
      reason: decision.reason,
      intent_digest: decision.intent_digest,
      registry_digest: decision.registry_digest,
      policy_id: decision.policy_id,
      policy_digest: decision.policy_digest,
      required_capability: decision.required_capability,
      required_side_effect: decision.required_side_effect,
      surface: decision.surface,
      selected: decision.selected,
      proposed: decision.proposed,
      rejected: decision.rejected,
      program_artifact: decision.program_artifact,
      fallback: decision.fallback,
      degraded: decision.degraded
    }

    case Repo.get_by(RouteReceipt,
           turn_receipt_id: turn_receipt_id,
           provider_call_id: provider_call_id
         ) do
      nil ->
        %RouteReceipt{}
        |> RouteReceipt.create_changeset(attributes)
        |> Repo.insert()

      %RouteReceipt{} = existing ->
        if same_decision?(existing, attributes),
          do: {:ok, existing},
          else: {:error, :route_receipt_conflict}
    end
  end

  defp same_decision?(receipt, attributes) do
    Enum.all?(
      [
        :status,
        :reason,
        :intent_digest,
        :registry_digest,
        :policy_id,
        :policy_digest,
        :required_capability,
        :required_side_effect,
        :surface,
        :selected,
        :proposed,
        :rejected,
        :program_artifact,
        :fallback,
        :degraded
      ],
      &(Map.get(receipt, &1) == Map.get(attributes, &1))
    )
  end
end