-- 000038_portal_chat.sql — 门户通用聊天:会话、消息链(哈希完整性)与用户运行时凭据。 -- 模型权限申请审批通过后,系统为该用户开通一把 gateway API Key(加密落库), -- 门户"通用聊天"页面用这把 Key 直接调用 /v1/chat/completions,用量与审计 -- 均归属到用户自己的 Key,而不是共享一个全局凭据。 CREATE TABLE IF NOT EXISTS gateway.portal_user_runtime_credentials ( portal_user_id uuid PRIMARY KEY REFERENCES gateway.portal_users(id) ON DELETE CASCADE, api_key_id uuid NOT NULL REFERENCES gateway.api_keys(id) ON DELETE CASCADE, encrypted_key bytea NOT NULL, key_kek_version integer NOT NULL, created_at timestamptz NOT NULL DEFAULT clock_timestamp() ); COMMENT ON TABLE gateway.portal_user_runtime_credentials IS 'Per-portal-user gateway credential used by the general chat. Plaintext is encrypted with the application KEK and never returned to a browser.'; CREATE TABLE IF NOT EXISTS gateway.portal_chat_sessions ( id uuid PRIMARY KEY, portal_user_id uuid NOT NULL REFERENCES gateway.portal_users(id) ON DELETE CASCADE, title varchar(160) NOT NULL DEFAULT '', provider_code text NOT NULL DEFAULT '', model text NOT NULL CHECK (length(model) BETWEEN 1 AND 512), status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived')), busy boolean NOT NULL DEFAULT false, busy_token text, busy_since timestamptz, next_sequence integer NOT NULL DEFAULT 1 CHECK (next_sequence >= 1), created_at timestamptz NOT NULL DEFAULT clock_timestamp(), updated_at timestamptz NOT NULL DEFAULT clock_timestamp() ); CREATE INDEX IF NOT EXISTS portal_chat_sessions_user_time_idx ON gateway.portal_chat_sessions (portal_user_id, updated_at DESC); CREATE TABLE IF NOT EXISTS gateway.portal_chat_messages ( id uuid PRIMARY KEY, session_id uuid NOT NULL REFERENCES gateway.portal_chat_sessions(id) ON DELETE CASCADE, sequence integer NOT NULL, role varchar(16) NOT NULL CHECK (role IN ('user', 'assistant')), content text NOT NULL, previous_hash varchar(64) NOT NULL, message_hash varchar(64) NOT NULL, created_at timestamptz NOT NULL DEFAULT clock_timestamp(), UNIQUE (session_id, sequence) ); CREATE INDEX IF NOT EXISTS portal_chat_messages_session_idx ON gateway.portal_chat_messages (session_id, sequence);