priv/repo/migrations/20260823143402_create_box_fanout_requests_and_labels.exs

58e6347eeb72 · 3 KB

defmodule OpenAgents.Repo.Migrations.CreateBoxFanoutRequestsAndLabels do
  use Ecto.Migration

  def up do
    alter table(:conversation_boxes) do
      add :label, :string
    end

    execute("""
    UPDATE conversation_boxes
    SET label = 'box-' || substr(id::text, 1, 64)
    WHERE label IS NULL
    """)

    alter table(:conversation_boxes) do
      modify :label, :string, null: false
    end

    create unique_index(:conversation_boxes, [:conversation_id, :label],
             name: :conversation_boxes_active_label_index,
             where: "stopped_at IS NULL"
           )

    create table(:box_fanout_requests, primary_key: false) do
      add :id, :binary_id, primary_key: true

      add :conversation_id,
          references(:conversations, type: :binary_id, on_delete: :delete_all),
          null: false

      add :requesting_principal, :map, null: false
      add :requested_count, :integer, null: false
      add :budgeted, :boolean, null: false, default: false
      add :effective_limits, :map, null: false
      add :state, :string, null: false, default: "admitted"
      add :admitted_count, :integer, null: false, default: 0
      add :queued_count, :integer, null: false, default: 0
      add :inserted_at, :utc_datetime_usec, null: false
      add :updated_at, :utc_datetime_usec, null: false
    end

    create index(:box_fanout_requests, [:conversation_id, :inserted_at])

    create table(:box_fanout_items, primary_key: false) do
      add :id, :binary_id, primary_key: true

      add :request_id,
          references(:box_fanout_requests, type: :binary_id, on_delete: :delete_all),
          null: false

      add :conversation_id,
          references(:conversations, type: :binary_id, on_delete: :delete_all),
          null: false

      add :position, :integer, null: false
      add :queue_sequence, :bigserial, null: false
      add :label, :string, null: false
      add :state, :string, null: false, default: "queued"
      add :queue_reason, :string

      add :conversation_box_id,
          references(:conversation_boxes, type: :binary_id, on_delete: :nilify_all)

      add :estimated_burn_rate_microusd, :integer, null: false
      add :requesting_principal, :map, null: false
      add :admitted_at, :utc_datetime_usec
      add :queued_at, :utc_datetime_usec, null: false
      add :inserted_at, :utc_datetime_usec, null: false
      add :updated_at, :utc_datetime_usec, null: false
    end

    create unique_index(:box_fanout_items, [:request_id, :position])

    create unique_index(:box_fanout_items, [:conversation_id, :label],
             name: :box_fanout_items_active_label_index,
             where: "state IN ('admitted', 'queued')"
           )

    create index(:box_fanout_items, [:conversation_id, :state, :queue_sequence],
             name: :box_fanout_items_queue_index
           )

    create constraint(:box_fanout_requests, :box_fanout_requests_count_check,
             check: "requested_count > 0 AND admitted_count >= 0 AND queued_count >= 0"
           )

    create constraint(:box_fanout_items, :box_fanout_items_state_check,
             check: "state IN ('admitted', 'queued', 'refused')"
           )

    create constraint(:box_fanout_items, :box_fanout_items_cost_check,
             check: "estimated_burn_rate_microusd >= 0"
           )
  end

  def down do
    drop_if_exists table(:box_fanout_items)
    drop_if_exists table(:box_fanout_requests)

    drop_if_exists index(:conversation_boxes, [:conversation_id, :label],
                     name: :conversation_boxes_active_label_index
                   )

    alter table(:conversation_boxes) do
      remove :label
    end
  end
end