개선관리 요구사항과 Google 로그인을 완료
This commit is contained in:
parent
cc0a15b7c6
commit
2a39636163
112 changed files with 10166 additions and 527 deletions
|
|
@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS app.app_user (
|
|||
account_status TEXT NOT NULL DEFAULT 'approved'
|
||||
CHECK (account_status IN ('pending','approved','suspended')),
|
||||
admin_access BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
affiliation TEXT NOT NULL DEFAULT '',
|
||||
legal_name TEXT NOT NULL DEFAULT '',
|
||||
department TEXT NOT NULL DEFAULT '',
|
||||
|
|
@ -175,6 +176,7 @@ CREATE TABLE IF NOT EXISTS app.sessions (
|
|||
theory_mode TEXT, -- 'humanistic'|'cbt'|'integrative'
|
||||
stage_path JSONB NOT NULL DEFAULT '[]', -- 단계 전이 이력
|
||||
session_goals JSONB NOT NULL DEFAULT '[]', -- 이번 회기 목표 단계(학습자 선택, 최대 4)
|
||||
learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE, -- 회기 시작 시 계정 정책 스냅샷
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
ended_at TIMESTAMPTZ,
|
||||
FOREIGN KEY (persona_id, persona_version)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,43 @@ CREATE TABLE IF NOT EXISTS kb.source (
|
|||
origin_path TEXT,
|
||||
citation TEXT,
|
||||
external_llm_ok BOOLEAN NOT NULL DEFAULT FALSE, -- C/D면 false → 국내 라우팅
|
||||
CONSTRAINT ck_kb_source_external_license
|
||||
CHECK (license_class IN ('A','B') OR external_llm_ok = FALSE),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- =============================================================================
|
||||
-- 1-A. 관리자 등록 프로토콜 수명주기
|
||||
-- draft 는 원문·메타데이터만 보관하고 검색 인덱스를 만들지 않는다.
|
||||
-- active 전환이 성공한 뒤에만 같은 source_id 의 document/chunk 가 검색 후보가 된다.
|
||||
-- =============================================================================
|
||||
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,
|
||||
CONSTRAINT ck_protocol_external_license
|
||||
CHECK (license_class IN ('A','B') OR external_llm_ok = FALSE),
|
||||
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)
|
||||
)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_protocol_registration_status
|
||||
ON kb.protocol_registration(status, registered_at DESC);
|
||||
|
||||
-- =============================================================================
|
||||
-- 1-B. 원문 격리 아티팩트 (RAG 색인 금지)
|
||||
-- raw_source_artifact 는 kb.chunk/embedding/FTS 로 들어가지 않는 별도 보관 증거다.
|
||||
|
|
|
|||
|
|
@ -424,6 +424,14 @@ CREATE POLICY p_kb_chunk_select ON kb.chunk FOR SELECT USING (
|
|||
OR app.current_role_name() IN ('admin','instructor')
|
||||
);
|
||||
|
||||
-- 프로토콜 등록/활성화 경로는 admin 트랜잭션 안에서 chunk를 인덱싱·퇴역한다.
|
||||
-- SELECT 정책만으로는 INSERT/UPDATE/DELETE가 모두 거부되므로 쓰기 권한은 admin에만 연다.
|
||||
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');
|
||||
|
||||
-- 참고: AI 엔진 DB 롤은 ALTER ROLE engine_svc BYPASSRLS 또는 매 연결에서
|
||||
-- SET app.ai_context='1'; SET app.current_ai_view='client';
|
||||
-- SET app.current_sens_max='1'; (정책별 4-튜플) 로 컨텍스트를 명시 주입한다.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ ALTER TABLE app.app_user
|
|||
ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
ADD COLUMN IF NOT EXISTS account_status TEXT NOT NULL DEFAULT 'approved',
|
||||
ADD COLUMN IF NOT EXISTS admin_access BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
|
||||
|
||||
ALTER TABLE app.app_user
|
||||
|
|
@ -348,7 +349,8 @@ ALTER TABLE app.sessions
|
|||
ADD COLUMN IF NOT EXISTS persona_code TEXT,
|
||||
ADD COLUMN IF NOT EXISTS persona_display_name TEXT,
|
||||
ADD COLUMN IF NOT EXISTS persona_difficulty TEXT,
|
||||
ADD COLUMN IF NOT EXISTS prev_rapport_credit REAL NOT NULL DEFAULT 0.0;
|
||||
ADD COLUMN IF NOT EXISTS prev_rapport_credit REAL NOT NULL DEFAULT 0.0,
|
||||
ADD COLUMN IF NOT EXISTS learner_feedback_enabled BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
ALTER TABLE app.session_state
|
||||
ADD COLUMN IF NOT EXISTS turns_in_stage INT NOT NULL DEFAULT 0;
|
||||
|
|
|
|||
120
infra/db/init/17_improvement_workbook_contracts.sql
Normal file
120
infra/db/init/17_improvement_workbook_contracts.sql
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
-- =============================================================================
|
||||
-- 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue