# DB Row Verification Audit

Date: 2026-05-14 (audit 시작) / 2026-05-15 (작성)
Scope: `backend/db.sqlite` 실제 query + `prompt_loader.py` 동작 재검증 + 11 affected module 운영 active source 매트릭스.

## 1. Audit Boundary + Critical Caveat

**Critical caveat (사용자 명시)**:
> "확인 필요"라고만 적으면 가치 없음. 실제로 affected (module, stem) 별 DB row 존재 여부, file fallback 여부, code pin 여부를 적어야 합니다. 아직 query 안 했으면 상태를 PENDING 으로 명확히, 완료 전에는 "verified" 라고 쓰면 안 됩니다.

본 파일은 위 명시 정책 의무.

Included:
- `backend/db.sqlite` 실제 query 시도
- `prompt_loader.py` DB-first / file-fallback 동작 재read (사용자 메모 명시 line 1-5, 7-14, 30-33, 108-137)
- 11 affected module 의 `(DB row | active | file fallback | code pin)` 매트릭스
- `_PROMPT_VERSION` 상수 grep 전수
- PENDING 항목 명시 (production DB / table DDL 등 — local 에서 검증 불가)

Excluded:
- prompt content 자체 결함 (시나리오 leak / 도메인 분산) → `02-`
- code regex / substring → `01-`
- LLM classifier instruction → `03-`
- 본 audit 영역 = **운영 active source 확정** 만.

## 2. Verification Passes Performed

### 2.1 DB file 접근 확인

```
$ ls -la backend/db.sqlite
-rw-r--r-- 1 user staff 0 ...  backend/db.sqlite
$ sqlite3 backend/db.sqlite ".tables"
(empty output)
$ sqlite3 backend/db.sqlite "SELECT COUNT(*) FROM sqlite_master;"
0
```

- DB file 존재. 크기 = **0 bytes**.
- `sqlite_master` 0 rows = **0 tables**. `prompt_template` table 부재.
- `find backend -name "*.sqlite*" -o -name "*.db"` 결과 = `backend/db.sqlite` 단 1개. 대체 DB 없음.

**결론**: local checkout 에서 DB row override 가 **architecturally 불가능** (table 자체가 없음). 본 audit 의 모든 DB row 결과는 **현재 checkout 기준**.

### 2.2 `prompt_loader.py` 동작 재검증

`backend/app/modules/prompt_loader.py` 전수 read (1-404 LOC).

**핵심 동작 (line 인용)**:

- **line 1-5** (header docstring): `1. DB에서 module+name+is_active=true 조회 / 2. 없으면 파일에서 최신 버전 로드 / 3. 새 프롬프트 저장 시 DB + 파일 동시 저장` — DB-first / file-fallback / dual-write 정책.
- **line 7-14** (stem-level latest scan): 하나의 module dir 안에서 각 stem (`system.md` / `schema.json` 등) 이 **독립적으로** 자신의 latest version dir 선택. `system.md` v11 + `schema.json` v10 coexist 가능.
- **line 30-33** (DB-aware caller bypass): `db=` 인자로 DB row hit 시 `_load_from_file` 호출 없음 — strict file pack 검증 우회. 거의 모든 caller 가 file-only (db 미전달), `location_floor_plan_step` 등 일부만 db-aware.
- **line 73-99** (`_select_latest_active_row`): SQL = `SELECT * FROM prompt_template WHERE module = :m AND name = :n AND is_active = true`. Python-side `_version_sort_key`-desc sort.
- **line 108-137** (`load_prompt`): `if db: row = _select_latest_active_row(...); if row: content = row.content` → DB row wins when present. `if content is None: content = _load_from_file(...)` → file fallback otherwise.

**Inferred schema** (table 부재로 직접 query 불가, attribute access 로 추론):
- `prompt_template(module, name, version, content, schema_json, is_active, id)` — `row.content`, `row.version`, `row.schema_json`, `row.id` 접근.
- PK 추정 `(module, name, version)` — **DDL 확인 불가 (PENDING)**. alembic migration file 로 확인 가능.

## 3. 11 Module 운영 Active Source 매트릭스

각 column 의미:
- **DB row**: production DB 에 row 존재 여부 (local DB empty 이므로 PENDING 또는 N/A).
- **active=true**: DB row 가 active 인지 (DB row 있을 때만 의미).
- **file fallback latest dir**: file fallback path 의 latest version dir.
- **code pin**: `prompt_loader` 우회 (hardcoded Path) 또는 load-time version selector 상수.
- **Status**: verified (file/code path 검증 완료) / PENDING (production DB 검증 불가).

| # | module | DB row | active=true | file fallback latest dir | code pin | Status |
|---|---|---|---|---|---|---|
| 1 | `entity_extraction` | N/A (table 부재) | N/A | `v7` (3 vers: v5/v6/v7) | **hardcoded** `ENTITY_PROMPT_DIR = PROMPTS_BASE/"entity_extraction"/"v7"` (`entity_extractor_legacy.py:14`) — **prompt_loader 우회** | verified (file v7 pin, **orphan 가능성**: 0 importers outside self) |
| 2 | `scene_extractor_v2` | N/A | N/A | `17.202604101200` (3 vers) | none | verified — `scene_extractor_v2.py:31, :35` `load_prompt(_MODULE, ...)` **no `db=`** (pure file fallback) |
| 3 | `t2i_visual_converter` | N/A | N/A | `v3` (3 vers: v1/v2/v3) | **hardcoded** `PROMPT_DIR = .../t2i_visual_converter/v3` (`t2i_visual_converter.py:9-12`), `.read_text()` direct at line 62 — **prompt_loader 우회** | verified (file v3 pin, DB path architecturally 불가능) |
| 4 | `t2i_review` | N/A | N/A | `3.202605121200` (5 vers) | none | verified — `t2i_review.py:167, :256` `load_prompt("t2i_review", ...)` **no `db=`** |
| 5 | `entity_all` | N/A | N/A | `4.202603310100` (3 vers) | none | verified — `entity_lister.py:34-36, :181-184, :231-233` `load_prompt(_MODULE, ...)` **no `db=`** |
| 6 | `entity_character_list` | PENDING (production) | PENDING | `2.202605011057` (2 vers) | none | **only db-aware caller** — `character_list_step.py:66-67` `load_prompt("entity_character_list", "system", db=self.db)`. local DB empty → file path effective. **production DB row 존재 시 file override silently** — verify production. |
| 7 | `outlook_extractor` | N/A | N/A | `11.202603311724` (3 vers) | none | verified — `outlook_extractor.py:21` + `outlook_extractor_v2.py:22, :69, :138` `load_prompt(_MODULE, ...)` **no `db=`** |
| 8 | `entity_relation` | N/A | N/A | `2.202603301800` (2 vers) | none | verified — `entity_relation.py:93-95` `load_prompt(_MODULE, ...)` **no `db=`** |
| 9 | `visual_world_rules` | N/A | N/A | `6.202605021400` (6 vers) | none | verified — `visual_world_rules.py:35-36` `load_prompt(_MODULE, ...)` **no `db=`** |
| 10 | `shot_director` | N/A | N/A | `5.202605131800` (5 vers) | `SHOT_DIRECTOR_PROMPT_VERSION = "5.202605131800"` (`shot_director_step.py:30`) — **metadata stamp only** (loader 미사용) | verified (constant = manifest stamp, not load-time selector) |
| 11 | `scene_detail_owned_judge` | N/A | N/A | `3.202605051746` (3 vers) | none | verified — `_owned_judge.py:75-77` `load_prompt(...)` **no `db=`** |

## 4. Code Constant Pin Grep 전수

```
$ rg -n '\b[A-Z_]*PROMPT_VERSION\s*=' backend/app
# 또는: grep -rn -E '\b[A-Z_]*PROMPT_VERSION\s*=' backend/app --include="*.py"
# Note: leading-underscore-only (`_PROMPT_VERSION\s*=`) = 2 hits (shot_director_step.py, detail_steps.py).
# 본 audit 의 11 hits = broad `[A-Z_]*PROMPT_VERSION` 정합 (9 `PROMPT_VERSION` + 2 `_PROMPT_VERSION`).

backend/app/core/steps/location_floor_plan_step.py:39:       PROMPT_VERSION = "3"
backend/app/core/steps/background_prompt_step.py:36:        PROMPT_VERSION = "6.202605091200"
backend/app/core/steps/shot_director_step.py:30:           SHOT_DIRECTOR_PROMPT_VERSION = "5.202605131800"  ← module 10
backend/app/core/steps/background_chain_planning_step.py:22: PROMPT_VERSION = "4"
backend/app/core/steps/floor_plan_prompt_step.py:29:        PROMPT_VERSION = "4.202605091200"
backend/app/core/steps/background_planner_step.py:42:        PROMPT_VERSION = "3"
backend/app/core/steps/background_classify_step.py:28:       PROMPT_VERSION = "3"
backend/app/core/steps/background_master_plan_step.py:40:    PROMPT_VERSION = "3.202605092023"
backend/app/core/steps/detail_steps.py:115:                SCENE_DETAIL_PROMPT_VERSION = "23.202605141758"
backend/app/core/steps/floor_plan_render_step.py:34:        PROMPT_VERSION = "1"
backend/app/core/steps/background_render_step.py:45:        PROMPT_VERSION = "2.202604301500"
```

**11 hits 전수 분석**:

| Line | 변수 | 용도 | load-time selector? |
|---|---|---|---|
| location_floor_plan_step.py:39 | `PROMPT_VERSION = "3"` | manifest stamp | **No** |
| background_prompt_step.py:36 | `PROMPT_VERSION = "6.202605091200"` | manifest stamp | No |
| shot_director_step.py:30 | `SHOT_DIRECTOR_PROMPT_VERSION` | step_run.prompt_version annotation | No (module 10 metadata) |
| background_chain_planning_step.py:22 | `PROMPT_VERSION = "4"` | manifest stamp | No |
| floor_plan_prompt_step.py:29 | `PROMPT_VERSION = "4.202605091200"` | manifest stamp | No |
| background_planner_step.py:42 | `PROMPT_VERSION = "3"` | manifest stamp | No |
| background_classify_step.py:28 | `PROMPT_VERSION = "3"` | manifest stamp | No |
| background_master_plan_step.py:40 | `PROMPT_VERSION = "3.202605092023"` | manifest stamp | No |
| detail_steps.py:115 | `SCENE_DETAIL_PROMPT_VERSION = "23.202605141758"` | manifest stamp + checkpoint invalidation | No (load-time 미사용) |
| floor_plan_render_step.py:34 | `PROMPT_VERSION = "1"` | manifest stamp | No |
| background_render_step.py:45 | `PROMPT_VERSION = "2.202604301500"` | manifest stamp | No |

**결론**: 11 hits 모두 **metadata stamp** (manifest hashing / step_run 기록용). `load_prompt` 가 `version=` parameter 없음 — 선택은 항상 "latest active" (DB or file). 즉 **load-time 핀이 아님**.

**진짜 load-time pin (hardcoded Path bypass)**:
- module 1 `entity_extraction` — `entity_extractor_legacy.py:14`
- module 3 `t2i_visual_converter` — `t2i_visual_converter.py:9-12, :62`

이 2개는 `prompt_loader` 우회 — 새 file dir 추가만으로는 효과 없음, Python source 의 path string 도 bump 의무.

## 5. 운영 반영 영향 분석

DB empty 가정 (local checkout 기준):

### 5.1 8개 module — Pure file fallback (modules 2, 4, 5, 7, 8, 9, 10, 11)

- 새 version dir 만들면 numeric-desc `_version_sort_key` 가 자동 latest 선택.
- code 변경 0.
- **Hygiene v1 P-1 ~ P-6 affected module = 1, 2, 3, 4 (4개)**. 그 중 pure file fallback = **module 2, 4** → file 새 version dir 만으로 active. module 1 (`entity_extraction`) + module 3 (`t2i_visual_converter`) = hardcoded Path bypass — §5.3 참조.
- module 5, 7, 8, 9, 10, 11 = pure file fallback 이지만 Hygiene v1 직접 대상 아님. module 11 (`scene_detail_owned_judge`) = LLM-side L-2 영역 (`03-`), module 5/7/8/9 = visual_world_rules SOT 일원화 별도 area 대상, module 10 (`shot_director`) = LLM-side L-1 영역.

### 5.2 1개 module — db=-aware caller (module 6 `entity_character_list`)

- `character_list_step.py:66-67` 만 `db=` 전달.
- local DB empty → file fallback 적용 (현재 checkout).
- **production DB 에 active row 존재 시 file override 됨** — file edit 만으로는 runtime 도달 X.
- 조치: production DB 에 active row 존재 확인. 존재 시 **double-write** (DB + file) 또는 row inactivate 후 file 만.
- **PENDING**: production DB 검증 불가 (local 에서).

### 5.3 2개 module — hardcoded Path bypass (modules 1, 3)

- `entity_extraction` (module 1): `ENTITY_PROMPT_DIR / "v7"` hardcoded. swap 시 **(a)** path string `"v7"` → `"v8"` 으로 bump + **(b)** `v8/` dir 생성 모두 필요. 새 dir 만으로는 효과 없음.
- `t2i_visual_converter` (module 3): `PROMPT_DIR / "v3"` hardcoded, `.read_text()` direct. 동일 규칙.
- **module 1 orphan 가능성**: `entity_extractor_legacy.py` 0 static importers in `backend/app` (grep 결과 self-reference 만). dynamic import / project_config switch 통해 reachable 일 가능성 있음 — **PENDING**.

### 5.4 module 10 `shot_director` — metadata constant only

- `SHOT_DIRECTOR_PROMPT_VERSION` 은 manifest stamp. loader 가 미사용.
- 새 file dir 만들면 auto-active. constant 도 sync 의무 (manifest 정확성 위해, 단 prompt 효력에는 영향 X).

## 6. PENDING (must not be marked "verified")

다음 4 항목은 **local checkout 에서 검증 불가**. "verified" 로 표시 금지 (사용자 명시).

### 6.1 Production DB row state for all 11 modules

- Local `backend/db.sqlite` 0-byte / empty. Production 은 별도 DB (Postgres or SQLite remote).
- 검증 방법:
  ```sql
  SELECT module, name, version, is_active 
  FROM prompt_template 
  WHERE module IN ('entity_extraction', 'scene_extractor_v2', 't2i_visual_converter', 't2i_review', 'entity_all', 'entity_character_list', 'outlook_extractor', 'entity_relation', 'visual_world_rules', 'shot_director', 'scene_detail_owned_judge')
    AND is_active = true
  ORDER BY module, name, version DESC;
  ```
- 특히 module 6 `entity_character_list` 우선 — only db-aware caller.

### 6.2 `prompt_template` table DDL

- column list (`content`, `schema_json`, `is_active`, `id`) 는 `_select_latest_active_row` row attribute access 로 추론. DDL 직접 확인 불가.
- PK columns `(module, name, version)` 추정 — alembic migration file 검토 의무.

### 6.3 module 1 `entity_extraction` live-or-orphan

- `entity_extractor_legacy.py` 가 `backend/app` 내 0 importers (grep 결과 self 만).
- dynamic import / project_config switch / version_registry 통해 reachable 일 가능성 있음.
- 검증 방법: runtime trace / `grep -rn entity_extractor_legacy` outside backend / pipeline config 확인.
- 만약 truly orphan 이면 module 1 hygiene fix 우선순위 ↓.

### 6.4 `entity_extractor_v2` separate dir boundary

- `prompts/_base/entity_extractor_v2/` 가 7 version dirs (up to `9.202605130226`) 보유.
- 본 audit 11 module 인벤토리에 부재 (Codex / 사용자 메모 미명시).
- `version_registry.py:45` 가 `entity_extractor_v2/v9` 참조 — 실제 운영 module 가능성.
- `entity_extraction` (module 1) 가 truly orphan 이라면 사실상 `entity_extractor_v2` 가 active.
- **본 audit scope 외** — 별도 검증 의무.

## 7. Boundary Notes

- DB content fidelity (LLM closed-list pollution, scenario leakage) → `02-` (prompt-side)
- Code-side regex / substring semantic judgment → `01-` (code-side)
- LLM classifier instruction quality → `03-` (LLM-side)
- 본 audit = **운영 active source 확정** 만 (DB row vs `_load_from_file` numeric-desc vs hardcoded `Path` 상수 — load-time binding).

## 8. Key Findings Summary

1. **DB row override risk minimal in current scope**: 11 module 중 1개 (`entity_character_list`) 만 `load_prompt` 에 `db=` 전달. 나머지 10개는 DB 완전 무시 (8개 no `db=` arg, 2개 hardcoded Path bypass).
2. **2개 module bypass `prompt_loader` outright** (modules 1, 3) — DB override architecturally **불가능**. **module 1 (`entity_extraction`) = orphan 확정** (2026-05-15 follow-up): runtime path 0, Hygiene v1 외 별도 cleanup area. **module 3 (`t2i_visual_converter`) = active path** (P-5 fix): path string bump 의무 (`t2i_visual_converter.py:9-12` `"v3"` → `"v4"`).
3. **No `PROMPT_VERSION`-family constant gates `load_prompt` selection** — `PROMPT_VERSION`-family 11 hits (9 `PROMPT_VERSION` + 2 `_PROMPT_VERSION`) 모두 metadata stamp. module 10 `SHOT_DIRECTOR_PROMPT_VERSION = "5.202605131800"` 은 manifest 정확성용, 효력에는 영향 X.
4. **Local DB empty** → 본 audit 결과 모두 "현재 checkout 기준". Production DB 별도 검증 의무 (§6.1).
5. **PENDING 2 항목 (2 해소됨, 2026-05-15 follow-up)**:
   - 남은: production DB rows / `prompt_template` table DDL — local 에서 결정 불가.
   - **해소 (§6.3)**: module 1 `entity_extraction` = **orphan 확정** — `entity_extractor_legacy.py` import 0, caller 0.
   - **해소 (§6.4)**: `entity_extractor_v2` boundary 명확화 — `entity_extractor_v2/9.202605130226/` = current active prompt path + `entity_extractor_v3.py` (impl) + `entity_extractor_v2_legacy.py` (fallback, provenance label emit).

## 9. Hygiene v1 Patch 적용 시 운영 반영 결정 매트릭스

P-1 ~ P-6 fix 시 module 별 적용 의무:

| Hygiene v1 row | Affected module | 적용 의무 |
|---|---|---|
| ~~P-1~~ (orphan, Hygiene v1 외) | module 1 (orphan) | **Hygiene v1 fix 불요** — `entity_extractor_legacy.py` import 0, runtime 효과 0 (2026-05-15 follow-up 확정). Hygiene v1 에서 `entity_extraction/v8` 새 dir 생성 또는 path bump **금지**. Orphan cleanup 별도 area: `entity_extractor_legacy.py` + `prompts/_base/entity_extraction/` archive 또는 삭제 검토. |
| P-2 / P-3 / P-3b (`scene_extractor_v2/.../system.md`, `turn_scene_detail.md`) | module 2 | 새 version dir 생성만. code 변경 0. |
| P-4 (`scene_extractor_v2/.../turn_scene_detail.md`) | module 2 | 동일. |
| P-5 (`t2i_visual_converter/v3/system.md:19`) | module 3 | (a) `prompts/_base/t2i_visual_converter/v4/` 새 dir / (b) `t2i_visual_converter.py:9-12` path string `"v3"` → `"v4"` bump. |
| P-6 (`t2i_review/.../scene_system.md:45-78`) | module 4 | 새 version dir 생성만. code 변경 0. |

**별도 area carry (visual_world_rules SOT 일원화) — module 별 적용 의무**:

- D-1 / D-2 / D-3 / D-4 / D-5 / ADD-6~ADD-11 = modules 5, 6, 7, 8, 9 외 추가 (scene_director / scene_verify / scene_detail / shot_extract / entity_extractor_v2).
- module 6 `entity_character_list` 가 only db-aware caller — production DB row 확인 의무 (§6.1).
- 다른 module 들은 file 새 dir 만으로 active.
