父任务
背景
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 的公开内容和可见性,不应同时承担文件对象所有权、计费归属及清理责任。
当前问题
语义耦合
修改 Resource payload 可能意外改变文件的计费、清理和 mutation 语义。阅读模型时也无法直接判断节点是在引用 Files API File,还是拥有 Filestore 对象。
SQL 复杂且分散
Catalog、Files 可见性、Source 删除保护、配额重算、TTL、Session cleanup 和对象清理都需要重复使用 payload IS NULL 或 payload IS NOT NULL 推断所有权。
数据库无法准确表达不变量
当前约束只能校验 payload 和字段形状,不能直接声明一个 File Resource 必须是 referenced 或 owned,也不能阻止所有权语义随无关字段变化。
生命周期风险
判别遗漏可能导致 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 的对象和计费语义。
验收标准
失败场景优先:
成功场景:
测试要求
依赖关系
参考
父任务
背景
PR #190 移除
filestore_entries后,session_resources保存 Session namespace 节点及其file_uuid引用,files保存真实文件身份、内容元数据和对象定位。当前文件节点通过
payload是否为 SQLNULL推断两种不同的所有权:payload IS NOT NULLpayload IS NULL但
payload原本表达 Sessions Resource API 的公开内容和可见性,不应同时承担文件对象所有权、计费归属及清理责任。当前问题
语义耦合
修改 Resource payload 可能意外改变文件的计费、清理和 mutation 语义。阅读模型时也无法直接判断节点是在引用 Files API File,还是拥有 Filestore 对象。
SQL 复杂且分散
Catalog、Files 可见性、Source 删除保护、配额重算、TTL、Session cleanup 和对象清理都需要重复使用
payload IS NULL或payload IS NOT NULL推断所有权。数据库无法准确表达不变量
当前约束只能校验 payload 和字段形状,不能直接声明一个 File Resource 必须是 referenced 或 owned,也不能阻止所有权语义随无关字段变化。
生命周期风险
判别遗漏可能导致 referenced 文件被重复计费或误删,也可能导致 owned 文件未扣减配额、未创建清理任务或被当作 Source File 保护。
目标
在
session_resources中为 namespace File Resource 引入显式file_ownership:file_ownershipreferencedownedNULLpayload此后只表达 Sessions Resource API 的公开 Resource 合同,不再用于推断文件所有权。预期行为
/uploadsattach 创建referenced文件节点,继续引用原 Source File。/outputs文件创建owned文件节点。/uploads、/outputs路径策略决定可见范围,但通过显式所有权判断 Input 与 Output。迁移要求
referenced;owned;实施范围
非目标
file_、sesrsc_、路径和挂载行为。/uploads、/outputs等固定根目录。验收标准
失败场景优先:
成功场景:
referenced节点,并继续返回原 Sourcefile_id。owned节点。payload IS NOT NULL只保留在确实判断 Sessions Resource API 可见性的查询中。测试要求
依赖关系
参考