24 KiB
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 接入的可信基线。建议顺序:
- [性能 · 影响验收] P1:图标缓存 fetch 移出锁,消除滚动 convoy。
- [防漂移 · 低成本] P2:补跨适配行为一致性测试。
- [消除二义] P3:裁定多标签筛选归属并对齐文档/实现。
- [健壮性] P4/P6:VisibleItems 拷贝或硬约束;图标 symlink 缓存告警/隔离。
- [可维护性 · 低优先] 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 的代码级整改也已经接线闭合。
但原整改清单不宜原样落任务:
- P1 的并发问题真实,但当前
IconCache尚未接入生产装配,不能直接认定已经破坏列表滚动验收。 - P2/P4/P5 的现象成立,实施方式需要兼顾双 Gio 隔离与每帧分配成本。
- P3 引用了已经不存在的文档冲突,应撤销。
- P6 是可辩护的 fail-closed 安全策略,不应定性为 Phase 2 缺陷。
- 原审核遗漏了远端响应读取上限和 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(),若每次访问都复制数百项,会引入与性能目标相反的持续分配。建议二选一:
refilter每次构造新的 snapshot backing array并原子替换,访问方约定只读;旧 snapshot 可安全保留到下一帧之后。- 提供
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 滚动性能测量。
修订后的处理顺序
- [正式图标接入前 · 最高]
IconCache改为按 key in-flight 去重,网络/磁盘工作移出全局锁;HTTP Fetcher 在读取阶段执行硬大小上限;补并发、取消和有界读取测试。 - [正式图标接入前 · 契约] 后台结果通过事件队列回到 Gio UI goroutine 后执行
ApplyIcon/Invalidate,禁止后台 goroutine 直接改 shell map。 - [防漂移] 为 modern/win7 补适配器交互契约测试,重点验证 Gio 事件接线、虚拟化和上下文保持,不重复证明共享 ViewModel 的纯逻辑。
- [API 健壮性] 修正
VisibleItems快照生命周期,避免每帧无条件复制。 - [可维护性 · 低优先] 在继续增加视图前按职责拆分两个
shell.go。 - [观察项] 为
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/iconsmap 读写点、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 图标接入整改合并处理。
最终处理顺序(定稿)
- [正式图标接入前 · 最高]
IconCache按 key in-flight 去重 + 网络/磁盘移出全局锁;HTTP Fetcher 读取阶段 Content-Length 拒绝 +io.LimitReader硬上限;内存层加容量/LRU 上限,SetItems剪枝shell.icons;补并发/取消/有界读取/内存上限测试。 - [正式图标接入前 · 契约] 后台结果经事件队列回 UI goroutine 后再
ApplyIcon/Invalidate,禁止后台直接改 shell map;补go test -race或等价单线程契约测试。 - [防漂移] modern/win7 适配器交互契约测试,验证 Gio 事件接线/虚拟化/上下文保持,不重复证明共享 ViewModel。
- [API 健壮性]
VisibleItems快照生命周期:refilter 原子替换新快照,避免每帧复制。 - [可维护性 · 低优先] 增视图前按职责拆两个
shell.go。 - [观察项]
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/win7shell.icons剪枝已实现并通过完整双 workspace 闸门。T-607已完成最终处理顺序第 2 项:后台图标 Load/Decode 只发布强类型 application event,有界 FIFO relay 无损背压并请求重绘,由 Gio Frame/UI goroutine drain 后执行ApplyEvent/ApplyIcon;最新请求、删除 app、IconRef/DPI 变化和取消都阻断迟到结果回写。core 与双 workspace 定向测试及完整闸门通过。T-608已完成最终处理顺序第 3 项:modern/win7 使用除 edition/窗口尺寸外一致的场景矩阵,验证 Editor/Clickable 经 Layout 更新共享 model、重排后 AppID 行身份、详情关闭上下文、500 项 viewport、app/category controls 释放及两类空状态语义;双端定向重复测试与完整闸门通过,生产shell.go无需修正。T-609已完成最终处理顺序第 4 项:refilter在局部新 backing array 完整构造后发布,旧 generation 跨五类公开 model mutation 保持稳定;同 generation 读取共享 backing 且零分配。core 定向重复测试、双 Gio 回归与完整闸门通过,未扩展为并发安全或防御性深拷贝。shell.go拆分和 unsafe cache 诊断尚未编号;下一任务从双端shell.go职责拆分开始,继续按顺序串行落成。