lib/mobus/stepwise/runtime.ex

defmodule Mobus.Stepwise.Runtime do
  @moduledoc """
  Stable accessor helpers for stepwise runtime maps.

  Provides deterministic inspection and mutation of runtime state
  without depending on internal map structure. Orchestrators should
  prefer these helpers over direct map access.

  ## Meta

  The `:meta` field is an open map for orchestration metadata
  (session IDs, DAG coordinates, tags, etc.). It survives
  checkpoint/restore cycles and is passed read-only to capabilities.

      runtime = Runtime.put_meta(runtime, :session_id, "sess-123")
      Runtime.get_meta(runtime, :session_id)
      #=> "sess-123"
  """

  @doc "Returns the full `:meta` map from the runtime."
  @spec meta(map()) :: map()
  def meta(runtime), do: Map.get(runtime, :meta, %{})

  @doc "Gets a single key from `:meta`, with optional default."
  @spec get_meta(map(), term(), term()) :: term()
  def get_meta(runtime, key, default \\ nil) do
    runtime |> meta() |> Map.get(key, default)
  end

  @doc "Puts a single key into `:meta`."
  @spec put_meta(map(), term(), term()) :: map()
  def put_meta(runtime, key, value) do
    Map.update(runtime, :meta, %{key => value}, &Map.put(&1, key, value))
  end

  @doc "Shallow-merges a map into `:meta`."
  @spec merge_meta(map(), map()) :: map()
  def merge_meta(runtime, %{} = map) do
    Map.update(runtime, :meta, map, &Map.merge(&1, map))
  end

  @doc "Returns the `:context` map from the runtime."
  @spec context(map()) :: map()
  def context(runtime), do: Map.get(runtime, :context, %{})

  @doc "Puts a single key into `:context`."
  @spec overlay_context(map(), term(), term()) :: map()
  def overlay_context(runtime, key, value) do
    Map.update(runtime, :context, %{key => value}, &Map.put(&1, key, value))
  end

  @doc "Shallow-merges a map into `:context`."
  @spec merge_context(map(), map()) :: map()
  def merge_context(runtime, %{} = map) do
    Map.update(runtime, :context, map, &Map.merge(&1, map))
  end

  @doc "Returns the `:artifacts` map from the runtime."
  @spec artifacts(map()) :: map()
  def artifacts(runtime), do: Map.get(runtime, :artifacts, %{})

  @doc "Puts a single key into `:artifacts` (normalized via `Artifacts.merge/2`)."
  @spec overlay_artifacts(map(), term(), term()) :: map()
  def overlay_artifacts(runtime, key, value) do
    alias Mobus.Stepwise.Artifacts

    Map.update(runtime, :artifacts, %{}, fn arts ->
      Artifacts.merge(arts, %{key => value})
    end)
  end

  @doc "Returns the `:execution_id`."
  @spec execution_id(map()) :: String.t() | nil
  def execution_id(runtime), do: Map.get(runtime, :execution_id)

  @doc "Returns the `:tenant_id`."
  @spec tenant_id(map()) :: String.t() | nil
  def tenant_id(runtime), do: Map.get(runtime, :tenant_id)

  @doc "Returns the `:current_state`."
  @spec current_state(map()) :: atom() | String.t() | nil
  def current_state(runtime), do: Map.get(runtime, :current_state)
end