"""Store schema. Tenant keying, from the first migration onward, per `business-app-service-contract_v0.1` § 1.3. Every table holding owned data carries `tenant`, and no query may assume it is the only tenant (§ 1.4). The service is deployed single-tenant today; the key is here so a later consolidation is a data copy rather than a rewrite. The format's three distinct things stay distinct: - a **package version** is immutable content addressed by `@` (§ 17), scoped to a registry because identity is registry-scoped (§ 3.2); - an **index entry** records how a version arrived in this store (§ 20.3); - **files** are content, stored as blobs rather than parsed into rows. """ from __future__ import annotations import datetime as dt from sqlalchemy import ( BigInteger, DateTime, ForeignKey, Index, LargeBinary, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from .db import Base def utcnow() -> dt.datetime: return dt.datetime.now(dt.timezone.utc) class PackageVersion(Base): """One immutable `:@` within one tenant.""" __tablename__ = "package_versions" __table_args__ = ( UniqueConstraint( "tenant", "registry", "package_id", "version", name="uq_package_version" ), Index("ix_package_versions_tenant_id", "tenant", "package_id"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) tenant: Mapped[str] = mapped_column(String(64), nullable=False) registry: Mapped[str] = mapped_column(String(128), nullable=False) package_id: Mapped[str] = mapped_column(String(512), nullable=False) version: Mapped[str] = mapped_column(String(64), nullable=False) # Indexed manifest fields, for discovery without opening every package. name: Mapped[str] = mapped_column(String(256), nullable=False) summary: Mapped[str] = mapped_column(Text, nullable=False, default="") package_type: Mapped[str] = mapped_column(String(32), nullable=False, default="template") license: Mapped[str | None] = mapped_column(String(128)) tags: Mapped[str] = mapped_column(Text, nullable=False, default="") # The manifest as published, so a consumer sees exactly what was stored. manifest: Mapped[str] = mapped_column(Text, nullable=False) content_digest: Mapped[str] = mapped_column(String(71), nullable=False) published_at: Mapped[dt.datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) files: Mapped[list["PackageFile"]] = relationship( back_populates="package_version", cascade="all, delete-orphan" ) class PackageFile(Base): """A file belonging to a package version. Content, not parsed rows.""" __tablename__ = "package_files" __table_args__ = ( UniqueConstraint("package_version_id", "path", name="uq_package_file_path"), Index("ix_package_files_tenant", "tenant"), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) tenant: Mapped[str] = mapped_column(String(64), nullable=False) package_version_id: Mapped[int] = mapped_column( ForeignKey("package_versions.id", ondelete="CASCADE"), nullable=False ) path: Mapped[str] = mapped_column(String(1024), nullable=False) content: Mapped[bytes] = mapped_column(LargeBinary, nullable=False) package_version: Mapped[PackageVersion] = relationship(back_populates="files") class IndexEntry(Base): """How a package version arrived in this store (§ 20.3). Store metadata, deliberately separate from the package: `included_at` is first arrival and is never overwritten; a re-record updates `last_seen_at`. """ __tablename__ = "index_entries" __table_args__ = ( UniqueConstraint( "tenant", "registry", "package_id", "version", name="uq_index_entry" ), ) id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) tenant: Mapped[str] = mapped_column(String(64), nullable=False) registry: Mapped[str] = mapped_column(String(128), nullable=False) package_id: Mapped[str] = mapped_column(String(512), nullable=False) version: Mapped[str] = mapped_column(String(64), nullable=False) source: Mapped[str] = mapped_column(Text, nullable=False) method: Mapped[str] = mapped_column(String(32), nullable=False) declared_author: Mapped[str | None] = mapped_column(String(256)) declared_source: Mapped[str | None] = mapped_column(Text) license: Mapped[str | None] = mapped_column(String(128)) included_at: Mapped[dt.datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=utcnow ) last_seen_at: Mapped[dt.datetime | None] = mapped_column(DateTime(timezone=True))