lib/openagents/deployments/execution.ex

58e6347eeb72 · 2 KB

defmodule OpenAgents.Deployments.Execution do
  @moduledoc """
  The immutable, fully admitted work a provider is handed.

  An execution is built only after policy admitted the run, and it carries no
  caller authority: no token, no membership, no scope. A provider that is
  compromised can therefore misreport its own deployment, but cannot deploy to
  another environment or read another repository.

  Resolved secret values live here and only here, for the duration of one
  attempt. The struct redacts them from `inspect/1` so a crash report, a logger
  metadata dump, or an exception message cannot carry a tenant credential.
  """

  @derive {Inspect, except: [:secrets]}

  @enforce_keys [
    :run_id,
    :repository,
    :environment,
    :commit_sha,
    :artifact_digest,
    :input_digest,
    :attempt,
    :deadline
  ]
  defstruct [
    :run_id,
    :repository,
    :environment,
    :commit_sha,
    :artifact_digest,
    :input_digest,
    :attempt,
    :deadline,
    provider_config: %{},
    secrets: %{}
  ]

  @type t :: %__MODULE__{
          run_id: String.t(),
          repository: String.t(),
          environment: String.t(),
          commit_sha: String.t(),
          artifact_digest: String.t(),
          input_digest: String.t(),
          attempt: pos_integer(),
          deadline: DateTime.t(),
          provider_config: map(),
          secrets: %{optional(String.t()) => String.t()}
        }

  @doc "Whether the execution's deadline has passed at `now`."
  @spec expired?(t(), DateTime.t()) :: boolean()
  def expired?(%__MODULE__{} = execution, %DateTime{} = now),
    do: DateTime.compare(now, execution.deadline) != :lt

  defimpl Jason.Encoder do
    # Executions are never serialized with their secrets. Encoding exists for
    # bounded diagnostics, so it emits identities only.
    def encode(execution, opts) do
      Jason.Encode.map(
        %{
          "run_id" => execution.run_id,
          "repository" => execution.repository,
          "environment" => execution.environment,
          "commit_sha" => execution.commit_sha,
          "artifact_digest" => execution.artifact_digest,
          "input_digest" => execution.input_digest,
          "attempt" => execution.attempt
        },
        opts
      )
    end
  end
end