priv/repo/migrations/20260816235500_create_module_route_receipts.exs

58e6347eeb72 · 2 KB

defmodule Sarah.Repo.Migrations.CreateModuleRouteReceipts do
  use Ecto.Migration

  def up do
    create table(:module_route_receipts, primary_key: false) do
      add :id, :binary_id, primary_key: true

      add :turn_receipt_id,
          references(:turn_receipts, type: :binary_id, on_delete: :delete_all),
          null: false

      add :provider_call_id, :string, null: false
      add :status, :string, null: false
      add :reason, :string, null: false
      add :intent_digest, :string, null: false
      add :registry_digest, :string, null: false
      add :policy_id, :string, null: false
      add :policy_digest, :string, null: false
      add :required_capability, :string, null: false
      add :required_side_effect, :string, null: false
      add :selected, :map
      add :proposed, :map
      add :rejected, {:array, :map}, null: false, default: []
      add :program_artifact, :map
      add :fallback, :boolean, null: false, default: false
      add :degraded, :boolean, null: false, default: false
      timestamps(type: :utc_datetime_usec, updated_at: false)
    end

    create unique_index(:module_route_receipts, [:turn_receipt_id, :provider_call_id])

    create constraint(:module_route_receipts, :module_route_receipt_status_check,
             check:
               "status IN ('selected','unavailable','refused') AND required_side_effect IN ('read_only','reversible_write','external_effect')"
           )

    create constraint(:module_route_receipts, :module_route_receipt_digest_check,
             check:
               "intent_digest ~ '^[0-9a-f]{64}$' AND registry_digest ~ '^[0-9a-f]{64}$' AND policy_digest ~ '^[0-9a-f]{64}$'"
           )

    execute("""
    CREATE FUNCTION reject_module_route_receipt_mutation()
    RETURNS trigger AS $$
    BEGIN
      RAISE EXCEPTION 'module route receipts are append-only';
    END;
    $$ LANGUAGE plpgsql;
    """)

    execute("""
    CREATE TRIGGER module_route_receipts_append_only
    BEFORE UPDATE OR DELETE ON module_route_receipts
    FOR EACH ROW EXECUTE FUNCTION reject_module_route_receipt_mutation();
    """)
  end

  def down do
    execute("DROP TRIGGER IF EXISTS module_route_receipts_append_only ON module_route_receipts")
    execute("DROP FUNCTION IF EXISTS reject_module_route_receipt_mutation()")
    drop table(:module_route_receipts)
  end
end