lib/openagents/forge/wal_anchor.ex

58e6347eeb72 · 1 KB

defmodule OpenAgents.Forge.WALAnchor do
  @moduledoc """
  One published anchor document (`forge_wal_anchors`).

  The row holds the exact bytes that were served, not the fields they were
  built from, because the digest a reader computes is a digest of the bytes
  they fetched. Re-rendering a document from columns would let key order drift
  between releases and turn every archived copy into apparent tampering, which
  is the same reason `OpenAgents.Forge.WAL`'s chain encoding is not JSON.

  `previous_digest` names the digest of the anchor published before this one,
  so the published sequence is itself a hash chain: one archived anchor pins
  every anchor before it, the way an entry link pins every entry before it.

  This table is a publication record, never authority. Nothing reads it to
  decide what the forge serves, and `OpenAgents.Forge.Verification` reaches no
  database at all — a verifier consulting PostgreSQL would be asking the
  operator to confirm the operator.
  """

  use Ecto.Schema
  import Ecto.Changeset

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

  @type t :: %__MODULE__{}

  schema "forge_wal_anchors" do
    field :anchor_seq, :integer
    field :digest, :string
    field :previous_digest, :string
    field :body, :string
    field :published_at, :utc_datetime_usec
    timestamps(updated_at: false)
  end

  def changeset(anchor, attrs) do
    anchor
    |> cast(attrs, [:anchor_seq, :digest, :previous_digest, :body, :published_at])
    |> validate_required([:anchor_seq, :digest, :body, :published_at])
    |> validate_number(:anchor_seq, greater_than_or_equal_to: 0)
    |> unique_constraint(:anchor_seq)
  end
end