Spaces:
Running
Running
| """SQLAlchemy ORM — the real persistence layer (Batch C1). | |
| Replaces the placeholder dataclass ``Entity``/``InMemoryRepository`` with mapped Postgres | |
| tables. The schema follows the design document's SQL baseline (users, projects, and the seven | |
| workflow tables) with one deliberate addition: every workflow table carries a denormalized | |
| ``owner_id`` so row-level security can be a simple, fast ``owner_id = app_current_user()`` | |
| policy rather than a deep parent-chain subquery. | |
| Tables (DDL + RLS) are created by the Alembic migration, not by ``create_all``, so the RLS | |
| policies and the ``app_current_user()`` helper are part of the versioned schema. | |
| """ | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from sqlalchemy import ( | |
| BigInteger, | |
| DateTime, | |
| ForeignKey, | |
| Integer, | |
| String, | |
| Text, | |
| UniqueConstraint, | |
| func, | |
| ) | |
| from sqlalchemy.dialects.postgresql import JSONB | |
| from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column | |
| # Tables that are exposed to API callers and therefore get owner-scoped RLS. | |
| RLS_TABLES: tuple[str, ...] = ( | |
| "projects", | |
| "bundle_versions", | |
| "build_batches", | |
| "matrix_commits", | |
| "prompt_versions", | |
| "validation_runs", | |
| "validation_findings", | |
| "artifacts", | |
| "run_events", | |
| "gitpilot_runs", | |
| "design_bundles", | |
| ) | |
| class Base(DeclarativeBase): | |
| pass | |
| def _pk() -> Mapped[str]: | |
| # UUIDs generated app-side (or by gen_random_uuid in the migration default). | |
| return mapped_column(String(36), primary_key=True) | |
| def _ts() -> Mapped[datetime]: | |
| return mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) | |
| class User(Base): | |
| __tablename__ = "users" | |
| id: Mapped[str] = _pk() | |
| email: Mapped[str | None] = mapped_column(String(320), unique=True) | |
| display_name: Mapped[str | None] = mapped_column(String(200)) | |
| plan: Mapped[str] = mapped_column(String(32), default="free", nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class Project(Base): | |
| __tablename__ = "projects" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| title: Mapped[str] = mapped_column(Text, nullable=False) | |
| slug: Mapped[str] = mapped_column(String(120), nullable=False) | |
| description: Mapped[str] = mapped_column(Text, default="", nullable=False) | |
| status: Mapped[str] = mapped_column(String(32), default="active", nullable=False) | |
| privacy: Mapped[str] = mapped_column(String(16), default="private", nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| updated_at: Mapped[datetime] = _ts() | |
| class BundleVersion(Base): | |
| __tablename__ = "bundle_versions" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| project_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| parent_version_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("bundle_versions.id") | |
| ) | |
| version_label: Mapped[str] = mapped_column(String(32), nullable=False) # v1.0.0 | |
| title: Mapped[str] = mapped_column(Text, nullable=False) | |
| requirements_md: Mapped[str] = mapped_column(Text, default="", nullable=False) | |
| blueprint_artifact_id: Mapped[str | None] = mapped_column(String(36)) | |
| status: Mapped[str] = mapped_column(String(32), default="active", nullable=False) | |
| created_by: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class BuildBatch(Base): | |
| __tablename__ = "build_batches" | |
| __table_args__ = (UniqueConstraint("version_id", "ordinal", name="uq_batch_ordinal"),) | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| version_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("bundle_versions.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| parent_commit_id: Mapped[str | None] = mapped_column(String(36)) | |
| ordinal: Mapped[int] = mapped_column(Integer, nullable=False) | |
| title: Mapped[str] = mapped_column(Text, nullable=False) | |
| goal_md: Mapped[str] = mapped_column(Text, default="", nullable=False) | |
| change_type: Mapped[str] = mapped_column( | |
| String(32), nullable=False | |
| ) # small-update|add-feature|fix-issue | |
| status: Mapped[str] = mapped_column(String(32), default="draft", nullable=False) | |
| requested_by: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class MatrixCommit(Base): | |
| __tablename__ = "matrix_commits" | |
| __table_args__ = (UniqueConstraint("version_id", "commit_no", name="uq_commit_no"),) | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| batch_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("build_batches.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| version_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("bundle_versions.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| parent_commit_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("matrix_commits.id") | |
| ) | |
| commit_no: Mapped[int] = mapped_column(Integer, nullable=False) | |
| summary: Mapped[str] = mapped_column(Text, default="", nullable=False) | |
| tree_hash: Mapped[str] = mapped_column(String(80), nullable=False) | |
| validation_status: Mapped[str] = mapped_column(String(32), default="not-run", nullable=False) | |
| prompt_pack_id: Mapped[str | None] = mapped_column(String(36)) | |
| # The commit manifest (added/changed/deleted file lists + scope), persisted so diffs and | |
| # the build timeline can be reconstructed without re-reading object storage (Batch C2). | |
| manifest: Mapped[dict] = mapped_column("manifest_jsonb", JSONB, default=dict, nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class PromptVersion(Base): | |
| __tablename__ = "prompt_versions" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| batch_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("build_batches.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| coder: Mapped[str] = mapped_column(String(32), nullable=False) | |
| prompt_text: Mapped[str] = mapped_column(Text, nullable=False) | |
| constraints: Mapped[dict] = mapped_column( | |
| "constraints_jsonb", JSONB, default=dict, nullable=False | |
| ) | |
| artifact_id: Mapped[str | None] = mapped_column(String(36)) | |
| created_at: Mapped[datetime] = _ts() | |
| class ValidationRun(Base): | |
| __tablename__ = "validation_runs" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| # Nullable since C3: an async run is validated *before* a commit exists; the commit is | |
| # created only on approval, otherwise the run carries a repair suggestion instead. | |
| commit_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("matrix_commits.id", ondelete="CASCADE"), index=True | |
| ) | |
| batch_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("build_batches.id", ondelete="CASCADE"), index=True | |
| ) | |
| status: Mapped[str] = mapped_column( | |
| String(32), nullable=False | |
| ) # running|approved|needs-repair|rejected|failed | |
| score: Mapped[int | None] = mapped_column(Integer) | |
| runner: Mapped[str] = mapped_column(String(32), default="local", nullable=False) | |
| started_at: Mapped[datetime] = _ts() | |
| ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) | |
| class ValidationFinding(Base): | |
| __tablename__ = "validation_findings" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| validation_run_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("validation_runs.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| severity: Mapped[str] = mapped_column(String(16), nullable=False) | |
| status: Mapped[str] = mapped_column(String(16), nullable=False) | |
| check_name: Mapped[str] = mapped_column(String(120), nullable=False) | |
| file_path: Mapped[str | None] = mapped_column(Text) | |
| message: Mapped[str] = mapped_column(Text, nullable=False) | |
| remediation: Mapped[str | None] = mapped_column(Text) | |
| class RunEvent(Base): | |
| """Append-only event log for a validation run (Batch C3). | |
| Rows are never updated or deleted. ``seq`` is a per-run monotonic cursor: clients page with | |
| ``?after=`` and WebSocket subscribers replay missed events by ``seq`` before streaming live. | |
| """ | |
| __tablename__ = "run_events" | |
| __table_args__ = (UniqueConstraint("run_id", "seq", name="uq_run_event_seq"),) | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| run_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("validation_runs.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| seq: Mapped[int] = mapped_column(BigInteger, nullable=False) | |
| event_type: Mapped[str] = mapped_column(String(48), nullable=False) | |
| payload: Mapped[dict] = mapped_column("payload_jsonb", JSONB, default=dict, nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class Artifact(Base): | |
| __tablename__ = "artifacts" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| project_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| version_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("bundle_versions.id", ondelete="CASCADE") | |
| ) | |
| commit_id: Mapped[str | None] = mapped_column( | |
| String(36), ForeignKey("matrix_commits.id", ondelete="CASCADE") | |
| ) | |
| artifact_type: Mapped[str] = mapped_column( | |
| String(32), nullable=False | |
| ) # bundle_zip|prompt_md|patch_diff|log|manifest|thumbnail_svg | |
| storage_key: Mapped[str] = mapped_column(Text, nullable=False) | |
| sha256: Mapped[str] = mapped_column(String(80), nullable=False) | |
| size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False) | |
| created_at: Mapped[datetime] = _ts() | |
| class GitPilotRun(Base): | |
| """A GitPilot run (cloud or local) recorded for history + audit (Batch 10). | |
| Owner-scoped like every other workflow table. Records the run id, the bundle | |
| it targeted, the synced GitPilot status, and the Matrix verdict — so a user's | |
| runs survive restarts and are queryable. | |
| """ | |
| __tablename__ = "gitpilot_runs" | |
| id: Mapped[str] = mapped_column(String(64), primary_key=True) # the gp-run-... id | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| bundle_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) | |
| task_id: Mapped[str | None] = mapped_column(String(64)) | |
| source: Mapped[str] = mapped_column(String(32), default="cloud") # cloud|local | |
| status: Mapped[str] = mapped_column(String(32), default="queued") | |
| test_status: Mapped[str] = mapped_column(String(32), default="not_run") | |
| summary: Mapped[str] = mapped_column(Text, default="") | |
| changed_files: Mapped[dict] = mapped_column(JSONB, default=list) | |
| # Matrix verdict (approved|needs-repair|rejected) — null until validated. | |
| validation_status: Mapped[str | None] = mapped_column(String(32)) | |
| can_commit: Mapped[bool] = mapped_column(default=False) | |
| parent_run_id: Mapped[str | None] = mapped_column(String(64)) | |
| created_at: Mapped[datetime] = _ts() | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False | |
| ) | |
| class DesignBundleRecord(Base): | |
| """A saved Matrix Designer Blueprint Details + chat for a build (batch-10). | |
| Owner-scoped like every other workflow table. Keyed by (owner, build, candidate) | |
| so reopening a build restores the same blueprint and Talk-to-blueprint history. | |
| """ | |
| __tablename__ = "design_bundles" | |
| id: Mapped[str] = _pk() | |
| owner_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) | |
| build_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) | |
| candidate_id: Mapped[str] = mapped_column(String(32), nullable=False) | |
| idea: Mapped[str] = mapped_column(Text, default="") | |
| details: Mapped[dict] = mapped_column(JSONB, default=dict) | |
| chat_history: Mapped[list] = mapped_column(JSONB, default=list) | |
| created_at: Mapped[datetime] = _ts() | |
| updated_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False | |
| ) | |
| __table_args__ = (UniqueConstraint("owner_id", "build_id", "candidate_id", name="uq_design_bundle_owner_build_cand"),) | |
| __all__ = [ | |
| "Base", | |
| "RLS_TABLES", | |
| "User", | |
| "Project", | |
| "BundleVersion", | |
| "BuildBatch", | |
| "MatrixCommit", | |
| "PromptVersion", | |
| "ValidationRun", | |
| "ValidationFinding", | |
| "RunEvent", | |
| "Artifact", | |
| "GitPilotRun", | |
| "DesignBundleRecord", | |
| ] | |