diff --git a/migrations/211_md_canonical_layer.sql b/migrations/211_md_canonical_layer.sql new file mode 100644 index 0000000..dd2c24b --- /dev/null +++ b/migrations/211_md_canonical_layer.sql @@ -0,0 +1,58 @@ +-- 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/212_document_lineage.sql b/migrations/212_document_lineage.sql new file mode 100644 index 0000000..7d61565 --- /dev/null +++ b/migrations/212_document_lineage.sql @@ -0,0 +1,29 @@ +-- 212_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 ( + 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, + relation_type TEXT NOT NULL + CHECK (relation_type IN ('cited','summarized_from','generated_from','revised_from')), + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT document_lineage_no_self + CHECK (source_document_id <> derived_document_id), + 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);