실측 origin: Tailscale direct 경로 ~10분 플랩(13:25~13:34)으로 300건 run 이 32건에서
조기 종료. 보류 시멘틱 자체는 정상(무손상) — run 지속성만 보강. 연속 보류 5회까지
120s 간격 재시도, 한도 도달 = sleep 판정 종료. 성공 시 카운터 리셋.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
llm_router.py 실측: upstream 연결 실패/생성 중 절단 = HTTPException 502 (4곳).
맥북 sleep 절단의 실제 표면이라 503 단독 분류는 보류 누락 → 502/503/504 로 확장.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GPU 회선에서 moel.go.kr 첫 TLS 연결이 간헐 드랍(curl rc=35, 직후 재시도 5/5 성공,
맥북 무발생·단일 A 레코드) → 사이클당 1회 fetch 인 피드가 ConnectError('') 누적,
입법행정예고 circuit open. ConnectError/ConnectTimeout 만 1.5s 후 1회 재시도,
HTTP 상태 오류 비대상. 회귀 테스트 3건 (42 passed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
httpx 의 Response.is_redirect 는 3xx 전체(304 Not Modified 포함)에 True 라,
조건부 GET 으로 304 를 받으면 location 없는 같은 URL 을 3회 재요청 후
'redirect 3회 초과'로 오류 처리 → ETag/Last-Modified 받는 안정 피드(SEP/HSE/OSHA
/철학 RSS 등)가 2번째 사이클부터 전멸하던 systematic 버그.
- 304 처리를 redirect 루프보다 앞으로 이동.
- redirect 판별을 has_redirect_location(=location 헤더 있는 진짜 redirect)으로 교체.
news_collector._fetch_rss + crawl_politeness.fetch_page 동일 함정 양쪽 수정.
- 사이클 1 파일럿(경향)은 304 를 받은 적 없어 잠복했고, 안정 피드 첫 304 에서 발현.
- 회귀 테스트 3건(304 비-redirect / 진짜 redirect / 코드 패턴 audit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
르몽드 기사 = DataDome Client Challenge(316자)가 200자 본문 floor 통과 → 챌린지
HTML 이 기사 본문으로 승격되는 silent corruption 위험. fetch_page_via_browser 에
챌린지 마커 게이트 추가 → CrawlBlocked(degrade=RSS 요약 유지). 헤드리스 탐지라 재시도 무의미.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- fulltext_worker.reconcile_unresolved: EXISTS 서브쿼리 aliased(ProcessingQueue) —
auto-correlation 이 FROM 전부 제거해 매 실행 InvalidRequestError (안전망 dead code).
SQLAlchemy 2.0.50 컴파일 재현·수정 확인.
- news_collector._fetch_rss: ETag/Last-Modified/content-hash 영속을 bozo 파싱 검증
뒤로 이동 — 부패 응답 워터마크 저장 시 영구 304-skip 차단.
- news_collector.run: 모듈 락으로 수동 collect vs 6h 스케줄 동시 실행 차단 —
_get_or_create_health 동시 INSERT 의 uq_source_health_source_id 위반이
사이클 전체를 죽이는 경합 봉쇄.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BBC Technology 매 사이클 MultipleResultsFound (06-04~) 해소.
- 저장 edit_url=raw vs 조회 normalized 비대칭으로 URL dedup 무력화돼
교차게시(HN x BBC) 시 2행 동시매칭 -> scalar_one_or_none raise.
- _normalize_url: query 전체 제거 -> tracking 파라미터만 제거로 교정
(hada.io/topic?id= 등 query-식별 사이트 870건 붕괴 방지, 리뷰 게이트).
- 조회 .first() + edit_url IN (normalized, raw) 레거시 행 내성. RSS/NYT 양쪽.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pyhwp(hwp5html) 가 bindata/ 로 추출하는 raster 이미지를 NAS 에 영속한다. 기존엔
변환 tempdir 와 함께 폐기돼 경고 없이 silent 유실(도식·수식)이었다(적대 리뷰 MEDIUM).
- office_md.py: _run_hwp5html 으로 hwp5html 1회 실행 → (markdown, raster_images).
convert_hwp_to_md_and_images() 신규 = marker_worker 이미지 경로용. hwp5html 은 이미지를
본문 xhtml 에 <img> 앵커하지 않아(--css/--html 동일) 인라인 위치 복원 불가 → 호출부가
말미 갤러리로 부착. OLE 수식/도형은 앵커도 raster 도 아니라 영속 제외.
- marker_worker._process_office: .hwp raster 를 marker(PDF)의 _persist_images_to_nas 로
NAS 영속 + document_images UPSERT(_sync_document_images, 재변환 orphan 정리) + md 말미
## 첨부 이미지 docimg: 갤러리 + quality.warnings hwp_images_appended. docx/xlsx/pptx/
hwpx 는 이미지 미처리(기존 동작 유지).
- scripts/backfill_hwp_library.py: 지정 PKM 폴더 .hwp 를 content-hash dedup(Inbox 중복 +
_1/카피본 사본 흡수) 후 category=library 일회성 ingest.
검증(E2E): Knowledge/Engineering 18개 → dedup 후 신규 5개(산업안전기사 3~7과목) ingest,
5/5 success. 제4과목 raster 3장 → NAS extracted_images/35778/img_001~003.jpeg 실재 +
document_images 3 row(engine=pyhwp) + md 갤러리 docimg ref. 이미지 없는 문서는 갤러리
미생성. 텍스트/표 경로 회귀 0(기존 4건 재변환 success).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
트리(/documents/tree)는 deleted 만 제외하고 뉴스/법령/메모를 다 세는데, 문서함 list 는
source_channel news/law_monitor + file_type note 를 기본 제외 → '트리는 N건인데 클릭하면
0건' 불일치(예: Philosophy/Aesthetics 5건 전부 news+note 라 클릭 시 0). 트리 쿼리에 동일
제외 적용해 카운트=실제 표시 일치. 영향: Philosophy 12→2, General 189→84 등 정상화.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
B2 /due 가 due_at<=now(progress 보유) 카드만 반환 → progress 는 rate_card(=/rate)로만 생기고 /rate 는 /due 카드만 평가 → 신규 승인 카드가 SR 큐에 영영 못 들어가는 순환 갭. 복습 트랙이 절대 안 채워짐.
- /due 를 outerjoin 으로 재작성: 신규(progress 없음=첫 회상 전) OR 예정 due(due_at<=now, stage<4). 예정 due 먼저, 신규(due NULL) 뒤로. '첫 회상 후 due' 규칙·시안('오늘 복습'에 stage0 신규 포함)과 일치.
- 신규 카드 '암'은 백엔드가 due 안 박음(외움→큐 제외, 큐 폭발 방지)이라 correctLabel(null)='안 나옴'으로 정합(기존 '+3일'은 거짓 라벨). 큐 stage0 '암'은 그대로 '+3일'.
검증: py_compile OK. 신규 암→progress(due null, 재출제 X) / 애매·모름→due 내일 입고 / 큐 stage 전진 불변.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
검토 완료 카드를 학습하는 백엔드. 복습(SR)=즉시 자동 입고 / 그냥공부(cram)=봤다 기록, SR 무관.
- migrations 299(idx_card_progress_due partial) + 300(study_memo_cards view_count/last_viewed_at).
- StudyMemoCardProgress 모델(294 미러, UNIQUE user+card) + rate_card(get-or-create → sr_schedule.advance/first_due, 즉시 자동 입고: 애매/모름 평가 즉시 due, 암은 due 안 박음).
- StudyMemoCard view_count/last_viewed_at + record_card_view 헬퍼(cram, SR 무관).
- API: GET /study-cards/due(복습 큐, 검수통과만) · POST /{id}/rate(자기평가 read-time 매핑) · GET /deck(cram, 덜 본 순) · POST /{id}/view(봤다 기록).
검증: 부팅+8라우트 등록 · 287~300 ephemeral 적용(인덱스·컬럼 확인) · sr_schedule 회귀 7/7(B1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
직접 추가 자료 카드(source_kind='manual', 출처 문제 없음)가 검수 UI에서 null 한 덩어리로
뭉치지 않도록 extra.material 별 그룹("[자료] ...") + CardItem.source_kind 노출(프론트 '직접 추가 자료' 라벨).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
577 카드(needs_review=true)를 보고 채택/수정/폐기하는 첫 검수 화면(학습 흐름 '마지막 한 칸' 1번).
- 백엔드 app/api/study_cards.py(prefix /api/study-cards): GET(출처 문제별 그룹, evidence 동반)·needs-review/count·PATCH(승인 needs_review=false / 수정 시 dedup_hash 재계산+검수완료)·DELETE(soft)·approve-batch(문제 단위, 전체 일괄승인 없음).
- 프론트 /study/cards-review: 반응형 그룹 목록(문제+카드) · 카드별 승인/수정(인라인)/삭제 · 문제 단위 일괄승인 · format 필터 · 세이지 토큰. study 허브에 진입 링크+대기 카운트 배지.
- 카피 drift 정정: 허브 '예정(Phase 2~)'이 가동 중인 퀴즈/SRS/통계를 잘못 표기 → 예정은 카드 SRS·모바일·알람으로 수정.
검증: 백엔드 부팅+라우트 등록 OK(4 route). 프론트 빌드는 배포 시 vite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_lookup_news_source prefix 미일치 시 silent (None) 반환 → warn 로그 추가.
loader 의 drop 로그와 대칭, 신규 source / RSS category 오염 재발 즉시 가시. 동작 변경 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-DocSrv-Hier-PassageRAG-Diagnose-1 c1. /ask evidence retrieval 의 chunk leg 를
측정 뷰(prehier/hier_sim_*)로 교체 + exact_knn — passage evidence 단위(hier 절 vs
legacy 윈도우) 비교용. /search 와 동일 패턴, run_search 전달. EVAL-ONLY 박제,
default(미지정) 시 기존 /ask byte/behavior 동일(회귀 0). pattern 검증 → 잘못된 값 422.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marker_worker 는 변환 시작 시 doc.md_status=processing 으로 표시하는데, 변환이
_fail()/_set_skipped() 를 거치지 않고 예외(예: 대형 batch ReadTimeout)로 죽으면
queue_consumer 가 큐 행만 failed 처리하고 doc.md_status 는 processing 에 영구 고착
= orphan (큐 failed, 문서 processing). markdown consumer 분리 후 이 orphan 이
tail 재처리에서 재발(5149/5201)하여 근본 원인 차단.
_process_stage except 블록에서 큐 항목이 영구 실패(attempts>=max)할 때 stage가
markdown 이고 doc.md_status=processing 이면 failed 로 동기화. 재시도 중
(attempts<max)엔 pending 큐 행이 남아 orphan 아니므로 미터치.
검증: synthetic 영구 실패 경로 → md_status processing→failed 동기화 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
대형 PDF split 변환(5210 ≈ 40분 실측)이 단일 consume_queue 코루틴을 점유해
extract/classify/embed/chunk 등 전 파이프라인을 stall 시키던 문제 제거.
- consume_markdown_queue 신규 — markdown 전용 scheduler job (id=markdown_consumer)
- consume_queue 는 MAIN_QUEUE_STAGES (markdown 제외) 만 처리
- _process_stage / _load_workers 헬퍼로 per-stage 로직 공유
- reset_stale_items(stages, threshold_minutes) 파라미터화: main=10min(markdown 제외),
markdown=MARKDOWN_STALE_MINUTES(기본 120). marker_worker 는 heartbeat 미기록이라
40분 변환을 10분 stale 로 오인하던 함정 차단
- enqueue flow (classify -> embed,chunk,markdown) 불변
STT/deep_summary 분리 + GPU 동시성 튜닝은 out of scope (follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-DocSrv-LargeDoc-Split-Markdown-1 follow-up (plan brisk-paging-quokka.md).
commit 4(marker_section→document_chunks) 드롭으로, split md_content/manifest 의
「권위 검색본 = document_chunks (source_type=marker_section)」 문구가 실제와 불일치.
실제 = 검색 인덱스는 기존 document_chunks(extracted_text long_pdf window chunks),
marker_section chunk 부재, md_content 는 Markdown 렌더링 preview.
- _build_large_md_content 헤더: 「검색 인덱스 = 기존 document_chunks long_pdf/
extracted_text window chunks. 아래는 Markdown 렌더링 preview.」
- _split_manifest: canonical_storage(marker_section) → search_index(legacy/extracted_text)
- 상수 주석 + _process_split docstring: commit 4 드롭/이중적재 회피 반영
뷰어에 없는 source_type 으로 디버깅 오도 방지. 이미 처리된 5 docs 의 md_content 는
즉시 재처리 X — 자연 reprocess 시 갱신(사용자 결정).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-DocSrv-LargeDoc-Split-Markdown-1 commit 5 (plan brisk-paging-quokka.md).
이미 마크다운인 문서는 marker 변환 불필요 → _process_markdown_passthrough 로
파일 내용(없으면 extracted_text)을 md_content 에 직접 적재(success), 비면 skipped.
- _is_markdown_doc: file_format=md/markdown 또는 .md/.markdown 확장자
- 분기 위치 = file_path validation 이전 (fileless md = file_path NULL 처리 위함)
- engine=passthrough 로 marker 변환본과 구분
기존 버그 해소: fileless md 43건=「no file_path」 fail / .md 파일=unsupported extension
skip → 둘 다 md_content 미생성이었음.
검증(docker cp 격리): 13948(.md+file_path)→success md_len=1805(파일) /
23409(fileless 931자)→success(extracted_text) / 20237(fileless 6자)→success.
PDF 경로 무영향(_is_markdown_doc=False).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>