test/support/fixtures/compensation_fixtures.ex

58e6347eeb72 · 5 KB

defmodule OpenAgents.CompensationFixtures do
  @moduledoc """
  Test helpers for accepted-outcome decisions.

  An outcome decision is the accepted-outcome receipt other contexts bind to,
  and it exists only at the end of the real path: a routed tool step, an
  immutable outcome, and an independent review. The helper walks that path so
  callers get a decision no shortcut could have produced.
  """

  import ExUnit.Assertions

  alias OpenAgents.{Compensation, Context.Composer, Conversations}
  alias OpenAgents.Providers.Request

  @doc "An independently reviewed outcome decision, accepted or rejected."
  def outcome_decision_fixture(decision \\ "accepted", reason_code \\ "verified_outcome") do
    suffix = System.unique_integer([:positive, :monotonic])
    step = completed_step("reputation-#{suffix}")

    assert {:ok, record} =
             Compensation.decide_outcome(
               step.id,
               reviewer("reputation-#{suffix}"),
               decision,
               reason_code
             )

    record
  end

  defp completed_step(suffix) do
    %{turn: turn, receipt: receipt} = begin_turn(suffix)
    artifact = artifact()
    route = route!(receipt, "call-#{suffix}", artifact)
    policy = artifact.attribution_policy

    assert {:ok, step, :created} =
             Conversations.request_tool_step(turn, receipt, %{
               provider_call_id: "call-#{suffix}",
               provider_item_id: "item-#{suffix}",
               provider_response_id: "response-#{suffix}",
               tool_name: "recall_messages",
               tool_version: artifact.version,
               module_id: artifact.module_id,
               module_artifact_digest: artifact.artifact_digest,
               executor_implementation_digest: artifact.implementation_digest,
               routing_receipt_id: route.id,
               side_effect_class: artifact.side_effect_class,
               attribution_policy_id: policy["id"],
               attribution_policy_version: policy["version"],
               attribution_policy_digest: policy["digest"],
               cost_units: 100,
               raw_arguments: "{}"
             })

    outcome = %{
      "schema" => "sarah.tool_outcome.v1",
      "call_id" => step.provider_call_id,
      "module_ref" => %{
        "module_id" => step.module_id,
        "tool_name" => step.tool_name,
        "version" => step.tool_version,
        "artifact_digest" => step.module_artifact_digest
      },
      "executor_ref" => %{
        "id" => "sarah.local",
        "disclosure" => "Sarah local recall",
        "implementation_digest" => step.executor_implementation_digest
      },
      "status" => "succeeded",
      "result" => %{"private" => "private customer result"},
      "error" => nil,
      "target_receipt_refs" => ["message:opaque"],
      "attribution_refs" => ["OpenAgentsInc/openagents.com"],
      "started_at" => "2026-08-16T20:00:00Z",
      "completed_at" => "2026-08-16T20:00:01Z"
    }

    assert {:ok, completed} = Conversations.complete_tool_step(step, outcome)
    completed
  end

  defp begin_turn(browser_key) do
    assert {:ok, conversation} = Conversations.ensure_conversation(browser_key)
    assert {:ok, records} = Conversations.create_turn(conversation, "Use a module.")
    context = Composer.compose!()

    request = %Request{
      model_id: "reputation-test-model",
      instructions: context.instructions,
      input: Conversations.provider_messages(conversation.id)
    }

    assert {:ok, inference} =
             Conversations.begin_inference(records.turn, context, request, "test.provider",
               tool_catalog_digest: OpenAgents.Tools.Registry.current!().digest
             )

    inference
  end

  defp artifact,
    do:
      Map.fetch!(
        OpenAgents.Tools.Registry.current!().modules,
        {"sarah.tool.recall_messages", 1}
      )

  defp route!(receipt, call_id, artifact) do
    snapshot = OpenAgents.Tools.Registry.current!()

    proposal = %{
      "module_id" => artifact.module_id,
      "version" => artifact.version,
      "artifact_digest" => artifact.artifact_digest,
      "registry_digest" => snapshot.digest
    }

    assert {:ok, decision} =
             OpenAgents.Modules.Router.route(
               snapshot,
               OpenAgents.Modules.RoutingPolicy.default(),
               %{
                 intent_digest: receipt.input_digest,
                 required_capability: "conversation.read",
                 required_side_effect: "read_only",
                 surface: "text",
                 data_scope: "browser_conversation",
                 authorities: MapSet.new(["conversation.read"]),
                 proposal: proposal,
                 exact_proposal: true
               }
             )

    assert {:ok, route} =
             OpenAgents.Modules.RoutingReceipts.persist(receipt.id, call_id, decision)

    route
  end

  defp reviewer(suffix),
    do: %{
      authenticated: true,
      role: "outcome_reviewer",
      actor_id: "outcome-reviewer:test",
      auth_method: "test_session",
      decision_receipt_ref: "outcome-decision:#{suffix}"
    }
end