P1 3번("매칭 호스트 기반 신뢰출처 구분")을 착수하며 전제를 검증했더니 틀렸다.
호스트는 "이미 저장돼 있고 점수만 안 쓰는" 게 아니라 저장 시점에 파괴되고
있었다.
google_custom_search.py:240,296 이 실호스트(displayLink)를 data["domain"] 에
넣는데, store_serialization.py:298 이 data["provider"] 를 우선하므로 그 값이
API 이름으로 덮어써진다. Vision·Naver 는 호스트를 아예 안 넣고 URL 만 준다.
결과적으로 저장된 모든 행의 domain 이 "google" 이고, LLM 프롬프트 12줄이 전부
domain=google 이었다 — 저작권 심사에서 가장 결정적인 "어디에 떠 있나"를 모델도
운영자도 볼 수 없었다. 같은 케이스의 payload 55행 중 41행이 url 을 갖고 있는데도.
domain 필드는 재사용할 수 없다. provider 커버리지 동기화와 CSE 쿼리 시그니처
매칭이 그 값을 키로 쓰고(store_serialization.py:195, 599) 콘솔도 provider
라벨로 렌더한다. 그래서 host 를 별도 키로 신설했다.
_result_host(): data["domain"] 이 provider 와 다르면 그것을, 아니면 결과/페이지
URL 에서 호스트를 파싱한다. www 제거·소문자화. 파싱 실패는 "호스트 없음"이지
영속화 실패가 아니다.
점수에는 영향이 없다 — RiskScorer 는 domain 도 host 도 읽지 않는다. 증거 id 도
안 바뀐다: _evidence_id 는 도메인 객체의 data 를 해싱하고 payload 를 해싱하지
않으므로 payload 키 추가가 id 를 움직이지 않는다.
PROMPT_DATA_FIELDS 의 domain -> host. 화이트리스트 크기는 그대로.
콘솔은 증거 카드 제목 줄에 monospace host-chip 을 붙인다. 기존 저장 행에는 host
키가 없으므로 evidenceHost() 가 저장된 URL 에서 파생한다 — 292행 마이그레이션도,
bootstrap 마다 행당 파싱도 필요 없다(렌더되는 행만 지나간다).
실렌더 검증(127.0.0.1:9512, 실 DB)에서만 드러난 레이아웃 결함 2건을 고쳤다.
코드 리딩으로는 보이지 않았다.
1) 긴 CDN 호스트(cloudfront-ap-northeast-1.images.arcpublishing.com)가 제목 줄
폭을 다 먹어, evidence-title 의 overflow-wrap:anywhere 와 결합해 제목이 한
글자씩 세로로 깨졌다. 칩을 max-width 20ch 로 묶고 제목에 flex:1 1 auto 를 줘
남는 폭을 갖게 했다.
2) 그 수정 후에는 반대로 biz.chosun.com(14자)까지 잘렸다. 칩을 flex:0 0 auto 로
고정했다 — 제목("구글 full image match found")은 전 행이 동일한 상용구이고
호스트가 그 행의 실제 내용이므로, 들어갈 수 있는 호스트가 잘리는 쪽이 잘못이다.
최종: 칩 82개 전부 유효 호스트 형태(TLD com/edu/hk/jp/kr/net/org), 가시 칩 41개 중
잘리는 것 4개(모두 20자 초과 CDN), 콘솔 에러 0.
PROVENANCE 배점은 보류한다. 두 조건이 다 안 맞는다. (a) detect_screen_capture 의
카테고리가 geometry 와 subtitle_band 둘뿐인데 2개를 요구하므로 라벨의 실제 의미는
"geometry AND 자막"이고, 자막 없는 방송 캡처는 구조적으로 도달 불가다 — 임계를
낮추는 게 아니라 독립적인 세 번째 신호가 필요하고 그건 새 탐지기다. 한계를 실측과
함께 상수 옆에 기록했다. (b) "새 탐지 신호는 0점 라벨로 출시한 뒤 decision_stats
상관을 보고 가중치 부여"가 2026-07-25 결정이고, 판정 0건인 지금 배점하면 그 결정을
위반한다.
신뢰출처 판정 자체는 "어느 도메인이 우리 것/라이선스 계약처인가"라는 운영자
데이터가 있어야 한다. 호스트가 살아났으므로 목록만 정해지면 cleared_reference 와
같은 패턴(점수 0 + 사유 노출)으로 붙는다.
테스트 4건 추가. pytest 574 passed(착수 전 570), 린트 클린.
20 KiB
기능 실효성 평가 — "이 도구가 실제 업무에 도움이 되는가"
2026-08-08 · 6축 조사 + 축별 적대적 반증(에이전트 12). 모든 수치는 실코드·실DB·실모델 실행 결과다.
결론
검색·기록 노동은 실제로 줄여준다. 심사할 건수는 전혀 줄여주지 못한다. 현재 이 도구는 위험 판별기가 아니라 증거 수집·기록기다.
| 축 | 판정 |
|---|---|
| 증거 수집 자동화 | 작동 — 단, 탐지력 이득이 아니라 배치·영속화·감사 이득 |
| 위험 점수 판별력 | 미작동 — 전건 high 수렴, 우선순위 정보 0비트 |
| LLM 증거 판독 | 격리는 성공, 산출물 가치는 미검증 — 운영 DB 기준 한 번도 표시된 적 없음 |
| 콘솔 작업 경제성 | 승인/보류 루프만 빠름 — 반려·일괄·감사는 병목 |
| 운영 준비도 | 폐쇄망 반입·백업은 견고, 신원·처리량은 미구현 |
1. 실제로 도움이 되는 것 (반증 통과)
- Cloud Vision 웹탐지가 신규 제출 전건에 자동 1회 실행되고 결과가 케이스에 영구 저장 + 감사기록으로 남는다
(
integrations/cloud_vision_web_detection.py:39-98,jobs/batch_analyzer.py:77-84,sqlite_store.py:430-446). 탐지 인덱스는 운영자가 Lens에 직접 넣는 것과 동일하므로 이득은 탐지력이 아니라 배치·영속화·감사다. - 승인/보류 키보드 1타 + 자동 다음 케이스 이동 (
app-keyboard-batch.js:182-200). 저위험 대량 승인 루프는 실제로 빠르다. - 중복 탐지가 실증적으로 일한다 — 재저장(JPEG q90)/80%축소/50%축소/밝기+10% 변형 24케이스 중 24건 그룹핑, 같은 24케이스에서 SHA-256 완전일치는 0건. 지각 해시 레이어가 exact 해시가 못 잡는 재제출을 잡는다. 갭은 5% 크롭 6/6 미탐(해밍 2~5비트 vs 임계 0.97의 1비트 예산).
- 판정 시점 스냅샷 · 메모 없는 반려 차단(서버+클라이언트) · 대상별 내보내기 화이트리스트
(
store_workflow.py:43-85,:21-37APPLICANT 5필드 vs INTERNAL 10필드). - 백업/복구 왕복 실증 성공 (
Connection.backup+ pre-restore 구제 사본 + 사이드카 제거). - 폐쇄망 Python 반입 절차 완결 — 런타임 서드파티 3개 핀 고정, GUI CDN 참조 0건, Pretendard 동봉, YuNet 가중치 실물이 문서 표의 232,589 bytes와 일치.
- SSRF 가드 실동작, 쿼터 원자 예약(try_reserve/release), 쿼리 시그니처 dedup으로 재분석 시 Naver/CSE 재소모 0.
- LLM이 점수·밴드를 절대 움직이지 못하도록 격리 (
risk_scoring.py:89, 101-102). Ollama 부재 시 읽을 수 있는 failure 행 1건으로 끝나고 점수 영향 없음(실측 2.01s / 0.19s).
2. 판별력 붕괴 — 이 도구가 "심사 건수"를 못 줄이는 이유
신호조합 224개 전수 계산: high 174건(77.7%) / medium 42건(18.8%) / low 8건(3.6%), 126건(56.2%)이 정확히 100점. 실 DB 6건도 100×4 / 45×2, low 0건.
지배적 동인은 match 타입 스택이 아니라 web entity 누적이다 (analysis/risk_scoring.py:159-160, 168-174).
버킷이 entity 이름별로 분리돼 서로 dedup되지 않고 round(40 × confidence)씩 쌓인다. Vision은 maxResults=10
(cloud_vision_web_detection.py:47)이라 모든 이미지가 라벨 10행을 받는다. 실측:
| 입력 | 결과 |
|---|---|
| entity 2개만 (이미지 매칭 0건) | 72 / high |
| entity 3개만 (이미지 매칭 0건) | 100 / high |
| 웹에 전혀 없는 오리지널 사진 (dog/pet/grass) | 66 / medium |
| full+partial+visual+page 각 1건 (평범한 웹 존재) | 100 / high |
| entity4 + full + partial + visual + page + 얼굴 (명백 침해) | 100 / high |
이 누적은 사고가 아니라 테스트가 고정한 동작이다 — test_risk_scoring.py:234-258이
"서로 다른 엔티티 2개는 반드시 high로 쌓여야 한다"를 assert한다. 따라서 기존 테스트로는 절대 잡히지 않는다.
파생 결과:
- 큐 기본 정렬이 riskScore 내림차순(
app-selectors.js:304)인데 전건 100이면 정렬이 no-op다. - low 밴드가 사실상 도달 불가 — low 8건은 전부 "아무것도 못 찾음" 조합뿐이라 "안 봐도 된다"는 결론을 못 준다.
- 신호 우선순위 역전 — 법적으로 가장 결정적인 EXIF/IPTC 권리 표기가 0점이라
(c) Getty Images단독 제출은 0점/low인데, Vision의 추측 라벨 3개는 100/high다 (risk_scoring.py:80-88PROVENANCE는 reason만 남기고 continue). - 운영자 되먹임이 순증 노동 — irrelevant 표시는 버킷당 "점수 매기는 첫 미표시 행"만 밀어내므로,
실 DB 제출물 4번을 100→35로 내리려면 google 증거 51행을 개별로 표시해야 한다. 일괄 API가 없다(개별
/api/evidence/{id}/status뿐). - cleared_reference가 재심사를 못 막는다 — FINGERPRINT 분기에서만 0점이라 [cleared_reference + 평범한 Vision 응답] = 100 high. 외부 호출 쿼터도 그대로 소모한다.
정답 라벨 0건: submissions 6건 전부 unreviewed, corrections 0행, Operator decision created 감사 이벤트 0건.
밴드 임계 70/30이 실제 판정과 한 번도 대조된 적이 없다.
3. 검증 코퍼스가 없다 (전제 정정)
data/copyrighter.sqlite3의 evidence 292행 중 288행이 2026-05-26, 이 리포의 첫 커밋은 2026-06-09이다.
즉 DB는 git 이력보다 2주 앞선 구빌드 스냅샷이며 6~8월 두 달치 변경이 반영돼 있지 않다:
- 저장 entity 행 40개 전부
entity=null→ 리스코어 시 0점 (현행 코드는 저장함) - 저장 entity confidence가 14.862 (현행 코드는 [0,1] 클램프)
- 증거 292행 전부
summary/domainSource/faceTier/verified/groundedClaims키 부재 - submissions 6건 전부
scorerVersion부재,submittedEpoch=0
판별력 판정을 확정하려면 실이미지 30~50장을 현행 코드로 새로 돌려야 한다. 그것이 밴드 캘리브레이션의 출발점이기도 하다.
4. 이번에 수정한 것 (P0)
(1) 폴더 정리가 판정 기록을 지우던 문제 — 확정 후 수정
실증: record_decision('3','rejected') → export 1행 → 원본 파일 제거 → _prune_missing_submission_files →
submissions 0행 / evidence 0행(cascade) / export 0행.
sqlite_store.py:_prune_missing_submission_files에 판정 완료 가드를 넣었다. 판정된 케이스는 원본 파일이 없어도
보존하고 assetMissing 마커 + 감사 이벤트를 1회만 남긴다(매 리로드마다 재기록되면 500행 감사 캡을 먹는다).
미판정 케이스의 프루닝 동작은 그대로다.
(2) 비상 차단 스위치가 장식이던 문제 — 확정 후 수정
대조실험: 스텁 어댑터로 계수 → CONTROL 5회 / emergency_disable_external_providers() 후 5회로 동일.
원인은 스위치가 DB 플래그만 쓰고 자동 보강 경로가 그 플래그를 아예 읽지 않은 것. 게이트는 수동 검색에만 있었다.
_external_calls_blocked(provider_id)를 신설(store_enrichment.py)하고 자동 외부 호출 4개 경로 전부에 배선했다:
| 경로 | provider |
|---|---|
_auto_naver_search |
naver |
_auto_google_custom_search |
google_search |
_rerun_google_image_search |
google |
_google_face_crop_web_detection |
google (생체 전송이라 상위 게이트에 의존하지 않고 독립 게이트) |
시드 경로 Vision 배치 (sqlite_store.py) |
google → ExternalApiPolicy(disabled=True) |
게이트는 enabled가 아니라 operatorDisabled 를 본다. 전자는 연결성/헬스에도 반응하므로 운영자가 멈추라고
한 적 없는 트래픽까지 죽인다. 후자는 개별 토글과 비상 스위치만 세우는 의도적 정지이고 리로드에도 유지된다.
(3) 생체정보(얼굴 크롭)가 보존기간을 초과 생존하던 문제 — 확정 후 수정
두 경로가 있었다: (a) 스냅샷이 face-crops를 통째로 담는데 프루닝은 나이가 아니라 개수 10개 기준이라
90일에 파기됐어야 할 크롭이 암호화 없는 zip 안에 남았다. (b) 복구는 copyfileobj로 새로 쓰므로 mtime이 리셋되고,
purge가 mtime을 보므로 파기 대상이 복구 직후부터 다시 90일을 산다.
store_backup.py의 _MEDIA_SOURCES에서 face-crops를 제외하고, 복구 시에도 _RETENTION_BOUND_MEDIA로
레거시 아카이브의 크롭을 건너뛴다. 크롭은 원본 이미지에서 재분석으로 재생성되므로 손실 비용이 없다.
(4) 오프라인 설치 문서가 계약 위반 모델을 지목하던 문제
offline-install.md가 qwen2.5:0.5b-instruct 반입을 지시했다 — 같은 리포가 55.7초 + 프롬프트 형식 예시 반향으로
출력 계약 위반 기록을 남긴 모델이다. 문서대로 반입하면 타깃에서 요약이 조용히 죽는다.
qwen2.5:7b로 정정하고, ollama 바이너리 반입 누락·GPU 전제·"env 비우면 LLM 꺼진다"는 거짓 서술
(실측: build_provider_runtime({}) → enabled=True, compliance 문자열까지 참으로 표시)을 함께 고쳤다.
검증: .\verify.ps1 — ruff 클린, pytest 565 passed(착수 전 562, 신규 회귀 테스트 3건).
5. 남은 우선순위
P1 — 판별력 복구 (이것이 "업무 도움" 여부를 가르는 축)
web entity 누적 캡→ 완료(2026-08-09,SCORER_VERSIONv1→v2). 아래 6절 참조. 조사 단계의 "test_risk_scoring.py:234-258이 현 동작을 고정한다"는 예측은 틀렸다 — 그 테스트는category가 붙은 엔티티(+45 분기)를 다루고, 포화를 만든 것은category없는 raw 라벨(+round(40*conf)분기)이다. 두 분기를 갈라 라벨만 캡했으므로 그 테스트는 개정 없이 통과한다.- PROVENANCE(EXIF/IPTC 권리 표기)에 배점. 단 실이미지 56장에서 증거 행 0건이었고,
방송캡처 분기는
MIN_CAPTURE_CATEGORIES=2에서 geometry가 1개 카테고리라 자막 없는 방송 캡처는 구조적으로 도달 불가다 (provenance.py:85-88). 보류 — 조건이 둘 다 안 맞는다. (a) 카테고리가geometry와subtitle_band둘뿐인데 2개를 요구하므로 라벨의 실제 의미는 "geometry AND 자막"이다. 임계를 낮추는 게 아니라 독립적인 세 번째 신호(채널 로고·하단 띠)가 필요하고 그건 새 탐지기다. 한계를 실측과 함께provenance.py상수 옆에 기록해 두었다. (b) "새 탐지 신호는 0점 라벨로 출시한 뒤decision_stats상관을 보고 가중치 부여"가 2026-07-25 사용자 결정이다. 판정 0건인 지금 배점하면 그 결정을 위반한다. 순서는 분기 수정 → 0점 출시 → 상관 → 배점. 매칭 호스트 기반 신뢰출처 구분→ 전제가 틀렸다(7절). 호스트는 "저장돼 있는데 점수만 안 쓰는" 게 아니라 영속화 시점에 파괴되고 있었다. 파괴를 먼저 멈췄다.- 밴드 재캘리브레이션은 판정 30~50건이 쌓인 뒤.
decision_stats교차표는 이미 구현돼 있다.
P2 — 운영
- 운영자 신원. 현재 감사 actor가 전부 하드코딩 리터럴(
rights.ops/admin.ops/system)이라 "누가 반려했나"에 답할 수 없다. - 레거시 llm 행 마이그레이션.
summary키가 없는 9행이_provider_item_has_result를 통과해only_if_missing경로를 막으므로 6개 제출물 어디에도 요약이 영원히 생성되지 않는다. 커버리지 패널은 "했다"고 보고한다. - LLM 프롬프트의
domain이 실호스트가 아니라 provider 이름(store_serialization.py:298이data['provider']를 우선). 저작권 심사에서 가장 중요한 "어디에 떠 있나"를 모델이 원천적으로 말할 수 없다. 같은 케이스 55행 중 41행이 url을 갖고 있는데 프롬프트 12줄 전부domain=google이다. - 자동 쿼리 3슬롯을 페이지 제목이 전부 선점(우선순위 100 vs entity 88/82)해 권리 지향 쿼리가 구조적으로 실행되지 않음
_clean_title이 말줄임표를 제거하지 않아 저장 제목 39개 중 13개(33%)가 잘린 채 검색어가 된다.
- 판정당 bootstrap 전량 왕복 2회. 300케이스 실측 9.58MB, 유효 델타는 36KB.
POST
/decision응답이 이미 완결된 케이스 페이로드인데 버리고/api/bootstrap을 다시 친다. - 반려 경로의 키보드 단절(제출 단축키 없음), 감사 로그 500건 고정 상한 + 필터·내보내기 부재.
6. P1 1차 — 신호 풀 도입 (2026-08-09, SCORER_VERSION v1 → v2)
포화의 동인은 하나가 아니라 둘이었다. 둘 다 "같은 사실을 여러 행으로 세는" 같은 형태다.
| 풀 | 상한 | 무엇을 묶나 | 근거 |
|---|---|---|---|
WEB_ENTITY_MAX_POINTS |
20 (medium 30 미만) | category 없는 raw Vision 라벨 |
Vision 은 모든 이미지에 라벨을 최대 10개 붙인다. 모집단 전체에 상수로 존재하는 신호는 판별에 기여할 수 없다 |
WEB_PRESENCE_MAX_POINTS |
60 (high 70 미만) | full / partial / page / visual / url | 응답 1회가 "이 이미지가 웹에 있다"는 하나의 사실을 4개 상관된 뷰로 보고한다. 합산은 45+35+35+10=125 로 상한을 넘겨 웹에 조금이라도 있으면 전부 100 이 됐다 |
category(character/celebrity/work)가 붙은 엔티티는 풀에서 제외했다 — 그것은 raw 라벨이 아니라 권리 분류이고 엔티티별 +45 를 유지한다.
설계 원칙: 웹에 존재한다는 사실만으로는 high 가 되지 않는다. high 가 되려면 두 번째 종류의 신호 (인물, 지식DB 지문 매칭, 승격된 검색 히트)가 필요하다. 존재 자체는 침해가 아니고, 그 판단이 운영자의 몫이다.
신호조합 224개 전수 재측정 (v1 → v2):
| 케이스 | v1 | v2 |
|---|---|---|
| 웹에 없는 오리지널 사진, 라벨만 | 100 / high | 20 / low |
| 팀이 이미 안전 확인한 자산(cleared) | 100 / high | 20 / low |
| 인물 사진, 라벨만 | 100 / high | 55 / medium |
| 웹 존재 광범위(4개 매치 타입) | 100 / high | 60 / medium |
| 웹 존재 + 배경 인물 | 100 / high | 70 / high |
| 완전일치 사본 + 식별가능 인물 | 80 / high | 80 / high |
| 지식DB 매칭 + 인물 | 100 / high | 100 / high |
| 명백한 침해(전 신호) | 100 / high | 100 / high |
다른 신호 없이 웹 신호만으로 high 가 되는 조합: 2/7 → 0/7. 100점 포화 조합 66.5% → 51.3%.
정직한 한계: 224조합 전수의 high 비율은 82.6% → 76.3% 로 크게 안 움직인다. 이 격자는 신호 축을 균등 샘플링하므로(지식DB 강매칭이 1/4을 차지) 실제 큐 분포의 대리값이 아니다. 실 DB 6건도 100×4 → 95×4 로 밴드가 바뀌지 않는다 — 그 4건은 전부 "웹에 완전일치 사본 + 인물"이라 high 가 맞다. v2 가 고친 것은 거짓 high (웹에 없음/라벨만/이미 안전 확인)이지, 진짜 high 를 줄인 게 아니다.
풀 상한 60/20 은 신호의 구조적 성질에서 나온 값이지 캘리브레이션 결과가 아니다. 판정 30~50건이 쌓이면
decision_stats 교차표로 임계와 함께 재조정해야 한다.
7. P1 2차 — 게시 호스트 복구 (2026-08-09)
P1 3번의 전제("호스트는 이미 저장돼 있고 점수만 안 쓴다")는 틀렸다. 실제로는:
google_custom_search.py:240,296이 실호스트(displayLink)를data["domain"]에 넣는다.store_serialization.py:298이data["provider"]를 우선하므로 그 호스트가 저장 시점에 덮어써진다.- Vision·Naver는 호스트를 아예 안 넣는다. URL만 있다.
- 결과: 저장된 모든 행의
domain이 API 이름(google)이고, LLM 프롬프트 12줄이 전부domain=google이었다. 저작권 심사에서 가장 중요한 "어디에 떠 있나"를 모델도 운영자도 볼 수 없었다.
domain 필드는 재사용할 수 없다 — provider 커버리지 동기화와 CSE 쿼리 시그니처 매칭이 그 값을 키로 쓰고
(store_serialization.py:195, 599), 콘솔도 provider 라벨로 렌더한다. 그래서 host를 별도 키로 신설했다:
_result_host()—data["domain"]이 provider와 다르면 그것을, 아니면 결과/페이지 URL에서 호스트를 파싱.www.제거, 소문자화. 파싱 실패는 "호스트 없음"이지 영속화 실패가 아니다.- 점수에는 영향 없다(
RiskScorer는domain도host도 읽지 않는다). 라운드트립 불변식 유지. _evidence_id는 도메인 객체의data를 해싱하고 payload를 해싱하지 않으므로 기존 증거 id가 바뀌지 않는다.PROMPT_DATA_FIELDS의domain→host. 화이트리스트 크기는 그대로.- 콘솔: 증거 카드 제목 줄에 monospace
host-chip. 기존 저장 행에는host키가 없으므로evidenceHost()가 저장된 URL에서 파생한다 — 292행 마이그레이션도, bootstrap마다 행당 파싱도 필요 없다.
실렌더 검증(127.0.0.1:9512, 실 DB): 케이스 1건에서 호스트 칩 82개 전부 유효 호스트 형태(TLD com/edu/hk/jp/kr/net/org),
콘솔 에러 0. 예: biz.chosun.com, thumb.mt.co.kr, cloudfront-ap-northeast-1.images.arcpublishing.com.
실렌더에서만 드러난 결함 2건을 고쳤다 — 코드 리딩으로는 안 보였다:
- 긴 CDN 호스트가 제목 줄 폭을 다 먹어 제목이 한 글자씩 세로로 깨졌다(
overflow-wrap: anywhere와 결합). → 칩을max-width: 20ch로 묶고 제목에flex: 1 1 auto를 줘 남는 폭을 갖게 했다. - 그 수정 후에는 반대로
biz.chosun.com(14자)까지 잘렸다. → 칩을flex: 0 0 auto로 고정. 제목("구글 full image match found")은 전 행이 동일한 상용구이고 호스트가 그 행의 실제 내용이므로, 들어갈 수 있는 호스트가 잘리는 쪽이 잘못이다. 최종: 가시 칩 41개 중 잘리는 것 4개(모두 20자 초과 CDN).
남은 것: 신뢰출처 판정 자체. 이건 "어느 도메인이 우리 것/라이선스 계약처인가"라는 운영자 데이터가
있어야 하고, 그 목록을 .env에 둘지 지식DB 항목으로 둘지는 운영 워크플로 결정이다. 호스트가 살아났으므로
목록만 정해지면 cleared_reference와 같은 패턴(점수 0 + 사유 노출)으로 바로 붙는다.
적대적 검증에서 반증된 항목 (되돌리려는 시도 방지용 기록)
폐쇄망에서 증거 썸네일이 안 보인다→ 런타임 외부 호출은 허용된 설계이고http_app.py:510-511에 근거 주석이 있다. 다만 서버 egress와 브라우저 egress는 다른 문제이므로 납품 전 타깃망에서 썸네일 1장 실로드 확인은 남긴다.중복 그룹핑 backfill이 미작동→ reload/import-folder/upload 3개 라우트 전부가 backfill을 거친다. 배지가 안 뜬 건 테스트 이미지 6장이 서로 다른 사진이라서다.재분석이 Naver/CSE 쿼터를 재소모→ 시그니처 dedup으로 0콜. 실제 재소모는 Vision 쪽(dedup 없음 + 얼굴크롭 최대 3콜).쿼터로 스킵된 쿼리가 영구 등재돼 재시도 불가(7/24 H-3)→_RETRYABLE_SKIP_DOMAIN_SOURCES로 이미 수정됨.판정 내보내기가 decidedAt/memo를 빈 값으로 낸다→ 현행 경로는 정상. 실 DB의 공백 63행은 전부 비판정 이벤트다.TLS 부재가 취약점→ 기본 바인드 127.0.0.1,0.0.0.0참조 0건. 미착수로 문서화된 항목이지 숨은 취약점이 아니다.