Skip to content

ADR-0104 D3 wave 2 — file-as-reference:独占所有权模型 + 到开启回收的分步序列(PR-5b 门禁改由 #3617 承载) #3459

Description

@os-zhuang

跟踪 ADR-0104 D3 wave 2。所有权模型已于 2026-07-27 定案(ADR addendum),序列相应重排。

定案:字段引用是独占

附件面(sys_attachment)故意共享一个文件给多条记录——那是它的特性。字段引用取相反模型:一个文件最多被一个 (对象, 记录, 字段) 槽位拥有,所有权记在 sys_file.ref_object / ref_id / ref_field;把已被拥有的 id 写进第二个槽位会复制字节到新的 sys_file,而不是共享行。

理由是失败模式不对称,不是优雅:

  • 共享的最坏情况——文件的可读集合变成所有引用者的并集(把私有记录的 id 拷进公开记录 = 无声泄漏),以及一次漏记就授权不可逆删除
  • 独占的最坏情况——字节重复占用。花钱,可恢复。

存储成本应在字节层用内容哈希去重解决(sys_file.etag 已在),而不是在行层共享——去重字节,不去重行

核心安全原则

一个文件变得可回收,只能因为平台观察到它唯一的所有者放手了,绝不能因为一次扫描推断出没人引用它。

独占所有权彻底消除了计数:没有计数,就没有漏记能授权删除;也化解了新账本的冷启动隐患。

序列与进度

顺序修正(已落地)

旧计划把写切换和开 GC 打包、回填排在后面。两处都错了

  1. 回填必须先于写切换。记录里还存着遗留值时就收窄可接受的存储形态,会让任何改写该字段的 update 被拒——在回填赶上之前,正常工作的应用会挂。
  2. 回填 + 对账必须先于开 GC。一本账在被证明与现实一致之前,不该被授予对不可逆删除的权力。

PR-5a 为什么今天不是破坏性变更

值形态检查是 warn-first(ADR-0104 R1/R2):还没回填的行照样写得进去,作者拿到的是指名字段的告警。硬拒绝只在部署方主动打开 OS_DATA_VALUE_SHAPE_STRICT_ENABLED 时发生——而那应该在跑完回填并用 verifyFileReferences() 确认对账之后。strict 默认值的翻转由 #3438 跟踪。

PR-5b 的两条硬约束

  1. 两半必须同一个 change:放松 scope === 'attachments' 墓碑护栏 扩展 sys_file reap guard 的扫描时复核到所有权列。只做前一半比完全不做更糟——打了墓碑而 guard 仍只复核 sys_attachment(对字段文件恒为空),会让每一次释放都变成保证性的字节删除,而不只是有风险。这一点在 releaseOwnership 的文档注释和一条专门的回归测试里都锁死了。
  2. R4 验收门(可执行)verifyFileReferences() 必须在真实租户数据上、连续 ≥7 天报告零阻断项才允许合并。阻断项 = unowned_reference(持有但无人拥有)、foreign_owner(持有的文件被别的槽位拥有)、shared_reference(一个文件被两个槽位持有)。提示项(stale_owner / unreferenced_file)倒向保留,不阻断。

R5(公开姿态清单)和 R6(子键读取静态扫描)依然有效。

PR-5b 的合并与迁移的实际执行都需要明确的人工决定,不应自动进行。(已于 2026-07-30 经人工授权合并,#4208;各部署的迁移执行仍由其运维自行决定。)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions