chore-memo-NULL-backfill 6/6 H1 (historical artifact) 확정 후 Apply PR 영구 보류.
406b810 의 8-line logger.info 블록 제거 (behavior 변경 0, 진단 데이터 더 이상 불필요).
backup: app/workers/classify_worker.py.pre-eventkind-cleanup (7일 안전망 ~2026-05-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer-A Diagnose only. classify_worker.py:691 직전에 event_kind_hint 의
raw/normalized/in_valid/confidence 값 capture (logger.info 5줄 insert,
lazy formatting + %r repr). guard 통과 X 의 specific root cause (A1 field
부재 / A2 빈 string / A3 invalid enum) 확정용.
specific fix (default note / enum mapping / prompt 강화) 는 별 PR-4B-Fix-EventKindHint-Apply.
Apply PR closure gate 에 logging cleanup (info → DEBUG 또는 제거) 흡수.
plan: ~/.claude/plans/c-1-pr-infra-drift-1-phase-1b-linear-frost.md
backup: app/workers/classify_worker.py.pre-4b-eventkind-logging.20260517
Layer 1 root cause 진단을 위해 classify_worker.py:595 의 exception logging
을 lazy formatting + exc_info=True 로 강화. f-string 1줄 → 5줄 block.
- type=%s: exception class name (TimeoutError/JSONDecodeError/ValueError/etc.)
- repr=%r: full exception state
- exc_info=True: traceback 까지 capture (wrapper 정확 지점 추적)
본 PR scope = Diagnose only. Layer 1 specific fix (H1/H2/H3/H4) + Layer 2
escalate path ai_event_kind fallback set 은 별 PR queue.
plan: ~/.claude/plans/c-1-pr-infra-drift-1-phase-1b-linear-frost.md
backup: app/workers/classify_worker.py.pre-4b-diagnose.20260517
PR-2B (Memo Inbox Triage) backend 1/2. plan: beszel-tingly-sloth.md 라운드 13.
사용자 비전 = 메모는 inbox, AI 는 triage assistant. AI worker 는 events row 직접 생성 X.
Migrations 250–253 (실측 N=250):
- 250 CREATE TYPE event_kind_hint AS ENUM (note|task|calendar_event|activity_log|reference)
- 251 ALTER TABLE documents ADD ai_event_kind event_kind_hint
- 252 ALTER TABLE documents ADD ai_event_confidence NUMERIC(3,2) + CHECK 0–1
- 253 CREATE INDEX idx_documents_ai_event_kind partial WHERE ai_event_kind IS NOT NULL
ORM:
- Document.ai_event_kind / ai_event_confidence 컬럼 추가 (Enum SQLAlchemy 동기)
- source_channel enum 에 'voice' 추가 (PR-2C 와 호환)
Worker:
- classify_worker Phase 3 (Gemma 4B triage) 확장
· TriageOutput 에 event_kind_hint + event_kind_confidence 필드 추가
· 4B 응답에 hint 가 있을 때만 Document 에 저장 (enum 외 값은 무시)
- prompt p3a_short_summary.txt 확장 — note/task/calendar_event/activity_log/reference
분류 기준 + confidence + default='note' 명시
원칙: AI worker 는 hint 만 제공. events 생성은 다음 commit 의 promote endpoint 에서만.
D9 Track B revised (2026-05-08):
1) STT owner GPU 정식 복귀:
- docker-compose.yml: stt-service profiles:[legacy] 제거 → 상시 활성
- fastapi STT_ENDPOINT = http://stt-service:3300 (compose 내부 DNS)
- 정책: Mac mini = Gemma 26B 전용 우선이므로 STT/Whisper 는 호출량 무관
GPU 서버 소유. 이전 "Mac mini 이전본" 주석은 trace 오인 기반.
2) KGS Code 등 외부 학습 자료 추가 스캔 경로:
- ADDITIONAL_WATCH_TARGETS env (쉼표 구분, PKM 상대경로)
- app/core/config.py: additional_watch_targets list 설정 추가
- app/workers/file_watcher.py: 추가 watch path 처리
- app/workers/classify_worker.py: KGS Code 분류 분기 (가스기사 학습 자료)
- 모두 expected_category=library 처리 (md/pdf/docx 만)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AI 응답에서 dataOrigin='knowledge' 같은 doc_purpose enum 값이 data_origin
컬럼에 잘못 매핑되면 asyncpg InvalidTextRepresentationError 발생. 같은
classify_worker session 의 후속 autoflush 호출이 PendingRollbackError
로 cascade 되어 batch 안 다른 문서까지 모두 실패.
doc_purpose 처럼 enum 허용값(work/external) 검증 후 박도록 수정. 외 값은
skip (data_origin NULL 유지). 가스기사 토픽 결손 15건의 RAG 결손 root cause.
PR-B refactor 과정에서 e88640d 의 process() 진입부 source_channel='law_monitor'
skip 분기가 사라져 매일 07:00 신규 법령 분할마다 26B legacy classify(8s) +
26B legacy summarize(10s) + 4B triage(1.5s) 전부 호출되고 있었다.
법령 분리 PR (stateless-churning-raccoon) 의 명제:
"법령은 외부 source-of-truth + immutable + 자동 재수집 → 다른 수명주기"
와 일치하도록 process() 진입부에 skip 분기 복원. 최소 필드 (ai_domain='법령',
ai_tags=['법령'], importance='medium') 만 세팅 후 return. queue_consumer 의
NEXT_STAGES['classify']=['embed','chunk'] 가 자동 chain 하므로 검색 영향 0.
법령 도메인 AI 산출물 가치 분석:
- ai_summary: 법령 해석 환각 위험 (ASME/안전 엔지니어 사고 책임 소지)
- ai_tldr/bullets: 이미 title 이 같은 정보 노출 — redundant
- ai_inconsistencies: 공식 정합 문서라 100% false positive
→ 비용 (월 ~14분 26B 점유) 대비 가치 음수, skip 합당.
tier_backfill.py 도 함께 수정:
- DOMAIN_PRIORITY 에서 ('law', source_channel='law_monitor') 항목 제거
- safety 필터에 source_channel != 'law_monitor' 추가 (기존 ai_domain LIKE
'Industrial_Safety%' 매칭 안에 backfill 기 처리한 법령 doc 들이 잡혀
들어가는 case 차단)
- 사유: skip 처리될 doc 을 enqueue 하면 야간마다 enqueue→skip→NULL→
enqueue 무한 루프
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
실측 발견 (safety 8건 재분류):
- 10574 KRAS (safety_operational) → escalate=true (guard 전 pass)
- 10568 JSA (safety_operational) → escalate=false suppressed=True
- 10570 PPE (safety_operational) → escalate=false suppressed=True
- 동일 도메인인데 4건 중 1건만 26B 처리. 같은 질의 종류 문서가
누구는 깊이 있고 누구는 짧음 → 사용자 관점 일관성 붕괴.
원인: risk_flag_requires_26b 가 soft escalate 분류 → R2 backlog guard
의 ratio 임계치(0.3) 에 걸림. 방금 classify 8건 enqueue 중 앞선 건들이
deep_summary 큐 채우자 뒤 건들이 전부 suppress.
수정: HARD_ESCALATE_REASONS 에 risk_flag_requires_26b 추가. safety/
health/chemical 등 도메인 정책 기반 escalate 는 절대 억제하지 않음.
soft 영역은 여전히 남아있음: self_declare (4B 자가선언), deep_requested
(recommend_deep_summary). 이 둘만 backlog guard 가 억제 대상.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
실측 발견 (safety md 8건 tier triage 결과):
1. **분류 오분류**: 본문에 "MSDS" 한 번 스쳐도 msds 도메인 매칭됨.
개인보호구/중대재해/밀폐공간/산업안전보건법 전부 msds 로 잘못 판정.
2. **RoutingDecision 무시**: PR-A domain_policy 의 high_impact=true 와
risk_flag_requires_26b 때문에 RoutingDecision.escalate_to_26b=True 이지만
내 _classify_escalation_reason 이 이걸 안 봐서 escalate=False 로 마감.
safety/msds/hazard_specific 전부 4B 만 돌고 26B 정책 우회.
수정:
- _match_subject_domain: (a) title 기반 매칭 우선 추가 — 파일명이 의도의
1차 시그널. (b) 본문 키워드는 **2회 이상 등장**해야 match (single-mention
오분류 방지). 우선순위도 재배열 (msds 맨 앞 → hazard/safety 뒤로).
- _classify_escalation_reason: routing_decision 파라미터 추가. 4B 자체
판정 (long_context / low_confidence / self_declare / deep_requested)
이후 PR-A routing_decision.escalate_to_26b 가 True 이면 그 escalation_reasons
중 "high_impact" 외의 구체 사유(risk_flag_requires_26b 등) 를 채택.
- _run_tier_triage: routing_decision 을 먼저 계산하여 _classify_escalation_reason
에 전달. _apply_triage_result 는 routing_decision 을 param 으로 받음
(중복 계산 제거).
이 변경 후 safety/msds/hazard_specific/incident_report 도메인 문서는 항상
26B escalate → deep_summary 큐. MLX 부하 증가하지만 plan 의도대로 정책 준수.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
doc 5260 (confidence 0.3 low_confidence 에스컬레이션) 실측에서 발견:
EscalationEnvelope(from_stage='summary_triage') 가 PR-A ValidFromStage
({triage, summarize_short, advice_trigger, classify, night_sweep, ask_pre,
unknown}) 에 없어 ValueError 발생 → 모든 deep_summary enqueue 가 envelope
생성 단계에서 터짐. tldr/bullets 기록은 envelope 실패 전에 완료되어 영향
없음 (try/except 가 classify 전체는 보호).
P3a short summary 에서의 에스컬레이션 의미에 맞춰 'summarize_short' 로 변경.
내부 task 이름 (SUMMARY_TRIAGE_TASK = 'p3a_short_summary') 는 analyze_events.
prompt_version 기록 전용이라 그대로 유지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- migrations/152: ALTER TYPE doc_category ADD VALUE 'law' (DDL only; PG16 단일-트랜잭션 제약상 backfill 은 별도)
- models/document.py: Enum 에 'law' 추가 (7 활성 + 3 유보)
- workers/law_monitor.py: Document(..., category='law') — 신규 유입부터 세팅
- workers/classify_worker.py: source_channel='law_monitor' early-return + 최소 필드 (ai_domain='법령', ai_tags=['법령'], importance='medium'). AI classify skip — 법령 구조 고정/외부 source of truth/자동 재수집
- scripts/backfill_category.py: law 분기 + WHERE re-target ((source_channel='law_monitor' AND category='document')) + VERIFY cat_law/law_source_count + fail 조건
- api/documents.py: default 목록 제외에 law_monitor 추가 (news 와 동일 패턴)
- api/dashboard.py: documents count FILTER 에 law_monitor 제외 (category_counts.law 는 기존 GROUP BY category 로 자동 노출)
- frontend/Sidebar.svelte: '법령 알림' 버튼 ?source=law_monitor → ?category=law (explicit category 경로가 default exclusion 을 skip)
plan: ~/.claude/plans/stateless-churning-raccoon.md
axis 원칙: category=UI 축, policy/telemetry=source_channel+ai_domain 축 (feedback_category_vs_ai_domain_axis.md)
배포 순서: push → GPU pull → compose up --build fastapi frontend → backfill --dry-run → --apply.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- extract_worker: LibreOffice 15000자 절단 제거 (full text 저장 원칙)
- classify_worker/summarize_worker: 요약 입력 15000→50000자 확대
- client.py: 길이 기반 Claude 자동전환 제거 (require_explicit_trigger 정책 준수)
_call_chat의 primary→fallback(exaone3.5) 체인은 유지
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config.yaml: 6개 domain × 3단계 taxonomy + 13개 document_types 정의
- classify.txt: 영문 프롬프트, taxonomy 경로 기반 분류 + 분류 규칙 주입
- classify_worker: taxonomy 검증, confidence 기반 분류, document_type 저장
- migration 008: document_type, importance, ai_confidence 컬럼
- API: DocumentResponse에 document_type, importance, ai_confidence 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- queue.py: process_stage enum에 'preview' 추가
- classify_worker: ai_summary에 strip_thinking() 적용
- CLAUDE.md: 현재 아키텍처 전면 반영 (파이프라인, UI, 인프라, 코딩규칙)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- queue_consumer: extract 완료 시 classify + preview 동시 등록
- classify_worker: _move_to_knowledge() 제거, 파일 원본 위치 유지
- DocumentCard: 좌측 domain별 색상 바 (4px) 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HIGH:
- Lock setup TOTP/NAS endpoints behind _require_setup() guard
(prevented unauthenticated admin 2FA takeover after setup)
- Sanitize upload filename with Path().name + resolve() validation
(prevented path traversal writing outside Inbox)
MEDIUM:
- Add score > 0.01 filter to hybrid search via subquery
(prevented returning irrelevant documents with zero score)
- Implement Inbox → Knowledge file move after classification
(classify_worker now moves files based on ai_domain)
- Add Anthropic Messages API support in _request()
(premium/Claude path now sends correct format and parses
content[0].text instead of choices[0].message.content)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Implement kordoc /parse endpoint (HWP/HWPX/PDF via kordoc lib,
text files direct read, images flagged for OCR)
- Add queue consumer with APScheduler (1min interval, stage chaining
extract→classify→embed, stale item recovery, retry logic)
- Add extract worker (kordoc HTTP call + direct text read)
- Add classify worker (Qwen3.5 AI classification with think-tag
stripping and robust JSON extraction from AI responses)
- Add embed worker (GPU server nomic-embed-text, graceful failure)
- Add DEVONthink migration script with folder mapping for 16 DBs,
dry-run mode, batch commits, and idempotent file_path UNIQUE
- Enhance ai/client.py with strip_thinking() and parse_json_response()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>