# task-2428 로키 G2 적대적 평가 보고서

**작성**: 보안팀(레드팀) 팀장 로키 (Devil's Advocate)
**작성일**: 2026-05-03
**대상**: task-2428 IDS Phase 2+3 (디자인팀 산출)
**모델**: opus 4.7 (적대적 분석 깊이)

---

## 결과: FAIL — 약점 4건 (CRITICAL 1, HIGH 1, MEDIUM 1, LOW 1)

회귀 테스트 32/32 PASS, py_compile PASS, forbidden_paths 무수정, 외부 API 직접 호출 0건은 모두 검증 완료.
그러나 **결정성(reproducibility) 위배 1건이 [중대 CRITICAL]에 해당**하며, 회장 확인 패키지 신뢰성을 직접 훼손한다.

---

## 발견 약점 상세

### [중대 CRITICAL] L-01 — `hash()` 사용으로 인한 결정성 깨짐 (동일 시드 → 다른 PNG)

**위치**: `/home/jay/workspace/skills/hybrid-image/patterns/_pil_render.py:119`

**코드**:
```python
rng = np.random.default_rng(seed=hash((theme, w, h)) & 0xFFFFFFFF)
```

**위배 사항**:
- 적대적 검증 시나리오 (c) 시드 동일성 검증 위배
- 회장 패키지 명시 약속 위배: `"본 패키지의 모든 PNG는 결정론적으로 생성되었습니다 (BASE_SEED=42 + ...)"` (chairman-review-package.md:17)
- task-2421 정책 (silent corruption 차단)의 정신 위배: 회장이 같은 시드로 재현했을 때 다른 결과가 나오면 evidence 신뢰성 자체가 훼손됨

**재현 시나리오**:
```bash
# 프로세스 1
$ python3 -c "
import sys, importlib.util, hashlib
from pathlib import Path
spec = importlib.util.spec_from_file_location('p', '/home/jay/workspace/skills/hybrid-image/patterns/__init__.py', submodule_search_locations=['/home/jay/workspace/skills/hybrid-image/patterns'])
m = importlib.util.module_from_spec(spec); sys.modules['p']=m; spec.loader.exec_module(m)
m.PATTERNS['h1']('보험료 과다 청구', '올해 평균 23% 인상.', Path('/tmp/r1.png'), size=(1080, 1350),
    design_tokens={'gradient_theme':'navy','title_size':80,'body_size':38,'hint_seed':1042,
                   'hint_force_brand_color':'#0f1729','primary_hex':'#0f1729',
                   'title_color':'#fafaf8','body_color':'#d4d8e0','accent_color':'#0f1729'})
print(hashlib.sha256(open('/tmp/r1.png','rb').read()).hexdigest())
"
# 출력 1회차: ea304d92...

# 프로세스 2 (동일 코드, 새 Python 프로세스)
# 출력 2회차: 6892f527...  ← 다른 hash!
```

**실증 결과** (본 평가에서 직접 실행):
- 프로세스 1 sha256: `ea304d9251e1db30dff63a6466c0f926a001e482980aa51a114a7d3d4e783c58`
- 프로세스 2 sha256: `6892f527a9b82c07b3e98d5ffa261c8abf4921a1b106611772a331b853f76357`
- 동일 시드(1042)로 동일 입력 → 다른 PNG 바이트
- 원인: Python의 `hash()`가 PYTHONHASHSEED 환경 변수에 따라 프로세스마다 무작위화 (CPython 3.3+ 기본 동작)

**보완 권고** (Surgical patch):
```python
# Before (line 119):
rng = np.random.default_rng(seed=hash((theme, w, h)) & 0xFFFFFFFF)

# After (deterministic):
import hashlib
key = f"{theme}|{w}|{h}".encode("utf-8")
det_seed = int.from_bytes(hashlib.sha256(key).digest()[:4], "big")
rng = np.random.default_rng(seed=det_seed)
```

또는 더 간단히 `hash()` 사용 회피 (단순 shift+xor):
```python
det_seed = (w * 1009 + h * 1013 + sum(ord(c) for c in theme) * 1019) & 0xFFFFFFFF
rng = np.random.default_rng(seed=det_seed)
```

추가로, 시드를 함수 인자로 받게 하여 caller(`render_h1_procedural` 등)에서 `hint_seed`를 그대로 전파하면 프로세스 독립성이 보장된다.

---

### [높음 HIGH] L-02 — 회귀 테스트가 H2 visual_diversity 실패를 silent pass로 가장

**위치**: `/home/jay/workspace/tests/skills/satori/test_real_render_25.py:205-208`

**코드**:
```python
if pattern == "h2_illustration_card":
    assert visual["std_mean"] >= 25.0, ...  # std_mean만 검증, visual["passed"]는 검증 안 함
```

**위배 사항**:
- task-2421 정책 (silent pass 차단 의무) 위배
- `quality_evaluator.evaluate_image`는 H2 5장 모두 `visual_diversity.passed=False` 반환 (unique_colors 944~989 < 1000)
- 적대적 검증 시나리오 (b) silent skip 차단 위배: 평가 모듈 FAIL을 테스트 레이어가 isolating해서 PASS 처리

**재현 시나리오**:
```bash
$ python3 -c "
import json
from pathlib import Path
base = Path('/home/jay/workspace/memory/reports/task-2424-evidence-25-stratified-v4')
for f in sorted(base.glob('h2_v*.meta.json')):
    d = json.loads(f.read_text())
    print(f\"{d['label']}: real_fail={d['real_fail_reasons']}\")
"
# h2_v1: ['[visual_diversity] 색상 다양성 부족: unique colors 982 < 1000']
# h2_v2: ['[visual_diversity] 색상 다양성 부족: unique colors 971 < 1000']
# h2_v3: ['[visual_diversity] 색상 다양성 부족: unique colors 947 < 1000']
# h2_v4: ['[visual_diversity] 색상 다양성 부족: unique colors 944 < 1000']
# h2_v5: ['[visual_diversity] 색상 다양성 부족: unique colors 989 < 1000']
```

`evaluate_image`는 정직하게 FAIL을 반환하지만, 테스트는 `visual["passed"]`를 검증하지 않고 `std_mean`만 본다. 그 결과 32/32 PASS는 false-positive를 포함한다.

**보완 권고**:
1. (정공) 패턴별 procedural renderer를 강화하여 H2 unique_colors > 1000 충족 (`render_h2_procedural`의 `palette` 다양성을 40 → 200+로 확대)
2. (대안) `quality_evaluator.PATTERN_THRESHOLDS["h2_illustration_card"]`의 unique_colors_min=5000을 hybrid-specific으로만 사용하고, 일반 visual_diversity의 unique_colors_min=1000을 H2에 한해 800으로 완화 (단 dq-rules.json + quality_evaluator.py는 forbidden_paths)
3. 결국 (1)이 정공 — 본 task에서 즉시 처리 권고. 보고서/플랜에 "H2 5장 known-fail" 명시 의무.

---

### [중간 MEDIUM] L-03 — 6축 hint 중 4축이 procedural renderer에서 무시됨 (retry-until-pass 효율 0)

**위치**: `/home/jay/workspace/skills/hybrid-image/patterns/_pil_render.py` 전체 (소비 누락)

**위배 사항**:
- retry_loop가 `force_pattern_diversity`, `force_pattern_signature`, `force_korean_font`, `force_spatial_coherence` 4종 hint를 design_tokens에 주입하지만, `_pil_render.py` 패턴 함수들은 이를 읽지 않음
- `hint_seed` 외에는 시드 변형도 hint와 무관하게 계산됨 (`seed + 100`, `seed + 200` 등)
- 결과: H2가 unique_colors < 1000으로 1차 FAIL → retry_loop가 `force_pattern_diversity=True` 주입 → 2차 attempt도 동일한 결과 → 5회 모두 동일 fail mode → RuntimeError raise

**재현 시나리오** (이미 evidence에 포함):
```
# h2_v1.meta.json history:
attempt 1 hints={}              → score 70 (visual_diversity FAIL)
attempt 2 hints={force_korean_font, fallback_check} → score 70 (동일)
attempt 3-5: 동일 score 70
```

retry-until-pass의 본질은 hint를 받아 다음 시도를 다르게 시도하는 것. 본 구현은 "loop이 5회 돌긴 하지만 아무것도 변하지 않는다" — task-2421 retry 정책의 형식적 준수일 뿐.

**보완 권고**:
- `force_pattern_diversity=True` 시 H2의 도형 개수(60)와 palette 크기(40)를 1.5x 확대
- `force_spatial_coherence=True` 시 noise_amplitude를 4 → 2로 축소
- 또는 (간단한 즉시 보완) seed에 hints의 hash를 XOR하여 attempt마다 결정성 있는 변형 강제:
  ```python
  effective_seed = seed ^ (sum(1 for k,v in tokens.items() if k.startswith("hint_") and v) * 31337)
  ```

---

### [낮음 LOW] L-04 — `check_ocr_confidence`와 `check_font_size`의 BLOCKED 처리 비일관

**위치**: `/home/jay/workspace/skills/satori-cardnews/scripts/quality_evaluator.py:597-608` (수정 금지 — 본 task forbidden_paths)

**위배 사항** (본 task 책임 외이지만 evidence 해석에 영향):
- pytesseract 모듈은 설치돼있고 tesseract 바이너리만 미설치 → `check_font_size`는 `passed=True, blocked=True` 반환
- 같은 조건에서 `check_ocr_confidence`는 `passed=False`, blocked 키 없음 → 진짜 FAIL로 카운트됨
- 결과: 27장 모두 "OCR 실행 오류" 사유로 FAIL이지만, font_size는 BLOCKED, ocr_confidence는 진짜 FAIL — 의미가 다른 두 결과가 같은 환경에서 나옴
- evidence-25-stratified-v4의 27/27 FAIL이 진정한 silent pass 차단인지, 아니면 evaluator 내부 비일관인지 분간 어려움

**재현**: 본 task는 forbidden_paths이므로 수정 불가. 후속 task 권고로 분류.

**보완 권고**:
- 후속 task에서 `check_ocr_confidence`의 except 블록도 `passed=True, blocked=True` 패턴으로 통일 (또는 둘 다 진짜 FAIL로 통일)
- 본 task는 SUMMARY.md에 "ocr_confidence FAIL과 font_size BLOCKED는 동일 원인 (tesseract binary 부재)" 1줄 명시 권고

---

## 회귀 0 실증 (테스트 실행 결과)

### A. 적대 PNG → evaluate_image 명시 FAIL 검출 (5건)

```
$ python3 -m pytest tests/skills/satori/test_real_render_25.py::test_adversarial_tv_static_blocked -v
PASSED — TV-static spatial_diff > 25 → 명시 FAIL ✓

$ test_adversarial_monotone_gradient_blocked
PASSED — 단조 그라데이션 std_mean < 25 → 명시 FAIL ✓ (task-2401 회귀 차단)

$ test_adversarial_99_gray_1_brand_blocked
PASSED — matching_area_ratio < 0.10 → 명시 FAIL ✓

$ test_adversarial_korean_lt_50_blocked
PASSED — OCR 한글 비율 < 50% 또는 BLOCKED → silent pass 차단 ✓

$ test_adversarial_font_lt_40_blocked
PASSED — 폰트 < 40px 또는 BLOCKED → silent pass 차단 ✓
```

5/5 PASS. task-2389 한글 깨짐 + task-2401 단조 그라데이션 회귀는 evaluator 레이어에서 차단됨.

### B. tesseract 미설치 silent skip 우회 점검

evidence-25-stratified-v4 27/27이 OCR 관련 사유로 FAIL 처리됨. retry_loop는 5회 retry 후 RuntimeError raise (silent skip 없음). meta JSON에 모든 attempt history 기록. **silent skip 우회 차단 ✓** (단 L-04 비일관 잔존).

### C. 시드 동일성 검증 — 동일 seed 2회 호출 sha256 비교

```
in-process (같은 Python 프로세스): MATCH ✓
cross-process (별도 Python 프로세스): MISMATCH ✗  ← L-01 CRITICAL
```

**회귀 0 실패** — 회장 패키지에 명시된 "결정론적 생성" 약속이 깨짐.

### D. forbidden_paths 무수정 검증

```
$ git status | grep -E "(quality_evaluator|dq-rules|magazine|mobile-prototype|motion-cardnews|frontend-design|insane-design|ids-router|design-md)"
(empty — 0건)

$ git diff skills/satori-cardnews/scripts/quality_evaluator.py
(empty)

$ git diff memory/specs/dq-rules.json
(empty)
```

**PASS ✓** — quality_evaluator.py / dq-rules.json / 5 forbidden skills / resources/design-md 모두 무수정.

### E. 6축 hint 미존재 시 회귀 0 — 기존 SKILL.md 예제 호환

```python
>>> from skills.hybrid_image.patterns import render_h1_photo_card
>>> render_h1_photo_card("헤드라인", "본문", "/tmp/loki_skillmd_h1.png", prompt_hint="modern office")
# 결과: PNG 정상 생성 (377788 bytes), 예외 없음
```

**PASS ✓** — design_tokens / hint 없이도 패턴 정상 호출. 회귀 0.

### F. 외부 API 직접 호출 audit

```
$ grep -r "^\s*(import|from)\s+(openai|anthropic|google\.generativeai)" skills/hybrid-image/ skills/satori-cardnews/scripts/
(0건)
```

**PASS ✓** — `import openai`/`anthropic`/`google.generativeai` 직접 호출 0건.

### G. 32 회귀 테스트 실행

```
$ python3 -m pytest tests/skills/satori/test_real_render_25.py -v
============================== 32 passed in 21.05s ==============================
```

**PASS** (단 L-02의 silent-pass 우회 패턴 검출됨 — 진정한 PASS는 27/32).

---

## 회장 5대 규칙 점검

| 규칙 | 상태 | 비고 |
|---|---|---|
| 빌드 | PASS | py_compile 7개 파일 모두 OK |
| 배포 | PASS | skills/ 즉시 사용 가능 |
| 실 산출물 | PARTIAL | PNG 27장 산출, 단 5장 H2는 visual_diversity 미달 (real-fail) |
| 회장 confirm | BLOCKED | L-01로 재현 불가능 → confirm 신뢰성 훼손 |
| affected_files | PASS | forbidden_paths 무수정 |

---

## 종합 판정

**FAIL** — task-2428은 **L-01 CRITICAL 결정성 위배 1건**으로 인해 G2 통과 불가.

회장이 패키지에 명시된 시드(예: 1042)를 본인 머신에서 재실행했을 때 evidence와 다른 PNG가 나오면 **evidence 신뢰성 전체가 훼손**된다. 이는 task-2401 silent corruption 패턴의 회귀에 준한다 (시각적으로는 유사하지만 객관적으로 동일하지 않은 결과).

추가로 L-02 HIGH는 retry_loop FAIL을 테스트가 가리는 패턴으로, task-2421 정책의 정신적 위배에 해당한다.

### 즉시 권고 (Phase 2-1 책임자 이나리)

1. **L-01 (CRITICAL)** — `_pil_render.py:119`의 `hash()` 호출을 결정론적 시드 함수로 대체. 1줄 패치, 5분 작업.
2. **L-02 (HIGH)** — `_pil_render.py:render_h2_procedural`의 palette 다양성 확대 (40 → 256+) 또는 노이즈 텍스처 추가로 unique_colors > 1000 충족. 또는 테스트가 정직하게 H2 known-fail을 표기.
3. **L-03 (MEDIUM)** — 4축 hint 중 최소 1개 (`force_pattern_diversity`)를 procedural renderer에서 소비. attempt 간 차별화로 retry-until-pass의 실효성 확보.
4. **L-04 (LOW)** — 본 task 외 후속 task에서 `check_ocr_confidence`의 BLOCKED 통일.

### 재평가 조건

L-01 + L-02 패치 후 재실행:
- 시드 동일성 cross-process sha256 일치 (회귀 0 실증 C 재통과)
- evidence-25-stratified-v4의 H2 5장이 real-fail 0건 (또는 SUMMARY에 명시 + 회장 승인)

위 두 조건 충족 시 G2 PASS 가능.
