feat(safety): 분류 축 A-1 — material_type/jurisdiction/published_date + legal_acts/legal_meta (mig 340~351)

안전 자료실 plan safety-library-1 A-1 (r3 계약 반영):
- documents 3컬럼 (TEXT+CHECK, nullable additive) + law→jurisdiction NOT NULL 구조 강제
- legal_acts 단일 레지스트리(워치리스트 겸, watermark·repeal_detected_at 포함)
- legal_meta 최소형 (version_key 합성형 UNIQUE, 전 버전 pending 적재 계약)
- partial 인덱스 2 + family 인덱스 + paper DOI partial UNIQUE (doi=서지 단일 보유 계약)
- ephemeral PG16 스모크: 12파일 적용 + CHECK/UNIQUE 계약 6종 검증 PASS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-06-12 21:25:04 +09:00
parent 85304878f4
commit 5da94213ec
14 changed files with 183 additions and 2 deletions
@@ -0,0 +1,6 @@
-- 340_documents_material_type.sql
-- 안전 자료실 분류 축 A-1 (1/12) — 자료유형 컬럼.
-- plan: safety-library-1 (PKM plans/2026-06-12-safety-library-plan.html)
-- TEXT+CHECK 방식 (PG enum 아님 — 152 의 enum ADD VALUE 동일-런 사용 불가 함정 회피).
-- 값 부여 = 수집기 ingest 시점 deterministic (classify_worker 아님 — classify-skip 경로 다수).
ALTER TABLE documents ADD COLUMN IF NOT EXISTS material_type TEXT;
@@ -0,0 +1,6 @@
-- 341_documents_material_type_check.sql
-- 안전 자료실 분류 축 A-1 (2/12) — material_type 값 공간 named CHECK.
-- plan: safety-library-1 0-1 확정 7값. 값 추가 시 = 본 제약 DROP + 재ADD 2파일 (named 라 가능).
-- NULL 은 CHECK 통과 (비안전/일반 문서는 NULL 유지 — 전수 분류 시도 금지).
ALTER TABLE documents ADD CONSTRAINT chk_documents_material_type
CHECK (material_type IN ('law', 'paper', 'book', 'incident', 'manual', 'standard', 'guide'));
@@ -0,0 +1,5 @@
-- 342_documents_jurisdiction.sql
-- 안전 자료실 분류 축 A-1 (3/12) — 관할(나라) 컬럼. 법령 1급 시민 축.
-- plan: safety-library-1 0-1. 'GB' 표기 (news_sources.country 실측 어휘와 통일, UI 라벨만 UK).
-- paper 는 NULL 허용 (국제 학술지 — 관할 개념 부적합). INT = ISO 류 국제기구 자료 유보.
ALTER TABLE documents ADD COLUMN IF NOT EXISTS jurisdiction TEXT;
@@ -0,0 +1,4 @@
-- 343_documents_jurisdiction_check.sql
-- 안전 자료실 분류 축 A-1 (4/12) — jurisdiction 값 공간 named CHECK.
ALTER TABLE documents ADD CONSTRAINT chk_documents_jurisdiction
CHECK (jurisdiction IN ('KR', 'US', 'EU', 'JP', 'GB', 'INT'));
@@ -0,0 +1,7 @@
-- 344_documents_law_jurisdiction_check.sql
-- 안전 자료실 분류 축 A-1 (5/12) — 나라 혼선 금지를 구조로 강제.
-- 법령(material_type='law')인데 jurisdiction NULL 인 행은 적재 자체가 거부된다.
-- 업로드 승인 경로는 proposed_jurisdiction 필수 입력 (KR 기본값 오염 금지 — plan A-2).
-- material_type 이 NULL 이면 식 전체가 NULL = CHECK 통과 (비법령 무영향).
ALTER TABLE documents ADD CONSTRAINT chk_documents_law_jurisdiction
CHECK (material_type <> 'law' OR jurisdiction IS NOT NULL);
@@ -0,0 +1,5 @@
-- 345_documents_published_date.sql
-- 안전 자료실 분류 축 A-1 (6/12) — 유형별 대표 날짜 (패싯 연도·freshness 단일 날짜 축).
-- 법령 = COALESCE(effective_date, promulgation_date) — plan 0-1 R2-M2 확정.
-- 논문 = 발행일 / 재해 = 발생일 / 뉴스·크롤 = extract_meta.published_at backfill (A-3).
ALTER TABLE documents ADD COLUMN IF NOT EXISTS published_date DATE;
+22
View File
@@ -0,0 +1,22 @@
-- 346_legal_acts_table.sql
-- 안전 자료실 A-1 (7/12) — 법령 레지스트리 = 워치리스트 (news_sources 패턴의 법령판).
-- plan: safety-library-1 0-2. statute_watchlist 별도 테이블 안 만듦 (R2 blocker — 이중 정의 해소, watermark 흡수).
-- KOSHA GUIDE / KGS Code 는 비대상 (guide=비법령, KGS=watch-폴더 단독 트랙 R3-M5).
-- 시드 = B-1 PR① (레거시 law_monitor 26개 superset, watch=true 전부 — R3-B1).
-- repeal_detected_at: 어댑터(코어)는 폐지 감지 마킹만, 전이는 일일 잡 단일 지점 (R3-M3).
CREATE TABLE IF NOT EXISTS legal_acts (
family_id TEXT PRIMARY KEY,
jurisdiction TEXT NOT NULL CHECK (jurisdiction IN ('KR', 'US', 'EU', 'JP', 'GB', 'INT')),
law_level TEXT NOT NULL CHECK (law_level IN ('statute', 'decree', 'rule', 'admin_rule', 'code')),
title TEXT NOT NULL,
title_ko TEXT,
parent_family_id TEXT REFERENCES legal_acts(family_id),
native_id TEXT NOT NULL,
source_api TEXT NOT NULL,
watch BOOLEAN NOT NULL DEFAULT TRUE,
poll_cycle TEXT NOT NULL DEFAULT 'daily' CHECK (poll_cycle IN ('daily', 'weekly', 'monthly', 'quarterly')),
watermark TEXT,
repeal_detected_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
+20
View File
@@ -0,0 +1,20 @@
-- 347_legal_meta_table.sql
-- 안전 자료실 A-1 (8/12) — 법령 문서 1건(=1버전 또는 1부속문서)당 1행. documents 1:0..1 위성, 최소형.
-- plan: safety-library-1 0-2. supersedes 체인 컬럼은 미포함 (개정 이벤트 10건 관찰 후 승격).
-- version_key: KR primary = MST / annex = 'MST|별표N' 합성 (같은 MST 별표 다건 UNIQUE 충돌 회피)
-- / interpretation = 소스 native id. dedup 키도 이 합성형 그대로 (R3-M4 silent skip 차단).
-- version_status 운영 계약 (B-1 PR② 일일 잡이 유일한 전이 지점, R2-B2·R3-M3):
-- 전 버전 pending 적재 → 잡이 KST 기준 시행일 도래분 current 승격 + 직전 current 를 superseded
-- + 구버전 청크 in_corpus=false 를 한 트랜잭션 처리. repeal 도 잡 경유.
-- 입법예고 등 신호류 문서는 legal_meta 없음 (legal_meta 존재 = 법령 본문).
CREATE TABLE IF NOT EXISTS legal_meta (
document_id BIGINT PRIMARY KEY REFERENCES documents(id) ON DELETE CASCADE,
family_id TEXT NOT NULL REFERENCES legal_acts(family_id),
law_doc_kind TEXT NOT NULL DEFAULT 'primary' CHECK (law_doc_kind IN ('primary', 'annex', 'interpretation')),
version_key TEXT NOT NULL,
promulgation_date DATE,
effective_date DATE,
version_status TEXT NOT NULL DEFAULT 'pending' CHECK (version_status IN ('pending', 'current', 'superseded', 'repealed')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_legal_meta_version UNIQUE (family_id, law_doc_kind, version_key)
);
@@ -0,0 +1,4 @@
-- 348_documents_material_type_idx.sql
-- 안전 자료실 A-1 (9/12) — material_type partial index (128~131 facet 인덱스 선례).
CREATE INDEX IF NOT EXISTS idx_documents_material_type
ON documents (material_type) WHERE material_type IS NOT NULL;
@@ -0,0 +1,4 @@
-- 349_documents_jurisdiction_idx.sql
-- 안전 자료실 A-1 (10/12) — jurisdiction partial index.
CREATE INDEX IF NOT EXISTS idx_documents_jurisdiction
ON documents (jurisdiction) WHERE jurisdiction IS NOT NULL;
+6
View File
@@ -0,0 +1,6 @@
-- 350_legal_meta_family_idx.sql
-- 안전 자료실 A-1 (11/12) — point-in-time 조회 축.
-- 술어 = COALESCE(effective_date, promulgation_date) (KGS 류 시행일 미상 row 침묵 탈락 방지)
-- 이나 인덱스는 effective_date 단순형으로 시작 — COALESCE expression index 는 실측 후.
CREATE INDEX IF NOT EXISTS idx_legal_meta_family
ON legal_meta (family_id, effective_date DESC);
@@ -0,0 +1,9 @@
-- 351_documents_paper_doi_uq.sql
-- 안전 자료실 A-1 (12/12) — 논문 DOI dedup 구조 강제 (partial UNIQUE).
-- doi 보유 계약 (R3 — R2-B1): paper.doi 는 서지 Document 단일 보유.
-- OA 전문 PDF / 구매분 file Document 는 paper.doi 를 갖지 않고 paper.parent_doi 링크로 연결
-- → 인덱스 식이 NULL 이라 다중 행 허용, 2-Document 구조와 무충돌.
-- DOI 정규화(소문자·prefix 제거)는 단일 함수 경유 — 저장=조회 동일 함수 원칙 (B-3).
CREATE UNIQUE INDEX IF NOT EXISTS uq_documents_paper_doi
ON documents (lower(extract_meta #>> '{paper,doi}'))
WHERE material_type = 'paper' AND extract_meta #>> '{paper,doi}' IS NOT NULL;