test/openagents/delegations/target_test.exs

main at 58e6347eeb72 · 7 KB

defmodule OpenAgents.Delegations.TargetTest do
  @moduledoc """
  The seam is only worth having if it is total.

  These tests derive their inputs from the substrate schemas rather than
  listing them, so a new Box state, run state, or work-job status fails here
  the day it lands instead of silently reading as `failed` on five surfaces.
  """

  use ExUnit.Case, async: true

  alias OpenAgents.Box.ConversationBox
  alias OpenAgents.Box.Run
  alias OpenAgents.Delegations.Target
  alias OpenAgents.Machines.Machine
  alias OpenAgents.Work.Job

  describe "the lifecycle vocabulary" do
    test "target lifecycles are the openagents.cloud_computer.v1 state enum" do
      assert Target.lifecycles() == ~w(cold queued starting active stopping failed destroyed)
    end

    test "every declared Box state maps into the vocabulary" do
      for state <- ConversationBox.states() do
        assert Target.box_lifecycle(state) in Target.lifecycles(),
               "Box state #{state} has no lifecycle"
      end
    end

    test "a fan-out item that admission has not admitted reads as queued" do
      assert Target.box_lifecycle("queued") == "queued"
    end

    test "no declared Box state silently reads as failed" do
      # `error` is the one Box state that means failure. If another state
      # starts reading as `failed`, it fell through the catch-all.
      failed = Enum.filter(ConversationBox.states(), &(Target.box_lifecycle(&1) == "failed"))
      assert failed == ["error"]
    end

    test "a Box is only startable once it is ready, idle, or already running" do
      active = Enum.filter(ConversationBox.states(), &(Target.box_lifecycle(&1) == "active"))
      assert Enum.sort(active) == ~w(idle ready running)
    end

    test "every declared Box run state maps into the delegation vocabulary" do
      for state <- Run.states() do
        assert Target.run_lifecycle(state) in Target.delegation_lifecycles(),
               "Box run state #{state} has no lifecycle"
      end
    end

    test "every declared work job status maps into the delegation vocabulary" do
      for status <- Job.statuses() do
        assert Target.job_lifecycle(status) in Target.delegation_lifecycles(),
               "work job status #{status} has no lifecycle"
      end
    end

    test "a lost or timed-out run and an interrupted or exhausted job all read as failed" do
      assert Target.run_lifecycle("lost") == "failed"
      assert Target.run_lifecycle("timed_out") == "failed"
      assert Target.job_lifecycle("interrupted") == "failed"
      assert Target.job_lifecycle("budget_exhausted") == "failed"
    end

    test "a terminal run and a terminal job agree on the word" do
      for state <- Run.terminal_states(), state != "lost" do
        refute Target.run_lifecycle(state) in ~w(queued starting active),
               "terminal run state #{state} still reads as live"
      end
    end
  end

  describe "a Computer's lifecycle" do
    test "an active, reachable computer is active" do
      assert Target.computer_lifecycle(machine(), true) == "active"
    end

    test "an active but unreachable computer is cold, never failed" do
      assert Target.computer_lifecycle(machine(), false) == "cold"
    end

    test "a revoked computer is destroyed even while a socket is still up" do
      revoked = %Machine{machine() | status: "revoked", revoked_at: DateTime.utc_now()}

      assert Target.computer_lifecycle(revoked, true) == "destroyed"
    end

    test "a revocation stamp alone is enough to read as destroyed" do
      stamped = %Machine{machine() | revoked_at: DateTime.utc_now()}

      assert Target.computer_lifecycle(stamped, true) == "destroyed"
    end
  end

  describe "custody" do
    test "is derived from the target kind and nothing else" do
      assert Target.custody("box") == "openagents_managed"
      assert Target.custody("computer") == "customer_premises"
    end

    test "covers every kind, and every value is admitted" do
      for kind <- Target.kinds() do
        assert Target.custody(kind) in Target.custodies()
      end
    end

    test "does not change with the lifecycle or the caller's authority" do
      for lifecycle <- Target.lifecycles(), authorized? <- [true, false] do
        assert Target.seam("box", lifecycle, authorized?)["custody"] == "openagents_managed"
        assert Target.seam("computer", lifecycle, authorized?)["custody"] == "customer_premises"
      end
    end
  end

  describe "runtime class" do
    test "a paired computer is the connected class the capacity catalog admits" do
      assert Target.runtime_class("computer") == "connected"
      assert OpenAgents.Capacity.Catalog.get("connected")["isolation"] == "customer_controlled"
    end

    test "a Box claims no class until its egress posture is established" do
      assert Target.runtime_class("box") == nil
    end
  end

  describe "capabilities" do
    test "an active target the caller can reach may be started" do
      assert Target.seam("box", "active", true)["capabilities"] == ["start"]
      assert Target.seam("computer", "active", true)["capabilities"] == ["start"]
    end

    test "no other lifecycle offers any capability" do
      for kind <- Target.kinds(), lifecycle <- Target.lifecycles(), lifecycle != "active" do
        assert Target.seam(kind, lifecycle, true)["capabilities"] == [],
               "#{kind} in #{lifecycle} advertised a capability"
      end
    end

    test "an unauthorized caller is offered nothing, whatever the lifecycle" do
      for kind <- Target.kinds(), lifecycle <- Target.lifecycles() do
        seam = Target.seam(kind, lifecycle, false)

        assert seam["capabilities"] == [], "#{kind} in #{lifecycle} leaked a capability"
      end
    end

    test "authority is refused by name, so it is not confused with a cold target" do
      assert Target.seam("computer", "active", false)["unavailable_reason"] == "not_authorized"
      assert Target.seam("computer", "cold", true)["unavailable_reason"] == "cold"
    end

    test "a reachable, authorized target gives no reason, because there is none" do
      assert Target.seam("box", "active", true)["unavailable_reason"] == nil
    end

    test "the reason for refusing is always the lifecycle itself" do
      for kind <- Target.kinds(), lifecycle <- Target.lifecycles(), lifecycle != "active" do
        assert Target.seam(kind, lifecycle, true)["unavailable_reason"] == lifecycle
      end
    end
  end

  describe "delegation capabilities" do
    test "a live delegation can be cancelled" do
      for lifecycle <- ~w(queued starting active) do
        assert Target.delegation_seam(lifecycle)["capabilities"] == ["cancel"]
      end
    end

    test "a finished delegation cannot" do
      for lifecycle <- ~w(succeeded failed cancelled) do
        assert Target.delegation_seam(lifecycle)["capabilities"] == []
      end
    end

    test "every delegation lifecycle is covered" do
      for lifecycle <- Target.delegation_lifecycles() do
        assert is_list(Target.delegation_seam(lifecycle)["capabilities"])
      end
    end

    test "every terminal work job status yields an uncancellable delegation" do
      for status <- ~w(completed failed cancelled interrupted budget_exhausted) do
        seam = status |> Target.job_lifecycle() |> Target.delegation_seam()

        assert seam["capabilities"] == [], "#{status} still offered cancel"
      end
    end
  end

  describe "the seam document" do
    test "carries the same fields for every kind and lifecycle" do
      expected = ~w(capabilities custody lifecycle runtime_class unavailable_reason)

      for kind <- Target.kinds(), lifecycle <- Target.lifecycles() do
        seam = Target.seam(kind, lifecycle, true)

        assert seam |> Map.keys() |> Enum.sort() == expected
        assert seam["lifecycle"] == lifecycle
      end
    end
  end

  defp machine do
    %Machine{
      id: Ecto.UUID.generate(),
      name: "workshop",
      status: "active",
      tier: "probe",
      revoked_at: nil
    }
  end
end