# Project INDEX - 2026-09-26 · **main 병합 · 원격 push.** `fix/2026-07-11-review-remediation` 을 main 에 fast-forward 병합(`89c1fe4..2735ce1`, 54커밋) 후 Forgejo(`git.posar.info/cw2007/POSA_Copyrighter`)에 branch·main 모두 push. 병합 시점 verify: ruff clean · 684 passed. - 2026-09-26 · **페르소나 리뷰 권고 ④⑤ — 운영자 신원 + 게시처 성격.** ④ 감사 actor 31곳이 `"rights.ops"` 리터럴이라 "누가 반려했나"에 답이 없었다(법무 2-A 나머지 절반). `operator_identity.py` 의 ContextVar 로 요청 범위 운영자를 걸고 스토어는 `current_actor()` 를 읽는다 — 스토어 메서드 시그니처 무변경, 요청 밖(시드·테스트)은 기본값 유지. 콘솔: 좌측 하단 **운영자** 칸(localStorage), `X-Operator` 헤더(한글은 percent-encode, 서버가 되돌리고 제어문자 제거 — 로그 줄 위조 방지, 40자), 이름 없으면 단건·일괄 판정이 막힌다. 인증이 아니라 기록. E2E 는 init script 로 이름을 넣고 판정. ⑤ 기준 DB 유형 `news_host`(언론·공식)·`scraper_host`(무단 게시처) 신설 — **점수 무관**(판정 데이터 전 가중치 금지), 한 줄 요약이 `웹 완전일치 사본 4곳 (언론·공식 2 · 무단 게시처 1)`, 증거 호스트 칩 옆에 성격 배지(하위 도메인 포함). 테스트: HTTP actor(제어문자 제거 포함) · 스코어러 헤드라인 · 스토어 등록→헤드라인·점수 불변. - 2026-09-25 · **페르소나 리뷰 권고 1~3.** ① 내부 판정 내보내기가 현재 점수·현재 `사용` 표시 대신 **`decisionSnapshot`(판정 시점)** 을 읽는다 — 판정 뒤 표시를 바꾸거나 리스코어돼도 "무엇을 보고 판정했나"가 바뀌지 않는다. 드리프트를 숨기지 않도록 `currentRiskScore`·`scoredVersion` 열 추가, 스냅샷 없는 옛 행은 현재값 + `no-snapshot`. 지원자용에는 둘 다 안 나간다(테스트). ② '위험' 명칭: 큐 열 `위험` → `검토 우선`(툴팁 "침해 판정이 아닙니다"), 점수 구성 패널에 "웹 노출·인물 식별성으로 매긴 검토 순서, 권리 증빙은 반영 안 됨" 한 줄, 가이드에 라이선스 인물 80 vs 무단 그림 50 예시로 명시. 통계 화면의 고위험/저위험 용어는 밴드 이름이라 유지. ③ `docs/operations/operating-modes.md` 신설 — A 전체 egress / B API 호스트만(결과 페이지 fetch 실패는 예외를 삼켜 **사본 비교가 조용히 빠짐**) / C 완전 폐쇄(**키를 둔 채 망만 막으면 매 호출이 실패 → 큐 전체 30/medium**, 키 제거·비상 차단 시 0 + `외부 검색 꺼짐`). offline-install.md 에서 링크. - 2026-09-25 · **페르소나 × 소크라테스 3단논법 리뷰** (`docs/reviews/2026-09-25-persona-socratic-review.md`). 5 페르소나 9 논증. 붕괴 3: 법무 2-A(actor 익명 + **내보내기가 decisionSnapshot 이 아니라 현재 점수·현재 '사용' 표시를 읽음** — 코드 확인), 법무 2-B(점수는 권리 위험이 아니라 노출×식별성 — '위험' 명칭 과장), 결정자 5-A(판정 0건, 가중치 4회 변경 모두 판정 대조 없음). 권고 1~3(스냅샷 내보내기 · 명칭 교정 · 운영 모드 문서)은 S. - 2026-09-25 · **codex 검토 잔여 ③④⑤ + 밀린 부채 4건.** ③ 파일명 충돌 — 제출 id 가 파일 stem 이라 `지원자A/photo.jpg`·`지원자B/photo.png` 가 한 건으로 합쳐져 둘째가 조용히 미심사였다. id 재유도는 하지 않고(기존 판정 보존, `_scan_records` 주석의 이유) `id_collisions()` 로 탐지 → 시드마다 `intakeConflicts` 로 bootstrap 에 싣고 파일당 1회 감사(`Submission file name collision`), 콘솔 투입 상태줄에 주황 경고 목록. 이름을 바꾸면 다음 리로드에서 사라진다. ④ 인용/무관 — 행 버튼 `미사용` 이 `false_positive` 로 가 대표 근거 하나를 고르면 나머지 유효 증거의 점수가 지워졌다 → 버튼을 `사용`/`무관` 으로, `사용` 재클릭은 `pending`(표시 해제, 점수 유지), 옛 `ignored` 는 `irrelevant` 로 보낸다. ⑤ 분모 0 은 `None` → 콘솔 `측정 불가`("미탐 0%" 가 측정한 적 없는 수치였다); 외부 검색이 꺼진(disabled·거버넌스) 케이스는 점수 0 그대로 두되 한 줄 요약에 `외부 검색 꺼짐` — low 가 "확인하고 깨끗함"으로 읽히지 않게. 부채: `operator-review-workflow` 1440×900 13px 넘침 → 겹치지 않는 제출·자동 레인 병합 + 노드 폭 120 → **다이어그램 5종 첫 REBUILD OK**, 카드의 v6 이전 문구("건너뛴 검색은 0점")도 교정; `tests/conftest.py` autouse 로 `OLLAMA_BASE_URL` 을 죽은 포트로 — ollama 가 실제로 떠 있는 상태에서 override 없이 test_http_app 38 passed, PROJECT_MAP 부채 행 삭제; 루트 `VERSION`(0.1.0) 단일 출처 + `rights_filter.version.release_version()`, `/health` 와 기동 로그에 버전·스코어러 버전. 가이드 근거 표시 행 갱신. - 2026-09-23 · **판정 정정 일관성 — codex F4 · 4.2 · 4.3 · 4.5.** ① 내보내기 메모가 "마지막 비어 있지 않은 메모"라 반려(메모)→승인(무메모) 뒤 지원자용 CSV 에 반려 사유가 나갔다 → **마지막 판정의 메모만**(`_latest_decision_memo`, 검색 대기 마커 `HOLD_WAITING_MARKER` 는 벗겨냄). ② 보류·반려→승인 시 그 판정이 만든 주의 후보(`kb-watchlist-`)를 **자동 철회**(`excluded`, 사유 "승인으로 판정 변경 - 자동 철회", 감사 `Watchlist candidate withdrawn`); 운영자가 이미 `승격` 한 확정 항목은 손대지 않고 감사에만 남긴다. ③ 철회·제외된 항목에 걸려 있던 **다른 케이스의 저장된 일치 행**은 `contributed=False` + `withdrawnAt` 로 퇴역시키고 리스코어(`_invalidate_matches_for_entry`, 기존 `exclude_watchlist_entry` 경로에도 적용 — 스코어러는 항목 상태가 아니라 저장된 행을 읽으므로 항목만 바꿔선 점수가 안 움직였다). `status` 컬럼은 CHECK 제약이라 못 쓰고 별도 스탬프. ④ 재보류·재반려가 `excluded`/`confirmed` 항목을 `watchlist`/active 로 되살리지 않는다. 테스트 3(내보내기 메모 · 승인 철회+리스코어+재보류 유지 · 검색 대기 보류). 가이드 승인 행 갱신. - 2026-09-23 · **스코어러 v7 + 검색 대기 보류 — codex F1 차단.** 보류·반려로 자동 생성된 주의 후보(`entryStatus=watchlist`)와의 일치가 확정 항목과 같은 80/high 를 받아, 검색 장애로 보류한 정상 이미지의 재제출이 "알려진 권리 대상"처럼 올라왔다(`internal_analyzer` 는 status 를 실어 보내지만 스코어러가 안 봤다). 두 갈래로 끊었다: ① 스코어러 — `knowledge_entry_status == watchlist` 는 별도 축 **40/20, 상한 40, 단독 medium**("다시 보라"지 "알려진 대상"이 아니다), 확정(승격·수동 등록)만 80. 한 줄 요약도 `주의 후보 일치 0.95` 로 구분. 등록 제외 합이 135 로 상한에 닿을 수 있는 조합(주의 후보+웹 사본+인물+검색+라벨)은 순서를 잃어도 되는 케이스로 문서화. ② 스토어 — `record_decision(held)` 가 `has_coverage_gap()`(실패·일일 한도 미수행, 운영자가 무관 처리한 행 제외)이면 **주의 후보를 만들지 않고** 감사에 `검색 대기 (주의 후보 미생성)` 를 남긴다; 반려는 커버리지와 무관하게 생성. 테스트: 스코어러 5(+헤드라인 1) · 스토어 1(장애 보류 → 항목 0, 이어 반려 → watchlist 1) · 기존 `watchlist_similarity_is_high_risk` 는 인물 30 + 주의 후보 40 = 70 으로 그대로 high. 가이드·MAP 갱신, 다이어그램 태그 v7 재생성. - 2026-09-23 · **codex 기능 검토** (`docs/reviews/2026-09-23-codex-feature-review.md`, 읽기 전용 샌드박스). 검증: F1 보류→주의 후보→재제출 시 80/high **CONFIRMED**(`internal_analyzer.py` 가 watchlist 도 knowledge_entry_id 로 내고 스코어러는 entry_status 를 안 본다) · F4 반려(메모)→승인(무메모) 시 내보내기 메모가 이전 반려 메모 **CONFIRMED**(`_latest_decision_memo` 가 마지막 비어있지 않은 메모를 고른다) · F2 '미사용' 버튼이 오탐과 구분 없이 점수 제거 **PLAUSIBLE**(UI 는 사용/미사용 2버튼, 서버 상태는 4종) · F6 파일명 stem 충돌 CONFIRMED(기존 문서화 한계) · 통계 분모 0 → 0% 표시 CONFIRMED(기존 부채). 채택 순위: ① 보류 사유 분리 + 주의 후보의 80점 자동 승격 차단 ② 판정 정정 일관성(메모·파생 기준) ③ 투입 충돌 감지 ④ 인용/무관/오탐 분리 ⑤ 검색 완결성 표시. 미채택: 출처별 신뢰도 자동 학습(codex 도 보류 권고), 지원자 통지 연동(L, 외부 시스템). - 2026-09-23 · **심사 가이드 다이어그램** — `docs/diagrams/review-guide.html` (archify workflow v2, 5번째 다이어그램). 문서의 6단계를 3레인(읽기·근거 확인 / 판정 / 기준 DB 에 남는 것)으로: 한 줄 요약 → 점수 구성 → 증거 열기 → 근거 표시 → 판정 → 다음 미심사, 분기는 미수행 하한(→ 보류 후 재분석)과 판정 뒤 DB(주의 후보 자동 · 클리어 기준 등록 수동). 첫 안은 4레인·3항목 카드로 1440×900 에서 127px 넘쳐 레인 병합·제목 한 줄·카드 2항목으로 압축(노드 폭 148 로 종횡비 1.52 충족). validate 9/9 showcase · visual-check 4뷰포트 containment pass · manifest 5종. rebuild 스크립트·manifest 갱신기에 등록, PROJECT_MAP 표 추가. - 2026-09-23 · **심사 가이드 v2** — `docs/operations/review-guide.md`. 기존 `operator-review-workflow` 다이어그램은 시스템 흐름(폴더→분석→채점)이라 "어떻게 심사하고 무엇을 보는가"에 답하지 못했다. 운영자 관점으로 다시 썼다: 한 건 심사 6단계(한 줄 읽기 → 점수 구성 → 동일→부분→페이지 순 증거 → 사용/무관/오탐 표시 → A/H/R → N), 한 줄 요약의 신호 9종과 판정 무게(라벨은 권리와 무관, 미수행은 안전 아님), 승인/보류/반려 기준과 **뒤에 일어나는 일**(보류·반려 → 주의 후보, 승인은 DB 에 안 남으니 `클리어 기준 등록` 별도), 반려 사유 프리셋, 키보드, 일괄 처리, 헷갈리는 것 5개, 종료 루틴(내보내기·통계·임계 재검토). 콘솔 라벨은 실제 index.html 문자열로 대조. - 2026-09-22 · **한 줄 요약(headline) — 운영자의 첫 1초를 사유 원문 덤프에서 사실·규모로.** 코퍼스 DB 로 실 콘솔을 띄워 보니 큐의 '상위 근거' 칸이 `구글 visually s... 구글 페이지 with ...`(사유 원문 둘을 잘라 붙인 것)이었고 증거 제목도 `구글 full image match found` 로 절반 영어였다. 스코어러가 점수 계산과 별개로 집계(`_Tally`)해 `riskBreakdown.headline` 에 `웹 완전일치 사본 4곳 · 식별 가능 인물 · 이름 있는 검색 승격 2건 · 라벨 9개` 를 싣는다 — 순서는 예산표, 웹은 가장 강한 뷰만 **호스트 수(곳)** 로, 등록 일치·클리어 이력·미수행·실패·신뢰 출처 제외까지 한 줄. 큐 행과 케이스 요약 보드의 첫 줄이 이것으로 바뀌고 옛 케이스(headline 없음)만 예전 배치. `formatReason` 에 Vision 매치 4종·검색결과 사본·기준 DB 유사도 한국어 매핑 추가. 실렌더(claude-in-chrome, 코퍼스 20장)로 큐·케이스 확인 — 점수·밴드 변화 없음(SCORER_VERSION 유지). - 2026-09-22 · **스코어러 v6 — 외부 검색 미수행의 조용한 통과를 막는다.** 배포 판단에서 파일럿 전 유일한 차단으로 꼽은 구멍: 네이버 일일 한도 100 = 20장이라 21장째부터 검색이 `SEARCH_SKIPPED` 0점·무사유로 빠져 점수가 **낮게** 나왔다(키 만료·비상 차단도 같은 경로). 정책 거절 사유 5종 중 하루 안에서 케이스마다 달라지고 어디에도 안 보이는 하나(`usage limit reached`)만 v5 의 실패 하한 30 에 합류시키고 사유에 ` 미수행 — <사유>` 를 남긴다. `disabled` 는 키 미설정 배포와 비상 차단이 같은 사유를 쓰고 둘 다 콘솔 providerState 에 보이므로 그대로 0점·무사유(하한을 걸면 키 없는 배포의 큐 전체가 medium 이 된다 — 기본 스토어 테스트가 잡아냈다); 거버넌스 스위치 셋(compliance · metadata logging · online mode)도 배포 전체에 균일해 0점·무사유. 라운드트립은 신선 점수를 재현(테스트). 다이어그램 태그 v6 재생성. - 2026-09-22 · **스코어러 v5 — 축 예산을 총합 기준으로 재배분, 실이미지 100점 포화 20/20 → 0/20.** 첫 실측이 보인 원인은 축 예산의 **합**(245 vs 상한 100)이었고, 캡 없는 두 축만 캡하는 시뮬레이션(80/50 · 60/40)으로는 20/20 이 그대로 100 이라 축 하나 손보기로는 안 됐다. 새 예산: 등록 권리 대상 일치 80(단독 high) · 웹 존재 40(검색결과 사본 유사도·Vision 매치·CSE 페이지가 제공자 무관 **한 풀** — 같은 사실의 다른 뷰, 뷰 강도로만 40/30/10 차등) · 인물 30/10 · 이름 있는 검색 승격 15 · 라벨 10 → 등록 제외 최대 95. 외부 검색 실패는 +30/행 가산에서 medium **하한**으로(네이버 장애 하루가 웹에 있는 모든 이미지를 high 로 밀던 경로 차단). 검색결과 사본 행은 `knowledge_entry_id` 부재 + `url`/`source_evidence_ids` 로 판별(둘 다 라운드트립 생존). 격자 432: high 92→75%, 포화 80→44%(남은 포화는 전부 등록 강한 일치 조합), 웹 신호만 high 13/24→**0/24** — v2 원칙 복원. 실이미지 20장 리스코어: 100점 20→**0**, 구별 점수 1→4(75/80/92/95), 밴드는 전부 high 유지 — 전부 웹에 있는 인물 사진이라 그것이 맞는 답이고 medium/low 임계는 여전히 미검증(저위험 기대 이미지 0장). 스코어러 테스트 45(신규 10) · 다른 파일의 인물 단독 35 고정값 4곳은 상수 참조로 · 다이어그램 spec 3종 v5(+카드 문구) · `calibration-corpus.md` 전후표. 다이어그램 4종 재생성(archify 2.17.0-dev.1) — `operator-review-workflow` 는 HEAD spec 그대로 새 `desktop-readability` 검사에 걸려(viewBox 1112 > 1085, 7px 라벨 투영 5.89px) 노드 폭 132→124 로 통과시켰으나, 그만큼 확대되어 1440×900 에서 세로 13px 넘침(visual-check containment fail; HEAD 는 같은 뷰포트에서 readability fail). 검증 9/9·showcase 는 통과, 사실 게이트·매니페스트도 통과 — 잔여는 툴체인 드리프트로 기록. - 2026-09-22 · **실이미지 20장 첫 실측 — 20/20 이 100점/high, 포화율 100%.** 이 프로젝트 최초의 실이미지 코퍼스(`sample/` 20장 → `data/corpus.sqlite3`, 증거 1,615행, Vision 809 + 네이버 692). 8/8 평가는 합성 격자에서 high 77.7%·포화 56.2%였는데 **실이미지에서는 전부 상한**이다. 탐지 자체는 정상 — 15/20 은 웹에서 **완전일치(similarity 1.00) 사본**이 발견됐고 4장은 근사일치다. 문제는 밴드가 아니라 **순서가 없다는 것**이다(큐 정렬이 riskScore 내림차순인데 전부 100이라 no-op). 근본 원인은 축별 예산의 합이다 — 인물 35 + 웹게시 60 + 엔티티 20 = **115 로 이미 상한 초과**이고, 여기에 캡이 없는 두 축(검색결과 이미지 유사도 장당 평균 216점, 네이버 승격 평균 70점)이 더해진다. 최대 245점 대 상한 100. **캡 없는 두 축에 캡을 씌워도(지문 80/네이버 50, 60/40 모두 시뮬레이션) 20/20 이 그대로 100** — v2~v4 가 축을 하나씩 캡했지만 **합을 한 번도 보지 않았다.** 검색결과 유사도가 +80/건 무제한인 것은 v2 가 고친 것과 같은 오류다(같은 이미지가 웹에 있다는 **하나의 사실**을 URL 수만큼 셈). 코퍼스 한계도 명시: 20장 전부 웹 유통 인물 사진이라 **low 기대군이 0건** — 이 코퍼스로는 medium/low 임계를 영원히 검증할 수 없다(`lowBandRejectionRate` 분모 0). 첫 시도는 `.env` 로딩 누락으로 프로바이더가 전부 비활성이었고(외부 호출 0콜) 그 무효 런이 부수적으로 알려준 것 — **웹 증거 0건이어도 인물만으로 35/medium** 이고 `external_skipped` 는 점수·사유 어디에도 안 뜬다(키 만료·쿼터 고갈이 조용히 지나간다). `sample/`·`corpus/` 를 gitignore 에 추가(저작권 이미지 커밋 방지) - 2026-09-22 · **캘리브레이션 측정 도구 신설 + 구조적 거동 첫 실측.** `SCORER_VERSION` 이 v1→v4 로 갔는데 v3(제공자별 풀링)·v4(신뢰 출처)는 분포를 한 번도 측정하지 않고 나갔다 — 8/8 평가의 224조합 하네스가 저장소에 없고(그 문서의 두 수치 174/224=77.7% 와 82.6% 가 서로 안 맞아 격자가 달랐음을 시사) 과거와 비교 가능한 재현이 불가능해, 격자를 명시적으로 고정한 `scripts/score_distribution.py`(432조합)를 새 기준선으로 만들었다. **실측 결과(v4)**: 신뢰 출처 미등록 시 high 92% · 100점 포화 80% · **웹 신호만으로 high 13/24**, 신뢰 출처 등록 시 high 63% · 포화 38% · 웹 전용 high 0/24. 드러난 것 — v2 가 세운 "웹 존재만으로 high 불가" 원칙이 깨져 있다(presence 풀 60 + entity 풀 20 = 80, 두 풀의 **합**은 캡되지 않음), 그리고 **v3 의 제공자별 풀링이 이를 키웠다**(웹 전용 high 4/12 → 9/12, 전체 high 88% → 97%). 대응 후보(웹 유래 총합 상한)는 미적용 — 적용하면 또 한 번의 미측정 변경이 되므로 결정 후 진행한다. 함께 `scripts/sweep_thresholds.py`(판정 시점 `decisionSnapshot.riskScore` 기준 임계 스윕, 가짜 판정 6건으로 동작 검증)와 `docs/operations/calibration-corpus.md`(이미지 구성·쿼터 준비·투입·판정·측정 절차) 신설. **실이미지 코퍼스와 판정은 사람이 해야 한다** — 판정을 내가 만들면 측정이 성립하지 않는다. **ruff clean · pytest 645 passed** - 2026-09-21 · **반려 키보드 완결** — 승인·보류는 1타인데 반려만 메모 필수라 손이 마우스로 갔다(8/8 평가 P2-10). `R` 이 메모 필드에 포커스만 주고, 입력 중에는 전역 단축키가 막혀서 거기서부터는 **마우스로만 제출**할 수 있는 구조였다 — 법적으로 가장 중요한 경로가 가장 느렸다. `R` → 사유 선택 모드(프리셋 1~9 번호 표시) → 숫자 1타로 반려 완료, 자유 서술은 메모 안에서 `Ctrl+Enter`. 화면의 번호가 곧 단축키가 되도록 프리셋 칩에 키 배지를 붙였다(외울 필요 없음). **숫자는 사유 선택 모드 안에서만 의미를 갖는다** — 모드 밖에서 잘못 누른 키 하나로 반려가 나가면 안 되고, 그 가드 순서를 테스트로 고정했다. 실렌더(Playwright): `R`→`2` 로 `{"decision":"rejected","memo":"라이선스 확인 불가로 반려"}`, 메모+`Ctrl+Enter` 로 `{"decision":"rejected","memo":"자유 서술 사유"}`, 콘솔 에러 0. 반려 후 승인·보류와 같이 다음 미심사 케이스로 이동한다. **ruff clean · pytest 645 passed**(정적 3건) - 2026-09-21 · **점수 구성 패널** — "증거 51개인데 왜 35점"에 답한다. 스코어러가 풀 상한(v2/v3)과 신뢰 출처 제외(v4)를 갖게 되면서 증거 개수와 점수의 간극이 벌어졌는데, 그 이유가 화면 어디에도 없었다 — 제가 그 간극을 만든 장본인이라 자체 부채였다. `ScoreResult.breakdown` 을 신설해 종류별 기여·풀 상한에 삼켜진 점수·제외된 행을 기록하고, 제출물에 `riskBreakdown` 으로 영속화한 뒤 증거 요약과 증거 목록 **사이**(그 질문이 생기는 지점)에 렌더한다. 실렌더 예: `지식DB 지문 매칭 80 / 웹 게시(google) 60 (원래 125 · 상한 60) / 웹 엔티티 라벨 20 (원래 114 · 상한 20) / 신뢰 출처 posar.info 3행 제외 / 합계 115점이 상한 100에 걸렸습니다`. **점수는 하나도 안 바뀐다** — 기존 스코어러 테스트 31건이 그대로 통과하므로 `SCORER_VERSION` 은 v4 유지(가중치·버킷·밴드 불변). 분해 합계가 항상 점수와 일치하는지도 테스트로 고정했다(분해가 다른 이야기를 하면 분해가 아니라 또 하나의 주장이 된다). **ruff clean · pytest 642 passed**(스코어러 5 + 정적 1) - 2026-09-21 · **증거 일괄 표시** (`POST /api/evidence/status-batch`) — 운영자 되먹임이 순증 노동이던 문제를 끝낸다. 8/8 평가 기준으로 실 DB 제출물 하나를 100→35 로 내리려면 google 증거 **51행을 개별 클릭**해야 했다(단건 API 만 존재). 증거 그룹 헤더의 개수 옆에 `전부 무관` 1타를 붙였다. 서버는 **한 트랜잭션 · 리스코어 1회 · 감사 이벤트 1건** — 51행이면 감사도 51건이 되어 500행 캡을 한 번의 클릭으로 먹는다. 행별 who/when/note 는 그대로 남으므로 개별 행이 설명 불가가 되지는 않는다. 남의 증거 id 가 섞이면 **아무것도 표시하지 않고 전체 실패**(절반만 반영되면 운영자가 무엇이 됐는지 알 수 없다), 상한 500(일괄 판정 200 과 단위가 다름 — 그쪽은 케이스 수, 이쪽은 한 케이스의 증거 행 수). 그룹 액션은 `irrelevant`(무관)로 보낸다 — 행별 "미사용"은 `false_positive`(오탐)에 매핑되는데, 제공자 결과 전체를 오탐이라 단정하는 건 과한 주장이다. 실렌더(Playwright 1440×900): 51행 그룹에서 1타로 `{"status":"irrelevant","evidence_ids":[51개]}` 전송, 콘솔 에러 0. **ruff clean · pytest 636 passed** - 2026-09-21 · **신뢰 출처** (`SCORER_VERSION` v3 → v4) — 자사·계약처 도메인에서 발견된 웹 탐지 행을 0점 처리한다. 8/8 평가가 "목록만 정해지면 `cleared_reference` 패턴으로 바로 붙는다"고 남긴 항목이고, 전제였던 host 파괴는 9/18 에 고쳤다. 저장 위치는 **지식DB**(사용자 결정) — `trusted_host` 항목의 이름이 호스트이고 별칭이 같은 주체의 다른 도메인이다. 신뢰 철회에 별도 스위치를 두지 않고 지식 항목의 기존 활성/비활성 수명주기를 그대로 쓴다. **행 단위 제외**라 자사 사본과 스크래퍼 게시가 같이 있으면 후자는 그대로 센다(실측: 신뢰 등록 전 80/high → 후 35/medium). 서브도메인은 접미사 매치로 같은 주체 취급(`cdn.posar.info`), `posar.info.evil.test` 같은 유사 도메인은 불일치, 점 없는 값(`com`)은 무시. 신선한 CSE 행은 `host` 키가 없으므로 `domain`(displayLink)에서 해석한다. 제외된 행은 점수에서 빠지되 **사유로 남는다** — 증거 목록에 그 행이 그대로 보이므로 왜 조용한지 말해줘야 한다. 다이어그램 사실 게이트가 v3→v4 를 **3개 다이어그램에서 자동으로 잡아** 스펙 수정 + 재생성으로 이어졌다(게이트 신설 이틀 만의 첫 실사용). **ruff clean · pytest 632 passed**(스코어러 7 + 스토어 3) - 2026-09-21 · **선례 승계** — 재제출을 다시 심사하지 않게 한다. 8/8 실효성 평가의 미해결 결론("검색·기록 노동은 줄여주나 심사 건수는 못 줄인다")을 직접 겨냥한 첫 기능이다. 중복 탐지는 이 도구에서 실증으로 검증된 유일한 판별 기능인데(재저장·80%/50%축소·밝기 변형 24/24 그룹핑, 같은 24건 SHA-256 일치 0건), 서버가 계산해 내려보내던 `priorDecisions`·`conflicting` 이 **큐 배지의 title 속성에만** 있었다 — 마우스를 올린 사람만 보는 자리라 아무것도 막지 못했다. 서버 변경 없이 콘솔만 고쳤다: 배지 본문에 선례 표시, 판정 패널(결정 메모 바로 위)에 선례 배너 + `선례 승계` 1타. 승계는 별도 경로를 만들지 않고 메모를 채워 기존 `setDecision` 을 타므로 반려 메모 필수·판정 스냅샷·감사 이벤트가 그대로 유지된다. **판정 불일치(conflicting)면 승계를 제안하지 않는다** — 그때가 사람이 봐야 하는 상황이다. 실렌더 확인(Playwright, 1440×900): 세 상태(승계 가능/불일치/선례 없음) 배너·버튼 유무가 의도대로였고 승계 클릭 시 `{"decision":"rejected","memo":"선례 승계: 동일 이미지 SUB-OLD1, SUB-OLD2 와 같은 판정"}` 전송, 콘솔 에러 0. **ruff clean · pytest 622 passed**(정적 3건 추가) - 2026-09-21 · 다이어그램 용도를 **내부 문서로 확정**하고 그 기준으로 부채를 재판정. 한글 폰트 미번들·영어 뷰어 UI 는 archify 한계라 spec 으로 못 고치는데, 사내 머신에는 한글 폰트가 있으므로 low 로 내렸다(인수물로 승격하면 둘이 먼저 blocker — PROJECT_MAP 다이어그램 절에 승격 조건으로 명시). 이어서 **코드↔그림 리터럴 드리프트 게이트** `tests/test_diagram_facts.py` 19건 신설 — 그림에 하드카피된 코드 유래 값(SCORER_VERSION · SCHEMA_VERSION · 믹스인 8개 · `app-*.js` 15개 · 바디 10MB · 인증 10회/60초 · 프롬프트 12행 · 풀 상한 60/20 · 기본 모델 · 런타임 의존성 3종)을 실제 코드 상수와 대조하고, 상수가 아닌 "지문 유사도 0.9+ → 80점" 은 실제로 채점해 확인한다. SCORER_VERSION v3→v4 와 MAX_PROMPT_EVIDENCE 12→16 을 실제로 넣어 **영향받는 세 다이어그램 전부에서 차단됨**을 실증. 발견 가능성도 정리 — README 문서 색인에 등재, `docs/diagrams/README.md` 신설(폴더 안에 '직접 수정 금지'·재생성 명령·CSP 주의). **ruff clean · pytest 619 passed**(사실 게이트 +19) - 2026-09-21 · 다이어그램 감사 후 **드리프트 게이트 신설**. 에이전트 6축 감사가 주간 한도로 절반(2축)만 완주했고 검증 단계도 죽어, 완주분 15건을 저널에서 복구한 뒤 결정적 주장은 직접 재검증했다. 확정된 구멍 — 그림 4종의 재생성 절차가 **문서대로는 실행 불가**(`archify` 실행파일이 PATH 에 없음), 소스↔산출물 동기화를 보는 장치 0개, 사이드카 24개가 개발 머신 절대경로를 담은 채 미추적·비ignore, 임베드 폰트에 **한글 구간 0건**(`U+AC00` 매치 0 · 폴백은 중국어 폰트뿐). 실수정: `scripts/rebuild-diagrams.ps1`(validate→deliver→visual-check→manifest, 실행 확인)과 `scripts/update_diagram_manifest.py`, 수신증 `docs/diagrams/manifest.json`, 게이트 `tests/test_diagram_sync.py` 14건(HTML 직접 수정·spec 만 수정 두 시나리오 모두 실제로 차단됨을 실증), `.gitignore` 에 사이드카 제외(미추적 24→0), `.gitattributes` 에 `linguist-generated -diff merge=binary`. 재생성해도 4종 sha256 불변 — 바이트 결정성 재확인. 한글 폰트·영어 뷰어 UI·architecture 좌표 하드코딩·lifecycle 손튜닝 8개소는 PROJECT_MAP 부채 표에 등재. **ruff clean · pytest 600 passed**(게이트 +14) - 2026-09-19 · 다이어그램 4종으로 확장 — `system-architecture`(폐쇄망 경계·SSRF 가드 앞 호출 게이트), `operator-review-workflow`(4레인, 비상 차단 시 심사 계속 경로), `submission-lifecycle`(분석 실패·보류가 분석으로 복귀하는 두 고리)를 신설하고 기존 `evidence-dataflow` 포함 4종의 **범례 항목**을 한글화했다(렌더러 기본값이 "User UI / Agent logic / policy·PII" 같은 타 도메인 문구였다). **뷰어 크롬은 영어로 남는다** — `locale` enum 이 en/zh-CN 뿐이고 템플릿이 `` 을 하드코딩하므로 Light/Dark·Present·Export·범례 제목("Legend")은 저자가 바꿀 수 없다. 9/21 감사에서 확인. 작성 중 내린 고도 판단: 아키텍처에서는 analyzer/scorer 를 한 컴포넌트로 합쳤고(세부는 dataflow 담당), 라이프사이클에서 "재분석 요청"은 decisionStatus 가 아닌 운영자 액션이라 상태에서 제외해 복귀 엣지를 하나로 줄였다. 4종 모두 archify showcase 통과(아티팩트 검사 9/9 · 합성 오류 0 · 경고 0) + visual-check 통과(1440×900 · 2048×1320 각 Light/Dark, 오버플로 0). 수신증 — dataflow html a62f0f5c / architecture f5a4b6e1 / workflow 402914a3 / lifecycle 62e934a3 - 2026-09-19 · `docs/diagrams/evidence-dataflow.html` 신설 — 증거 데이터 흐름(수집 → 경계 검사 → 정규화·채점 → 영속화 → 소비) 자체 완결 다이어그램. 9/18 리뷰 수정의 불변식을 그림으로 고정한다: **제공자별 풀·버킷**(RiskScorer v3), **라운드트립이 원 점수를 재현**(`_evidence_payload` ↔ `_domain_evidence_from_ui`), **host 는 저장 시 해석·복원 시 우선 반환**, 지식DB 지문 매칭이 풀링 밖 유일한 high 승격 경로. 프로덕션 LLM 경로가 영속화 payload 를 복원해 쓴다는 사실(`store_operations.py:487`, `EvidenceEnricher` 는 테스트 전용)도 반영. archify showcase 검증 통과(아티팩트 검사 9/9 · 합성 오류 0 · 경고 0), 브라우저 증거 통과(1440×900 · 2048×1320 각 Light/Dark 4컷, 오버플로 0, 최소 투영 텍스트 6.71px ≥ 6px). spec sha256 42c334cd · html sha256 268f3352 (822,655 B). CDN·원격 fetch 없음 — 폐쇄망 규칙 준수 - 2026-09-18 · `/code-review` 15건 전수 처리 (**헤드라인 1건은 반증**). 반증: "CSE 웹 증거가 라운드트립 후 0→45점" — 리뷰가 `match` 키 없는 합성 Evidence 로 재현한 것이고, 실제 CSE 웹 결과는 `google_custom_search.py:301` 에서 `match="page"` 를 달고 오며 `matchType` 이 충실히 왕복한다(신선/복구 모두 35점). 직렬화 불변식 위반 없음. 대신 그 조사에서 **리뷰가 놓친 라이브 버그**가 나왔다: `counted_web_buckets` 가 제공자를 안 가려 Vision 이 `match="page"` 를 먼저 보고하면 **CSE 웹 히트가 점수도 사유도 없이 통째로 버려졌다**. 실수정 — **점수 v2 → v3**(풀·버킷을 제공자별로: Vision 자기 상관 뷰 4개는 60/medium 유지, Vision full + CSE page 는 45+35=80/high 복원 · 도달 불가였던 `category` 탈출구 제거 + 미등록 유명인 미탐은 의도된 트레이드임을 명시, 승격 경로는 지식DB/FINGERPRINT), **저장·복구 5종**(`_result_host` 가 자기 host 를 안 읽어 재직렬화 시 API 이름으로 퇴행하던 잠재 버그 + 점 없는 토큰 호스트 거부 · 복구가 남긴 고아 `faceCrops` 참조를 재생성/정리 · `assetMissing` 해제 경로 신설로 두 번째 소실도 감사에 남김 · 플래그+감사를 한 트랜잭션으로 · 스코어러 마이그레이션 밴드 변경에 `scoredVersion` 스탬프 + 감사 1회), **콘솔 4종**(긴 CDN 호스트가 제목을 세로 컬럼으로 만든 flex 버그 — 실측 360px 51×95px→246×19px, **1280px 9×380px→205×19px 로 폰 전용이 아니었음** · 20ch 로 잘린 호스트를 툴팁에 · `bloggerLink` 폴백 누락 · 보존 케이스 설명 표시), **PROMPT_VERSION ko-v4-structured-host**(실모델 프로브 qwen2.5:7b — grounded 3/dropped 0/verified True, 반향 없음). 테스트 +14건. **ruff clean · pytest 586 passed**. 발견된 기존 결함 1건은 미수정 기록: 로컬 ollama 가 떠 있으면 `test_http_app.py` 3건이 실호출 타임아웃으로 실패한다(기본 생성 store 가 `127.0.0.1:11434` 기본값에 붙음 — clean HEAD 에서도 동일). 커밋 b0aa4c5 / 4ba83eb / e38dad8 - 2026-08-09 · P1 2차 — **게시 호스트 복구**. P1 3번의 전제("호스트는 저장돼 있고 점수만 안 쓴다")가 틀렸다: CSE 가 실호스트를 `data["domain"]` 에 넣는데 `store_serialization.py:298` 이 `provider` 를 우선해 **저장 시점에 덮어썼고**, Vision·Naver 는 URL 만 준다. 결과적으로 LLM 프롬프트 12줄이 전부 `domain=google` 이라 "어디에 떠 있나"를 모델도 운영자도 못 봤다. `domain` 은 provider 커버리지·CSE 시그니처 매칭의 키라 재사용 불가 → **`host` 별도 키 신설**(`_result_host`: CSE displayLink 또는 URL 파싱, www 제거·소문자). 점수 무영향·`_evidence_id` 무변화(도메인 객체 해싱이라 payload 키 추가가 id 를 안 바꿈). `PROMPT_DATA_FIELDS` 의 `domain`→`host`, 콘솔 증거 카드에 monospace `host-chip`(레거시 행은 `evidenceHost()` 가 저장 URL 에서 파생 — 마이그레이션 불요). **실렌더 검증**(실 DB, 칩 82개 전부 유효 호스트, 콘솔 에러 0)에서만 드러난 결함 2건 수정: 긴 CDN 호스트가 제목을 한 글자씩 세로로 깨뜨림 → 칩 20ch 상한 + 제목 `flex:1 1 auto`, 이후 짧은 호스트까지 잘림 → 칩 `flex:0 0 auto` 고정. PROVENANCE 배점은 **보류**(카테고리가 2종뿐이라 라벨이 사실상 "geometry AND 자막"이고, "새 신호는 0점 출시 후 상관 확인" 결정과 충돌 — 한계를 `provenance.py` 상수 옆에 기록). **pytest 574 passed**(착수 전 570) - 2026-08-09 · P1 1차 — 판별력 복구를 위한 신호 풀 도입, **`SCORER_VERSION` v1 → v2** — 포화 동인이 둘이었다: `category` 없는 raw Vision 라벨이 이름별 버킷으로 무한 누적(`WEB_ENTITY_MAX_POINTS=20`, medium 미만으로 캡)되고, full/partial/page/visual 이 **하나의 사실을 4개 상관된 뷰로** 보고하는데 합산돼 125→100 포화(`WEB_PRESENCE_MAX_POINTS=60`, high 미만으로 캡). `category`(character/celebrity/work) 엔티티는 권리 분류이므로 풀에서 제외하고 +45 유지. 원칙: **웹에 존재한다는 사실만으로는 high 가 되지 않고, 두 번째 종류의 신호가 필요하다.** 224조합 재측정 — 웹 신호만으로 high 가 되는 조합 **2/7 → 0/7**, 100점 포화 66.5%→51.3%(전체 high 비율 82.6%→76.3%은 격자가 균등샘플이라 대리값이 아님을 명기). 웹에 없는 오리지널·cleared 자산이 100/high → 20/low 로 교정. 라운드트립 버킷 보존 테스트는 캡에 가려지지 않도록 저신뢰도로 재작성. **pytest 570 passed**(착수 전 565) → [평가 리포트 6절](reviews/2026-08-08-utility-assessment.md) - 2026-08-08 · 기능 실효성 평가(6축 조사 + 축별 적대적 반증, 에이전트 12) 후 P0 4종 수정 — 결론은 **"검색·기록 노동은 줄여주나 심사 건수는 못 줄인다"**(신호조합 224개 전수 시 high 77.7%·low 3.6%, 지배 동인은 match 스택이 아니라 **web entity 누적** — 이미지 매칭 0건이어도 entity 3개면 100/high, 웹에 없는 오리지널도 66/medium). 실수정: **폴더 정리가 판정 기록을 지우던 경로 차단**(판정 완료 가드 + `assetMissing` 1회 마킹; 실증으로 export 1행→0행 확인됨), **비상 차단 스위치 실배선**(`_external_calls_blocked` 신설 → 자동 Naver/CSE/Vision/얼굴크롭 5개 경로; 이전엔 눌러도 외부 호출 5회 동일), **생체 크롭의 보존기간 초과 생존 차단**(스냅샷 제외 + 레거시 아카이브 복구 스킵 — 복구가 mtime을 리셋해 90일을 재시작시켰음), **오프라인 설치 문서의 계약 위반 모델 정정**(0.5b→7b + ollama 바이너리·GPU 전제·거짓 disable 서술). DB 6건은 첫 커밋보다 2주 앞선 구빌드 스냅샷이라 검증 코퍼스가 아님을 확인. **pytest 565 passed**(착수 전 562), 린트 클린 → [docs/reviews/2026-08-08-utility-assessment.md](reviews/2026-08-08-utility-assessment.md) - 2026-08-08 · 프로젝트 분석 후 인프라·구조·보안 정비 — **원격 백업 복구**(20커밋이 로컬에만 있었음: `main` 2 + 작업브랜치 18 → Forgejo push), **단일 verify 게이트 신설**(`verify.ps1` + `pytest.ini` + `ruff.toml`, ruff 0.12.0 dev 핀; 린트 18건 정리), **`app.js` 5,428줄 → `app-*.js` 15개 분할**(순수 라인범위 추출로 바이트 단위 라운드트립 검증, 로드 순서를 정적 테스트로 고정), **보안 2종 실수정**(SSRF DNS-rebinding TOCTOU — 단일 DNS 해석 후 그 답변으로만 연결하는 `_guarded_create_connection`; 실패 인증 스로틀 — 자격증명 제시 실패만 10회/60초, `429`+`Retry-After`), 7/11 체크리스트 재확인(6건은 이미 수정돼 있던 낡은 항목, Batch 8 은 Batch 6 과 중복이라 정리), `CLAUDE.md`·`docs/PROJECT_MAP.md` 신설. E2E 플레이크(업로드 대기 10초 → `UPLOAD_ANALYSIS_TIMEOUT_MS` 30초) 해소. **pytest 562 passed**(착수 전 533/1 failed), 린트 클린. 실렌더 확인: 큐·케이스 심사 정상 렌더, 스크립트 15개 전부 200, 새로고침 후 콘솔 메시지 0 - 2026-07-24 · 멀티에이전트 프로젝트 점검(에이전트 109, 적대적 검증 포함) — pytest 377/379, 런타임 스모크 9/9 통과, 신규 CONFIRMED 43건(HIGH 7) + 7/11 체크리스트 중 3건 실수정 확인 + 기능 제안 상위 5 선정 → [docs/reviews/2026-07-24-project-review.md](reviews/2026-07-24-project-review.md) - 2026-07-26 · 운영자 콘솔 실렌더 검증 + 결함 5종 수정 (`d20e6d6`) — 서버 기동 불가(cp949에서 em-dash `UnicodeEncodeError`로 프로세스 사망, 납품 환경 치명) · 요약이 모델이 못 본 78건을 "연결 근거"로 주장(B단계가 절반만 막았음) · `grounded/droppedClaims` 미영속화 · app.js가 서버 그룹 분류를 자체 매핑으로 덮어씀 · 기본 모델이 클라이언트/배선에서 불일치(미설치 모델 지목). 실렌더 확인: 출처 표기 그룹 자체 섹션 렌더, `[E1,E2...]` 인용 2단 요약, `sourceIds` 78→10, qwen2.5:7b 6.0초. **pytest 534 passed** - 2026-07-25 · C단계: 출처 신호 2종 (`5a9fcc0`) — EXIF/IPTC 권리 표기 추출(화이트리스트 6필드, GPS·시리얼·Make/Model 미판독) + 방송캡처 포렌식(레터박스·자막밴드·프레임크기, 독립 카테고리 2개 이상). 하나의 `EvidenceSource.PROVENANCE`·하나의 GUI 그룹으로 배관 12곳 비용 1회 지불. **둘 다 0점 라벨**(캘리브레이션 전). 실측: 실제 이미지 6장에서 캡처 오탐 0·권리메타 히트 0(정밀도 확인, 가치는 이 코퍼스에서 미입증). XMP/C2PA는 `defusedxml` 미반입으로 제외. **pytest 531 passed** - 2026-07-25 · 실모델 프로브로 형식 예시 반향 결함 발견·차단 (`6bbaaf6`) — qwen2.5:0.5b가 프롬프트의 형식 예시를 그대로 출력하며 실제 라벨 12개를 전부 인용해 집합 포함 검사를 통과. 가짜 transport 테스트로는 불가능한 발견. 기본 모델을 실측으로 `qwen2.5:7b` 확정(0.5b 55.7s 계약위반 / 7b 8.8s 준수 / qwen3:8b 35.7s 준수) - 2026-07-25 · B단계: LLM을 요약기에서 증거 판독기로 (`6be263a`) — 입력 화이트리스트 9필드+상위 12건 상한(`promotion_reason` 제외), `format=json` 구조화 인용 + 라벨 집합 포함 검사로 미인용 claim 폐기, 기본 모델 0.5B→3B 및 `num_ctx`/`num_predict`/`keep_alive`/`timeout` 노출, 시드 LLM 호출을 `_write_lock` 밖으로(시드당 20건 상한). 잠금 이동이 만든 provider 커버리지 회귀(요약 존재하는데 `not_run` 보고)를 나레이션 후 재계산으로 수정. **pytest 515 passed** - 2026-07-25 · 실사용 투입 전 T0 4종 + 임베딩 정책 개정 구현 — (T0-1) `initialRiskScore`/`initialRiskBand`/`scorerVersion` 각인, (T0-2) YuNet 얼굴검출 교체 + 인물 등급(identifiable/background) 도입 및 라운드트립 보존, (T0-3) 판정 시점 피처 스냅샷(라벨 순환 차단), (T0-4) 모델·프롬프트 버전 + `latency_ms` 스탬프. Scene Embedding Policy 신설로 임베딩 금지 범위를 생체 식별로 축소(얼굴 임베딩 금지는 landmark·descriptor 추가로 강화). YuNet 가중치는 `models/`(gitignore) + `offline-install.md`에 sha256 반입 절차. **pytest 502 passed**(착수 전 463). 실측: 6장 기준 검출 378ms(YuNet) vs 654ms(Haar), 2장에서 등급 판정이 갈림 - 2026-07-25 · AI 기능 확장 브레인스토밍(에이전트 14: 5렌즈 발산 30건 → 적대적 실현가능성 검증 6건 REFUTED → 심사 3인 채점) — 1위 「LLM 예산·계측·입력 경계」 7.90(만장일치). 검증 중 실측으로 확인: 판정 라벨 0건·`operatorStatus` 292행 전부 None·밴드 high 4/medium 2/low 0(4건 100점 포화)·`_perceptual_hash`는 pHash가 아닌 8x8 평균해시. 문서의 WAL 미설정 주장은 검증 결과 오류로 정정(이미 적용됨) → [docs/brainstorms/2026-07-25-ai-capability-expansion-brainstorm.md](brainstorms/2026-07-25-ai-capability-expansion-brainstorm.md) - 2026-07-24 · 위 점검의 전체 remediation + 신규 기능 5종 구현 — HIGH 7·MEDIUM 15·LOW 21 및 F1~F5(키보드 심사·일괄 판정/메모 프리셋·지문 중복 인덱스·판정 내보내기·백업 복구) 완료. cv2 환경 드리프트 복구(OpenCV 5.0.0 제거). **pytest 457 passed**(착수 전 377/2 failed), 실서버 스모크 및 백업 복구 왕복 검증. GUI 스펙의 "일괄 승인/반려 금지(v1)" 제약은 가드레일 조건부로 개정 이력 남기고 해제 → [docs/reviews/2026-07-24-implementation-checklist.md](reviews/2026-07-24-implementation-checklist.md)