# 과제 정본 (SOT) — 2026-08-29

> ## ★★★ 최상위 불변식 — HITL 0 (사용자 2026-09-03 재지시 · Codex 정정으로 옛 「verified 요구」 폐기)
>
> 실서비스 기본 주행은 **사람 없이 끝까지 간다.** 사람 판정/선택 행이 **0건**이어도 파이프라인은
> 멈추지 않고, 검색 → VLM 자동 선택 **한 장**이 실제 참조로 붙는다.
> - `verified` 부재가 attachment 0 · `fidelity_unverified` 제외 · blocked · partial · 재개 지문 대기 중
>   **어느 것으로도** 작동하면 실패다. production 소비자(정책 lane · sidecar · canonical ref · 야외 투영)는
>   중앙 CP 를 **날것으로** 읽고 `usable_as_reference` 는 `outcome == selected` **만** 본다.
> - 사람 review/selection(8943 화면 · `grounding_reference_fidelity_review`/`_selection` 표)은 **코딩
>   canary · A/B · 사후 품질 평가 · 선택 override 도구**다. production 의 depends_on · config hash ·
>   usable gate · 운영 절차가 되면 안 된다. 명시적 거절/선택은 도구(`apply_reviews`)가 CP 산출에 옮길 때만
>   반영되고, 「아무 결정 없음」은 자동 선택 유지다.
> - 자동 획득 순서(사용자 지정): ①텍스트 검색으로 「무엇인가」+「이미지에서 확인할 시각 기준」을 구조화해
>   VLM 판정 문구를 만든다 ②처음엔 좁고 조밀하게 이미지 검색 ③그 문구로 VLM 판단 ④없으면 뼈대만 남겨
>   넓게 재검색하고 가장 비슷한 후보를 다시 VLM 판단 ⑤**반드시 한 장** 자동 선택. 작품 명사·나라/장소
>   목록·regex·하드 프롬프트 0.
> - 실측 (2026-09-03 새벽): 사람 검토 75건 뒤에도 usable 1/18 — 사람을 파이프라인 안의 문으로 세운 결함.
>   고친 자리: `reference_acquisition.usable_as_reference` · `detail_steps._write_grounding_sidecar` ·
>   `episode_reference_policy_step` (CP 읽기 + 지문) · `grounding_canonical_ref_inputs` ·
>   `outdoor_structure_form_reference_step._project_front_checkpoint`. 시험 31개를 이 계약으로 뒤집음.


> **왜 이 파일이 생겼나.** 2026-08-29 사용자가 「고증 말고 남은 태스크 없냐」고
> 물었는데 답할 근거가 없었다. GitHub 이슈 0개, 저장소에 목록 문서 없음 —
> 번호는 Codex 가 매기고 Claude 는 **기억에 의존**하고 있었다.
> 그래서 대화 밖에 둔다. **여기가 정본이다.**
>
> - `TASK-*` 는 과제 번호, `PR #*` 는 GitHub PR 번호. **다른 열이다.**
> - 상태를 바꿀 때는 **실물 근거**(커밋 sha · 측정값 · 파일 경로)를 함께 적는다.
> - 「완주」와 「문제 해결」을 가른다.

### 자동 획득 5단계 (사용자 2026-09-03 · 구현 2026-09-03 새벽)

| 조각 | 상태 |
|---|---|
| ① 텍스트(웹) 조사 — `grounding_target_research` (팩 `1.202609030300`): 뼈대+원고 근거(전문) → `what_it_is` · `appearance_criteria`(VLM CRITERIA) · `narrow_queries` · `rough_queries` · 출처. 대상당 **한 번**, 재개는 앞 라운드 기록을 되쓴다 | ✅ 무료 시험 |
| ② 좁은 이미지 검색 — 1라운드 = `narrow_queries`(좌표 기계 보강) | ✅ |
| ③ VLM 판정 — `coarse_type_pick` 팩 **v2** (`2.202609030300`): `object_type_match` · `visible` + `criteria_match` · `similarity 0~100`. eligible = 셋 다 yes(모든 심판) | ✅ |
| ④ 넓은 재검색 — 2라운드 = `rough_queries`(뼈대 + 좌표) | ✅ |
| ⑤ 반드시 한 장 — `NEXT_SELECT_CLOSEST`: 마지막 라운드까지 eligible 0 이면 받은 모든 후보 중 similarity 최고(종류 `no` 합의는 뒤) · `match_quality: closest` · `forced_pick`. 심판이 하나도 유효하지 않으면 retryable(못 봤다) · 후보 0 이면 no_match | ✅ |
| production 배선 — `reference_acquisition_step._write_brief` → `gtr.make_writer` · `_judge(candidates, criteria=)` · adapter `search_target.source_quotes`(원고 근거 전문) | ✅ |
| 유료 canary — 새 팩 신원으로 조사 단계 재실행(17 대상) | ✅ **2026-09-03 02:11→02:58 완주** — 대상 17/17 selected(기준 일치 13 · closest 강제 4) · alias 정산 0 · `grounding_target_research` web_search 17 = 대상 17(llm_call_log) · 글 누계 290→352 · `research_acceptance.py` overall_ok(10 검사 + 감사 1 · 언어 잠금 어긴 질의 22건은 비차단 후속) · OpenAI primary 키 credit 소진 → secondary 슬롯 |
| 이미지 단계(참조→합성→씬 샷) | ✅ 03:52→04:07 완주(exit 0): scene_detail 9/9 · world_guide · 참조 10 · 합성 8 · 씬 16 = 이미지 34 · 글 61 · 누계 458/480 · 거절 0 · failover OpenAI 429→secondary. (03:07 attempt 는 partial — S4_Shot4 PRO-13: HITL 0 로 소품 여섯이 전부 강제되자 빈 visible 이 문을 열어 여섯을 다 요구 → 9cd30564 수리) · exit probe: S1_Shot2 sha 3(LP03·L01·C01O01)/S2_Shot6 sha 2(LP05·L03) 전부 요청에 실림 |
| 두 샷 A/B (S1_Shot2 · S2_Shot6 — 이 run 의 실제 사이드카 부분 참조 LP03·LP05) | ✅ clone A/B(arm B = LP03#detail,LP05#detail 참조 줄 제외 · diff 는 grounding 두 키 · capture 는 sha 하나씩 · 텍스트는 Reference image 역할/번호 줄만) · fresh 생성 각 image 4 · text 5 · 갤러리 `artifact/20260903_period_ab_gallery/index.html`(순서 가림 4쌍 · 열쇠 `artifact/ab_key.json`) |
| 사람 사후 평가(§6.5b) | ✅ **마감(2026-09-03 10:20 · 사용자 판단)** — 「사용자 판단: 현재 결과는 잘 되는 것으로 보이며 추가 수동 평가 없이 마감」. §6.5 갤러리 평가는 선택적 후속/보존된 감사 자료 · production HITL 요구 0 · 미실시를 실패나 BLOCK 으로 해석하지 않음. A/B 승패는 적지 않는다(판정 안 함). 패키지는 보존: 양식 `artifact/20260903_human_eval_template.md` · A/B 갤러리 · 참조 고증 화면 둘(중앙 86장 · 보충 8장) · seed 산출 — HANDOFF ⑥ 「§6.5 사람 평가 패키지」. 결과와 근거를 여기·설계문서에 적는다. production 문이 아니다(HITL 0) |
| 검토 화면(도구) 대상당 한 물음 | ✅ `fidelity_review_page.render` — 대상마다 「쓸 사진 하나(라디오) / 없음」 + 버튼 하나(`decide`: /select 또는 기계 선택에 rejected) · 후보당 세 버튼 폐기 · HITL 0 문구(검증용 도구 · 실서비스는 자동 선택) · 질의 출처(우리 것/도구 확장) 대상당 한 번 · 시험 17 |
| 언어 잠금 감사 정정 | ✅ 우리 질의(directive·terms)만 판정 · `site:`/URL 은 언어가 아니라 빼고 셈 · provider 확장은 관측으로 따로 — run 45075f79441e: 우리 것 1건(P06 r1 영어 낱말 섞임 · 비차단) · 확장 비한국어 0 |
| **야외 ON 유료 canary** (structure_form 보충 실측) | ⬜ **전제 미충족** — 야외 lane 은 `background_classify`(if_background_mode) 에 매달려 background on 이 필요하고, fresh dry(run f7e61b1ba1e3 · background on · outdoor on)에서 계측 단위 **unverified 10 스텝**(background_classify · master_plan · floor_plan_prompt/render · background_prompt · outdoor_place_spec · lane_plan · place_canon · structure_form_reference · structure_seed · background_render)이 남아 live 문이 열리지 않는다. 같은 run(45075f79441e)에 outdoor on 은 scope 불일치로 거부(별도 run 필요). Codex 정정(04:30): target 을 `outdoor_structure_form_reference` 로 잡으면 closure 의 미확인은 **넷**(background_classify · outdoor_place_spec · outdoor_lane_plan · outdoor_structure_form_reference) — loop 를 읽어 선언함: classify=single(재시도 3) · place_spec/lane_plan=`outdoor_group`(fixture `OUTDOOR_GROUP_CAP`=2 · 시도 3) · form_reference=`outdoor_group` × (정상 3 · hard 5). seed/render 여섯은 후속 묶음. 야외 acceptance 는 Codex 목록(fresh · HITL 0 · 보충 1행/장소 · 중복 0 · projection 에 final_id/path/SHA · 재개 provider 0 · 422 사유) |
| **야외 ON 유료 canary 1판 결과** (run `4398a55dc0bb` · 04:26~04:43 · exit 0 · 글 누계 63/480) | ❌ **③④⑤ 미확정** — 실제 수 먼저: building_groups 3 · 실외 그룹 **1**(bg_alley) · unique 실외 location **1**(L02) → 상한 2 이하. 그러나 `step_manifest` 에 `outdoor_structure_form_reference → reference_acquisition` 의존이 **없어** 닫힘 32 스텝에 중앙이 빠졌고(step_run 에도 없음), 앞쪽 CP 가 없으니 `acquisition_owner(v2_chunk, front=False)` 가 OWNER_OUTDOOR 로 내려가 야외 스텝이 **제 legacy 경로로 5라운드**를 샀다(llm_call_log 4회: gpt-5.6 ×3 · sol ×1). 보충 CP 0 · journal 0 · `projected_from None`. acceptance(`outdoor_acceptance.py`) ①②⑥⑦ ✅ / ③④⑤ ❌. 자동 재실행 안 함. **수리(side worktree)**: `918e1fbb` manifest depends_on + 사는 모드에서 front 없으면 `FrontCheckpointMissing` → 422 `form_reference.front_missing`(legacy 소유자는 사지 않는 모드에만 · 옛 fallback 시험 뒤집음) · `e3d6a104` producer 상한 문(`assert_outdoor_group_cap_covers` — production 술어 `outdoor_groups_of` 한 곳 · `run_pipeline(before_step=)` 이 그 단위 스텝 직전에 부름 · 네 단위 `PRODUCER_CAP_GATES` 표) + 언어 문(기대 문자 체계 = lock+terms_native · 좌표 지난 최종 질의). 분모 고정: 그룹 수 = 계측·상한 분모 · 보충 행 = **필수 unique location 마다 live 1행**. 다음 판은 의존이 바뀌어 **새 run** `f7cc45c576c0`(닫힘 +5: grounding_screen · outlook_phase1/2/3 · reference_acquisition) — dry 수를 live 전에 Codex 에 보고. |
| **야외 ON 유료 canary 2판 = 같은 run 재개** (`4398a55dc0bb` · 05:04~05:13 · exit 0 · 누계 94/480) | ✅ **acceptance ①~⑦ 전부** — transition 1bf41bd0→563db91f→5dd3b92e 뒤 dry scope 일치(fresh 안 씀) · forced_for_drift=[outdoor_structure_form_reference] · 그룹 문 checked groups 1 · unique loc 1. 중앙 reference_acquisition 16 대상(selected 7 · retryable 9 — 내 정지 요청이 맞힘 · not_applicable 10) → 야외 스텝이 **보충 경로**로: supplement live 1행 `L02#structure_form` selected · projection bg_alley ok · path `L02#structure_form_r1_03.png` · sha 9bcfe4f9… · `projected_from reference_acquisition`. 3판 재개 **provider 0**(llm_call_log 44→44 · 누계 94 그대로). **Codex 긴급 BLOCK(05:08)**: 중앙 상한이 dry 때 screen CP 없이 선언 하한 5 → 25 였는데 live 안에서 의무 20 — 협조 정지(cancel_requested_at) 요청. 실측 cap 미도달(purchases.cap 32) 이었지만 계약을 넣음: `assert_central_cap_covers`(provider 앞 · screen CP counts.obligation vs `plan_basis.central_target_count`) `faf70a87` · 근거는 dims 가 아니라 `plan_basis`(dims 에 넣자 `assert_scope` 가 섬) `00f02dc1`. **정지 요청이 「검색 실패」로 접혀** 9행 why "" · disposition acquired → `run_control.is_abort` 로 세 except 에서 올리고 `row_why` 로 마지막 라운드 까닭 `cd210f76`. 미정: retryable 9 를 `--steps reference_acquisition` 으로 열지(≈30콜) — Codex 검토 중(「completed 로 봉인」을 production 결함으로 봄). |
| **배경 사슬 네 스텝 계측 단위** (통합 canary 전제) | ✅ 무료 `295ff202`·`d0ff43dc`(main) — loop 를 읽어 선언: `background_master_plan` chain_bg 그룹마다(술어 `chain_groups_of` 한 곳) · `floor_plan_prompt`/`floor_plan_render` floor plan 마다(글 2 / 이미지 (1,3)) · `background_prompt` background 마다. fixture 셋에 `CHAIN_GROUP_CAP · FLOOR_PLAN_CAP · BACKGROUND_CAP` — `background_classify`·`background_master_plan` CP 를 세어 넘으면 provider 앞에서 선다(`PRODUCER_CAP_GATES` 셋 더함). 배경 on → scene_detail 닫힘의 unverified 0(앞 dry 넷). 남은 unverified: outdoor_structure_seed · background_render · shot_ref_classify · shot_continuity · shot_conti_light(이 닫힘 밖). |
| **취소에 맞은 대상 = 자동 재시도 빚** (Codex BLOCK 05:30) | ✅ 코드 `fe428dae`·`3ebd32dc`·`85617c9c`(main) — 실물 CP(retryable 9 · 후보 0)가 completed 로 봉인돼 plain resume 이 SKIP 할 자리였다(`rows_to_rejudge` 가 후보 있는 줄만 셈). `pending_rows`(공개)가 후보 있는 retryable=재판정 · 후보 없는 retryable=재조사 로 갈라 둘 다 미완료로 세고, `central_wrap.failed_count` 와 `verify_completion` 이 같은 total 을 쓴다(retryable 은 raw terminal 아님 · `no_match_after_retry` 만 terminal · 사람 대기 문구 없음). 끝점: production `StepRunner._evaluate_resume_decision(resume)` 이 그 CP 를 RERUN_SELF(artifact_missing) 로 · 깨끗한 CP 는 SKIP · 장부 ok 되쓰기/incomplete 재구매 · 정지 요청은 스텝 밖으로. canary 쪽: `completion_debt_of`(runner 의 verify 로 물음) → `plan_reentry(debt=)` 정상 cap · dry 의 `reopened_for_debt` · 코드가 다시 연 중앙도 검색·받기 문을 표의 값으로. dry: 중앙 reopened_for_debt · cap 100(의무 20) · 문 120/240. **live 4판(plain resume) 05:52 시작** — 7 selected 장부 되쓰기(provider 0) · 9 만 구매 · 하류 야외 CP · 마지막 재개 provider 0 을 잰다. |
| **live 4판 = 빚 재개 plain resume** (`4398a55dc0bb` · 05:52~06:00 · exit 0 · 글 32 · 누계 126/480) | ✅ **acceptance 1~6 실측** — 1) 앞 판 selected 7 재구매 0(장부 attempt2 대상 9 == 앞 판 retryable 9) 2) rejudge 0 · research_retry 9 → 아홉 다 selected(criteria 7 · closest 2) 3) 최종 CP completed · failed_count 0 · pending 0/0/0 · rows 26 = selected 16 + not_applicable 10 4) outdoor 스텝 재실행 없음 · production reader (`outdoor_reference_rows`) 로 L02#structure_form identity aa3e0d4f… 동일 · projection sha 9bcfe4f9… 동일 · supplement provider 0 · `outdoor_acceptance` 7/7 5) 직후 plain resume provider 0(llm_call_log 62→62) 6) 검색 10/120 · 받기 37/240 · 원본 fetch 42 · why 빈 retryable 0. 감사 사실: 실행 tip `85617c9c`, 그 뒤 HEAD `1bf77d07` = 문서 3개뿐(코드 0) — Codex 확인, 재시작 없이 관측. |
| **통합 canary** (`f7cc45c576c0` · 배경 on + outdoor on → scene_detail · 06:21~07:26 · 5 attempt · 누계 글 228/480 · 이미지 1/18) | ✅ **배경→scene_detail 통합 통과**(Codex 판정) — 중앙 15/15 selected · retryable 0 · 배경 사슬 5스텝 completed · scene_detail 8/8 · payload probe(production reader) 5/5(sidecar lane L01·L02·L03·LP03·LP06 · props 정책 lane · 겹침 0 · 새는 것 0) · HITL 0 · 최종 씬 이미지 0. **한 run 야외 통합은 미측정**(scene_detail 닫힘에 야외 lane 없음 — 실측; 야외 보충은 `4398a55dc0bb` 7/7 로 독립 통과, 새 유료 run 안 엶). 다섯 번 서고 고침: ① entity 큐 (name,type) dedupe → LP07 소실(`13d5b551` short_id 신원 · 팩 v15 `f9bc5973`) ② 제 팩 버전 실수(9.x 를 최신으로 오독) ③ sync 가 동명 둘을 한 행으로(`5c5afa7f`) ④ floor_plan_render 스텝 문 0(`fecf048b`) ⑤ plain resume 가 scene_detail 을 매번 재구매 — episode_reference_policy 결과에 config_hash 없어 저장/비교 지문 불일치 → 매 재개 force → 하류 stale(`2eecf788`). **재개 idempotency 실측**: 연속 plain resume 세 번 counted 0 · provider 0 · RECOVERY 0 · status 변화 0. outbound 문은 소비자 계약(가벼운 모듈 · AST 잠금)으로(`eb2ca749`·`45828298`). 팩 지문 결속 `d6f9cac4`. |
| **한 run 야외 통합** (같은 run `f7cc45c576c0` · target 만 scene_detail→outdoor_structure_seed · 2026-09-03 09:02~09:30 · attempt 3 · 글 +15 · 이미지 장부 차감 +3/실제 성공 2) | ✅ **통과** (Codex 09:20·09:27 승인 · acceptance A~F overall_ok · `artifact/canary_f7cc45c576c0/seed_integration_acceptance.json` · `link_report_seed.txt`) — 중앙 CP 를 앞으로 둔 21.915 가 보충 L02#structure_form 을 같은 공장·journal 로 샀고(1판 5콜) force 재실행은 journal 재생으로 provider 0 · 투영이 ImageAsset `a34cdc4f`(structure_form_ref/bg_alley/form_ref · 파일 sha == CP sha 9bcfe4f9)를 묶고 → seed CP `form_reference_input`={a34cdc4f, 9bcfe4f9} → seed 자산 `636a7867` 의 input_image_ids=[a34cdc4f] (양방향). 서고 고친 것 셋: ① entity_detail 팩 지문 결속으로 hash 이동 → canary force 면 하류 59 CP 삭제(dry 가 안 보여 줌) → **정확한 hash adoption**(`canary_hash_adoption` · tuple 결속 · data 불변 · 사건 8cd4198bba62 · 전이 6573acc25bb8) + dry 가 force 마다 지울 하류를 찍음 · 범용 drift_ack 는 Codex BLOCK 으로 걷음 ② 투영 경로가 자산을 안 묶어 seed fail-closed(1판) → `_bind_projected_asset`(uuid5 · insert-or-verify · `FRONT_PROJECTION_CONTRACT_VERSION` 2.202609030915) ③ 2판 seed 첫 롤 gpt-image-2 「upstream disconnect」(SDK 재시도 0 잠금) → Codex 승인 plain 재개 1판(18/5/13 기록) 완주. 계측 단위 shot_ref_classify(SINGLE 8/5)·outdoor_structure_seed(OUTDOOR_GROUP 10/4 · 이미지 (2,6)) 선언 · 남은 unverified 3(background_render·shot_continuity·shot_conti_light) |
| **대상 병렬** (Codex 계약 7 · 2026-09-03 03:00, branch `feat/grounding-parallel-acquire` de94ccec): `ca.run(workers=)` bounded pool(설정 `grounding_acquire_workers` 기본 4 · 상한 8) · 1차 전원 join 뒤 2차 · 결과 index 순 · 워커는 {ident,result} 만 · `bind_current_research_budget` · subject_id 중복 fail-closed · `OutboundBudget`/`RawFetchObserver` RLock · 조사 저작기 legacy alias 차단(alias 0 끝점 시험) · memo (sid, fp) | ✅ 무료 시험 14 · live 병합은 유료 판 종료 뒤 |
| A/B arm B = 날것 CP 에서 참조 줄 제외(리뷰 삭제 아님 · HITL 0) | ✅ (같은 branch) |
| **야외 structure_form 보충 획득** (설계 §8 · 6412fe5b): `grounding_outdoor_supplement` — 그룹→실외 loc_id(ID 로만)→`structure_form` 줄 없는 장소마다 의무(뼈대=중앙 ledger_row 복사 · 부류=place spec `structure_desc`) · `ca.run` 을 **같은 공장**(`reference_acquisition_step.make_search/make_download/make_judge/make_writer` 모듈 함수)으로 · 별도 CP `reference_acquisition_supplement` append-only(`superseded_by`) · merge view `outdoor_reference_rows` 한 곳 · 투영은 그 view 만 · 사람 대기 없음 | ✅ 무료 시험 11 · **outdoor ON 유료 canary 는 아직**(`OUTDOOR_*` 켠 판 필요) |
| 병렬 fatal 뒤 pending 0 (Codex BLOCK): `_pool` as_completed · cancel · 도는 것 join · 워커 abort Event | ✅ 시험(job1 fatal · workers 2 · 뒤 job 0회) |
| Codex BLOCK 1~3 (56047391): REPLAY_JOURNAL 복원 · 보충 의무는 base 만 막음(옛 보충 줄은 ca.run/장부 판단) · merge 는 유효 결과 비교 · atomic_write_json · validate_targets 가 provider 앞 · stop_check | ✅ 끝점 4 + 계약 시험 · 재리뷰 대기 |

## 지금 할 일 — 순서

| # | 무엇 | 유료 | 상태 |
|---|---|---|---|
| — | pyright 설정 | 무료 | **완료** — PR #58 병합 (거짓 오류 183 → 12) |
| 1 | **GROUNDING-V2** 계약·플랜 문서 PR | 무료 | **완료** — §2-0 |
| 2 | **GROUNDING-V2** 무료 전수 감사표 | 무료 | **완료** — §2-1 |
| 3 | `feat/grounding-v2` worktree — schema + Sol classifier + planner (검색 호출 0) | 무료 | **완료** — §2-2 |
| 4 | `shadow_plan` 회귀 → text-first phase + 검색 이관 → `v2` canary → 눈가림 A/B | 일부 유료 | **진행 중** — 아래 표 참조 |
| 5 | **TASK-107** services 39 분류 + 전체 59 manifest | 무료 | ★**V2 프로덕션 병합 직전 관문** — 무료 감사를 막지 않는다 |
| 6 | 통합 E2E 한 판 — TASK-59 / 55 / 31 / 71 **표는 넷으로 분리** | 유료 | 4·5번 뒤 |


### ★★★방향 재설정 (2026-08-30 저녁 · 사용자 확정)

**조사 여부를 「출처가 시대 차이를 말하나」로 정하려던 틀이 틀렸다.**
§2-3c 유료 1회가 아홉 축 **전부 미확정**으로 끝나 그것이 드러났다 — 「다르다」는
증명되는데 「같다」는 아무도 출판하지 않아 증명이 안 된다.

사용자 설계는 이렇다:

```
기존 축 (그대로 둔다)   등장 2번 이상        → 중요 엔티티로 추출
얹는 예외               이미지 모델이 만들기 → **1번만 나와도 엔티티 등록**
                        어려운 것              (옛 화폐 · 옛 브랜드 제품 ·
                                                잘 알려진 장소 …)
                              ↓
                        그 엔티티는 **검색 → 이미지 검색**을 탄다
                              ↓
                        사전 검색 = 판정이 아니라 **「무엇을 찾을지」 자료 모으기**
                        이미지 검색 결과 3~5개를 **한꺼번에** VLM 에게
                        VLM 은 **「우리가 찾던 종류가 맞나」만** — 자동차인지 화폐인지
                        없으면 **질의를 좁혀 한 번 더** 검색
                              ↓
                        고른 참조로 이미지 생성
```

★**새로 만드는 것이 아니다.** `search_grounded_ref.py`(958줄)에 검색·안전
다운로드·VLM 선택·재검색이 통째로 있고, 지금 **뒤쪽**(야외 구조물 21.915)에만
쓰인다. **호출 소유권을 앞으로 옮기는 일**이다.

★내가 오늘 `difficulty` 를 폐기한 것이 잘못이었다 — 그 축이 **예외의 문**이었다.

| 조각 | 상태 |
|---|---|
| `generation_difficulty` 3값 복원 — 보존 신호로만, route 는 안 가름 | ✅ `55c88ce9` |
| 어려운 단발 대상을 **실제 엔티티 행으로 등록** | ✅ `34cf1925` |
| 거친 종류 선택 팩·combine (`object_type_match`+`visible`, 평균 안 냄) | ✅ `728282fb` |
| 참조 획득 소유권을 `entity_filter` 뒤 / `entity_detail` 앞으로 | ⬜ |
| `image_steps` 계약 SHA 를 **새 앞쪽 계약**으로 (지우지 않음) | ⬜ |
| 21.915 를 **read-only adapter** 로 (삭제 아님 · late 호출 0 먼저) | ⬜ **원인 확정 2026-09-03** — 소유권 문 `acquisition_owner` 가 `buys_v2_research`(=v2 만) 를 봐서 `v2_chunk` 에선 중앙 CP 가 있어도 야외가 **다시 산다**(같은 규칙 두 곳 · manifest 는 `if_grounding_reference`) · 소유자가 앞쪽이면 `_project_front_checkpoint` 가 `NotImplementedError` · 야외 pick 팩(v11:18,24)은 VLM 에 **시대·나라를 판정시킨다**(goal 5 위반). canary 는 `OUTDOOR_*=false` 라 안 보였다. 설계 `docs/design/2026-09-03-grounding-v2-21915-adapter.md` · **Codex 판정 01:40**: 술어 SOT·fail-closed APPROVE, 투영은 `central_cp_with_reviews`+`usable_as_reference`(사람 확인) 와 `structure_form` **목적 일치**만, members 여럿이면 선다, 보충 구매는 `ca.run` 전체 경계 + supplemental CP append-only + 야외 전용 merge 한 곳 (§6) |
| 21.915 소유권 — **사는 모드 + 앞쪽 없음 = fail-closed** | ✅ 코드 `918e1fbb`(side) — 실측 `4398a55dc0bb` 에서 「앞쪽이 없으면 legacy 로 산다」가 의존 누락을 조용히 덮었다. `acquisition_owner` 는 `buys_reference(mode)` 인데 front 가 없으면 `FrontCheckpointMissing`, 스텝은 422. legacy 소유자는 사지 않는 모드에서만 남는다(시험 대역·부분 조립 54건 그대로). 이관 완료 선언은 여전히 fallback 이 **사지 않는 모드에도** 없어질 때. |

★§2 표의 「4 text-first phase + 검색 primitive 이관」 줄이 이 일이다. 단계 수는
안 바뀌고 **무엇을 이관하는지**가 바뀌었다.

### GROUNDING-V2 — 지금 어디까지 왔나 (2026-08-31)

★acceptance 정의는 `docs/design/2026-08-29-grounding-v2-plan.md` §2 가 정본이다.
여기는 **현재 상태만** 적는다 — 조건을 여기서 고쳐 쓰지 않는다.

| 단계 | 무엇 | 상태 |
|---|---|---|
| 0 | 계약·계획 병합 | ✅ |
| 1 | 전수 감사표 | ✅ |
| 1.5 | 선행 PR (`or` 사슬) | ✅ |
| 2 | schema + classifier + planner (바깥 호출 0) | ✅ |
| 3a | `shadow_plan` offline 재생기 | ✅ |
| 3b | production mode 배선 | ✅ |
| **3c** | 지정 대조군 아홉 축 | ⏸ **미확정 9/9** (2026-08-30 유료 1회 · exit 1) — **맞음 0 · 어긋남 0 · 미확정 9**. 검색 전 route 는 아홉 축 **모두 research×6 만장**이고, 갈린 자리는 **sourced delta** 다. ①필수 구별점을 못 찾아 7축 ②「차이 없음」을 말한 출처가 없어 2축. 누계 논리 42/42 · 물리 시도 44/84 · 검색 9/18. 기록 `artifact/20260830_3c_designated_controls/` (`--replay` 로 무료 재채점) |
| 3.5 | 후보 승격 overlay + 완전성 gate + 분류기 배선 | ✅ |
| **4a** | sourced-claims primitive + sourced-delta 결정 API | ✅ (PR #75~#78) |
| **4b** | text-first phase production 배선 | ⏸ **PASS 철회 유지** (Codex) — route 는 계약대로 고쳤고(`01d772c5`) 폐기 축을 schema·지시문·record·지문·planner·시험·계약 SOT·fixture 에서 다 걷었다(`d0c20011`~`c25581b5`). ★그 과정에서 **main 의 live 결함**이 나왔다: 검색 스키마에 `claim_id` 칸이 없는데 `build_revision_row` 가 그 칸을 무조건 대조해 **모든 조사 결과가 `unresolved` 로 닫혔다** — `research_required → reference_required` 강제가 한 번도 안 걸렸다. 되돌리는 것은 §3c 유료 주행이 실제로 `delta=yes` 를 내는 것을 본 뒤 |
| 4.7 | 아웃룩 phase2 모순 수리 | ✅ |
| 5 | prop/character 한정 canary | ⬜ — ★자동은 **실행 무결성만**(중복 구매 0 · resume 재구매 0 · 캐시 키 · rollback byte/provenance). **이미지가 고증상 맞고 나아졌는가는 사람 acceptance** |
| **C(c)-A** | 구간당 단일 판독 producer — 샷 catalog · runtime enum · **세 상태** · 격리 · 두 신원 | ✅ **inert** (활성화 0 · 유료 0) |
| **C(c)-B** | 합성 에피소드 무료 끝점 + 실제 20 에피소드 검증 | ✅ 유료 0 |
| **C(c)-C** | 실제 에피소드 1개 **유료 canary** | ✅ **기계적 통과만** — 논리 5 · dispatch 5/5 · 물리 ≥6 ≤10 · 장부↔Opik 양방향 차집합 0 · 무표식 0. 산출 `artifact/20260831_cc_preflight/c_live_*`. ★**의미 품질은 아직 아무도 안 봤다** |
| **C(c)-사람** | v3 검토 화면 — 샷 결속 의미 · 두 축 · 엔티티/facet 품질 | ⏸ **사람 GO 대기**. `artifact/20260831_cc_preflight/c_live_veto_review_v3.html` |
| **C(c)-D** | `entity_merge` 호환 adapter (provenance 한 벌 + 소비 전 계약 검사) | ✅ **inert** — `app/` import 0 · `STEP_CLASSES` 0 · manifest 무변 |
| **C(c)-D 활성화** | 원자적 전환 — 새 producer ON · 옛 v2 provider OFF · screen/filter 결정적 · `generation_difficulty` 제거 · stamp bump 를 **같이** | ⚠️ **켰다 · 아직 한 칸 비었다** (2026-09-01, `2520ab8c` + BLOCK 수리 다섯 판 `97a53ca0`·`f1348082`·`a0935f47`·`7ec1727a`·`c9cf3f8e`·`bfac171b`). ★차례표 `docs/design/2026-08-31-grounding-v2-D-cutover-order.md` §2 의 아홉 중 **①③④⑤⑥⑦⑨ 됐고 ②의 뒷반과 ⑧ 이 남았다** — ⑨ 무효화는 manifest stamp 가 아니라 `fingerprint_value` 가 `v2_chunk` 를 **제 값으로** 접어 config_hash 로 움직인다(확인함). ★★**②의 뒷반 — 2026-09-02 닫았다.** `episode_reference_policy` 가 `v2_chunk` 에서 **중앙 조사 CP** 를 읽고, 갈래마다 집행자가 하나인 표(`REFERENCE_ENFORCEMENT_BY_OWNER`)로 갈린다 — 인물·소품은 정책 문, 장소·장소부분은 배경 묶음, **아웃룩은 인물 아웃룩 자리**. 아웃룩은 카드가 이미 요구하는 `C##O##` 짝에만 붙고(조사가 새 짝을 안 만든다), 역할은 **이미 선언된 `outfit_ref_explicit`** 을 되쓴다(production emitter 0곳 실측 · 지시가 「standalone outfit/costume · dress the character」라 맞다). 기존 합성본은 `grounding:` 앞머리 값 때문에 고증 부착을 **대신하지 못한다**(시험으로 못박음). 안전문에 ⑥「선언만 있고 집행하는 모듈이 없는 값」 축을 더했다. ★그 뒤 Codex 재리뷰가 **셋을 더 잡았고**(`1a57fbe9`) 다 닫았다: ①중앙 CP 부재·손상을 「대상 0」으로 삼키던 것(→`required=True` 다섯 문 + raw 상태와 처분 대조, `detail_steps` 의 넓은 `except` 제거) ②`scene_detail` 재개 지문이 새 sidecar 계약을 몰라 **옛 카드를 그대로 재사용**하던 것(→켠 판에서만 접는다, 옛 판 지문 불변) ③복장 사진이 **누구 것인지** 프롬프트에 없던 것(→`subject_final_ids` 결속, 없거나 둘 이상이면 선다, 아웃룩은 주인마다 따로 붙는다). ★회귀 대조는 **같은 명령으로 이전 tip 을 재서** 확정했다 — `c9cf3f8e` 109 · 지금 109 · 실패 집합 양방향 차집합 0. 앞서 인용한 「63」은 이 명령의 수가 아니었다. **아래는 닫히기 전 기록** —  `episode_reference_policy` 가 아직 `grounding_research` CP(:135)와 `grounding_plan._short_id`(:192)를 정본으로 읽는데 `v2_chunk` 에서 그 둘은 안 돈다. **실측**(무료, 프로덕션 스텝 객체로 직접): 중앙 CP 가 `C01`·`LP01` 을 `selected` 로 골라 뒀는데 강제 **0개**·막은 것 **0개** — 하류 `render_prompt_card.research_forced_refs` 가 받을 것이 없다. `db=None` 지름길을 빼고 다시 쟀다. 도구 `scratchpad/probe_policy_gap.py`. 앞서 적은 **(가) 접두 소비자 2곳은 닫혔다** — 둘 다 `reference_owner_of` 를 부르고 `REFERENCE_SUPPORTED_OWNERS` 가 다섯 갈래로 열렸다. 남은 것은 아래 (나) —

**(가) 활성 접두 소비자 — 2곳. ✅ 2026-09-01 닫힘** (둘 다 `reference_owner_of` 를 부르고 `REFERENCE_SUPPORTED_OWNERS` 가 다섯 갈래로 열렸다 — 아래는 **닫히기 전** 기록이다.) `episode_reference_policy_step.py:174` · `render_prompt_card.py:2197`(`research_forced_refs`). 둘 다 `C`/`P` 만 받고 `location_part`·`outlook` 을 「없는 §2-6.5 producer 몫」으로 막는다 — **같은 규칙이 두 곳**이라 한 계약으로 모아야 한다. ★`background_planner`(19.55)는 manifest 가 `applicability='disabled'` 인 **죽은 스텝**이라 D BLOCK 으로 안 센다(내가 처음 셋으로 셌던 것을 정정). `export_service.py:96` 은 파이프라인 밖 API 라 **별도 debt**.

**(나) 원자적 cutover 전체** — 파서 둘보다 크다. `episode_reference_policy` 가 아직 `grounding_research` CP/DB revision 과 `grounding_plan._short_id` 를 **정본으로 읽고** manifest 도 `grounding_research` 에 의존한다. `v2_chunk` 에서 옛 plan/research 를 no-call 로 만들려면 **source 와 dependency 를 새 중앙 acquisition CP 의 `final_id` + raw `status` + `outcome`** 으로 같이 바꿔야 한다. `selected` 만 참조 의무를 만들고 `reference_unavailable` 는 **감사에 남되 막지 않는다**. render card 도 **같은 projection 한 벌**만 소비한다. 여기에 새 producer/adapter ON · 옛 provider OFF · filter/protection · `generation_difficulty` 제거 · stamp bump · invalidation 이 **한 판**에 들어간다.

**잠재 결함** — `LP01` 을 location 으로 오독하는 것은 `background_planner_step.py:315`(죽은 스텝)와 `export_service.py:96`. `background_chain_planning.py:146` 은 `[1:].isdigit()` 로 막힌다. DB 에 `LP` 접두 **0개**라 지금은 자고 있고 D 가 `location_part` 를 materializable 로 만드는 순간 깨어난다. 계약 함수 `owner_of_final_id` 사용처는 **3곳(전부 adapter)** |
| **D 무료 inert** | 계약·helper·끝점 — **활성 경로 새 실패 0** | ✅ 2026-08-31. `grounding_host_context`(세 상태·결정적 합집합·구간별 사유) · `grounding_reference_bundle`(맥락+상세 묶음 · 좌표 갈래 · 면제 못 뚫는 정확 부착 · 원자적 `write_sidecar`) · `grounding_relation_projection`(`part_of` 를 갈래로 가름 · 타입별 delta) · `grounding_entity_sync_ext`(표식 문 · 갈래별 번호 · `short_id` 우선 canon 열쇠) · `split_final_id`(한 계약) · 역할 둘 선언 · **provider 능력 계약**(client instance 가 낸다) · **유료 canary 문**. ★Codex 가 **재현한 결함 9건**을 닫았다 |
| **D 끝점** | **진짜** `build_scene_attached_refs` 를 태운다 | ✅ 무료 16개. sidecar 없으면 **반환 tuple 전체가 같고**(비회귀), 있으면 묶음이 `attached_meta`·프롬프트까지 간다. 요구 미기록·불일치·해시 불일치·파일 없음·**붙이는 쪽 무력화**가 전부 선다. ★`assert_from_rpc` 는 **별칭 import** 라 codegraph 가 caller 0 으로 봤다 — AST 로 확인 |
| **D 유료 ④** | 실제 provider 입력 이미지 수 | ⏸ **Codex GO 대기**. 문(`bundle_canary_preflight`)은 지었다 — 이미지 생성 **1회** · 고정 샷 exact N · 참조 **bytes 까지** 잠금 · 능력은 **client 에게** 묻고 `max=None` 이면 **발명 없이 기록**. ★실제 checkpoint 에 조사 sidecar 가 **0건**이라(D 가 inert) 자격 샷이 없다 — manifest 53벌·샷 3762행 전수 실측. 그래서 **전용 fixture** 로 간다 |
| **D-canary 격리** | 전용 fixture project 를 **production 스텝**으로 세운다 | ✅ **완주** (2026-09-01, run `9255933547c1`). **2씬 3샷** 원고(작품 고유명사 0) · 별도 PostgreSQL DB `theroad_canary_<run_id>` · `app.core.database` **import 전에** 격리 · Alembic 은 별도 프로세스이고 `env.py` 가 연결 **안에서** DB 를 확인 · 지우는 길 없음. 통합 입구 `canary_run.py` 가 격리→DB→migration→부트스트랩→pipeline 을 한 차례로 부른다. ★**43/43 스텝 완주 · `scene_detail` 까지 도달** · canary DB `completed 39 · not_applicable 3 · 미실행 0` |
| **D-canary 판정** | 여섯 축 · **미확정을 합격으로 안 바꾼다** | ✅ **6/6 맞음 · 어긋남 0 · 미확정 0 · exit 0** (2026-09-01). ①시작값 cumulative **42**/scope_cap **50**(장부에서 셈 — 손으로 안 적음) ②앞서 끝난 **39개 재구매 0** ③적용 제외 3개 `not_applicable`·counted 0 ④열린 판 0·terminal ⑤누계 **47/92**·이미지 **0** ⑥원본 sentinel 무변(파일 51382·지문 `dfee26b0…`·행 61/63/14469/3825/2). 도구 = `canary_report.py`(exit 1=어긋남·2=미확정·0=통과). 산출 `artifact/canary_9255933547c1/canary_report.json` |
| **D-canary 이미지 문** | 글 예산은 **이미지를 못 센다** | ✅ **막았다** (2026-09-01). `scene_detail` closure 안 `floor_plan_render` 가 `gpt-image-2` 를 사는데 글 문 다섯을 하나도 안 지난다 — 그대로 돌렸으면 장부엔 0 이고 실제로는 나갔다. ★`image_call_budget.reserve_current_call` 은 **예산이 없으면 no-op** — 「문이 있다」와 「문이 잠겼다」는 다르다. `canary_image_scope` 로 잠그고 `APPROVED_IMAGE_CALLS=0`. 실측 이 판 이미지 **used 0 · denied 0** |
| **D-canary 전송문** | 사후 경보기 → **provider 앞 문** | ✅ (2026-09-01, Codex BLOCK 반영). 앞 판은 스텝이 **끝난 뒤** delta 로 서서 목록 밖 스텝이 **이미 보낸 뒤** 잡혔다(`visual_continuity_anchor` 실측 1회). 이제 부르기 **전에** `StepScopedBudget`+`canary_step_cap` 으로 그 스텝 몫만 건다(무료·건너뜀·목록 밖 `cap=0`), run 전체 상한과 **동시에**. 막은 것은 `denied_by_step_cap` 으로 따로 적는다 |
| **D-canary 계획 거짓말** | 선언과 production 술어가 다르면 **아무것도 열기 전에** 선다 | ✅ (2026-09-01). fixture 가 `visual_continuity_anchor_enabled: False` 라 적었는데 그 규칙은 **`settings`** 를 보고 `.env` 는 `true` 였다 — 계획표는 「0원」, pipeline 은 사러 갔다. `if_*` 를 **`APPLICABILITY_VALIDATORS` 에게 직접** 묻고(`_production_says`), runner 를 보는 술어는 **AST 로 가려** 「모른다」로 둔다(`_if_has_outlooks`·`_if_planning_doc` 은 속으로 `except` 를 잡고 `False` 를 준다 — 모른다가 아니다로 둔갑). 다르면 `ApplicabilityContradiction` |
| **★Opik 은 성공만 남긴다** | 「Opik 에 없다」≠「안 샀다」 | ✅ **확정** (2026-09-01). litellm `OpikLogger` 에 `log_success_event` 뿐이고 부모 `CustomLogger.log_failure_event` 는 몸통이 `pass`. 실측 `afee7cf0238b` 장부 **27** vs Opik llm span **26**(차이 1 = `entity_all_character` 의 실패한 전송), `60cbec699525` **5 = 5**. ★돈의 정본은 **문에서 센 장부**, Opik 은 부분집합. 세는 법도 셋 고침 — 태그 아닌 **project_id** 로 결속 · trace 아닌 **span(`type=="llm"`)** · `…Z` 파싱 |
| **D-canary 상한** | 층 셋을 **갈라서** 잠근다 | ✅ 무료 (2026-08-31). 문 **위**(세어짐)=tier×키슬롯 · 문 **아래**(못 셈)=Router `num_retries`×SDK `max_retries`. 아래를 **진짜로** 잠근다(`canary_request_lock` — Router 짓기 전에 걸고, **지어진 객체에서** 0을 읽고, 못 읽으면 provider 0으로 정지). 실행 39(계측 35·무료 4) · 기대 counted 42 · 최악 252/756 · **승인 정지선 92/276**. 부트스트랩 유료 1회는 **별도 상한 4** |
| **깨진 세션 부류** | DB 조회를 잡고 넘어가며 **되돌리지 않는** 자리 | ⬜ **후속**. `prompt_loader` 한 곳은 고쳤다(유료 canary 2판에서 실제로 물렸다 — `prompt_template` 이 없어 조회가 터지고 그 세션의 뒤 문장이 전부 거절돼 스텝이 죽었다). ★AST 전수로 **7군데가 더** 같은 모양이다 — `main.py:160` · `run_control.py:196` · `image_service_helpers.py:248·291` · `pipeline_graph_service.py:520` · `indoor_shared_pose_guide_context.py:170·421`. 각각 세션 수명이 달라 **다 결함은 아니다**; 하나씩 확인해야 한다. ★`prompt_template` 이 모델·`init_db` 어디에도 없다는 것도 따로 볼 일이다(시험 DB 에도 없다) |
| **canary ① 1차 유료** | v2_chunk 첫 실주행 — **제 결함으로 중단** | ⚠️ 2026-09-02 (run `69e821758f3d` · 코드 `d992a611`). 쓴 것 = 부트스트랩 **2** · pipeline **15**(정지선 70 · denied 0) · 이미지 **0**. 잠금 셋이 기록에서 실제로 걸린 것을 확인(`llm_max_retries` 3→0 · `sdk_max_retries` 0 · `enable_fallback` False · router 0 · scope 일치). ★죽은 자리 = `GroundingChunkStep` 이 `_load_prev_checkpoint` 를 못 가져 `AttributeError` — **새 producer 가 한 번도 안 돌았다**. 시험 3539개가 통과하는 동안 그 자리를 안 지났다(시험이 CP 를 손으로 넣어 줬다). 수리 = 이웃과 같이 `_EntityStepMixin` 상속 + **부류째 잡는 시험**(각 스텝이 `self.<이름>(...)` 로 부르는 것이 실제로 있나 · AST · `hasattr` 가드는 뺀다) |
| **canary ① 2차 유료** | 실제로 **샀고** 선택은 0장 — 판정이 못 여는 경로 | ⚠️ 2026-09-02 (run `69e821758f3d` · 코드 `397fea99` · 수리 `0734adf5`). ★**중앙 조사가 처음으로 실제로 돌았다** — 검색 요청 **10/10**(거절 2) · 받기 **38장 25MB** · 최종 이미지 **0/0**. `reference_acquisition` `durable_status=completed` · `owners_present` **다섯 다** · dispatch 12 · reused 0 · refused 0 · `downstream_blocked` **0** · HITL **0** · 옛 조사 producer 호출 **0**(로그 0 · Opik 에 `web_search · gpt-5.6` 10건뿐). ★그런데 **`selected_count` 0 · unresolved 16 · 전부 `reference_unavailable`**. 원인 넷, 전부 재는 쪽·잇는 쪽 — ①**판정이 상대 경로를 받아 프로세스 cwd 기준으로 열었다**(`[Errno 2]`; 파일은 `PROJECTS_DIR` 아래 멀쩡히 있다). 12대상이 **검색·받기를 다 사고 나서** 여기서 떨어졌다. 기록은 상대로 두고 **여는 쪽에만** 절대 경로를 준다 ②유료 단계 뒤에 `_save()` 가 **하나도 없어** 마지막 문이 서자 **pipeline 단계가 산출 파일에서 통째로 사라졌다**(`pipeline_run.json` 이 따로 남아 살았다) ③client 재시도를 **보기만** 했다 — 중앙 조사 Responses client 가 `max_retries` 없이 지어져 `'<없음>'` 으로 관측됐고 그것이 판을 세웠다(SDK 기본 2 → 한 논리 호출이 최대 3회). 이제 **보기 전에 박고** `requested`/`forced_to_zero` 로 원래 값을 남긴다 ④`era_coverage` 에 값을 **내는 곳이 0곳**이라 24라운드 전부 `measured:false`. 저작기가 든 시대 조각을 그대로 넘긴다 |
| **canary ① 2차 장부** | 운반 문 · Opik · 텍스트 예산을 **갈라서** | ✅ 2026-09-02. 운반 예약 **57**(gemini-pro 30 · gpt 17 · **`responses.create` 10**) · 그중 **6건은 보내기 전에 막힘**(`entity_t2i` 스텝 상한 20, 로그 6줄 — 돈 안 나감) · Opik **성공 47**(gemini 24 · sol 13 · gpt-5.6 **10**) · gpt **4건은 Opik 에 없다**(Opik 은 성공만 남기므로 나갔는지 **모른다** — 나간 것으로 보고 뺐다). 차감 **51**(`correct_usage` · logical 51 · physical **47~51** · exact 미확정). **누계 70 / 승인 93 / 남은 23**, 넘긴 적 없음. ★**텍스트 예산 장부는 48 이었다** — `responses.create` 는 `llm_client._completion` 을 안 지나 **중앙 조사 문을 못 본다**. 운반 계수기가 그것을 봤다 |
| **나간 질의의 좌표** | 우리가 만든 것 ↔ 모델이 확장한 것 | ⚠️ 2026-09-02 실측. **지시문·검색어에는 좌표가 실렸다**(「…지역에서 …무렵의 …에 쓰인 …를 조사하라 · 모든 검색어는 반드시 그 나라 말로만」). 그런데 **실제 나간 질의 212개는 검색 도구가 확장한 것**이고 시대 낱말 없는 것 **126** · 지역 낱말 없는 것 **189**(예: `site:<기관>.go.kr <대상> 유물`). 우리 배선이 빠뜨린 것이 아니다. ★`era_coverage` 자동 검사는 이어도 **이 원고에서는 못 잰다** — `era_tokens_of` 가 연도 **숫자만** 뽑는데(낱말로 뜻을 고르지 않으려고) 이 원고의 시대 선언에 3~4자리 숫자가 없다. **데이터의 답이지 규칙이 아니다** |
| **canary ① 재판정** | 받아 둔 38장으로 **판정만 다시** — 검색 0 · 받기 0 | ✅ **배선 판정 통과** (2026-09-02, Codex GO · 코드 `c9fc9177`). **selected 5** · no_match_after_retry 5 · not_applicable 5 · skipped 4 · retryable 2(**검색 승인 10/10 을 다 써서 후보를 못 받은 두 대상** — 되볼 것이 없다). 스텝 `completed` · `rejudge_pending 0` · **재개하면 VLM 0회**. 장부 **Opik 10 = 이 판의 논리 10** 으로 맞음. 누계 **80/93** · 남은 13 · 열린 attempt 0 · **검색 0 · 받기 0 · 최종 이미지 0**. 심판은 `gemini-pro` **단독**(`single_judge`) · 후보 4장 9건 · 2장 1건. ★**고증 판정은 아니다** — 아래 줄 참조 |
| **재판정을 지으며 나온 결함 넷** | 전부 「끝났다·못 봤다」를 안 가른 자리 | ✅ 2026-09-02. ①**안 나간 호출이 승인선을 먹었다** — 운반 계수기가 안쪽 문 **앞에서** 세어, `entity_t2i` 의 거절 16건이 남은 23을 다 썼다(Opik 성공 0 · 한 푼도 안 나감). 이제 `reserve` 가 통과한 **뒤에** 센다 ②**재판정 판이 closure 전부를 돌았다** — `partial` 스텝이 제 실패를 다시 시도하는데 상한 0 이라 거절되고 그 거절이 문을 세운다. `reopen` 을 준 판은 **그 스텝만** 돈다 ③**산 것을 못 본 판이 `completed` 로 닫혔다** — 재개가 `SKIP` 으로 지나갔다. `failed_count`+`verify_completion`(`origin='artifact_missing'` — `force` 로 가면 **받아 둔 사진과 장부를 지운다**)으로 `partial` 이 되게 했다 ④**재판정이 구매 장부의 상한을 나눠 써서** 12 중 4개에서 멈췄다 → `journal_replay.json` 으로 가름(원 줄은 한 글자도 안 건드림) ⑤**「다 보고 없었다」를 끝난 것으로 안 세어** 재개마다 다시 샀다 → `TERMINAL_RAW_STATUSES`/`is_terminal()` |
| **§2-4 고증 판정** | 이 사진들이 그 시대·그 지역 것이 맞나 | ⏸ **미확정 — 아직 아무도 안 봤다**. Codex 계약 2026-09-02: 재판정 결과가 `selected` 여도 **sidecar·최종 이미지 생성 입력으로 승격 금지**. 이유는 provider 가 확장한 실제 질의 212개에서 시대·지역 좌표가 대량 빠졌기 때문이고(시대 없는 것 126 · 지역 없는 것 189), 그래서 지금 사진들은 **「판정 경로 복구 표본」**이지 고증 통과 표본이 아니다. ★**canary ② 전 BLOCK** = 런타임 구조화 era/region 을 후보 판정까지 전달하는 **일반 계약**으로 닫는다. 숫자 연도만 보는 `era_coverage` 로 통과시키지 않는다. 지역만 있으면 지역은 의무이고, 시대가 없으면 만들지 않는다. 장소명·국가명 목록·정규식 분기·시나리오 전용 프롬프트 금지 |
| **두 lane 회계** | 복사만으로는 안 돌아온다 | ✅ 2026-09-02 (Codex BLOCK 1). 재판정 줄이 구매 장부에 섞여 `bought()` 가 12 대신 **16**. `ChunkJournal` 에 **일반** append-only 이관 기록(`transfer_out`·`transfers`)을 두고 `bought()`·`get()` 이 해석한다 — **원 줄은 안 지운다**. ★장부는 **무엇이 어느 lane 인지 모른다**; `replay:` 의 뜻은 조립 경계(`reconcile_lanes`)가 안다(AST 로 「일반 장부에 lane 낱말 0」을 잠갔다). 이관 기록이 깨졌거나 옮긴 곳에 줄이 없으면 fail-closed. **실물: acquisition 12 · replay 10 · 겹침 0 · 원 줄 16 보존** |
| **coarse 「골랐다」 ≠ 고증 확인** | 아무도 안 본 사진이 붙을 뻔했다 | ✅ 2026-09-02 (Codex BLOCK 2). 심판은 「무엇인가 · 보이는가」만 본다 — 시대·나라·정확성은 **안 묻는다**(사용자 확정 08-31). 그러니 `selected` 는 「그 종류의 사진을 골랐다」일 뿐이다. `FIDELITY_*`·`fidelity_of()`·`usable_as_reference()` 로 축을 가르고, 소비자(`episode_reference_policy`)가 **둘 다** 통과한 줄만 붙인다. 미확인은 `fidelity_unverified` 갈래로 **안 붙이고 안 막는다**(HITL 0 — 참조 없이 내려간다). ★**칸이 없으면 미확인**이라 옛 CP 도 자동으로 안 붙는다 — 체크포인트를 다시 쓸 필요가 없고 `force`/cleanup 이 **받아 둔 사진 38장과 장부를 지우지 않는다**. 투영 판 `2.202609020700`. **실물: selected 5 · 붙는 것 0** |
| **나가는 질의의 좌표 문** | 우리 것에만 건다 | ✅ 2026-09-02 (Codex GO · 가+나). ★전제 정정 — **우리가 만든 검색어 50개 중 좌표 빠진 것 0**, provider 가 스스로 더한 200개 중 **195개**에 없음. 앞서 「212 중 126/189」로 올린 수는 **섞어 센 것**이라 acceptance 근거로 안 쓴다. (가) `assert_coordinates_carried` — 선언된 **비어 있지 않은** 좌표만, **선언 문자열 그대로**. 지역만 있으면 현대도 지역 의무 · 시대 없으면 만들지도 요구하지도 않는다. **상수를 질의와 견주는 자리 0** 을 AST 로 잠그고 지어낸 좌표로 도는 음성 대조를 뒀다. 판 전체가 아니라 **대상 하나**를 세운다. (나) `query_provenance` — `provided`/`provider_expanded` 를 갈라 세고 확장 쪽 누락은 **관측값**(문 아님). provider 가 후보-질의 결속을 안 주므로 후보는 `query_origin='unknown'` — 짐작하지 않는다. (다) 「준 질의만 검색하라」는 **다음 유료 A/B 가설**로만 기록 |
| **소비자 문이 둘이었다** | 한쪽만 막고 「붙는 것 0」이라 보고했다 | ✅ 2026-09-02 (Codex 재리뷰 BLOCK). `episode_reference_policy` 는 `fidelity` 를 봤지만 `grounding_bundle_projection` 의 **배경·아웃룩 두 갈래**는 `outcome == selected` 만 보고 그대로 붙였다 — 실물 미확인 LP01/03/04/05 가 생성 입력에 들어갈 수 있었다. ★내가 잰 「0」은 **집행자 하나만** 잰 수다(조립 자리에서 잰 부류). 이제 두 갈래가 `usable_as_reference()` **한 문**을 쓴다. ★그 김에 제 결함 하나 더 — 그 술어가 `status` 만 봤는데 sidecar 투영 줄은 `outcome` 만 들어서 **verified 인 줄까지 안 붙었다**; 소비자가 보는 접은 값을 먼저 본다. 미확인은 `member_identity`·`sha`·좌표를 **안 만들고** `not_attached='unverified'` 논리 의무로만 남는다(`unverified_members()` 로 「못 구한 것」과 갈라 읽는다). ★번들 계약을 처음엔 「신원 없으면 무조건 통과」로 **넓혔다가 진짜 결손까지 샜고** 잠가 둔 시험이 잡았다 — **일부러 안 만든 것**만 지나간다. 계약 판 셋을 올렸고 `scene_detail` `config_hash` 는 그 넷을 이미 접어 **자동으로 움직인다**(결속을 시험으로 잠금). **실물 끝점**: `members_for_shot` 0 · `outlook_members_for_shot` 0 · LP 하나 verified → **그것만** 정본대로 붙음 |
| **사람 고증 판정 화면** | 5장을 사람이 보고 정한다 | ✅ 2026-09-02 (commit d4dc9589 → e79a5f6b 여섯). `app/api/v1/grounding_fidelity.py` 가 **유일한 문**(인증된 backend → 표 `grounding_reference_fidelity_review` · append-only · 정정은 `supersedes_id` 새 행 · 본 사진 SHA·좌표가 붙일 것과 다르면 안 받음 · CP 직접 수정 길 없음) · 화면은 backend 가 같은 origin 으로 내준다(갤러리) · canary backend 로 열어 원본 DB 는 안 건드림. **실측**: 사람(admin) 판정 5건 09:49~09:50 KST — P01·LP01·LP03·LP05 verified · LP04 rejected. 소비자 둘(`episode_reference_policy`·`grounding_bundle_projection`)이 `central_cp_with_reviews` 한 문으로 읽는다(e79a5f6b · 앞서 호출자 0곳이던 것). 산출 `artifact/20260902_fidelity_review/`. ★거절과 미확인은 감사에서 다른 칸(`fidelity_rejected` · 8374cc33) — **새로 계산한 CP 에서만** 갈린다. 옛 completed policy CP 가 되쓰이면 그 칸이 없다(소비자 0 · 추가 칸이라 계약판 안 올림). UI/보고가 그 칸을 계약으로 읽기 시작하면 그때 policy 판을 올려 재생성시킨다. 원래 계약(참고): **다음 차례** (Codex 순서 고정 2026-09-02). 계약 초안 = 결정을 **사진 SHA + acquisition identity + 요구 era/region** 에 결속해 durable 로 남기고, `verified`/`rejected` 둘 다 누가·언제·무엇을 보고 정했는지 적는다. **CP 를 사람이 직접 고치는 길은 안 만든다** — 결정 기록이 정본이고 CP 가 그것을 읽는다. 화면은 사진·출처·나간 질의(우리 것/provider 확장 갈라서)·좌표를 함께 보여 준다. 사람이 안 봐도 파이프라인은 **안 막힌다**(HITL 0). ★verified 가 최소 1장 생긴 뒤에야 canary ② 로 간다 — 지금 그대로 돌리면 붙는 사진 0장만 재게 된다 |
| **1단계 canary(배경 off · world_guide 까지 글만)** | 사이드카 참조가 production scene_detail 카드에 붙는가 | ✅ **완주** 2026-09-02 16:16 (attempt e02db4801500 · run 69e821758f3d · 코드 5da5b667). 누계 **135/150** · 이 판 전송 11(gemini 8 · gpt 3 · 429 failover 1) · 거절 0 · search/download/참조판정/image **0**. CP: scene_detail 7/7 · world_guide 1/1 · entity_t2i 15/15(LP01·LP02·LP03 을 3호출로 채움) · scene_camera_flow 4/4 · shot_staging 7/7 · scene_consistency 4/4 · anchor 0/0 · policy 16/16. **사이드카(subject 단위)**: 씬1샷1 LP01#detail 붙음(identity 5c8630d1 · sha 9c29a9da · 파일 좌표) · 씬2샷4 LP03#detail 붙음(da322924 · 0cb38683) · 못 구한 줄 9(#context·L01·아웃룩)는 identity None·부착 0 · LP04(거절)·LP05(미가시)·LP02(못 구함) 멤버 없음. ★오늘 다섯 번 서고 다섯 번 고쳤다(각각 Codex 재리뷰): ①부트스트랩 신원 문(격리 near-miss · env 보다 먼저 settings) ②entity_t2i partial 을 cap 0 으로 되쓰기 + location_part 중립 모양 결함 ③scene_camera_flow 씬 단위(계측 계약표 `METERING_UNITS`·`CALLS_PER_UNIT`·hard/normal 분리) ④사이드카가 CP 에 없는 `chosen.content_sha256` 칸을 읽음(helper 한 곳 · 좌표 대조) ⑤정지선 93→110→120→135→150. ★무료 exit probe(`canary_exit_probe.py` · public `generate_single_scene_image` · urlopen sentinel · socket 문 · 글 cap 0): 씬1샷1 에서 production readiness 문 `gate.incomplete_references`(참조 이미지 14개 미생성)에서 섰다 — 전송 0. HTTP 직전 캡처는 **2단계(참조 이미지 생성) 뒤에만** 닿는다. **증명한 것**: 배경 off 에서 글 사슬 완주 · 확인된 참조가 production 카드에 결속. **아닌 것**: 배경 on 전체 경로 · 최종 이미지 생성 · 화질/고증 판정. 2단계(이미지 · ref 11+합성 4+씬 14 ≈ 30장 · 이미지 승인표는 상수 0)는 별도 승인 |
| **modern_episode canary(현대 한국 배경 축 · 주유소·편의점) — 별도 run** | 시대가 아니라 「모델이 틀릴 만한가·아는 사람이 알아채는가」 축이 실주행에서 고증 대상으로 잡히는가 | ⏸ **사람 판정 대기** — 유료 판 **한 attempt 에 완주** 2026-09-02 19:43 (run `be17560a08d0` · 코드 175c2b6c · attempt 184f0c253635 · 글/VLM 117/240 · 이미지 12/80(참조 2·아웃룩 2·합성 2·씬 6/6) · 검색·받기 문 10/40 안 · A/B 와 DB·artifact·장부 분리). **§6 사용자 요구 실측**: ERA 빈 채(world_rules 가 「현대」로 채움) REGION 대한민국으로 producer 가 하드·눈에 띔 대상 **16** 을 냈다 — 주유소 지붕 테두리·장식띠·주유기 섬·주유기·방호기둥·기둥 간판·편의점 유리 판촉물·아이스크림 냉동고·국도변 주유소·세차 터널·편의점 앞·국도 갓길… 인물·차창·카드·컵라면은 아님(기대대로). ★fixture 가 plain 으로 둔 **가로등(LP10)** 도 하드로 잡혔다(모델 판단 · 과포함 1) · 종이컵은 안 잡힘. 중앙 조사: 사진 고른 것 **9** · 못 구한 것 7(L01~L04·LP05~LP07·P04). 사이드카: 씬1샷1 에 LP01~LP04 멤버(identity None · 사람 판정 전) · 씬3샷3 L04 unavailable. 씬 이미지 6 은 고증 사진 **없이** 그려짐. 갤러리: http://192.168.231.91:8940/artifact/canary_be17560a08d0/gallery_modern/index.html · **판정 화면**(별도 backend 8942 · canary DB · admin): http://192.168.231.91:8942/api/v1/grounding-fidelity/login?next=/api/v1/grounding-fidelity/review-page?project_id=4aaed34e-4349-4667-8173-f6c1ca837f06%26episode_id=147d5dd0-f6d5-4e82-974b-169bf98b73be . 판정 뒤: 사이드카 재투영(production `write_for_shot` · LLM 0) → 씬1샷1 재생성 → 고증 사진 붙은 판과 견줌 |
| **A/B — 고증 사진 있음(A)/없음(B) 두 격리 clone 에서 fresh 생성 · 순서 가린 사람 판정** | 사람이 확인한 고증 사진이 그림을 낫게 하는가 (판정은 사람만) | ⏸ **사람 판정 대기** 2026-09-02 19:05. 도구 `tools/grounding_audit/canary_ab_arms.py`(clone·capture·compare·generate·gallery). clone: `CREATE DATABASE … TEMPLATE …` + projects 복사 → arm A `69e821758f3daa` · arm B `69e821758f3dbb`(clone 에서만 LP01#detail·LP03#detail 두 판정 삭제 → production helper `write_for_shot` 로 재투영: 바뀐 키 = grounding_bundle_members·grounding_required_attachments 뿐, 원본 판정 표 5행 불변). **무료 캡처 대조(urlopen sentinel · 전문 diff)**: 두 arm 의 요청은 ①inline 차이 = LP01 사진 sha 9c29a9da…(씬1샷1)·LP03 0cb38683…(씬2샷4) 뿐 ②글 차이 = 그 사진의 역할 문단(「FIXED PART OF THE PLACE … reproduce the component」)과 참조 번호 밀림뿐 — 그 밖 본문 동일. 사용자 지시대로 기존 stage2a A 는 안 쓰고 **둘 다 fresh 병렬 생성**: 씬1샷1·씬2샷4 × 2회 = arm 당 4장(글 5) · 첫 시도는 actor FK 로 1장씩 헛삼(합 10장). 갤러리(순서 가림 · 정답표는 페이지 밖 `ab_key.json`): `artifact/canary_69e821758f3d/gallery_ab/index.html` (http://192.168.231.91:8940/artifact/canary_69e821758f3d/gallery_ab/index.html). stage2a 19장은 보존(재생성 0) |
| **2단계 stage2a 유료 완주 (참조·아웃룩·합성·씬 이미지)** | 사람이 확인한 고증 사진이 최종 이미지 요청까지 닿는가 | ✅ **완주** 2026-09-02 18:52 (run 69e821758f3d · 코드 3e2debb1 · attempt 다섯: 326a898b1128 stopped → 2620121dc61d crashed → 074784fe9ec3 crashed → 6b2c5b581d83 partial 6/7 → 108dcba84d52 completed). 사용자 지시(속도 우선 · 소액 재승인 왕복 금지): 글 정지선 210→240(정확한 범위 결속 `APPROVED_IMAGES_BY_SCOPE` 이미지 80). **결과**: 씬 7/7 · 참조 4(C01~C04 · 소품 5는 production 저빈도 skip — 어느 still 에도 안 보임) · 아웃룩 4 · 합성 4 = 이미지 **19**/80 · 글/VLM 누계 **171**/240 · 검색/받기 0 · 열린 attempt 0. **exit probe(무료)**: 씬1샷1 실제 HTTP 요청 parts 에 사이드카 LP01 sha 9c29a9da… 실림 · 빠짐 0. 갤러리(arm A · 판정은 사람만): `artifact/canary_69e821758f3d/gallery_stage2a/index.html` (http://192.168.231.91:8940/artifact/canary_69e821758f3d/gallery_stage2a/index.html). **서고 고친 것 다섯(전부 유료 0 에서 fail-closed)**: ①옛 규칙 「이미지 한 장이라도 나가면 선다」가 승인값을 안 봄(c3e40b35) ②scene_image_pipeline 진입 시 world guide 재생성 글 1(187750a4) ③production: 기본 shot_dependency CP(T2I 주석 없음)에 v7 keep_elements 요구(e882d4e6) ④production: close 샷 matrix 가 미주석 ref_usage 를 malformed 로 세움 → `t2i_annotated`(440b61b9) ⑤still 마다 번역 1(hard 3)·readback 이미지마다 1(hard 2) 계측(3e2debb1). ★4번째 attempt 는 번역·readback 이 문에 막힌 채(비차단) 6장이 나갔다 — 한글 프롬프트가 그대로 간 샷이 있을 수 있음(재생성 안 함). **남은 것**: A/B arm B(별도 run · clone 에서 LP01/LP03 두 판정만 무효화 · ≤12장) → 사람 순서 가린 판정 · scene 자산 lineage 에 file 좌표 멤버가 `actual_attached_refs` 에 안 남음(요청엔 실림 · 기록만) · pytest-xdist 워커 DB 안전문 · pg lane 옛 fake · wide 는 d543fa1b 에서 기준선 109 차집합 0, 이후 커밋 6개는 표적 시험만 |
| **2단계 산정표(무료 · production SOT)** | 이미지 canary 를 열기 전 정확한 장수 | ⏸ **사용자 승인 대상** 2026-09-02 18:40 (커밋 a9f40478 · 전이 677a9a1d554f · dry 산출 #35 · 유료 0). production dry(`--target scene_image_pipeline --fixture period_episode --background off --still-recipe off --outdoor off`): unknown 0 · unverified(살 자리) 0 · 대조 문 셋 통과(entity queue 15≤20 · 아웃룩 4≤8 · 상태 변형 0≤0). **실제로 살 수(CP 에서 읽음)**: ref_image_gen 9(C01~C04·P01~P05) → 이미지 9 · LVM 글 18(dual ask_both 2/entity) · hard 이미지 45(1+sanitized 3+severe 1)/글 36; composite_image_gen 아웃룩 4 → phase2 아웃룩 1 + phase3 쌍 1 = 이미지 8 · 글 16 · hard 40/32; character_state_variant 0; scene_image_pipeline 선택 still 7 × variation 2 = 이미지 14 · 글 0(scene LVM off) · hard 168(attempt 3 × (1+sanitized 3)). **합: 이미지 정상 31 · hard 253 · 글(LVM) 정상 34 · hard 68** — Codex 정정치와 일치(옛 27/79·13/52 폐기). fixture 상한 기준(문에 거는 수): 이미지 정상 56/hard 420 · 글 closure 정상 206/hard 590. 글 문: 누계 135 + 실제 34 = 169 이나 도구 규칙(승인선 ≥ 상한 정상 합)으로는 **206 이상**이어야 열린다 → 제안 정지선 글 210 · 이미지 `APPROVED_IMAGE_CALLS` 31+여유 = **40**(hard 253 은 안 연다 · 닿으면 inconclusive · 자동 확대 0). stage2a(참조 9·합성 8·씬 14)와 A/B arm B(≤12장 · clone)는 run 신원·승인표 분리. **Codex 비용 상한 BLOCK 둘 닫음(d543fa1b · 전이 540e1c255c1d · dry #36)**: ①이미지 상한이 attempt 마다 승인값을 통째로 다시 열던 것 → run 전체 누계(`cumulative_image_used`·`remaining_image_cap` · 이번 attempt 는 남은 것만 · 열린 attempt 거절 · 정정 `image_debit_now`) ②`APPROVED_IMAGE_CALLS` 전역 → `APPROVED_IMAGES_BY_SCOPE[scope_key]`(mode·fixture·target·배경·still_recipe·outdoor) 한 함수를 scenario·전이·재개 문·scope 문·dry·run_pipeline 이 같이 봄(없는 범위 0). dry #36: expected 이미지 31·글 34 · 이 범위 승인 0 · search/download 0 · unknown/unverified 0 · 글 문 206>150 에서 섬. **사용자 승인 뒤** `APPROVED_BY_MODE["v2_chunk"]["counted"]` 150→210 과 `APPROVED_IMAGES_BY_SCOPE[(v2_chunk, period_episode, scene_image_pipeline, off, off, off)] = 40` 을 한 커밋으로 → 전이 → dry(글 누계 135/이번 75/정지선 210 · 이미지 0/40/40) → live. 아직 유료 이미지 0 |
| **LP 집행자 하나 · verified C/P 사진이 canonical ref 에 실림 · gpt-image 문 여섯** (Codex BLOCK 1·2) | 집행 계약대로 한 갈래 한 집행자 · 조사 결과가 픽셀에 닿음 · 이미지 cap 이 모든 gpt-image 자리를 봄 | ✅ 2026-09-02 17:20 commit `db78acea`. **BLOCK 1**: 게이트(pipeline_gate)·readiness(asset_readiness)·생산자(reference_phase1·orchestrator 저빈도)가 각자 `not in (location, outlook)` 로 적어 location_part 가 base ref 대상에 들었고(sidecar 와 집행자 둘 · canary 5장/hard 25장 헛구매 예정), 최종 ref map 은 location 글자 하나만 뺐다 → `grounding_entity_contract.canonical_ref_owner_types()`(= 정책 갈래 C/P) 한 이름을 넷이 쓰고 map 은 `sidecar_owner_types()`(L/LP) 를 뺀다(outlook 등 기존 계약 보존). main DB 는 LP canon 0 → production 데이터 변화 0. 끝점(PG) `test_canonical_refs_have_one_executor_per_owner.py`: 다섯 갈래 심고 게이트=readiness=계약 집합 · 옛 LP 참조 자산 음성 대조. **BLOCK 2**: 정책은 verified C/P 를 `required_refs` ID 로만 올리고 Phase1 `extra_references` 는 의존 엔티티 ref map 뿐 → P01 사진이 그림에 안 닿았다. 새 모듈 `grounding_canonical_ref_inputs.verified_policy_references`(같은 reader `central_cp_with_reviews` · `usable_as_reference` 한 곳 · final_id=short_id 결속 · `row_content_sha256`)를 orchestrator.run 한 곳(스텝·API 둘 다)에서 읽어 `ctx.grounding_references` → Phase1 `extra_references=(dep_refs+grounding_refs) or None`(variant 판정이 dep_refs 유무를 보므로 뒤에 따로) · 자산 `pipeline_metadata_json.grounding_inputs` 에 sha 좌표 · 호출 수 불변 · 사진 없으면 종전 그대로. 끝점(PG) `test_the_verified_photo_reaches_canonical_ref_generation.py`: 얼린 실물 중앙 CP(21줄) 위에서 `generate_base_references` → `generate_and_validate_reference` 직전 kwargs 에 P01 정확한 bytes · C01 None · LP01 미생성 · 호출 2=2. canary 무료 probe: P01 만 · sha 26319b13… == 파일. **문**: gpt-image-2 는 primitive 에 문이 없고 부르는 쪽이 건다 — 22곳 중 shot_conti_light 2·still_recipe 2·gpt_image_gen 1·confined_fp 1 에 `reserve_current_call` 없음 → 여섯 자리 추가 + AST 래칫 `tests/core/test_every_gpt_image_call_site_reserves.py`. wide 109/109 기준선과 양방향 차집합 공집합 · pg lane 44 통과 |
| **재개 신선도 = 입력 지문 · v2_chunk 직접 API fail-closed · lane 축 셋 · run 신원 대조** (Codex 재개 계약 BLOCK) | verified 사진이 바뀌면 canonical ref 가 다시 구워지고, 안 바뀌면 0장; 2단계 closure 의 unknown 0 | ✅ 2026-09-02 (커밋은 wide 대조 뒤). **재개 계약**: `grounding_canonical_ref_inputs.grounding_input_digest`(final_id·acquisition_identity·content_sha256 정렬 + 계약판 `1.202609021800` · 입력 없음도 명시 값 `EMPTY_INPUT_DIGEST`) 를 자산 `pipeline_metadata_json.grounding_input_digest` 와 `reference_checkpoint` completed 행 **둘 다**에 남기고, orchestrator 는 already_done 을 정하기 **앞에서** 사진을 읽어 둘이 지금 값과 같을 때만 되쓴다(`is_fresh` · 한쪽만 옛 지문이면 재사용 0 · 옛 자산(칸 없음)은 입력 없음일 때만 신선 — legacy 수백 장 재구매 방지). 새 생성 성공 뒤에만 옛 primary 를 내린다(Phase1 순서 그대로). `RefImageGenStep._config_hash` 는 v2_chunk 에서만 전체 입력 지문을 접는다(legacy/v2 지문 무변) + 저장/비교 대칭. `verified_policy_references(fail_closed)`: v2_chunk 인데 중앙 CP 부재·손상이면 provider 앞에서 선다(public `/generate-reference-images` 포함) · legacy/v2 는 옛 길. 끝점(PG) 10개 추가: 옛(지문 없는) P01 → verified → 정확히 1회 · 같은 상태 0 · 사진 SHA 변경 → 1회(실패면 옛 primary 유지) · CP만/자산만 옛 지문 → 재사용 0 · v2_chunk+CP 없음 → 0장 fail-closed · legacy 비회귀 · 스텝 지문(legacy=base · v2_chunk 는 판정에 따라 움직임). **lane 축**: `_SETTING_AXES` = background·still_recipe·outdoor(실제 settings 술어로 읽고 `--still-recipe/--outdoor` 는 대조만 · 뒤집힘 검사는 축마다 folding∩done · 끝난 CP 중 그 설정을 접는 것 0 실측) · fixture 가 `HAS_OUTLOOKS` 를 선언해 `composite_image_gen` 이 계획표에 들어오고(unknown 0) outdoor_place_spec/canon·background_share_plan 은 lane off 면 skipped. `METERING_UNITS` 에 넷을 unverified 로 적음(살 자리면 live 가 선다 — 다음 무료 일). **run 신원**: 전이를 적다 `--fixture` 를 빠뜨려 CLI 기본값 canary_one_scene 이 장부에 적혔다(append-only → supersedes 정정 f64f87a61039) → `assert_scenario_matches_run` 을 전이 기록·재개 문 둘 다에 걸었다 |
| **현대 배경도 고증한다 — 사용자 재확인** | 「주유소」·한국식 「편의점 + 앞쪽 벤치」 | ✅ 2026-09-02 무료로 닫음 (canary ② 통과 뒤 · 아래 「현대·지역 고증 축」 acceptance 일곱 통과 · production 변경 0 · 유료 현대 canary 는 안 열었다). 원래 계획: **canary ② 뒤 바로 이 축** (사용자 2026-09-02 재지시: 「너 잊지말고 이것 구현후에 꼭 …현대 배경이라도 고증이 필요한 경우는 되도록 진행해야해」). ★그 낱말들은 **사례일 뿐** — production 코드·프롬프트에 장소명·나라명·상표명·명사 목록·정규식 분기를 **넣지 않는다**. 입력 시나리오나 fixture 원고에만 나온다. 판별은 두 축 그대로다: 「모델이 근거 없이 그리면 겉모습을 틀릴 만한가 · 그곳 사정을 아는 사람이 알아채는가」. **시대가 기준이 아니다** — 주유소·편의점·약국·개찰구는 나라마다 간판·구조·기계가 다르고 모델이 모른다. 아래 「현대·지역 고증 축」의 무료 acceptance 일곱이 이 축의 문이고, 거기에 **현대 원고 fixture**(시대 선언 없음 · 지역만)를 새로 만들어 `location`/`location_part` 가 두 축으로 걸리는지 본다 |
| **현대·지역 고증 축** | 과거물 전용이 아니다 — **마지막 보강 축** | ✅ 2026-09-02 (무료 · 시험 32 · production 변경 0). **실측(코드)**: v2_chunk 활성 경로에 시대를 요구하는 자리 **0** — 시대·지역 둘 다 없으면 raise 하는 다섯 자리(`grounding_a0`·`grounding_claims_search`·`grounding_shadow`·`grounding_steps` plan/research)는 전부 `mode == v2` 전용(`buys_v2_research` 정확 비교)이거나 offline. 판별 `project_from_producer`·살 자격 `assert_buyable`·붙일 자격 `usable_as_reference`·sidecar `members_from_rows` 는 좌표를 **안 읽는다**(AST 로 잠금). 그래서 「막는 것을 걷어 낸다」가 아니라 **「안 막는다」를 잠갔다**. fixture `tests/grounding/fixtures/modern_episode.py`(ERA "" · REGION 만 · 3씬 8샷 · `canary_bootstrap.FIXTURES` 등록) + 시험 `test_modern_places_are_grounded_without_an_era.py`. ⑥ 은 `app/` **문자열 리터럴**(docstring 제외)과 활성 팩만 본다 — `app/` 세 파일 주석에 지난 실측 기록으로 같은 낱말이 있고 그것은 규칙이 아니다. ⑦ 은 `tools/grounding_audit/coordinate_shape.py` — 모양 이름을 historical/modern 으로 **붙이지 않는다**(글자로 뜻 판단 금지), `era+region`/`region_only`/`era_only`/`none`. ★**증명한 것 / 안 한 것을 가른다** (Codex 재리뷰 2026-09-02 · 내가 시험 코드에서 확인): 증명한 것 = v2_chunk 하류 계약이 시대를 자격조건으로 안 삼고, era 가 비어도 region 만 운반하며, 두 축이 참인 location/location_part 를 조사·부착 경로가 받고, production 코드/활성 팩에 fixture 장소명이 없다는 것. **안 한 것** = producer 모델이 실제 현대 원고에서 주유소·편의점 같은 어려운 현대 장소를 안정적으로 `hard_to_generate=true`·`viewers_would_notice=true` 로 내는 것 — 시험의 두 축은 **fixture 가 넣는다**(`_subject()`). 그래서 「현대 실주행/선별 품질까지 완료」라 쓰지 않는다. ★남은 것: 유료 현대 canary(visual_world_rules 가 시대를 「현대」로 채워도 경로는 같다 — 두 모양 다 같은 시험이 덮는다) 는 별도 승인. 원래 계약: **canary① 뒤 무료로 먼저 닫는다** (사용자 2026-09-02 · Codex 계약화). 「조금만 복잡해도 이미지 모델이 잘 못 그리는, 나라·지역별 생김새가 중요한 현대 장소도 반드시 고증한다.」 ★주유소·편의점은 **사례일 뿐** — production 코드·프롬프트에 장소명·나라명·상표명·과거/현대 명사 목록·정규식·키워드 분기를 **넣지 않는다**. 그 낱말은 입력 시나리오나 fixture 원고에만 나올 수 있다. **계약** = 시대가 없거나 현대여도 제외하지 않는다 · `location`/`location_part` 포함 일반 두 축(`hard_to_generate && viewers_would_notice`)이 참이면 중앙 조사 대상에 **반드시** 든다 · 「무조건 고증」은 **실제 검색 시도를 장부에 남긴다**는 뜻이고 못 찾으면 `reference_unavailable` 은 되지만 **현대라는 이유로 `not_applicable`/`skipped` 가 되면 안 된다** · 지역 차이는 `visual_world_rules.region` 과 world facts 같은 **런타임 구조화 입력**으로 검색어와 구매 신원에 반영한다. **무료 acceptance 일곱** = ①era 비고 region 만 있는 현대 장소가 두 축으로 target ②같은 배경의 쉬운 대상은 음성 대조로 비대상 ③지역만 바꾸면 지시문·acquisition identity 가 바뀜 ④동의어·새 이름으로 바꿔도 구조적 판단 유지(키워드 매칭 아님) ⑤`location`·`location_part` 가 중앙 조사 → 배경 sidecar → 실제 scene provider payload 까지 닿음 ⑥`app/` 와 프롬프트 팩에 fixture 고유명사·키워드 분기 **0** ⑦보고는 historical / modern-localized 를 **따로 세되 집행 규칙은 한 벌** |
| **검색 질의에 좌표가 없던 것** | chunk 팩이 「뒤 단계가 붙인다」는데 **그 단계가 없었다** | ✅ 2026-09-02 (제가 잡고 Codex 가 계약을 정함). `grounding_acquisition_adapter.search_target` 이 `visual_brief` 를 질의로 그대로 넘겼고 저작기(`grounding_ref_brief` 팩)는 **canary 도구만** 불렀다 — production 호출 **0**. 그래서 시대 없는 질의는 **앞선 시기** 것이, 지역 없는 질의는 **같은 언어권 옆 나라** 것이 온다(뒤에서 아무도 안 본다 — 심판에게 시대·나라를 안 묻는 것이 계약). ★2026-08-31 사용자 실측(「참조가 그 시대보다 오래된 것」)의 원인 자리로 보인다. **수리** = `grounding_search_brief` 를 production 중앙 caller 가 넘긴다 · 팩 **v3**(`3.202609020400`)가 시대·지역을 **독립**으로 다루고(선언된 것만 싣고 없는 것은 안 지어냄 · 현대라고 안 뺌) 「원고 언어 = 배경 나라 언어」 단정을 걷었다 · 저작 입력(팩·계약·모델·시대·지역·world facts)을 **구매 신원에 접는다**(지역·시대·팩이 바뀌면 다시 산다) · 나간 질의의 **좌표 누락 0** 을 기계로 센다. 옛 팩 둘 보존 · 팩에 나라·가게 이름 **0**(시험 확인) |
| **상한 재산정 3판** | 「함수에 있다」와 「production 이 부른다」 | ✅ 2026-09-02. 앞서 「한 대상 한 라운드 = 2」로 드린 것이 **틀렸다** — `acquire_one` 소스에 두 이름이 보인다고 셌는데 그때 production 은 `write_brief` 를 **안 넘겨** 실제로는 1 이었다(정확히 「조립부를 쟀다」). 이제 `central_calls_per_target_per_round()` 가 **production 이 그 함수를 부르는 자리의 kwargs 를 AST 로 읽어** 센다. 저작기를 이은 뒤 다시 **2** 이고, 재개 산술이 정확히 맞는다 — 남은 미완 17 논리 + 중앙 20 논리 = 37 × 슬롯 2 = **74 counted**, 누계 19 더해 **93** = 승인선, 부족분 **0** |
| **canary ① 재개** | 같은 run · 앞단 재구매 0 | ⚠️ **한 번 이어 갔고 또 죽었다** (2026-09-02, `1f7832595eb7`). ★조건 ④ **통과** — 앞단 아홉 스텝 counted 전부 **0** · 되쓴 스텝 17 · 예산 used 0 · 검색 0/10 · 받기 0/40 · 이미지 0. 재개 문(`assert_resume_transition`)과 전이 기록이 실제로 지났다. ★죽은 자리 = `entity_relation` — producer 가 낸 `part_of` 에 **outlook→character · prop→prop** 이 있는데 DB 투영이 `location_part→location` 만 받아 **주행을 세웠다**. producer 가 맞다: 그 짝은 `grounding_facet_binding` 이 아웃룩을 인물에 붙일 때 **쓰는 재료**다. `select_db_part_of` 가 DB 계약이 받는 짝만 고르고 나머지는 갈래·까닭과 함께 `part_of_not_for_db` 로 남긴다(비정본 ID·자기 자신 링크는 fail-closed) |
| **canary ① 예산이 못 보던 자리** | 안쪽 scope 가 바깥을 **가린다** | ✅ 2026-09-02 (제가 잡고 Codex 가 조건을 더함). `grounding_chunk` 이 제 `research_run_scope(cap=24)` 를 열고 `install_budget` 으로 canary 예산을 **갈아 끼워**, 유료 호출 2건이 counted **0** 으로 적혔다 — 「run 전체 정지선」이 그 스텝을 못 봤다(실제 노출 93+24). 수리 = 전송마다 불리는 `reserve_current_research_call` **그 자리**를 감싸 아래에서 센다. ★`transport_used.jsonl` 에 append-only + `fsync`, 다음 판이 `adopt()` 로 **이어받는다**(덮으면 죽은 판 증거가 지워진다). ★`RLock` 으로 검사→증가→기록을 한 임계구역(fan-out 20 동시에 cap 20 정확). ★bootstrap(4)/pipeline 몫 분리. ★**production 의 중첩 예산 합성은 별건 · 일반 배포 전 BLOCK** — 두 `reserve` 를 차례로 부르면 한쪽만 먹으므로 원자적 composite/stack 이 필요하다 |
| **canary ① 상한 재산정 2판** | 승인 **93/93 + 10/40/0** | ✅ Codex 승인 (2026-09-02). 93 은 파생 — 앞단 15 + 남은 일반 38 + 중앙 5대상×2라운드×(저작+판정)×슬롯 2 = 40. **문이 셋이고 서로 못 본다**: 글/VLM counted 93 · 검색 **요청** 10 · 받기 **operation** 40(= raw source fetch 최대 80, redirect 안 셈 → 「total raw HTTP」라 쓰지 않는다) · 최종 이미지 0. 검색·받기는 preflight 기록이 아니라 **provider 앞 문**이다. ★유효 누계는 **19**(앞단 15 + chunk 안전 차감 4), 남은 것 **74**. chunk 의 실제 물리 시도는 **[2,4] 미확정** — Opik 은 성공만 남기고 `failover_on` 은 durable 자국을 안 남긴다. 그래서 `physical_attempts.exact=None` 으로 두고 승인선에서는 **상한을 뺀다** |
| **canary ① 장부 정직성** | 크래시를 `completed` 로 닫던 것 | ✅ 2026-09-02 (Codex BLOCK). `run_steps_batch` 가 **안에서 실패를 잡고** `None` 을 주므로 밖에서는 「예외가 안 나왔다」로 보였다. 이제 부른 **뒤 durable 상태를 되읽는다**(체크포인트 없으면 「안 돌았다」 — 실측에서 죽은 스텝부터 manifest 가 아예 없었다). `stopped`(상한)와 `crashed`(못 끝남)를 갈라 적는다. 이미 적힌 줄은 **안 고치고** append-only 정정을 덧붙인다(`correct_attempt`·`effective_status`) — 실제 장부에 `completed → crashed` 를 넣었고 누계 15·열린 판 0 은 그대로 |
| **canary ① 재개 문** | 전이 기록이 **문이 아니던 것** | ✅ 2026-09-02 (Codex BLOCK). `record_code_transition` **호출자 0** 이었다 — 다른 코드로 재개해도 막는 것이 없고 `canary_run.json` 을 덮어 첫 판 증거가 사라졌다. `assert_resume_transition` 이 `run()` 의 **첫 저장·DB·provider 앞**에 서고, `supersedes` 를 **실제로 해석**한다. 재개는 `canary_resume_*.json` 을 따로 쓴다. ★`recorded_kst`(초)를 열쇠로 썼더니 같은 초의 둘이 안 갈려 대신하는 줄이 자기까지 죽였다 → `event_id`. ★`from_tip` 을 인자로 받게 뒀더니 **내가 지어냈다**(없는 SHA) → run 산출에서 읽고 인자는 대조용 |
| **canary ① 되쓰기 문** | 삼켜진 `cap=0` 거절이 성공처럼 | ✅ 2026-09-02 (Codex BLOCK). 앞 판에 끝난 스텝은 `cap=0` 으로 열되, 거기서 막힌 흔적(`denied_by_step_cap`)이 하나라도 있으면 **provider 0 상태로 즉시 선다** — 옛 `completed` manifest 때문에 되읽기가 통과하던 자리 |
| **장소 신원 — 진짜 원인은 격리** | 이발소·국밥집·골목이 `L01` 하나 | ✅ 2026-09-02 밤 (제가 오진 → 장부로 바로잡음). 판독기는 장소 **넷**을 냈는데(이발소 앞·국밥집·이발소 안·골목) 「씬 밖 샷 결속 하나」로 셋이 **행째** 격리됐다(옛 run 12행 · 새 run 9행 — 이발사·미닫이문·교복도). 팩 v6 의 장소 규칙은 원인이 아니었다. 수리 = `resolve_rows` 가 **검증된 자리만 남기고 뺀 것을 `salvage_problems` 로 적는다**(언급 번호 초과 · 씬 밖/catalog 밖/중복 샷 ID). 검증된 언급 0 · 지어낸 근거 문장은 여전히 행째 격리. manifest 에 `grounding_salvaged`. 멈춘 run 응답을 무료 재처리 → 31행 0 격리 · location 4 · 이발사 C02 · 미닫이문 LP03. 처리 계약 `4.202609022340` |
| **장소 신원 끝점 검사기** | 좌표로만 대조 · 이름 0 | ✅ `tools/grounding_audit/location_identity_check.py` — fixture `EXPECTED_SCENE_PLACES`/`EXPECTED_PART_HOSTS`(씬·낱말·몇 번째, 0=모든 자리)를 production `scene_save` 글(제목 포함) 위 span 으로 바꿔 `occurrences` 와 겹침으로만 잇는다. 다른 자리가 한 ID 를 나눠 가지면 ✗ · 한 업소의 앞/안 분리는 `same_place_split` 로 적기만 · LP→자리 `parent_final_id` · 장소 다른 샷을 배경으로 물려받은 의존 0. 옛 run 69e8 에 대면 ✗5(양성 대조) |
| **Codex BLOCK 다섯 (a4f74a8c 리뷰)** | 셋 수용 · 둘 거부 | ✅ 2026-09-02 밤. ①2차 pass 상한: `_cap` = 대상 수 × pass 수(앞 판은 N 이라 2차가 문 앞에서 전부 막힘 — 실 `ChunkJournal` 로 [A1,B1,A2,B2] 와 재개 재구매 0 잠금) ③시대·지역 선언의 production 입구 `PUT/GET /projects/{id}/grounding-coordinates` + LLM 설정 저장이 스칼라 키를 남김 + `visual_world_rules` 지문에 좌표 계약 판 접기 ⑤지시문 팩 5(고정 명사 예시 제거). **거부** ②VLM 거친 종류/가시성 선택은 goal 문안 그대로(시대·정확성·좋고 나쁨은 안 묻는다) ④아웃룩 이중 구매: 실측 O01 조사 행 1(36 질의) — 여러 행은 LP 의 purpose 줄뿐(질의 0) |
| **얼어붙은 판독 v11 다시 얼리기** | 처리 계약 4 가 행 집합을 바꿈 | ⬜ `test_label_reaches_the_judge::TestTheFrozenRunShowsTheDefectIsGone` 둘이 skip — 옛 장부(`artifact/20260831_period_canary/v11.json`)의 merge 응답이 새 행 집합의 신원과 안 맞는다. 다시 얼리려면 **merge 호출 1회** 승인 필요(재채점은 사지 않는다) |
| **canary 재개가 BLOCK 을 삼켰다** | drift→409 를 completed 로 되읽음 | ✅ 2026-09-02 밤 (실측). runner 는 `grounding_chunk contract drift detected … force 재실행 필요`(409) 를 올렸는데 `run_steps_batch` 가 안에서 삼키고, canary 는 옛 CP 를 completed 로 되읽어 **새 처리 계약이 하류에 하나도 안 닿은 채** 조사 단계까지 갔다(attempt 832f7bc4 · 글 0 · 조사 1건 사기 전에 세움). 수리 = `canary_pipeline.contract_drift_of` 가 runner 인스턴스의 `_check_cp_mismatch` 를 **먼저** 묻고, `plan_reentry` 표대로 — 어긋남 + 승인된 전이(`code_transition_covers_head`) → **force**(하류 무효화) · 전이 없음 → 선다 · 깨끗 → cap 0. ★근본 결함: `grounding_chunk_step._execute` 가 제 `_config_hash()` 를 CP 에 안 적어 **재개마다** drift 였다(이제 적는다). VisualWorldRulesStep 에 접었던 좌표 계약 판은 되돌림 — 선언이 생기면 base 해시가 이미 움직이고, 판을 접으면 선언 없는 모든 프로젝트의 기존 CP 가 재개마다 BLOCK 된다(로그 실측 2행) |
| **재개 다섯 번 서고 고침 (45075f79441e)** | 유료 0 으로 잡은 것 넷 | ✅ 2026-09-02 밤. ①전이 확인이 `git_tip()` 의 dict 를 str 로 읽어 항상 거짓(시험 대역이 str — 이제 진짜 git_tip) ②not_applicable 스텝(`grounding_a0`)의 base 해시 CP 를 drift 로 잡음(돌지 않는 스텝은 안 묻는다) ③행 집합이 바뀐 merge 가 journal 상한 2 에 막힘 → 상한을 **자리 수**로(`slot` · 옛 두 줄은 손으로 slot 표기, 백업 `.bak_before_slots`) ④`grounding_salvaged` 가 다른 함수의 지역 변수를 읽어 NameError → 행에서 파생. **일곱 번째 attempt(21c47e1b)에서 chunk CP 가 처리 계약 4 · location 4(이발소 앞·국밥집·이발소 안·골목) · salvaged 9 · quarantined 0** 으로 하류로 갔다 |
| **조사 상한이 선언 5 로 잰 것** | 실제 의무 21 → 상한 40 에서 죽음 | ✅ 2026-09-02 밤 (attempt 21c47e1b · 유료 40 쓴 뒤 `research_call_budget 초과 40/40` at rejudge). `central_logical_cap` 이 원고 선언(갈래 5)만 세었다 — production 이 세운 의무는 21. 수리 = `build_plan(run_id)` 가 `grounding_screen.counts.obligation` 을 읽어 `max(선언, 의무) × (라운드 2 × 라운드당 2 + 재판정 1)` = 105 (선언 기준 25). 정상 예상 합 216 → 301 이라 글 정지선 **240 → 320** (사용자 지시: 상한 승인으로 서지 않는다 · 시간이 문제). 여덟 번째 attempt 로 재개(누계 123 · 남은 197) |
| **구매 장부 append-only** | settle 제자리 갱신·reserve 덮어쓰기 | ✅ 2026-09-02 밤 (Codex BLOCK). `ChunkJournal` 이 `calls` 를 시도(attempt)마다 한 줄 덧붙이는 사건 로그로, `settlements` 에 사람의 정정(not_sent)·복구 사건을 덧붙이고, `entries` 는 접은 사본. 파일 재적재 뒤 원 uncertain·정정·새 결과가 다 남는 시험. 이 run 의 덮인 결정은 `recovery` 사건으로 복원해 적음(삭제·역수정 없음) |
| **끝나지 못한 라운드가 라운드로 세어짐** | P03·L02·L03 영원히 retryable | ✅ 2026-09-02 밤 (attempt e74f4f0b). 상한에 막혀 빈손으로 끝난 2라운드(decision.next=retryable · 받은 후보 0)를 재개가 라운드로 세어 3라운드부터 시작하려다 아무것도 안 했고, 2차 신원이 같아 되쓰임 → 스텝 partial → 하류 gate 막힘. 수리 = `acquire_one(resume_from)` 이 그런 라운드를 `incomplete_rounds` 로 두고 다시 돈다(후보를 받고 판정만 못 한 라운드는 재판정 lane 몫) · 2차 신원에 끝난 라운드 지문 · slot=대상:p<pass> |
| **아웃룩 7 이 조사되지 않음** | merge 가 옷→인물 part_of 를 안 냄 | ✅(팩) 2026-09-02 밤. 옛 run 은 O01→C01·O02→C03 을 냈는데 이번 merge(새 행 집합)는 옷끼리만 냈다 → facet debt `no_structural_parent` 7 → 등록·조사 0. 판 7 `merge_system.md` 에 「입은 것·걸친 것·신은 것은 입은 사람의 part_of · 장소 부분은 그 자리의 part_of」 규칙(범용). ★프롬프트 규칙이라 다음 판 산출로 확인 — 구조 칸(판독기가 입은 사람의 행 번호를 적는 것)은 후보 ⬜ |
| **구매 신원이 너무 잘았다 → 재구매 10건** | 저작 payload 전체 해시 | ✅ 2026-09-03 새벽 (Codex BLOCK). `skeleton_search_input` 하나 = 종류 이름·넓은 검색어·언어 잠금·시대·지역·저작 팩/모델/계약 — outbound 도 target_identity 도 이 객체(세부 묘사·근거 문장·원문 표본은 둘 다에서 뺌, 장부 줄에 감사·i2i 로 남음). 옛 신원은 `legacy_*` 로 남겨 `journal.alias` 사건으로 이관(옛 38건 재구매 0) |
| **아웃룩 조각마다 조사 (O01 세 줄)** | final_id 당 의무 하나 | ✅ 2026-09-03 새벽 (Codex BLOCK · 사용자 「옷 전체 뼈대 하나 · 세부는 i2i」). `outfit_obligation`: <fid>#outfit · obligation_kind=outfit · 뼈대 = phase3 아웃룩 이름(`ledger["outfits"]`) · 검색어 union · 조각은 `details`. acceptance 「outlook final_id 당 조사 행 1」 |
| **재판정 lane 이 Gemini 503 에 스텝째 죽음** | attempt 02f980a6 | ✅ 2026-09-03 새벽. `rejudge_rows` 가 보낸 뒤 provider 실패(503·429·연결·Timeout 종류)를 줄 단위 skip(current_unconfirmed) · 장부는 provider 거절을 `refused`(빈 자리 · 자동 재구매) 로(Timeout 은 uncertain 그대로) |
| **Codex BLOCK 셋 (96348c6c 재리뷰)** | pass 접미 · 실패 alias · 연결 예외 · 옷 검색어 | ✅ 2026-09-03 새벽 (3567085c 까지 · Codex 조사 단계 APPROVE). `pass_suffix` 한 함수 · alias 는 옛 답 selected 만(못 찾음은 새 넓은 검색 실제 호출 · 열두 번째 attempt 의 alias 넷은 `alias_void` 사건) · `is_provider_refusal` 은 503·429 만(연결·Timeout 은 uncertain) · outfit 검색어 = [옷 전체 이름] · BRIEF_CONTRACT 2 / legacy 1 · `--steps reference_acquisition` 로 다시 연 판은 검색·받기 문 표 값(attempt d75a52a0 이 문 0 에 빈손) |
| **옛 대상 구조의 장부 자리가 상한을 먹음** | attempt bb85e798 | ✅ 2026-09-03 새벽. run() 이 지금 대상 밖 주체의 slot 을 세어 상한에 더한다(`orphan_slots`). 열네 번째 attempt 로 O02·L02 2차 |
| **빈손 2차가 ok 로 저장돼 되쓰임** | attempt c9037991 O02 | ✅ 2026-09-03 새벽. `buy_or_reuse(complete=acquisition_is_complete)` — 마지막 라운드가 빈손(retryable·후보 0)이면 `incomplete` 로 저장(되쓰지 않고 자리 비움) · 저장돼 있던 17줄은 `settle_incomplete` 사건 |
| **exit 검증이 옛 CP 를 읽어 partial** | attempt 2510693b | ✅ 2026-09-03 새벽. runner 가 exit 검증을 save 앞에서 부르므로 `verify_completion` 은 `_last_execute_result` 를 보고, 재개 때만 CP 를 본다(시험 둘) |
| **1단계 조사 acceptance (run 45075f79441e)** | Codex 조사 단계 APPROVE | ✅ 2026-09-03 00:45. chunk 지문 일치 · 처리 계약 4 · 장소 3 · 검사기 ✅ · outlook final_id 당 1 · 조사 completed · **selected 15/17**(소품 6 · LP 5 · 이발소·국밥집 · 학생 교복 O01 · 두루마기+모자 O03) · unavailable O02(흰 무명 겉옷: 후보 5장 모두 unsure)·L02(골목: 7장 unsure) — 후보는 검토 화면(8943)에 76장. **다음 = 사람 후보 검토 → 이미지(≈14) → 두 샷 A/B → 사람 판정.** 남은 글 30 |
| **acceptance 도구 = 문** | overall_ok · 분모 갈라 적기 | ✅ 2026-09-03 새벽 (Codex 재리뷰). `research_acceptance.py` 가 `overall_ok=all(...)` 로 exit 1/0 · 아웃룩 대상 전부 C 부모 + 조각(O→O) 사슬이 C 뿌리에 닿고 대상이 아님 · 분모: 참조 필요 18 = selected 15 + unavailable 3 / 구매 대상 17 = 15 + 2 / 미등록 1 · 언어 잠금 감사(비차단 · 글자 종류만): O01 2차 질의 3건이 영어 위주 — 후속에서 저작기에 문 |
| **미등록 조각 행(단추)이 별도 의무로 남음** | O01 세부와 중복 | ⬜ 2026-09-03 (Codex). 판독기가 `part_of` 없이 낸 옷 조각은 facet debt(`no_structural_parent`) → final_id None → auto_completed/unavailable 로 「참조 필요」분모에 든다. 구조화된 wearer/host 칸(판독기 schema) 으로 부모 아웃룩 details 에 접거나 명시 비대상으로 가른다 — 작품 명사·regex 금지 |
| **저작기 2차 질의 언어 잠금** | O01 r2 영어 3건 | ⬜ 2026-09-03 (Codex NON-BLOCK). narrow retry 힌트가 영어로 넓히며 `language_lock_native` 를 어김 — 저작기 출력에 문(잠금 언어 글자 우세) 후속 |
| **Codex BLOCK (a91ca02e 리뷰)** | 셋 중 둘 닫음 · 하나 진행 | ✅①근거 문장도 **검증된 언급 씬** 것만(`Q_EVIDENCE_SCENE` 로 그 문장만 빼고 적음 — Codex 는 행째 격리를 제안했으나 실측 골목 행이 씬 4 언급 없이 씬 4 근거만 적은 「덜 적음」이라 행을 살림) ✅②좌표 PUT 에 owner/admin 쓰기 문(`deps.require_project_write` 로 올려 고증 POST 와 한 문; member 403·DB 무변) ✅③(재리뷰 BLOCK 까지) VLM 이 사람 검토 전에 후보를 없앰 → **후보별 판정 + 대상당 선택 하나**: 판정은 후보마다(둘 다 맞다가 옳다), 쓸 사진은 `grounding_reference_selection`(append-only · 새 선택이 앞 것을 `supersedes_id` 로 대신 · migration 013) — `POST /grounding-fidelity/select` · 페이지 세 번째 단추 「이 사진을 쓴다」 · `apply_reviews` 는 선택된 후보가 verified 일 때만 chosen 승격(선택 없고 verified 하나면 그것) · `reviews_digest` 에 선택 접음. 끝점 시험(실 DB·API 함수·production reader): 둘 다 verified+선택 B → B 만 usable · A→B 바꿈 · 판정 전 선택은 안 붙음. 후보별 사람 검토: `review_inputs_for` 가 받아 둔 후보 전부를 (행×후보) 입력으로 내고(`candidates_of` · 모델 선택은 `model_pick`/`model_eligible` 표시), `apply_reviews` 가 사람이 **정확히 하나** verified 한 후보를 `chosen` 으로 올림(`chosen_by human` · 옛 모델 선택은 `model_pick` 보존 · outcome selected → `usable_as_reference` 참). 둘 이상 verified → 미확인+사유 · 모델 선택 rejected → rejected · 결정 없음 → 미확인(비차단). 페이지 카드가 후보마다 하나(모델 표시만). 시험 7 (`test_all_downloaded_candidates_reach_human_review.py`) |
| **canary ① 상한 재산정** | 중앙 조사에 일반 `1` 을 주던 것 | ⏸ **승인 대기**. `reference_acquisition` 은 fan_out 이 아니라 **대상 수만큼** 돈다 — `cap=2` 면 첫 대상 언저리에서 끝나고 나머지는 **예산 때문에** `reference_unavailable` 이 되어 「다섯 갈래가 문을 지났다」를 못 잰다. `central_logical_cap` 이 **원고가 선언한 대상 5** × 라운드 2 × 라운드당 2(저작+판정)에서 **논리 20**. 그 「2」는 `acquire_one` 이 부르는 것을 AST 로 세어 대조. ★문 셋이 갈린다 — 글/VLM 논리 20 · 검색 **요청** 10 · 받는 **장수** 40(= `era_research.MAX_CANDIDATES` 4). ★계획 논리가 **65 → 84** 로 바뀌어 승인선 70 **밖**이라 지금 유료가 안 열린다. 재개 기준 한 라운드씩 **73** · 두 라운드 **93** — Codex 에 올려 둠 |
| **D-canary 주행 시도** | 유료 앞단 — **세 번 서고 유료 0** | ⏸ 2026-08-31. ①②DB: `str(URL)` 이 비밀번호를 `***` 로 가려 인증 실패 ②③schema: 빈 DB 에 `alembic upgrade` 불가(첫 migration 이 표를 전제) → `init_db()`+`stamp head` 로 바꿈 ③제 손으로 멈춤: 지문 표 seed 를 넣었다가 **효과 0** 임을 확인하고 뺌. ★그 과정에 제 결함 셋을 더 잡았다 — 비밀번호가 예외·산출물로 샐 수 있었다 · **안전문이 이관을 통째로 롤백**시켰다(`SELECT` 하나가 트랜잭션을 열어 둠) · helper 를 `main()` 뒤에 둬 **CLI 만 `NameError`** |
| **D-canary 지문 확인** | canary 가 production 과 **같은 지문**을 쓰나 | ✅ **영향 0 으로 확정**. `load_prompt` 는 **session 을 줄 때만** DB 를 본다. 이 판에서 `db=` 를 넘기는 (module,name) **10개**와 활성 override **54개**의 **교집합 0**(Codex 독립 전수 16개도 교집합 0). `scene_detail` 은 `db` 없이 **파일 prompt** 를 쓴다. ★앞서 「`detail_steps.py:1477` 이 override 를 읽는다」고 적은 것은 **철회**. 교집합 0 은 시험으로 잠갔다 — 늘면 **먼저 깨진다** |
| **D-canary 무료 끝점** | 되돌리면 **반드시 실패**한다 | ✅ 2026-08-31. 네 text 경계 각각이 cap 에서 운반층 앞에 서고(비 OpenAI 직접·슬롯 loop 안·키 없음·router), 워커 12개가 한 수를 같이 세고, 재시도 3 Router 가 있으면 **부트스트랩도 pipeline 도 한 번도 안 돌고** 선다 |
| **D 선행** | `kind_name_of` 의 **조용한 fallback** 제거 | ⬜ (Codex NON-BLOCK 2026-08-31) — 빈 부류 이름이면 심판에게 **표기를 보내지 말고** raw 사유를 적은 `reference_unavailable` 로 **자동 완료**. 파이프라인을 막는 게 아니다 |
| 5.5 | claims 를 소비하는 reference 생성·검증 | ⬜ |
| **§2-4 loop** | 참조 획득 라운드 loop (조각 여덟 잇기) | ✅ 무료 · 시험 17 |
| **§2-4 canary** | 합성 fixture 로 **유료 실행** | ✅ **기구는 돈다** — 대상 13(→잔부분 제외 후 5) · selected 10 · no_match 3 · 이미지 A/B 40장. ★**고증 정확성은 못 쟀다** — 지어낸 지역이라 찾을 정답 사진이 세상에 없다 |
| **§2-4 사람** | A/B 순서 가림 화면 · 참조 획득 화면 | ⏸ **사람 판정 대기**. `artifact/20260831_ref_canary/{ab_review,review}.html` |
| **§2-4 실원고** | 실제 현대 한국 원고로 표본 8 | ✅ 7/8 · **outlook 은 자격 미달**(현대 신발·시계는 어렵지 않다 — 옳은 판정) |
| **§2-4 시대판** | **1960년대 한국 원고**를 지어 다섯 갈래 전부 | ✅ **14/15 · 다섯 갈래 전부**. ★내 배선 잘못 셋을 고치며 10→12→14 (저작 단계 누락 · 영어 좁힘 문안을 원어 지시문에 붙임 · 심판에게 조각만 줌) |
| **§2-4 사람** | 참조가 그 시대 것이 맞나 · 참조가 그림을 끄나 | ✅ **사용자 판정 「거의 맞음」** (2026-08-31, `artifact/20260831_period_canary/review_v11.html`). ★이것은 **개발 진단 신호**이고 HITL 승인 게이트가 아니다(Codex) |
| **§2-4 공용팩** | 다섯 갈래 **공용** 저작 계약 (`grounding_ref_brief`) | ✅ 무료 — 앞 팩은 **야외 구조물 전용**이라 소품·인물·아웃룩·장소부분을 장소 구조물로 다시 읽었다. v2(`2.202608311930`)는 **받은 구체성을 그대로 둔다**(특정 실재 대상 금지 삭제) · 시대와 **지역**을 모든 질의에 유지. 옛 팩 보존 |
| **§2-4 R1** | v2 팩 **유료** canary — 다섯 갈래 각 1개 | ⚠️ **acceptance 아님** — 기계·비용은 확인(논리 18 · 세는 물리 19/60 · 거절 0 · 이미지 0 · HITL 0)했으나 `targets_from` 이 `coarse_type_label` 을 안 옮겨 **5대상 전부** 심판이 표기를 받았다. 「거친 종류만」이 검증 안 됨. `trace_id` 18줄 전부 null. 산출 `artifact/20260831_v2_canary/` |
| **§2-4 R2** | 부류 이름 수리 뒤 **재주행** (새 장부, 다섯 다 재구매) | ✅ **합격 주행** — must_hold 넷을 **얼어붙은 산출에서** 다시 셈: ①`trace_id` **18/18 non-null** ②5대상 모두 raw `status`+`outcome` 동시(prop=`no_match_after_retry`/`reference_unavailable`) ③**pick 사건에 실제 적힌 kind** 가 preflight label 과 **6/6 일치** ④이미지 0·`downstream_blocked` 0·사람 대기 0. 논리 18 · 세는 물리 19/60 · Opik 부모 5 ↔ 사건 `trace_id` 5 **겹침 5**. 시대 좌표 **69%→90%**, 어긋난 시대 **5→0**. 산출 `artifact/20260831_v2_canary_r2/` |
| **§2-4 R2 사람** | 참조가 그 시대 것이 맞나 · **됫박 후보 8장이 정말 아닌가** | ⏸ **사람 판정 대기**. `artifact/20260831_v2_canary_r2/review.html` (그림 24). ★됫박은 「곡식 됫박」으로도 못 구했다 — 부류 이름이 원인이 아니었다. 검색 탓인지 심판 탓인지는 **사람이 그림을 봐야** 갈린다 |
| 6 | 눈가림 A/B | ⬜ — ★자동은 **순서 은닉 · arm 신원 · 기록 · 누락 방지**만. **어느 쪽이 낫고 통과인가는 사람만** 판정하고 VLM 점수·critique·다수결을 PASS 근거로 안 쓴다 |
| **6.5a** | `location_part`·outlook facet **producer + stable ID + binding + 기계적 gate** | ⏸ **부분 · inert** — producer 는 다섯 갈래를 **낸다**(유료 주행 실측: prop 44·character 30·location 29·location_part 29·outlook 9). 결속은 `grounding_facet_binding` 이 **기존 `part_of` + owner 조합**으로만 한다(이름·substring 0). ★그러나 **하류 소비까지 간 것이 아니다** — `entity_canon.entity_type` 에 `location_part` 가 없어 그 갈래는 통째로 **durable debt**(유료 산출 29개 전부, 우회 등록 0). ★**중앙 획득의 선행조건으로 당김**(Codex 2026-08-30). 이것 없이 중앙 획득도, 「다섯 갈래 완료」 주장도 **금지** |
| 6.5b | 사람/시각 acceptance + 후속 품질 gate | ✅ 사용자 판단으로 마감(2026-09-03 10:20) — 갤러리·고증 화면은 선택적 후속/보존 감사 자료 · HITL 0 |
| 7 | 통합 E2E | ⬜ |

**9/16 + C(c) 넷.** ★`full V2 완료 = 6.5 뒤`다. 그 전 성과는 전부
**prop/character 한정**이라고 쓰고 「V2 됐다」로 쓰지 않는다.

★★**2026-09-03 10:20 — full V2 완료 (사용자 판단 기준).** 실제 통합 검증(배경→scene_detail · 야외 보충 독립 · 한 run 야외 통합 · 다섯 갈래 끝점 probe)이 닫혔고, §6.5 는 사용자 판단(「현재 결과는 잘 되는 것으로 보이며 추가 수동 평가 없이 마감」)으로 마감했다. 범위 밖 후속: unverified 계측 단위 3(background_render · shot_continuity · shot_conti_light) · adoption 사건 two-phase · 이미지 문 셈의 예약/전송 칸 분리. HANDOFF ⑦.

★★**C(c) 는 「기계가 도는가」까지만 확인됐다.** 실제 부하에서 구조화 추출과
샷 ID 선택이 도는 것을 유료 5회로 봤을 뿐, **production PASS 도 · D 활성화
승인도 · 「C(c)가 현행보다 낫다」도 아니다.** 의미 품질(샷 결속이 맞나 · 두
축이 옳나 · 엔티티/facet 품질)은 **아직 아무도 안 봤다** — v3 화면에서
**사람만** 판정하고 VLM·자동 점수로 대신하지 않는다.

★유료 주행이 **격리 뒤** 잔존 어긋남 0 인 것을 「모델이 계약을 다 지켰다」로
읽지 않는다. 모델이 낸 196행 중 **38행(19.4%)이 격리**됐고(샷 결속 18 · 인용
20 · 근거 8) 0 은 **검증기가 막아낸 뒤** 남은 수다.

★끊긴 판은 **이어 산다**(`--resume <기록>`). 분류는 `(판정자, 표본 번호)` 칸마다
따로 저장되고, 재개하면 **빠진 칸만** 실제로 산다. 신원(팩·계약·실제 payload·
판정자·표본 수)이 다른 칸은 **없는 것으로 치고 다시 산다** — 옛 판을 새 계약에
이어 붙이는 것은 재개가 아니라 오염이다.

★한 번 틀리게 적었던 것: 「부분 재개는 표본끼리 물리 모델이 갈릴 수 있어 일부러
안 만들었다」. 틀렸다 — 물리 모델이 갈리는 것은 `check_provenance` 가 막는
자리이고, 그것이 **이미 산 표본을 버릴 근거는 못 된다**(Codex).

★3c 가 막힌 이유: 세로축(`reliably_known`, 구현은 `difficulty`)이 판정 모델에게
**다른 모델**이 뭘 그릴 수 있는지 묻는 축이라 계약 §3 이 금지한 자기평가다.
실측으로 그 축만 흔들렸다 — 갈림 5/6, **한 모델 안에서만 봐도 4/6**, 두 판정자의
눈금이 한 칸 어긋난 자리가 정확히 route 를 자르는 자리였다. **그 축은 폐기했다.**

★VLM 으로 그것을 재려던 안(§C)은 **기각**했다 — VLM 도 그 시대 물건을 본 적이
없고, 정답 사진을 얻으면 이미 조사를 산 것이다. 그래서 그 축을 **없앤다**.
A 도 검색 없는 분류기 답으로 쓰지 않는다 → **§4a** 가 출처로 확정한다.
자세한 것은 계획서 §2 「§C 기각」 절.

★§10 기대값은 **안 바뀐다** — 엔티티 보존과 조사는 **별개 축**이다(계약 §7).
`non-research` 는 조사 축만 말하는 것이고 엔티티에서 빼라는 뜻이 아니다.

---

## 활성 과제

### GROUNDING-V2 · 고증 조사 재설계 ★본체
계약 = **`docs/design/2026-08-29-grounding-v2-contract.md`** (tracked) · 플랜 = `docs/design/2026-08-29-grounding-v2-plan.md`
  <br>★종전에는 `artifact/` 에만 있었는데 그 디렉토리는 **gitignore** 라 fresh clone 에 없었다 (Codex BLOCK)
- **왜**: 「1983년」은 전달되는데 **1983년 회수권이 어떻게 생겼는지는 아무도 안 찾아본다.**
  2026-08-29 사용자(그 시절을 겪음)가 요금통·회수권이 **전부 틀렸다**고 확인.
- **실물 결함 셋**: ①엔티티 시대 조사가 **죽은 코드**(`generate_for_entity_with_retry` 호출 0건,
  저장소 전체 엔티티용 eraref **0장**) ②묘사가 조사보다 **먼저** 상상으로 쓰임
  ③추출 제외 기준 「구분 어려우면 제외」가 **정확히 고증 대상을 거름**
- **판정** (2026-08-30 개정): `고증 필요 = A AND B` · `보존 = 고증 필요 OR C(2회 이상)`
  ★세로축 `reliably_known` **폐기** — 「다른 모델이 뭘 그릴 수 있나」는 계약 §3 이
  금지한 자기평가다. VLM 으로 재려던 안도 기각(계약 REJECTED 절).
  ★A 도 분류기 답으로 안 쓴다 — **§4a 가 출처로 확정**한다.
- **판정자**: 분류기는 `gpt` + `grok` **텍스트** 판정(사용자 지시) — 그림을 안 본다.
  `referent_specificity` enum SOT. exact_variant·unique_identity 는 **조사 강제**.
- **모드**: `legacy` / `shadow_plan` / `v2` **상호배타 enum** — 두 번 사는 것이 구조적으로 불가능
- **삽입점**: 추출에서 **후보 표식만** → canonical merge/sync → **통합 조사** → detail/t2i
- ★**미해결 설계 문제 둘 (Claude 발견)**:
  - 조사 경계(order 13.7~14.0)에서는 `shot_selection`(15.5)이 아직 안 돌아
    **어느 샷이 선택될지 모른다** → B 판정은 전체 샷 기준
  - ★**C 는 닫혔다 — planned `shot_count` 를 보존 기준으로 쓴다.**
    (selected count 는 planner 우선순위로만) 앞선 내 서술이 틀렸던 것: `entity_all_*` 가 `shot_count` 를
    만들고 `EntityFilterStep`(order 13.7)이 복원한다(`entity_steps.py:353`).
    내가 말한 「뒤에 세어진다」는 **DB 투영**(`sync_t2i_appearance_counts`) 얘기로 다른 값이다.
    **없는 것은 selected-shot count 뿐**(`shot_selection` 15.5).
    → 새 계수를 만들기 전에 **C = 전체 planned shot 인가 selected shot 인가**를 정한다
- **의존**: 아래 TASK-59 의 부모. TASK-55/31 은 canary 에 얹음

### TASK-107 · 테스트 기준선 59건
- `tests/core` **20** + `tests/services` **39** = **59**. services 39 는 **어느 목록에도 없었다**
  (`test_episode_projection_strict` 12 · `image_capture/` 계열 27)
- 지금 할 일은 **수리가 아니라 분류** — 제품 결함 / 환경·fixture / 순서 의존 / 의도된 skip
- `build_opik_metadata` 단독↔전체 역전이 **먼저 고칠 것**
- ★**V2 프로덕션 병합 전에 「예상 밖 실패 0」 기준선이 필요**

### TASK-59 · 조사 참조가 최종 그림에 반영되나 — **확정 결함으로 승격**
- 2026-08-29 사용자 실물 반증으로 「확정 결함 아님」에서 승격
- 수정은 **GROUNDING-V2 하위**로 처리
- acceptance: text evidence · image evidence · generated reference · 최종 형태 정합
- ★`_sel` 을 **별도 보존·판정** — repair 가 최종을 바꿔도 원인을 잃지 않게

### TASK-55 · lane 마커 화살표 방향 (S11sh1)
- 운영 우회는 적용, **근본 결함 미해결**
- ★`map_marker` 가 나왔다는 것만으로 통과시키지 않는다 —
  방향이 텍스트로 확정된 전용 lane shot → plan=map_marker → marker asset 화살표 → 최종 이미지 방향까지 이어져야 함
- 다른 샷이면 「일반 lane 도달」만 말할 수 있고 **S11sh1 종결은 못 함**

### TASK-31 · fix reference gate 잔여 검사
- critique master 를 **켜야** 의미 있음 (기본 OFF)
- kept / missing / all-ungated 분포만 관찰. 발화 안 하면 **미도달이지 통과 아님**
- ★이 결과로 TASK-59 이미지 품질을 설명하지 않는다

### TASK-71 · Opik 상시 관찰
- 통합 E2E 에 **네 번째 표**로 얹음

### EVAL-SPEC · 생성 프롬프트와 평가용 typed projection 분리
- 2샷 × 2역할 × 4모델 = 16 VLM 콜
- TASK-62(판정 프롬프트 축소)를 여기에 **흡수** — 시간 원인 가설은 반증됨

### REPAIR-V2
- critique 참조 대조의 거짓/혼재 지적 · 한 `fix_en` 안의 참·거짓 혼재 · bgfirst regenerate 확대 여부

### TASK-58 잔여 · TASK-89
- 58: 평평한 `chat.completion` 부모 계층 · ref_image metadata
- 89: `logical_request_key` · unique index · reconciliation (품질과 무관, 후순위)

---

## 기록 종결 / 재개 조건

| ID | 왜 종결 | 재개 조건 |
|---|---|---|
| **TASK-104 / C-2** | close/insert 충돌 **범위는 관찰**됐으나 typed 규칙이 confirmed conflict **6/86** 뿐 — 안전한 validator 근거 없음 | ★「반복 지목」은 같은 감사에서 **이미 4건·4작품으로 성립**했으므로 재개 조건이 못 된다. 실행 가능하게 좁힌다 — **2026-08-30 이후** 새 주행에서 같은 유형 confirmed conflict **10건 이상**, 또는 `required_subject_edge` 같은 typed 계약이 코드에 생길 때 |
| **B-3** 조명 스키마 분리 | | 실제 그림에서 조명 사실·연출 충돌이 반복될 때 |
| **B-4** GROUPBG priming | | **새 location grounding owner 뒤** 재검증 |
| **TASK-21** 백엔드 지연 | 병렬화로 충분 | Grok 한 콜이 여전히 3~4배 느릴 때 |
| **TASK-106** shot_selection | 정의·증거 미수령 | 사람 비교 뒤 |
| plate_multiroll 지문 acceptance | `plate_multiroll_enabled=False` | 플래그를 실제 켤 때 |

---

## 완료 — active 에서 내림

| ID | 근거 |
|---|---|
| 판정 병렬화·시간 단축 | `2f40bbf7` main 에 있음 · **E2E 127.2 → 60.4분**, `scene_image_pipeline` **94.3 → 31.6분** (같은 최소 시나리오) · PR #43·#44 <br>★오늘 3화 42분은 시나리오가 달라 **직접 비교 근거로 쓰지 않는다** |
| TASK-6 P0-A | PR #30 병합 (끝점 groupbg 시험까지) |
| TASK-64 era 캐시 신원 | PR #26·#27 — `2a26e81e`·`606b900b` |
| TASK-78 Opik payload 분해 | 분석 완료. ★**tracked 근거 없음** — Codex 판정에만 있다. 재개 조건: 발송 payload 가 다시 커지면 |
| TASK-79 전체 분석 교차 분류 | **PR #38 병합** (`tools(#79): 실발송 프롬프트 한 줄의 출처를 끝점에서 가른다`) |
| TASK-80 이미지에 닿는 프롬프트 전수 분해 | 활성 경로 감사 완료. 발견은 **TASK-104 로 분리**. ★tracked 근거 없음 |
| TASK-81 할루시네이션 유발 지점 | 현행 결함 가설 **반증 후 종결**. ★tracked 근거 없음 |
| TASK-82 플래그·버전·분기 사이드이펙트 | 감사 완료. **N2**(분석 모델을 CP 지문에 포함)만 조건부 — 기존 에피소드를 새 모델로 다시 분석해야 한다는 요구가 생기면 재개 |
| TASK-83 호출 폭증 | **활성 호출 폭증 결함 없음**으로 종결. ★tracked 근거 없음 |


★위 넷(78·80·81·83)은 **결론만 있고 근거가 저장소에 없다.** 이 파일을 만든 이유가
바로 그것이므로, 지어내지 않고 **「근거 미확보」로 적어 둔다.** 다시 필요해지면
Opik/PR 기록에서 되찾아 이 표에 채운다.

| ID | 근거 |
|---|---|
| TASK-97 재방문 캐시 | 2026-08-29 **도달** — L01·L02 각 1회, L01 재방문 추가 호출 **0** |
| TASK-108 검증 시나리오 E2E | 2026-08-29 완주 |
| PR #52 carried 인물 귀속 | 구조 계약 수리 완료 |
| PR #54 · #55 · #56 | 재사용 엔티티 결함 3종 (outlook link · 참조 게이트 · verify 거짓 미완료) |

---

## 아카이브된 브랜치

**`docs/design/2026-08-29-branch-archive.md`** 참조 (tracked).
`archive/*` 9개 — 되살리려면 `git branch -m archive/<이름> <원래 이름>`.
main 에 이미 들어간 껍데기 **56개 삭제**(조건 통과는 61개였고 5개는 다른 worktree 사용으로 **삭제 보류**). 복구표는 같은 문서 안에 있다.

## 2026-09-01 — 뒤로 미룬 것 (canary 밖)

- **저장소 뿌리에서 pytest 를 돌리면 두 파일이 collect 조차 안 된다** —
  `tests/grounding/test_grounding_binding.py` · `test_grounding_screen_step.py`
  가 `pathlib.Path("app/core/steps/entity_steps.py")` 처럼 **작업 디렉토리
  기준 상대 경로**를 모듈 로드 시점에 읽는다. `backend/` 에서 돌리면 통과한다.
  ★내가 「양쪽 디렉토리에서 통과」라고 잘못 보고한 원인이 이것이다 — grep 이
  `2 errors in 1.73s` 를 안 집었다. 고치려면 `Path(__file__).parents[N]` 기준
  으로 바꾼다. (내가 만든 파일 아님 · 이번 goal 범위 밖)

### 2026-09-01 — §2-6.5a (등록·결속·DB 저장까지)

- `location_part` 가 **제 갈래로 선다.** 막던 것은 schema 가 아니라 코드의
  목록이었다(`entity_canon.entity_type` 은 `text`·제약 없음 — **alembic 불필요**).
  계약을 셋으로 갈랐다: 전체 materializable(C·L·**LP**·O·P) / EntitySync 소유
  (C·L·LP·P — outlook 은 `OutlookSyncService` 몫) / relation_type 별 갈래 짝.
- **표식 한 벌**(`owner_keys(cp)`)이 `is_cp_syncable`·pre-scan·`_TYPE_DEFS`·
  조회·stale cleanup 을 같이 정한다. 표식 없음 → 옛 C/L/P(무변) · 아는 판 →
  +LP · 모르는 판 → **선다**. ★조회만 넓혔을 때 legacy CP 가 LP link 를 지우던
  **데이터 손실**을 Codex 가 잡았다.
- `part_of` 가 **DB 에 남는다**(fact + participant part/whole). 갈래 짝은
  `RELATION_OWNER_PAIRS` **한 벌**에서 오고 저장 직전(`desired_keys`)에도
  검사한다. canon 이 없으면 **지우기 전에 선다**(`RelationSyncRefused`).
- ★**부팅이 죽던 결함**: startup 이관이 `SUBSTRING(short_id FROM 2)` 로 한 글자만
  떼서 `LP01` → `'P01'` → fail-fast. 끝 숫자로 뽑게 고쳤고 원본 3825행에서 두
  식이 **같은 값**임을 실측했다. `CASE` 에 없던 `location_part` 도 넣었다.
- 실측: 얼어붙은 유료 산출 옮긴 행 69 → **89**(`location_parts` 20) · verifier
  bindings **18** · debt **0**.

★**남은 것**: 참조 획득·하류 소비(다섯 갈래 끝점) · outlook 무료 단계 ·
§5 중복 호출 · D 원자적 cutover · 사람 평가. `tests/grounding/
test_five_owners_endpoint.py` 는 **결정적 fixture 끝점**이지 replay 가 아니다.

### 2026-09-01 (이어서) — §2-6.5a **LP 결속 계약** (tip `eb2953e9`)

★`FACET_OWNERS` 하나에 **두 뜻**이 겹쳐 있던 것을 **세 축**으로 갈랐다 —

| 축 | 계약 | LP | outlook |
|---|---|---|---|
| 구조 facet | `FACET_PARENT` | ○ location | ○ character |
| 스캔할 갈래 | `ENTITY_KEY_TO_OWNER` | **○** | ✕ (order 19.2 라 앞단엔 없다) |
| 일반 승격 | `GENERIC_PROMOTION_OWNERS` | **✕** (부모 없으면 고아) | ✕ (OutlookSync SOT) |

- **`prop` 과 `location_part` 를 갈랐다.** 묶여 있던 동안은 표가 안 났는데 LP
  행이 생기자 **양방향 교차 결속**이 열렸다(실측: prop 후보가 LP 행에, LP 후보가
  prop 행에 이름으로 붙었다). 교차 0.
- **LP 는 이름·부분문자열로 결속하지 않는다.** 「간판」과 「회전 간판」은 다른
  부분일 수 있다. `bound_final_id`+발급자, 또는 같은 reduced `local_id` —
  **producer 가 낸 검증된 링크**만 받는다.
  ★후보가 스스로 적은 `short_id` 는 **안 받는다**: 실제 A0 산출 후보에는 id 칸이
  **아예 없다**(`owner_type·planned_occurrences·source_anchor·source_quote·
  surface_form·why_candidate` 뿐).
- **등록된 LP 는 A0 없이도 `entity_row`** — `build_subjects` 가 subject 마다
  처분을 낸다(`subject_dispositions`, 새 값 `entity_only`). 앞서는 후보 장부만
  봐서 그 행이 `unbound` 로 떨어졌다.
- **링크가 없으면 둘 다 보존** — 엔티티는 `entity_row`, 후보는 `deferred`.

실물 23개 수는 **그대로**다(carried 2 · promoted 7 · deferred 14 ·
`deferred_owners = {location_part: 10, outlook: 4}`) — 실데이터엔 producer
링크가 없어 LP 가 안 붙는 것이 맞다.

### outlook — 앞단이 **증거를 안 남긴다** (실측)

`facet_debt` 행은 `research_subject_id·owner_type·binding·is_facet·assess_key·
screen·assess_sha·subject_count` 를 담고, `plans[rsid]` 에 **저작된 질의와 언어
잠금**이 있다. 그런데 **`surface_form`·`source_quote`/span 이 없다** — 스텝 CP 는
`rows·plans·counts·by_owner` 만 싣고 `subjects` 를 안 남긴다. 즉 phase3 뒤
resolver 가 **결속할 증거가 없다.** 그것이 outlook 단계의 첫 조각이다.

★**남은 것**(다섯 갈래 완주 아님): 참조 획득 · 하류 소비 · outlook 증거 carry ·
중앙 19.x 획득 한 곳 · §5 중복 호출 · D 원자적 cutover · 사람 평가.

### §2-6.5a **producer 링크 계약** (tip `26d20592`)

A0 후보를 기존 `LP##` 행에 붙이는 유일한 길. ★이름으로는 **절대** 안 붙인다.

```
producer_link = {issuer, contract_version, final_id, local_id}
```

- 발급자·계약 판은 **중앙이 아는 값만**(`grounding_entity_contract.
  KNOWN_PRODUCERS`). 임의 비공백 문자열 거절.
- **링크가 낸 좌표는 대상에도 있어야 하고 정확히 같아야** 한다. 없으면 무시가
  아니라 **거절**(`target_has_no_such_coordinate`).
  ★엔티티 `local_id` 는 **`grounding_provenance` 안**에 있다 — top-level 만
  읽으면 늘 비어 「검사할 수 없는 좌표」가 그냥 지나간다(실측 결함).
- top-level 과 provenance 두 자리에 있으면 **서로 같아야** 한다.
- **「없음」과 「틀림」을 가른다**: 못 믿은 사유 다섯과 **원형 링크**를 후보 장부
  행에 남긴다(`link_fault` + `producer_link`). 링크가 없으면 그 칸이 안 붙는다 —
  producer 계약 drift 가 평범한 「없음」처럼 보이면 안 된다.
- 못 붙이면 엔티티는 `entity_row`, 후보는 `deferred` 로 **둘 다** 남는다.

### outlook 1단계 — screen 정본 행의 `source_evidence`

`surface_form·source_anchor·source_quote·quote_source·payload_sha` +
**있을 때만** `occurrences`/spans/provenance. ★**없는 span 은 안 만든다.**
`facet_debt` 는 그 rows 를 거른 **view** 다 — 사본을 안 만든다.
남은 것: 19.2 뒤 `outlook_binding` 장부 · 중앙 acquisition 한 곳.

### 2026-09-01 — `outlook_binding` 장부 · **중앙 참조 조사기** (tip `46b45584`)

`grounding_outlook_binding` (19.2 뒤 장부) 와 `grounding_central_acquisition`
(장부 → 참조 조사 한 곳). ★**아직 아무 스텝도 안 부른다** — manifest 배선은
D cutover 몫이고, 그것을 잠그는 시험이 있다. 유료 API 호출 0.

새 구조를 안 지었다 — 상한·신원·재개는 `ChunkJournal.buy_or_reuse`, 조사는
`reference_acquisition_rounds.acquire_one` 이 그대로 한다.

**공개 끝점 셋** (Codex 가 이것으로 잰다)

| | |
|---|---|
| `inputs_from_ledger` | **정상 입구는 하나**. 나머지 셋 직접 호출 금지(AST 시험) |
| `ledger_coverage(ledger, result)` | 장부의 모든 id 가 산출에 **정확히 한 번** |
| `unfinished_rows(result)` | **사람을 기다리는 줄** — 늘 빈 목록이어야 한다 |

**Codex 가 재현한 결함 다섯**(전부 닫힘, 각각 끝점 시험 있음) —

1. 상한에 닿으면 `raise` 로 서서 **이미 조사한 결과까지 반환값에서 날렸다**.
   지금은 남은 대상만 `retryable` → `reference_unavailable` 로 자동 종결.
2. 구매 신원이 **팩·라운드 계약을 안 접었다** — rounds 1 로 조사한 것을
   rounds 2 가 되썼다. 이제 `acquisition_contract_sha(rounds=)` 를 받고,
   `resolve_rounds()` 가 **한 번** 정한 값을 해시와 `acquire_one` 양쪽에 준다.
3. `NeedsHumanDecision` 이 남아 **사람을 기다렸다**(사용자 확정 「HITL 0」과
   반대). 중앙에서만 해석한다 — 장부 전역 정책은 안 건드린다.
4. 회계 이름이 거짓말 — `attempted=2` 인데 실제 조사 호출은 1회.
   `targets_total`/`targets_processed`/`dispatch_attempted_this_run`/
   `reused_this_run`/`bought_or_uncertain_epoch` 로 갈랐다. 이번 판 수는
   **장부 눈금의 차이**로 낸다.
5. 자동완료·비대상 줄이 **수로만** 남아 원행(screen·facet 좌표·인용)이
   사라졌다. 세 곳에서 버려지고 있었다(`acquisition_inputs` skipped/refused ·
   `inputs_from_ledger` · `run`). 이제 `rows` 가 **장부 전부**를 덮고
   `ledger_row` 를 그대로 안고 간다. 수는 그 행에서 파생.

**예외 경계는 allowlist** — `ProviderCallUncertain` **하나만** 접는다. 앞 판의
blacklist 는 `KeyError`·`ValueError`·`IndexError` 를 provider 장애로 둔갑시켰다.
재전파가 HITL 을 안 만드는 이유: 장부에 `uncertain` 이 남고 **다음 재개가**
사람 없이 자동 종결한다.

★내가 낸 결함 — 대역이 판정 모양을 **지어냈다**(`object_type_match` 에 `True`,
실물은 `ctp.TYPE_MATCH` enum). 2라운드로 간 것을 「상한을 넘겼다」로 읽을
뻔했다. 양성 대조(맞으면 1라운드에 `selected`)를 넣어 잡았다.

### 다섯 갈래 세로 완주를 막는 **일곱 자리** (2026-09-01 실측)

`location_part` 참조가 실제 이미지 호출까지 가려면 —

| # | 자리 | 무엇이 없나 |
|---|---|---|
| 1 | `ctx.entities` | `location_parts` 칸이 **아예 없다** |
| 2 | `detail_steps` `visible_entity_details` | 세 갈래를 **손으로 다시 적어** 뒀다 — 계약 `ENTITY_KEY_TO_OWNER` 에 네 번째가 이미 있는데 사본이라 안 따라온다 |
| 3 | `REFERENCE_SUPPORTED_OWNERS` | `("character", "prop")` |
| 4 | `episode_reference_policy_step` | 지원 밖이면 `blocked` — `scene_detail` 을 막는다 |
| 5 | `render_prompt_card.research_forced_refs` | `unsupported` 로 빼고 warning 만 |
| 6 | `ref_contract_validator._ALLOWED_KINDS` | `("character","background","prop")` |
| 7 | `scene_reference_service` | 참조 붙이는 갈래가 인물·아웃룩·상태·소품·배경뿐 |

★**배경 `reference_paths` 는 앞서 구운 배경 판**(`ref_bg_id` · `MAX_REFS_PER_BG=2`)
이지 고증 참조 사진이 아니다. 둘을 섞으면 안 된다(Codex 2026-09-01).
1~2·6~7 은 **더하기만** 하므로 3 이 닫혀 있는 한 inert 다 — 3 이 곧 cutover 스위치.

### 2026-09-01 (이어서) — 조사 **앞**에 서는 의무 · `host_context` producer

`grounding_acquisition_ledger`(갈래 중립 입구) · `grounding_reference_obligations`
(조사 앞 의무) · `grounding_bundle_projection`(옮기기만) 셋이 새로 섰다.
tip `89de5802`. ★아직 아무 스텝도 안 부른다.

**갈래별 resolver → 공통 장부 하나**

    character · location · prop   `resolve_entity_backed` (등록 줄로 바로)
    location_part                 `resolve_facet_rows` (제 신원은 **등록**에서,
                                  부모만 facet 결속에서 — 갈라야 부모 없는 LP 가
                                  안 빠진다)
    outlook                       `grounding_outlook_binding.bind` (phase3)

**의무는 조사 앞에서 선다** — 앞 판은 맥락 멤버를 조사가 끝난 뒤에 만들어
「맥락 사진을 따로 조사한 적이 없는」 상태였다.

    LP##detail   그 부분
    L##context   부모 좌표가 있을 때. 부모가 **비대상이어도** LP 의무에서
                 결정적으로 파생하고 screen 을 다시 안 묻는다.
                 형제들은 의무 **하나**에 `covers` 로 합쳐진다
    LP##context  부모 행이 **없을 때**(`explicit_context_only`).
                 ★`L##` 를 안 지어낸다

**`host_context` producer 를 만들었다** — 팩 `5.202609012800`.
스키마에 `["object","null"]` + `required`(OpenAI strict), `system.md` 에 세 상태
대응표. 작품 고유명사 0. 사슬: `grounding_chunk` → `_settle_host_context`
(기존 `normalize`+`reconcile`, 구간마다 갈리면 `unresolved`) → adapter → carry
→ screen → ledger → 의무 계획. 재료가 없으면 여전히 **지어내지 않고** `skipped`.

**물리 dedupe 는 내용 해시 하나**. 주인이 달라도 같은 bytes 면 한 장이고
`subject_final_ids`·`purposes`·`member_identities`·`coordinates`·
`covered_final_ids` 를 union 한다. 내용이 다르면 두 장 그대로.

**못 구한 것도 남는다** — `unavailable_members()` · `write_sidecar` 가
`RPC_MEMBERS_KEY` 에 전부 적고 `RPC_REQUIRED_KEY` 는 `selected` 만.

**실제 사슬 끝점** (`test_five_lanes_to_the_bundle`) — 기존 `period_episode`
fixture, 손으로 짓는 것은 모델 행 하나뿐:
`reduce_episode → project_rows_and_candidates → build_subjects →
build_population → screen_subjects → resolver 셋 → merge → 의무 계획 →
중앙 조사기 → 투영 → plan_bundle → write_sidecar → members_from_rpc`.
다섯 갈래 전부 통과 · 배경 자리 맥락 1 + 상세 1 · 부모 행 없는 LP 는
**두 대상**(`LP01#detail` 「…이발소 회전 간판」 · `LP01#context` 「…이발소」).

### 하류 준비 — 어디까지 됐나 (2026-09-01 실측)

| 자리 | 상태 |
|---|---|
| `prompt_service.REF_ROLE_VALUES` | ✅ 세 역할 선언됨 · `undeclared_roles()` 빈 목록 |
| `ref_contract_validator._ALLOWED_KINDS` | ✅ `background` 를 이미 받는다(새 kind 안 만듦) |
| `grounding_reference_bundle.attach_from_rpc` | ✅ 있음 · sidecar 없으면 활성 경로 **무변** |
| `detail_steps` 갈래 표 | ✅ `ENTITY_KEY_TO_OWNER` 에서 파생(칸 없으면 무동작) |
| `ctx.entities["location_parts"]` | ⏸ **cutover 몫** — `scene_context_loader` 가 `entity_t2i` CP 의 `data` 를 그대로 받는데 `entity_merge`/`entity_t2i` 가 세 갈래만 낸다. 칸을 더하면 **체크포인트 모양이 바뀌어** config hash·무효화가 같이 움직인다 |

★`REFERENCE_SUPPORTED_OWNERS` 한 줄만 여는 것이 cutover 가 **아니다** —
그 gate + manifest step + sidecar producer/consumer + config hash bump +
옛 중복 조사 경로 비활성화가 **같은 원자적 판**이어야 한다(Codex 09-01).
한 줄만 먼저 열면 옛 경로가 location/LP 를 blocked 에서 풀면서 새 중앙 산출
없이 `required_refs` 부터 내는 **반쪽 활성화**가 된다.

### §5 중복 유료 호출 — **네 곳이 아니었다** (2026-09-01 실측)

참조 사진을 **실제로 사는**(`search_reference_images` + `download_candidate`)
production 자리는 **한 곳뿐**이다 —

    app/core/steps/outdoor_structure_form_reference_step.py  (한 파일 안 두 자리)

그리고 그 스텝은 **이미 소유권 문을 지킨다** —
`_execute:1302` 가 `reference_acquisition.acquisition_owner(mode,
front_checkpoint_exists=)` 를 부르고, 소유자가 앞쪽이면
`_project_front_checkpoint(front_cp)` 로 **네트워크를 한 번도 안 쓰고**
읽어서 투영만 한다. 문은 **두 구매 자리보다 앞**에 있다(`_run_group` 은
`_execute` 안에서 그 뒤에 불린다). `assert_not_buying` 도 있다.

    v2 이고 **앞쪽 산출이 있으면**   → 앞쪽이 소유자 · 야외는 읽기 전용
    그 밖                            → 야외가 그대로 산다 (**명시적 legacy**)

★그래서 §5 는 「네 곳을 adapter 로 바꾸는 일」이 아니라 **앞쪽이 실제로
산출을 내게 하는 일**이다 — 지금은 `reference_acquisition_step._execute` 가
대상이 있으면 `NotImplementedError` 로 서 있어 소유자가 늘 야외다.
그 자리를 중앙 조사기로 채우는 것이 곧 cutover 이고, 그때 legacy fallback 이
사라진다. ★fallback 이 남아 있는 동안은 「이관 완료」라고 쓰지 않는다.

★`search_grounded_ref` 를 import 하는 다른 자리들(`image_steps` ·
`outdoor_structure_seed_step` · `grounding_claims_search` · `typology_prior` ·
`multiroll_gemini` · `grounding_ref_brief`)은 **웹 검색 도구나 계약 해시**를
쓰는 것이고 참조 사진을 사지 않는다 — 세어 넣으면 안 된다.

### cutover 뼈대는 이미 있다 (2026-09-01 실측)

`app/core/grounding_mode.py` 가 **선언과 수용을 이미 갈라 뒀다** —

    GROUNDING_MODE_V2_CHUNK = "v2_chunk"
    PLANNED_MODES  = {v2_chunk}        ← **아직 안 받는다**
    GROUNDING_MODES                     ← 실제로 받는 것
    assert not (PLANNED_MODES & GROUNDING_MODES)   ← 모듈 로드 때

`resolve_grounding_mode` 는 `GROUNDING_MODES` 밖 값을 **거절**한다(조용히
legacy 로 안 떨어뜨린다). 그래서 묶음을 **planned mode 뒤에서 무료로**
완성하고, 마지막에 `v2_chunk` 를 `PLANNED_MODES` → `GROUNDING_MODES` 로
옮기는 것이 활성화다 — 그 한 줄이 manifest·sidecar producer/consumer·
config hash bump·옛 경로 끄기와 **같은 판**에 있어야 한다.

**남은 것**: ~~D 원자적 cutover(위 묶음) · 격리 production canary ·
최종 이미지 생성 호출 1회 canary · 사람 평가(§6.5b)~~ — 2026-09-03 10:20 전부 닫힘(사람 평가는 사용자 판단으로 마감). 범위 밖 후속은 HANDOFF ⑦.
