from __future__ import annotations from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, func, ) from sqlalchemy.orm import Mapped, mapped_column from supply_infra.db.base import Base class PipelineStepRun(Base): """One durable attempt of a pipeline step.""" __tablename__ = "pipeline_step_run" __table_args__ = ( UniqueConstraint( "run_id", "step_key", "attempt", name="uk_pipeline_step_run_attempt", ), Index("idx_pipeline_step_claim", "status", "next_retry_at", "step_order"), Index("idx_pipeline_step_run", "run_id", "step_order", "attempt"), Index("idx_pipeline_step_lease", "status", "lease_until"), ) step_run_id: Mapped[str] = mapped_column(String(36), primary_key=True) run_id: Mapped[str] = mapped_column( String(36), ForeignKey("pipeline_run.run_id", ondelete="CASCADE"), nullable=False, ) step_key: Mapped[str] = mapped_column(String(64), nullable=False) step_order: Mapped[int] = mapped_column(Integer, nullable=False) attempt: Mapped[int] = mapped_column(Integer, nullable=False, default=1) status: Mapped[str] = mapped_column(String(32), nullable=False, default="pending") critical: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) dependency_snapshot_json: Mapped[list[str]] = mapped_column( JSON, nullable=False, default=list, ) input_snapshot_json: Mapped[dict[str, Any]] = mapped_column( JSON, nullable=False, default=dict, ) timeout_seconds: Mapped[int] = mapped_column(Integer, nullable=False) max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=1) retryable: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) next_retry_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) lease_owner: Mapped[str | None] = mapped_column(String(191), nullable=True) lease_until: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) exit_code: Mapped[int | None] = mapped_column(Integer, nullable=True) result_summary_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) error_code: Mapped[str | None] = mapped_column(String(64), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) log_uri: Mapped[str | None] = mapped_column(String(1024), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), ) updated_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), onupdate=func.now(), )