fix(canonical): split Phase 1A migrations into single-statement files (211-219)

asyncpg exec_driver_sql 의 prepared statement 제약상 multi-statement 파일은
"cannot insert multiple commands into a prepared statement" 에러로 적용 실패.
규칙: 한 migration = 한 statement (다중 ADD COLUMN 절은 단일 statement 라 허용,
인덱스/CHECK/CREATE TABLE 은 별도 파일).

이전 cee01af 의 211_md_canonical_layer.sql (6 statements) + 212_document_lineage.sql
(3 statements) 을 9 파일로 분할:
  211 ALTER TABLE documents ADD COLUMN x13
  212 ADD CONSTRAINT documents_md_draft_status_only_ai
  213 idx_documents_md_status_pending
  214 idx_documents_content_origin
  215 idx_documents_md_frontmatter_gin (선제 인덱스)
  216 idx_documents_md_draft_status
  217 CREATE TABLE document_lineage
  218 idx_document_lineage_source
  219 idx_document_lineage_derived

dry-run 재검증: 13 cols / 28 doc idx / 4 lineage idx PASS.
계획 변경 없음 — schema 결과 동일, 적용 단위만 분할.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-30 01:57:11 +00:00
parent cee01af96a
commit fe26aadb27
10 changed files with 67 additions and 70 deletions
-58
View File
@@ -1,58 +0,0 @@
-- 211_md_canonical_layer.sql
-- Phase 1A: Markdown canonical layer (schema only).
-- Plan: ~/.claude/plans/plan-idempotent-sundae.md (round 3 approved)
--
-- 어떤 워커도 이 컬럼을 채우지 않는다 (Phase 1A scope).
-- 신규 컬럼은 모두 nullable 또는 default 보장 → 기존 코드 경로 영향 0.
-- 상태값은 TEXT+CHECK (확장 시 enum drop/rebuild 비용 회피).
-- Postgres 16.13: ADD COLUMN ... DEFAULT <constant> 는 fast path (table rewrite 없음).
ALTER TABLE documents
ADD COLUMN md_content TEXT,
ADD COLUMN md_frontmatter JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN md_format_version TEXT NOT NULL DEFAULT '1.0',
ADD COLUMN md_status TEXT NOT NULL DEFAULT 'pending'
CHECK (md_status IN ('pending','processing','success','partial','failed','skipped')),
ADD COLUMN md_extraction_engine TEXT,
ADD COLUMN md_extraction_engine_version TEXT,
ADD COLUMN md_extraction_quality JSONB,
ADD COLUMN md_extraction_error TEXT,
ADD COLUMN md_content_hash TEXT,
ADD COLUMN md_source_hash TEXT,
ADD COLUMN md_generated_at TIMESTAMPTZ,
-- content_origin: canonical markdown 생성 경로 분류.
-- 'extracted' = 업로드 원본에서 추출되어야 하는 계열 (완료 여부는 md_status가 결정).
-- 'manual' = 사람이 UI에서 직접 작성한 markdown.
-- 'ai_drafted'= LLM이 검색 evidence 기반으로 작성 (Phase 4).
-- 'imported' = 외부 시스템/기존 markdown에서 가져온 문서 (DEVONthink 등).
ADD COLUMN content_origin TEXT NOT NULL DEFAULT 'extracted'
CHECK (content_origin IN ('extracted','manual','ai_drafted','imported')),
-- md_draft_status: AI-generated Markdown review lifecycle only.
-- 사용 조건은 별도 CHECK (documents_md_draft_status_only_ai)에서 강제.
ADD COLUMN md_draft_status TEXT
CHECK (md_draft_status IS NULL OR md_draft_status IN ('draft','pending_review','approved','revised','rejected'));
ALTER TABLE documents
ADD CONSTRAINT documents_md_draft_status_only_ai
CHECK (md_draft_status IS NULL OR content_origin = 'ai_drafted');
-- 큐 폴링용 (pending/processing만)
CREATE INDEX idx_documents_md_status_pending
ON documents (md_status)
WHERE md_status IN ('pending', 'processing');
-- content_origin 기반 필터 (AI 생성문서 분리 등)
CREATE INDEX idx_documents_content_origin
ON documents (content_origin);
-- frontmatter 기반 검색 (project_code, doc_type 등 GIN).
-- Phase 1A 시점엔 모든 행이 '{}' 이므로 즉시 활용 X.
-- Phase 1B 이후 md_frontmatter 필터링/조회를 위한 선제 인덱스.
-- INSERT latency 문제 발생 시 1순위 rollback 대상.
CREATE INDEX idx_documents_md_frontmatter_gin
ON documents USING GIN (md_frontmatter);
-- 검토 큐 (Phase 4 이후)
CREATE INDEX idx_documents_md_draft_status
ON documents (md_draft_status)
WHERE md_draft_status IS NOT NULL;
+25
View File
@@ -0,0 +1,25 @@
-- 211_md_columns.sql
-- Phase 1A: documents 테이블에 markdown canonical layer 컬럼 13개 추가.
-- Plan: ~/.claude/plans/plan-idempotent-sundae.md (round 3)
--
-- asyncpg exec_driver_sql 단일 prepared statement 제약 — ALTER TABLE 다중 ADD COLUMN
-- 절은 단일 statement 라 허용. 인덱스/CHECK/CREATE TABLE 은 별도 파일 (212~219).
-- PG 16.13: ADD COLUMN ... DEFAULT <constant> 는 fast path (table rewrite 없음).
ALTER TABLE documents
ADD COLUMN IF NOT EXISTS md_content TEXT,
ADD COLUMN IF NOT EXISTS md_frontmatter JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS md_format_version TEXT NOT NULL DEFAULT '1.0',
ADD COLUMN IF NOT EXISTS md_status TEXT NOT NULL DEFAULT 'pending'
CHECK (md_status IN ('pending','processing','success','partial','failed','skipped')),
ADD COLUMN IF NOT EXISTS md_extraction_engine TEXT,
ADD COLUMN IF NOT EXISTS md_extraction_engine_version TEXT,
ADD COLUMN IF NOT EXISTS md_extraction_quality JSONB,
ADD COLUMN IF NOT EXISTS md_extraction_error TEXT,
ADD COLUMN IF NOT EXISTS md_content_hash TEXT,
ADD COLUMN IF NOT EXISTS md_source_hash TEXT,
ADD COLUMN IF NOT EXISTS md_generated_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS content_origin TEXT NOT NULL DEFAULT 'extracted'
CHECK (content_origin IN ('extracted','manual','ai_drafted','imported')),
ADD COLUMN IF NOT EXISTS md_draft_status TEXT
CHECK (md_draft_status IS NULL OR md_draft_status IN ('draft','pending_review','approved','revised','rejected'));
+6
View File
@@ -0,0 +1,6 @@
-- 212_md_draft_status_check.sql
-- Phase 1A: md_draft_status 사용 조건 — content_origin='ai_drafted' 일 때만 NOT NULL 허용.
ALTER TABLE documents
ADD CONSTRAINT documents_md_draft_status_only_ai
CHECK (md_draft_status IS NULL OR content_origin = 'ai_drafted');
+6
View File
@@ -0,0 +1,6 @@
-- 213_md_status_pending_idx.sql
-- Phase 1A: 큐 폴링용 partial index (pending/processing 만).
CREATE INDEX IF NOT EXISTS idx_documents_md_status_pending
ON documents (md_status)
WHERE md_status IN ('pending', 'processing');
+5
View File
@@ -0,0 +1,5 @@
-- 214_content_origin_idx.sql
-- Phase 1A: content_origin 기반 필터 (AI 생성문서 분리 등).
CREATE INDEX IF NOT EXISTS idx_documents_content_origin
ON documents (content_origin);
@@ -0,0 +1,7 @@
-- 215_md_frontmatter_gin_idx.sql
-- Phase 1A: frontmatter JSONB 기반 검색 (project_code, doc_type 등) GIN.
-- Phase 1A 시점엔 모든 행 '{}' 이므로 즉시 활용 X. Phase 1B 이후 frontmatter 필터링용.
-- INSERT latency 문제 발생 시 1순위 rollback 대상.
CREATE INDEX IF NOT EXISTS idx_documents_md_frontmatter_gin
ON documents USING GIN (md_frontmatter);
+6
View File
@@ -0,0 +1,6 @@
-- 216_md_draft_status_idx.sql
-- Phase 1A: AI 생성문서 검토 큐 partial index (Phase 4 이후 활용).
CREATE INDEX IF NOT EXISTS idx_documents_md_draft_status
ON documents (md_draft_status)
WHERE md_draft_status IS NOT NULL;
@@ -1,12 +1,10 @@
-- 212_document_lineage.sql
-- 217_document_lineage.sql
-- Phase 1A: AI 생성 문서가 어느 원본에서 파생됐는지 추적.
-- Plan: ~/.claude/plans/plan-idempotent-sundae.md (round 3 approved)
--
-- 이력 테이블 FK = ON DELETE RESTRICT (feedback_history_table_fk_restrict).
-- 부모 문서 hard delete 차단, soft delete (documents.deleted_at) 만 허용.
-- relation_type 도 TEXT+CHECK (확장 시 enum drop/rebuild 비용 회피).
CREATE TABLE document_lineage (
CREATE TABLE IF NOT EXISTS document_lineage (
id BIGSERIAL PRIMARY KEY,
source_document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE RESTRICT,
derived_document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE RESTRICT,
@@ -19,11 +17,3 @@ CREATE TABLE document_lineage (
CONSTRAINT document_lineage_uq
UNIQUE (source_document_id, derived_document_id, relation_type)
);
-- reverse-lookup: '이 문서가 어느 생성문서의 source 였나'
CREATE INDEX idx_document_lineage_source
ON document_lineage (source_document_id);
-- forward-lookup: '이 생성문서의 source 는 무엇이었나'
CREATE INDEX idx_document_lineage_derived
ON document_lineage (derived_document_id);
+5
View File
@@ -0,0 +1,5 @@
-- 218_lineage_source_idx.sql
-- Phase 1A: lineage reverse-lookup ("이 문서가 어느 생성문서의 source 였나").
CREATE INDEX IF NOT EXISTS idx_document_lineage_source
ON document_lineage (source_document_id);
+5
View File
@@ -0,0 +1,5 @@
-- 219_lineage_derived_idx.sql
-- Phase 1A: lineage forward-lookup ("이 생성문서의 source 는 무엇이었나").
CREATE INDEX IF NOT EXISTS idx_document_lineage_derived
ON document_lineage (derived_document_id);