lib/openagents/incidents/incident.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Incidents.Incident do
  @moduledoc """
  One durable, owner-scoped record of an anomalous failure.

  An incident is the queryable evidence a failure happened, why (a real
  machine-readable `code`), and what has been done about it. It is written on
  the same transaction that finalizes the failure so it can never be lost, and
  it is visible to its owner exactly like their transcript and receipts.
  """

  use Ecto.Schema
  import Ecto.Changeset

  @surfaces ~w(text voice job delegation controller)
  @severities ~w(expected degraded anomalous)
  @statuses ~w(open triaged fixing resolved wont_fix)

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

  schema "incidents" do
    belongs_to :conversation, OpenAgents.Conversations.Conversation
    belongs_to :owner_user, OpenAgents.Accounts.User
    belongs_to :owner_visitor, OpenAgents.Conversations.Visitor
    belongs_to :fixer_job, OpenAgents.Work.Job

    field :surface, :string
    field :origin, :string
    field :correlation_ref, :string
    field :code, :string
    field :severity, :string, default: "anomalous"
    field :summary, :string
    field :context, :map, default: %{}
    field :status, :string, default: "open"
    field :receipt_ref, :string
    timestamps()
  end

  @castable [
    :conversation_id,
    :owner_user_id,
    :owner_visitor_id,
    :fixer_job_id,
    :surface,
    :origin,
    :correlation_ref,
    :code,
    :severity,
    :summary,
    :context,
    :status,
    :receipt_ref
  ]

  def changeset(incident, attributes) do
    incident
    |> cast(attributes, @castable)
    |> validate_required([:surface, :origin, :code, :severity, :status])
    |> validate_inclusion(:surface, @surfaces)
    |> validate_inclusion(:severity, @severities)
    |> validate_inclusion(:status, @statuses)
    |> validate_length(:code, max: 128)
    |> validate_length(:origin, max: 64)
    |> validate_length(:correlation_ref, max: 128)
    |> validate_length(:summary, max: 500)
    |> validate_length(:receipt_ref, max: 256)
  end

  def surfaces, do: @surfaces
  def severities, do: @severities
  def statuses, do: @statuses
end