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 的合并与迁移的实际执行都需要明确的人工决定,不应自动进行。

Metadata

Metadata

Assignees

No one assigned

    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