-- AwoooP Phase 1: Control Plane Schema Foundation
-- 2026-05-04 ogt + Claude Sonnet 4.6（ADR-111~118，Phase 1 Task 1.3~1.7）
-- 2026-05-04 db-expert review 修正版：C-1/C-2/C-4/C-5/M-1/M-2/M-4/M-5/Mi-1/Mi-2/Mi-3
-- 2026-05-04 critic review 修正版：awooop_app role 建立 + GRANT、移除 __platform__ 後門、
--            active_pointer_guard SECURITY DEFINER、pg_partman 冪等、immutability 強化
--
-- ⚠️  部署順序鎖死（ADR-118 RLS 前置條件）：
--     1. apps/api 必須先 deploy「會 SET LOCAL app.project_id」的版本
--     2. K8s rollout 完成（kubectl rollout status deploy/api = 100%）
--     3. 31 個 background loop 改用 awooop_platform_admin role（PR-10 完成）
--     4. 以上完成後，才執行此 migration SQL
--
-- ⚠️  不包含 Batch 1 高流量表（incidents/knowledge_entries/playbooks/audit_logs）
--     → 請執行 awooop_phase1_batch1_rls_2026-05-04.sql（三步式 migration）
--
-- 執行前確認：
--   SELECT relname, n_live_tup, pg_size_pretty(pg_total_relation_size(oid))
--   FROM pg_class WHERE relname IN ('incidents','knowledge_entries','playbooks','audit_logs');
--
-- 執行角色：awooop_migration（BYPASSRLS）
-- 預估執行時間：< 30 秒（全為新表，無既有資料修改）
--
-- 回滾路徑：
--   見 awooop_phase1_control_plane_ROLLBACK.sql
-- ---------------------------------------------------------------------------

CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- ===========================
-- Step 1: DB Roles（ADR-118 D1）
-- ===========================

DO $$
BEGIN
    -- awooop_platform_admin: 平台管理（BYPASSRLS，背景 loop 使用）
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'awooop_platform_admin') THEN
        CREATE ROLE awooop_platform_admin NOLOGIN;
    END IF;
    ALTER ROLE awooop_platform_admin BYPASSRLS;

    -- awooop_migration: migration 執行（BYPASSRLS，只在 migration 期間使用）
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'awooop_migration') THEN
        CREATE ROLE awooop_migration NOLOGIN;
    END IF;
    ALTER ROLE awooop_migration BYPASSRLS;

    -- awooop_app: 應用程式角色（受 RLS 約束，需 SET LOCAL app.project_id）
    -- 必須在 GRANT 之前建立；NOLOGIN 代表 app connection user 要 SET ROLE awooop_app
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'awooop_app') THEN
        CREATE ROLE awooop_app NOLOGIN;
    END IF;
END $$;


-- ===========================
-- Step 2: awooop_projects（租戶主表）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_projects (
    project_id       VARCHAR(64) PRIMARY KEY,
    display_name     VARCHAR(256) NOT NULL,
    migration_mode   VARCHAR(32) NOT NULL DEFAULT 'legacy_awoooi_default',
    budget_limit_usd NUMERIC(14, 4) CHECK (budget_limit_usd IS NULL OR budget_limit_usd >= 0),
    allowed_channels JSONB NOT NULL DEFAULT '[]' CHECK (jsonb_typeof(allowed_channels) = 'array'),
    is_active        BOOLEAN NOT NULL DEFAULT TRUE,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_migration_mode CHECK (
        migration_mode IN ('legacy_awoooi_default','shadow','canary','active')
    )
);

CREATE INDEX IF NOT EXISTS idx_awooop_projects_active
    ON awooop_projects(is_active) WHERE is_active = TRUE;


-- ===========================
-- Step 3: awooop_contract_revisions（六合約共用 revision，append-only）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_contract_revisions (
    revision_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id          VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
    contract_family     VARCHAR(32) NOT NULL,
    contract_id         VARCHAR(128) NOT NULL,
    version_major       SMALLINT NOT NULL DEFAULT 1 CHECK (version_major >= 0),
    version_minor       SMALLINT NOT NULL DEFAULT 0 CHECK (version_minor >= 0),
    lifecycle_status    VARCHAR(16) NOT NULL DEFAULT 'draft',
    body_json           JSONB NOT NULL,
    -- body_hash: SHA-256 hex（64 chars），強制格式
    body_hash           VARCHAR(64) NOT NULL CHECK (body_hash ~ '^[0-9a-f]{64}$'),
    body_schema_version VARCHAR(16) NOT NULL DEFAULT 'v1.0',
    -- publish_signature: HMAC-SHA256 hex，draft 時 NULL
    publish_signature   VARCHAR(128) CHECK (
        publish_signature IS NULL OR publish_signature ~ '^[0-9a-f]+$'
    ),
    publisher_id        VARCHAR(128),
    published_at        TIMESTAMPTZ,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_revision_version
        UNIQUE (project_id, contract_family, contract_id, version_major, version_minor),
    CONSTRAINT chk_contract_family CHECK (
        contract_family IN (
            'project_tenant','agent','mcp_gateway','policy_routing',
            'runtime_run_state','channel_event','platform_resource'
        )
    ),
    CONSTRAINT chk_lifecycle CHECK (
        lifecycle_status IN ('draft','published','active','revoked')
    )
);

-- runtime 讀取路徑：找某 contract 最新 published/active 版本
CREATE INDEX IF NOT EXISTS idx_revisions_lookup
    ON awooop_contract_revisions
       (project_id, contract_family, contract_id, lifecycle_status,
        version_major DESC, version_minor DESC);

-- forensic 驗章反查
CREATE INDEX IF NOT EXISTS idx_revisions_hash
    ON awooop_contract_revisions (body_hash);


-- ===========================
-- Step 4: awooop_active_revisions（active pointer）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_active_revisions (
    pointer_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id         VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
    contract_family    VARCHAR(32) NOT NULL,
    contract_id        VARCHAR(128) NOT NULL,
    -- NOT NULL + ON DELETE RESTRICT（C-1 修正）
    active_revision_id UUID NOT NULL REFERENCES awooop_contract_revisions(revision_id)
        ON DELETE RESTRICT,
    updated_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_active_pointer
        UNIQUE (project_id, contract_family, contract_id)
);


-- ===========================
-- Step 5: awooop_contract_outbox（ADR-113，C-2 修正版）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_contract_outbox (
    event_id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    event_type      VARCHAR(64) NOT NULL,
    -- FK 到 projects（C-2 修正：outbox 不可是孤兒事件）
    project_id      VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
    contract_family VARCHAR(32) NOT NULL,
    contract_id     VARCHAR(128) NOT NULL,
    old_revision_id UUID REFERENCES awooop_contract_revisions(revision_id),
    new_revision_id UUID NOT NULL REFERENCES awooop_contract_revisions(revision_id),
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    delivered_at    TIMESTAMPTZ,
    relay_attempts  INT NOT NULL DEFAULT 0,
    -- C-2 新增：exponential backoff 支援
    next_retry_at   TIMESTAMPTZ,
    last_error      TEXT,
    -- C-2 新增：上游 publisher 重試去重（同一 revision 的同一事件類型只記一次）
    CONSTRAINT uq_outbox_event UNIQUE (new_revision_id, event_type)
);

-- relay worker 主查詢：未投遞 + 可重試（含 next_retry_at NULL = 立即重試）
CREATE INDEX IF NOT EXISTS idx_outbox_pending
    ON awooop_contract_outbox (next_retry_at NULLS FIRST, created_at)
    WHERE delivered_at IS NULL;

-- 觀察用：per project backlog 體量
CREATE INDEX IF NOT EXISTS idx_outbox_backlog_per_project
    ON awooop_contract_outbox (project_id, created_at)
    WHERE delivered_at IS NULL;


-- ===========================
-- Step 6: awooop_channel_event_dedupe（ADR-114，M-1 Partition 版）
-- ===========================
-- pg_partman 維護 1 天 partition，retention 7 天，DROP PARTITION 毫秒清完

CREATE TABLE IF NOT EXISTS awooop_channel_event_dedupe (
    dedupe_id         UUID NOT NULL DEFAULT gen_random_uuid(),
    project_id        VARCHAR(64) NOT NULL,
    channel_type      VARCHAR(32) NOT NULL,
    provider_event_id VARCHAR(256) NOT NULL,
    run_id            UUID NOT NULL,
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    -- Partition key 必須是 PK 的一部分（declarative partition 要求）
    PRIMARY KEY (dedupe_id, created_at),
    CONSTRAINT uq_channel_event_dedupe
        UNIQUE (project_id, channel_type, provider_event_id, created_at)
) PARTITION BY RANGE (created_at);

-- 初始化 pg_partman（若 pg_partman 已安裝）
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_partman') THEN
        -- 冪等：已在 part_config 則跳過 create_parent（重跑 migration 安全）
        IF NOT EXISTS (
            SELECT 1 FROM partman.part_config
             WHERE parent_table = 'public.awooop_channel_event_dedupe'
        ) THEN
            PERFORM partman.create_parent(
                p_parent_table := 'public.awooop_channel_event_dedupe',
                p_control      := 'created_at',
                p_type         := 'native',
                p_interval     := '1 day',
                p_premake      := 4
            );
        END IF;
        UPDATE partman.part_config
           SET retention = '7 days',
               retention_keep_table = false
         WHERE parent_table = 'public.awooop_channel_event_dedupe';
    ELSE
        -- pg_partman 未安裝：手動建前 14 天 partition（含今日 ±7 天）
        DECLARE
            d DATE;
        BEGIN
            FOR d IN
                SELECT generate_series(
                    CURRENT_DATE - INTERVAL '7 days',
                    CURRENT_DATE + INTERVAL '7 days',
                    INTERVAL '1 day'
                )::DATE
            LOOP
                EXECUTE format(
                    'CREATE TABLE IF NOT EXISTS awooop_channel_event_dedupe_%s
                     PARTITION OF awooop_channel_event_dedupe
                     FOR VALUES FROM (%L) TO (%L)',
                    to_char(d, 'YYYYMMDD'),
                    d::TIMESTAMPTZ,
                    (d + INTERVAL '1 day')::TIMESTAMPTZ
                );
            END LOOP;
        END;
    END IF;
END $$;

-- run_id 反查（Mi-5）
CREATE INDEX IF NOT EXISTS idx_dedupe_run
    ON awooop_channel_event_dedupe (run_id);


-- ===========================
-- Step 7: awooop_platform_subjects（ADR-115）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_platform_subjects (
    subject_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id          VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
    channel_type        VARCHAR(32) NOT NULL,
    channel_user_id     VARCHAR(256) NOT NULL,
    channel_chat_id     VARCHAR(256),
    platform_subject_id VARCHAR(128) NOT NULL,
    display_name        VARCHAR(256),
    roles               JSONB NOT NULL DEFAULT '[]' CHECK (jsonb_typeof(roles) = 'array'),
    first_seen_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_seen_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_platform_subject
        UNIQUE (project_id, channel_type, channel_user_id)
);

CREATE INDEX IF NOT EXISTS idx_platform_subjects_lookup
    ON awooop_platform_subjects (project_id, channel_type, channel_user_id);

-- platform_subject_id 反查（Operator Console M2 用）
CREATE INDEX IF NOT EXISTS idx_platform_subjects_resolve
    ON awooop_platform_subjects (project_id, platform_subject_id);

-- 近期活躍 user 查詢
CREATE INDEX IF NOT EXISTS idx_platform_subjects_last_seen
    ON awooop_platform_subjects (project_id, last_seen_at DESC);


-- ===========================
-- Step 8: awooop_project_migration_state（Strangler Fig 追蹤）
-- ===========================

CREATE TABLE IF NOT EXISTS awooop_project_migration_state (
    state_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id       VARCHAR(64) NOT NULL REFERENCES awooop_projects(project_id),
    capability       VARCHAR(64) NOT NULL,
    current_phase    VARCHAR(32) NOT NULL DEFAULT 'legacy_awoooi_default',
    phase_entered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_project_capability UNIQUE (project_id, capability),
    CONSTRAINT chk_capability CHECK (
        capability IN (
            'run_execution','contract_governance',
            'budget_tracking','principal_mapping'
        )
    ),
    CONSTRAINT chk_phase CHECK (
        current_phase IN (
            'legacy_awoooi_default','shadow','canary',
            'read_only','suggest','auto_remediate'
        )
    )
);


-- ===========================
-- Step 9: awooop_published_revisions VIEW（ADR-112 D6 draft 隔離）
-- ===========================

CREATE OR REPLACE VIEW awooop_published_revisions AS
SELECT *
FROM awooop_contract_revisions
WHERE lifecycle_status IN ('published', 'active');


-- ===========================
-- Step 10: updated_at 自動更新 trigger（Mi-1）
-- ===========================

CREATE OR REPLACE FUNCTION awooop_set_updated_at()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$;

DO $$
DECLARE
    t TEXT;
BEGIN
    FOREACH t IN ARRAY ARRAY[
        'awooop_projects',
        'awooop_active_revisions',
        'awooop_platform_subjects',
        'awooop_project_migration_state'
    ] LOOP
        EXECUTE format(
            'DROP TRIGGER IF EXISTS trg_%s_updated_at ON %I;
             CREATE TRIGGER trg_%s_updated_at
             BEFORE UPDATE ON %I
             FOR EACH ROW EXECUTE FUNCTION awooop_set_updated_at();',
            t, t, t, t
        );
    END LOOP;
END $$;


-- ===========================
-- Step 11: Immutability Trigger（C-5 完整版，ADR-112 D2）
-- ===========================
-- 允許的 lifecycle 流轉：
--   draft    → published（publish 操作）
--   published → active  （activate 操作）
--   active   → revoked  （revoke 操作）
-- 禁止：body/hash/signature/version 在 published/active/revoked 後修改

CREATE OR REPLACE FUNCTION awooop_revision_immutability_guard()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    -- 所有 lifecycle_status 下都禁止修改身份欄位（project_id/family/contract_id）
    IF NEW.project_id IS DISTINCT FROM OLD.project_id
       OR NEW.contract_family IS DISTINCT FROM OLD.contract_family
       OR NEW.contract_id IS DISTINCT FROM OLD.contract_id
    THEN
        RAISE EXCEPTION
            'revision % identity fields (project_id/contract_family/contract_id) are immutable',
            OLD.revision_id;
    END IF;

    -- draft 可以自由修改，離開 draft 後鎖住核心欄位
    IF OLD.lifecycle_status IN ('published', 'active', 'revoked') THEN
        IF NEW.body_json IS DISTINCT FROM OLD.body_json
           OR NEW.body_hash IS DISTINCT FROM OLD.body_hash
           OR NEW.publish_signature IS DISTINCT FROM OLD.publish_signature
           OR NEW.version_major IS DISTINCT FROM OLD.version_major
           OR NEW.version_minor IS DISTINCT FROM OLD.version_minor
           OR NEW.publisher_id IS DISTINCT FROM OLD.publisher_id
           OR NEW.published_at IS DISTINCT FROM OLD.published_at
           OR NEW.body_schema_version IS DISTINCT FROM OLD.body_schema_version
        THEN
            RAISE EXCEPTION
                'revision % (%) is immutable: body/signature/version cannot be changed',
                OLD.revision_id, OLD.lifecycle_status;
        END IF;
    END IF;

    -- lifecycle_status 流轉白名單
    IF NEW.lifecycle_status IS DISTINCT FROM OLD.lifecycle_status THEN
        IF NOT (
            (OLD.lifecycle_status = 'draft'     AND NEW.lifecycle_status = 'published') OR
            (OLD.lifecycle_status = 'published' AND NEW.lifecycle_status = 'active')    OR
            (OLD.lifecycle_status = 'active'    AND NEW.lifecycle_status = 'revoked')
        ) THEN
            RAISE EXCEPTION
                'illegal lifecycle transition on revision %: % -> %',
                OLD.revision_id, OLD.lifecycle_status, NEW.lifecycle_status;
        END IF;
    END IF;

    RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS trg_revision_immutability ON awooop_contract_revisions;
CREATE TRIGGER trg_revision_immutability
    BEFORE UPDATE ON awooop_contract_revisions
    FOR EACH ROW EXECUTE FUNCTION awooop_revision_immutability_guard();

-- DELETE 完全禁止（append-only 語意）
CREATE OR REPLACE FUNCTION awooop_revision_no_delete()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    RAISE EXCEPTION
        'awooop_contract_revisions is append-only: DELETE forbidden on revision %',
        OLD.revision_id;
END;
$$;

DROP TRIGGER IF EXISTS trg_revision_no_delete ON awooop_contract_revisions;
CREATE TRIGGER trg_revision_no_delete
    BEFORE DELETE ON awooop_contract_revisions
    FOR EACH ROW EXECUTE FUNCTION awooop_revision_no_delete();


-- ===========================
-- Step 12: Active Pointer Guard（M-5，確保 active_revision_id 指向正確的 active revision）
-- ===========================

-- SECURITY DEFINER：trigger 以 migration 擁有者執行，繞過 awooop_contract_revisions 的 RLS，
-- 確保跨租戶指向檢測（FORCE RLS 下 SECURITY INVOKER 只能看自己租戶的 revision）
CREATE OR REPLACE FUNCTION awooop_active_pointer_guard()
RETURNS TRIGGER LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_catalog
AS $$
DECLARE
    rev RECORD;
BEGIN
    SELECT project_id, contract_family, contract_id, lifecycle_status
      INTO rev
      FROM awooop_contract_revisions
     WHERE revision_id = NEW.active_revision_id;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'revision % not found', NEW.active_revision_id;
    END IF;
    IF rev.project_id <> NEW.project_id
       OR rev.contract_family <> NEW.contract_family
       OR rev.contract_id <> NEW.contract_id
    THEN
        RAISE EXCEPTION
            'active pointer contract identity mismatch: pointer=(%,%,%) revision=(%,%,%)',
            NEW.project_id, NEW.contract_family, NEW.contract_id,
            rev.project_id, rev.contract_family, rev.contract_id;
    END IF;
    IF rev.lifecycle_status <> 'active' THEN
        RAISE EXCEPTION
            'active pointer must reference an active revision (got %)', rev.lifecycle_status;
    END IF;
    RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS trg_active_pointer_guard ON awooop_active_revisions;
CREATE TRIGGER trg_active_pointer_guard
    BEFORE INSERT OR UPDATE ON awooop_active_revisions
    FOR EACH ROW EXECUTE FUNCTION awooop_active_pointer_guard();


-- ===========================
-- Step 13: GRANT awooop_app 基本操作權限
-- ===========================
-- awooop_app 受 RLS 約束，需設定 app.project_id 才能存取資料
-- awooop_platform_admin / awooop_migration 有 BYPASSRLS，不需 GRANT（直接用 superuser 連線）

GRANT SELECT, INSERT, UPDATE, DELETE ON awooop_contract_revisions TO awooop_app;
GRANT SELECT, INSERT, UPDATE ON awooop_active_revisions TO awooop_app;
GRANT SELECT, INSERT ON awooop_contract_outbox TO awooop_app;
GRANT SELECT, INSERT ON awooop_channel_event_dedupe TO awooop_app;
GRANT SELECT, INSERT, UPDATE ON awooop_platform_subjects TO awooop_app;
GRANT SELECT ON awooop_projects TO awooop_app;
GRANT SELECT ON awooop_project_migration_state TO awooop_app;
GRANT SELECT ON awooop_published_revisions TO awooop_app;


-- ===========================
-- Step 14: awooop_* 表 RLS（ADR-118，C-4 fail-closed 修正版）
-- ===========================
-- ⚠️  fail-closed：沒有 SET LOCAL app.project_id 的 session 看不到任何資料
-- ⚠️  awooop_platform_admin / awooop_migration 已 BYPASSRLS，不受 policy 約束
-- ⚠️  WITH CHECK 防止 INSERT 時塞入不同 tenant 的 project_id
-- ⚠️  移除 __platform__ 後門（critic C-3 修正）：平台層改用 BYPASSRLS 角色，不靠 GUC 魔術字串

ALTER TABLE awooop_contract_revisions ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_contract_revisions FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS contract_revisions_tenant ON awooop_contract_revisions;
CREATE POLICY contract_revisions_tenant ON awooop_contract_revisions
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

ALTER TABLE awooop_active_revisions ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_active_revisions FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS active_revisions_tenant ON awooop_active_revisions;
CREATE POLICY active_revisions_tenant ON awooop_active_revisions
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

ALTER TABLE awooop_platform_subjects ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_platform_subjects FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS platform_subjects_tenant ON awooop_platform_subjects;
CREATE POLICY platform_subjects_tenant ON awooop_platform_subjects
    FOR ALL TO awooop_app
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));


-- ===========================
-- Step 15: AWOOOI 種子資料（ADR-111 bootstrap）
-- ===========================

INSERT INTO awooop_projects (project_id, display_name, migration_mode, is_active)
VALUES ('awoooi', 'AWOOOI', 'legacy_awoooi_default', TRUE)
ON CONFLICT (project_id) DO NOTHING;

INSERT INTO awooop_project_migration_state (project_id, capability, current_phase)
VALUES
    ('awoooi', 'run_execution',       'legacy_awoooi_default'),
    ('awoooi', 'contract_governance', 'legacy_awoooi_default'),
    ('awoooi', 'budget_tracking',     'legacy_awoooi_default'),
    ('awoooi', 'principal_mapping',   'legacy_awoooi_default')
ON CONFLICT (project_id, capability) DO NOTHING;


-- ===========================
-- 驗收查詢（執行後人工確認）
-- ===========================
-- \dt awooop_*
-- SELECT project_id, display_name, migration_mode FROM awooop_projects;
-- SELECT project_id, capability, current_phase FROM awooop_project_migration_state;
-- SELECT tablename, rowsecurity, forcerowsecurity FROM pg_tables
--   WHERE tablename LIKE 'awooop_%';
-- -- RLS fail-closed 測試：
-- SET LOCAL app.project_id = 'ewoooc';
-- SELECT count(*) FROM awooop_contract_revisions;  -- 應回傳 0（'ewoooc' 不存在 projects）
-- SET LOCAL app.project_id = 'awoooi';
-- SELECT count(*) FROM awooop_projects;  -- 應回傳 1
