分发架构 · 稳定通道

正式版本自动更新

OpenProgram 只把完整、稳定、经过 manifest 验证的 GitHub Release 视为可安装更新。Desktop 自动发现并校验平台 installer:macOS 使用 DMG,Windows 使用带 Authenticode 签名的 EXE;managed CLI/server 验证完整 runtime 后原子切换。仓库出现新提交不构成用户更新。

Design
已确定
一个 stable 通道,一套 Release 合同
Implementation
已合入 main
Desktop、受管 CLI、发布门禁
Source baseline
v0.8.1
当前源码与 runtime/package 版本基线;远端 latest 未在本页重新核验
Dependency
0 个新增 updater 包
Electron/Node、Python 与 shell 原生能力

Question

如何让正式安装发现和应用新版本,同时不跟踪 main、不降级产品功能,并保证失败时保留可用版本?

Audience

维护 Release、Desktop、CLI/server、设置页和安装文档的开发者与发布人员。

Scope

macOS/Windows Desktop,macOS/Linux/Windows managed CLI/server,以及 source checkout 的明确分流。

Exclusions

Windows 沙箱、macOS 签名、Linux Desktop、PyPI 产品安装、后台替换运行中的应用。

01

必须保持的产品合同

平台动作不同,但版本来源、完整产品定义和验证结果必须一致。

Release unit

更新单位是完整产品

不单独更新 wheel、Web、Program、OCR、Browser 或模型。每个受支持产物都满足统一 capability manifest,并包含固定 CPython。

Desktop

自动检查,校验后打开 installer

主进程选择当前平台与架构的 DMG 或 EXE,验证 bytes 与 SHA-256;Windows 再验证 Authenticode。renderer 不能提供 URL、hash 或文件名。

CLI / server

显式命令,原子切换 runtime

openprogram upgrade 验证 Release、runtime、capabilities 和 cold start,最后替换 current;不自动重启现有 worker。

Development

源码安装保留 Git 流程

source checkout 继续使用显式 ref、preflight、build 与 probe,不把开发分支伪装成 stable Release。

02

从 GitHub Release 到平台动作

两条用户路径在严格验证后分开。任何缺失、重复或不一致都在进入下载或安装前停止。

自动更新架构 GitHub latest Release 经过稳定版本、manifest、资产和架构验证后,分别进入 Desktop 的平台 installer 校验下载路径和 managed CLI 的完整 runtime 原子切换路径。 GitHub latest 非草稿 · 非预发布 Release gate vX.Y.Z · unique assets Manifest gate version · bytes · sha256 Platform route install type + arch Desktop · macOS arm64/x64 · Windows x64 受管 CLI · macOS/Linux/Windows Desktop 主进程 1 · 选择平台和架构完全匹配的安装包 2 · 流式写入独占的随机 .part 文件 3 · 验证精确字节数与 SHA-256 4 · 验证 Windows Authenticode,再打开安装包 用户确认可见的平台安装流程 受管 CLI 安装器 1 · 从不可变 tag 获取安装器 2 · 验证归档和能力清单 3 · 版本探测与隔离 worker 冷启动 4 · 通过 os.replace 替换 current 符号链接 用户显式重启正在运行的 worker 远程元数据只有验证后才可信
1 · GitHub latest
只接受 non-draft、non-prerelease、vX.Y.Z。
2 · 发布与清单门禁
验证唯一资产、版本、bytes、SHA-256 和平台架构。
3A · Desktop
校验平台 installer;Windows 额外验证 Authenticode;随后由用户确认安装。
3B · managed CLI
验证完整 runtime 与 cold start,最后原子切换 current。
metadata / Desktop完整 runtime 安装信任边界

renderer 从未获得资产 URL、文件名或 hash;CLI 子进程只继承安装所需的最小环境变量。

03

Desktop 与 CLI 的完成边界

“检查”“下载”“安装”分别对应可验证的动作。两个平台不使用同一套替换机制。

macOS Desktop

自动发现 + 完整下载 + 明确安装交接

  1. 启动调度packaged app 启动 30 秒后首次检查;开发模式不自动检查。
  2. 严格选择仅匹配当前 process.arch 的完整 unsigned DMG。
  3. 安全保存用户选择目标;随机 exclusive 临时文件;流式 size/hash 验证。
  4. 打开安装介质校验成功后打开 DMG。应用不后台替换运行中的 .app。

Windows Desktop

自动发现 + 完整下载 + 签名验证 + 明确安装交接

  1. 启动调度packaged app 启动后自动检查;开发模式不自动检查。
  2. 严格选择只接受精确命名的 OpenProgram-<version>-win-x64.exe。
  3. 双重校验流式验证 size/SHA-256,落盘后要求 Authenticode 状态为 Valid。
  4. 打开 installer校验成功后打开按用户安装的 NSIS 向导,不静默替换运行中的应用。

macOS / Linux CLI 与服务端

显式执行 + 完整 runtime 验证 + 原子激活

  1. 检查openprogram upgrade --check 只读报告当前与最新版本。
  2. 版本化 installer从已验证的不可变 tag 读取脚本,并固定 repository/version。
  3. 隔离验收checksum、capability manifest、version probe、worker cold start 全部成功。
  4. 激活准备 launcher 后以 os.replace 切换 current;用户显式重启 worker。
04

设置 → 通用 → 应用

下面是同一设置区的真实信息状态,不是独立产品版本。使用按钮或左右方向键切换。

Application

OpenProgram

Current version0.7.0
自动检查更新
Not checked yet
Check now

缓存不存在或已失效。默认开启自动检查。

Application

OpenProgram

Current version0.7.0
自动检查更新
正在检查 GitHub Releases…
Check now · disabled

一个进行中的 Promise 被所有窗口复用,不产生并发 metadata 请求。

Application

OpenProgram

Current version0.7.1
最新稳定版本0.7.1
OpenProgram 已是最新版本
Check nowView release notes

相同或更低的版本都显示 up to date,不允许 stable 降级。

Application

OpenProgram

Current version0.7.0
Available version0.7.1 · stable
PublishedGitHub Release date
此平台有可用的完整更新。
下载并打开安装包View release notes

Release Notes 在应用内按纯文本摘要显示;完整正文只打开固定 GitHub Release 页面。

Application

OpenProgram

Downloading1.52 GB / 2.45 GB · 62%
正在下载并验证完整安装包
正在下载 · 已禁用

主进程最多每 250ms 发布一次进度;完成时保证发送最终状态。

Application

OpenProgram

Available version0.7.1
下载校验和不匹配。临时文件已删除。
Retry downloadCheck nowView release notes

错误状态保留已验证的 release metadata,因此用户可直接重试,不需要先重新检查。

05

调度、持久化与恢复

自动检查只影响发现,不触发无人值守安装。手动检查始终强制刷新。

启动 + 30spackaged app 首次调度;已有缓存时先计算 due time。
检查成功 + 24hlastSuccessAt 决定下一次自动检查。
检查失败 + 6hlastAttemptAt 提供失败退避,不连续访问 GitHub。
resume / 设置变化清除旧 timer,最多立即检查一次,然后重新排程。
Persisted schema 1

update-state.json

保存 automaticChecks、lastAttemptAt、lastSuccessAt 和最近一次经过验证的 release metadata;使用临时文件加 rename。POSIX 可指定文件 mode,Windows 沿用当前用户目录 ACL。

Repair

损坏状态不能控制调度

无效 JSON、未知 schema、未来时间、版本/架构不匹配和缺失 release 都被归一化并重写。写入失败时不提交隐藏的内存设置变化。

06

信任边界

验证发生在获得权限的进程中;低权限界面只接收用于展示的投影状态。

Remote metadata

GitHub 响应默认不可信

HTTPS、固定 host、最多 5 次重定向、逐跳验证、大小与 body idle timeout;再次要求 draft === false 和 prerelease === false。

  • tag 必须是 vX.Y.Z
  • asset name 与 manifest basename 唯一
  • GitHub size 与 manifest bytes 一致
Renderer IPC

renderer 不能选择远端目标

preload 只暴露 getState、check、布尔设置、download、openRelease 和状态订阅。

  • 不接受 URL、path、hash、repository 或命令
  • 公开状态剥离 asset name/URL/SHA-256
  • 窗口关闭不能中止检查或下载
Downloaded files

完成校验前不激活

Desktop 使用随机 exclusive 临时文件并处理 short write;Windows 沿用目录 ACL 并额外验证 Authenticode。CLI 在新版本目录验证完整 runtime 后才替换 current。

  • 大小/hash/签名不符立即删除临时文件
  • archive path traversal 被拒绝
  • launcher 准备失败保留旧 current
Child process

版本化 installer 使用最小环境

managed upgrade 固定 repository 和目标 version,只传 PATH/HOME/temp/locale/proxy/XDG/OpenProgram state 等允许项。

  • 不继承 provider/API 凭据
  • 不继承 archive/checksum source override
  • --json 始终输出单一 JSON 文档
07

失败语义

每个错误都说明停止位置和保留内容,不用模糊的“更新失败”代替事务边界。

离线、超时、GitHub 限流
自动检查记录错误并按 6 小时退避;手动检查显示可重试错误。
应用和当前版本继续运行
Release / manifest 无效
拒绝响应,不下载;不回退 wheel、source archive、其他架构或旧版本。
最近一次有效 metadata 可保留
Installer body stall / size / hash / Windows 签名错误
中止 response,关闭文件,删除随机临时文件,恢复可重试动作。
未校验文件绝不打开
CLI archive / cold start / launcher 失败
installer 非零退出;不执行最后的 current 替换。
旧 runtime 仍为 current
切换成功但 worker 仍运行旧版本
命令明确报告 active runtime 已更新,并打印显式 restart 指令。
运行中请求不被隐式中断
08

安装类型决定更新路径

安装检测只回答当前运行方式,不创造功能不同的产品版本。

managed_release
→
正式 GitHub ReleaseDesktop bridge 或 complete runtime installer;immutable marker 优先。
source_checkout
→
显式 Git ref pipelinepreflight、checkout、依赖/构建、probe,必要时按开发合同重启。
unknown
→
稳定错误不猜测 PyPI、binary 或 Git 路径;提示使用正式 installer 或源码 checkout。

pip_wheel 不属于正式产品安装类型。开发者 wheel 可以作为构建输入,但不提供功能不完整的普通用户版本。

09

参考实现与采用方式

参考用于确定平台限制和状态模型;最终方案服从 OpenProgram 的 unsigned Desktop 与完整 runtime 合同。

采用

GitHub Releases API

latest stable metadata、Release Notes 和 assets。客户端仍对 stable 字段、tag、唯一资产和 manifest 做独立验证。

修改采用

Electron 更新生命周期

采用 check / available / download / error 状态模型;Desktop 完成边界为校验后打开平台 installer,不调用自动替换。

当前不采用

Electron autoUpdater

macOS 原生自动安装要求签名,Electron 不提供 Linux 内置 updater;与当前产物和平台合同不匹配。

不新增依赖

electron-updater

现有 Release manifest、Node/Electron API、Python 和 shell 已覆盖本阶段;新增包不能改变当前 Desktop 安装边界。

10

实现入口

链接分别指向当前主分支的公共兼容入口和正式实现;设计结论不依赖本页中的代码副本。

11

实现状态与验收证据

实现事实和仍需 Release 环境验证的事实分开记录。这里不保留日期化变更日志或 commit 清单。

Implemented

Desktop 完整路径

stable metadata、平台/架构选择、状态缓存、24h/6h 调度、最小 IPC、verified download、Windows Authenticode、Settings 状态和 Web fallback 已实现。

npm run check --workspace apps/desktop
Implemented

Managed CLI 完整路径

Release manifest gate、immutable installer、环境隔离、dry-run/JSON、真实 old→new current 切换与 launcher 失败保留旧版本已覆盖。

uv run pytest -q tests/component/config/test_formal_release_updates.py tests/component/config/test_distribution_release.py
Verified

安全与失败路径

逐跳 host 验证、metadata/download stall abort、short write、随机 exclusive 临时文件、checksum/size 清理、并发 Promise 和状态修复已验证。

apps/desktop/scripts/check-update-service.js
Verified

文档与发布门禁

双语安装/升级文档、Release latest 验证、文档构建、链接、Desktop/Web/TypeScript 与 workflow 静态门禁纳入发布检查。

python -m scripts.docs_site.checklinks
仓库发布记录

v0.8.1 acceptance record

仓库中的 v0.8.1 acceptance 记录了 runtime、CLI、Desktop、manifest 和 checksum 资产边界。源码版本不能证明当前 GitHub published release;本页不重新访问 GitHub latest,也不把历史远端资产检查当作当前运行中客户端验收。

已安装客户端验收

真实客户端更新交互

尚未操作当前用户窗口验证版本发现、平台 installer 打开、长时间运行与 sleep/resume 排程;这些交互不由发布资产 smoke 代替。Windows 还必须在正式 release runner 使用真实证书通过 Authenticode gate。

历史兼容性验收

v0.6.6 及更早版本的一次性过渡

历史记录中,Desktop 需要手动安装首个含 updater 的 v0.7.0 DMG;CLI/server 需要重新运行公开 installer。这是 v0.7.0 的迁移背景,不是当前 v0.8.1 的发布状态。

当前状态与历史背景

v0.7.0 是历史上首个包含 updater 的正式 release;当前源码和安装脚本版本基线为 v0.8.1。v0.6.6 及更早 Desktop 的迁移说明仍保留为历史背景;当前证据不包含前台客户端更新交互,因此 installed-client acceptance 仍待单独验证。