Skip to content

[Files][#184 子任务] 用显式文件所有权替代 payload 空值判别 #205

Description

@pxguan

父任务

背景

PR #190 移除 filestore_entries 后,session_resources 保存 Session namespace 节点及其 file_uuid 引用,files 保存真实文件身份、内容元数据和对象定位。

当前文件节点通过 payload 是否为 SQL NULL 推断两种不同的所有权:

当前判别 实际语义
payload IS NOT NULL Referenced/mounted Input:引用 Files API 已有文件,Session 只保存引用
payload IS NULL Owned File:由 Session/Filestore 创建并拥有,承担存储计费和对象清理

payload 原本表达 Sessions Resource API 的公开内容和可见性,不应同时承担文件对象所有权、计费归属及清理责任。

当前问题

  1. 语义耦合

    修改 Resource payload 可能意外改变文件的计费、清理和 mutation 语义。阅读模型时也无法直接判断节点是在引用 Files API File,还是拥有 Filestore 对象。

  2. SQL 复杂且分散

    Catalog、Files 可见性、Source 删除保护、配额重算、TTL、Session cleanup 和对象清理都需要重复使用 payload IS NULLpayload IS NOT NULL 推断所有权。

  3. 数据库无法准确表达不变量

    当前约束只能校验 payload 和字段形状,不能直接声明一个 File Resource 必须是 referenced 或 owned,也不能阻止所有权语义随无关字段变化。

  4. 生命周期风险

    判别遗漏可能导致 referenced 文件被重复计费或误删,也可能导致 owned 文件未扣减配额、未创建清理任务或被当作 Source File 保护。

目标

session_resources 中为 namespace File Resource 引入显式 file_ownership

file_ownership 语义
referenced 节点引用 Files API 管理的 File;不拥有对象、不重复计费、不负责清理
owned 节点拥有 Session/Filestore 创建的 File;由 Filestore 计费并随生命周期清理
NULL 非普通文件节点,例如 directory 或 skill archive

payload 此后只表达 Sessions Resource API 的公开 Resource 合同,不再用于推断文件所有权。

预期行为

  • /uploads attach 创建 referenced 文件节点,继续引用原 Source File。
  • Filestore 创建的普通文件和 /outputs 文件创建 owned 文件节点。
  • referenced 文件不能通过通用 Filestore mutation 覆盖、移动或删除。
  • 删除 referenced Resource 只移除挂载节点,不删除 Source File,也不释放 Files API 已计费的字节。
  • Source File 存在活动 referenced Resource 时仍禁止删除。
  • owned 文件的覆盖、删除和 Session cleanup 继续原子更新存储用量并登记对象清理任务。
  • Files Catalog 继续通过 /uploads/outputs 路径策略决定可见范围,但通过显式所有权判断 Input 与 Output。
  • Resource payload 的修改不得改变文件所有权、配额或清理责任。

迁移要求

  • 使用新的 goose migration 增加并回填显式所有权,不修改已应用 migration。
  • 按 PR feat: unify session resources and files #190 已建立的不变量回填:
    • 当前公开、引用 Source File 的普通文件节点迁为 referenced
    • 当前由 Session/Filestore 拥有的普通文件节点迁为 owned
    • directory、skill archive 等非普通文件节点保持为空。
  • 回填前执行 preflight;遇到无法唯一判断、字段形状冲突或租户归属不一致的数据时明确失败,不自动猜测。
  • 增加同行 shape 约束,确保活动普通文件节点具有合法所有权,非普通文件节点不能携带文件所有权。
  • 不创建 PostgreSQL 外键。

实施范围

  • namespace File 读模型和领域类型;
  • Input attach 与 Filestore-owned File 写入;
  • Files Catalog 和 Files 删除保护;
  • Filestore mutation 权限;
  • workspace storage usage 计算;
  • TTL、Session cleanup 和对象清理中的所有权判断;
  • migration、数据库约束、测试和设计文档。

非目标

  • 不改变 Files API、Sessions Resource API 或 Filestore 的公开响应结构。
  • 不改变现有 file_sesrsc_、路径和挂载行为。
  • 不重新引入 Session File projection 或 borrowed File 行。
  • 不重新设计 /uploads/outputs 等固定根目录。
  • 不在本 issue 中裁决文件级 TTL 是否保留。
  • 不改变 skill archive 的对象和计费语义。

验收标准

失败场景优先:

  • migration 遇到所有权不明确或字段形状冲突的数据时中止并报告原因。
  • 跨 workspace 的 referenced File 关联被拒绝。
  • referenced 文件不能被通用 Filestore mutation 覆盖、移动或删除。
  • referenced 文件不会增加 Filestore 用量,也不会生成 Source File 对象清理任务。
  • owned 文件不会因 payload 变为非空而退出计费或逃避清理。
  • Source File 存在活动 referenced Resource 时仍不能删除。
  • directory、skill archive 等非普通文件节点不能伪装成 referenced 或 owned File。

成功场景:

  • attach 创建明确的 referenced 节点,并继续返回原 Source file_id
  • Filestore create/copy 创建明确的 owned 节点。
  • owned 文件覆盖、删除和 Session cleanup 只释放一次配额并只清理一次对象。
  • scoped Files Catalog 对 Input 和 Output 的 ID、内容及分页行为保持不变。
  • 修改公开 Resource payload 不改变节点所有权。
  • 所有涉及计费、清理、mutation 和 Source 引用保护的生产 SQL 不再使用 payload 空值推断所有权。
  • payload IS NOT NULL 只保留在确实判断 Sessions Resource API 可见性的查询中。

测试要求

  • 覆盖显式所有权的查询生成和参数绑定。
  • 增加真实 PostgreSQL migration、preflight、约束和回填测试。
  • 更新 Input attach、Output mutation、Catalog、Source delete guard、配额和 Session cleanup 集成测试。
  • 保留真实 FUSE/E2B 输入只读和输出即时可见验收。
  • 更新 Session Resource/File 与 Filestore 生命周期设计文档。
  • 通过项目规定的 Go 测试、lint、死代码、重复代码、复杂度和大文件门禁。

依赖关系

参考

Metadata

Metadata

Assignees

Labels

ready-for-agentSpecification confirmed and ready for implementation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions