Files
knowledge-governance/docs/P6_RELEASE_WORKER_ACCEPTANCE.md
T
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00

13 KiB
Raw Blame History

P6 生产发布 Worker 与回滚验收

结论

P6 的发布控制面、v0.7.1 三通道 Sidecar、Release Worker、归档、补偿和回滚代码已经完成,并通过本地真实依赖 E2E。

生产发布仍未授权、未配置、未启动、未执行:

PRODUCTION_RELEASE_REQUESTS_ENABLED=false
RELEASE_WORKER_ENABLED=false
persistent release_executions=0
persistent release_item_executions=0
production release jobs=0

因此本文的“通过”表示实现和隔离验收通过,不表示生产已发布。

范围

P5 sealed batch
→ 受保护的生产授权请求
→ ReleaseExecution + release.publish Job 原子入账
→ Release Worker 按 KB 路由
→ 从测试 WeKnora 导出经过验收的同一 Knowledge
→ PostgreSQL / Elasticsearch / 对象存储三通道发布
→ 生产 WeKnora 官方 API 验收
→ 成功记录 production revision

回滚:

已完成的 publish execution
→ 受保护的 rollback 授权
→ release.rollback Job
→ 载入旧 0600 release bundle
→ 三通道恢复
→ 官方 API 验收

首次发布没有旧 revision 时,失败补偿和回滚采用禁用而不是删除:

PostgreSQL Knowledge enable_status=disabled
PostgreSQL Chunks is_enabled=false
Elasticsearch 文档保留但 is_enabled=false
对象保留
官方 knowledge-scoped Search 必须零命中

人工授权与双门禁

发布请求门禁:

PRODUCTION_RELEASE_REQUESTS_ENABLED=false

执行门禁:

RELEASE_WORKER_ENABLED=false

两个门禁默认都关闭且职责不同:

  • 请求门禁关闭时,授权 HTTP API 返回 403,Store 不会被调用。
  • 执行门禁关闭时,Release Worker 不加载 release config、不读取生产 secret、不连接生产数据面。
  • P5 的 sealed 仍不创建 Job。
  • 只有受保护的生产授权请求才会原子创建 ReleaseExecution 和 Job。

授权 HTTP API 不接受调用方提交的审核人身份。审核人来自服务端固定配置:

PRODUCTION_RELEASE_AUTHORIZER_REF

门禁开启时还必须提供普通、非软链接、权限 0600 的 Bearer token 文件:

PRODUCTION_RELEASE_AUTH_TOKEN_FILE

缺少固定 actor 或 token 文件时 API 拒绝启动。UI 不录入、不读取、不显示 token,并且没有“一键生产发布”按钮。

固定确认语句:

publish-to-production
rollback-production-release
retry-production-release

API

POST /api/v1/release-batches/{batch_id}/authorize-production
POST /api/v1/release-executions/{execution_id}/authorize-rollback
POST /api/v1/release-executions/{execution_id}/retry
GET  /api/v1/release-capabilities
GET  /api/v1/release-executions
GET  /api/v1/release-executions/{execution_id}

授权响应和只读审计响应不返回:

  • authorization token
  • authorization_code
  • bundle SHA
  • WeKnora API key
  • Elasticsearch / PostgreSQL / 对象存储凭据或 URL
  • 测试 KB 内部连接信息

审计响应显示:

  • publish / rollback
  • pending / running / completed / failed
  • 固定授权人
  • 总项数、成功数、失败数
  • 每项固定错误码
  • production revision / previous production revision
  • 开始和完成时间

Migration 7

新增:

release_executions
release_item_executions

约束:

  • operation 只允许 publish / rollback。
  • target_environment 固定为 production。
  • state 使用固定枚举。
  • publish execution 每个 sealed batch 唯一。
  • rollback execution 每个 prior publish execution 唯一。
  • Job 外键、逐项 Publication 外键和计数约束保留审计关联。
  • Migration 7 不回填历史 release execution,也不自动创建 Job。

往返验收:

up 7 → down 6 → up 7

Migration 7 应用前备份:

tmp/backups/governance-pre-migration7-20260906T133742+0800.dump
bytes=263001
mode=0600
pg_restore catalog=valid

Release Worker 状态机

批次 Job 在 Worker 中按每批最多50项展开,并持久记录当前批次/总批次;批内逐项执行:

pending execution
→ running
→ 为 batch items 幂等建立 release_item_executions
→ 每个 item 执行 publish 或 rollback
→ verified / rolled_back / failed
→ execution completed 或 failed

行为:

  • 一项永久失败会立即停止本项之后的同批项目与全部后续批次;未执行项记录固定release_stopped_after_failure,随后仅对已验证成功项安排自动回滚。
  • 可重试故障在重试次数耗尽前保留重试语义,不把尚未终态的项目误标为永久失败。
  • 可重试失败在 Job 还有 attempt 时返回固定 release_retryable。
  • 最后一次 attempt 仍失败时,逐项写固定错误码并收口,不留下永远 running 的 Execution。
  • 外部响应正文、对象 key、路径、凭据和错误正文不写入治理库。
  • Worker 重启后跳过已经 verified / rolled_back 的项。
  • 终态failed execution可由同一Bearer门禁和固定retry-production-release确认重排;只重置failed items,verified / rolled_back项保持完成,Job从completed/failed回pending并从attempt 0重新开始。
  • publish重排前重新验证P5 sealed snapshot;过期审核或Publication变化时返回冲突。
  • 未知 KB 不会落入默认适配器,而是 release_contract_mismatch。

v0.7.1 release bundle 契约

固定:

format             weknora-release-bundle/v1
WeKnora            v0.7.1
migration          74
retriever          elasticsearch_v8
embedding dimension 1024

每个 bundle 绑定:

  • ReleaseExecution ID
  • Publication ID
  • SourceItemVersion ID
  • source revision
  • parse revision(parser generation + index generation)
  • release revision(封批时锁定的 review ID)
  • tenant / KB / Knowledge ID
  • embedding model identity
  • Elasticsearch mapping SHA-256
  • 各 PostgreSQL 表行数和 canonical SHA-256
  • ES 文档数和 canonical SHA-256
  • 资产数量、总字节和 canonical SHA-256
  • 整包 SHA-256

任何 revision、模型、维度、KB、tenant、mapping 或 bundle digest 不一致都在目标写入前 fail-closed。

PostgreSQL 通道

只允许以下 v0.7.1 内容表:

knowledges
chunks
knowledge_tags
knowledge_tag_relations
resources
resource_bindings

没有读取或复制:

  • users / sessions / messages
  • tenants / memberships / permissions
  • API keys / model provider credentials
  • vector store connection credentials
  • Jobs / audit logs / chat history

导出范围同时绑定:

tenant_id + knowledge_base_id + knowledge_id

目标导入:

  • 必须是 migration 74 且 dirty=false。
  • 使用 per-Knowledge PostgreSQL advisory transaction lock。
  • 单 Knowledge 单事务替换。
  • ID 在其他 tenant / KB 已存在时拒绝。
  • Tag 定义和 relation 一起复制;共享 Tag 不删除。
  • Resource stable handle 保留。
  • production storage_backend_id、provider、storage URI 和 location_hash 按目标配置重写。
  • Chunk seq_id 冲突时整个事务失败,不静默改 ID。
  • 不调用 WeKnora 解析和 Embedding。

Elasticsearch 通道

源导出:

  • tenant / KB / Knowledge 三重 term filter。
  • 原样保存 _id、chunk文本、向量和其他 _source 字段。
  • mapping 只计算 mappings canonical SHA,不把索引名混入指纹。
  • 导出后按 _id canonical排序。
  • 单 Knowledge 超过 10000 个 ES 文档时 fail-closed,不截断。

目标替换:

  1. 先 bulk upsert 新文档,refresh=true。
  2. 再只删除同 tenant / KB / Knowledge 下、不属于新 release 的旧 _id。
  3. 读回数量和 canonical SHA。
  4. 失败时自动恢复 prior SearchSnapshot。

检索禁用使用 knowledge-scoped _update_by_query 将 is_enabled=false,不删除 ES 文档,并读回确认文档仍在且全部禁用。

对象存储通道

支持 S3-compatible MinIO / S3 / OSS:

  • secret 仅从 0600 文件读取。
  • 不在 URL 中携带 token。
  • 支持 v0.7.1 storage://<backend-id>/<provider-path>。
  • 拒绝 traversal、query、fragment、控制字符和越界 locator。
  • 导出时核对 resource size / content SHA。
  • staging:.governance-release-stage/<release_revision>/<relative-key>。
  • staging 写入后做完整流式 SHA-256。
  • Copy 到 final 相同 relative key 后再次完整流式 SHA-256。
  • ObjectStore 接口没有 Delete 方法。
  • 回滚重传旧 bundle 中的资产;不删除新资产。

Release bundle 归档

每个 KB 使用独立目录:

<archive_root>/<knowledge_base_id>/

要求:

  • archive root 和 KB 子目录是真实 0700 目录。
  • 不允许目录或文件软链接。
  • bundle 文件为普通 0600 文件。
  • 原子写、文件 fsync、目录 fsync。
  • 相同 release revision + 相同 SHA 可幂等重写。
  • 相同 revision + 不同 SHA 为碰撞并拒绝。
  • 加载时重验 manifest、PG、ES、资产和整包 SHA。
  • 默认每 bundle 最大 1 GiB。

Bundle payload 可能包含对象 locator 和资产内容,因此不能进入 Web/API/普通日志;只有摘要进入治理库。

Release Worker 配置

模板:

config/release.v071.example.json

Runtime 配置文件必须:

  • 使用绝对路径;
  • 是普通文件;
  • 不是软链接;
  • 权限为 0600;
  • JSON 不允许未知字段或尾随数据;
  • 每个 KB 只允许一个 route;
  • source 与 target 的数据库值、ES endpoint、对象 endpoint+bucket 必须不同;
  • 所有数据库URL、ES token、对象AK/SK、WeKnora API key 只通过各自 0600 文件引用。

模板不能直接启用生产。启用前必须根据真实数据面生成独立受限配置和 Compose override,并由人工授权。

本地真实 E2E

精确 schema

使用:

ParadeDB PostgreSQL 17
WeKnora v0.7.1 migrations 000000..000074
source DB + target DB

结果:

source migration=74
目标 migration=74
合成 source:
  knowledges=1
  chunks=1
  knowledge_tags=1
  knowledge_tag_relations=1
  resources=2
  resource_bindings=1

通过:

TestPostgresExporterV071Integration
TestPostgresImporterV071ReplacesAndRestoresDocument

覆盖:

old → new → restore old
disable keeps 1 Knowledge + 1 Chunk
Knowledge=disabled
chunk enabled count=0

对象存储

使用本机已有的 disposable MinIO,无卷、loopback随机端口、合成凭据和合成bucket。

通过:

TestS3ObjectStoreIntegration

覆盖:

Open storage:// source
Put staging
streaming SHA Stat
Copy final
streaming SHA Stat

三通道发布与回滚

通过:

TestThreeLaneReleaseAndRollbackV071Integration

链路:

publish review-release-1
→ publish review-release-2
→ rollback review-release-1
→ target PG chunks=1
→ target ES has es-release
→ target two assets non-empty

说明:

  • PostgreSQL 和 MinIO 为真实 disposable 服务。
  • Elasticsearch 为状态化 HTTP contract server,真实执行 mapping/search/bulk/delete-by-query/update-by-query 协议和 digest 回读。
  • WeKnora verifier 使用现有官方 client 接口 contract stub,真实执行 GetKnowledge/ListChunks/SearchKnowledge 的判断逻辑;没有连接生产 WeKnora。
  • 所有 disposable 容器最终均已停止并由 --rm 删除。

Worker装配

通过:

TestBuildReleaseRuntimeWithRestrictedFilesIntegration

验证:

两个真实 v0.7.1 DB连接
source/target secret文件0600
独立ES endpoint
独立对象bucket
独立官方WeKnora endpoint
KB archive 0700
RoutingPublisher构建
关闭时两个DB pool均释放

Web验收

Firefox Selenium只读验收:

review_candidates=5
release_batches=0
release_executions=0
production_request_gate=false
unauthorized_release_controls=0

截图:

tmp/P6_RELEASE_AUDIT_FIREFOX.png

可见:

  • 左侧发布管理导航和顶部标签。
  • P5审核候选与封批记录。
  • P6生产执行审计。
  • 生产请求门禁关闭。
  • Release Worker保持关闭。
  • 当前无生产执行记录。
  • 页面没有生产授权按钮。

当前持久治理库

schema migrations=1,2,3,4,5,6,7
publication_reviews=0
release_batches=0
release_batch_items=0
release_executions=0
release_item_executions=0
release publish/rollback jobs=0

现有 5 条候选仍为知识工程师业务待审,未由测试代替人工批准。

生产前仍需人工完成

以下不属于本地自动验收,也未执行:

  1. 明确批准生产发布窗口。
  2. 对测试和生产各选一份同ID真实Knowledge,做只读PG/ES/OSS依赖盘点。
  3. 确认两端均是WeKnora v0.7.1 migration 74、1024维、同模型和同mapping。
  4. 为source创建只读数据面凭据。
  5. 为target创建仅允许约定KB内容表、ES索引和对象bucket的最小写权限凭据。
  6. 创建0600 release config和secret文件,并让Release Worker独占挂载。
  7. 先做1份无敏感canary。
  8. 官方API读取、Chunks、检索、资产和审计全部通过后再扩批。
  9. 保留Release Worker关闭状态,直到上述材料由人工核对。

不得因为本文完成而自动执行生产发布、创建生产Job、连接生产数据面、删除任何WeKnora Knowledge或对象。