No description
Find a file
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
docs fix: 판정 기록 소실 · 비상차단 미작동 · 생체 크롭 보존기간 초과 차단 2026-08-09 01:23:21 +09:00
src/rights_filter fix: 판정 기록 소실 · 비상차단 미작동 · 생체 크롭 보존기간 초과 차단 2026-08-09 01:23:21 +09:00
tests fix: 판정 기록 소실 · 비상차단 미작동 · 생체 크롭 보존기간 초과 차단 2026-08-09 01:23:21 +09:00
web/operator-gui refactor: app.js 5,428줄을 관심사별 app-*.js 15개로 분할 2026-08-08 09:48:52 +09:00
.env.example fix: 형식 예시 반향 차단, 기본 모델을 실측으로 qwen2.5:7b 로 확정 2026-07-25 23:48:23 +09:00
.gitattributes chore: add .gitattributes to enforce LF line endings 2026-06-22 11:22:23 +09:00
.gitignore feat: 실사용 투입 전 T0 스탬프 4종 + 임베딩 정책 개정 2026-07-25 23:08:19 +09:00
CLAUDE.md docs: CLAUDE.md / PROJECT_MAP 신설, 7/11 체크리스트 재확인 2026-08-08 09:49:55 +09:00
package.json chore: initial commit of copyrighter (rights_filter) 2026-06-09 09:50:31 +09:00
pytest.ini chore: 단일 verify 게이트 신설 (pytest.ini + ruff.toml + verify.ps1) 2026-08-08 09:47:57 +09:00
README.md docs: CLAUDE.md / PROJECT_MAP 신설, 7/11 체크리스트 재확인 2026-08-08 09:49:55 +09:00
requirements-dev.txt chore: 단일 verify 게이트 신설 (pytest.ini + ruff.toml + verify.ps1) 2026-08-08 09:47:57 +09:00
requirements.txt chore: pin runtime dependencies for offline air-gapped install 2026-06-20 18:19:08 +09:00
ruff.toml chore: 단일 verify 게이트 신설 (pytest.ini + ruff.toml + verify.ps1) 2026-08-08 09:47:57 +09:00
run_copyrighter_server.py fix: 운영자 콘솔 실렌더 검증에서 드러난 결함 5종 2026-07-26 00:29:54 +09:00
verify.ps1 chore: 단일 verify 게이트 신설 (pytest.ini + ruff.toml + verify.ps1) 2026-08-08 09:47:57 +09:00

POSA Copyrighter

이미지 저작권 위험을 자동 분석하고, 운영자가 콘솔에서 근거를 확인해 승인 / 보류 / 반려를 판정하는 폐쇄망(air-gapped) 운영 도구입니다.

  • 분석 서버: 제출 이미지를 분석해 위험도·근거를 산출하고 SQLite에 저장
  • 운영자 콘솔: 심사 큐, 근거 검토, 판정, 지식 DB, 감사 로그를 제공하는 단일 페이지 웹 GUI
  • 외부 근거 검색: Naver 검색, Google Vision/Custom Search, 로컬 LLM(Ollama) 연동(선택)

요구 사항

  • Python 3.13
  • 런타임 서드파티 의존성은 이미지 처리 라이브러리뿐입니다(지연 로딩): numpy, opencv-python-headless, pillow

폐쇄망(air-gapped) 원칙: 모든 의존성·자산은 빌드/스테이징 머신에서 미리 받아 오프라인 휠로 설치합니다. 대상 서버는 설치·실행 시 인터넷에 접근하지 않습니다. 자세한 절차는 docs/operations/offline-install.md를 참고하세요.

설치

# 개발 머신 (온라인) — 의존성 설치
pip install -r requirements.txt          # 런타임
pip install -r requirements-dev.txt      # 테스트 포함

# 폐쇄망 대상 — 오프라인 휠로 설치 (예시)
pip install --no-index --find-links wheelhouse -r requirements.txt

.env.example를 복사해 .env를 만들고 필요한 값(인증 토큰, 검색 API 키 등)을 채웁니다. 키를 비워 두면 해당 외부 검색은 자동으로 비활성화됩니다.

cp .env.example .env

실행

python run_copyrighter_server.py
# 옵션은 런처에 그대로 전달됩니다
python run_copyrighter_server.py --host 127.0.0.1 --port 9500

python -m rights_filter.server 로 직접 실행하려면 src/ 가 import 경로에 있어야 합니다 (PYTHONPATH=src, Windows PowerShell 은 $env:PYTHONPATH="src"). 런처는 이 경로 설정과 작업 디렉터리 고정을 대신 해 주므로 run_copyrighter_server.py 사용을 권장합니다.

주요 옵션: --host, --port, --db(SQLite 경로), --images(제출 이미지 폴더), --static(운영자 GUI 경로), --env(.env 경로).

기본값으로 실행하면 http://127.0.0.1:9500 에서 운영자 콘솔이 열립니다.

보안: COPYRIGHTER_AUTH_TOKEN이 비어 있으면 데이터 라우트가 인증 없이 열립니다. 단일 호스트 개발 환경 외에는 반드시 토큰을 설정하세요.

토큰을 설정하면 틀린 토큰을 제시한 시도는 클라이언트 주소별로 60초당 10회로 제한되고, 초과 시 429와 Retry-After를 반환합니다. 토큰을 아직 입력하지 않은 요청(콘솔 최초 로드)은 시도로 세지 않으므로 정상 운영자가 잠기지 않습니다.

프로젝트 구조

src/rights_filter/      분석·서버·통합·도메인 로직 (Python)
  server/               http.server 기반 API + 정적 파일 서빙, SQLite 저장소
  analysis/             이미지 분석·위험도 산출
  integrations/         Naver / Google / LLM 어댑터
web/operator-gui/       운영자 콘솔 (정적 SPA: index.html, app-*.js, styles.css)
tests/                  pytest 테스트
  operator_gui/         운영자 GUI 정적 검증 + Playwright E2E
docs/                   기획·설계·운영 문서

콘솔 스크립트는 관심사별로 app-*.js 15개로 나뉘어 있고, index.html 의 로드 순서가 의미를 갖습니다(클래식 스크립트가 하나의 전역 스코프를 공유). 순서와 목록은 docs/PROJECT_MAP.md 에 있으며 정적 테스트가 이를 고정합니다.

검증 / 테스트

커밋 전 게이트는 단일 명령입니다(CI 도 같은 명령을 호출합니다). ruff 린트와 pytest 전체를 순서대로 실행합니다.

.\verify.ps1              # 린트 + 테스트 전체
.\verify.ps1 -SkipLint    # 빠른 내부 루프 (테스트만)
.\verify.ps1 -Coverage    # src/ 커버리지 리포트 포함

개별 실행이 필요할 때:

python -m pytest                 # 또는 그냥 pytest — pytest.ini 가 경로를 설정합니다
python -m ruff check src tests   # 설정: ruff.toml

# 운영자 GUI E2E (Playwright)
playwright install chromium      # 빌드 머신에서 1회 — 브라우저 바이너리는 오프라인 번들에 포함
python -m pytest tests/operator_gui/
  • 브라우저(E2E) 테스트는 Playwright/Chromium이 없으면 자동으로 건너뜁니다(importorskip + graceful skip).
  • 운영자 워크벤치는 tests/operator_gui/pages/의 Page Object(OperatorWorkbench)와 conftest.py의 공용 픽스처를 사용합니다.

문서