| 1 | # Release candidates |
| 2 | |
| 3 | The protected `main-v2` control workflow prepares an immutable candidate after |
| 4 | reviewed Stable Notes are embedded. `Prepare release candidate` with `version` |
| 5 | builds the shared CLI/npm files, signs Desktop files, runs final-package native |
| 6 | acceptance, and seals a record. `Publish release candidate` accepts only that |
| 7 | record, checks its provenance and bytes, and requires one `release` approval |
| 8 | before creating the three tags and publishing. `recover` reuses the same sealed |
| 9 | files; it must not rebuild or sign them. |
| 10 | |
| 11 | Rendering the notes credits people: each `refs` number that is a pull request |
| 12 | gains `by @author`; an issue gains `fixed in #M by @author` for each merged pull |
| 13 | request GitHub links as closing it. A closing Contributors list names those |
| 14 | humans once, in first-appearance order. Issues fixed by a direct push and bot |
| 15 | authors are left plain. The lookup needs a GitHub token (`GH_TOKEN`, else |
| 16 | `gh auth token` locally) and `issues: read` plus `pull-requests: read`. |
| 17 | Transient failures (5xx, rate limits, dropped connections) are retried with |
| 18 | backoff; one that persists, or an authorisation failure, fails the render with |
| 19 | a `release_credits.*` code instead of publishing notes without that credit. A |
| 20 | number that names a discussion or nothing stays plain with a warning. |
| 21 | |
| 22 | The seal job renders the notes once into `evidence/release-notes.md` inside the |
| 23 | candidate payload and records it as `notes.renderedPath`, bound to |
| 24 | `notes.renderedSha256`. Promotion verifies the payload and publishes those |
| 25 | bytes without calling GitHub, so an author renamed, an issue newly linked to a |
| 26 | fix or an API outage after sealing cannot change or block what ships. A |
| 27 | candidate sealed before this has no `renderedPath` and is rendered offline from |
| 28 | its own source, as before. |
| 29 | |
| 30 | ## Public checks / 公开渠道检查 |
| 31 | |
| 32 | Candidate preparation and publication execute offline identity, resolver-output, |
| 33 | archive-integrity, atomic-tag, and publication-ledger contracts before expensive |
| 34 | work or approval. A separate bounded HTTP check reads the public Stable manifest |
| 35 | from the runner without credentials. It accepts the current older version before |
| 36 | a new release, but rejects HTTP errors, browser challenges, and malformed JSON. |
| 37 | |
| 38 | 候选准备与发布在昂贵任务或审批前执行离线契约测试,覆盖身份、解析器输出、归档校验、原子标签及台账。 |
| 39 | 另从 runner 以无凭据请求检查公开 Stable 清单;发新版前允许清单仍为旧版,但 HTTP 错误、浏览器挑战及 |
| 40 | 无效 JSON 必须失败。离线契约测试与在线可访问性检查相互独立。 |
| 41 | |
| 42 | After every publisher succeeds, postflight verifies the tags, the CLI and |
| 43 | Desktop GitHub Releases, npm, the Desktop Stable manifest on `dl.reasonix.io` |
| 44 | and the Homebrew cask, then attaches `release-event.json`. Publication is |
| 45 | complete when those product surfaces are verified. A 90-day ledger records the |
| 46 | source/control SHA, observed surfaces, and the exact failed stage. On recovery |
| 47 | a proven newer Stable pointer is preserved, never a failed HTTP observation. |
| 48 | |
| 49 | 发布任务全部成功后,postflight 核验三个标签、CLI 与桌面端 GitHub Release、npm、`dl.reasonix.io` |
| 50 | 上的桌面端 Stable 清单和 Homebrew cask,再附上 `release-event.json`;这些产品渠道全部核验通过即发布完成。 |
| 51 | 90 天台账保留产品/控制 SHA、已验证渠道及失败阶段。恢复时只有确证存在更新的 Stable 指针才予以保留, |
| 52 | HTTP 失败不会被当作“新版已发布”。 |
| 53 | |
| 54 | The website is not part of this pipeline. It is built and deployed from the |
| 55 | `website` branch, and its changelog renders from that branch's own release |
| 56 | catalog, so a new version's page is released there, not here. |
| 57 | |
| 58 | 官网不在此流程内:它由 `website` 分支独立构建和部署,更新日志由该分支自己的发布目录渲染,新版本页面在那里发布。 |
| 59 | |
| 60 | For qualification without publication, dispatch `Prepare release candidate` on |
| 61 | protected `main-v2` with `version` and `rehearsal=true`. An already reviewed |
| 62 | version may be used for this isolated run. It uses separate |
| 63 | `release-candidate-rehearsal-*` artifacts, records `purpose=rehearsal`, and |
| 64 | cannot pass the normal publish resolver or payload verifier. The Desktop child |
| 65 | accepts the existing version tag only in this non-publishing mode. The run must |
| 66 | still complete source CI, signing, and native acceptance. It creates no tags, |
| 67 | GitHub Releases, npm packages, Homebrew updates, or R2 pointers. |
| 68 | |
| 69 | After sealing, run `Verify release candidate rehearsal` on `main-v2` with its |
| 70 | candidate ID. This independent workflow downloads the exact record and payload |
| 71 | artifact IDs. It checks the GitHub archive digest, protected producer run, |
| 72 | OIDC file attestations, sealed file digests, and native acceptance receipts. |
| 73 | Its 90-day report binds the producer and verifier runs without compiler or |
| 74 | signing credentials. The verifier proves reuse of the same signed bytes; it is |
| 75 | not a publication or a substitute for a later formal release's public checks. |
| 76 | |
| 77 | The candidate payload lasts 30 days and its record/evidence 90 days. If the |
| 78 | payload expires before publication, prepare a new candidate. The release |
| 79 | skill's public postflight remains the authority for tags, npm, Desktop |
| 80 | updates, and Homebrew after an authorized publication. |
| 81 | |
| 82 | ## Tag publisher identity / 标签发布身份 |
| 83 | |
| 84 | Publication requires the repository secret `RELEASE_TAG_TOKEN` and variable |
| 85 | `RELEASE_TAG_ACTOR` (the token owner's login). Use a maintainer already allowed |
| 86 | by the release-tag rulesets, with repository Contents write access. Do not copy |
| 87 | a workstation login token or repurpose another integration's secret. Configure |
| 88 | this dedicated credential before enabling publication; there is no default |
| 89 | `GITHUB_TOKEN` fallback. The token is available only to protected `main-v2` |
| 90 | identity-check and activation steps, not candidate builds. |
| 91 | |
| 92 | 发布前需配置仓库 secret `RELEASE_TAG_TOKEN` 和变量 `RELEASE_TAG_ACTOR`(凭据所有者登录名)。 |
| 93 | 使用标签保护规则已允许的维护者身份,并授予目标仓库 Contents 写权限。不要复制工作站登录凭据, |
| 94 | 也不要挪用其他集成的 secret。未配置时发布预检直接失败,不回退到默认 `GITHUB_TOKEN`。 |
| 95 | 该凭据只在受保护 `main-v2` 的身份检查及标签激活步骤使用,不传给候选构建。 |
| 96 | |
| 97 | Before approval, the workflow reads the authenticated actor, repository push |
| 98 | access, and inherited rulesets. Activation repeats those checks, binds the actor |
| 99 | ID to preflight, and uses the same credential for one atomic three-tag push. |
| 100 | Unknown matching rules fail closed. This is an early policy check, not proof of |
| 101 | every token scope or a reservation: GitHub remains authoritative at push time. |
| 102 | No rule is disabled, no tag is moved, and an existing complete recovery identity |
| 103 | is verified without pushing. Rotate the dedicated token when required. |
| 104 | |
| 105 | 审批前检查真实凭据身份、仓库推送权限及继承的规则集;激活前再次检查,并与预检的用户 ID 绑定。 |
| 106 | 三个标签仍以同一身份原子推送。无法判断的匹配规则按失败处理。预检不能证明全部 token scope, |
| 107 | 也不锁定远端状态,推送时仍以 GitHub 的实时判断为准。已有完整标签的恢复只校验,不重新推送。 |
| 108 | 保护规则不变,已发布标签不移动;按需轮换专用 token。 |
| 109 |