From fe26aadb2786f34e449ac5774ae5711cd776c9dd Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 30 Apr 2026 01:57:11 +0000 Subject: [PATCH] fix(canonical): split Phase 1A migrations into single-statement files (211-219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- migrations/211_md_canonical_layer.sql | 58 ------------------- migrations/211_md_columns.sql | 25 ++++++++ migrations/212_md_draft_status_check.sql | 6 ++ migrations/213_md_status_pending_idx.sql | 6 ++ migrations/214_content_origin_idx.sql | 5 ++ migrations/215_md_frontmatter_gin_idx.sql | 7 +++ migrations/216_md_draft_status_idx.sql | 6 ++ ...t_lineage.sql => 217_document_lineage.sql} | 14 +---- migrations/218_lineage_source_idx.sql | 5 ++ migrations/219_lineage_derived_idx.sql | 5 ++ 10 files changed, 67 insertions(+), 70 deletions(-) delete mode 100644 migrations/211_md_canonical_layer.sql create mode 100644 migrations/211_md_columns.sql create mode 100644 migrations/212_md_draft_status_check.sql create mode 100644 migrations/213_md_status_pending_idx.sql create mode 100644 migrations/214_content_origin_idx.sql create mode 100644 migrations/215_md_frontmatter_gin_idx.sql create mode 100644 migrations/216_md_draft_status_idx.sql rename migrations/{212_document_lineage.sql => 217_document_lineage.sql} (63%) create mode 100644 migrations/218_lineage_source_idx.sql create mode 100644 migrations/219_lineage_derived_idx.sql diff --git a/migrations/211_md_canonical_layer.sql b/migrations/211_md_canonical_layer.sql deleted file mode 100644 index dd2c24b..0000000 --- a/migrations/211_md_canonical_layer.sql +++ /dev/null @@ -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 는 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; diff --git a/migrations/211_md_columns.sql b/migrations/211_md_columns.sql new file mode 100644 index 0000000..8944f79 --- /dev/null +++ b/migrations/211_md_columns.sql @@ -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 는 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')); diff --git a/migrations/212_md_draft_status_check.sql b/migrations/212_md_draft_status_check.sql new file mode 100644 index 0000000..49cb313 --- /dev/null +++ b/migrations/212_md_draft_status_check.sql @@ -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'); diff --git a/migrations/213_md_status_pending_idx.sql b/migrations/213_md_status_pending_idx.sql new file mode 100644 index 0000000..cbf4fa5 --- /dev/null +++ b/migrations/213_md_status_pending_idx.sql @@ -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'); diff --git a/migrations/214_content_origin_idx.sql b/migrations/214_content_origin_idx.sql new file mode 100644 index 0000000..6842573 --- /dev/null +++ b/migrations/214_content_origin_idx.sql @@ -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); diff --git a/migrations/215_md_frontmatter_gin_idx.sql b/migrations/215_md_frontmatter_gin_idx.sql new file mode 100644 index 0000000..3d55046 --- /dev/null +++ b/migrations/215_md_frontmatter_gin_idx.sql @@ -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); diff --git a/migrations/216_md_draft_status_idx.sql b/migrations/216_md_draft_status_idx.sql new file mode 100644 index 0000000..cd539cc --- /dev/null +++ b/migrations/216_md_draft_status_idx.sql @@ -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; diff --git a/migrations/212_document_lineage.sql b/migrations/217_document_lineage.sql similarity index 63% rename from migrations/212_document_lineage.sql rename to migrations/217_document_lineage.sql index 7d61565..fbcc31b 100644 --- a/migrations/212_document_lineage.sql +++ b/migrations/217_document_lineage.sql @@ -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); diff --git a/migrations/218_lineage_source_idx.sql b/migrations/218_lineage_source_idx.sql new file mode 100644 index 0000000..1b9bc5a --- /dev/null +++ b/migrations/218_lineage_source_idx.sql @@ -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); diff --git a/migrations/219_lineage_derived_idx.sql b/migrations/219_lineage_derived_idx.sql new file mode 100644 index 0000000..43b6f84 --- /dev/null +++ b/migrations/219_lineage_derived_idx.sql @@ -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);