검증 완료. 양쪽 DB에서 4종 라우트가 서로 다른 단일 id로 정확히 해소됩니다(충돌 불가). 보고드립니다.
결론 — 수정된 0063 적용 준비 완료 (양쪽 DB 동일하게 안전)
[무엇을 바꿨나] migrations/0063_legal_doc_catalog_slug.sql 교체 (단일 파일, doc_slug.ts SSOT는 손대지 않음 — 4종 slug 철자 동일 유지).
- 라우트 4종 UPDATE를
WHERE id=(SELECT id ... WHERE doc_name=X AND in_scope='core' ORDER BY id LIMIT 1)형태로 변경 → 구조적으로 최대 1행만 갱신.
[왜 이 식별축인가 — 요청 1] id 못박기(1/9/238)와 core 둘 다 지금은 맞지만, in_scope='core'를 채택했습니다. id는 재시드 시 깨지는 시드순서 부산물이고, core는 의미축이라 더 안정적입니다. 게다가 LIMIT 1 서브쿼리로 감싸 core가 또 2개로 늘어도 UNIQUE 충돌이 원천 불가능 — 0063이 다시는 같은 에러를 못 냅니다.
[답변서 — 요청 2] 실DB 양쪽에 정확명칭 답변서 행이 0개입니다. 존재하는 건 전부 변형(답변서(청구취지/원인) 15·11·4행 …, 전부 out, core 없음). 임의 변형에 dapbyeon을 붙이면 의미가 어긋나므로 보류 처리(UPDATE는 현재 0행 no-op). dapbyeon은 카탈로그 미연결 → 메뉴에서 빠짐, SSOT의 dapbyeon 항목은 유지. 후속에 법무가 답변서 core 행을 추가하면 그 문장 재실행만으로 자동 연결됩니다.
[멱등성 — 요청 3] UPDATE(0/1행 멱등)·CREATE UNIQUE INDEX IF NOT EXISTS·doc-||id(NULL 행만) 전부 재실행 무해. 유일한 비멱등 문장은 ALTER ... ADD COLUMN(SQLite는 IF NOT EXISTS 미지원) — 실DB 양쪽 slug 컬럼 미존재 실측 확인했으니 그대로 적용 OK. 재적용 시엔 ALTER 한 줄만 주석 처리하라고 파일에 명시했습니다.
[prod 동일성 — 요청 4] lax-prod도 2062행, core id 1/9/238 동일, slug 컬럼 미존재 — lax-db와 완전 일치. 동일 마이그레이션이 양쪽에 그대로 먹습니다.
[검증 결과 — 자가 DONE 아님, 양쪽 DB 실측]
| route | lax-db target_id (core_n) | lax-prod target_id (core_n) |
|---|---|---|
| 소장→sojang | 1 (1) | 1 (1) |
| 지급명령→jigeup-myeongryeong | 238 (1) | 238 (1) |
| 준비서면→junbi-seomyeon | 9 (1) | 9 (1) |
| 답변서→dapbyeon | null (0, 보류) | null (0, 보류) |
3종이 서로 다른 단일 id로 해소 → 충돌 불가. (UPDATE의 서브쿼리만 SELECT로 비파괴 실행해 확인. 실제 적용은 변우석님 몫으로 남겨둠.)
[롤백] DB는 제가 건드리지 않았습니다(파일만 수정). 파일 원복: git checkout migrations/0063_legal_doc_catalog_slug.sql. 적용 후 DB 원복이 필요하면 DROP INDEX idx_doccat_slug; 후 신규 테이블 재생성 없이는 컬럼 제거가 SQLite상 번거로우니, 적용 전 스냅샷(wrangler d1 export)을 떠두시길 권합니다.
바로 테스트→프로덕션 적용·검증 진행하셔도 됩니다.