Unify auth middleware and remove X-User-ID trust, add project_members authorization, sandbox workspaces via resolver, and admin-only reconcile by run_id. Introduce goose migrations with startup schema gate, fail closed when DB unavailable outside explicit ALLOW_IN_MEMORY, bundle web/dist in app Dockerfile, and remove frontend pseudo-login fallback.
55 lines
2.2 KiB
SQL
55 lines
2.2 KiB
SQL
-- +goose Up
|
|
-- =============================================================================
|
|
-- Migration: 2026-09-11-message-export-policy.sql
|
|
-- Description: 为 chat_messages 新增正文类型、权威导出策略、版本与 SHA256 哈希
|
|
-- =============================================================================
|
|
|
|
-- 1. 结构变更:新增消息导出权威列
|
|
ALTER TABLE chat_messages
|
|
ADD COLUMN IF NOT EXISTS content_kind TEXT NOT NULL DEFAULT '',
|
|
ADD COLUMN IF NOT EXISTS export_policy TEXT NOT NULL DEFAULT 'none',
|
|
ADD COLUMN IF NOT EXISTS content_revision INT NOT NULL DEFAULT 1,
|
|
ADD COLUMN IF NOT EXISTS content_sha256 TEXT NOT NULL DEFAULT '';
|
|
|
|
-- 2. 导出策略枚举约束检查 (full | copy_only | none)
|
|
-- +goose StatementBegin
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'chk_chat_export_policy'
|
|
) THEN
|
|
ALTER TABLE chat_messages
|
|
ADD CONSTRAINT chk_chat_export_policy
|
|
CHECK (export_policy IN ('full', 'copy_only', 'none'));
|
|
END IF;
|
|
END $$;
|
|
-- +goose StatementEnd
|
|
|
|
-- 3. 索引优化:快速基于消息与版本校验
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_export_policy
|
|
ON chat_messages(id, export_policy);
|
|
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
-- 4. 存量消息安全保守回填:旧消息默认为 none,仅纯文本 assistant 消息标记为 copy_only
|
|
UPDATE chat_messages
|
|
SET
|
|
content_kind = CASE
|
|
WHEN role = 'user' THEN 'conversational_text'
|
|
WHEN role = 'system' THEN 'system_notice'
|
|
WHEN role = 'assistant' AND (metadata ? 'interactive_card') THEN 'action_card'
|
|
WHEN role = 'assistant' AND length(trim(content)) > 0 THEN 'document_text'
|
|
ELSE 'conversational_text'
|
|
END,
|
|
export_policy = CASE
|
|
WHEN role = 'assistant' AND NOT (metadata ? 'interactive_card') AND length(trim(content)) >= 10 THEN 'full'
|
|
WHEN role = 'assistant' AND length(trim(content)) > 0 THEN 'copy_only'
|
|
ELSE 'none'
|
|
END,
|
|
content_revision = 1,
|
|
content_sha256 = CASE
|
|
WHEN length(content) > 0 THEN encode(sha256(convert_to(content, 'UTF8')), 'hex')
|
|
ELSE ''
|
|
END
|
|
WHERE export_policy = 'none' AND role = 'assistant' AND length(trim(content)) > 0;
|