# MoneyPal 1.0.0 发布验收记录 此模板只记录版本、命令、环境与结果;不得写入账本内容、用户路径、账户名或交易说明。每个平台均从 registry 的 `next` 标签安装两个包,使用临时验收账本执行显式 `setup-runtime`、初始化、校验、查询、预览、确认提交和卸载后的账本哈希比对。 ## 发布流程 MoneyPal 按一个产品管理版本,一次发布生成 `dsh-moneypal` 与 `mcp-moneypal` 两个包。版本改动经过审查,合入 `main` 后手动打 tag,Release 工作流自动发布: 1. 用 `npm version <明确版本> --no-git-tag-version` 更新根版本(`X.Y.Z` 或 `X.Y.Z-rc.N`)。该命令更新根 `package.json` 与 `package-lock.json`,不自动提交、不打 tag。 2. 审查差异,提交 PR,经 Test 通过后以 squash 方式合并到 `main`,然后获取最新 `main`。 3. 对已合入的版本提交创建 annotated tag:`git tag -a v<版本> <提交SHA> -m "v<版本>"`。 4. 只推送本次 tag:`git push origin refs/tags/v<版本>`,不要使用 `git push --tags`。 5. Release 工作流只接受 `push.tags: ['v*']`:校验 tag 与源码,`npm ci` → 完整构建一次 → `node dist/src/main.js setup-runtime` → `npm run verify:release:built`(真实打包、内容检查、隔离安装与入口加载)→ `npm run release:preflight`,然后按 DSH、MCP 顺序把本次验收过的两个 tgz 发布到 npm `next`。 `next` 始终由工作流写入;`latest` 保持人工提升,工作流绝不改动它。`CHANGELOG.md` 只保留历史内容,后续发布说明手工维护,不再有自动 changelog。 ## 发布顺序与回退 1. 运行 `npm run test:release` 与干净 checkout 中的 `npm run release:preflight`。验收记录应保存当次命令的 pass、fail、skip 数量;存在 skip 时必须说明其运行时或环境原因。 2. 推送 tag 后确认工作流结果:两个包都发布到 `next` 才视为成功。工作流结果就是 npm 是否成功的唯一依据。 3. 在五个平台及真实 DSH/MCP 宿主完成下方记录;没有 registry 权限或目标平台时,运行 `npm run release:acceptance` 生成机器可读的待人工项并停止。 4. 稳定版仍先发到 `next`;两个包均完成 registry 复验、bridge 哈希与公共导出一致后,才执行 `npm run release:promote`。该命令默认 dry-run 只打印 `npm dist-tag add @<版本> latest`;复验通过后加 `--apply` 才同时提升 `latest`,并在提升后重新核验两个包的 `latest`。两次 dist-tag 更新不是原子操作,部分失败会如实报错,需要人工核对后再重试。 5. 任一包发布或复验失败时,不提升任何 `latest`。回退是停止提升并修复后发布新的候选;不得用 npm 覆盖已发布版本,也不得触碰正式账本或共享运行时。已提升的 `latest` 可通过 `npm dist-tag add @<旧版本> latest` 移回,但只作事故回退,不作为常规路径。 ## 发布失败重试 在新 tag 工作流的原运行中选择 **Re-run failed jobs**。不要重新推送、删除或移动 tag,也不要改用其他入口重跑: - 工作流要求 tag 解析出的提交等于本次事件提交、该提交在 `main` 历史中,且 tag 去掉 `v` 后与根版本、`package-lock.json` 两处版本一致。 - 发布脚本先检查两个包:registry 明确返回 E404 才发布;已存在且 `dist.integrity` 与本地 tgz 的 SHA-512 相同就跳过;integrity 不同或缺失则整体失败;网络、鉴权、限流和响应解析错误都当作失败,不当成 E404。 两个包的发布不是原子事务。第一包成功、第二包失败时工作流失败,重跑失败的 job 即可:已存在且字节一致的包安全跳过。若重试发现已发布包与重新生成的产物不同,停止并发布新版本,不绕过完整性检查。旧 Release Please 工作流的运行不适用这条重试说明。 ## 维护者一次性配置 1. 合并方式使用 Squash merge。 2. 把 Test 设为 `main` 的必需检查,并要求分支为最新。 3. 为 `dsh-moneypal` 和 `mcp-moneypal` 分别配置 npm Trusted Publisher:owner `ding112`、repo `MoneyPal`、workflow `release.yml`、environment 留空,允许 `npm publish`。`release.yml` 文件名未变,无须因流程调整改动 Trusted Publisher,但两个包都必须已授权该文件直接发布。 4. 本期没有自定义 Release 附件,可以开启 Immutable Releases;它不会让 npm 发布变成原子事务。 5. 不删除或修改现有 registry 版本与 dist-tag,尤其不补发 `mcp-moneypal@1.0.0-rc.3`。 迁移到手动版本流程后,下列远端设置需要人工清理,不由本仓库改动: - 关闭或删除遗留的 Release Please PR 与 `release-please--branches--main--components--moneypal-workspace` 分支。 - 检查 `main` 的必需检查是否仍包含只存在于旧流程的检查名;确认保留的 `test` job 名称仍与分支保护一致。 - 确认两个包的 Trusted Publisher 仍指向 `release.yml`(文件名未变)。 npm 使用 OIDC,工作流不读取 `NPM_TOKEN`,仓库也不需要该 secret。OIDC 的工具链要求由 Node 24 与 npm 11.14.1 满足。 ## 首次上线验收(历史:Release Please 流程) 以下七步是 1.0.0-rc.4 上线时按旧 Release Please 流程执行的记录,保留作历史依据,不代表当前手动版本流程已按此验证: 1. 合并实现 PR 后只生成 `1.0.0-rc.4` 的 Release PR,不发布 `1.0.0-rc.3`。 2. 人工关闭并重新打开该 Release PR,确认最新提交的 Test 通过。 3. 合并 Release PR。 4. 确认 `v1.0.0-rc.4`、GitHub Prerelease 与构建 SHA 一致。 5. 确认两个 npm `1.0.0-rc.4` 均存在、provenance 可用,且正常首次发布后两个包的 `next` 都是 `1.0.0-rc.4`。 6. 确认原有 `latest` 未被此次工作流改动。 7. 使用 tag 手动重试一次,确认两个包 integrity 匹配并安全跳过。 ## 1.0.0-rc.4 发布记录(2026-09-09,历史:Release Please 流程) 首次上线按上述七步执行,结果如下: 1. 实现 PR #2(`feat: v1.0.1 界面改进、专家包与自动发布流程`,正文含 `Release-As: 1.0.0-rc.4`)合并到 `main` 后,Release 工作流只生成了 `1.0.0-rc.4` 的 Release PR,没有发布 `1.0.0-rc.3`。 2. 人工关闭并重新打开 Release PR 触发 Test,最新提交通过。 3. 合并 Release PR 后创建了 `v1.0.0-rc.4` tag 与 GitHub Prerelease。 4. 两个 npm 包 `1.0.0-rc.4` 均已发布到 `next`;`latest` 未被改动(`dsh-moneypal` 仍为 `1.0.0-rc.3`,`mcp-moneypal` 仍为 `1.0.0-rc.2`)。 5. 使用 tag 手动重试一次:两个包 integrity 与本地 tgz 一致,安全跳过,运行全绿。 这次首次发布暴露并修复了三个工具缺陷: - 发布步骤用 `| tee` 写结果文件,而运行器的 bash 只带 `-e`、不带 `pipefail`,发布脚本的非零退出被 `tee` 掩盖;现改为 `set -o pipefail`。 - Summary 步骤对空的结果文件执行 `JSON.parse` 会崩溃;现改为只在文件非空时解析,解析失败时输出提示而不中断。 - 发布后复验读取 `npm view` 可能命中发布前的 404 缓存或 registry 可见性滞后,导致"发布成功但复验失败"的误报;现增加 `--prefer-online`,并把复验窗口改为最多 6 次、间隔 5 秒(最长 30 秒)。 ## 自动化门禁与已验证证据(agent 验收) | 门禁 | 命令 | 结果与证据 | | --- | --- | --- | | 全量测试与打包验收 | `npm run test:release`(只构建一次) | 记录当次测试结果与 skip 原因;覆盖 DSH 与 MCP 宿主仿真的工具名、参数 schema 与领域 DTO 深度相等(`dist/test/dsh-plugin.test.js`、`dist/test/mcp.test.js`),并验证两个 tarball 可隔离安装。 | | 版本一致性 | `dist/test/release-gates.test.js` | 根版本符合 `X.Y.Z` 或 `X.Y.Z-rc.N`;lockfile 顶层与 `packages[""]`、两个生成包都与根版本一致,不依赖某个具体候选号。 | | 无 hledger 遗留 | `npm run release:preflight` 扫描 | 活跃源码、测试、文档与两个发布包无旧名称与 `.journal` 文件 | | 稳定提升安全 | `dist/test/release-promote.test.js` | 使用独立 fixture 验证:RC 版本被拒绝(非零退出)、dry-run 不写 dist-tag、复验失败时不写、部分失败时如实报错 | | 双包发布决策 | `dist/test/publish-release.test.js` | 使用注入的假 npm 执行器覆盖两包缺失、单包已存在、两包已存在、integrity 冲突、E404、网络/鉴权/限流/解析错误,以及第二包失败后的重试路径 | | tgz 保留出口 | `dist/test/release-tarballs.test.js` | 使用假 npm 走完验收:通过时导出的字节与验收结果一致,任一验收失败不导出 | | 工作流契约 | `dist/test/release-workflows.test.js` | Test 只读权限、只构建一次、先准备运行时再验收;Release 只接受 `v*` tag、最小权限(`contents: read` + `id-token: write`)、固定 Action SHA、ref 经环境变量传入、验收先于发布、产物目录经 step 级环境变量传递、禁止直接发布、不读取 `NPM_TOKEN` | | 机器可读验收记录 | `npm run release:acceptance` | 本机(darwin-arm64)生成 `artifacts/releases/<版本>/darwin-arm64.json`,因未声明 registry 就绪而安全停止(status: blocked) | `npm run test:release`(`npm run build` 一次后执行 `npm run verify:release:built`)严格要求真实运行时可用且兼容;两个工作流都会在完整构建一次后执行 `node dist/src/main.js setup-runtime`,缺少运行时的环境必须明确失败,不允许跳过。 ## 手动版本 + tag 发布流程改造验证记录(2026-09-09) 环境:macOS arm64、Node v24.14.0、npm 11.14.1、已有 MoneyPal 托管运行时(Python 3.11.11 / Beancount 3.2.3 / beanquery 0.2.0)。 - `npm run build`:通过(一次完整构建,产出两个发布包与专家 ZIP)。 - `node dist/src/main.js setup-runtime`:通过,`source: managed`、`available: true`、`compatible: true`。 - `npm run verify:release:built`:集成层 112 项、发布层 40 项,全部通过,0 失败 0 跳过;真实 tarball 打包、内容检查、隔离安装与入口加载通过(`ok: true`),并导出到 `MONEYPAL_TARBALL_OUTPUT`。 - 干净 worktree(`git worktree add --detach` 指向包含本次最终变更的提交)中 `npm run release:preflight`:通过(version 与根清单一致,两个发布包的 bridge 哈希一致)。 - 工作流契约测试:`dist/test/release-workflows.test.js` 2 项通过;`dist/test/release-gates.test.js` 5 项通过。 - 未执行真实 npm 发布、版本提升,未创建或推送 tag。tag 触发、OIDC 发布与 **Re-run failed jobs** 重试只能在首次真实 tag 发布时观察,本次未验证。 ## 历史实现验证记录(Release Please 流程) 在干净 worktree(`git worktree add --detach` 指向实现提交)中执行,环境为 macOS arm64、Node v24.14.0、npm 11.14.1、`HOME` 指向临时目录并用 `node dist/src/main.js setup-runtime` 安装托管运行时(Python 3.11.11 / Beancount 3.2.3 / beanquery 0.2.0): - `npm ci`:通过。 - `npm run test:fast`:36 项通过;`npm test`:112 项通过(`build:base` 下不再依赖 `dist/packages`)。 - `npm run test:release`:集成层 112 项、发布层 39 项,全部通过,0 失败 0 跳过(真实运行时可用,`run-release-tests.mjs` 的严格检查通过)。 - `npm run release:preflight`:通过(version 与根清单一致,两个发布包的 bridge 哈希一致)。 - GitHub Actions:Test 工作流在 ubuntu-24.04 / Node 24 上通过(PR 标题检查 → `setup-runtime` → `npm run test:release`)。这一步同时暴露并修掉了两个只在 Linux CI 上出现的缺陷:bridge 子进程提前退出时 `stdin` 的 EPIPE 变成未捕获异常;取消后终止原因被后续超时计时器覆盖。 - `scripts/publish-release.mjs` 只读冒烟:对 registry 已存在的 `dsh-moneypal@1.0.0-rc.3` 传入字节不同的 tgz,脚本在检查阶段如实报告 integrity 不一致并以非零状态退出,没有发布任何包;未发布版本的 `npm view` 返回 `npm error code E404`,与脚本的 E404 判定一致。 - 未执行真实 npm 发布、版本提升或 PR 合并。 ## 已知限制与阻塞 - Actions 环境无法在本地完整验证:Test 工作流可通过 PR 验证,Release 工作流只能在推送 tag 后观察 Actions run,失败时用原运行的 **Re-run failed jobs** 重试。 - 双包发布不是原子事务,也没有自定义 Release 附件或 SBOM。 - 遗留的 Release Please PR、`release-please--branches--main--components--moneypal-workspace` 分支和只存在于旧流程的必需检查需要人工清理,见“维护者一次性配置”。 - 本环境没有 npm registry 写入凭证,五平台 registry 安装验收无法在 agent 侧执行。 - 当前机器仅为 macOS arm64;macOS x64、Linux x64、Windows x64/arm64 需目标平台或 CI。 - 本机未配置真实 MCP 宿主;DSH Web 宿主已存在,但以注册表 RC 安装的实机验收需在 RC 发布后进行,且会改动宿主配置,由人工执行。 - 本地已有 MoneyPal 托管运行时(Beancount/beanquery 就绪),可直接支撑后续流程的 `setup-runtime` 与查询验证。 - 已有真实 Python 运行时测试若因环境缺失而 skip,必须在记录中写明原因,不能据此宣称完成五平台验收。 - 当前分支的 `npm run test:fast` 与 `npm test` 只执行 `build:base`,因此不生成 `dist/packages`;`install-preset` 用例导入发布包的 `dist/packages/dsh-moneypal/dist/src/install-preset.js`,已按测试分层标准移入发布层 `test:release:built`。 ## 平台矩阵 | 目标 | Node | Python | Beancount | beanquery | tarball/registry | DSH | MCP | 结果/证据文件 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | macOS arm64 | v24.14.0 | 托管运行时就绪 | 3.2.3 | 就绪 | 待 registry | 待验收 | 待验收 | `artifacts/releases/<版本>/darwin-arm64.json` | | macOS x64 | 待填 | 待填 | 待填 | 待填 | 待填 | 不适用 | 待填 | 待填 | | Linux x64 | 待填 | 待填 | 待填 | 待填 | 待填 | 不适用 | 待填 | 待填 | | Windows x64 | 待填 | 待填 | 待填 | 待填 | 待填 | 不适用 | 待填 | 待填 | | Windows arm64 | 待填 | 待填 | 待填 | 待填 | 待填 | 不适用 | 待填 | 待填 | 机器可读结果由 `npm run release:acceptance` 写入 `artifacts/releases/<版本>/<平台>.json`;成功执行时应把每个阶段的命令与结果写入同一文件。当前脚本在未显式声明 registry 就绪时安全停止,防止本地 tarball 被误作 registry 验收。 ## 人工宿主结论 - 自动化宿主仿真(agent 侧已完成):`dsh-plugin.test.js` 验证 DSH 注册六个稳定只读工具、保持输入 JSON Schema 与领域 DTO;`mcp.test.js` 验证 MCP `tools/list` 与 DSH 同源、`preview -> 确认 -> commit` 两阶段写入与 DTO 深度相等。真实宿主路径仍待注册表 RC 安装验收。 - DSH Web(macOS arm64):RC 安装、托管预设、六项只读工具、余额抽屉、取消与确认写入、运行时缺失提示:待验收(RC 发布后执行)。 - MCP 宿主:RC 安装、工具发现、六项查询、preview → 人确认 → commit、取消/一次消费、运行时缺失提示:待验收。 - 卸载:DSH 侧先执行 `dsh plugin --profile web exec dsh-moneypal uninstall-preset` 移除托管预设、再执行 `dsh plugin --profile web remove dsh-moneypal` 移除包;MCP 侧移除配置条目与全局包;比较验收账本哈希,并确认共享 MoneyPal 运行时未被自动删除:待验收。