2 Commits

Author SHA1 Message Date
hyungi d50be9f2e7 fix(publish): ingest_study 동시경합을 already_ingested 로 흡수 (500 회피)
같은 client_session_uuid 동시 POST 2건이 최초 멱등체크를 둘 다 통과 → 둘째가
(client_session_uuid, study_topic_id) uq(mig376) 위반으로 IntegrityError → 미처리 500.
데이터는 안전(원자 1-tx 롤백, SR 이중 advance 없음)이나 비우아한 500이 문제.

변이 구간(quiz_session insert ~ commit)을 try/except IntegrityError 로 감싸 승자 결과
재조회 후 already_ingested 반환. uuid 경합이 아닌 진짜 무결성 오류는 재조회 비어 re-raise.
멱등 응답 빌더 _already_ingested 헬퍼 추출(최초 체크 + 경합 흡수 공용).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:22:36 +09:00
hyungi 8d3b648b5f feat(ingest): P2 DS write-back — /ingest/study/attempts 멱등 finalize 재생 (study→viewer)
뷰어 로컬 풀이 세션을 DS 로 흘려 학습엔진(SR/pattern/오답/4-A·4-B) 재생. 기본 inert(flag off).
- 마이그 373~376: study_quiz_sessions 에 finalized_at(멱등 마커)·client_session_uuid·source
  + UNIQUE(client_session_uuid, study_topic_id) partial.
- outcome.py derive_outcome = 채점 단일 소스(라이브 submit_attempt 도 이걸로 리팩터 → 정오 어휘
  한 곳, ingest 는 raw 신호 selected+unsure 만 싣고 DS 산출 = '무수정 재생' 성립).
- ingest_study.py: Bearer(VIEWER_SYNC_TOKEN)+study_ingest_enabled gate. pub_id→source_id→question
  해소(graceful skip)·principal=question.user_id(mixed 거부)·topic 별 DS 세션(source=viewer·uuid)
  생성+attempt+finalize_session 무수정 재생+finalized_at, 1-tx 원자. uuid 존재=already_ingested
  캐시반환(멱등 → at-least-once 재전송에도 SR 이중 advance 0).
- config study_ingest_enabled + compose 매핑 + main 등록.

검증: py_compile·ephemeral 마이그(373~376 라이브스키마 위 클린)·single-statement. 배포 후
합성 세션 멱등/무이중SR 실측 예정. 배포=inert(STUDY_INGEST_ENABLED 미설정=503).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:27:34 +09:00