# 源码维护者的 GitHub Release 发布与恢复 本文对应 CFK-27,是源码工程工具说明,不是用户部署指南、独立 cfKanban CLI 或 Cloudflare 写入通道。安装 Skill 和升级 Instance 仍由各自独立计划执行。脚本不修改 tag、已发布 Release、现有附件或 Cloudflare 资源。 维护者可在本仓库调用项目级 [`$project-release`](../.agents/skills/project-release/SKILL.md),例如“准备下个 RC,先给范围与验证结果”或“完整发布指定 RC,并升级默认测试实例”。该技能组织下述工程流程,默认远端目标为持久的公开测试/演示实例 `cfkanban.dev`;GitHub stable 是用户自部署的推荐发行。它不属于对外四个 cfKanban Skills,不随其 bundle 分发。只发布 GitHub、同步实例和更新本机插件可分别指定,技能名或默认目标本身不授予写入权限。 ## 发布前准备 1. 对当前源码运行 `npm run validate`,审查 staged set,然后按当前授权提交并推送准确 tag。脚本要求远端 tag 已存在且指向完整的预期 commit;不会隐式创建 tag。 2. 用现有 `build-release-bundles.mjs` 与 `generate-release-metadata.mjs` 构建和生成元数据。GitHub 下载路径使用 `urlLayout=flat`。这一步仍需校验可重现构建和打包白名单。 3. 准备一个独立的平铺目录,只包含以下六个非秘密文件:`prerelease.json`(稳定版使用 `stable.json`)、`cfkanban-release-.json`、`cfkanban-skills-.zip`、`cfkanban-service-.zip`、`install.md`、`install.zh-CN.md`。任何额外文件(包括 Repo scope)、目录或附件 symlink 都会拒绝。不要把源码仓根目录作为上传目录。 4. 使用已安装并完成 GitHub 登录的 `gh`。脚本复用 gh 身份,不读取或输出 Token,不执行 OAuth 登录,不把 Cloudflare Credential 传给 gh。网络失败、权限失败和读取超时都不是“Release 不存在”的证据。 5. 创建非秘密 config JSON,字段为 `repository`(固定 `breakstring/cfKanban`)、准确 `version`、40 位 `commit`、上传 `directory` 的绝对路径、完整 `notes` 字符串。配置不放进上传目录,不提交本机绝对路径。 产品版本以 `release/version.json` 为源码声明。同步 plugin metadata 与当次 `release/config/.json`,重新生成 OpenAPI 并构建 Worker/Web;打包会拒绝文档错版本、缺失或变化的构建入口。OpenAPI `info.version` 对应产品发行,API 的 `service_version` 则独立来自 `contracts/service-api.json`,schema 由 migration manifest 决定。历史 tag、migration SQL、发行配置和已发布工件不原地修改。 新发行清单使用 manifest schema 2(pointer 仍为 1),要求支持独立 API 声明的部署技能;旧技能应在正常发现/校验入口拒绝。先完成获准的本地技能/宿主更新,再执行新 Service 工件部署,不能绕过未知 manifest 格式检查。 普通用户 README 和双语部署/加入/安装指南保持版本无关,不为每次发行替换数字。开发后续功能前将声明切到下一个准确预发行版本;本地隔离验证后,公开 RC 并按授权在 cfkanban.dev 验收,仅在正式发行时推进 stable。公开测试实例仍保留真实数据和既有迁移、权限、恢复保护。 `main` 同时是支持 Git 来源的 Agent 宿主的默认正式渠道,必须保持在已公开并校验的正式提交;目录型宿主使用同一发行的完整已验证 bundle,不要求 Codex 或 marketplace。所有开发及规则/文档修改先进入 feat/fix 等专门分支。项目主目录切换分支即可,worktree 只按并行或隔离需要选择。发布工具固定的是 commit/tag,不要求从 main 发版;RC 从开发分支发布,不推进 main 或 Latest。 RC 转正式版时,从已验收候选的准确源码基线收敛版本与说明,重新构建并校验正式工件;不直接改 RC zip 名称或已发布 Release 属性。同步网站须部署正式工件并核对 `release_version`。若网站已运行更新 RC,先核对降级和 schema 影响,不因发布旧系列修复版而自动降级网站。 正式候选在固定 tag 和构建前先包含目标 main。若冲突解决、squash 或合并产生新内容,重新核对最终 tree 并验证受影响行为,不把分支旧验收当作合并结果的证据。先完成正式 `stage → publish` 和匿名工件/Latest 读回,再按已有授权把 main 快进到同一正式 commit;未获准的 Git 操作另行展示具体目标,不由本脚本自动执行。不要先合 main 再准备发布,也不要用 force push 处理发行漂移。 Git 分支与 GitHub Latest 不能原子推进。发布后、main 推进前的短暂不一致须在安装校验中识别;main 暂时保留上个正式版,不能据此宣称渠道更新完成。中断后读取远端 main、正式 tag 和 Latest,只续做仍匹配的步骤;若其他发行已推进,停止核对,不回退 Latest 或重写 tag。发行交付增加 main/tag/manifest/plugin 一致性证据,实例及本机技能读回仍分别报告。 ## 简洁双语发行说明 后续发行以[可复用模板](../.agents/skills/project-release/templates/release-notes.md)为起点;[1.5.0 本地示例](../.agents/skills/project-release/examples/1.5.0.md)展示同一格式,只供后续编写参考,不替换历史 `release/notes/1.5.0.md` 或公开 Release 正文。公开页面先帮助用户判断收益与升级影响,完整验证输出留在对应 Issue、SPEC 或研究记录中,以链接补充。 - 中文、英文各写一句版本概述和 3–5 条用户或管理员可感知的收益,覆盖相同事实。合并同类变化,说明新行为带来的用途;API 字段、内部模块、预算、测试数量和逐项实现细节只在影响用户操作或升级时进入正文。 - 明确比较基线。正式版累计比较上一个正式版与本次正式候选,不只比较最后一个 RC;已验收 RC 可以作为验证来源另行链接。RC 明写相对上一个 RC 或上一个正式版的范围,连续 RC 只描述新增变化时也注明这一点。不要按版本号猜测上一个发行,先核对实际 tag、发行顺序和源码。 - 用完整 commit SHA 固定比较两端。先核对基线是候选的祖先,再从实际 Git 区间确认收益、migration 和兼容性;`main`、`HEAD` 等浮动引用只用于寻找候选,不能留在最终 commit 或 compare 链接中。尚未固定最终发行 commit 时保留本地草稿;固定后补齐发布 config 的 `notes`,不为嵌入自身 SHA 改写已固定的 commit。 - 在双语「升级与已知限制 / Upgrade and known limitations」中简短说明必须执行的迁移、兼容性或重要限制;没有新增 migration 时明确说明,并提醒仍需检查真实 ledger/schema。不能因产品版本变化虚构 schema 升级,也不能将本地验收写成线上结果。 - 正文末尾只放一份默认折叠的「相关提交 / Commits」。选择实际支撑摘要收益的提交;同一提交支撑多条收益只列一次,按实际 Git 区间的 `--reverse --topo-order` 顺序排列。提交链接使用完整 SHA,标签可用短 SHA 与原提交标题;compare 链接涵盖完整基线到候选区间,包括未逐项列出的发行准备提交。不要把中英文提交列表重复一遍,也不自动粘贴全部日志代替收益摘要。 在准确源码仓库本地核对以下内容;将占位符替换为已核验值,不根据 commit 标题自动生成或执行命令: ```sh git rev-parse '^{commit}' '^{commit}' git merge-base --is-ancestor '' '' git log --reverse --topo-order --format='%H %s' '..' git diff --stat '..' git diff '..' -- migrations/ contracts/service-api.json ``` 发布前检查双语标题、3–5 条收益的语义一致性、真实升级要求、提交去重与顺序,以及每个 commit/compare 链接的完整 SHA。示例中的 `1.4.0` 到 `1.5.0` 完整区间共有 5 个提交,折叠列表选取其中支撑收益的 3 个;migration manifest 从 schema `13` 到 `14`,唯一新增 SQL 为 `0014_principal_theme.sql`。这些是示例的历史事实,不能复制成下一版默认基线或升级要求。 最终 Markdown 整体作为 config 的非空 `notes` 字符串,经人工检查后执行 `inspect`。模板和本地示例不参与自动上传;发布脚本继续原样使用该正文,`notes` 的全部内容仍绑定 `plan_digest` / `approvedPlanDigest`,已存在 draft 的正文必须逐字匹配。修改概述、链接或折叠区也需要重新 `inspect`;不得借格式调整更新已公开 Release、tag、manifest 或工件。 ## 三个独立动作 ```text node scripts/publish-github-release.mjs inspect node scripts/publish-github-release.mjs stage node scripts/publish-github-release.mjs publish ``` `inspect` 是只读 preflight:本地先复用 `release verify` 的 pointer/manifest/bundle 校验实现,再核对完整版本、channel、publisher、准确 GitHub 下载 URL、六份 name/size/SHA-256 和远端 tag commit。它输出 `plan_digest`,以及 Release ID、draft 状态和缺少的附件。已发布版本还会进行匿名公网下载验证,不能只用本地文件自证成功。 只有确认准确 repository、tag/commit、notes、六份附件摘要与本次远端写入范围后,才把输出摘要放入 config 的 `approvedPlanDigest`。`stage` 与 `publish` 都要求准确匹配;任何文件、notes、版本或 commit 变化都需要重新 inspect。此字段只是工程工具的计划一致性门槛,不能替代用户/Agent 宿主授权。它不把本机路径写进远端 metadata。 `stage` 在不存在 Release 时只创建空 draft,之后逐个上传缺失附件。已经存在的 draft 必须具有相同 tag、标题、notes 和 prerelease 标记。每次上传前后都重新读取同一 Release ID 和完整分页附件清单,核对 name、size、`state=uploaded` 和 `digest=sha256:...`;stage 成功也不会公开 Release。 `publish` 不补传附件。仅当当前 draft 的六份附件全部匹配、tag 未漂移时才公开。随后重新读取公开状态,匿名下载六份文件,核对 size/SHA-256,并用**刚下载的** pointer、manifest 和两个 bundle 执行同一 `loadAndVerifyRelease`。bootstrap 两份 MD 的摘要也独立核对。GitHub signed redirect 仅在内存使用,不进入报告。 正式版公开时设置 GitHub `make_latest=true`,预发行版保持 `false`。正式 `publish` 还从固定 `https://github.com/breakstring/cfKanban/releases/latest/download/stable.json` 发现入口重新读取并校验 manifest,要求准确版本与本次工件一致。该入口只负责发现;安装/部署计划继续固定不可变目标。Latest 校验失败不能报告完整发布成功,不删除或覆盖已公开发行;先只读检查,不能自动将已经前进的 Latest 降回旧版。`inspect` 对历史正式版只校验其不可变资产,不要求它仍是 Latest。 ## 中断与恢复 - 创建、单附件上传或 publish 超时:保持相同输入,先重新 `inspect`。不把 CLI 非零当作远端未提交,不自动换版本、删除 draft 或重建 Release。 - 对已确认的同一 draft 重新 `stage`:已匹配的附件跳过,只补缺失项。即使进程在服务端提交后、收到响应前退出,下一次也以远端读回为准。 - publish 响应丢失或公网下载失败:再次 inspect/publish 已发布版本只读校验,不再次发布,不删除公开版本。公网验证失败不能报告完整发布成功。 - 同名附件摘要/size 不符、重复/额外附件、`starter` 等非 uploaded 状态、缺少 digest、draft metadata 漂移:停止,交由维护者检查;脚本没有 delete、clobber 或接管分支。 - 一次只允许一个维护者执行同一 tag 的发布。GitHub API 不提供覆盖整个“核对附件→公开”序列的事务;逐次读回不能替代上层串行约束。外部并发改动被发现时停止,不能声称拥有远端原子锁。 - 本地文件也必须保持不变。脚本在写入前反复检查;它不能防止另一进程恰在检查与 gh 读取文件之间改写内容。发布前的远端摘要核对会阻止这类内容被当作已验证附件公开。 ## 验证范围 `node --test scripts/tests/release-publication.test.mjs` 使用隔离假 GitHub 后端覆盖创建/上传/发布提交前后中断、每个附件缺失恢复、读失败、tag 漂移、摘要/数量/状态冲突、本地漂移、重复执行,以及匿名下载的实际字节校验;它不会写 GitHub。 故障注入单测不是 GitHub 真实上传中断演练。首次实际使用时仍应记录 draft Release ID、每个附件读回和公开下载结果;CFK-27 在真实恢复证据补齐前保持进行中。此源码工具不包含在 Skill/Service bundle 白名单里,单独新增它不要求升级 Worker 或用户 Skill。 正式发行专项:`node --test scripts/tests/release-discovery.test.mjs scripts/tests/release-version.test.mjs scripts/tests/release-publication.test.mjs`。它覆盖 stable/显式 RC 发现、来源/摘要/重定向与大小限制、无静默回退、正式 Latest 推进与读回、历史发行检查、构建与声明版本一致性。完整跨任务 Codex 加载及宿主回退仍使用 CFK-149 的真实验收,不能由 mock 结果替代。 平台依据(2026-09-05):[GitHub Release API](https://docs.github.com/en/rest/releases/releases)、[Release asset 的 state/size/digest 合同](https://docs.github.com/en/rest/releases/assets)、[gh release upload 的 clobber 删除语义](https://cli.github.com/manual/gh_release_upload)。工具使用 GitHub API 版本 `2022-11-28`,不依赖 gh 的人类错误文案判断提交结果。