289 lines
24 KiB
Markdown
289 lines
24 KiB
Markdown
# Phase 2 审查(T-201 ~ T-204:清单接入 / 本地状态 / 虚拟列表 / 详情与图标缓存)
|
||
|
||
> 审查范围:`2e21c9f`(T-201 catalog 接入)、`819b1cd`(T-202 本地状态识别)、`db8d93e`(T-203 虚拟列表)、`a52acbf`(T-204 详情与图标缓存)。
|
||
> 审查视角:全栈开发工程师。
|
||
> 审查方式:静态代码审计 + 对照 Phase 0/1 遗留项核验。因 WSL 无 Go,未执行测试。
|
||
> 附带确认:Phase 0 的 P1(workspace 隔离,T-604)与 Phase 1 的 P0(Windows 路径,T-605)是否真正闭合。
|
||
> 日期:2026-07-17
|
||
|
||
## 总体判断
|
||
|
||
Phase 2 质量稳定,延续了 Phase 1 的工程水准。**Phase 0 与 Phase 1 审查提出的两个结构性问题已被真正修复并接线**(见"遗留项闭合确认")。Phase 2 本身无安全级缺陷,主要是若干性能与可维护性优化项。M3 前半(清单→列表→详情)成立。
|
||
|
||
## 遗留项闭合确认(重要)
|
||
|
||
| 遗留项 | 状态 | 证据 |
|
||
| --- | --- | --- |
|
||
| Phase 1 P0 · Windows dot-space 路径逃逸 | **已闭合** | 新建 `core/internal/safepath`:`validateSegment` 逐段拒绝首/尾 ASCII 空格、尾随句点、DOS 设备名(含 `COM¹²³` 变体)、控制字符与 `<>:"\|?*`;`JoinUnder` 在 `filepath.Join` 后用 `filepath.Rel` 做 destination 包含性兜底。`extractor.go` 已改用 `safepath.ValidateRelative` + `JoinUnder`(不再只靠 POSIX `path.Clean`);`parser.go`、`storage/installed_app.go` 共用同一校验器,无各层漂移。两条整改建议(逐段校验 + 包含性兜底)都落地。 |
|
||
| Phase 0 P1 · 根 workspace Gio 版本污染 | **已闭合**(T-604) | 见 `7efcab5 Isolate modern and Win7 workspaces`(需在该任务范围单独确认,非本次重点) |
|
||
|
||
## 逐模块核验
|
||
|
||
### T-201 · 清单接入(catalog/parser + filter + client)
|
||
|
||
- `parser.go` 严格:`DisallowUnknownFields`、拒绝尾随 JSON、拒绝重复 app id、signature 必须 strict Base64 且解码为 64 字节、URL 校验。签名验证已在 T-101 闭环,parser 作为 `DocumentValidator` 接入 loader(Phase 1 已确认 `TestClientValidatesBeforeReplacingCache`)。
|
||
- `filter.go`:**channel 交叉拒绝正确**——`manifest.Channel != target.Channel` 直接整份拒绝,Win7 客户端不会消费 modern 清单;`hidden` 跳过,`deprecated`/`min_os` 不满足/无对应架构包的项**保留可见并带 Reason、不可安装**,符合"不兼容软件可见说明但不可下载"。
|
||
- `supportsOS` 用 rank map(7/10/11),高版本系统可装低 min_os 应用,正确。
|
||
- `entry.Package = &publishedPackage`:`publishedPackage` 是循环体内 `:=` 新变量,逐次地址独立,无 Go 循环变量取址陷阱。
|
||
|
||
### T-202 · 本地状态识别(domain/semver + status_resolver + storage/installed_app)
|
||
|
||
- `semver.go` 符合 SemVer 2.0.0:prerelease 数值/字母混合比较、数值 < 字母、较短 prerelease 优先、build 元数据不参与排序、prerelease 数值前导零拒绝。`compareNumericText` 用"长度优先再字典序"比较数值文本,规避大版本号整数溢出,且因核心标识拒绝前导零而正确。
|
||
- `status_resolver.go`(Phase 0 复核已见):按"恢复事务→活跃操作→运行→不兼容→是否安装→是否有更新"从 facts 推导单一可见状态,IO-free。真实的状态权威在此,`allowedTransitions` 迁移表仍是并行的测试脚手架(Phase 0 复核已记,待收口)。
|
||
- `installed_app.go` 共用 safepath 校验文件路径。
|
||
|
||
### T-203 · 虚拟列表(application/CatalogListModel + 两个 Gio 适配)
|
||
|
||
- **共享 ViewModel 纪律落地**(回应 Phase 0 P2):`core/application.CatalogListModel` 持有 items/visible/categories/query/category/view/selectedID,全部 IO-free 纯内存;搜索、单分类、all/installed/updates 视图、选中态都在 core。
|
||
- **控件状态按 app ID 保存**(架构硬约束满足):`shell.rows map[string]*rowControls`、`shell.icons map[string]paint.ImageOp`、`categoryControls map[string]*widget.Clickable` 均以稳定 ID/分类名为键;`SetItems` 按 `item.ID` 保留既有 `rowControls`,列表过滤/重排时点击态不串行。
|
||
- Gio 侧用惰性 `layout.List`;Layout 内 grep 无 `os.`/`http.`/`sha256`/`ReadFile`,确认无 IO。
|
||
|
||
### T-204 · 详情与图标缓存(catalog/icon_cache + Gio 详情)
|
||
|
||
- `icon_cache.go` 安全性对齐下载/清单缓存:
|
||
- `cacheKey` 要求 `sha256:` 前缀 + 校验 hex 解码/长度/DPI 范围(48–768);文件名 `<64hex>-<dpi>.icon` 由已验证 hex + int 拼成,无路径分隔符,无穿越面。
|
||
- 内存→磁盘→远端;磁盘与远端字节**都重新 SHA-256 复验 + 尺寸/大小复核**,不盲信磁盘。
|
||
- **图片解压炸弹防御**:`DecodeConfig` 先取宽高并按 2048 上限拒绝,**再**做完整 `image.Decode`,阻止"小文件声明巨大尺寸";输入 2 MiB 上限。
|
||
- 磁盘 Lstat 拒绝符号链接/非普通文件;原子写(temp+chmod 0600+sync+rename);损坏普通缓存删除重取。
|
||
- Gio 详情右栏只读 `SelectedItem()` 与内存 `paint.ImageOp`,关闭详情不清筛选/列表位置。
|
||
|
||
## 需优化项(按优先级)
|
||
|
||
### P1 · 图标缓存的 mutex 横跨网络 fetch,拖累滚动吞吐
|
||
|
||
**现状:** `IconCache.Load` 全程 `cache.mu.Lock()` + `defer Unlock`,且在锁内调用 `cache.fetcher.FetchIcon(ctx, request)`(网络 IO)。
|
||
|
||
**影响:** 任意一个图标的远端拉取在飞行中时,**所有**其他 `Load`(哪怕是内存命中)都阻塞在同一把锁上。数百项列表滚动时按需拉图标会形成 convoy,与"数百项流畅滚动"验收目标直接冲突。当前实现用全局串行换取了"同一图标不重复拉取",但牺牲了不同图标的并行度——对网络受限的图标加载,并行度通常更重要。
|
||
|
||
**建议:** 锁内只做内存/磁盘查与内存写,fetch 在锁外(check→release→fetch→re-acquire→store);或用 per-digest 锁 / `singleflight`(注意保持 core 的 Go 1.20 兼容)。
|
||
|
||
### P2 · 两个 Gio shell 的并行布局代码持续翻倍
|
||
|
||
**现状:** T-203 给 `app-modern/ui/gio/shell.go` 加了 712 行、`app-win7` 加了 626 行;modern shell 已 922 行。共享 ViewModel 覆盖的是**逻辑**,布局/控件代码仍是两份平行实现。
|
||
|
||
**影响:** modern(Gio v0.10.1)与 win7(Gio v0.6.0)API 有差异,平行是既定取舍;但每新增一个视图(详情、下载、设置、授权),这 600+ 行平行代码就再翻一倍,且两份必须手动保持行为一致,漂移风险随视图增加上升。
|
||
|
||
**建议:** 不强行共享 Gio 控件代码,但补一层**跨适配的行为一致性测试**(对同一 `CatalogListModel` 状态断言两个 shell 的可见项/选中项/视图切换语义一致),成本低、能挡住漂移;长期可评估数据驱动的布局描述层(纯数据、非 Gio)减少平行量。
|
||
|
||
### P3 · 多标签筛选:需求与实现口径不一致
|
||
|
||
**现状:** `CatalogListModel` 提供单分类过滤 + 标签并入搜索(`matchesQuery` 含 Tags),但没有"多标签筛选"。`docs/routes.md` 写"分类和多标签筛选",而 `02-requirements.md` 表格把标签定位为搜索项。
|
||
|
||
**影响:** 文档内部口径不一,实现满足 requirements 表但不满足 routes 的"多标签筛选"。
|
||
|
||
**建议:** 先裁定多标签筛选是否属 MVP:若是,补 `CatalogListModel` 的多标签过滤;若否,改 `routes.md` 表述与实现对齐。不要留文档-实现二义。
|
||
|
||
### P4 · `VisibleItems()` 返回可被 refilter 复用的切片
|
||
|
||
**现状:** `refilter` 用 `model.visible = model.visible[:0]` 复用底层数组,`VisibleItems()` 直接返回 `model.visible`,注释称"immutable-by-convention"。
|
||
|
||
**影响:** 单线程 UI 下每帧现取现用,安全;但任何调用方若跨一次 `refilter` 持有该切片,底层数组会被 append 覆盖,是隐性 footgun。
|
||
|
||
**建议:** 返回拷贝,或把"不得跨 SetQuery/SetView/SetItems 持有"写成硬约束注释并在测试固化。
|
||
|
||
### P5 · shell.go 单文件 922 行,随视图增长将更臃肿
|
||
|
||
**现状:** 搜索、分类条、虚拟列表、详情右栏都在一个 `shell.go` 的 Layout 链里。
|
||
|
||
**建议:** 视图增多前,按视图拆文件(list/detail/category),降低单文件复杂度与两适配漂移面。优先级低。
|
||
|
||
### P6 · 图标磁盘缓存遇 symlink 硬失败而非自愈
|
||
|
||
**现状:** `loadDisk` 遇符号链接返回 `ErrIconCacheUnsafe`,`Load` 对该错误早返回、不删除、不回退远端。
|
||
|
||
**影响:** 被植入的 symlink 缓存项会**永久**卡住该图标直到人工清理。相较普通损坏缓存"删除重取"的自愈,这里选择了保守硬失败。
|
||
|
||
**建议:** 保守是可辩护的(不自动删可疑文件),但至少记一条明确告警/诊断,便于运维定位;或对 symlink 缓存项也走"隔离到 .quarantine 后回退远端"。
|
||
|
||
## 结论与处理顺序
|
||
|
||
Phase 2 可作为后续下载/安装 UI 接入的可信基线。建议顺序:
|
||
|
||
1. **[性能 · 影响验收]** P1:图标缓存 fetch 移出锁,消除滚动 convoy。
|
||
2. **[防漂移 · 低成本]** P2:补跨适配行为一致性测试。
|
||
3. **[消除二义]** P3:裁定多标签筛选归属并对齐文档/实现。
|
||
4. **[健壮性]** P4/P6:VisibleItems 拷贝或硬约束;图标 symlink 缓存告警/隔离。
|
||
5. **[可维护性 · 低优先]** P5:shell.go 按视图拆分。
|
||
|
||
> 声明:本报告为静态审计;P1 的吞吐影响、两适配行为一致性建议在真实 Windows Gio 运行与 `go test` 下验证。Phase 0 P1(T-604)与 Phase 1 P0(T-605)已确认接线闭合,但其各自任务范围的完整验收以对应任务复核为准。
|
||
|
||
## Codex 全栈二次复核
|
||
|
||
> 复核视角:Codex 全栈开发工程师。
|
||
> 复核方式:对照 T-201~T-204 任务边界、当前实现调用点、现行需求/路由文档和双 workspace 测试逐项核验。
|
||
> 复核基线:`6fd19d0`。
|
||
> 日期:2026-07-17
|
||
|
||
### 复核结论
|
||
|
||
原审核的总体判断成立:Phase 2 没有阻断后续开发的功能或安全缺陷,T-201~T-204 可以作为后续下载/安装 UI 接入的基线;T-604/T-605 的代码级整改也已经接线闭合。
|
||
|
||
但原整改清单不宜原样落任务:
|
||
|
||
1. P1 的并发问题真实,但当前 `IconCache` 尚未接入生产装配,不能直接认定已经破坏列表滚动验收。
|
||
2. P2/P4/P5 的现象成立,实施方式需要兼顾双 Gio 隔离与每帧分配成本。
|
||
3. P3 引用了已经不存在的文档冲突,应撤销。
|
||
4. P6 是可辩护的 fail-closed 安全策略,不应定性为 Phase 2 缺陷。
|
||
5. 原审核遗漏了远端响应读取上限和 Gio UI 线程投递两个正式接入前必须冻结的约束。
|
||
|
||
### 对原优化项的修订
|
||
|
||
#### P1 · 接受问题,修正影响范围与落地方案
|
||
|
||
`IconCache.Load` 当前从 `cache.mu.Lock()` 到返回全程持锁,锁内包含磁盘读取、图片验证、`FetchIcon` 网络调用、磁盘写入和内存更新。不同 digest/DPI 的请求因此也会互相阻塞,慢请求还会阻塞本应很快的内存命中。该并发设计需要修复。
|
||
|
||
但当前生产代码没有 `IconCache.Load` 调用者,仅测试直接调用;T-204 也明确把真实网络请求装配留给后续任务。图标未命中时 UI 使用内存占位,Layout 不等待 `Load`。因此准确结论是:
|
||
|
||
- 这是正式图标后台加载接入前必须关闭的吞吐风险。
|
||
- 它尚不是已经复现的“滚动卡顿”或 Phase 2 验收失败。
|
||
|
||
建议使用按 cache key 的 in-flight 去重:全局 mutex 只保护 memory/in-flight map,不同 key 的磁盘和网络工作可以并发;同一 key 只允许一个 leader 获取并让等待者复用结果。不要只做简单的 check→unlock→fetch→lock→store,否则同一图标仍会重复下载和竞争写盘。
|
||
|
||
并发测试至少覆盖:
|
||
|
||
- 慢 key 不阻塞另一 key 的内存命中。
|
||
- 不同 key 可以并行获取。
|
||
- 同一 key 并发只获取一次。
|
||
- leader 失败、context 取消和磁盘写 warning 能正确传播,且不会遗留永久 in-flight 状态。
|
||
|
||
#### P2 · 部分接受,测试应覆盖适配器接线而非重复 ViewModel 语义
|
||
|
||
两个 `shell.go` 的平行实现和持续膨胀属实,modern/win7 的行为漂移风险存在。但筛选、可见项、选中项和视图切换的核心语义已经由共享 `core/application.CatalogListModel` 决定并在 core 测试覆盖;把相同 ViewModel 断言再对两个 shell 各跑一次,不能有效发现 Gio 事件接线漂移。
|
||
|
||
更有价值的做法是建立一组适配器交互契约,分别在 modern 与 win7 包中复用同一用例矩阵,验证:
|
||
|
||
- 搜索、分类、视图按钮和清空筛选事件实际更新共享 model。
|
||
- 行点击打开正确 app ID,关闭详情保留筛选和列表上下文。
|
||
- 500 项有限 viewport 仍只布局可见子集。
|
||
- row controls 按 app ID 保持,删除的 app/category controls 被释放。
|
||
- 空 Catalog 与过滤无结果保持不同恢复语义。
|
||
|
||
测试辅助数据可以共享,但不要让 modern workspace import win7 Gio,也不要为了测试重新合并两个不兼容的 Gio 版本。
|
||
|
||
#### P3 · 撤销:现行文档不存在多标签筛选冲突
|
||
|
||
现行文档口径一致:
|
||
|
||
- `docs/02-requirements.md` 定义“按名称/标签搜索,按分类和全部/已安装/可更新视图筛选”。
|
||
- `docs/routes.md` 明确记录“多标签复选随对应用例后续接入”。
|
||
- `docs/tasks/T-203.md` 的边界明确“不实现多标签复选”。
|
||
|
||
当前实现满足 Phase 2 已冻结范围。除非产品后来把多标签复选提升为 MVP,否则无需修改代码或文档,P3 不应进入整改任务。
|
||
|
||
#### P4 · 接受 API footgun,但不建议每帧无条件返回拷贝
|
||
|
||
`VisibleItems()` 暴露 `model.visible`,而 `refilter()` 用 `model.visible[:0]` 复用 backing array。调用者若跨一次 refilter 保存旧切片,内容可能被覆盖;调用者也能直接改写当前切片元素。这是导出 API 的生命周期风险。
|
||
|
||
不过两个 Gio Layout 每帧都会读取 `VisibleItems()`,若每次访问都复制数百项,会引入与性能目标相反的持续分配。建议二选一:
|
||
|
||
1. `refilter` 每次构造新的 snapshot backing array并原子替换,访问方约定只读;旧 snapshot 可安全保留到下一帧之后。
|
||
2. 提供 `VisibleLen()` + `VisibleItem(index)` 只读索引接口,Layout 不再持有内部切片。
|
||
|
||
无论采用哪种方式,都应补“跨 refilter 持有旧快照不会变化”或“调用者无法取得内部切片”的测试。
|
||
|
||
#### P5 · 接受为低优先级维护项
|
||
|
||
按 list/detail/category 或 header/content/detail 拆分同 package 文件有助于降低单文件复杂度和双适配 diff 噪声,且不会破坏 Gio 版本隔离。建议在新增下载队列、授权或设置视图前完成,但不需要为了纯文件拆分阻断当前功能任务。
|
||
|
||
#### P6 · 改为安全策略与诊断观察项
|
||
|
||
对 symlink/非普通缓存项返回 `ErrIconCacheUnsafe` 并停止回退,是明确的 fail-closed 策略:代码不会跟随可疑链接读取或写入攻击者选择的位置。自动删除、rename 或隔离可疑对象需要额外处理 Windows reparse point、跨进程竞争和 TOCTOU,不能默认比硬失败更安全。
|
||
|
||
正式装配日志/错误 UI 时应把 `ErrIconCacheUnsafe` 转成可定位的诊断和人工清理指引。只有产品明确接受自动隔离策略并补齐攻击测试后,才考虑 quarantine;P6 不作为 Phase 2 必修复代码缺陷。
|
||
|
||
### 原审核遗漏的接入风险
|
||
|
||
#### 新 P1 · 2 MiB 校验不能限制 Fetcher 已经读取和分配的响应
|
||
|
||
`IconFetcher.FetchIcon` 返回完整 `[]byte`,`IconCache.validate` 在 Fetch 完成后才检查 `len(document)`。这可以阻止超大对象进入缓存,却不能阻止未来 HTTP Fetcher 先下载并在内存中分配任意大响应。原审核把它概括为“输入 2 MiB 上限”不够准确。
|
||
|
||
正式网络 Fetcher 必须在读取阶段同时执行:
|
||
|
||
- 拒绝明显超过上限的 `Content-Length`。
|
||
- 用 `io.LimitReader(maxBytes+1)` 或等价有界读取处理缺失/伪造的长度头。
|
||
- 读到 `maxBytes+1` 即返回资源超限,不得把完整大响应交给 `IconCache`。
|
||
|
||
该约束应与 P1 并发整改一起进入正式图标加载任务。
|
||
|
||
#### 新 P2 · `ApplyIcon` 必须在 Gio UI 事件循环中执行
|
||
|
||
modern/win7 的 `ApplyIcon` 都直接写 `shell.icons` map,Layout 同时读取该 map,当前没有 mutex。现阶段只有测试在同一 goroutine 调用,没有数据竞争;未来若后台加载 goroutine 直接调用 `ApplyIcon`,会产生 map race,并且不会自动遵守 Gio 的 UI 更新模型。
|
||
|
||
正式接入必须保持现有架构硬约束:后台任务只发布完成事件;UI goroutine 在 drain/ApplyEvent 阶段执行 `DecodeIcon` 之外的 model/ImageOp 更新并调用 `Window.Invalidate`。应增加 `go test -race` 可执行环境下的事件投递测试或等价的单线程契约测试,禁止把 `ApplyIcon` 宣称为可从任意 goroutine 调用的线程安全 API。
|
||
|
||
### 验证补充
|
||
|
||
原审核因 WSL 无 Go 未执行测试。本次在 Windows 环境按隔离 workspace 补跑并通过:
|
||
|
||
- Go 1.20.14、`GOWORK=off`:`go -C core test -count=1 ./catalog ./application ./domain ./storage`。
|
||
- Go 1.25.0、根 `go.work`:`go -C app-modern test -count=1 ./ui/gio`。
|
||
- Go 1.20.14、`app-win7/go.work`:`go -C app-win7 test -count=1 ./ui/gio`。
|
||
|
||
测试通过证明当前覆盖范围内实现一致,不能替代未来 IconFetcher 并发/有界读取测试、UI 线程投递测试或真实 Windows 滚动性能测量。
|
||
|
||
### 修订后的处理顺序
|
||
|
||
1. **[正式图标接入前 · 最高]** `IconCache` 改为按 key in-flight 去重,网络/磁盘工作移出全局锁;HTTP Fetcher 在读取阶段执行硬大小上限;补并发、取消和有界读取测试。
|
||
2. **[正式图标接入前 · 契约]** 后台结果通过事件队列回到 Gio UI goroutine 后执行 `ApplyIcon`/Invalidate,禁止后台 goroutine 直接改 shell map。
|
||
3. **[防漂移]** 为 modern/win7 补适配器交互契约测试,重点验证 Gio 事件接线、虚拟化和上下文保持,不重复证明共享 ViewModel 的纯逻辑。
|
||
4. **[API 健壮性]** 修正 `VisibleItems` 快照生命周期,避免每帧无条件复制。
|
||
5. **[可维护性 · 低优先]** 在继续增加视图前按职责拆分两个 `shell.go`。
|
||
6. **[观察项]** 为 `ErrIconCacheUnsafe` 增加上层诊断/人工清理指引;是否自动 quarantine 另行做威胁模型裁定。
|
||
|
||
> 二次复核裁定:保留 Phase 2“可信基线”的总体结论;接受原 P1/P2/P4/P5 的问题事实但修订定级或落地方式;撤销原 P3;把原 P6 降为安全策略观察项;新增远端有界读取和 UI 线程投递两项正式接入门槛。当前不需要回滚或重做 T-201~T-204。
|
||
|
||
## 交叉复核裁定(定稿)
|
||
|
||
> 复核视角:Claude 全栈开发工程师,对 Codex 二次复核逐条核验后裁定。
|
||
> 核验方式:重读当前 `routes.md`/`T-203.md`/`02-requirements.md` 措辞;对照 `icon_cache.go` 的 `validate` 位置、`shell.go` 的 `ApplyIcon`/`icons` map 读写点、`SetItems` 剪枝范围、`IconCache.Load` 生产调用者确认技术主张;因 WSL 无 Go,未执行测试。
|
||
> 日期:2026-07-17
|
||
|
||
### 承认原审核错误
|
||
|
||
本轮 Codex 复核比原审核更准。原审核有一处 stale 误判与两处不精确表述,应更正:
|
||
|
||
- **原 P3(多标签筛选文档冲突)撤销——原审核错。** 当前文档口径一致:`routes.md` 第 26 行"多标签复选随对应用例后续接入"、`T-203.md` 边界"不实现…多标签复选"、`02-requirements` 把标签定位为搜索项。原 P3 依据的是最初 routes.md 版本的记忆,**未重读当前文档**;Codex 在 T-203 已将其更新为"后续接入"。不存在文档-实现二义,P3 不进整改。
|
||
- **原 T-204 "输入 2 MiB 上限" 不精确。** `validate` 的 `len(document) > maxBytes` 在 `FetchIcon` 返回完整 `[]byte` **之后**执行,只界定"进缓存",不界定 fetch 内存。
|
||
- **原 T-204/P1 "ApplyIcon 从后台调用" 表述有误且危险。** `ApplyIcon` 写 `shell.icons`、Layout 读同 map、无 mutex;当前仅测试同 goroutine 调用,无生产调用者。正确契约是后台发事件、UI goroutine 应用,不得从任意 goroutine 调 `ApplyIcon`。
|
||
- **原 P1 "已直接冲突流畅滚动验收" overstate。** `IconCache.Load` 无生产调用者(仅测试),应定级为"正式图标接入前必须关闭",非"已破坏验收"。
|
||
|
||
### 已确认的事实(可作为后续任务前提)
|
||
|
||
| 项 | 结论 | 证据 |
|
||
| --- | --- | --- |
|
||
| 多标签筛选文档一致 | 属实(P3 撤销) | `routes.md:26` / `T-203.md:50` / `02-requirements.md:28` 三者一致,多标签复选明确后续接入 |
|
||
| 图标大小上限只界定 cache 入口 | 属实(NEW P1) | `validate` 在 `FetchIcon` 返回完整 `[]byte` 后才查 `len`;正式 HTTP Fetcher 须 Content-Length 拒绝 + `io.LimitReader(maxBytes+1)` |
|
||
| ApplyIcon/icons map 无锁 | 属实(NEW P2) | `shell.go:98` 写、`shell.go:514` 读,无 mutex;仅测试调用,无生产调用者 |
|
||
| IconCache 未接生产 | 属实 | `IconCache.Load` 仅测试调用,T-204 把网络装配留后续 |
|
||
| 内存缓存无界(双方原漏) | 属实(新增) | `IconCache.memory` 无 delete/LRU/上限;`SetItems` 重建 rows/categories 但**不剪枝** `shell.icons`,移除的 app 图标永久滞留 |
|
||
|
||
### 采纳 Codex 的修订与 sharpen
|
||
|
||
- **接受** 原 P3 撤销、NEW P1(远端有界读取)、NEW P2(UI 线程投递契约)。
|
||
- **接受** P1 落地方案:按 cache key 的 in-flight 去重(全局锁只护 memory/in-flight map,不同 key 磁盘/网络并发,同 key 单 leader),优于 naive check-unlock-fetch-lock-store(后者同图标会重复拉取)。
|
||
- **接受** P2 sharpen:适配器**交互契约测试**(验证 Gio 事件真更新 model、行点击开对 app ID、viewport 虚拟化、controls 按 app ID 保持),而非把共享 ViewModel 语义再断言一遍。
|
||
- **接受** P4 sharpen:`refilter` 构造新快照原子替换(筛选变化时分配),而非每帧返回拷贝。
|
||
- **接受** P6 收紧:fail-closed + 诊断为默认;自动 quarantine 需先做威胁模型裁定(reparse point / TOCTOU / 跨进程竞争),不默认更安全。
|
||
|
||
### 新增(双方原漏):无界内存缓存
|
||
|
||
- `IconCache.memory` 无淘汰;`shell.icons` 在 `SetItems` 不按当前 items 剪枝。数百款 × 每图标最大 2 MiB,内存只增不减(典型图标数十 MB,极端可更高)。
|
||
- 建议:内存层加容量/LRU 上限;`SetItems` 按当前 items 剪枝 `shell.icons`。与 P1 图标接入整改合并处理。
|
||
|
||
### 最终处理顺序(定稿)
|
||
|
||
1. **[正式图标接入前 · 最高]** `IconCache` 按 key in-flight 去重 + 网络/磁盘移出全局锁;HTTP Fetcher 读取阶段 Content-Length 拒绝 + `io.LimitReader` 硬上限;**内存层加容量/LRU 上限,`SetItems` 剪枝 `shell.icons`**;补并发/取消/有界读取/内存上限测试。
|
||
2. **[正式图标接入前 · 契约]** 后台结果经事件队列回 UI goroutine 后再 `ApplyIcon`/`Invalidate`,禁止后台直接改 shell map;补 `go test -race` 或等价单线程契约测试。
|
||
3. **[防漂移]** modern/win7 适配器交互契约测试,验证 Gio 事件接线/虚拟化/上下文保持,不重复证明共享 ViewModel。
|
||
4. **[API 健壮性]** `VisibleItems` 快照生命周期:refilter 原子替换新快照,避免每帧复制。
|
||
5. **[可维护性 · 低优先]** 增视图前按职责拆两个 `shell.go`。
|
||
6. **[观察项]** `ErrIconCacheUnsafe` 上层诊断/人工清理指引;自动 quarantine 另做威胁模型裁定。
|
||
|
||
> 裁定:本轮 Codex 复核成立且优于原审核——撤销原审核 stale 的 P3、纠正两处不精确表述、sharpen 三处落地方案。保留 Phase 2"可信基线"结论,T-201~T-204 不需回滚;新增"内存层无界"一项并入处理顺序第 1 步。
|
||
|
||
## 任务落地追踪
|
||
|
||
- `T-606` 已完成最终处理顺序第 1 项:按 key in-flight 去重、Fetcher 流式有界读取、32 MiB/256-key memory LRU 与 modern/win7 `shell.icons` 剪枝已实现并通过完整双 workspace 闸门。
|
||
- `T-607` 已完成最终处理顺序第 2 项:后台图标 Load/Decode 只发布强类型 application event,有界 FIFO relay 无损背压并请求重绘,由 Gio Frame/UI goroutine drain 后执行 `ApplyEvent`/`ApplyIcon`;最新请求、删除 app、IconRef/DPI 变化和取消都阻断迟到结果回写。core 与双 workspace 定向测试及完整闸门通过。
|
||
- 适配器交互契约、`VisibleItems` 快照、`shell.go` 拆分和 unsafe cache 诊断尚未编号;下一任务从双适配器交互契约开始,继续按顺序串行落成。
|