lib/openagents/continual_learning/artifact.ex

58e6347eeb72 · 2 KB

defmodule OpenAgents.ContinualLearning.Artifact do
  @moduledoc """
  The terminal model artifact of one continual-learning job.

  The artifact digest is taken over the exact base model, the exact licensed
  dataset bindings, the exact training code, the exact configuration, the
  ordered checkpoint chain, and the exact evaluation inputs and results, so a
  reader can reproduce the identity of what was produced without trusting the
  producer.
  """

  use Ecto.Schema
  import Ecto.Changeset

  @digest_regex ~r/\A[0-9a-f]{64}\z/

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  @timestamps_opts [type: :utc_datetime_usec]

  schema "continual_learning_artifacts" do
    belongs_to :job, OpenAgents.ContinualLearning.Job
    field :model_ref, :string
    field :model_digest, :string
    field :base_model_digest, :string
    field :training_code_digest, :string
    field :configuration_digest, :string
    field :dataset_bindings, {:array, :map}, default: []
    field :checkpoint_digests, {:array, :string}, default: []
    field :evaluation_result, :map, default: %{}
    field :accepted_outcome, :map, default: %{}
    field :settlement, :map, default: %{}
    field :artifact_digest, :string

    timestamps()
  end

  @type t :: %__MODULE__{}

  def changeset(artifact, attributes) do
    artifact
    |> cast(attributes, [
      :model_ref,
      :model_digest,
      :base_model_digest,
      :training_code_digest,
      :configuration_digest,
      :dataset_bindings,
      :checkpoint_digests,
      :evaluation_result,
      :accepted_outcome,
      :settlement,
      :artifact_digest
    ])
    |> validate_required([
      :model_ref,
      :model_digest,
      :base_model_digest,
      :training_code_digest,
      :configuration_digest,
      :evaluation_result,
      :accepted_outcome,
      :artifact_digest
    ])
    |> validate_format(:model_digest, @digest_regex)
    |> validate_format(:base_model_digest, @digest_regex)
    |> validate_format(:training_code_digest, @digest_regex)
    |> validate_format(:configuration_digest, @digest_regex)
    |> validate_format(:artifact_digest, @digest_regex)
    |> validate_checkpoint_chain()
    |> unique_constraint(:job_id)
    |> unique_constraint(:artifact_digest)
    |> foreign_key_constraint(:job_id)
  end

  defp validate_checkpoint_chain(changeset) do
    digests = get_field(changeset, :checkpoint_digests) || []

    if digests != [] and Enum.all?(digests, &Regex.match?(@digest_regex, &1)) do
      changeset
    else
      add_error(changeset, :checkpoint_digests, "must name the ordered checkpoint chain")
    end
  end
end