priv/repo/migrations/20260816230000_create_shadow_program_runs.exs

58e6347eeb72 · 2 KB

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

  def up do
    create table(:shadow_program_runs, 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 :signature_id, :string, null: false
      add :signature_version, :integer, null: false
      add :artifact_id, :string
      add :artifact_digest, :string
      add :input_digest, :string, null: false
      add :baseline_output, :map, null: false
      add :candidate_output, :map, null: false
      add :candidate_output_digest, :string, null: false
      add :status, :string, null: false
      add :comparison, :map, null: false
      add :provider_id, :string, null: false
      add :provider_response_id, :string
      add :usage, :map, null: false, default: %{}
      add :latency_ms, :bigint, null: false
      add :failure_code, :string
      add :completed_at, :utc_datetime_usec, null: false
      timestamps(type: :utc_datetime_usec, updated_at: false)
    end

    create index(:shadow_program_runs, [:turn_receipt_id, :signature_id, :inserted_at])

    create constraint(:shadow_program_runs, :shadow_program_runs_status_check,
             check: "status IN ('completed', 'degraded', 'malformed', 'failed', 'timed_out')"
           )

    create constraint(:shadow_program_runs, :shadow_program_runs_artifact_pair_check,
             check:
               "(artifact_id IS NULL) = (artifact_digest IS NULL) AND " <>
                 "(artifact_digest IS NULL OR artifact_digest ~ '^[0-9a-f]{64}$')"
           )

    create constraint(:shadow_program_runs, :shadow_program_runs_digest_check,
             check:
               "input_digest ~ '^[0-9a-f]{64}$' AND candidate_output_digest ~ '^[0-9a-f]{64}$'"
           )

    create constraint(:shadow_program_runs, :shadow_program_runs_bounds_check,
             check:
               "signature_version > 0 AND latency_ms >= 0 AND " <>
                 "octet_length(baseline_output::text) <= 16384 AND " <>
                 "octet_length(candidate_output::text) <= 16384 AND " <>
                 "octet_length(comparison::text) <= 4096 AND " <>
                 "octet_length(usage::text) <= 4096"
           )

    execute("""
    CREATE FUNCTION reject_shadow_program_run_mutation()
    RETURNS trigger AS $$
    BEGIN
      RAISE EXCEPTION 'terminal shadow-program receipts are immutable';
    END;
    $$ LANGUAGE plpgsql;
    """)

    execute("""
    CREATE TRIGGER protect_shadow_program_runs
    BEFORE UPDATE OR DELETE ON shadow_program_runs
    FOR EACH ROW EXECUTE FUNCTION reject_shadow_program_run_mutation();
    """)
  end

  def down do
    drop table(:shadow_program_runs)
    execute("DROP FUNCTION reject_shadow_program_run_mutation()")
  end
end