120 lines
4.3 KiB
PL/PgSQL
120 lines
4.3 KiB
PL/PgSQL
-- =============================================================================
|
|
-- Vignette · 17_improvement_workbook_contracts.sql
|
|
-- 개선관리 워크북의 기존 DB forward 계약.
|
|
-- 신규 볼륨의 02/03/05 foundation과 중복 적용되어도 안전해야 한다.
|
|
-- =============================================================================
|
|
|
|
BEGIN;
|
|
|
|
ALTER TABLE app.app_user
|
|
ADD COLUMN IF NOT EXISTS learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE;
|
|
|
|
ALTER TABLE app.sessions
|
|
ADD COLUMN IF NOT EXISTS learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE;
|
|
|
|
CREATE SCHEMA IF NOT EXISTS kb;
|
|
|
|
-- 과거 행도 외부 LLM 경계를 위반하지 않도록 먼저 fail-closed로 정규화한다.
|
|
UPDATE kb.source
|
|
SET external_llm_ok = FALSE
|
|
WHERE license_class IN ('C', 'D')
|
|
AND external_llm_ok IS TRUE;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint c
|
|
JOIN pg_class t ON t.oid = c.conrelid
|
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
WHERE n.nspname = 'kb'
|
|
AND t.relname = 'source'
|
|
AND c.conname = 'ck_kb_source_external_license'
|
|
) THEN
|
|
ALTER TABLE kb.source
|
|
ADD CONSTRAINT ck_kb_source_external_license
|
|
CHECK (license_class IN ('A', 'B') OR external_llm_ok = FALSE)
|
|
NOT VALID;
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE kb.source
|
|
VALIDATE CONSTRAINT ck_kb_source_external_license;
|
|
|
|
CREATE TABLE IF NOT EXISTS kb.protocol_registration (
|
|
protocol_id UUID PRIMARY KEY,
|
|
source_id TEXT NOT NULL UNIQUE,
|
|
title TEXT NOT NULL CHECK (btrim(title) <> ''),
|
|
source_ref TEXT NOT NULL CHECK (btrim(source_ref) <> ''),
|
|
version INT NOT NULL CHECK (version > 0),
|
|
license_class CHAR(1) NOT NULL CHECK (license_class IN ('A', 'B', 'C', 'D')),
|
|
external_llm_ok BOOLEAN NOT NULL DEFAULT FALSE,
|
|
content TEXT NOT NULL CHECK (btrim(content) <> ''),
|
|
content_hash CHAR(64) NOT NULL CHECK (content_hash ~ '^[0-9a-f]{64}$'),
|
|
status TEXT NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'active', 'retired')),
|
|
registered_by UUID NOT NULL REFERENCES app.app_user(user_id) ON DELETE RESTRICT,
|
|
registered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
activated_at TIMESTAMPTZ,
|
|
retired_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- 테이블이 이전 런타임 DDL로 만들어졌어도 정책 제약을 같은 이름으로 보강한다.
|
|
UPDATE kb.protocol_registration
|
|
SET external_llm_ok = FALSE
|
|
WHERE license_class IN ('C', 'D')
|
|
AND external_llm_ok IS TRUE;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint c
|
|
JOIN pg_class t ON t.oid = c.conrelid
|
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
WHERE n.nspname = 'kb'
|
|
AND t.relname = 'protocol_registration'
|
|
AND c.conname = 'ck_protocol_external_license'
|
|
) THEN
|
|
ALTER TABLE kb.protocol_registration
|
|
ADD CONSTRAINT ck_protocol_external_license
|
|
CHECK (license_class IN ('A', 'B') OR external_llm_ok = FALSE)
|
|
NOT VALID;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_constraint c
|
|
JOIN pg_class t ON t.oid = c.conrelid
|
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
WHERE n.nspname = 'kb'
|
|
AND t.relname = 'protocol_registration'
|
|
AND c.conname = 'ck_protocol_lifecycle_timestamps'
|
|
) THEN
|
|
ALTER TABLE kb.protocol_registration
|
|
ADD CONSTRAINT ck_protocol_lifecycle_timestamps CHECK (
|
|
(status = 'draft' AND activated_at IS NULL AND retired_at IS NULL)
|
|
OR (status = 'active' AND activated_at IS NOT NULL AND retired_at IS NULL)
|
|
OR (status = 'retired' AND activated_at IS NOT NULL AND retired_at IS NOT NULL)
|
|
) NOT VALID;
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE kb.protocol_registration
|
|
VALIDATE CONSTRAINT ck_protocol_external_license;
|
|
ALTER TABLE kb.protocol_registration
|
|
VALIDATE CONSTRAINT ck_protocol_lifecycle_timestamps;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_protocol_registration_status
|
|
ON kb.protocol_registration(status, registered_at DESC);
|
|
|
|
-- 활성화/퇴역은 admin 역할로 kb.chunk를 같은 트랜잭션에서 갱신한다.
|
|
-- 기존 볼륨에도 신규 볼륨과 동일한 admin-only write RLS 계약을 보강한다.
|
|
ALTER TABLE kb.chunk ENABLE ROW LEVEL SECURITY;
|
|
DROP POLICY IF EXISTS p_kb_chunk_admin_write ON kb.chunk;
|
|
CREATE POLICY p_kb_chunk_admin_write ON kb.chunk
|
|
FOR ALL
|
|
USING (app.current_role_name() = 'admin')
|
|
WITH CHECK (app.current_role_name() = 'admin');
|
|
|
|
COMMIT;
|