876b38bd1b
라이브 pkm DB 가 scan-feature-build 의 365(scan_jobs)·366(pending_command) 을 이미 apply + schema_migrations 스탬프함. 발행 마이그가 365 부터면 러너가 365/366 을 적용필로 보고 스킵 → published 테이블 미생성 → 367 깨짐. 다음 free=367 로 +2 시프트해 회피. 파일 rename + 헤더 주석 + published.py 모델 주석(mig 번호) 동기화. 내용 무변경(멱등 CREATE ... IF NOT EXISTS). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
2.8 KiB
Python
61 lines
2.8 KiB
Python
"""발행 레이어 ORM (docsrv-viewer-publish) — published projection + publish_outbox.
|
|
|
|
관계(relationship) 없음 = 독립 테이블, configure_mappers 무영향. 마이그 367~372.
|
|
published = 뷰어가 read API(P0-2)로 당기는 render-ready projection(kind-discriminated).
|
|
publish_outbox = 저작/4-A 트랜잭션이 같은 tx에서 INSERT, 발행 워커가 drain 하며 rev 부여.
|
|
|
|
불변식(plan study-to-viewer-slice1):
|
|
pub_id opaque+stable = dedup키 = progress키 / rev = 워커 커밋순 gapless(pg_advisory_lock 단일 라이터)
|
|
/ (payload_hash, deleted) 디둡 / 삭제 = tombstone(deleted=true) / schema_version = 엔벨로프 버전.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import BigInteger, Boolean, DateTime, SmallInteger, String, Text
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.database import Base
|
|
|
|
|
|
class Published(Base):
|
|
__tablename__ = "published"
|
|
|
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
kind: Mapped[str] = mapped_column(String(40), nullable=False)
|
|
source_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
pub_id: Mapped[str] = mapped_column(Text, nullable=False)
|
|
payload: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
|
payload_hash: Mapped[str] = mapped_column(Text, nullable=False)
|
|
schema_version: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1)
|
|
rev: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now, nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now, nullable=False
|
|
)
|
|
|
|
# UNIQUE(kind, pub_id)=mig368, UNIQUE(kind, source_id)=mig369, idx(rev)=mig370.
|
|
|
|
|
|
class PublishOutbox(Base):
|
|
__tablename__ = "publish_outbox"
|
|
|
|
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
kind: Mapped[str] = mapped_column(String(40), nullable=False)
|
|
source_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
|
payload: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
|
payload_hash: Mapped[str] = mapped_column(Text, nullable=False)
|
|
schema_version: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1)
|
|
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now, nullable=False
|
|
)
|
|
processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
|
|
# 미처리 부분 인덱스 idx(id) WHERE processed_at IS NULL = mig372.
|