feat(publish): P1-1 digest 발행 read API scaffold(503)

기존 /published 라우터에 GET /published/digest 추가 — _verify_token(Bearer)
+ FeedResponse 엔벨로프 재사용(신규 라우터 X). DIGEST_PUBLISH_ENABLED 플래그
(기본 false=inert): off=503 "not enabled", on+projection 미구현=503. 실데이터·시크릿 0.
검증: 무토큰 401·잘못된 토큰 403·유효+off 503·기존 /feed 200 무회귀.
docsrv-viewer-publish 트랙 (plan viewer-daily-report P1-1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-06-25 08:20:32 +00:00
parent e0772cda68
commit 631e4cd8ef
3 changed files with 26 additions and 0 deletions
+22
View File
@@ -111,3 +111,25 @@ async def published_feed(
next_since=next_since,
has_more=has_more,
)
# ── P1-1: 뉴스/다이제스트 발행 read API (scaffold, docsrv-viewer-publish) ──────────
# 첫 실사용 = 추상화 적합성 시험(plan r2). 다이제스트는 문서용 불변식 일부 제외(content-type
# 파라미터화): rev=단순 version 커서(증분 pull) · pub_id=date-as-id(admin-gated feed 라 opacity
# 불필요) · tombstone 없음(다이제스트 미삭제). 엔벨로프는 /feed 와 동일(FeedResponse) → 뷰어
# pull-sync 클라이언트 재사용. scaffold-first: DIGEST_PUBLISH_ENABLED off(기본)=503(명시적 미가동,
# no-silent). 점등돼도 projection 미구현이면 503 — 실데이터·시크릿 0.
DIGEST_FEED_SCHEMA_VERSION = 1
@router.get("/digest", response_model=FeedResponse)
async def published_digest(
since: int = Query(0, ge=0),
limit: int = Query(DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
_auth: None = Depends(_verify_token),
):
"""뉴스/다이제스트 발행 feed (version 커서 since). scaffold — 점등/projection 전 503."""
if not settings.digest_publish_enabled:
raise HTTPException(status_code=503, detail="digest publish not enabled (scaffold)")
# TODO(P1 후속): global_digests/digest_topics -> render-ready projection (version 커서).
raise HTTPException(status_code=503, detail="digest publish projection not implemented (scaffold)")
+3
View File
@@ -187,6 +187,7 @@ class Settings(BaseModel):
study_card_extract_enabled: bool = True
# 발행 레이어(docsrv-viewer-publish): publish_outbox 워커 게이트. 저자/4-A enqueue 결선(P0-1b) 후 true.
study_publish_enabled: bool = False
digest_publish_enabled: bool = False # docsrv-viewer-publish P1-1 (뉴스/다이제스트 발행 feed gate)
# 뷰어 write-back ingest(study-to-viewer P2) 게이트. /ingest/study/attempts 활성. 기본 false=inert(503).
study_ingest_enabled: bool = False
@@ -204,6 +205,7 @@ def load_settings() -> Settings:
study_explanation_enabled = os.getenv("STUDY_EXPLANATION_ENABLED", "true").lower() in ("1", "true", "yes")
study_card_extract_enabled = os.getenv("STUDY_CARD_EXTRACT_ENABLED", "true").lower() in ("1", "true", "yes")
study_publish_enabled = os.getenv("STUDY_PUBLISH_ENABLED", "false").lower() in ("1", "true", "yes")
digest_publish_enabled = os.getenv("DIGEST_PUBLISH_ENABLED", "false").lower() in ("1", "true", "yes")
study_ingest_enabled = os.getenv("STUDY_INGEST_ENABLED", "false").lower() in ("1", "true", "yes")
internal_worker_token = os.getenv("INTERNAL_WORKER_TOKEN", "")
viewer_sync_token = os.getenv("VIEWER_SYNC_TOKEN", "")
@@ -340,6 +342,7 @@ def load_settings() -> Settings:
study_explanation_enabled=study_explanation_enabled,
study_card_extract_enabled=study_card_extract_enabled,
study_publish_enabled=study_publish_enabled,
digest_publish_enabled=digest_publish_enabled,
study_ingest_enabled=study_ingest_enabled,
internal_worker_token=internal_worker_token,
viewer_sync_token=viewer_sync_token,
+1
View File
@@ -212,6 +212,7 @@ services:
- INTERNAL_WORKER_TOKEN=${INTERNAL_WORKER_TOKEN}
# docsrv-viewer-publish: 발행 워커/저작 enqueue 게이트(기본 false=inert) + 뷰어↔DS feed Bearer.
- STUDY_PUBLISH_ENABLED=${STUDY_PUBLISH_ENABLED:-false}
- DIGEST_PUBLISH_ENABLED=${DIGEST_PUBLISH_ENABLED:-false}
- VIEWER_SYNC_TOKEN=${VIEWER_SYNC_TOKEN:-}
# study-to-viewer P2: 뷰어 write-back ingest 게이트(기본 false=inert, 검증 후 점등).
- STUDY_INGEST_ENABLED=${STUDY_INGEST_ENABLED:-false}