Files
knowledge-governance/docs/library-capture.md
T
姚章浩 910c0fcd76
build / build (push) Failing after 16s
feat(library): 页面发起封存的最小权限主机侧执行边界
- libraryagent:0700 目录内 0600 unix socket + SO_PEERCRED 同 uid,
  固定 binary/归档/日志/项目/写入者声明,请求只能选范围与作业号
- API:POST/GET /api/v1/library/captures(入队 library.capture + 读队列状态)
- worker:library.capture handler 走 socket;启动先 recover;不挂 Docker socket、
  不持有来源凭据
- web:整库版本页显示真实作业状态并可发起封存(未接线/无权限给出原因)
- scripts/library-agent-install.sh:构建、安装 systemd user 服务并读回权限
2026-10-28 12:11:08 +08:00

6.4 KiB
Raw Blame History

整库封存(libraryctl)运维手册

本文只讲测试库的整库封存链:怎么跑、它证明了什么、它没有证明什么。生产发布与恢复、清理不在本文范围。

它做什么

libraryctl capture -scope internal|external:

  1. 从本机真实测试栈(默认 Compose 项目 weknora071full)读来源配置:容器环境里的数据库/检索/对象存储参数,运行时读取,不落盘、不回显。
  2. 用只读会话(default_transaction_read_only=on)连来源 PostgreSQL;连接串只在进程内存里。
  3. 核对来源已结清:所有文档 parse_status=completed、无待处理操作、无死信(任一不满足就拒绝,不看其他 KB 的局部状态)。
  4. 从持久数据推出兼容契约:迁移版本、dirty、租户、Embedding 模型与维度、检索引擎、索引名、索引映射摘要(text-embedding-v4 / 1024 维 / schema 74)。
  5. 在维护窗口内停住声明过的写入者(只对声明为 writer 的服务发 SIGTERM,等它真的停下),导出 PostgreSQL 十表 + Elasticsearch 正文与向量 + 对象资源,做双向覆盖与摘要核验,封存为内容寻址产物。
  6. 无论成败都恢复写入者并等到 healthy;崩溃留下的日志用 libraryctl recover 收尾。
  7. 打印快照 ID、分项(kind/name/数量/字节/SHA256)与兼容契约。

写入者声明(安全边界)

scripts/library-capture.sh 里逐项声明了项目内每一个服务:一个 writer(app),其余每个都写了"为什么它不会写知识"。ProjectController 的规则:

  • 整个 Compose 项目被枚举(含停止的),只按声明停机;
  • 项目里出现未声明的服务 → 拒绝执行,不会"只停已知的那几个";
  • 首次清点后固定容器 ID,容器被重建(同名新实例)→ 拒绝,避免停到一个没被声明的实例;
  • 只发 SIGTERM,没有会在恢复后才触发的 SIGKILL 定时器;
  • 启动后必须等到 healthy 才算恢复。

命令

# 接线与来源探针(只读,不停服务)
./scripts/library-capture.sh status
# 预检:读来源 + 结清检查 + 候选校验,不写盘、不停服务
./scripts/library-capture.sh preflight -scope internal
# 真实封存(停 app → 导出 → 恢复 app)
./scripts/library-capture.sh capture -scope internal
./scripts/library-capture.sh capture -scope external
# 已封存产物
./scripts/library-capture.sh list
./scripts/library-capture.sh verify -snapshot <id>
# 崩溃后的收尾(恢复写入者)
./scripts/library-capture.sh recover

产物默认写到 ~/.local/share/knowledge-governance/library(0700),日志写到 ~/.local/share/knowledge-governance/freeze(0700)。api 容器只以只读方式挂载前者,「整库版本」页据此展示。

同一个作业号重跑是幂等重放:直接返回已封存的同一份候选(replay=true、source_read=false、writers_stopped=false),不读来源、不停写入者;同一作业号换了 scope 或绑定库则明确拒绝。

从页面发起封存(主机侧 agent)

页面点「发起对内/对外封存」只是投递一个作业;执行发生在这台主机上的 agent:

  • scripts/library-agent-install.sh 构建并安装 libraryagent(systemd user 服务), 监听 ~/.local/share/knowledge-governance/agent/agent.sock,目录 0700、socket 0600, 并额外用 SO_PEERCRED 要求对端与 agent 同 uid。
  • agent 自己固定 libraryctl 的路径、归档与日志根、Compose 项目与写入者声明; 请求只能选 scope(internal/external)和作业号,不能指定路径、项目、容器或命令。
  • 只有 worker 通过挂载的 socket 目录投递作业(LIBRARY_AGENT_SOCKET)。容器里 没有 Docker socket,没有来源凭据:停写入者、读来源、恢复都在主机进程里完成。
  • worker 启动时会先调一次 recover,把上次崩溃留下的停机窗口收尾; 失败会记日志而不是假装就绪。
  • 同一 scope + 作业号 重复投递时,agent 走幂等重放,不会第二次停源。

安全边界要说清:socket 目录对 worker 可写,因此被攻破的 worker 可以让 agent 执行 "封存 internal/external"这一类动作(这正是页面能力),但拿不到 Docker API、 拿不到来源凭据,也不能重定向到别的项目或路径。

2026-10-28 实测(本机测试栈)

项 对内 对外
快照 f992d64ea45bf737a0721a465b5aef2b150ef7757157eb84f3aaf8b6972d98f9 01343b0b310b3977c2fdda05f5b8a6272148c4f7bfa124a6c5944123cf0c51c3
文档 / 分块 / 索引 4 / 38 / 122 0 / 0 / 0
资源 / Wiki 页 21 / 31 0 / 1
分项数 / 逻辑字节 32 / 55,309,044 13 / 1,121
策略 artifact-acceptance-v1 artifact-acceptance-v1

两次封存都记录了 stop_verified=true、restored=true,WKNFull-app 恢复后 healthy;来源未做任何写操作。

它证明了什么 / 没有证明什么

已证明(A04 数据完整性口径):这一份整库内容在写入者停住之后被完整导出,PostgreSQL 与 Elasticsearch 双向覆盖、生成问题、资源与摘要逐项核验通过,产物可寻址、可复读、可校验。这正是封版所需的完整性口径。

用户已确认取消 A06:不要求内容、问答、引用/图片效果与性能测试及其报告,也不作为封版/生产发布门槛。因此这里不再把"没有做效果测试"列为缺口。

仍然不成立(不许把完整性通过说成这些):

  • 跨 PG / ES / 资源的一致切点只在维护窗口内取得;写入者组完整性有检查,但没有独立的切点证据,不能当成已验收的一致性证明;
  • Wiki 删除贡献的语义重算没有做;Wiki 队列空不等于语义正确;
  • 对外库当前是空的(0 文档 1 Wiki 页),这份快照只说明"它当时确实是空的";
  • 生产独立预备份、恢复演练、双闸授权、整库同步协调与失败续作都没有接线,本文不涉及;
  • 从页面发起封存、持久作业互斥、启动恢复还没有接线(封存目前只能在目标主机执行命令);
  • 上游父子分块漏正文索引的风险仍在:本次通过是因为库里现有数据已补齐,以后再重解析仍可能漏项,届时封存会因覆盖核验失败而阻断(这是设计如此)。