Commit graph

5 commits

Author SHA1 Message Date
changukyu
02395ac3aa fix: 판정 기록 소실 · 비상차단 미작동 · 생체 크롭 보존기간 초과 차단
"이 도구가 실제 업무에 도움이 되는가"를 6축으로 조사하고 축마다 결론을
반증시켰다(docs/reviews/2026-08-08-utility-assessment.md). 결론은 "검색·기록
노동은 줄여주나 심사 건수는 못 줄인다"이고, 그 과정에서 실행으로 확정된
P0 결함 4건을 여기서 고친다. 판별력 붕괴(P1)는 별도 작업이다.

폴더 정리가 판정 기록을 지웠다 (sqlite_store.py)
  _prune_missing_submission_files 가 원본 파일이 없는 submission 행을 판정
  여부와 무관하게 delete 했고, foreign_keys=on 이라 evidence 가 cascade 로
  함께 사라졌다. 심사 끝난 이미지를 아카이브로 옮기는 평범한 운영 행위가
  승인/반려를 DB 와 내보내기 양쪽에서 지운다. 실증: rejected 판정 후
  export 1행 -> 파일 제거 -> submissions 0행 / evidence 0행 / export 0행.
  판정된 케이스는 파일이 없어도 보존하고 assetMissing 마커를 남긴다. 마커가
  필요한 이유는 이 스윕이 매 리로드마다 돌기 때문이다 — 표시 없이 보존만
  하면 같은 감사 이벤트가 리로드마다 쌓여 500행 캡을 먹는다.

비상 차단 스위치가 아무것도 막지 않았다 (store_enrichment.py)
  emergency_disable_external_providers 는 DB 의 enabled/operatorDisabled 만
  쓰는데 자동 보강 경로가 그 플래그를 아예 읽지 않았다. 게이트가 걸린 곳은
  수동 검색뿐이라, 버튼을 눌러도 폴더 리로드·업로드·rerun-enrichment 가
  그대로 Naver/Google 로 나갔다. 스텁 어댑터 대조실험에서 CONTROL 5회 /
  차단 후 5회로 완전히 동일했다.
  _external_calls_blocked(provider_id) 를 신설해 자동 외부 호출 5개 경로에
  배선했다: auto naver, auto CSE, rerun Vision, 얼굴 크롭 웹탐지, 시드 배치
  (후자는 ExternalApiPolicy(disabled=True) 로 전환).
  enabled 가 아니라 operatorDisabled 를 본다. enabled 는 연결성/헬스에도
  반응하므로 운영자가 멈추라고 한 적 없는 트래픽까지 죽인다. 얼굴 크롭은
  생체 파생물을 전송하므로 상위 게이트에 의존하지 않고 독립 게이트를 건다.

생체 크롭이 보존기간을 넘겨 살아남았다 (store_backup.py)
  스냅샷이 face-crops 를 통째로 담는데 프루닝은 나이가 아니라 개수 10개
  기준이라, 90일에 파기됐어야 할 크롭이 암호화 없는 zip 안에 남았다. 복구는
  더 나빴다 — copyfileobj 로 새로 쓰므로 mtime 이 리셋되고, purge 가 mtime 을
  보므로 파기 대상이 복구 직후부터 다시 90일을 살았다.
  스냅샷에서 제외하고, 복구 시에도 레거시 아카이브의 크롭을 건너뛴다. 크롭은
  원본에서 재분석으로 재생성되므로 손실 비용이 없다.

오프라인 설치 문서가 계약 위반 모델을 지목했다 (offline-install.md)
  qwen2.5:0.5b-instruct 반입을 지시했는데, 같은 리포가 이 모델을 55.7초 +
  프롬프트 형식 예시 반향(출력 계약 위반)으로 기록해 두고 기본값을 7b 로
  고정해 뒀다. 문서대로 반입하면 타깃에서 요약이 조용히 죽는다. 모델명을
  정정하고, 반입물 목록에 없던 ollama 바이너리와 GPU 전제를 추가했으며,
  "env 를 비우면 LLM 없이 돈다"는 서술을 실측으로 정정했다 — 실제로는
  127.0.0.1:11434 로 폴백해 provider 가 enabled=True 로 보고된다.

회귀 테스트 3건 추가. pytest 565 passed(착수 전 562), 린트 클린.
2026-08-09 01:23:21 +09:00
changukyu
4f140efd9b feat: 실사용 투입 전 T0 스탬프 4종 + 임베딩 정책 개정
두 갈래의 작업이 한 커밋에 들어 있다. llm_assistance.py 에서 T0-4 가 앞선
작업의 프롬프트 수정을 대체했기 때문에(프롬프트 리터럴이 llm_prompts.py 로
이동), 분리하려면 실제로 존재한 적 없는 중간 상태를 만들어야 한다.

## (1) 앞서 진행 중이던 review-intelligence 작업

- decision_stats(): 위험밴드 x 실제판정 교차표 + 캘리브레이션 지표
- cleared_reference 지식엔트리 타입: 이전에 클리어된 이미지 매칭은 +80 이
  아니라 0점 긍정 신호
- LLM 요약 본문 영속화(_evidence_payload 에 summary/sourceUrls) — 그전까지
  요약이 저장되지 않아 운영자에게 한 번도 보인 적이 없었다
- 운영자 GUI 반영

## (2) 실사용 투입 전 T0 — 첫 판정 이후에는 소급 불가한 것들

- T0-1 initialRiskScore/initialRiskBand/scorerVersion 각인.
  _rescore_submission 이 12곳에서 riskScore 를 덮어쓰므로 저장된 점수는 늘
  왕복 점수였고, 그것을 재채점과 비교하는 테스트는 항상 통과하는 빈 테스트다.
  레거시 행은 scorerVersion="legacy" 로 표시만 하고 점수를 날조하지 않는다.
- T0-2 얼굴검출 Haar -> YuNet, 인물 등급(identifiable/background) 도입.
  기존의 평평한 +35 는 사람이 찍힌 모든 사진을 medium 문턱 위로 올려 밴드
  분포를 붕괴시켰다(실측 high 4 / medium 2 / low 0). 등급은 payload 라운드
  트립으로 보존한다 — 잃으면 재채점마다 조용히 +35 로 되돌아간다.
  가중치는 models/(gitignore) 에 두고 offline-install.md 에 sha256 반입 절차.
  가중치가 없으면 Haar 로 강등하고 1회만 경고한다.
- T0-3 판정 시점 피처 스냅샷. 큐가 riskScore 내림차순이고 화면이 점수를 먼저
  보여준 뒤 판정을 받으므로, 스냅샷 없이 모은 라벨은 "이 근거가 반려를
  예측한다"가 아니라 "운영자가 내가 보여준 점수에 동의했다"를 기록한다.
- T0-4 모델/프롬프트 버전 + latency_ms 스탬프. latency_ms 는 _evidence_id
  해시에서 제외한다 — 포함하면 같은 요약이 재생성될 때마다 id 가 바뀐다.

## (3) 임베딩 저장 금지 정책 개정

금지의 취지는 생체 식별이지 이미지 유사도가 아니므로 범위를 좁힌다.
장면 임베딩(전체 이미지 의미 벡터)을 이미지 유사도 판정에 한해 허용하되
얼굴 마스킹 / 인물 지배 프레임 제외 / 목적 제한 / 별도 저장 클래스 /
연쇄 삭제 5조건을 assert_scene_embedding_allowed 로 강제한다. 마스킹 증명이
없으면 거부한다 — 누락은 마스킹을 건너뛴 것과 구별할 수 없다.
얼굴 임베딩 금지는 landmarks/face_descriptor/face_encoding 추가로 강화.

pytest 502 passed (착수 전 463).
2026-07-25 23:08:19 +09:00
changukyu
2c607e0df6 docs: 2026-07-11 multi-agent review report, remediation checklist, offline extras
- Add the 2026-07-11 multi-agent project-review report and the remediation
  checklist (per-item status, with deferred items and their rationale).
- offline-install: document Ollama model and Playwright Chromium transfer, a
  hash-verified lock, --only-binary, and correct the egress note (image
  comparison fetches arbitrary SSRF-guarded public URLs, not only API hosts).
2026-07-11 16:08:04 +09:00
유창욱
8958dd1b83 chore: pin runtime dependencies for offline air-gapped install
Add requirements.txt (numpy/opencv-python-headless/pillow — the only
third-party runtime imports) and requirements-dev.txt, plus an offline
install runbook. Ignore .coverage and wheelhouse/.
2026-06-20 18:19:08 +09:00
유창욱
3f7b3a9cf2 chore: initial commit of copyrighter (rights_filter)
Image rights / copyright detection system: SQLite store, HTTP app,
search integrations (Naver, Google Custom Search, Google Cloud Vision
web detection), image analysis (fingerprints, face/person detection,
evidence enrichment, risk scoring), an admin/review layer, governance
and retention policies, batch jobs, and a browser-based operator GUI.

This baseline incorporates a full code-review remediation pass
(46 fixes; 358 tests passing). Highlights:

CRITICAL
- Prevent evidence cascade-delete during the schema-constraint
  migration by disabling FK enforcement around the table rebuild.

Security
- Sandbox served media (neutralize stored XSS from uploaded/collected
  SVGs) via CSP + nosniff on the untrusted media routes.
- Strip embedded EXIF/GPS from external image derivatives before they
  are sent to third-party APIs.
- Return a clean 404 (not an uncaught StopIteration) for PATCH on an
  unknown provider.

Correctness
- LLM-summary failures no longer add +30 to the risk score.
- Decode only explicit JS escapes so Korean image URLs are not mangled.
- Consume search quota only after a successful request.
- Naver/Google adapters map responses inside the failure boundary, so a
  malformed response degrades to evidence instead of crashing enrichment.
- Domain-aware provider attribution; face-box IoU de-duplication; count
  searches (not result items); per-box crop isolation; clamp evidence
  confidence and Google CSE num; real submittedEpoch; and more.

Robustness
- Offline LLM connect fast-fails (short connect timeout) so seed/reload
  requests are not stalled; full read timeout preserved for generation.
- Malformed numeric env vars fall back to defaults instead of crashing
  startup.

Performance
- Per-submission evidence reads (no full-table scan per rescore),
  audit-log LIMIT, lazy active-store lookup, hoisted timestamps.

Tests
- ~24 regression tests added pinning the above fixes.

Runtime data (data/, outputs/, *.sqlite3, *.log), secrets (.env), and
node_modules are gitignored.
2026-06-09 09:50:31 +09:00