ROLLICA / v0.0.4
OpenSpec v1·2026-08-13

附件能下载,
头像能显示,
数据不再被误删。

MIA-277 / 278 / 279 不是三个互不相干的小洞,而是一条“持久对象引用如何安全交给浏览器原生消费者”的完整链路。MIA-284 Agent 已把 staged 生命周期 / GC 整体移交本方案,一起回归上游最终模型。

核心结论
三个 Issue 可以在同一个 v0.0.4 完成核心修复。“阶段”是同版内的数据安全与兼容顺序,不是三次发版。
01 / Issues

三个 Issue,一条完整链路

每个 Issue 都有自己的症状和验收点,但解决原理共享:数据库只保存稳定对象身份,Server 在授权后按使用意图生成原生消费者可用的访问能力。

MIA-279Urgent

附件下载无响应 / download.txt

根因

原生下载不携带 Renderer bearer token;同一个 inline URL 也没有明确下载语义和原文件名。

修复

点击时换 fresh attachment_download_url;JuiceFS 下是 60 秒、单附件、intent-bound capability。

原理

把已完成的身份鉴权转换成窄权限票据,再交给 Web / Electron 原生流式下载。

老数据

无需迁移。任意旧附件每次点击都现场拿一张新票据。

MIA-277Bug

默认广告分析助手头像丢失

根因

鉴权 URL 让原生图片加载失败;旧 expires_at + sweeper 还可能把头像误判为孤儿并物理删除。

修复

先停 sweeper、expiry 读写/query/config,再盘点四类 owner、修引用、读取时解析头像 URL;安全后清理旧 schema/triggers。

原理

先止住继续损失,再把持久对象身份与浏览器当次访问 URL 分离。

老数据

对象仍在即可修;对象已被删除只能用权威 seed asset 恢复或重传。

MIA-278Design

头像上传走错 fork 专用方案

根因

v0.0.2 止血端点分裂 Web/CLI,额外白名单挡住 GIF,触发器依赖脆弱字符串相等。

修复

回归 ordinary upload;写入 durable reference,读取走 avatar resolver,兼容期后删除专用端点。

原理

上传负责对象身份,读取负责访问适配,生命周期不再靠头像专用补丁。

老数据

合法旧对象继续可用;错误引用与 MIA-277 共用同一幂等 repair。

一个容易混淆的纠正:MIA-278 没有新建头像专用表。实际增加的是 1 个 PL/pgSQL 函数和 4 个数据库触发器;方案确实不理想,但问题形态不是“多了一张表”。
02 / Flow

它到底怎样工作

点击任一节点查看责任边界。下载与预览共用授权入口,但使用不同 intent;头像上传与头像显示也不是同一个 URL 生命周期。

旧链路为什么会坏

  • Renderer API fetch 有 bearer,原生下载 / 图片元素没有。
  • 一个鉴权 URL 被同时当成预览、下载和持久引用。
  • 没有明确 attachment disposition,文件名可能退化。
  • 头像生命周期依赖字符串触发器,仍可能被 sweeper 删除。

新链路为什么稳

  • 先走一次 authoritative authorization,再 mint 窄票据。
  • load intent 与 download intent 分开。
  • 数据库只保存无 TTL 的对象身份;60 秒只让票据过期,不删除对象。
  • JuiceFS、CloudFront、S3 对客户端暴露相同字段 contract。
03 / Data

现有用户数据会怎样

“停 Sweeper”是保护操作,不是清库操作;真正的数据变更只有可证明安全的头像引用 repair。JuiceFS 文件不需要搬家。

现有数据形态本次操作移动对象?结果
任意历史普通附件,row/object 存在不迁移;点击时签发 fresh capability可下载,恢复原文件名
合法 raw avatar reference保持不变;读取时 resolver可渲染
/api/attachments/{id}/download,row/object 都存在幂等改回 durable reference可渲染;重复执行无副作用
attachment row 或 object 缺失不伪造成功;输出 owner/id/损失类型权威默认资产恢复或用户重传
已有 expires_at先停 sweeper,惰性列暂留不再继续按时间删除
旧函数 / 触发器 / 索引 / expiry schema生产盘点、旧 Server 退出后由本工作流做 Phase 2 cleanup结构清理不阻塞行为修复
下载附件不需要数据迁移。
历史头像需要小范围、幂等、可审计 repair。
JuiceFS不搬文件、不换存储、不强制开 CDN。
Schema先停行为;物理删列/触发器后做,但仍归本工作流。
04 / Rollout

一个 v0.0.4,五个闸门

版本是一份产品交付;闸门是部署和迁移的先后依赖。闸门让失败可以在局部回滚,不代表要让用户升级五次。

G0

只读盘点与恢复证据

产出四项精确统计:expiry 风险集合、其中头像引用、四类 owner 的错误 URL、对应 row 已缺失数量;核对默认助手 seed asset,在生产形状快照演练 repair。

G1

先停止继续损失

当前交付直接停 staged sweeper,删除 expiry query,停止全部 expiry 读写并移除 TTL 配置;不先做 destructive migration,并验证旧 rows/objects 未被修改。

G2

Server 能力与数据修复

发布下载 capability、forced disposition、avatar resolver/route;运行幂等 repair。旧鉴权下载端点与专用头像端点暂作兼容/回滚入口。

G3

客户端收敛

Web/Desktop 获取 fresh download intent;头像控件和 CLI 回归 ordinary upload,补 GIF。部署顺序是 Server → Web → Desktop,并保留双向版本错位兼容。

G4

全矩阵验收与清理决定

覆盖三个 Issue、四类 owner、四端、JuiceFS 与文件类型矩阵。旧 Server 退出且数据闸口通过后,本工作流执行 expiry schema/trigger cleanup;客户端兼容窗口结束后再删旧 route。

可以同一次产品发版完成的是:停继续删除、下载修复、头像 resolver、老数据 repair、客户端回归普通上传和 GIF。Phase 2 schema cleanup 也归本工作流,但必须等旧 Server 退出;不能靠“版本号相同”跳过数据盘点、Server-first 顺序和旧 Desktop 兼容窗口。
05 / Ownership

MIA-284 已正式把生命周期轴交过来

三个目标 Issue 自身没有 Agent/Squad run;MIA-284 Agent 确实存在,但交接信明确说明它只做授权与绑定,不再碰生命周期、URL 和头像。

MIA-279:in_progress、urgent、未分配。
MIA-278:in_progress、未分配。
MIA-277:in_progress、分配给 member,不是 Agent。

本方案接受完整移交:负责 staged expires_at、sweeper/query/config、迁移 245 function/triggers、URL、下载、头像与 Phase 2 schema cleanup。MIA-284 只负责移除附件级读授权、移除 Issue 绑定 uploader 条件、增加未绑定 id 告警。

两边唯一共享热点 attachmentRequiresCreatorScope 也已切清:MIA-284 只摘掉授权调用,本方案决定并清理 URL 用途。孤儿 blob 不按时间自动删除,attachment_latest_head 不按同名合并。

06 / QA

什么才算真的修好

不是单测里调用了一次 anchor.click 就算完成;必须在真实 Web / Electron 下载与生产形状数据上证明。

1

JuiceFS/proxy 下 Web 与 Desktop 可下载 PNG、GIF、Markdown、PDF、binary;大文件不进 JS 内存,Range/206 正常。

2

保存原文件名,图片明确下载而不是只预览;Desktop 不再得到 download.txt

3

旧附件任意时刻点击都能拿新票据;60 秒只让旧票据过期,不让附件过期。

4

默认广告分析助手在冷启动和旧库升级后都有头像,损失对象使用权威 seed asset 恢复。

5

agent/user/workspace/squad 头像可显示,GIF 可动;绑定的 Work/Issue 私有图不能借 avatar route 公开。

6

Gate 1 后没有新的定时删除;repair 只修改 row/object 均存在、证据完备的引用。

7

新客户端不再调用专用头像端点;旧客户端兼容窗口、支持矩阵与回滚方案均有记录。

8

Web、Desktop、Mobile Web/PWA 与 shared frontend 的 malformed response / old Server fallback 都通过。

07 / Architecture

分端架构影响

Daemon 协议不变,JuiceFS 不替换。主要变化集中在 Server URL 能力、共享下载 hook、头像读写边界与生命周期止血。

Layer状态v0.0.4 后的责任
Webchanged点击时换 fresh download intent;头像消费统一 resolver 返回值。
DesktopchangedRenderer 传 fresh URL;既有 Electron native download bridge 继续流式下载,不新增文件名 IPC 真相源。
Mobile Web / PWAchanged复用 Web 下载和 native avatar load;没有单独 native app。
Shared frontendchangedoptional schema 字段、统一下载 hook、ordinary avatar upload 与旧 Server fallback。
Serverchanged授权后 mint capability、forced disposition、avatar resolver/route;移除 sweeper、expiry query/读写/config。
Databasechanged · schema/data幂等修复 avatar_url;不移动对象;Phase 2 删除 expiry 列/索引、函数与四个触发器。
Daemon / CLI / Runtimechanged · CLI onlydaemon/runtime/protocol 不变;CLI 头像回归 ordinary upload 并支持 GIF。
Deployment / externalchanged保证签名 secret 稳定;保留 JuiceFS,不要求 CloudFront 或更换对象存储。
Cross-cuttingchanged明确 TTL、不持久化、intent、已安装客户端兼容、回滚与历史数据证据。
Upstream 方向:按模块 port bdae0d2a04345bf56702e8e6c35607209de7 的最终能力,不盲目 cherry-pick。结果应净减少 Rollica fork 差异。
08 / Decision

建议批准单版方案,按 G0 → G4 开始实现。

默认不把三个 Issue 拆成多个产品版本;也不为了在同一天物理删除旧 route 或数据库列,而牺牲已安装 v0.0.2 客户端和历史数据安全。

ONE VERSION
ORDERED GATES
NO DATA FICTION