Ver Fonte

fix(desktop): preserve the dsh base in dated test versions

winewill há 4 dias atrás
pai
commit
a2215799b8
21 ficheiros alterados com 149 adições e 30 exclusões
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml
  2. 2 0
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
  3. 2 0
      .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md
  4. 6 0
      .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.i18n.yaml
  5. 29 0
      .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md
  6. 29 0
      .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md
  7. 2 2
      .agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml
  8. 3 1
      .agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md
  9. 3 1
      .agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md
  10. 2 2
      apps/desktop/README.i18n.yaml
  11. 17 0
      apps/desktop/README.md
  12. 17 0
      apps/desktop/README.zh.md
  13. 4 3
      apps/desktop/scripts/installed-update-qualification.ts
  14. 1 1
      apps/desktop/scripts/prepare-installed-update.ts
  15. 2 2
      apps/desktop/tests/README.i18n.yaml
  16. 1 1
      apps/desktop/tests/README.md
  17. 1 1
      apps/desktop/tests/README.zh.md
  18. 10 2
      apps/desktop/tests/installed-update-qualification.spec.ts
  19. 2 2
      apps/desktop/tests/installed-update/README.i18n.yaml
  20. 7 5
      apps/desktop/tests/installed-update/README.md
  21. 7 5
      apps/desktop/tests/installed-update/README.zh.md

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
-2026-08-25-electron-desktop-packaging-and-updates.md: f80dd8add1db21a4fb044f45a686b42dcd53b3ad
-2026-08-25-electron-desktop-packaging-and-updates.zh.md: 1facc73a9ec2a71962224f6c6326579797cdad6a
+2026-08-25-electron-desktop-packaging-and-updates.md: abdb898af67dfe0ea0699fc0ba6d82eda541db29
+2026-08-25-electron-desktop-packaging-and-updates.zh.md: eb7dd9c644e3b78b3275b1a0d9e7259d97761d44

+ 2 - 0
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md

@@ -18,6 +18,8 @@ The current GUI protocol binds the Web client and backend release. Independently
 
 ## Decision
 
+Production/test derivation of the shared version follows the [release version decision](../process/2026-09-16-desktop-release-version-derivation.md); this record still governs package-version equality.
+
 Ship a small Electron shell and pinned pnpm; the [runtime decision](2026-09-11-desktop-electron-node-runtime.md) owns the executable choice. The [thin-wrapper decision](2026-09-10-desktop-web-wrapper.md) owns Host boot and transport: the private Host runs the shared Web profile runner, Electron loads its authenticated HTTP URL, and child IPC carries lifecycle messages.
 
 Electron owns the reserved profile at `.dsh/profiles/desktop`. The [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md) owns core resource storage, external plugin dependencies, shared package links, and profile reconciliation. The private Desktop Host remains outside the public CLI package and is never published to npm.

+ 2 - 0
.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md

@@ -18,6 +18,8 @@ DeepSeek Harness 需要一个复用 Web UI 的 Electron 桌面应用。该应用
 
 ## 决策
 
+共享版本的 production/test 派生遵循[发布版本决策](../process/2026-09-16-desktop-release-version-derivation.zh.md),本记录的包版本一致性要求保持适用。
+
 交付小型 Electron 壳和固定版本 pnpm;[运行时决策](2026-09-11-desktop-electron-node-runtime.zh.md)持有可执行文件选择。[薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责 Host 启动与传输:私有 Host 运行共享 Web profile runner,Electron 加载其认证 HTTP URL,子进程 IPC 承载生命周期消息。
 
 Electron 拥有 `.dsh/profiles/desktop` 保留 profile。[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)负责核心资源存储、外部插件依赖、共享包链接和 profile 协调。私有 Desktop Host 保持独立于公共 CLI 包,且不会发布到 npm。

+ 6 - 0
.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md
+2026-09-16-desktop-release-version-derivation.md: 0f1d917cb0d382453fbb0dca561a1eae7d530d67
+2026-09-16-desktop-release-version-derivation.zh.md: e9576e30232b5eb247de047cb67f51db5fa43ae3

+ 29 - 0
.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md

@@ -0,0 +1,29 @@
+# Agent Note: Derive Desktop test versions from the complete dsh version
+
+Status: implemented
+
+English | [中文](2026-09-16-desktop-release-version-derivation.zh.md)
+
+## Problem
+
+Replacing a dsh prerelease identifier with the update channel name loses the base release identity and changes SemVer ordering. A date alone cannot distinguish multiple test builds of the same base.
+
+## Decision
+
+The [Desktop release rules](../../../../apps/desktop/README.md#release-versions) preserve the complete dsh base for production and derive dated, indexed test versions from that base. Final release-family package versions remain equal. The base is retained separately before manifests are retagged, so another test release cannot append a second date suffix.
+
+The fixed Nightly feed is a distribution address, independent of version derivation. Existing clients continue to use that address with prerelease updates enabled and downgrades disabled. Test distribution does not publish the unsuffixed base. Operators check existing release records and objects before assigning an index.
+
+The packaging decision retains runtime/version equality; the installed-update materials decision retains private identities and data isolation. Their Nightly naming examples do not define the test version policy. Historical release receipts and frozen archived records remain evidence of what was actually built.
+
+## Alternatives considered
+
+**Replace alpha, beta, or rc with nightly.** This discards the base prerelease and can sort above its later releases, preventing updates to the intended version line.
+
+**Rename the feed with the prerelease identifier.** Installed clients would continue checking their existing feed and miss the replacement publication.
+
+**Enable downgrade to repair an incorrectly numbered release.** A global downgrade allowance changes ordinary update safety. Affected installations use a manual installer; correcting the feed alone cannot migrate them.
+
+## Consequences
+
+A test build has an unambiguous base and creation date without changing production version identity. Signing and upload still validate the final equal package versions. Installed-update material allocation accepts dated prerelease versions; retained legacy Nightly material remains readable for audit.

+ 29 - 0
.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md

@@ -0,0 +1,29 @@
+# Agent Note: 从完整 dsh 版本派生 Desktop 测试版本
+
+Status: implemented
+
+[English](2026-09-16-desktop-release-version-derivation.md) | 中文
+
+## 问题
+
+用更新通道名替换 dsh 预发布标识会丢失基础发布身份,并改变 SemVer 排序。只有日期无法区分同一基础版本的多次测试构建。
+
+## 决策
+
+[Desktop 发布规则](../../../../apps/desktop/README.zh.md#release-versions)在 production 中保留完整 dsh 基础版本,并从该基础版本派生带日期和序号的测试版本。最终发布家族包版本保持一致。修改清单版本前单独记录基础版本,避免下一次测试发布追加第二个日期后缀。
+
+固定 Nightly feed 是分发地址,与版本派生独立。现有客户端继续使用该地址,允许预发布更新并禁止降级。test 分发不发布无后缀基础版本。操作者分配序号前检查已有发布记录和对象。
+
+打包决策继续规定运行时与包版本一致性;安装版更新物料决策继续规定私有身份和数据隔离。其中的 Nightly 命名示例不定义测试版本规则。历史发布回执和已冻结归档保留为实际构建的证据。
+
+## 考虑过的替代方案
+
+**用 nightly 替换 alpha、beta 或 rc。** 这会丢弃基础预发布标识,并可能排在其后续版本之上,阻止更新到预期版本序列。
+
+**按预发布标识重命名 feed。** 已安装客户端仍查询原有 feed,将无法获取替换后的发布。
+
+**为修复错误版本号而开启降级。** 全局允许降级会改变常规更新的安全性。受影响安装使用手动安装包;仅修正 feed 无法迁移它们。
+
+## 结果
+
+测试构建具有明确的基础版本和创建日期,production 版本身份保持一致。签名与上传仍验证最终各包版本相等。安装版更新物料分配接受带日期的预发布版本;保留的旧 Nightly 物料仍可读取以供审计。

+ 2 - 2
.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md
-2026-09-14-desktop-installed-update-materials.md: 0b35dacf5ec655dacf97624360894a6a1a8e9bcc
-2026-09-14-desktop-installed-update-materials.zh.md: 0b4df0f67c440b126ec1e2d012c09f03d2382899
+2026-09-14-desktop-installed-update-materials.md: c402d83608e32c3c40569edb67569efa30f603f8
+2026-09-14-desktop-installed-update-materials.zh.md: 75123c62c99684de75005b014cf821c8c427a3d0

+ 3 - 1
.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md

@@ -10,7 +10,9 @@ An installed-update walkthrough needs two increasing versions with a shared appl
 
 ## Decision
 
-The [material preparer](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts) accepts a retained test-only manifest with a random identity and distribution namespace. It copies a verified source runtime into separate synthetic Nightly versions, changes only release-family versions and matching dependency references, and seals and verifies both inventories. The original runtime is verified again and its descriptor hash must remain unchanged. Existing output directories are not overwritten; a failed copy retains a failure record and cannot create a completion receipt.
+Version derivation follows the [release version decision](../process/2026-09-16-desktop-release-version-derivation.md); this record continues to govern material, application identity, and user-data isolation.
+
+The [material preparer](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts) accepts a retained test-only manifest with a random identity and distribution namespace. It copies a verified source runtime into separate derived test versions, changes only release-family versions and matching dependency references, and seals and verifies both inventories. The original runtime is verified again and its descriptor hash must remain unchanged. Existing output directories are not overwritten; a failed copy retains a failure record and cannot create a completion receipt.
 
 The generated bootstrap validates the installed package identity before importing production main. Each version assigns the same application-data subdirectory to its test Electron profile, Harness home, and external journals. Environment values inherited from the first launch do not determine those paths. The manifest reader rejects changed test destinations, identities, and directory locations. The [operator guide](../../../../apps/desktop/tests/installed-update/README.md) distinguishes source preparation, private runtime copies, and signed installed-app qualification.
 

+ 3 - 1
.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md

@@ -10,7 +10,9 @@ Status: implemented
 
 ## 决策
 
-[物料准备器](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts)接受保留的 test 专用清单,其中包含随机身份和分发命名空间。它将已验证的原始运行时复制为独立的合成 Nightly 版本,仅修改发布家族版本和匹配的依赖引用,再生成并验证两个完整性清单。原始运行时再次验证,其描述文件哈希必须保持不变。已有输出目录不被覆盖;复制失败保留失败记录,不能生成完成回执。
+版本派生遵循[发布版本决策](../process/2026-09-16-desktop-release-version-derivation.zh.md);本记录继续规定物料、应用身份和用户数据的隔离。
+
+[物料准备器](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts)接受保留的 test 专用清单,其中包含随机身份和分发命名空间。它将已验证的原始运行时复制为独立的派生测试版本,仅修改发布家族版本和匹配的依赖引用,再生成并验证两个完整性清单。原始运行时再次验证,其描述文件哈希必须保持不变。已有输出目录不被覆盖;复制失败保留失败记录,不能生成完成回执。
 
 生成的启动入口在导入生产主程序前验证已安装包身份。两个版本将相同的应用数据子目录用于测试 Electron profile、Harness home 和外部日志。首次启动继承的环境变量不决定这些路径。清单读取器拒绝已改变的 test 目标、身份和目录位置。[人工指南](../../../../apps/desktop/tests/installed-update/README.zh.md)区分源码准备、独立运行时副本和签名安装版验收。
 

+ 2 - 2
apps/desktop/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/desktop/README.md
-README.md: 3d311c9c5e235f6e4534f112922c12bdf4127085
-README.zh.md: f440de02452995d819cc853def379a9c10efd2ab
+README.md: 4e261ab294699a9b83ab55d5538752ee41738d6a
+README.zh.md: 0b69a9812aa522deb93f454f356c5d65cea6f4b4

+ 17 - 0
apps/desktop/README.md

@@ -86,6 +86,23 @@ Workspace development runs the current CLI and private Desktop Host packages und
 
 ## Package
 
+<a id="release-versions"></a>
+
+### Release versions
+
+Record the current dsh version as the base before changing any manifests. A production Desktop release uses that exact version, including any `alpha`, `beta`, or `rc` identifiers. A test release preserves the complete prerelease base and appends `.YYYYMMDD.index`; a stable base uses `-test.YYYYMMDD.index` instead.
+
+| dsh base | Production Desktop | Test Desktop example |
+|---|---|---|
+| `0.1.6-alpha.1` | `0.1.6-alpha.1` | `0.1.6-alpha.1.20260916.1` |
+| `0.1.6-beta.2` | `0.1.6-beta.2` | `0.1.6-beta.2.20260916.1` |
+| `0.1.6-rc.3` | `0.1.6-rc.3` | `0.1.6-rc.3.20260916.1` |
+| `0.1.6` | `0.1.6` | `0.1.6-test.20260916.1` |
+
+Use the actual creation date in Asia/Shanghai. For each base and date, start the index at 1 and increment after checking retained release records and published objects; never reuse a published version. Derive once from the recorded base, not from a manifest already carrying a test suffix. The final root, Desktop, bundled dsh, private Desktop Host, and other release-family manifests must all carry the same derived version. Test distribution does not publish the corresponding unsuffixed base.
+
+Version derivation does not change the fixed update channel or `nightly.yml` / `nightly-mac.yml` filenames. SemVer orders `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`, and a stable base's test version precedes that stable release. Clients only accept a greater version: replacing a feed cannot move an installed higher version to a lower corrected version. Such clients need manual installation; keep automatic downgrade disabled. The [version decision](../../.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md) explains why the channel does not supply the prerelease identifier.
+
 Packaging, upload, and manual macOS signature verification read `apps/desktop/.env.windows` or `.env.macos`, selected by target platform. Copy the [Windows template](.env.windows.example) or [macOS template](.env.macos.example) and fill in the local settings; Git ignores both local files, and packaged artifacts exclude them. Release fields come only from the target file, without fallback to system or shell variables; `PATH`, proxies, and build-tool settings remain inherited. Files use UTF-8 with optional BOM; relative certificate, SignTool, Apple API key, and keychain paths resolve from `apps/desktop`, values are not shell-expanded, and passwords containing `#` or spaces need quotes. CI also creates the target file before invoking packaging.
 
 Every package command checks the application ID, update origin, and mode-specific signing configuration before building or downloading. macOS checks the identity, Team ID, one complete notarization strategy, readable local `CSC_LINK` p12 file, explicit `CSC_KEY_PASSWORD`, and referenced API key and keychain files; Windows checks the public code-signing certificate, SignTool file, container name, and PIN format. Windows preparation-only and explicit unsigned builds do not require signing credentials. Configuration checks do not authenticate the PIN, log in to the token, unlock a keychain, or contact Apple; actual signing and notarization perform those checks. Run the same checks separately:

+ 17 - 0
apps/desktop/README.zh.md

@@ -86,6 +86,23 @@ Workspace 开发使用 Electron RunAsNode 运行当前 CLI 与私有 Desktop Hos
 
 ## 打包
 
+<a id="release-versions"></a>
+
+### 发布版本
+
+修改任何清单前,记录当前 dsh 版本作为基础版本。production Desktop 使用完全相同的版本,包括其中的 `alpha`、`beta` 或 `rc` 标识。test 发布保留完整的预发布基础版本并追加 `.YYYYMMDD.index`;稳定基础版本则追加 `-test.YYYYMMDD.index`。
+
+| dsh 基础版本 | production Desktop | test Desktop 示例 |
+|---|---|---|
+| `0.1.6-alpha.1` | `0.1.6-alpha.1` | `0.1.6-alpha.1.20260916.1` |
+| `0.1.6-beta.2` | `0.1.6-beta.2` | `0.1.6-beta.2.20260916.1` |
+| `0.1.6-rc.3` | `0.1.6-rc.3` | `0.1.6-rc.3.20260916.1` |
+| `0.1.6` | `0.1.6` | `0.1.6-test.20260916.1` |
+
+日期使用实际创建时的 Asia/Shanghai 日期。每个基础版本、每天的序号从 1 开始,检查保留的发布记录与已发布对象后递增;绝不复用已发布版本。只从记录的基础版本派生一次,不从已有测试后缀的清单继续追加。最终根包、Desktop、内置 dsh、私有 Desktop Host 和其他发布家族清单必须全部使用同一个派生版本。test 分发不发布对应的无后缀基础版本。
+
+版本派生不改变固定更新通道,也不改变 `nightly.yml` / `nightly-mac.yml` 文件名。SemVer 排序为 `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`,稳定基础版本的测试版低于该稳定版。客户端只接受更高版本:替换 feed 无法让已安装的较高版本更新到较低的纠正版。这类客户端需要手动安装;保持自动降级关闭。[版本决策](../../.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md)解释为什么不能用通道名替换预发布标识。
+
 打包、上传以及手动 macOS 签名检查使用 `apps/desktop/.env.windows` 或 `.env.macos`,由目标平台选择。复制对应的 [Windows 模板](.env.windows.example) 或 [macOS 模板](.env.macos.example),填写本机配置;Git 忽略这两个本地文件,安装产物也不包含它们。发布字段只从目标文件读取,不回退到系统或 shell 中的同名变量;`PATH`、代理和构建工具环境仍保留。文件使用 UTF-8,支持 BOM;相对证书、SignTool、Apple API Key 和钥匙串路径以 `apps/desktop` 为基准,变量值不做 shell 展开,包含 `#` 或空格的密码需要引号。CI 同样在运行前生成目标文件。
 
 每条打包命令在构建与下载前检查应用 ID、更新地址和该模式需要的签名配置。macOS 检查身份、Team ID、一套完整公证凭据、`CSC_LINK` 指定的可读本地 p12 文件、显式配置的 `CSC_KEY_PASSWORD`,以及引用的 API Key 和钥匙串文件;Windows 检查公开代码签名证书、SignTool 文件、容器名称和 PIN 格式。仅准备 Windows 资源或显式未签名打包不要求签名凭据。配置检查不验证 PIN 是否正确、Token 是否登录、钥匙串是否解锁或 Apple 是否接受凭据;实际签名与公证负责这些检查。单独运行相同检查:

+ 4 - 3
apps/desktop/scripts/installed-update-qualification.ts

@@ -31,7 +31,7 @@ export interface InstalledUpdateRun {
 /**
  * Allocate a new local run and retain its manifest without reading release credentials.
  * @param parent Ignored material directory; each invocation acquires a separate child atomically.
- * @param versions Explicit original and successor Nightly versions, in increasing order.
+ * @param versions Explicit original and successor test versions, in increasing order.
  * @param source Source version, Git commit, and dirty-file list captured before material preparation.
  * @returns The retained run manifest; no package or publication is implied by its presence.
  */
@@ -57,9 +57,10 @@ export async function createInstalledUpdateRun(
 }
 
 function validateVersions(versions: readonly [string, string]): void {
-  if (versions.some(version => valid(version) !== version || !/^\d+\.\d+\.\d+-nightly\.[0-9.]+$/u.test(version))
+  const pattern = /^\d+\.\d+\.\d+-(?:nightly\.[0-9.]+|[0-9A-Za-z.-]+\.\d{8}\.[1-9]\d*)$/u
+  if (versions.some(version => valid(version) !== version || !pattern.test(version))
     || !gt(versions[1], versions[0])) {
-    throw new Error('installed update: two increasing numeric Nightly versions are required')
+    throw new Error('installed update: two increasing dated test versions are required')
   }
 }
 

+ 1 - 1
apps/desktop/scripts/prepare-installed-update.ts

@@ -35,7 +35,7 @@ async function main(): Promise<void> {
   }
   if ((command !== 'init' && command !== 'inspect') || original === undefined || successor === undefined
     || extra.length !== 0 || (command === 'init' && directory !== undefined) || (command === 'inspect' && directory === undefined)) {
-    throw new Error('usage: prepare-installed-update.ts init <original-nightly> <successor-nightly> | bootstrap <run.json> | runtime <run.json> | application <run.json> | files <run.json> <version> | collect <run.json> <journal-directory> | inspect <original-nightly> <successor-nightly> <journal-directory>')
+    throw new Error('usage: prepare-installed-update.ts init <original-test-version> <successor-test-version> | bootstrap <run.json> | runtime <run.json> | application <run.json> | files <run.json> <version> | collect <run.json> <journal-directory> | inspect <original-test-version> <successor-test-version> <journal-directory>')
   }
   if (command === 'inspect') {
     const evidence = await inspectInstalledUpdateJournals(directory!, [original, successor])

+ 2 - 2
apps/desktop/tests/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/desktop/tests/README.md
-README.md: d2a78a4342dcbb3443c95d4882f061f42735f753
-README.zh.md: ca840169eee03faef7bf7fa70c98beab0ca39814
+README.md: fb6399e2e65e51af2cf44d68765f0ea0843d44ea
+README.zh.md: 8cd8304ecf8e8c7edba2b2e2615a0fb18c394e5d

+ 1 - 1
apps/desktop/tests/README.md

@@ -82,7 +82,7 @@ The 2026-09-15 local run `local-updater-Y770Uz` passes 17 real-Electron scenario
 
 TODO (deferred Desktop UX): provide continuous feedback from installation confirmation through the new workspace becoming usable. The old application should describe task shutdown, the installer should own visible progress after the old process exits, and the new application should show startup status. Use real percentages only for measurable installation work, and indeterminate feedback for shutdown/startup; require no additional confirmation. Add timestamps for installer handoff, file replacement, process startup, and first visible window before optimizing the delay. This is a backlog item, not implemented behavior or part of the current error-copy and publication fixes.
 
-On 2026-09-14, operator-authorized builds in `.desktop-build/qualification/installed-update-r5dYNH/` produce signed `0.1.6-nightly.20260914.1` and `0.1.6-nightly.20260914.2` installers with one shared private identity and test feed. Each supervised build records 15 successful signing operations and no signing failures; this count does not measure internal token authentication attempts. Version-local `verification/check-KaQ0AB/result.json` and `verification/check-nO2tMM/result.json` pass real updater/Authenticode verification, timestamps, archive paths, frozen application bytes, runtime contents, and embedded update settings. The installers contain 155,855,536 and 155,854,520 bytes respectively. The retained authorization and incident archive document software-interlock recovery based on a successful single-file probe; current token login state and retry count were not queried. No installer was executed or remote feed written during these builds and inspections. These local receipts qualify the files, not an installed upgrade.
+On 2026-09-14, operator-authorized builds in `.desktop-build/qualification/installed-update-r5dYNH/` produce signed `0.1.6-nightly.20260914.1` and `0.1.6-nightly.20260914.2` installers with one shared private identity and test feed. Each supervised build records 15 successful signing operations and no signing failures; this count does not measure internal token authentication attempts. Version-local `verification/check-KaQ0AB/result.json` and `verification/check-nO2tMM/result.json` pass real updater/Authenticode verification, timestamps, archive paths, frozen application bytes, runtime contents, and embedded update settings. The installers contain 155,855,536 and 155,854,520 bytes respectively. The retained authorization and incident archive document software-interlock recovery based on a successful single-file probe; current token login state and retry count were not queried. No installer was executed or remote feed written during these builds and inspections. These local receipts qualify the files, not an installed upgrade. These Nightly numbers identify historical qualification material only; new releases follow the [release version rules](../README.md#release-versions).
 
 The 2026-09-14 run `local-updater-k1Sbo5` passes 17 real-Electron/local-server scenarios, including same-modal installation confirmation and deferral. The complete-workspace report `electron-workspace-updates-7J0rLB` records 10 scenarios using the compiled main entry, real preload, and real Host process, including mandatory task-stop failure and recovery. Neither run executes an installer. Eight focused suites pass 94 regressions for mandatory rendering, version-bound approval, stale frame rejection, clipboard failures, task recovery, and one-shot Windows/macOS attention adapters. Native notification display is not qualified by these substitutes.
 

+ 1 - 1
apps/desktop/tests/README.zh.md

@@ -82,7 +82,7 @@ Chromium headless shell revision 1228 已安装在忽略目录 `.desktop-build/p
 
 TODO(暂缓实施的桌面体验):从确认安装到新工作区可用,提供连续状态反馈。旧应用说明任务收尾,旧进程退出后由安装器展示进度,新应用展示启动状态。仅对可测量的安装工作显示真实百分比,任务收尾和启动使用不定进度反馈;不增加确认操作。优化耗时前补齐安装器交接、文件替换、进程启动和窗口首次可见的时间戳。这是待办,不是已实现行为,也不属于本轮错误文案与发布流程修复。
 
-2026-09-14,经操作者授权的构建在 `.desktop-build/qualification/installed-update-r5dYNH/` 中生成签名 `0.1.6-nightly.20260914.1` 和 `0.1.6-nightly.20260914.2` 安装包,两者共用私有身份和 test feed。每次受监督构建记录 15 次成功签名,没有签名失败;此计数不测量令牌内部认证次数。版本目录内的 `verification/check-KaQ0AB/result.json` 和 `verification/check-nO2tMM/result.json` 通过真实 updater/Authenticode 验签、时间戳、归档路径、冻结应用字节、运行时内容及内嵌更新设置检查。安装包分别为 155,855,536 和 155,854,520 字节。保留的授权和事故归档记录了基于成功单文件探针的软件保护锁恢复;当前令牌登录状态和剩余次数没有查询。上述构建与检查没有执行安装器或写入远端 feed。这些本地回执验证文件,不证明已安装版本升级成功。
+2026-09-14,经操作者授权的构建在 `.desktop-build/qualification/installed-update-r5dYNH/` 中生成签名 `0.1.6-nightly.20260914.1` 和 `0.1.6-nightly.20260914.2` 安装包,两者共用私有身份和 test feed。每次受监督构建记录 15 次成功签名,没有签名失败;此计数不测量令牌内部认证次数。版本目录内的 `verification/check-KaQ0AB/result.json` 和 `verification/check-nO2tMM/result.json` 通过真实 updater/Authenticode 验签、时间戳、归档路径、冻结应用字节、运行时内容及内嵌更新设置检查。安装包分别为 155,855,536 和 155,854,520 字节。保留的授权和事故归档记录了基于成功单文件探针的软件保护锁恢复;当前令牌登录状态和剩余次数没有查询。上述构建与检查没有执行安装器或写入远端 feed。这些本地回执验证文件,不证明已安装版本升级成功。 此处 Nightly 版本号仅标识历史验收物料;新发布使用[发布版本规则](../README.zh.md#release-versions)。
 
 2026-09-14 的 `local-updater-k1Sbo5` 通过 17 个真实 Electron/本地服务器场景,包括同弹窗安装确认和稍后更新。完整工作区报告 `electron-workspace-updates-7J0rLB` 记录了使用编译主入口、真实 preload 和真实 Host 进程的 10 个场景,包括强更停止任务失败及恢复。两次运行均不执行安装器。八个定向套件通过 94 个回归,覆盖强更渲染、版本绑定批准、过期 frame 拒绝、剪贴板失败、任务恢复及 Windows/macOS 单次提醒适配器。这些替身不认证原生通知展示。
 

+ 10 - 2
apps/desktop/tests/installed-update-qualification.spec.ts

@@ -8,8 +8,8 @@ import { describe, expect, it } from 'vitest'
 import { collectInstalledUpdateJournals, createInstalledUpdateRun, inspectInstalledUpdateJournals } from '../scripts/installed-update-qualification.ts'
 import { DesktopUpdateJournal } from '../src/update-journal.ts'
 
-const versions = ['0.1.6-nightly.20260914.1', '0.1.6-nightly.20260914.2'] as const
-const source = { version: '0.1.5-rc.2', commit: 'a'.repeat(40), dirtyFiles: [' M apps/desktop/example.ts'] }
+const versions = ['0.1.6-alpha.1.20260916.1', '0.1.6-alpha.1.20260916.2'] as const
+const source = { version: '0.1.6-alpha.1', commit: 'a'.repeat(40), dirtyFiles: [' M apps/desktop/example.ts'] }
 interface CollectionReport {
   readonly evidence: unknown
   readonly files: readonly { path: string; sha256: string; bytes: number }[]
@@ -61,6 +61,14 @@ describe('installed-update qualification materials', () => {
     },
   )
 
+  it.each(['alpha.1', 'beta.2', 'rc.3', 'test'])('accepts dated %s versions', async (prefix) => {
+    await fixture(async (directory) => {
+      const pair = [`0.1.6-${prefix}.20260916.1`, `0.1.6-${prefix}.20260916.2`] as const
+      const run = await createInstalledUpdateRun(directory, pair, source)
+      expect(run.versions).toEqual(pair)
+    })
+  })
+
   it('rejects an invalid source commit before allocating material', async () => {
     await fixture(async (directory) => {
       await expect(createInstalledUpdateRun(directory, versions, { ...source, commit: 'unknown' })).rejects.toThrow('Git commit')

+ 2 - 2
apps/desktop/tests/installed-update/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/desktop/tests/installed-update/README.md
-README.md: de340cbb43c629f139fbe4d8640d625881d907d7
-README.zh.md: 5eb8d40ba40980fba287f66142cf4f1716b73216
+README.md: 574d462b18030afaac83152c7a4e3ea1cd9682da
+README.zh.md: 65bfa67fa6171da15493d7106b1a1096b89f334a

+ 7 - 5
apps/desktop/tests/installed-update/README.md

@@ -21,10 +21,12 @@ Prepare a private test application and fresh test distribution namespace, then l
 
 ## Prepare materials
 
-From the repository root with dependencies installed, allocate a run with two increasing numeric Nightly versions. This writes only an ignored local manifest; it does not build, sign, upload, install, or read credentials.
+From the repository root with dependencies installed, allocate a run with two increasing derived test versions. This writes only an ignored local manifest; it does not build, sign, upload, install, or read credentials.
+
+The examples use base version `0.1.6-alpha.1`. Before creating new material, substitute the actual base, Asia/Shanghai date, and unused index according to the [release version rules](../../README.md#release-versions).
 
 ```powershell
-node --import tsx apps/desktop/scripts/prepare-installed-update.ts init 0.1.6-nightly.20260914.1 0.1.6-nightly.20260914.2
+node --import tsx apps/desktop/scripts/prepare-installed-update.ts init 0.1.6-alpha.1.20260916.1 0.1.6-alpha.1.20260916.2
 ```
 
 Retain the returned `run.json` and reuse its random identity and `qualification/<id>` paths for both packages. The source commit and dirty-file list identify the starting checkout, not the contents of a later package. Record final build source identifiers and artifact hashes separately; do not regenerate the manifest midway through an update.
@@ -45,7 +47,7 @@ The `application` command copies built main/preload modules, renderer files, and
 The [packaging entry](../../scripts/package-installed-update.ts) defaults to checks. A retained signing interlock rejects before credentials are loaded; it never clears that interlock. With no interlock, checks load `.env.windows`, validate prepared inputs, and launch no child. Run this from the repository root with one exact version:
 
 ```powershell
-node --import tsx apps/desktop/scripts/package-installed-update.ts "<run.json>" 0.1.6-nightly.20260914.1 --check
+node --import tsx apps/desktop/scripts/package-installed-update.ts "<run.json>" 0.1.6-alpha.1.20260916.1 --check
 ```
 
 Actual packaging remains unverified and requires separate hardware-recovery approval and a present operator. The `--execute` mode requires a terminal and an exact confirmation containing the version and run ID; there is no piped approval option. It rechecks the interlock and exclusively allocates that version's `packaging` directory. The supervised child builds only that version, disables publication, strips unrelated credentials, and stops on failure or a 15-minute overall deadline. This deadline does not bound individual CSP authentication attempts. Records retain source/tool hashes, redacted output, events, and artifact file hashes; an existing attempt or output refuses reuse. `builderCompleted` and a successful supervisor result do not establish package verification, signature acceptance, or installation success; `packageVerification` remains `pending` until those checks are performed independently.
@@ -57,7 +59,7 @@ The Windows [read-only signature helper](../../scripts/installed-update-signatur
 The [package verifier](../../scripts/verify-installed-update-package.ts) checks the final installer before extracting it with a reviewed local 7-Zip executable. Supply absolute paths for the manifest, trusted public certificate, and tool; never select a tool extracted from the installer being verified. This command creates a fresh `verification/check-*` directory, preserves partial records, and fails immediately when the installer is absent:
 
 ```powershell
-node --import tsx apps/desktop/scripts/verify-installed-update-package.ts "<run.json>" 0.1.6-nightly.20260914.1 "<public.cer>" "<reviewed-7za.exe>"
+node --import tsx apps/desktop/scripts/verify-installed-update-package.ts "<run.json>" 0.1.6-alpha.1.20260916.1 "<public.cer>" "<reviewed-7za.exe>"
 ```
 
 The verifier checks actual archived application identity, entry, frozen application bytes, updater dependency versions, feed/cache/publisher configuration, and the bundled Harness runtime against prepared inputs. Changed runtime executables require separate signature checks; other runtime bytes must match. It rejects unsafe archive paths before extraction and records installer, application, and runtime-executable signatures. Before success, it rechecks installer, feed/blockmap, manifest, certificate, and tool hashes. `passed` covers these checks only: dependency bytes are not frozen, and installer registration, startup, upgrade, and data retention remain explicit manual checks. Real archive reading has been observed on a retained older package; complete signed-package verification for the prepared versions remains pending.
@@ -92,7 +94,7 @@ The intentional 404 is a check-failure case, not the later interrupted-download
 Retain the manifest, build records, original feeds, binary hashes, publication receipts and readbacks, fault setup/removal evidence, screenshots, and all process journals. Installer logs and test data may contain private paths or content; review before sharing. The inspector is read-only and copies only milestone references, not raw diagnostics. Replace the directory placeholder and use the manifest's exact versions:
 
 ```powershell
-node --import tsx apps/desktop/scripts/prepare-installed-update.ts inspect 0.1.6-nightly.20260914.1 0.1.6-nightly.20260914.2 "<journal-directory>"
+node --import tsx apps/desktop/scripts/prepare-installed-update.ts inspect 0.1.6-alpha.1.20260916.1 0.1.6-alpha.1.20260916.2 "<journal-directory>"
 ```
 
 Exit 0 means the ordered journal milestones are present; exit 2 means missing milestones; exit 1 means input or command validation failed. A failure/retry sequence cannot combine different version 1 processes. A successor started before the original process quit is not counted; clock changes can leave evidence incomplete and require investigation. `recordedFlow: complete` is not overall acceptance. The report always requires independent operator checks for publication timing, network failure and recovery, installer completion and path, preserved data, and screenshots and confirmations.

+ 7 - 5
apps/desktop/tests/installed-update/README.zh.md

@@ -21,10 +21,12 @@ description: "Windows 已安装应用更新的人工清单,覆盖下载失败
 
 ## 准备物料
 
-在已安装依赖的仓库根目录中,用两个递增的纯数字 Nightly 版本创建批次。这只写入已忽略的本地清单,不构建、签名、上传、安装或读取凭据。
+在已安装依赖的仓库根目录中,用两个递增的派生测试版本创建批次。这只写入已忽略的本地清单,不构建、签名、上传、安装或读取凭据。
+
+示例以 `0.1.6-alpha.1` 为基础版本。创建新物料前,按[发布版本规则](../../README.zh.md#release-versions)替换为实际基础版本、北京时间日期和未使用的序号。
 
 ```powershell
-node --import tsx apps/desktop/scripts/prepare-installed-update.ts init 0.1.6-nightly.20260914.1 0.1.6-nightly.20260914.2
+node --import tsx apps/desktop/scripts/prepare-installed-update.ts init 0.1.6-alpha.1.20260916.1 0.1.6-alpha.1.20260916.2
 ```
 
 保留返回的 `run.json`,两个安装包复用其中的随机身份与 `qualification/<id>` 路径。源提交与脏文件列表标识起始工作区,不代表后续安装包内容。最终构建来源和产物哈希另行记录;不要在更新中途重新生成清单。
@@ -45,7 +47,7 @@ node --import tsx apps/desktop/scripts/prepare-installed-update.ts application "
 [打包入口](../../scripts/package-installed-update.ts)默认只检查。保留的签名保护锁会在读取凭据前使其拒绝;入口绝不清除此锁。没有保护锁时,检查会加载 `.env.windows`、校验准备输入,不启动子进程。在仓库根目录使用一个准确版本执行:
 
 ```powershell
-node --import tsx apps/desktop/scripts/package-installed-update.ts "<run.json>" 0.1.6-nightly.20260914.1 --check
+node --import tsx apps/desktop/scripts/package-installed-update.ts "<run.json>" 0.1.6-alpha.1.20260916.1 --check
 ```
 
 实际打包仍未验证,必须另行获得硬件恢复授权并有操作者在场。`--execute` 模式要求终端和包含版本、批次 ID 的准确确认,没有管道批准选项。它再次检查保护锁,并独占创建该版本的 `packaging` 目录。受监督子进程只构建该版本、禁用发布、移除无关凭据,失败或达到 15 分钟整体期限时停止。该期限不限制单次 CSP 内部认证尝试。记录保留源码/工具哈希、脱敏输出、事件和产物文件哈希;已有尝试或输出拒绝复用。`builderCompleted` 与监督程序成功结果不证明包验证、验签或安装成功;独立完成这些检查前,`packageVerification` 保持 `pending`。
@@ -57,7 +59,7 @@ Windows [只读签名检查器](../../scripts/installed-update-signature.mjs)使
 [安装包检查器](../../scripts/verify-installed-update-package.ts)先校验最终安装包,再使用经过审查的本地 7-Zip 可执行文件解包。清单、可信公钥证书和工具均须提供绝对路径;绝不选择从待验安装包中解出的工具。此命令创建全新的 `verification/check-*` 目录,保留部分记录,安装包缺失时立即失败:
 
 ```powershell
-node --import tsx apps/desktop/scripts/verify-installed-update-package.ts "<run.json>" 0.1.6-nightly.20260914.1 "<public.cer>" "<reviewed-7za.exe>"
+node --import tsx apps/desktop/scripts/verify-installed-update-package.ts "<run.json>" 0.1.6-alpha.1.20260916.1 "<public.cer>" "<reviewed-7za.exe>"
 ```
 
 检查器核对实际归档内的应用身份、入口、冻结应用字节、updater 依赖版本、feed/缓存/发布者配置,并将内置 Harness 运行时与准备输入比较。发生变化的运行时可执行文件须单独验签;其他运行时字节必须一致。它在解包前拒绝不安全归档路径,记录安装器、应用及运行时可执行文件的签名。成功前再次核对安装包、feed/blockmap、清单、证书和工具哈希。`passed` 仅覆盖这些检查:依赖字节未冻结,安装器注册、启动、升级和数据保留仍是明确的人工检查项。保留的旧包已完成真实归档读取;本批次准备版本的完整签名包检查仍待执行。
@@ -92,7 +94,7 @@ node --import tsx apps/desktop/scripts/verify-installed-update-package.ts "<run.
 保留清单、构建记录、原始 feed、二进制哈希、发布回执与回读、故障设置和移除证据、截图及全部进程日志。安装器日志与测试数据可能包含私有路径或内容,分享前须审查。检查器只读,仅复制里程碑引用,不复制原始诊断。替换目录占位符并使用清单中的准确版本:
 
 ```powershell
-node --import tsx apps/desktop/scripts/prepare-installed-update.ts inspect 0.1.6-nightly.20260914.1 0.1.6-nightly.20260914.2 "<journal-directory>"
+node --import tsx apps/desktop/scripts/prepare-installed-update.ts inspect 0.1.6-alpha.1.20260916.1 0.1.6-alpha.1.20260916.2 "<journal-directory>"
 ```
 
 退出码 0 表示按序日志标记齐全;退出码 2 表示缺少标记;退出码 1 表示输入或命令验证失败。失败重试序列不能拼接不同版本 1 进程。早于原进程退出就启动的后继进程不计入;系统时间变化可能导致证据不完整,需要调查。`recordedFlow: complete` 不等于整体验收通过。报告始终要求独立人工检查发布时间、网络失败与恢复、安装器完成与路径、数据保留以及截图和确认。