# Handoff Report — Reviewer 2: Milestone M1 Iteration 2 Gate Verification

**Verdict**: `REQUEST_CHANGES`

---

## 1. Observation

### 1.1 Implementation Code Inspection (`05_Production_Pipeline/production_orchestrator.py`)
- **Lines 344–348**: Definition of character indicator flags in `resolve_start_frame`:
  ```python
  is_kim_trong = ("kim_trong" in curr_anchor or ("kim trọng" in combined and "thúy kiều" not in combined and "nàng kiều" not in combined)) and "thuy_kieu" not in curr_anchor
  is_thuy_kieu = ("thuy_kieu" in curr_anchor or (("thúy kiều" in combined or "nàng kiều" in combined) and "kim trọng" not in combined)) and "kim_trong" not in curr_anchor
  is_vuong_quan = ("vuong_quan" in curr_anchor or ("vương quan" in combined and "thúy kiều" not in combined)) and "thuy_kieu" not in curr_anchor
  is_vuong_ong = ("vuong_ong" in curr_anchor or ("vương ông" in combined and "thúy kiều" not in combined)) and "thuy_kieu" not in curr_anchor
  is_thuy_van = ("thuy_van" in curr_anchor or ("thúy vân" in combined and "thúy kiều" not in combined)) and "thuy_kieu" not in curr_anchor
  ```
- **Lines 352–366 (Step A: character_asset_ref inspection)**:
  `is_thuy_van` is handled at line 364:
  ```python
  elif is_thuy_van and "thuy_kieu" in ref_lower:
      asset_ref = "04_Assets/characters/01_Main_Protagonists/thuy_van_maiden_16yo_720p.png"
  ```
- **Lines 409–415 (Step C: reference_start_frame inspection)**:
  `is_thuy_van` is **completely missing**:
  ```python
  violation = (
      (is_kim_trong and "thuy_kieu" in ref_name) or
      (is_thuy_kieu and "kim_trong" in ref_name) or
      (is_vuong_quan and "thuy_kieu" in ref_name) or
      (is_vuong_ong and "thuy_kieu" in ref_name)
  )
  ```
- **Lines 419–432 (Step D: Final Bilateral & Multi-character Safeguards override)**:
  `is_thuy_van` is **completely missing**:
  ```python
  if resolved_path:
      res_lower = resolved_path.lower()
      if is_kim_trong and "thuy_kieu" in res_lower:
          kt_portrait = CHARACTERS_DIR / "01_Main_Protagonists" / "kim_trong_18yo_720p.png"
          resolved_path = str(kt_portrait.resolve()) if kt_portrait.exists() else None
      elif is_thuy_kieu and "kim_trong" in res_lower:
          tk_portrait = CHARACTERS_DIR / "01_Main_Protagonists" / "thuy_kieu_maiden_16yo_720p.png"
          resolved_path = str(tk_portrait.resolve()) if tk_portrait.exists() else None
      elif is_vuong_quan and "thuy_kieu" in res_lower:
          vq_portrait = CHARACTERS_DIR / "02_Vuong_Family_And_Fate" / "vuong_quan_16yo_720p.png"
          resolved_path = str(vq_portrait.resolve()) if vq_portrait.exists() else None
      elif is_vuong_ong and "thuy_kieu" in res_lower:
          vo_portrait = CHARACTERS_DIR / "02_Vuong_Family_And_Fate" / "vuong_ong_55yo_720p.png"
          resolved_path = str(vo_portrait.resolve()) if vo_portrait.exists() else None
  ```
- **Lines 238, 253, 263, 268 (`resolve_character_portrait_from_anchor`)**:
  Line 238 checks `if "kim_trong" in clean and "thuy_kieu" not in clean:`
  Line 253 checks `if "vuong_quan" in clean and "sai_nha" not in clean and "thuy_kieu" not in clean:`
  Line 263 checks `if "thuy_van" in clean and "thuy_kieu" not in clean:`
  Line 268 checks `if "thuy_kieu" in clean:` (no exclusion of other characters, causing Thúy Kiều to dominate on compound anchors).

### 1.2 Empirical Failure Reproductions
1. **Thúy Vân Safeguard Bypass via Step C & Step D**:
   ```python
   thuy_van_shot = {
       'reference_start_frame': '04_Assets/characters/01_Main_Protagonists/thuy_kieu_maiden_16yo_720p.png',
       'scene_title': 'Thúy Vân trang trọng đoan trang',
       'motion_prompt': 'Thúy Vân mỉm cười ung dung'
   }
   res = po.resolve_start_frame('ep01_scene01_shot05', thuy_van_shot)
   # Actual Output: C:\Projects\KieuStory\04_Assets\characters\01_Main_Protagonists\thuy_kieu_maiden_16yo_720p.png
   # Expected: C:\Projects\KieuStory\04_Assets\characters\01_Main_Protagonists\thuy_van_maiden_16yo_720p.png
   ```
2. **Compound Anchor Safeguard Disablement (`kim_trong + thuy_kieu_maiden`)**:
   ```python
   adv_data = {
       'character_asset_ref': '04_Assets/characters/01_Main_Protagonists/thuy_kieu_maiden_16yo_720p.png',
       'scene_title': 'Kim Trọng viết lời thề nguyền',
       'motion_prompt': 'Kim Trọng cầm bút lông viết chữ'
   }
   res = po.resolve_start_frame('ep01_scene10_shot07', adv_data)
   # Actual Output: C:\Projects\KieuStory\04_Assets\characters\01_Main_Protagonists\thuy_kieu_maiden_16yo_720p.png
   # Reason: curr_anchor has 'thuy_kieu', so is_kim_trong evaluated to False, disabling Step A and Step D.
   ```
3. **Prompt Database Representation Split**:
   In `02_AI_Prompts/gemini_banana_prompts.json`:
   - Top-level `d['ep01_scene09_shot01']['character_anchor']` is `"kim_trong"`
   - Nested `d['ep01_start_frames']['ep01_scene09_shot01']['character_anchor']` is `"thuy_kieu_maiden"`

### 1.3 Test Suite Execution Results
- `python -m pytest tests/test_critic_gate.py -v`:
  **30 passed in 12.77s** (100% PASS)
- `python -m pytest tests/test_m1_challenger2_probe.py -v`:
  **41 passed in 0.53s** (100% PASS)
- `pytest tests/test_tier1_features.py -k "not test_render" -v`:
  **65 passed in 2.91s** (100% PASS)
- Total tests executed across all 3 suites: **136 passed, 0 failed**.

---

## 2. Logic Chain

1. **Directive Requirement**: Review instruction item 1 mandates:
   > *"Verify that is_kim_trong, is_thuy_kieu, is_vuong_quan, is_vuong_ong, and is_thuy_van are enforced across Step A, Step C, and Step D."*
   Worker claimed in `handoff.md` (lines 14, 44–49) that bilateral and multi-character safeguards were added for all 5 characters across Step A, Step C, and Step D.
2. **Observation of Missing Guards**:
   In `production_orchestrator.py`:
   - `is_thuy_van` was declared at line 348.
   - `is_thuy_van` was added to Step A at line 364.
   - In Step C (lines 409–415), `is_thuy_van` was omitted from `violation`.
   - In Step D (lines 419–432), `is_thuy_van` was omitted from the override branches.
3. **Verification of Hole**:
   Executing the reproduction probe demonstrated that passing a Thúy Kiều reference into a Thúy Vân shot via `reference_start_frame` directly escapes both Step C and Step D and resolves to `thuy_kieu_maiden_16yo_720p.png`.
4. **Self-Certifying Test Omission**:
   In `tests/test_critic_gate.py` (lines 530–571), the worker wrote tests for:
   - `adv_kt` (Kim Trọng with Thúy Kiều ref)
   - `adv_tk` (Thúy Kiều with Kim Trọng ref)
   - `adv_vq` (Vương Quan with Thúy Kiều ref)
   - `adv_vo` (Vương Ông with Thúy Kiều ref)
   The worker omitted testing `adv_tv` (Thúy Vân) in `test_adversarial_bilateral_safeguards_override`, which hid the incomplete implementation of `is_thuy_van`.
5. **Prompt Alignment Verification**:
   - Scene 10 shots 03, 06, 09, 11, 12 resolve to `thuy_kieu_maiden_16yo_720p.png` in both `ep01` and master. (PASS)
   - Scene 10 shots 04, 05, 07, 08, 10, 13 resolve to `kim_trong_18yo_720p.png` in both `ep01` and master. (PASS)
   - Scene 06 Kim Trọng shots 03, 04, 06, 10, 12, 14 resolve to `kim_trong_18yo_720p.png`. (PASS)
   - Scene 05 Vương Quan shots 13, 16, 24 resolve to `vuong_quan_16yo_720p.png`. (PASS)
   - Reciprocal protection between Thúy Kiều and Kim Trọng is 100% enforced in Step A, Step C, and Step D. (PASS)
6. **Verdict Deduction**:
   Because `is_thuy_van` is not enforced across Step C and Step D as mandated by the instructions and claimed in worker documentation, the gate verification cannot be approved. Changes are requested to complete the safeguard.

---

## 3. Findings

### [Critical] Finding 1: `is_thuy_van` Omitted from Step C and Step D in `resolve_start_frame()`
- **What**: Multi-character safeguard fails for Thúy Vân when `reference_start_frame` or post-resolution path points to Thúy Kiều.
- **Where**: `05_Production_Pipeline/production_orchestrator.py`, lines 409–415 (Step C) and lines 419–432 (Step D).
- **Why**: Violates the explicit requirement: *"Verify that is_kim_trong, is_thuy_kieu, is_vuong_quan, is_vuong_ong, and is_thuy_van are enforced across Step A, Step C, and Step D."*
- **Remediation**:
  1. In Step C, update `violation`:
     ```python
     violation = (
         (is_kim_trong and "thuy_kieu" in ref_name) or
         (is_thuy_kieu and "kim_trong" in ref_name) or
         (is_vuong_quan and "thuy_kieu" in ref_name) or
         (is_vuong_ong and "thuy_kieu" in ref_name) or
         (is_thuy_van and "thuy_kieu" in ref_name)
     )
     ```
  2. In Step D, add the override branch:
     ```python
     elif is_thuy_van and "thuy_kieu" in res_lower:
         tv_portrait = CHARACTERS_DIR / "01_Main_Protagonists" / "thuy_van_maiden_16yo_720p.png"
         resolved_path = str(tv_portrait.resolve()) if tv_portrait.exists() else None
     ```
  3. In `tests/test_critic_gate.py`, add `adv_tv` to `test_adversarial_bilateral_safeguards_override` and test both `character_asset_ref` and `reference_start_frame`.

### [High] Finding 2: Compound Character Anchors Neutralize `is_kim_trong` and `is_thuy_kieu` Safeguards
- **What**: For shots with joint anchors like `character_anchor: "kim_trong + thuy_kieu_maiden"` (e.g., `ep01_scene10_shot07`), the condition `and "thuy_kieu" not in curr_anchor` evaluates to `False`. This completely turns off `is_kim_trong`, preventing Step A and Step D from protecting the shot against erroneous `character_asset_ref` assignments.
- **Where**: `05_Production_Pipeline/production_orchestrator.py`, lines 344–348.
- **Why**: Joint scenes (two-shots) frequently have one character as the focus of a specific shot. The negative anchor check blinds the safeguard even when `scene_title` or `motion_prompt` explicitly names only one character.
- **Remediation**: Allow title/prompt clarity to take precedence over compound anchors:
  ```python
  title_kt_only = ("kim trọng" in combined and "thúy kiều" not in combined and "nàng kiều" not in combined)
  is_kim_trong = (("kim_trong" in curr_anchor and "thuy_kieu" not in curr_anchor) or title_kt_only)
  ```

### [Medium] Finding 3: Anchor Resolution Asymmetrically Favors Thúy Kiều on Compound Anchors
- **What**: `resolve_character_portrait_from_anchor()` excludes Thúy Kiều for Kim Trọng, Vương Quan, and Thúy Vân, but does not exclude any characters for Thúy Kiều (line 268: `if "thuy_kieu" in clean:`).
- **Where**: `05_Production_Pipeline/production_orchestrator.py`, lines 236–272.
- **Why**: Any anchor with multiple characters (e.g., `"kim_trong + thuy_kieu_maiden"`) skips Kim Trọng and defaults to Thúy Kiều.
- **Remediation**: Check `and "kim_trong" not in clean` in the Thúy Kiều branch, or handle joint anchors explicitly by returning None or delegating to shot title context.

### [Minor] Finding 4: Dual-Representation Inconsistency in `02_AI_Prompts/gemini_banana_prompts.json`
- **What**: `ep01_scene09_shot01` has `character_anchor: "kim_trong"` at the JSON root, but still contains `"thuy_kieu_maiden"` under `d["ep01_start_frames"]["ep01_scene09_shot01"]`.
- **Where**: `02_AI_Prompts/gemini_banana_prompts.json`.
- **Why**: `episode_manager.py --sync-to-master` synced items into root keys instead of updating the legacy nested `ep01_start_frames` dictionary. Fixtures or scripts reading `d["ep01_start_frames"]` get stale data.
- **Remediation**: Ensure sync updates both root keys and nested episode buckets.

---

## 4. Caveats

- All 136 automated tests across the 3 test suites currently pass without errors. The failure of `is_thuy_van` was exposed through independent adversarial edge-case probing.
- Implementation code in `05_Production_Pipeline/production_orchestrator.py` was strictly NOT modified by this reviewer, conforming to the review-only role constraint.
- Prompt files `episodes/ep01/prompts/muse_prompts.json` and `02_AI_Prompts/muse_ai_video_prompts.json` are verified to be fully aligned for the 20 targeted shots across Scenes 05, 06, and 10.

---

## 5. Conclusion

**Verdict: REQUEST_CHANGES**

Milestone M1 Iteration 2 has achieved significant progress:
- The 2-Tier Critic Quality Gate (`antigravity_critic_gate.py`) is well architected with real OpenCV heuristic analysis, valid Pydantic schemas, and severe defect penalties.
- Prompt alignments for Scenes 05, 06, and 10 are completely verified across both local and master registries.
- Reciprocal protection between Thúy Kiều and Kim Trọng is verified.

However, **Finding 1** represents an incomplete implementation of the multi-character safeguard: `is_thuy_van` was omitted from Step C and Step D in `resolve_start_frame()`, directly contradicting the dispatch mandate and leaving Thúy Vân vulnerable to Thúy Kiều image leaks. Once the remediations in Findings 1 and 2 are applied, Milestone M1 will achieve 100% compliance.

---

## 6. Verification Method

To independently reproduce the findings:

```powershell
# 1. Reproduce Thúy Vân safeguard leakage via Step C / Step D
python -c "
import sys
sys.path.insert(0, '05_Production_Pipeline')
import production_orchestrator as po

adv_tv = {
    'reference_start_frame': '04_Assets/characters/01_Main_Protagonists/thuy_kieu_maiden_16yo_720p.png',
    'scene_title': 'Thúy Vân trang trọng đoan trang',
    'motion_prompt': 'Thúy Vân mỉm cười ung dung'
}
res = po.resolve_start_frame('ep01_scene01_shot05', adv_tv)
print('Resolved:', res)
assert 'thuy_van' in res.lower() and 'thuy_kieu' not in res.lower(), 'FAIL: Thuy Van leaked Thuy Kieu!'
"

# 2. Reproduce Compound Anchor Safeguard disablement
python -c "
import sys
sys.path.insert(0, '05_Production_Pipeline')
import production_orchestrator as po

adv_kt = {
    'character_asset_ref': '04_Assets/characters/01_Main_Protagonists/thuy_kieu_maiden_16yo_720p.png',
    'scene_title': 'Kim Trọng viết lời thề nguyền',
    'motion_prompt': 'Kim Trọng cầm bút lông viết chữ'
}
res = po.resolve_start_frame('ep01_scene10_shot07', adv_kt)
print('Resolved:', res)
assert 'kim_trong' in res.lower() and 'thuy_kieu' not in res.lower(), 'FAIL: Kim Trong leaked Thuy Kieu!'
"

# 3. Existing test suites run
python -m pytest tests/test_critic_gate.py -v
python -m pytest tests/test_m1_challenger2_probe.py -v
pytest tests/test_tier1_features.py -k "not test_render" -v
```
