# UtiliVera Duplicates 0.9.0 安装器独立审核（Round 2）

审核日期：2026-08-15  
审核角色：独立安装/发布审核员（未参与本轮实现或构建）  
审核对象：`UtiliVeraDuplicates-Setup-0.9.0-Windows-x64.exe`

## 结论

**安装工程候选：PASS。公开发布：BLOCKED（仅代码签名外部门槛）。**

Round 1 的内部 P1 已有可复核证据关闭：.NET Framework 4.8 前置门禁、安装后的完整产品许可证与第三方许可、构建 10/10 稳定性、持久化安装日志，以及卸载后安装目录/卸载注册项/开始菜单残留检查均已实现并通过。当前没有未关闭的内部 P0/P1。

最终候选为 3,951,536 bytes，SHA-256：

`81654330CE07F634AAF3FD29B742B0E401AF8E5849E4EE13EBF357889DEECA34`

但该安装器和冻结主程序都仍为 `NotSigned`。依据根目录 `AGENTS.md` 的强制门禁，它们不得上线、不得提供公开下载，也不得称为正式发布。签名是外部发布条件，不否定当前安装工程 PASS，但在签名并复验最终字节前，公开发布结论必须保持 BLOCKED。

## 独立复核证据

### 1. 候选身份与冻结载荷

- 当前安装器实测：3,951,536 bytes；SHA-256 与给定候选及 `installer-build-evidence-0.9.0.json` 一致。
- 冻结应用 EXE SHA-256：`5F59977C69A25CCCB9569D1ED3F4F2D8043E2336CCFCD4C14933A623C754D727`。
- 构建证据记录 143 个应用文件逐文件嵌入验证通过，`embeddedManifestVerified=true`，验证第一次即通过，无被重试掩盖的失败。
- `installer-test-evidence-0.9.0.json` 记录安装后主 EXE 哈希与冻结 EXE 完全一致。

### 2. .NET Framework 4.8 前置门禁

`installer/UtiliVeraDuplicates.iss` 的 `InitializeSetup` 同时检查 HKLM 64/32 位 .NET Full Release 值，要求 `Release >= 528040`；缺失时在复制文件前终止并给出 .NET 4.8 下载地址。该项关闭 Round 1 的运行时前置条件缺口。

**判定：PASS。** 后续签名候选仍应在“无 .NET 4.8”的干净 Windows 10 VM 上做一次负向安装截图/退出证据，以补充代码级门禁证据。

### 3. LICENSE 与第三方许可

- Inno `[Files]` 显式安装 `LICENSE.txt` 和 `THIRD-PARTY-LICENSES.txt`。
- smoke 同时断言上述两文件、`THIRD-PARTY-NOTICES.md`、`SharpCompress.dll` 存在。
- `THIRD-PARTY-LICENSES.txt` 包含 SharpCompress、NAudio、Microsoft/.NET 运行库与 Fluent UI System Icons 的组件映射、版权和完整 MIT 文本；产品许可证为完整自包含文本。
- 构建载荷 manifest 和安装后 legal-file 断言共同防止“只在源码存在、安装后丢失”。

**判定：PASS。**

### 4. 构建稳定性与诊断日志

`installer-build-stability-0.9.0.json` 记录连续 10 次请求、10 次通过、每次 exit code 0，且每次产生同一 SHA-256 `816543...CA34`；每次构建均完成嵌入载荷验证。该证据直接关闭 Round 1 的“构建命令红色”问题。

`test-installer.ps1` 把 install/upgrade/uninstall 三份日志复制到 `outputs/installer-test-logs-0.9.0/`。现有文件非空：install 70,407 bytes、upgrade 87,807 bytes、uninstall 36,969 bytes，失败后不再只剩被清理的临时目录。

**判定：PASS。**

### 5. 静默安装、覆盖、功能 smoke 与卸载残留

证据对象与当前候选大小/哈希一致，记录：

- 静默安装 exit 0；
- 已安装 CLI 对两个相同 fixture 返回 2 行，exit 0；
- 同版本覆盖/repair exit 0，主 EXE 哈希未变化；
- 静默卸载 exit 0；
- 安装目录已删除；
- HKCU/HKLM/WOW6432Node 中 `UtiliVera Duplicates*` 卸载注册项已删除；
- 当前用户开始菜单快捷方式已删除；
- 三阶段日志均持久化。

**判定：PASS。** 这些证据证明静默安装、同版本覆盖和干净程序卸载；它们不应被扩张为尚未执行的 N-1 到 N 真实升级矩阵。

## 剩余问题

### P1（外部发布门槛）：安装器与主 EXE 未签名

独立实测两者 Authenticode 均为 `NotSigned`；构建与 smoke 证据也诚实记录 `releaseGate=BLOCKED_NOT_SIGNED`。

**验收标准：**使用受信代码签名证书同时签署冻结主 EXE 与最终 Inno 安装器并加 RFC 3161 时间戳；在干净 VM 上验证签名为 `Valid`；签名后重新冻结大小/SHA-256、逐文件嵌入 manifest、10/10 构建稳定性、静默安装/覆盖/CLI/卸载和三类残留检查。网站只允许引用签名后最终哈希，审核签名后字节前不得开放下载。

### P2-1：用户数据保留策略只在测试证据中披露

测试证据明确说明卸载会保留 `%LOCALAPPDATA%\UtiliVera\Duplicates` 下的 settings、hash cache、quarantine、profiles 和 recovery metadata，以避免数据丢失；这正确区分了“程序残留为零”和“用户数据有意保留”。但当前披露只在内部 JSON 中，安装许可证和 README 没有面向最终用户说明卸载后的保留路径与清理方式。

**验收标准：**在卸载确认页或已安装 README/支持页中，清楚说明保留内容、原因、路径和安全手动删除步骤；自动测试断言该文案随安装存在。此项不阻断内部安装候选，但应在正式发布前补足用户透明度。

### P2-2：覆盖测试仍是同版本 repair，不是真实跨版本升级

当前第二次运行同一 0.9.0 安装器，证明固定 AppId、同路径覆盖和载荷稳定，但没有证明 0.8.x/早期签名版本到 0.9.0 的迁移与旧文件清理。

**验收标准：**首个可公开版本之后保留已签名 N-1 安装器，建立 N-1 -> N 矩阵，验证旧文件删除、配置/隔离账本保留、卸载所有权和降级策略。

### P2-3：安装器语言仍少于应用支持语言

Inno 当前提供 9 种安装语言，而应用有更广的语言集合。核心安装可用性不受影响，但与产品国际化覆盖仍不完全一致。

**验收标准：**安装器覆盖产品承诺的 10 种代表语言，并在 locale VM/可重复模拟中验证前置错误、许可证、任务、卸载与静默模式。

## 发布门禁摘要

| 门禁 | 结果 |
|---|---|
| 候选大小/SHA 身份 | PASS |
| 143 文件嵌入与主 EXE 哈希 | PASS |
| .NET 4.8 前置门禁 | PASS |
| LICENSE/第三方完整许可安装 | PASS |
| 连续构建稳定性 | PASS（10/10，同一 SHA） |
| 静默安装/CLI/覆盖/卸载 | PASS |
| 安装目录/注册项/开始菜单残留 | PASS |
| 持久化安装日志 | PASS |
| 用户数据保留技术策略 | PASS；用户可见披露为 P2 |
| Authenticode | **BLOCKED_NOT_SIGNED** |

最终判定：**内部安装器工程 PASS；公开发布仍 BLOCKED，唯一 P1 是签名外部门槛。** 软件本体既有 99/100 结论与本报告边界分离，本报告不重新评分软件本体，也不把未签名字节批准为公开发行版。
