state-hub/api/models/review_contract.py
tegwick 598f6418e7
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 25s
feat(review): add multi-owner contracts and receipts
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
2026-08-22 20:57:51 +02:00

128 lines
4.5 KiB
Python

import uuid
from datetime import datetime
from sqlalchemy import (
Boolean,
DateTime,
ForeignKey,
Index,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from api.models.base import Base, TimestampMixin, new_uuid7
class ReviewContract(Base, TimestampMixin):
"""Replaceable projection of one authoritative review-contract revision."""
__tablename__ = "review_contracts"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=new_uuid7
)
contract_key: Mapped[str] = mapped_column(String(160), nullable=False, index=True)
schema_version: Mapped[str] = mapped_column(String(40), nullable=False)
contract_digest: Mapped[str] = mapped_column(
String(64), nullable=False, unique=True
)
source_repo: Mapped[str] = mapped_column(String(100), nullable=False)
source_path: Mapped[str] = mapped_column(Text, nullable=False)
source_revision: Mapped[str] = mapped_column(String(64), nullable=False)
document: Mapped[dict] = mapped_column(JSONB, nullable=False)
active: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=True, index=True
)
required_for_decision: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False
)
decision_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("decisions.id", ondelete="RESTRICT"),
nullable=True,
index=True,
)
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("workplans.id", ondelete="RESTRICT", onupdate="CASCADE"),
nullable=True,
index=True,
)
task_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("tasks.id", ondelete="RESTRICT", onupdate="CASCADE"),
nullable=True,
index=True,
)
projected_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
receipts: Mapped[list["ReviewReceipt"]] = relationship(
"ReviewReceipt", back_populates="contract", lazy="selectin"
)
__table_args__ = (
UniqueConstraint(
"contract_key",
"source_repo",
"source_path",
"source_revision",
name="uq_review_contract_source_revision",
),
Index("ix_review_contract_key_active", "contract_key", "active"),
)
class ReviewReceipt(Base, TimestampMixin):
"""Immutable evidence submitted against an exact contract digest."""
__tablename__ = "review_receipts"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=new_uuid7
)
contract_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("review_contracts.id", ondelete="RESTRICT"),
nullable=False,
index=True,
)
owner_id: Mapped[str] = mapped_column(String(160), nullable=False, index=True)
actor: Mapped[str] = mapped_column(String(160), nullable=False)
disposition: Mapped[str] = mapped_column(String(32), nullable=False)
contract_digest: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
receipt_digest: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
artifact_hashes: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
assertion_ids: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
checks: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
note: Mapped[str | None] = mapped_column(Text, nullable=True)
source_repo: Mapped[str] = mapped_column(String(100), nullable=False)
source_path: Mapped[str] = mapped_column(Text, nullable=False)
source_revision: Mapped[str] = mapped_column(String(64), nullable=False)
submitted_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, index=True
)
document: Mapped[dict] = mapped_column(JSONB, nullable=False)
contract: Mapped[ReviewContract] = relationship(
"ReviewContract", back_populates="receipts"
)
__table_args__ = (
UniqueConstraint(
"source_repo",
"source_path",
"source_revision",
name="uq_review_receipt_source_revision",
),
Index(
"ix_review_receipt_contract_owner_time",
"contract_id",
"owner_id",
"submitted_at",
),
)