Files
bid-doctor/server/migrations/sql/0001_baseline/09_audit.sql
YANG JIANKUAN ad7fba6021 feat(server): 工程骨架后端——上传 → 解析 → 清单最小链路
- core:配置(prod 拒绝默认密钥 / dev 短信 / 假模型)、租户事务 + 行级安全策略、
  problem+json 错误码、信封加密与签名下载、对象存储、事务内入队、Valkey stream 事件流、权限点
- identity:手机号 HMAC 存储、注册、登录(挤下线需确认,API3)、服务端会话
- billing:额度流水只追加,预扣 / 结算 / 退回,一笔预扣只有一个结局
- document:分片上传、按文件头识别格式、Gotenberg 转换、文字层与稳定句子编号
- checklist:标注规则识别、模型抽取、三道校验、标注类条款规则兜底、重跑对齐
- ai:提示词注册表(extract.substantive v1 来自 S4 v2)、假模型、OpenAI 兼容供应商、ai_call 流水
- 迁移:data-model 基线、套餐、Procrastinate、会话状态函数
- 测试 30 个:租户隔离、额度、失败退回、对象加密、契约一致性
- Compose(含 migrate)、镜像、CI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:48:10 +08:00

43 lines
2.2 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 9. audit:审计事件(架构 §3.10,FR-M6-07)
-- 来源:docs/architecture/schema-draft.sql(2026-11-03 验证过的草案)
CREATE TABLE audit_event (
id uuid PRIMARY KEY,
tenant_id uuid NOT NULL REFERENCES tenant (id),
actor_kind text NOT NULL CHECK (actor_kind IN ('user', 'ops', 'system')),
actor_user_id uuid REFERENCES user_account (id),
actor_ops_id uuid,
actor_name text, -- 姓名快照:人离职后留痕上仍显示当时的人
action text NOT NULL, -- 如 checklist_item.update、finding.resolve
object_type text NOT NULL,
object_id uuid,
project_id uuid, -- 不加外键:删除项目后审计保留
before jsonb,
after jsonb,
content_digest bytea, -- before / after 的哈希;抹掉内容后哈希链仍可校验
content_scrubbed_at timestamptz, -- 删除项目时抹掉业务内容(G10)
request_id text,
ip inet,
prev_hash bytea, -- 可选的哈希链
row_hash bytea,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ON audit_event (tenant_id, project_id, created_at);
CREATE INDEX ON audit_event (tenant_id, object_type, object_id);
-- 审计只追加;唯一允许的修改是「抹掉业务内容」
CREATE FUNCTION audit_scrub_only() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
RAISE EXCEPTION 'audit_event is append-only';
END IF;
IF NEW.before IS NOT NULL OR NEW.after IS NOT NULL OR NEW.content_scrubbed_at IS NULL
OR (to_jsonb(NEW) - ARRAY['before', 'after', 'content_scrubbed_at', 'actor_name'])
IS DISTINCT FROM (to_jsonb(OLD) - ARRAY['before', 'after', 'content_scrubbed_at', 'actor_name']) THEN
RAISE EXCEPTION 'audit_event only allows scrubbing content';
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER audit_event_guard BEFORE UPDATE OR DELETE ON audit_event
FOR EACH ROW EXECUTE FUNCTION audit_scrub_only();