priv/repo/migrations/20260507000001_extend_relyra_metadata_sources_with_auto_refresh.exs

defmodule Relyra.Repo.Migrations.ExtendRelyraMetadataSourcesWithAutoRefresh do
  use Ecto.Migration

  def change do
    alter table(:relyra_metadata_sources) do
      add :auto_refresh_enabled, :boolean, default: false, null: false
      add :refresh_cadence, :string, default: "daily", null: false
      add :next_refresh_at, :utc_datetime_usec
      add :require_signed_metadata, :boolean, default: true, null: false
      add :metadata_trust_fingerprints, {:array, :string}, default: [], null: false
      add :legacy_unsigned_metadata_policy, :map
      add :last_known_metadata_signing_certs, {:array, :string}, default: [], null: false
      add :consecutive_failure_count, :integer, default: 0, null: false
      add :first_failure_at, :utc_datetime_usec
      add :last_success_at, :utc_datetime_usec
      add :last_failure_error_code, :string
      add :last_validity_warning_for, :utc_datetime_usec
      add :auto_suspended_until, :utc_datetime_usec
      add :auto_suspended_reason, :string
    end

    # Partial index per D-12. The predicate is restricted to `auto_refresh_enabled = true`
    # rather than the broader `auto_refresh_enabled = true AND (auto_suspended_until IS NULL
    # OR auto_suspended_until <= now())` from PLAN/RESEARCH because Postgres requires
    # partial-index predicates to use IMMUTABLE functions only — `now()` is STABLE, not
    # IMMUTABLE, so it cannot appear in the WHERE clause of CREATE INDEX. The runtime due-row
    # query (RESEARCH Pattern 5) still applies the suspension/time filter at query time,
    # which is the canonical Postgres idiom for time-based partial indexes; the index keeps
    # scheduler-tick work O(enabled-source-count), preserving D-12's intent.
    create index(:relyra_metadata_sources, [:next_refresh_at],
             name: :relyra_metadata_sources_due_idx,
             where: "auto_refresh_enabled = true"
           )
  end
end