卓卓安Claw项目方案库
展示首页/方案库/架构与实现
架构与实现

卓安Claw 数据存储与处理架构:先存后处理 v1.0

约 30 分钟·卓安Claw · 2026.11
本页目录
  1. 0. 结论
  2. 1. 先把问题拆开:四类数据,四种顺序
  3. 2. 为什么不能“先处理后存”
  4. 2.1 法律场景的原件价值
  5. 2.2 处理本身是不确定的、会变的
  6. 2.3 工程上的失败模式
  7. 2.4 合规与数据主权
  8. 3. 目标架构:原始层 + 派生层
  9. 3.1 五层数据
  10. 3.2 原始层的“不可变”具体指什么
  11. 4. 一次上传的完整时序
  12. 4.1 同步段(请求内,目标 < 1 秒,不含大文件传输)
  13. 4.2 异步段(worker)
  14. 4.3 状态机
  15. 5. 数据模型(V1 建议)
  16. 5.1 为什么用 version 而不是覆盖
  17. 5.2 幂等键
  18. 6. 处理流水线的工程要求
  19. 6.1 同步与异步的边界
  20. 6.2 失败处理
  21. 7. GitHub 调研:别人怎么做的
  22. 7.1 直接证据:文档管理系统“原件永不覆盖”
  23. 7.2 媒体系统:原图与派生图分离
  24. 7.3 文档 AI 工具:它们是“转换器”,不是“档案库”

日期:2026-11-11
状态:架构对齐稿(待评审)
范围:卓安律所(单所/分所)的案卷、业务记录、Agent 产物、知识沉淀;以及庭立方平台的数据边界
关联:ADR-003-单壳一体化重构.md、卓安Claw-AI原生办案系统-设计需求-v1.0.md、曾鸣智能-卓安对齐-v1.0.md

0. 结论

先存储,后处理。原件是唯一事实源,处理结果是可重建的派生层。

更准确地说,不是“先处理再存”或“先存再处理”二选一,而是:

  1. 同步落原件:上传/扫描/企微/邮件进来的文件,先做准入检查(类型、大小、病毒),边读边算 SHA-256,写入不可变原始层,并落一条最小清单(manifest)。
  2. 异步做处理:OCR、版面解析、正文抽取、分页、缩略图、实体/时间线抽取、切块、向量化、索引,全部进任务队列,失败可重试,升级可重跑。
  3. 派生可重建:文本、Markdown、页码映射、摘要、向量、索引都可以删掉重建;原始件永远不覆盖。
  4. 业务事实先落,指标后算:线索、案件、节点、财务这些业务记录,先写事实/事件,再异步汇总成经营指标;不要让报表反过来阻塞业务。
  5. AI 产物先以“提议/草稿”落,律师确认后才写业务表:Agent 不能直接改事实。

一句话给非技术同事:先把原件锁进保险柜,再让机器去读;机器读错了可以重读,保险柜里的东西不能动。

1. 先把问题拆开:四类数据,四种顺序

“数据怎么存”容易混在一起。卓安Claw 至少有四类数据,处理顺序不同:

数据类型 例子 正确顺序 事实源 能否重建
案卷文件 证据、笔录、起诉书、判决书、会见记录、录音录像 先存原件,后解析 原始文件 + 哈希 + 清单 原件不可重建;解析可重建
业务记录 线索、客户、案件、12 节点、清单、财务、渠道成本 先记事实/事件,后算指标 业务表 + 事件日志 指标可重算;事实不可凭空重建
Agent 产物 摘要、检索初稿、文书草稿、风险提示、建议 先落草稿,律师确认后写回 草稿区 + 确认记录 可重生成;已确认版本要留痕
知识与记忆 结案复盘、办案经验、所内知识、个人偏好 先结构化复盘/审核,再入知识库 审核通过的知识条目 可从复盘重建;原始案卷不直接入知识库

所以,本文的“先存后处理”主要针对案卷文件;对业务数据,等价原则是“事实先落,派生后算”;对 AI 产物,是“草稿先落,确认后生效”。

2. 为什么不能“先处理后存”

2.1 法律场景的原件价值

刑事案卷不是普通文档。原件承载的不只是文字,还有:

  • 文件格式、排版、页码、印章、签名、手写批注、表格结构;
  • 文件的创建/修改时间、来源、上传人、传递链路;
  • 证据的完整性(哈希)与保管链(谁在什么时候看过、导出过、发给谁);
  • 庭审/质证时对“原件与复制件一致”的说明能力。

如果先 OCR/转 Markdown,只存处理结果,等于把“证据”降级成“文本”。一旦 OCR 错一个字、漏一页、表格串行,后面所有检索、摘要、Agent 引用都建立在错误底稿上,而且无法证明错在哪。

2.2 处理本身是不确定的、会变的

  • OCR 引擎会升级,识别率会变;
  • PDF 解析器对扫描件、双栏、表格、手写件的效果不同;
  • 大模型摘要/抽取有随机性,提示词和模型版本会变;
  • 同一份文件,今天解析失败,明天换个引擎可能成功。

如果“处理完才存”,每次升级都等于把历史数据重新赌一遍;如果“只存处理结果”,旧版本的错误永远无法追溯。正确做法是:原件只进一次,派生结果带版本,随时可重跑、可对比、可回滚。

2.3 工程上的失败模式

  • 上传 1GB 案卷,同步 OCR 要几分钟,请求超时,用户以为没存上,重复上传;
  • 处理到一半进程崩溃,原件和数据库都不完整;
  • 外部模型调用失败/限流,整个上传失败;
  • 同一文件重复上传,存了多份,检索结果重复;
  • 处理结果写进业务表后,发现解析错了,无法回退。

“先存原件 + 异步处理”把这些问题拆开:存储要可靠,处理要可重试。两者失败互不拖累。

2.4 合规与数据主权

  • C 类案卷要求本地/私有云保存,原件不出所;
  • 对外部模型只能送必要的片段,且要脱敏、留痕;
  • 如果先处理再存,处理过程本身就可能把全文送出去,违背“案卷不出所”;
  • 原件先落本地,后续处理才能按级别选择“本地 OCR / 本地模型 / 脱敏后外部模型”。

3. 目标架构:原始层 + 派生层

flowchart LR
  U[上传/扫描/企微/邮件] --> G[准入检查<br/>类型/大小/病毒]
  G -->|通过| S[原始层<br/>SHA-256 内容寻址<br/>不可变]
  G -->|拒绝| Q[隔离区<br/>保留审计]
  S --> M[清单 manifest<br/>+ 事务 outbox]
  M --> J[处理队列]
  J --> P[解析/OCR/版面/抽取]
  P --> A[派生层<br/>文本/页码/实体/摘要]
  A --> I[索引层<br/>全文/元数据/向量]
  I --> AG[Agent/检索/质控/经营]
  A -.流水线升级.-> P
  S -.永不修改.-> S

3.1 五层数据

层 内容 是否事实源 生命周期 存储建议
L0 原始层 原始文件、哈希、大小、MIME、来源、上传人、保管链 是 按案件保留期/法律留痕,不可变 本地磁盘/NAS/私有对象存储;内容寻址
L1 解析层 逐页文本、Markdown、版面块、表格、OCR 置信度、页码映射 否 可重建,随流水线版本 文件 + 数据库索引
L2 索引层 全文索引、元数据索引、向量索引 否 可重建 SQLite FTS5 / PostgreSQL / pgvector / Qdrant
L3 语义层 当事人、机关、日期、金额、罪名、证据清单、时间线、争点、摘要 否 可重建,需标注模型/提示词版本 JSON/表
L4 业务/展示层 案件、节点、清单、记录、财务、经营指标、Agent 草稿 业务事实是;指标否 业务事实长期;指标可重算 SQLite/PostgreSQL

原则:L1–L3 都可以删了重建;L0 不能。L4 的业务事实不能靠 AI 重写,AI 只能提议。

3.2 原始层的“不可变”具体指什么

  • 文件按内容寻址:sha256 作为 blob ID;同一文件重复上传只存一份,多个案件/文书版本引用同一个 blob;
  • 原文件写入后不原地修改;需要修订时新增版本,旧版本保留;
  • 数据库只存相对路径,不存绝对路径;换机器/换盘可迁移;
  • 每个 blob 有 size、mime、sha256、created_at、encryption、legal_hold;
  • 每条案件文书版本有 document_id、blob_id、version、original_name、source、uploaded_by;
  • 删除是“墓碑 + 审批 + 保留期”,不是直接 rm;派生层先删,原始层最后删;
  • 备份只承诺“原件 + 元数据”可恢复;派生层丢了就重跑。

4. 一次上传的完整时序

4.1 同步段(请求内,目标 < 1 秒,不含大文件传输)

  1. 客户端上传到临时文件(流式,不一次性读进内存);
  2. 准入检查:扩展名/MIME/大小上限/病毒扫描(可异步但未通过前不进案件库);
  3. 边写边算 SHA-256;
  4. 原子移动到内容寻址路径(先写临时文件,再 rename,避免半截文件);
  5. 同一事务写: - zhuoan_doc_blob(没有则插入); - zhuoan_doc_version(案件文书版本); - zhuoan_ingest_job / outbox(待处理事件);
  6. 返回 202 Accepted + document_id + job_id + status=stored。

关键:原件落盘和“待处理事件”要在同一个逻辑事务里完成(本地 SQLite 可用同库事务;跨文件系统用 outbox + 对账)。 否则会出现“文件存了、任务没入队”或“任务入队了、文件没存”的双写不一致。

4.2 异步段(worker)

  1. worker 取任务(queued → running,带租约/超时);
  2. 识别文件类型:PDF 文本型/扫描型、Word、图片、音频、压缩包;
  3. 解析/OCR: - 文本型 PDF:直接抽文本 + 版面; - 扫描型 PDF/图片:OCR,保留逐页置信度; - Office:转 PDF/HTML/Markdown(按引擎能力); - 音频/视频:转写 + 分段时间戳;
  4. 写派生结果(zhuoan_doc_artifact),带 blob_id + artifact_type + pipeline_version 唯一键;
  5. 建全文索引;需要时切块、向量化;
  6. 更新版本状态 ready;失败则 failed + 错误摘要,按退避重试;超过次数进 dead,人工处理;
  7. 全过程追加 zhuoan_doc_event(uploaded/parsed/indexed/failed/viewed/exported/model_sent)。

4.3 状态机

received → stored → queued → running → parsed → indexed → ready
                         ↘ failed ↗(重试)
                         ↘ dead(超限,人工)
                         ↘ quarantined(病毒/损坏/哈希不符)

对用户展示的状态可以简化成:

  • 原件已存(可下载、可取证、可审计)
  • 解析中(OCR/抽取)
  • 可检索(全文/语义)
  • 需人工处理(失败/加密/损坏)

Agent 的读取门槛:

  • stored:只能读元数据,不能把内容当事实;
  • parsed:可读文本,但标注“未完成索引/可能有识别误差”;
  • ready:可检索、可引用,引用必须带 blob_id + page;
  • failed/dead:Agent 不得引用,提示人工处理。

5. 数据模型(V1 建议)

不直接改上游 document 表的核心字段,避免上游合并冲突;用 zhuoan_* 侧表扩展。

-- 原始件:内容寻址,不可变
CREATE TABLE IF NOT EXISTS zhuoan_doc_blob (
    id              TEXT PRIMARY KEY,
    sha256          TEXT NOT NULL UNIQUE,
    size_bytes      INTEGER NOT NULL,
    mime_type       TEXT,
    storage_path    TEXT NOT NULL,          -- 相对路径
    encryption      TEXT DEFAULT 'none',    -- none/bitlocker/envelope
    legal_hold      INTEGER DEFAULT 0,
    created_at      TEXT NOT NULL
);

-- 案件文书版本:关联上游 document
CREATE TABLE IF NOT EXISTS zhuoan_doc_version (
    id              TEXT PRIMARY KEY,
    document_id     TEXT NOT NULL,          -- 上游 document.id
    blob_id         TEXT NOT NULL,
    version         INTEGER NOT NULL,
    original_name   TEXT,
    source          TEXT,                   -- upload/scan/wecom/email/agent
    uploaded_by     TEXT,
    uploaded_at     TEXT NOT NULL,
    status          TEXT NOT NULL,          -- stored/queued/processing/ready/failed/quarantined
    UNIQUE(document_id, version)
);

-- 处理任务:或作为 outbox
CREATE TABLE IF NOT EXISTS zhuoan_ingest_job (
    id              TEXT PRIMARY KEY,
    blob_id         TEXT NOT NULL,
    pipeline_version TEXT NOT NULL,
    stage           TEXT NOT NULL,          -- parse/ocr/thumbnail/extract/embed/index
    status          TEXT NOT NULL,          -- queued/running/succeeded/failed/dead
    priority        INTEGER DEFAULT 100,
    attempts        INTEGER DEFAULT 0,
    max_attempts    INTEGER DEFAULT 5,
    last_error      TEXT,
    idempotency_key TEXT NOT NULL UNIQUE,
    created_at      TEXT NOT NULL,
    started_at      TEXT,
    finished_at     TEXT
);

-- 派生结果:同一 blob/类型/流水线版本唯一
CREATE TABLE IF NOT EXISTS zhuoan_doc_artifact (
    id              TEXT PRIMARY KEY,
    blob_id         TEXT NOT NULL,
    artifact_type   TEXT NOT NULL,          -- text/markdown/pages/thumbnail/entities/summary
    pipeline_version TEXT NOT NULL,
    engine          TEXT,                   -- docling/mineru/paddleocr/tika...
    model           TEXT,
    prompt_hash     TEXT,
    content_path    TEXT,
    content_json    TEXT,
    page_map        TEXT,                   -- JSON: page -> char range / bbox
    confidence      REAL,
    created_at      TEXT NOT NULL,
    UNIQUE(blob_id, artifact_type, pipeline_version)
);

-- 只追加事件:审计与保管链
CREATE TABLE IF NOT EXISTS zhuoan_doc_event (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    blob_id         TEXT,
    document_id     TEXT,
    event_type      TEXT NOT NULL,          -- uploaded/viewed/parsed/indexed/exported/model_sent/deleted
    actor           TEXT,
    detail          TEXT,
    created_at      TEXT NOT NULL
);

5.1 为什么用 version 而不是覆盖

  • 上游 document 已有 version / replaced_by,可以复用“第几版”的语义;
  • 但文件内容用 blob_id 指向不可变 blob,document.version 只是业务版本号;
  • 同一文书改稿:新增 zhuoan_doc_version 行 + 新 blob;旧行保留;
  • 同一文件重传:SHA-256 命中已有 blob,只新增引用,不重复占空间。

5.2 幂等键

建议:

idempotency_key = sha256(blob.sha256 + ':' + stage + ':' + pipeline_version)
  • 同一文件、同一阶段、同一流水线版本,只允许一个成功产物;
  • 重跑时先查已有 artifact:成功则直接复用;失败则新建任务;
  • 流水线升级 = pipeline_version 变化 = 新产物,不覆盖旧产物。

6. 处理流水线的工程要求

要求 做法
可重试 任务租约 + 超时 + 指数退避;失败不丢原件
可恢复 worker 崩溃后任务回到 queued;用 started_at/租约判断僵尸任务
可重跑 管理端“重新解析”按钮;按 pipeline_version 新生成 artifact
可对比 新旧 artifact 并存;可看差异/置信度;不自动替换已确认内容
可限流 按案件/用户/引擎限并发;大文件低优先级;夜间批量 OCR
可观测 队列深度、成功率、平均耗时、失败原因、单文件成本
可隔离 病毒/损坏文件进隔离区;不进入案件库,不送模型
可审计 谁上传、谁查看、谁导出、谁送过外部模型、用了哪个模型/提示词

6.1 同步与异步的边界

动作 同步/异步 说明
哈希、大小、MIME 检查 同步 很快,决定是否接收
病毒扫描 同步门槛或快速异步 未通过不进案件库;隔离区保留
写原件 + manifest 同步 必须完成才返回成功
首页预览/缩略图 可同步(小文件) 只作预览,不当作已解析
全文 OCR/版面 异步 大文件、扫描件可能几分钟
实体/时间线抽取 异步 依赖全文,LLM 可能慢/不稳定
切块/向量化 异步 可批量、可重建
全文/语义索引 异步 ready 后才对 Agent 开放
结案复盘/知识蒸馏 异步 + 人工审核 不能自动把案卷变知识

6.2 失败处理

  • 哈希不符/文件损坏:quarantined,通知上传人,保留审计;
  • 加密 PDF/不支持格式:failed,提示人工解密/转换,不反复重试;
  • OCR 质量低:仍可 ready,但标 confidence,Agent 引用时提示“识别质量低”;
  • 外部模型失败:不影响原件和本地解析;重试或降级本地模型;
  • 磁盘满:拒绝新上传,告警;不能写半截文件;
  • 重复上传:哈希命中,复用 blob,返回已有文档提示。

7. GitHub 调研:别人怎么做的

调研方式:2026-11-11 通过 GitHub REST API 检索 + 读取公开 README/文档;星数为当时快照。只做架构参考,未做代码审计,许可证结论不构成法律意见。

7.1 直接证据:文档管理系统“原件永不覆盖”

paperless-ngx/paperless-ngx(约 48k stars,GPL-3.0)的文档明确写了:

Paperless will always store the original document ... and will never overwrite that document ... Archived versions are stored alongside the original versions.

它的做法:

  • 原件保存在 originals/,OCR 后的可搜索版本保存在 archive/;
  • OCR 用 OCRmyPDF,处理是可重跑的;
  • 文件有 checksum、archive checksum、原始文件名、归档序号;
  • 删除进回收站,原件不是立即物理删除。

这几乎就是卓安Claw 需要的“原件层 + 派生层”原型。注意:GPL-3.0,不能直接把代码嵌入闭源产品;可以借鉴设计,或作为独立服务评估。

7.2 媒体系统:原图与派生图分离

immich-app/immich(约 119k stars,AGPL-3.0)把原始照片/视频与缩略图、转码视频分开存;派生文件可以重新生成,原图不动。这个模式对案卷里的图片、扫描件、录音录像同样适用:原图/原音是证据,缩略图/转写只是浏览辅助。

7.3 文档 AI 工具:它们是“转换器”,不是“档案库”

项目 Stars 许可证 定位 对卓安的用法
docling-project/docling 约 70k MIT 文档转结构化,面向 GenAI 本地解析 PDF/Office,产出 Markdown/JSON
Unstructured-IO/unstructured 约 16k Apache-2.0 文档 ETL,分区/清洗/分块 解析流水线的一环
opendatalab/MinerU 约 84k Apache-2.0 + 附加条款 PDF/Office 转 Markdown/JSON,扫描件强 本地重解析;商用前看附加条款阈值
apache/tika 约 4k Apache-2.0 检测/抽取 1000+ 文件类型 文件类型识别、元数据抽取

共同点:输入是一个已存在的文件,输出是派生结果;它们都不负责“唯一事实源”。 所以正确用法是“先存原件,再调用它们;把输出按版本缓存”,而不是“先调用它们,只存输出”。

7.4 数据湖/表格式:不可变文件 + 快照 + 时间旅行

  • apache/iceberg(约 9.4k,Apache-2.0):数据文件不可变,通过快照管理表版本,支持时间旅行;
  • delta-io/delta(约 9.1k,Apache-2.0):ACID、时间旅行、可重算。

可借鉴的不是“上大数据平台”,而是三条原则:

  1. 原始数据只追加,不原地改;
  2. 派生表/指标可重算;
  3. 每次处理有版本,可回到某个时间点。

卓安不需要 Iceberg/Delta 的复杂度;用“blob + artifact + pipeline_version”就能表达同样语义。

7.5 工作流引擎:长任务要“可恢复、可重试、可幂等”

  • temporalio/temporal(约 24k,MIT):持久化执行、重试、超时、补偿;
  • dagster-io/dagster(约 16k,Apache-2.0):以数据资产为中心,支持重新物化(re-materialization)。

卓安 V1 不必引入 Temporal/Dagster;用数据库任务表 + 单机 worker 就够。但要借鉴它们的任务状态机、租约、幂等键、重试上限、死信。等做私有云/多所平台时,再评估 Temporal。

7.6 向量库:索引不是事实源

  • pgvector/pgvector(约 23.6k,PostgreSQL License):在 Postgres 里做向量检索;
  • qdrant/qdrant(约 35.5k,Apache-2.0):独立向量检索服务。

两者都应该是派生索引:文本和页码映射存在 L1/L2,向量只是加速检索。换 embedding 模型时,重建索引即可;不能把“向量库里的片段”当成唯一案卷副本。

7.7 法务 DMS 集成:DMS 是事实源,AI 通过接口读

protomated/legal-context-ce(约 29 stars,MPL-2.0)是一个 MCP 服务,把律所 Clio 文档管理系统接给 Claude:

  • Clio 仍是文档事实源;
  • AI 通过 API/MCP 查询,不把整个 DMS 复制进模型;
  • 访问受 OAuth/权限控制。

这印证了卓安的方向:案管库/原件库是事实源,Agent 是通过工具访问的“读者”,不是数据的拥有者。

7.8 脱敏:对外部模型之前的派生步骤

data-privacy-stack/presidio(原 microsoft/presidio,约 11.8k,MIT):检测/脱敏姓名、电话、身份证号、地址等 PII。

用法:原件不动;在“送外部模型”前,从 L1 文本生成脱敏副本,记录 model_sent 事件;C 类默认禁止送外部模型。脱敏副本也是派生层,不能替代原件。

7.9 对象存储:S3 兼容 + 版本/对象锁

minio/minio(约 61k,AGPL-3.0):S3 兼容对象存储,支持版本、对象锁(WORM)。适合私有云原始层,但 AGPL-3.0 商用/分发需法务评估;也可以选云厂商 OSS/COS/S3 或本地 NAS + 文件系统。

7.10 调研结论

  1. 成熟系统没有“先处理后存”的:Paperless、Immich、Iceberg/Delta 都是“原始不可变 + 派生可重建”。
  2. 文档 AI 工具是转换器:Docling/Unstructured/MinerU/Tika 都假设输入文件已存在;不应让它们承担存储职责。
  3. 长任务需要工作流语义:Temporal/Dagster 的价值是重试、幂等、可观测;V1 用 DB 队列即可,但状态机不能省。
  4. 索引/向量是缓存:pgvector/Qdrant 不能当事实源。
  5. 许可证要提前看:GPL/AGPL(Paperless、Immich、MinIO)不要直接嵌入闭源产品;Apache/MIT 也不是“零义务”,要保留声明、注意专利条款;MinerU 有附加商用条款。

7.11 许可证速查

许可证 项目 对卓安的含义
GPL-3.0 Paperless-ngx 借鉴设计;嵌入/分发需法务评估,倾向独立服务或重写
AGPL-3.0 Immich、MinIO 网络服务也可能触发义务;商用分发前必须评估
Apache-2.0 Unstructured、Tika、Iceberg、Delta、Dagster、Qdrant 较宽松;保留 NOTICE/许可证,注意专利条款
MIT Docling、Temporal、Presidio 较宽松;保留版权声明
Apache-2.0 + 附加条款 MinerU 有商用阈值/附加条件,使用前读 LICENSE
PostgreSQL License pgvector 宽松,类似 BSD/MIT
MPL-2.0 legal-context-ce 文件级 copyleft;修改文件需开源该文件

8. 存储选型与容量

8.1 分阶段选型

阶段 场景 原始层 元数据/业务 全文索引 向量
V1 单机 单所/演示/试点 本地磁盘/NAS,内容寻址目录 SQLite WAL SQLite FTS5 可选 sqlite-vec,或暂不做
V2 私有云 多律师/多分所 S3 兼容对象存储或 NAS PostgreSQL PostgreSQL FTS pgvector
V3 平台 庭立方多租户 各律所自持原件;平台不存原件 平台元数据仓 平台只索引脱敏知识/指标 平台知识索引

不要做的事:

  • 不要把 1GB 文件塞进 SQLite 的 BLOB;
  • 不要把“解析后的文本”当唯一副本;
  • 不要让向量库成为唯一检索入口;
  • 不要跨租户做全局哈希去重(会泄露“某文件在某所存在”);律所内可以去重,平台层禁止。

8.2 容量估算(按用户口径)

  • 单案约 1GB,单所约 1,000 案/年 → 每年约 1TB 原始件;
  • 7 家分所 → 每年约 7TB;
  • 3 年保留 → 约 21TB,加备份约 42TB(未计压缩/去重);
  • 扫描件/录音录像可能更大;建议冷热分层:在办热存,结案后转冷/近线,保留索引;
  • 去重(同一证据多案引用)、PDF 压缩、视频转码只能优化派生层,不能牺牲原件。

这些是量级估算,不是承诺;实际按试点所的真实案卷测量后再定采购。

8.3 备份与恢复

  • 原始层:每日增量 + 定期全量 + 异地/离线副本;至少 3-2-1;
  • 元数据:SQLite 定期快照/WAL 备份;PostgreSQL 持续归档;
  • 派生层:可不备份,恢复后重跑;但索引重建需要时间,重要案件可保留缓存;
  • 恢复演练:每季度抽一个案件,从原件 + 元数据恢复,验证哈希、页码、审计链;
  • 加密密钥单独备份;否则备份不可读。

9. 安全、合规与数据边界

9.1 数据分级

级别 例子 存储 外部模型 平台
A 公开 宣传内容、公开案例 任意 可用 可用
B 内部 经营指标、脱敏知识、模板 所内/私有云 脱敏后可用 聚合/脱敏后可用
C 案卷秘密 案卷、证据、当事人信息、会见记录 本机/私有云 默认禁止;确需时仅脱敏片段 + 审批 + 留痕 不上传原件/全文

9.2 技术控制

  • 传输 TLS;静态加密(BitLocker/LUKS/对象存储 SSE);
  • 案件级 ACL:客服/客户经理/谈案/承办/管理者按权限;C 类文件默认最小可见;
  • 每次查看/下载/导出/送模型写 zhuoan_doc_event;
  • 外部模型网关:按数据级别拦截;C 类直接拒绝;B 类先脱敏;记录 provider/model/prompt_hash;
  • 导出:水印、审批、有效期;导出件也记哈希;
  • 删除:审批 + 墓碑 + 保留期;派生先删、原件后删;SSD 场景用“加密密钥销毁(crypto-shredding)”而不是承诺物理擦除;
  • 法务保留(legal hold):涉及诉讼/调查的案件,禁止删除,直到解除。

9.3 平台(庭立方)边界

  • 平台拿:租户/律所、用户/角色、案件状态、节点进度、质量指标、渠道/经营聚合、脱敏后的知识条目;
  • 平台不拿:案卷原件、全文、当事人敏感信息、会见记录、证据细节;
  • 跨所知识:必须律师审核 + 脱敏 + 授权;保留来源案件 ID(平台侧可用假名);
  • 平台报表:小样本阈值(如少于 N 家律所不展示),防止反推单所经营数据;
  • 平台不能做跨租户内容哈希去重/比对,避免推断“某文件在某所”。

9.4 与“数据主权”口径一致

对客户/律师的说法:

  • “资产存本机 + 模型自选”;
  • “原件不出所,平台只拿流程和指标”;
  • “AI 处理在本地优先;确需外部模型时,只送必要片段并留痕”;
  • 不说“完全本地/无需联网/案卷绝对不出终端”(在线模型、激活/更新仍需联网)。

10. 与当前卓安Claw 的差距

10.1 现状

  • document 表有 file_path、file_size、version、mime_type、replaced_by,但没有 SHA-256、没有处理状态、没有派生结果表;
  • db.ocr_file() 是同步、按需调用 OCR,不缓存结果,不记录流水线版本;
  • Agent 的 zhuoan_case 工具主要拿案件/节点/清单/文档数量,没有把案卷全文/页码引用接入;
  • 知识库/记忆是另一套,不能和案卷原件混为一谈;
  • 目前没有任务队列/worker,也没有“解析中/可检索”的状态展示。

10.2 V1 最小落地(建议)

  1. 建侧表:zhuoan_doc_blob、zhuoan_doc_version、zhuoan_ingest_job、zhuoan_doc_artifact、zhuoan_doc_event;
  2. 上传改造:上传接口先算哈希、写原件、写 manifest + job,返回 202;原 document.file_path 保留兼容;
  3. 单机 worker:一个后台线程/进程轮询 job;先支持 PDF 文本抽取 + 扫描件 OCR + 缩略图;
  4. FTS5 索引:解析完成后写全文索引;Agent 工具只读 ready 的文本,引用带页码;
  5. 管理端:显示“原件已存/解析中/可检索/失败”,提供“重新解析”;
  6. 回填:对现有 document 逐条算哈希、建 blob/version、入队;不移动原文件,先建立映射,验证后再迁移;
  7. 审计:上传/查看/导出/送模型四类事件先做;C 类外部模型拦截先做规则。

10.3 暂不做

  • 不做完整数据湖/Iceberg/Delta;
  • 不做多租户对象存储;
  • 不做跨所知识共享;
  • 不做自动把案卷全文向量化后送外部 embedding(C 类默认本地);
  • 不在 11/27 前重构整个案管文件模型;用侧表渐进。

11. 验收标准

场景 预期
上传 1GB PDF 快速返回“原件已存”,后台解析;页面不阻塞
解析中杀掉 worker 原件完整;任务回到队列;重试后成功;不产生重复 blob
同一文件重复上传 哈希命中,只存一份;两条案件引用;提示已存在
升级 OCR/解析版本 生成新 artifact;旧 artifact 仍可读;可对比;不自动覆盖已确认内容
删除索引/向量库 可从原件 + artifact 重建;业务不丢
哈希不符/文件损坏 进隔离区;告警;不进入案件库;不送模型
C 类文件送外部模型 被拦截;审计记录尝试;界面提示原因
Agent 引用 能定位到 blob_id + page;标注解析版本/置信度
备份恢复 原件 + 元数据一致;派生重建;审计链连续
删除案件 审批 + 墓碑 + 保留期;派生先删、原件后删;有审计

12. 什么情况下可以“先处理再存”

不是绝对禁止,但必须限定为不承担事实源职责的临时处理:

  1. 上传前预览:用户选文件后,本地/临时 OCR 首页,确认“是不是这份”;临时文件 TTL 删除,不进入案件库;
  2. 准入检查:病毒扫描、格式探测、损坏检测;不通过进隔离区,保留审计;
  3. 客户端脱敏:在律师本机先脱敏,再上传脱敏副本;原件仍在本地保存;
  4. 即时聊天附件:非案卷的临时附件,可处理后在内存/临时目录使用,明确 TTL,不索引、不训练;
  5. 离线导入预处理:批量导入前先做清单/去重/格式转换,但转换前的原件必须保留在导入包/隔离区,确认后再入库。

红线:

  • 不能只存处理结果、不存原件;
  • 不能把 C 类全文先送外部模型再存;
  • 不能把临时预览结果当“已解析”给 Agent 引用;
  • 不能因为处理失败而丢原件。

13. 11/27 演示口径

可以说:

  • “原件先存、处理可重跑;机器读错了可以重读,原件不动。”
  • “案卷留在所里/本机,平台只拿流程和经营指标。”
  • “Agent 引用能回到原件页码,不是凭空生成。”
  • “同一份证据多个案件引用,只存一份,但权限和审计分开。”

不要承诺:

  • “全自动解析所有格式”;
  • “OCR 100% 准确”;
  • “案卷全文上平台”;
  • “已经建成数据湖/多租户平台”;
  • “AI 可以独立判断证据真伪”。

演示建议(如果时间允许):

  1. 上传一份 PDF → 状态“原件已存/解析中”;
  2. 切到案件 → 显示“可检索”,点开引用 → 定位到页码;
  3. 点“重新解析” → 显示新版本,旧版本仍可查;
  4. 展示审计:谁上传、谁查看、是否送过外部模型。

14. 待决策

  1. V1 原始层放哪:单机本地目录,还是直接上 NAS/对象存储?建议试点期本地目录 + 可配置根路径,私有云再切对象存储。
  2. OCR/解析引擎组合:Docling(MIT)+ Tika(Apache-2.0)+ PaddleOCR/系统 OCR;MinerU 作为扫描件增强,商用前审附加条款。
  3. 向量化是否默认开启:C 类默认本地 embedding;外部 embedding 需审批。V1 可先只做 FTS5,向量后置。
  4. 保留期:按案件类型/结案时间定;需要与卓安档案管理制度对齐,本文不替律所定年限。
  5. 平台指标口径:与经营分析 V2 的 ID/口径统一后再上平台,避免两套数字。
  6. 是否引入 Temporal:V1 不引入;当出现多机 worker、跨服务补偿、长流程编排时再评估。

15. 参考

  • Paperless-ngx:https://github.com/paperless-ngx/paperless-ngx
  • 文档:docs/usage.md(原件永不覆盖、archive 并存)
  • Immich:https://github.com/immich-app/immich
  • Docling:https://github.com/docling-project/docling
  • Unstructured:https://github.com/Unstructured-IO/unstructured
  • MinerU:https://github.com/opendatalab/MinerU
  • Apache Tika:https://github.com/apache/tika
  • Apache Iceberg:https://github.com/apache/iceberg
  • Delta Lake:https://github.com/delta-io/delta
  • Temporal:https://github.com/temporalio/temporal
  • Dagster:https://github.com/dagster-io/dagster
  • pgvector:https://github.com/pgvector/pgvector
  • Qdrant:https://github.com/qdrant/qdrant
  • MinIO:https://github.com/minio/minio
  • Presidio:https://github.com/data-privacy-stack/presidio
  • LegalContext:https://github.com/protomated/legal-context-ce

说明:以上星数/许可证为 2026-11-11 GitHub API 快照;许可证结论不构成法律意见,商用前由法务复核。