Files
knowledge-governance/docs/object-write-gateway.md
姚章浩 5eef13b615 feat(object-gateway): 统一对象写网关与不可变治理日志
方案A:所有对象写入经 loopback 网关,持久计划/grant/intent/逐版本 receipt 与
attempt→grant delivery 落在治理 PG;scope 由 provider+endpoint+bucket 决定,
同一物理桶只允许一个写域。版本 append 不等于 CAS,未锚定计划在首笔业务写前拒绝。

本提交只含网关与其适配层接线、协议文档;生产独占、凭据切换与桶设置未改动。
2026-11-01 11:35:05 +08:00

11 KiB
Raw Permalink Blame History

对象写入网关(开发候选,尚不可部署业务)

协议与持久化

cmd/object-gateway 提供 kg-object-gateway-v1,只监听 loopback;外部接入需要另行审核的 TLS 终结与网络隔离。治理 PG 保存不可变计划、grant、intent、逐版本 receipt 和 attempt→grant delivery。部署迁移身份显式应用 internal/objectgateway/schema.sql;启动不自动迁移。运行身份只授必要 SELECT/INSERT 和 scope 行 UPDATE,不授 DDL/TRUNCATE/证据修改或业务库写权限。

scope 是 provider、原 endpoint、bucket 的 NUL 分隔 SHA256。同一物理桶只能有一个受审核 scope 和一套共享 journal,不能用 endpoint 别名或另一日志库建立平行写域。

数据认证端点:POST /v1/acquire、/v1/resolve、/v1/execute、/v1/reconcile、/v1/observe,以及 GET /v1/capabilities。所有请求核验协议和 scope。capabilities.automatic_grants 必须为真,正式 stack 才接受自动授权能力;未接控制源、只读角色权限异常、协议不符或不可达均拒绝,无直写回退。

PG 行锁负责原子准入,不是远端存储 fence。intent 必须持久提交后才发送一次请求;连接死亡、租约过期不清除未知 intent。任何未终态 intent 均阻止 scope 换 epoch。重复 operation 只读回执或精确对账,不重发 PUT;暂时查不到版本仍 held。删除必须绑定已确认 create 的精确 VersionID,不删 latest、不生成删除标记。

不可变证据表拒绝 UPDATE/DELETE/TRUNCATE。Ready 核 fsync、synchronous_commit、full_page_writes、主库可写及必要表/触发器;管理员与迁移身份仍是可信边界,触发器不能抵御 DBA 的 DDL。

正式控制交付路径

生产代码路径已接入,并已在专属本机隔离栈运行正式 worker 的两次 SIGKILL 接管;这不是生产部署验收:

cmd/worker.runOwnedLibraryAttempt → attempt context → librarystack → GatewayObjectStore(Automatic) → ProductionLibraryTarget.Apply → EnsureObjectGrant → /v1/acquire → GatewayGovernanceAuthority → 原持久 WriteSet → journal delivery → 数据 execute。

冻结/采用计划时只保存自校验后的 WriteSet,不提前索取 grant;observer 先把摘要锚定到原预备份事件,首笔业务 PG Apply 前才请求授权。worker 只提交 job/operation/attempt epoch/owner/plan digest,不提交可替换的 key/body allowlist,也不能选择网关 epoch。

可信控制域使用独立 SELECT-only 治理连接,核对目标身份、job/operation、当前 attempt、owner、有效租约、running 状态和原预备份事件中的摘要、job、backup、目标快照;再从归档加载并验 hash、范围、provider/bucket。客户端自述不能替代这些持久事实。控制读角色不得是 superuser、CREATEDB、CREATEROLE,不能写/截断/建触发器到这些治理表。每次加载和能力查询重验角色。

网关在 scope 锁内再次核对授权事实,原子写入 grant、当前 epoch、不可变 delivery。同一 attempt 重试返回原 grant;owner/operation/plan 替换拒绝;同一 job 的后续 attempt 必须沿用原 operation 和 plan。旧发送未终态时,新 attempt 可先只读对账,但无充分证据仍 held,不能交接。

控制服务配置了治理 authority 后,旧 /v1/plans、/v1/advance 手动控制端点关闭,控制 token 也不能绕过原计划绑定。旧 cmd/object-gateway-control 仅保留离线/协议兼容路径,不是正式 worker 的交付方案。

独立恢复请求

library.recover 的队列ID由原窗口job确定;它具有独立恢复operation与request digest,绑定scope、queue、原window/operation、target、source、backup和原WriteSet。原计划的job、operation和摘要不改写。Claim中的recovery_job_id与recovery_operation_id仅为独立授权上下文,原job_id/operation_id仍是发送域。网关重新读取持久队列payload、running状态、当前owner/attempt/有效租约与恢复operation,不信任worker自行声明。可选显式来源/目标/backup/plan字段只能收紧原绑定,不能覆盖它。

SELECT-only治理角色需要读取library_jobs、library_operations、library_attempts、library_versions、jobs,均不能INSERT/UPDATE/DELETE/TRUNCATE/TRIGGER。已有原窗口恢复请求时不自动重启原发布;过期running恢复队列只在相同target、未释放维护窗口、原业务租约也过期时重新投递,业务attempt/epoch仍重新申请。

索引unknown恢复边界

原冻结before-image非空时,恢复先持久化restore-mark-intent,再以原CAS(或确认缺失的create)写入包含原计划/index/id归属标记的完整before-image。丢响应后仅匹配完整标记正文及有效CAS才能登记restore-claimed并CAS去标记;已知回执的代次不一致拒绝。恢复完成的restored回执仍须匹配正文和CAS,不能接受同正文的新代次。

去标记前持久化restore-finalize-intent。发布clean-finalization或恢复clean-finalization已提交但响应丢失时,当前干净正文并非充分归属证据;继续held,不盲删、不重发、不把相同外观当回执。旧unknown缺少写前标记intent亦不自动升级认领。本候选没有服务端可验证的clean提交关联凭证,因此这部分自动收敛仍是明确缺口,不是可部署完整恢复保证。

跨 epoch 的不可变发送身份

Request.Grant 继续代表首次发送的不可变身份,后端 claim、intent 和 receipt 都不改。新 ExecuteRequest.authorization 仅代表本次准入权限。/v1/resolve 根据相同 operation/key/action/hash/length/owner/plan 取回旧请求;只允许 epoch 差异,不能换内容。

新 attempt 获得新 grant 后,已发送 key 沿用旧 request/claim,未发送 key 使用新授权 epoch 建立首次 intent。当前授权必须匹配 journal 的当前 epoch;旧 epoch 的新写拒绝。新 epoch 补偿删除仍使用原 create 回执和精确版本,删除本身有独立不可变发送身份。

配置与凭据归属

网关进程环境:

  • OBJECT_GATEWAY_LISTEN:仅 127.0.0.1:<port> 或 [::1]:<port>。
  • OBJECT_GATEWAY_DATABASE_URL:独立 journal 连接,迁移与运行角色分离。
  • OBJECT_GATEWAY_GOVERNANCE_DATABASE_URL:仅可信网关持有的 SELECT-only 治理读连接;独立于 journal 运行身份和普通 worker 身份。
  • OBJECT_GATEWAY_ARCHIVE_ROOT:原持久计划所在归档绝对路径,部署只读挂载。
  • OBJECT_GATEWAY_TARGET_ID:与正式 worker LibraryTargetIdentity() 完全相同的逻辑目标身份;不能用另一个环境的值。
  • OBJECT_GATEWAY_PROVIDER、OBJECT_GATEWAY_ENDPOINT、OBJECT_GATEWAY_BUCKET、OBJECT_GATEWAY_REGION:精确存储身份。
  • OBJECT_GATEWAY_ACCESS_KEY、OBJECT_GATEWAY_SECRET_KEY:仅网关持有的对象写凭据。
  • OBJECT_GATEWAY_DATA_TOKEN、OBJECT_GATEWAY_CONTROL_TOKEN:不同且至少 32 字节;普通 worker 仅持有 data token。
  • OBJECT_GATEWAY_ID、OBJECT_GATEWAY_AUDIT_FILE、OBJECT_GATEWAY_AUDIT_PUBLIC_KEY:独立审核者的 Ed25519 签名隔离证据,绑定协议、scope、gateway ID、时间窗及 writer/policy/拒写/排空摘要。审核私钥不能交给 worker 或网关。

秘密只经服务环境/私有文件注入,不进 argv、URL 或日志。所有调用端错误都使用固定原因码。

正式 worker 使用 LIBRARY_OBJECT_GATEWAY_URL 和 LIBRARY_OBJECT_GATEWAY_TOKEN;不再依赖预先生成的 grant 文件。LIBRARY_OBJECT_GATEWAY_GRANT_DIRECTORY 保留旧离线兼容用途,不是自动模式的授权来源。发布 stack 对所有对象 provider 强制网关;S3 构造仍用于只读捕获/预备份,部署必须使用独立只读凭据。

已关闭的本仓旁路

  • ObjectImporter.Stage/Promote 无条件拒绝,不留配置开关,不执行 PUT/COPY。
  • legacy release-worker runtime/route 在读凭据及连接目标前拒绝。
  • release-diag、release-channel-e2e 旧写型入口启动即非零退出,避免 probe PUT 或文档级旁路。
  • librarystack 不再为缺网关的 MinIO/S3 返回直写 writer。
  • libraryctl capture 的 S3 构造与读取保留;未把读取当作写入口禁用。

这不等于已经撤销运行环境旧进程的凭据;源码变更尚未部署。librarydrill 等无 owned-attempt 的旧写型工具不能靠静态 grant 绕过自动模式,需补正式执行身份后才可用于写入。低层 S3 adapter 仍用于网关后端、读取及显式隔离夹具;部署权限才负责防止外部旧进程直接调用存储。

实测与未完成边界

已实跑:真实治理 PG(独立 SELECT-only 登录角色)、原 archive、数据 HTTP、业务 PG 与 MinIO 的自动授权/跨 epoch 链;未锚定计划在第一笔 PG 前拒绝,接管保持原发送身份且不增加 PUT。另有真实网关二进制的两次中断验证。隔离审计 authority/故障注入仍是测试设施,不是生产权限证明。

正式 worker 隔离进程证据:真实 Docker 入口闸门/队列、只读 worker 对象身份、独立治理读取角色、网关与业务 PG/ES/MinIO;首次 PG 后和对象提交后两次 SIGKILL,同一原 job/operation/plan 的三代 attempt 完成发布,原对象发送 epoch 与回执不变。隔离构建标签 kg_isolated_drill 仅把真实租约缩短至4秒,并拒绝非 loopback 一次性 r8 库;正常构建仍为10分钟。维护上游是无业务凭据的独立 HTTP 夹具,不是运行中的 WeKnora;签名证据绑定本次真实拒写/排空材料,不代表生产独占审核。

已补独立 library.recover 正式worker链、恢复中再次SIGKILL、完整before-image/CAS/精确版本读回,以及来源/目标/原job/计划/operation/任意队列身份拒绝。ES故障代理只转发真实ES并丢弃已提交响应,未伪造服务响应。

尚未完成:clean-finalization unknown的充分归属与自动收敛;独立运行环境只读对象凭据、ACL/网络和全部writer排空;网关真实OSS端到端;原D0/F11/F12与55项其余缺口。测试/隔离验收与真实部署独占分列,不把生产变更授权当成本机开发授权的前置。

维护隔离、独占审核与 grant 是独立前置。配置 true、健康 200、测试自签 authority 都不能代替已部署拒写/排空证据。目前未签发真实环境独占证明,未改生产权限或上游 WeKnora 源码/Schema。

部署与回退(未执行)

未来测试主机 120.76.217.51、生产 120.76.218.137 各自独立批准。每批先冻结主机、桶、全部角色/STS/预签名写入口、端口/网络规则、影响、维护窗口与回滚;当前没有执行这些外部变更。

先关新写准入→排空并对账旧请求→撤旧直写权限/切只读身份并实测拒写→独立审核→批准的新限定对象验收→父复核,才可切业务客户端。r5 的已完成 OSS 测试授权不能重复使用。

回退先关准入并保留 journal;所有已发送请求有终态才可撤网关,unknown 继续 held。不得因网关故障自动恢复旧直写凭据。权限/网络回退须另批。当前整模块 INCOMPLETE,禁止部署部分候选。