test/openagents/modules/router_test.exs

436d37facc96 · 9 KB

defmodule OpenAgents.Modules.RouterTest do
  use ExUnit.Case, async: true

  alias OpenAgents.Modules.{Router, RoutingPolicy, SurfacePolicy}
  alias OpenAgents.Provenance.Canonical
  alias OpenAgents.Tools.{Registry, ScvDeploy}

  test "identical intent, policy, and catalog select the same deterministic baseline" do
    snapshot = Registry.current!()
    policy = RoutingPolicy.default()
    input = input()

    assert {:ok, first} = Router.route(snapshot, policy, input)
    assert {:ok, second} = Router.route(snapshot, policy, input)
    assert first == second
    assert first.status == "selected"
    assert first.selected["registry_digest"] == snapshot.digest
    assert first.reason == "deterministic_baseline_selected"
  end

  test "an exact admitted proposal is revalidated and restrictive policy cannot be weakened" do
    snapshot = Registry.current!()
    artifact = Map.fetch!(snapshot.modules, {"sarah.tool.conversation_read.v1", 1})
    proposal = reference(artifact, snapshot.digest)
    policy = RoutingPolicy.default()

    assert {:ok, selected} =
             Router.route(
               snapshot,
               policy,
               Map.merge(input(), %{proposal: proposal, exact_proposal: true})
             )

    assert selected.selected == proposal
    assert {:ok, ^artifact} = Router.revalidate(selected, snapshot, policy, input())

    assert {:ok, strict_policy} =
             RoutingPolicy.new(%{allowed_residencies: ["user_device_only"]})

    assert {:ok, refused} =
             Router.route(
               snapshot,
               strict_policy,
               Map.merge(input(), %{proposal: proposal, exact_proposal: true})
             )

    assert refused.status == "refused"
    assert refused.selected == nil
    assert Enum.any?(refused.rejected, &("residency_refused" in &1["reasons"]))
  end

  test "missing authority yields unavailable rather than an unauthorized fallback" do
    snapshot = Registry.current!()
    policy = RoutingPolicy.default()
    no_authority = %{input() | authorities: MapSet.new()}

    assert {:ok, decision} = Router.route(snapshot, policy, no_authority)
    assert decision.status == "refused"
    assert decision.selected == nil
    assert Enum.all?(decision.rejected, &("authority_refused" in &1["reasons"]))

    assert {:error, :module_route_unavailable} =
             Router.revalidate(decision, snapshot, policy, no_authority)
  end

  test "every restrictive policy axis is a hard filter before ranking" do
    snapshot = Registry.current!()
    artifact = Map.fetch!(snapshot.modules, {"sarah.tool.conversation_read.v1", 1})
    proposal = reference(artifact, snapshot.digest)
    exact_input = Map.merge(input(), %{proposal: proposal, exact_proposal: true})

    matrix = [
      {:allowed_publishers, ["DifferentPublisher"], "publisher_refused"},
      {:allowed_costs, ["paid"], "cost_refused"},
      {:allowed_qualities, ["unreviewed"], "quality_refused"},
      {:allowed_privacy, ["user_device_only"], "privacy_refused"},
      {:allowed_residencies, ["user_device_only"], "residency_refused"},
      {:allowed_jurisdictions, ["eu_only"], "jurisdiction_refused"},
      {:allowed_censorship_resistance, ["required"], "censorship_resistance_refused"},
      {:allowed_approval_classes, ["explicit_operator_approval"], "approval_class_refused"},
      {:allowed_side_effects, ["reversible_write"], "side_effect_policy_refused"}
    ]

    Enum.each(matrix, fn {field, value, reason} ->
      assert {:ok, policy} = RoutingPolicy.new(%{field => value})
      assert {:ok, decision} = Router.route(snapshot, policy, exact_input)
      assert decision.status == "refused"
      assert Enum.any?(decision.rejected, &(reason in &1["reasons"]))
    end)

    costly = %{artifact | facets: Map.put(artifact.facets, "cost_units", 1)}
    costly = %{costly | artifact_digest: OpenAgents.Modules.Artifact.artifact_digest(costly)}

    costly_snapshot = %{
      snapshot
      | modules: Map.put(snapshot.modules, {costly.module_id, 1}, costly)
    }

    costly_input =
      Map.merge(input(), %{proposal: reference(costly, snapshot.digest), exact_proposal: true})

    assert {:ok, decision} = Router.route(costly_snapshot, RoutingPolicy.default(), costly_input)
    assert decision.status == "refused"
    assert Enum.any?(decision.rejected, &("budget_refused" in &1["reasons"]))
  end

  test "stale catalog and policy proposals fail immediate revalidation" do
    snapshot = Registry.current!()
    policy = RoutingPolicy.default()
    assert {:ok, decision} = Router.route(snapshot, policy, input())

    stale_snapshot = %{snapshot | digest: String.duplicate("0", 64)}

    assert {:error, :stale_module_registry} =
             Router.revalidate(decision, stale_snapshot, policy, input())

    assert {:ok, changed_policy} =
             RoutingPolicy.new(%{id: "sarah.routing.policy.changed.v1"})

    assert {:error, :stale_routing_policy} =
             Router.revalidate(decision, snapshot, changed_policy, input())

    assert {:error, :stale_module_surface} =
             Router.revalidate(decision, snapshot, policy, %{input() | surface: "voice"})
  end

  test "surface contracts are finite and search cannot admit effectful executors" do
    assert SurfacePolicy.surfaces() ==
             ~w(agent computer mcp repository search text voice)

    assert SurfacePolicy.contracts()["search"].effects == ["read_only"]
    assert "agent_executor" in SurfacePolicy.contracts()["computer"].kinds
    assert "plugin" in SurfacePolicy.contracts()["repository"].kinds
  end

  test "a rejected optional program proposal degrades to the stable baseline with provenance" do
    snapshot = Registry.current!()
    policy = RoutingPolicy.default()

    stale_proposal = %{
      "module_id" => "sarah.tool.missing",
      "version" => 1,
      "artifact_digest" => String.duplicate("b", 64),
      "registry_digest" => snapshot.digest
    }

    route_input =
      input()
      |> Map.put(:proposal, stale_proposal)
      |> Map.put(:program_degraded, true)

    assert {:ok, decision} = Router.route(snapshot, policy, route_input)
    assert decision.status == "selected"
    assert decision.fallback
    assert decision.degraded
    assert decision.program_artifact == nil
    assert decision.reason == "deterministic_baseline_selected"
  end

  test "machine-effect modules route under paired-machine and operator policies" do
    snapshot = Registry.current!()
    artifact = Map.fetch!(snapshot.modules, {"sarah.tool.computer_run.v1", 1})
    proposal = reference(artifact, snapshot.digest)

    machine_input =
      Map.merge(input(), %{
        required_capability: "computer.control",
        required_side_effect: "external_effect",
        authorities: MapSet.new(["computer.control"]),
        proposal: proposal,
        exact_proposal: true
      })

    assert {:ok, refused} = Router.route(snapshot, RoutingPolicy.default(), machine_input)
    assert refused.status == "refused"
    assert refused.selected == nil

    assert Enum.any?(
             refused.rejected,
             &("approval_class_refused" in &1["reasons"] or
                 "side_effect_policy_refused" in &1["reasons"])
           )

    for policy <- [RoutingPolicy.paired_machine(), RoutingPolicy.operator()] do
      assert {:ok, selected} = Router.route(snapshot, policy, machine_input)
      assert selected.status == "selected"
      assert selected.selected == proposal
    end
  end

  test "SCV deployment routes only under the operator policy" do
    assert {:ok, snapshot} = Registry.build([ScvDeploy])
    artifact = Map.fetch!(snapshot.modules, {"sarah.tool.scv_deploy.v1", 1})
    proposal = reference(artifact, snapshot.digest)

    scv_input =
      Map.merge(input(), %{
        required_capability: "scv.deploy",
        required_side_effect: "external_effect",
        authorities: MapSet.new(["scv.deploy"]),
        proposal: proposal,
        exact_proposal: true
      })

    for policy <- [RoutingPolicy.default(), RoutingPolicy.paired_machine()] do
      assert {:ok, refused} = Router.route(snapshot, policy, scv_input)
      assert refused.status == "refused"
      assert refused.selected == nil
      assert Enum.any?(refused.rejected, &("residency_refused" in &1["reasons"]))
    end

    operator_policy = RoutingPolicy.operator()
    assert {:ok, selected} = Router.route(snapshot, operator_policy, scv_input)
    assert selected.status == "selected"
    assert selected.selected == proposal
    assert {:ok, ^artifact} = Router.revalidate(selected, snapshot, operator_policy, scv_input)
  end

  test "an unadmitted routing program identity is rejected before it can propose" do
    snapshot = Registry.current!()

    input =
      input()
      |> Map.put(:program_artifact, %{
        "artifact_id" => "unadmitted.routing.program",
        "artifact_digest" => String.duplicate("a", 64)
      })

    assert {:error, :module_route_program_invalid} =
             Router.route(snapshot, RoutingPolicy.default(), input)
  end

  defp input do
    %{
      intent_digest: Canonical.sha256("find the exact source"),
      required_capability: "conversation.read",
      required_side_effect: "read_only",
      surface: "text",
      data_scope: "browser_conversation",
      authorities: MapSet.new(["conversation.read"])
    }
  end

  defp reference(artifact, registry_digest) do
    %{
      "module_id" => artifact.module_id,
      "version" => artifact.version,
      "artifact_digest" => artifact.artifact_digest,
      "registry_digest" => registry_digest
    }
  end
end