90ab1c3a5e
build / build (push) Successful in 1m24s
写入用户明确接受的两项边界:发布前旧分页游标在发布后失效(刷新重新分页,不改 WeKnora、不声称后端可识别无代次游标);ES 已写成功但回执丢失且无法证明归属时 保持维护锁定并走人工核验/恢复,不承诺自动收敛,禁止凭相同正文或 seq_no 推断归属、 补写 receipt、跳过校验、强制开门或删除未知内容。可证明归属的自动补偿不回退。
90 lines
14 KiB
Markdown
90 lines
14 KiB
Markdown
# v0.7.1 整库发布的数据契约与覆盖边界
|
||
|
||
这是候选代码的契约,不是部署许可。真实隔离执行证据在交付目录 `data-contract-r10.md`、`test-summary-r10.json` 及其 raw 索引。PG/ES/MinIO 均为本机隔离真实服务;发布/恢复测试直接调用适配器;B22 另走实际 `librarystack.OpenProductionStack` 和 HTTP 网关。网关的独占审核是测试夹具,不代表生产已隔离;B22 控制读取角色是真实 SELECT-only PG 角色。
|
||
|
||
## 1. 源 schema、历史身份与准入
|
||
|
||
- 新 `ExportLibraryWithReleases` 在读取内容的同一 REPEATABLE READ / READ ONLY 事务中读取 columns、constraints、indexes、triggers、relations、sequences;非普通表、RLS/继承/分区、自定义触发器或未知入边 FK 拒绝。
|
||
- 新归档增加一个内容寻址的 `schema/migration-catalog` 部件。归档核 JSON 目录结构、计数、大小和 SHA256;来源清单摘要绑定该部件。PlanWriteSet 在分配序列或执行任何业务写前要求其摘要与当前目标目录一致。序列定义属于目录,last_value/is_called 不属于 DDL 指纹。
|
||
- 旧快照没有该部件:不追加字段、不重新 seal、不改历史 hash;继续走原目标 catalog 和逐行字段/身份检查。它不能被称为“原捕获时已有 schema 证明”。旧V1台账的 canonical 规则不变。
|
||
- 目录严格一致不是自动迁移方案。源/目标定义不同会拒绝,需要单独评审兼容性,不能在业务库上临时 DDL 绕过。
|
||
- 实际 stack 准入读取目标模型、维度、schema 和 graph 状态。同维不同模型、模型行缺失、空模型名、graph 开启、schema dirty 均拒绝;不重算 embedding、不关闭 graph。正例和负例都比较 PG、ES正文/CAS、对象精确版本与网关 journal,证明准入无业务副作用。
|
||
|
||
## 2. 逐表身份、闭包和保真规则
|
||
|
||
|表|身份及本轮已执行的边界|保真/未闭合范围|
|
||
|---|---|---|
|
||
|knowledges|稳定字符串 ID;租户与KB按计划映射;正文/file_path/metadata 中规范资源引用进入闭包|其余原始列保留;模型/存储定位只按已冻结目标策略改写。不声称已穷尽全部业务 metadata 中非资源ID的语义|
|
||
|chunks|稳定 ID;knowledge_id 必须存在且同租户同KB;源 scoped 孤儿不能被 JOIN 静默丢掉;seq_id 使用固定数值映射|chunk_index 排序、按tenant+seq_id查询实跑;规范pre_chunk_id/next_chunk_id/parent_chunk_id要求同knowledge;tag_id及relation_chunks/indirect_relation_chunks要求在范围内,父树拒绝环;不把任意metadata数字猜成seq引用|
|
||
|knowledge_tags|稳定 ID;租户/KB必须匹配;seq_id按源数值大小分配并固定|sort_order ASC、created_at DESC、seq_id DESC 的并列排序实跑;原数值在恢复时回原,不回拨共享序列|
|
||
|knowledge_tag_relations|精确(knowledge_id,tag_id)元组;任一父可证明范围;缺knowledge、缺tag、跨KB/租户在源、入站及目标规划处拒绝|不通过内连接删除异常关系、不把元组拼成单列。非空正常/既存元组由完整隔离套件执行|
|
||
|resources|稳定 ID/22字符handle;引用资源必须存在,原对象字节、精确版本保真;目标正文引用也进入before-image和验证范围|共享资源仍按原不可变before-image/来源证明判断复用,不放宽成“字节相同即认领”。无本次归属的历史孤儿保持不动|
|
||
|resource_bindings|稳定 ID;resource_id必须在来源闭包;owner仅knowledge/chunk,必须可解析且租户一致|未知owner_type、NULL owner_type、缺owner及跨租户拒绝。被本次引用资源上的所有绑定都核验,包括指向其它KB的共享绑定;合法共享允许、未知共享归属拒绝|
|
||
|wiki_folders|稳定 ID、tenant/KB保真;整行JSON的规范资源字符串参与识别|parent_id必须在本次目录闭包且无环;目录ID碰撞写前拒绝;未知外部持久路径仍不声明支持|
|
||
|wiki_pages|稳定 ID;正文和嵌套metadata资源在无binding时也捕获/备份/发布/恢复|页面内容原样保留;按真实字段source_refs(含knowledgeID|title旧形状)、chunk_refs、folder_id检查闭包;不按不存在的source_knowledge_ids字段推断支持|
|
||
|wiki_page_issues|稳定 ID、tenant/KB保真;整行资源字符串参与识别|原v071按slug引用/展示的问题记录原样保留;未知扩展字段不自动映射数值ID,不能当作任意插件引用支持|
|
||
|wiki_log_entries|BIGINT正整数,不经float64;按源数值大小映射(2在100之前),映射绑定原计划|id DESC和cursor id<实际SQL实跑;恢复原id。旧外部持久游标不能跨映射直接复用,原A04此项仍需决策/验收|
|
||
|resource_access_grants(非迁移表)|唯一已审核的resources外部FK入边;不搬运、不删除授权|新建资源清理/Restore前若出现授权,持父键锁拒绝删除;恢复入口另核未知绑定/计划外绑定和外库正文metadata引用,有引用时在ES/对象/PG写前held,锁内再次核全部正文/绑定;当前完整隔离测试读回grant、PG、ES、原对象版本均未变。其它未知入边/触发器拒绝|
|
||
|
||
既有目标历史软删除行按原 destructive predicate 进入 before-image,不用来源活跃行过滤规则裁剪恢复数据。来源导出只包含活跃内容;可归属于本库的异常边在过滤前检查。完全没有任何父或范围锚的历史记录不能猜成属于本库:不全局扫出后删除、不自动迁移;这不是“所有历史孤儿已修复”。
|
||
|
||
## 3. 资源引用的实际格式
|
||
|
||
只读参考源码:`WeKnora-v0.7.1-dev/internal/types/resource.go` 中 `ResourceHandleLength=22` 与 `ParseResourcePath`;本轮没有修改WeKnora源码。
|
||
|
||
支持并真实执行:
|
||
|
||
- knowledge或Wiki正文里的 `resource://` 加22个URL-safe字符,包含Markdown图片语法;
|
||
- 嵌套JSON对象/数组字符串值里的同一规范引用,例如metadata.image_info[].url;
|
||
- 无binding的上述四种组合:源捕获、目标before-image、发布、恢复;完整PG及精确对象版本含历史孤儿比对。
|
||
|
||
实现先解码JSON再递归扫描字符串,不将JSON数字转为float64,也不重写正文。所有迁移行的字符串参与资源发现。目标 SQL 选择覆盖所有带tenant/KB的迁移表整行JSON,并与严格解析/完整引用存在性检查配合,不再只认chunk.content。
|
||
|
||
明确拒绝本轮实测的未知表示:短handle、过长handle、URI query/path/fragment后缀、再次转义的resource协议串;不截取22字符前缀冒充合法引用。未知owner独立拒绝。
|
||
|
||
覆盖边界:这不是任意富文本、任意编码或所有插件格式解析器。不承诺识别不含规范协议标识的opaque/base64内容、插件自定义裸UUID引用或全部非资源语义ID。新增格式需给出真实生产/读消费者契约、负例与保真证据后再支持;当前总体验收保持INCOMPLETE。
|
||
|
||
## 4. 数值序列与消费者
|
||
|
||
- chunks.seq_id、knowledge_tags.seq_id、wiki_log_entries.id:正常分配、落后于已占值、耗尽、源显式NULL分别真实执行。后3类在业务阶段前拒绝;所有序列的last_value/is_called、PG全文、ES正文/CAS、S3版本/删除标记不变。
|
||
- 正常分配是明确副作用,会推进序列;必须先固化P0/预备份,不能称只读。新target接管同一WriteSet再Apply不再取号;恢复原行但不rewind序列,合法空洞不等于身份丢失。
|
||
- v071消费者位置:repository/tag.go(sort_order/created_at/seq_id排序)、repository/chunk.go(seq_id读取及chunk_index排序)、repository/wiki_log_entry.go(id DESC、cursor id<)。本轮执行其SQL语义,不声称启动了完整WeKnora UI或所有外部消费者。
|
||
|
||
## 5. 后续门与安全边界
|
||
|
||
- 真实v071历史catalog镜像对照及UUID/自然键矩阵在r11执行,隔离结论与外部当前目录采样分开。A04已识别嵌套引用有有限支持,旧外部cursor仍不自动转换,不因内部结构正例而升级整项。
|
||
- r8/r9进程恢复历史证据只在原发送协议、不可变计划/receipt及相应场景前提不变时复用。本轮对schema/引用准入有收紧,不能把历史执行日志称为当前源码正式进程全链重跑;完整当前隔离适配器回归单列。
|
||
- clean-finalization unknown保持held是安全行为,但未满足全部自动收敛要求。若要求自动完成,需要能绑定原写入、完整内容和代次的充分服务端证据;相同干净正文、人工猜测归属或放宽共享规则均不可替代。
|
||
- 最终提交/push、远端CI、父复核和本机部署为明确下一门;整体安全验收未齐不部署。生产独占/ACL网络凭据、网关OSS业务端到端与宿主重启/断电是另行授权的部署/破坏性验收,不能作为停止本机授权开发的借口。
|
||
|
||
## 6. 目录与唯一键的有限支持矩阵
|
||
|
||
`testdata/catalog-v071-r11.json` 来自既存只读取证 `source-schema-live-r3b.json`,不是当前线上目录新采样。隔离镜像按原列、约束、索引、关系和触发器定义重建并逐项比较;sequence定义和OWNED BY归属独立绑定,不将last_value伪装成只读DDL。未知外部入边、外部出边、迁移表内部数值FK扩展不能因为源/目标同样扩展就默认放行。
|
||
|
||
稳定主键:knowledges、chunks、knowledge_tags、resources、resource_bindings、wiki_folders、wiki_pages、wiki_page_issues按同租户异KB/异租户碰撞检查;knowledge_tag_relations使用父身份元组;三个本地数值身份按固定WriteSet映射而非稳定UUID处理。资源共享的合法同ID复用仍依赖原before-image和所有权证明。
|
||
|
||
自然键以实际catalog的unique/partial indexes为基准:资源handle、有效tenant/location_hash、资源绑定复合键、tag tenant/KB/name、目录KB/parent/name、wiki KB/slug,以及provider/physical_path的应用层排他。wiki_page_issues在该目录中只有主键,没有额外自然唯一键。partial predicate按有效/软删除判定,并验证不同parent的正常目录;不能无差别拒绝全部正常资料。具体逐键DDL、正负例对应表见交付 `data-contract-r11.md`。
|
||
|
||
Wiki日志pages_affected仅支持消费者已有的字符串slug数组和{slug,title}对象数组;已删除页面/knowledge的历史日志是展示事实,不以缺现存父强删/拒绝历史日志。数字数组、对象中新增log_id等未知结构拒绝。普通metadata数字原样保存,不推断为日志ID。
|
||
|
||
旧外部cursor是明确剩余风险:目标新游标可正确分页,但源数字cursor或发布前旧cursor不能直接带入目标新映射。原A04“改本地ID不破坏引用/语义”没有豁免条款,所以仍未整项通过;本模块不擅改上游WeKnora,也不把“刷新页面即可”当作已经实现的自动失效协议。
|
||
|
||
授权以执行时真实时钟为准;完成预备份及持久计划登记后均再验。目标实现 `GrantRevalidator` 时按治理库授权行回读:`ActiveLibraryAuthorization` 只返回未撤销、未过期且参数摘要一致的记录,撤销后连写集合都不计算(避免序列分配),预备份保留待重新授权。真实发布 wrapper 无条件接线;未接线不等于安全,只是本机历史证据的边界。
|
||
|
||
## 7. r12 追加的真实边界
|
||
|
||
- **软删除 before-image**:destructive predicate 能碰到的 7 张带 `deleted_at` 的表(knowledges/chunks/knowledge_tags/resources/wiki_folders/wiki_page_issues/wiki_pages)逐表核对备份行数与删除谓词命中数;晚阶段失败后的恢复把这些行连同 `deleted_at` 原值一起放回(r12 真实 PG)。
|
||
- **索引独立 oracle**:发布成功后,正文、向量(同维不同值)、metadata(knowledge_id)任一被改都会让校验变红;改回后必须重新通过,说明合法本地列映射没有被这项收紧误杀。
|
||
- **外部索引文档**:同 `_id` 属于其它知识库时整轮在写前拒绝,且那份文档的 `_source`、`_seq_no`、`_primary_term` 与对象版本逐字节不变。
|
||
- **共享资源差异**:同 ID 共享资源在 handle / 定位 / 字节三类差异下都写前拒绝,共享对象版本不变;完全相同的共享资源按 no-op 复用,行 `xmin`/整行 JSON 与对象精确版本均不变。
|
||
- **晚阶段失败与对象**:Wiki 阶段失败回滚后,本次新建对象不残留,**已有对象的精确版本**(versionId + SHA256 + 字节数)不变。
|
||
- **clean 归属能力(实测)**:两个不同操作写入完全相同的字节后,索引侧只剩同一份 `_source`;`_seq_no`/`_version` 是分片提交计数,不绑定原 operation。因此"相同正文"或"seq_no+1"都不能证明是同一次 clean,缺服务端归属证据时继续 held(探针:`b19-capability-probe.json`)。
|
||
- **旧外部游标**:游标是裸数字 id(`next_cursor` → `id < cursor`),发布按源数值升序重排本地 id。目标自己发出的游标在同代内正确;发布前或来自其它实例的游标没有代次信息,无法自动翻译,会静默错位而非报错。详见交付 `a04-cursor-semantics-r12.md` 的 A/B 决策。
|
||
|
||
## 8. 已接受的可用性边界(r13,用户明确接受)
|
||
|
||
- **旧分页游标跨发布失效**:`next_cursor` 是裸数字 id(`id < cursor`),发布按源数值升序重排本地 id,请求里没有代次信息。发布前/跨实例游标无法翻译,回传会静默错位而非报错。已接受做法:发布或恢复后刷新页面、重新从第一页分页;不改 WeKnora 源码/Schema。同代内游标与引用重映射仍然正确。
|
||
- **clean-finalization 缺归属证据时保持 held**:真实 ES 探针显示两个不同操作写入相同字节后只剩同一份 `_source`,`_seq_no`/`_version` 是分片提交计数、不绑定 operation。此分支保持维护锁定并走人工核验/恢复,不承诺自动收敛;**不允许**凭相同正文或 seq_no+1 推断归属、补写 receipt、跳过校验、强制开门或删除未知内容。带原 marker/before-image/CAS/不可变 receipt 的 unknown 仍走既有自动补偿,不因本边界回退。
|
||
|
||
配套只读诊断:`GET /api/v1/library/jobs/{id}` 在作业失败且目标仍被本作业持有时返回 `maintenance`(held/owner/state/ingress/target_identity/note)与 `next_step` 文本,明确"按预备份与校验证据人工核验后再放行",且不提供任何强制成功入口;作业本身仍带 operation_id/attempt_epoch/backup/target 快照与错误、恢复说明。
|