---
license: "MIT. Translation of the Hermes Agent documentation, Copyright (c) 2025 Nous Research. See https://wiki.winsmux.dev/hermes/licenses.txt"
title: "安定版リリースの受け入れと昇格"
description: ""
upstream_path: developer-guide/stable-releases.md
upstream_blob: 5add96e6090a9ee8c49baed6f751dc71d98d895f
sources:
  - https://hermes-agent.nousresearch.com/docs/developer-guide/stable-releases
---

# 安定版リリースの受け入れと昇格 {#stable-release-admission-and-promotion}

`Stable Release` はリリースの関門です。ビルドが成功しただけでは安定版リリースにはなりません。
安定版リリースでは、試行 ref をロックとして、最終タグを公開の受領証として使います。
どちらのタグもワークフローの起動条件ではありません。カナリアビルドには専用の定期実行ワークフローがあります。

コミットされているプロジェクトのバージョンは常に `0.0.0` です。リリースジョブは受け入れた ref から
配布物のバージョンを導き出し、隔離したビルドツリーに刻印します。`main` 上のバージョンファイルは
上げないでください。

## 手順 {#order}

1. `origin/main` と、リモートの試行 ref・マーカー ref（`rc.*` と
   `abandoned-rc.*`）を最新にします。次の SemVer は、`0.21.5`（旧来の CalVer による最後のリリース、v2026.9.24）を起点とする公開済みリリース系列だけから
   導きます。基準は、保護された R2 の安定版 head と、`vX.Y.Z` タグを持つ最新の公開済み非プレリリース
   GitHub リリースのうち、新しいほうです。デスクトップパッケージを1つもビルドしないリリースが動かすのは後者だけです。
   試行はバージョン系列を動かしません。次のバージョンにすでに最終 `vX.Y.Z` タグがある場合、
   その公開が終わるまで切り出しは拒否されます。
2. 注釈付きの `rc.<N>-vX.Y.Z` 試行 ref をアトミックに push し、その上に非プレリリースの
   GitHub ドラフトを1つ作り、既定ブランチから、その ref を `tag` 入力に指定して `Stable Release` を起動します。試行番号は、
   そのバージョンの既存の試行 ref から決まります。
   クレームのメッセージには、コミット、試行番号、自動公開ポリシー、
   レーンの組（[レーン](#lanes)を参照）、そして
   単調増加で割り当てられるエポック1つが結び付けられます。このエポックがリリース日となり、
   すべてのマトリクス系統と再試行でのネイティブパッケージングの時刻になります。
   未解決の試行が1つでもあれば、バージョンを問わず新しい `release` はブロックされます。
3. リモートの注釈付きタグオブジェクトの SHA、剥がした先のコミット、そしてそのコミットの
   `origin/main` 上の祖先関係が一致するものだけを受け入れます。実行そのものは、既定ブランチから起動した
   `Stable Release` でなければなりません。ワークフローとリリース用スクリプトは `main` のものを使うので、
   Actions のキャッシュは試行をまたいで1つのスコープを共有します。`GITHUB_SHA` は `main` の先端であり、
   テスト対象のコミットではありません。配布物をテストまたはビルドするレーンには、すべて受け入れたコミットが渡されます。
   これは `tests/ci/test_stable_release_commit_threading.py` にあるコミット受け渡しの lint が強制します。
4. クレームに記録されたレーンを実行します。既定では、ソース、Docker、Nix、PM バンドル、
   インストール/更新、Windows、署名済みパッケージ、ネイティブアップグレードの受け入れグラフ全体で、
   macOS Intel と Termux は含みません。
5. テスト済みのバイト列から、バージョン付きで変更不可の Docker と R2 の成果物を push します。
   Docker イメージは自身のテストが通った時点で、試行 ref をタグにして push されます。
   `stable` や `latest` のエイリアスはまだ動かさず、公開のために再ビルドもしません。
6. 注釈付きの最終 `vMAJOR.MINOR.PATCH` 受領証は、テストが通った時点ではなく公開時に作ります。
   これには、採用された試行の ref、オブジェクト、コミット、アーカイブ接頭辞、
   候補マニフェストの SHA256（クレームがバンドルを省いた場合は `null`）、Docker の
   マニフェストダイジェスト、自動公開ポリシーが結び付けられます。
7. 安定版の公開コントローラーは、古いリリースから順に解決します。`vX.Y.Z` を作り、
   まだドラフトのリリースの参照先をそこへ付け替え、警告ブロックを取り除き、
   最後の呼び出しでリリースを公開します。そのあと受領証に結び付いた Docker ダイジェストを検証して昇格させ、
   App Installer、macOS、APT、ダウンロードページ、保護された R2 の head を進めます。

テストが通ったドラフトが1つだけのときは、そのクレームが自動公開を選んでいない限り待機します。
あとからテストが通ったクレームは、それより前に連続してテストが通っているドラフトをすべて順番に押し流します。
古いクレームが実行中なら、新しいものの公開はブロックされます。焼き切れた試行は飛ばされ、
そのバージョンは `abandon` のあとに再び切り出されます。失敗、キャンセル、欠落、想定外のスキップとなった
必須項目は赤のまま残ります。

Docker Hub、R2、APT、GitHub、Store は、サービスをまたぐ単一のトランザクションに対応していません。
そのためコントローラーは冪等に作られています。途中で失敗したら、もう一度実行してください。
各変更処理は現在の状態を確かめてから先へ進みます。
最終タグが管理の受領証です。ポインターを直すために、受け入れ済みのバイト列を再ビルドしたり
差し替えたりしないでください。

昇格では、R2 の公開オリジン上にある `releases/stable/index.html` の安定版ダウンロードページが置き換わります。
カナリアのタグビルドは `releases/canary/index.html` を、
コミットビルドは `releases/commit/<sha>/index.html` を持ちます。各ページに載るのは、ステージング済みで
受領証の裏付けがあるオブジェクトだけです。古い実行が保護されたチャンネルを後退させることはできません。

## 安定版リリースを実行・公開・中止する {#run-publish-or-abandon-a-stable-release}

リモートの `main` にすでにある特定のコミットから始めます:

```sh
python scripts/release.py release --commit "$(git rev-parse origin/main)" --bump patch --remote origin
```

`--bump` の既定値は `patch` です。`minor` や `major` は、その変更を意図しているときだけ指定してください。
選んだコミットは、最新の公開済み
`vX.Y.Z` の子孫でなければなりません。公開済みのタグが無い場合は、リモートの `main` にすでにあるコミットなら
どれでも受け入れられます。中止した試行は次の切り出しに何の制約も課しません。そもそもコミットに問題があって
中止されたことが多いからです。`release` は、バージョンを問わず未解決の試行があるあいだも拒否します。
拒否するときは、例外を出す前に、その試行のワークフロー実行の
URL、`abandon` コマンド、再実行コマンドを表示します。対話型のターミナルでは、そのあと
`Abandon <ref> and cut a new attempt? [y/N]` と尋ねます。`y` と答えるとその試行を中止して次の試行を切り出し、
既定は no です。標準入力か標準出力がターミナルでない場合は、尋ねずに例外を出します。

試行 ref は公開のロックであって、SemVer のプレリリースではありません。表記は
`v0.21.5-rc.1` ではなく `rc.1-v0.21.5` です。パッケージのバージョンは素の `0.21.5` のままです。
この形の文法は `scripts/releases/versioning.py` の `parse_attempt_ref` ただ1つです。

ドラフトの本文には、先頭と末尾に囲み付きの警告ブロックがあります。GitHub の画面からリリースを
公開しないでください。手で公開すると、
`vX.Y.Z` の受領証タグ、更新フィード、Docker のエイリアス、Store の確認がすべて飛ばされます。
公開済みのリリースは変更できないので、手で公開したリリースはあとから直せません。さらにパイプラインも
止まります。その試行にはマーカー ref も最終タグも無いため、`release` は未解決として拒否し、
`abandon` は公開済みだとして拒否するからです。公開処理は、リリースがまだドラフトのうちに
両方の囲みブロックを取り除きます。

`--autopublish` を付けると、クレームがテストの通った最古のリリースになった時点ですぐ公開されます。
付けない場合は、明示的に公開するか、あとからテストが通ったクレームが順序どおりの解決を強制するまで、
リリースはドラフトのままです。

### レーン {#lanes}

安定版リリースは、レーンの組でできています。ビルドレーンは利用者の手に届くものを作り、
テストレーンはリリースの関門となるテストスイートです。`release` は既定の組から始めて、それを編集します:

```sh
# The default: everything except macOS Intel and Termux
python scripts/release.py release --commit "$(git rev-parse origin/main)" --remote origin
# Only the signed Windows bundles and the Docker image, no tests
python scripts/release.py release --commit "$(git rev-parse origin/main)" --only windows,docker --remote origin
# Everything except the signed Windows pipeline (the Store bundle still builds and is submitted)
python scripts/release.py release --commit "$(git rev-parse origin/main)" --without windows --remote origin
# Only the Store bundle: no Azure signing anywhere
python scripts/release.py release --commit "$(git rev-parse origin/main)" --only windows-store --remote origin
# Everything, plus macOS Intel, without the install/update E2E
python scripts/release.py release --commit "$(git rev-parse origin/main)" --with macos-intel --without install-e2e --remote origin
```

`--only` は既定の組を置き換え、`--with` はレーンを加え、`--without`
はレーンを外します。どれもカンマ区切りの名前を受け取り、繰り返し指定できます。`release --help`
を実行すると、すべてのレーンが表示されます。

| レーン | 既定 | 何を制御するか |
|---|---|---|
| `docker` | オン | Docker イメージのビルドと公開 |
| `windows` | オン | 署名済みの Windows x64・arm64 の MSIX とユニバーサルバンドル。更新チャンネルに使われます |
| `windows-store` | オン | 署名なしの Microsoft Store バンドル。Partner Center に提出されます（Azure の署名は不要） |
| `macos-arm64` | オン | macOS Apple Silicon の DMG と ZIP |
| `macos-intel` | オフ | macOS Intel の DMG と ZIP。`main` では対応しておらず、そのランナーだけで1時間以上かかります |
| `termux` | オフ | Termux（Android aarch64）の APT パッケージ |
| `ci`, `nix`, `docker-tests`, `pm-bundle`, `windows-live`, `install-e2e`, `bootstrap-version` | オン | 単独のテストスイート |
| `termux-checks` | オン | Termux の検証。`termux` がオンのときだけ実行されます |
| `smoke` | オン | ビルドした各デスクトップパッケージのネイティブインストールとチャットのスモークテスト |
| `upgrade` | オン | ビルドした各デスクトップパッケージの、署名済みパッケージのアップグレード受け入れ（OLD から NEW へ） |

レーンは互いに独立したスイッチです。ジョブは、必要なレーンがすべてオンのときだけ実行されます
（`scripts/releases/lanes.py` の `JOB_LANES`）。そのため、ビルドされないものに対するテストレーンは何もしません。
たとえば `macos-arm64` がオフのとき、`upgrade` は macOS の
アップグレード系統を計画しません。

レーンの組はクレームのメッセージに書き込まれ（`"lanes": [...]`）、ワークフローの入力として渡されることはありません。
`admit` がクレームからそれを読み取り、2つの出力を出します。
`lanes`（レーンごとに1つのフラグ）と `jobs`（レーンが制御するジョブごとに1つの実行フラグ）です。
そうしたジョブの `if` は、どれも自分の `jobs` の項目を読みます。何が実行されるかを決めるのは、レーンの表だけです。
復旧のための再実行でそれを変えることはできません。レーンを変えるには、
試行を `abandon` して切り出し直してください。

関門は厳格なままです。`scripts.releases.stable gate` はレーンを読み、
レーンが外すジョブにはすべて `skipped` の報告を、それ以外の関門対象ジョブにはすべて
`success` の報告を求めます。レーンが外すはずのジョブが実行された場合も、
リリースはブロックされます。レーンができる前に切り出したクレーム（`skipBundles`、`skipTests`、
`includeMacosIntel`、`includeTermux`）は、同じジョブを関門としていたレーンの組として読まれます。
`lanesRevision` を持たないクレームは、`windows` が Store
バンドルも意味していた時期に切り出されたものなので、その `windows` は `windows` と `windows-store` の両方として読まれます。

リリースが公開するものは、ビルドしたものに従います:

- **Docker。** `docker` がオフのときはイメージをビルドも昇格もせず、最終
  タグには `dockerManifestDigest: null` が記録されます。
- **バンドル。** デスクトップと Termux のレーンが1つも無いときは候補マニフェストが存在せず、
  最終タグには `candidateManifestSha256: null` が記録されます。
- **デスクトップのチャンネルが動くのは、デスクトップの組がそろったときだけです**（`windows` と
  `macos-arm64`）。一部だけの組でもパッケージはステージングされ、ビルドしたターゲットだけをちょうど載せた
  候補マニフェストが記録され、`windows-store` がオンなら Store バンドルが提出されます。
  Store だけのリリースにはパッケージの行が無く、Store
  バンドルのファイルだけがあり、どのチャンネルも動かしません。保護された R2 の安定版 head、App Installer と macOS の
  フィード、APT チャンネル、ダウンロードページ、
  `releases/stable/release-candidates.json`、署名済みパッケージの基準は、
  前回のそろったリリースのままです。公式リポジトリのソースチェックアウトは
  `releases/stable/release-candidates.json` に従うので、これも動きません。
- **完了。** デスクトップのチャンネルを進めるリリースは、保護された R2 の head がそれを指した時点で完了です。
  Docker だけを動かすリリースは、`stable` エイリアスが最終タグに結び付いたダイジェストを指した時点で完了です。
  どのチャンネルも動かさないリリースは、公開された時点で完了です。シーケンサーはこれらの目印を使って、
  どのリリースにまだ公開処理が必要かを判断します。
- **テスト無し。** 候補マニフェストには、ネイティブのスモークテストがそれぞれ `skipped` として記録され、
  合格とは記録されません。クレームを知っているすべての読み手（`complete` と
  保護された R2 の前進処理）は、スモークテストの結果がクレームの `smoke` レーンと食い違う
  マニフェストを拒否します。次のそろったリリースは、ほかと同じくこのマニフェストをアップグレードの
  基準に使います。テストを通していないリリースのあとには、通常のリリースを切り出してください。

macOS Intel を外した場合、その行は候補マニフェスト、リリース本文のダウンロード表、
ダウンロードページから消えます。macOS の
安定版フィードは1つだけ（`releases/darwin/stable/stable-mac.yml`）で、`promote` はこれを
arm64 の系統だけで置き換えます。そのため、このフィードを使う Intel の Mac は、`--with macos-intel` を付けて
リリースを切り出すまで更新を受け取りません。Termux を外した場合は APT の
インデックスが書き換えられないので、APT チャンネルは前回のリリースのまま残ります。
ターゲットを外したリリースのあとで、それを再び加えたリリースには、そのターゲットの公開済みの
基準が無いので、初回のリリースと同じくそのターゲットのアップグレード系統は省略されます。

クレームの push が、アトミックなバージョンロックです。2つの呼び出し元が同じ次バージョンを
導くことはあり得ますが、push に成功するのは1つだけです。負けた側は、勝った側のタグ付け者、時刻、
コミットを報告します。拒否されたクレームや中止されたクレームは、使用済みのまま残ります。公開または中止するには:

```sh
python scripts/release.py publish --version 0.21.5 --remote origin
python scripts/release.py abandon --version 0.21.5 --remote origin
```

`publish` は、置き換えの有無を同期的に事前確認してから、自動復旧でも使われる同じ
順序付きコントローラーを起動します。より新しい公開済みリリースより下にある、焼き切れたと分かっているバージョンは拒否します。
`abandon` は、その試行で進行中の `Stable Release` の実行を強制キャンセルし、
ドラフトがあれば削除し、
`abandoned-rc.<N>-vX.Y.Z` マーカー ref を書き込み、試行 ref は残します。
マーカーが中止の記録で、試行 ref は決して削除されません。
バージョンは使用済みにならないので、次の切り出しは `rc.<N+1>-vX.Y.Z` になります。

最終タグを指定して `Stable Release` を手動で起動しないでください。復旧では、元の
クレーム ref、オブジェクト SHA、コミット、ドラフトのデータベース ID、自動公開ポリシー、
省略フラグがそのまま保たれます。

## 失敗と復旧 {#failure-and-recovery}

安定版の実行が失敗すると、その完了イベントが `Stable Release
Publication` を起動し、同じ GitHub
Actions の実行の中で失敗したジョブだけをすぐに再実行します。待ち時間の増加も定期実行もありません。
この処理が適用するのは、未解決の再試行のうち最も古いものだけです。GitHub は、共有の
安定版リリース並行実行グループの中で保留中の実行を1つしか保持しないためです。
再実行は、処理が終わるまでそのグループで待機します。受け入れる再試行は最大2回（実行試行 2 と 3）です。
試行 3 が失敗すると、そのクレームは焼き切れとなり、シーケンサーはそれより後のクレームを解決できるようになります。
再試行の要求が失われた場合や、公開の変更処理の途中でクラッシュした場合は、
次の失敗イベントか、`Stable Release Publication` を手で起動することで復旧します。

新たに push されたクレームで、ワークフローの実行がまだ観測されていないものは、1時間は未解決のまま扱われます。
この猶予時間は、ドラフト作成と起動の手順がアトミックでないことを補うためのものです。
1時間を過ぎても始まっていないクレームは、焼き切れと判定されます。
通常の作成時間内にクレームが焼き切れになることはありません。

デスクトップと Termux の受け渡し物は、変更不可の R2 タグアーカイブに置かれます。アーカイブの
キーは試行 ref で、`releases/tag/rc.<N>-vX.Y.Z/` です。`releases/tag/vX.Y.Z/`
には決して書き込みません。各生成元は、タグ、コミット、パス、サイズ、SHA256 ダイジェストを含む
`handoff-<target>.json` 受領証を書き込みます。アーキテクチャごとの受領証は、
その接頭辞の下で候補マニフェストの隣に置かれます。候補の組み立てでは、
固定された `release-candidates.json` が1つ出力されます。利用側はそのダイジェストを検証し、
パッケージを再アップロードしません。このダイジェストは公開前にはどこにも記録されません。
公開処理がアーカイブ側のコピーをハッシュし、その値を最終
タグに書き込みます。Docker の公開では、試行 ref をタグにしたバージョン付きで変更不可のマニフェストを1つ push し、
そのレジストリダイジェストを最終タグに記録します。公開を遅らせる場合は、
そのダイジェストをレジストリ側で昇格させるので、期限切れになる Actions の成果物には依存しません。

Windows、macOS、Termux の候補ジョブは、受け入れ前に
`releases/tag/<attempt ref>/` の下へパッケージとメタデータをステージングします。この段階では
安定版のフィードは書き込まれません。安定版の APT プールのパスにも試行 ref が入ります:
`releases/termux/stable/pool/<attempt ref>/<c>/<name>.deb`。プールへのアップロードは
変更不可で、1年のキャッシュヘッダーが付くため、同じバージョンを切り出し直すと
別のプールキーに書き込まれます。
変更不可のアップロードは、既存のオブジェクトのバイト列が一致する場合にだけそれを受け入れます。
アーカイブのオブジェクト、最終タグのダイジェスト、バージョン付きの Docker タグ、読み戻し結果のいずれかが食い違ったら、
受け入れ済みの候補を差し替えるのではなく、復旧を止めてください。

候補マニフェストには、受け入れたクレームのエポックが結び付けられます。受け入れ時には、そのエポックから
安定版の Windows 用4要素バージョンを計算し直し、形式は正しくても値が誤っている
MSIX、実行ファイルの VERSIONINFO、App Installer のバージョンを拒否します。ネイティブの受け入れでは、
ビルドしたパッケージから Electron 成果物のファイル名と macOS の plist も読み取ります。
受け入れグラフでは、隔離したブートストラップインストーラーのツリーに刻印し、Cargo に
生成された Tauri パッケージのバージョンを読ませ、Python の wheel/sdist をビルドして
メタデータとファイル名を検査し、Nix と Docker の実行時の識別情報を確認します。
利用者から見える場所に `0.0.0` や一致しないバージョンがあれば、関門は失敗します。

ドラフトは公開まで試行 ref の上にとどまります。ドラフトを削除することが `abandon` で、
バージョンは焼き切れになりません。注釈付きの最終タグには、クレームと一緒に受け入れた GitHub リリースの
データベース ID が結び付けられます。公開処理がリリースを編集するのは、
まだドラフトのあいだだけです。`vX.Y.Z` を作り、リリースの参照先をそこへ付け替え、本文から
警告ブロックを取り除き、タグと本文を読み戻します。リリースを公開する呼び出しは最後です。
公開済みのリリースは変更できないので、あとから参照先の付け替えやタグの付け直しはできません。

公開されるまで、クライアントを試行へ向けるものは何もありません。アップデーターが比較するのは
URL ではなくバージョンで、それが安全なのはこのためです。
`releases/tag/<attempt ref>/index.html` にある診断ページは更新フィードではなく、
そこからインストールした試行はアップグレードに対して安全ではありません。あるバージョンの試行はすべて
同じパッケージバージョンを持つので、公開されたビルドがそれを置き換えることはないからです。Docker
イメージは試行 ref の名前で早めに push され、`stable` と `latest` のエイリアスは
公開処理でフィードと一緒に動きます。Store への提出は保留されます。
テストが通った実行は、自動公開をオフにして提出します。提出 API は保留中の提出を
リリースできないので、公開処理はそれを確認して GitHub の
警告を表示するだけです。認定に通ったら、Partner Center でその提出の「今すぐ公開」をクリックしてください。
提出が失敗すると、公開の実行は赤のまま残ります。安定版チャンネルの
記録には、試行 ref を示す任意の `archiveRef` が入ります。`releaseTag`
は `vX.Y.Z` のままで、`archiveRef` が無いときは、保護された接頭辞はそれを代わりに使います。

デスクトップのワークフローの任意入力 `termux_upgrade_from_tag` には、Termux の R2 受け渡し物を持つ、
特定の公開済みリリースを指定します。公開を伴わないと明示したデスクトップビルドは、
ダウンロードできるジョブ成果物を残しません。

デスクトップのワークフローは `jobs` 入力を受け取り、グループは `darwin-arm64`、
`darwin-x64`、`win32-arm64`、`win32-x64`、`win32-bundle`、`linux-x64`、
`linux-arm64`、`termux` です。安定版はグループごとに1回ずつ呼び出しますが、linux の
グループは本物の Linux ビルドができるまで待ちます。また、`darwin-x64`、`termux`、
そしてクレームがそのレーンを外しているほかのグループは呼び出しません。Mac の各アーキテクチャと Windows のバンドルは
受領証をステージングし、各インストール系統は、自分のバイト列がステージングされた時点で
自分の受領証から開始します。公開をブロックする合流点は `acceptance` ただ1つです。

すべての関門とすべての候補は、`admit` の直後に始まります。CI は
`acceptance` が必要とする関門の1つであって、署名済みビルドがその後ろで待つロックではありません。
そのため遅い関門がバンドルを遅らせることはありません。その代わり、`main` が壊れていても、
`acceptance` が最終的に拒否する署名済み候補のコストは払うことになります。
`smoke-win32-universal` ジョブは廃止されました。アーキテクチャごとの MSIX スモークテストが各
アーキテクチャを確かめ、Windows のインストール系統は両方のアーキテクチャで `.msixbundle` をインストールします。
Stable はこの2つの MSIX スモークテストを、アーキテクチャごとの呼び出しではなく、`win32-bundle` の呼び出しの中で
組み立てと並べて実行します。そのため組み立ては、遅いほうのアーキテクチャのスモークテストを待たずに、両方のビルドが
済んだ時点で始まります。それでもどちらかのスモークテストが失敗すれば、バンドルの呼び出し（つまり
`candidate-manifest`）は失敗します。

## タグの名前空間と受領証 {#tag-namespaces-and-receipts}

試行 ref をロックとして頼る前に、
`refs/tags/v*`、`refs/tags/rc.*`、`refs/tags/abandoned-rc.*` に対するリポジトリのルールセットを適用してください。
作成を組織の管理者とリリース用の連携だけに限り、
更新と削除を禁止するものです。このルールセットはまだ適用されていません。管理者が適用するまでは、
これらのロックは自主的な運用にとどまり、push 権限を持つ人なら誰でもマーカー ref を削除したり
受領証タグを動かしたりできます。試行 ref、マーカー ref、最終タグは変更不可です。
チャンネルビルドとコミットビルドは、公開に成功したあとにだけ、
`v0.0.7+channel.<YYYYMMDDTHHMMSSZ>.<run-id>` や
`v0.0.0+commit.<YYYYMMDDTHHMMSSZ>.<run-id>` のような注釈付きのビルド後受領証を書き込みます。
受領証タグは GitHub リリースを作らず、ワークフローも起動しません。

カナリアのソースの識別情報は
`v<stable>+canary.<YYYYMMDDTHHMMSSZ>` だけです。ビルドメタデータにより、意図的に
SemVer 上はその安定版の本体と等しくなります。どちらへ進むかは、保護されたチャンネルの記録と埋め込まれた UTC
タイムスタンプが決めます。デスクトップのクライアントは、ビルドメタデータの順序付けを SemVer に任せず、
検証済みのそのチャンネルの並びを更新の根拠として扱います。`Canary Release` は毎日 06:41 UTC に既定の
ブランチから実行され、手動で起動すればいつでも1回走らせられます。実行は
`cancel-in-progress: false` で順番待ちになります。`main` に新しいコミットが無ければ、
未公開のカナリアを再開するか、何もしません。過去の `-canary.` 形式の識別情報には
対応していません。
カナリアのタグを push したあとでプロセスが止まった場合は、コマンドを再実行すると、
リモートのタグオブジェクトを厳密に検証し、欠けたドラフトを補い、
保護されたカナリアの head がそのタグを受領するまで起動し直します。
保護された公開コントローラーは、GitHub のプレリリースをドラフトから公開に切り替える前に
その注釈付きタグオブジェクトを厳密に検証し、両方の状態を読み戻し、
そのあとで初めて R2 の head を進めます。

## カナリアと単発のデスクトップ識別情報 {#canary-and-one-off-desktop-identities}

`release.py --canary` は、別アプリとしてのカナリア版をビルドします。そのパッケージの
識別情報と CLI コマンド（`hermes-canary`）は安定版とは異なり、既存の
カナリアフィードが更新するのはこのアプリだけです。単発のビルドには
`release.py --build-commit REV --remote REMOTE` を使います（起動まで行うには `--publish` を追加）。
そのアプリの識別情報と CLI コマンド（`hermes-<7-character-sha>`）には、固定したコミットが含まれます。
異なる2つのコミットビルドが互いを置き換えることはありません。

ブランディングは実行時の設定ではなく、これらのビルド入力から選ばれます。
カナリアは黄色/暗い黄色のアイコンを、単発のビルドは短い SHA を載せた赤いアイコンを使います。
デスクトップのアイコン形式はすべて同じ原画から作られます。

単発ビルドの刻印には `source: commit-build` が使われます。これらにはアプリの更新フィードも App Installer の
購読も公開されず、GUI と同梱の CLI のどちらも更新の要求を拒否します。
受け取った人には、開発者に新しいビルドを頼むよう案内します。ソースチェックアウトは別物で、
使えるチャンネルは `main` だけです。

`--build-commit` は、起動の前に、決定的に決まるダウンロードページの URL を表示します。
ドライランでも表示されます:
`https://hermes-assets.nousresearch.com/releases/commit/<full-sha>/index.html`。
`CLOUDFLARE_R2_PUBLIC_URL` で公開オリジンを上書きできます。受け入れのあと、
ビルドや組み立てのジョブが失敗してもコミットの要約は実行されます。そこには受領証の裏付けがある
既存のダウンロードだけが載り、欠けたバイナリは未ビルドと表示されます。
欠けたバイナリは、存在しないダウンロードではなく、**View build run** の下でワークフローの実行にリンクします。
無効にしたプラットフォームには、ダウンロードも失敗のリンクもありません。
ページの公開には、引き続き有効な R2 へのアクセスが必要です。コミットページは GitHub 上のソースに
リンクし、タグページとチャンネルページは対応する GitHub リリースのタグにリンクします。
コミットページには、CI の環境ではなくデスクトップのバンドルに渡された、秘密でない明示的な `--bundle-env` の既定値と
`--bundle-unset` による解除も載ります。
値は JSON 文字列で表示され（空の値は `""`）、解除には
**Unset** の表示が付きます。上書きが何も指定されていなければ、この節は出ません。

タグ付きのビルドは、ビルドやフィードが失敗したあとにも、成果物が1つもアップロードされなかった場合を含めて、タグごとの診断ページを
`releases/tag/<tag>/index.html` に公開します。不完全なビルドはチャンネルのページを進めず、
リリース成功の関門も通りません。

Store への提出は、固定された公式の安定版の識別情報を保ちます。安定版以外の
パッケージをその識別情報で提出してはいけません。

## R2 上の動的チャンネル {#dynamic-channels-in-r2}

チャンネル名はリポジトリ内の登録簿ではなく、R2 のオブジェクトです。プレビューチャンネルは、
コミットを指定した複数のビルドをまたいで、1つのネイティブアプリの識別情報を持ちます。変更不可のビルド
要求には、ソースコミット、バンドルの既定値、チャンネルの並び、パッケージの
バージョンが別々に記録されます。チャンネルの head が進むには、既存のネイティブビルドとスモークテストのジョブが
すべて通る必要があります。

カスタムビルドをプレビューしてから、明示的に起動します。リポジトリの識別情報で
挙動が変わることはありません。同じ直接起動はどの GitHub リモートからでも動きますが、
`--channel` はローカルでは R2 に一切アクセスしません。push 済みの特定のコミットを解決し、
既定ブランチのワークフローを起動するだけで、その特権付きの割り当て手順が CI の中で
チャンネルを作り、変更不可のビルド要求を発行します。ローカルのコマンドに必要なのは、
選んだリポジトリに対して write、maintain、admin のいずれかの権限を持つ `gh` トークンだけで、
R2 の認証情報は要りません。

```sh
python scripts/release.py --channel pm-preview --build-commit my-branch --remote origin
python scripts/release.py --channel pm-preview --build-commit my-branch --remote origin --publish
python scripts/release.py --channels --remote origin
```

既定では、プレビューのチャンネルは独立したアプリ（`Hermes
NAME`、専用のパッケージ ID）として並べてインストールされます。特定のコミットを通常のアプリとして試したいときは、
`--branding stable` を付けてチャンネルを作ってください。公開済みの安定版チャンネルの名前・アイコン・パッケージ ID を引き継ぐので、
ビルドは公式アプリに上書きでインストールされ、デスクトップの設定も共有し、以後の更新はそのチャンネルに従います。ブランディングはチャンネルを
作った時点で固定されます。そのチャンネルを起動するたびにこのフラグを付け直し、変えたいときは新しいチャンネル名を使ってください。
`--branding` には `--channel` が必要です。チャンネルを使わない一度きりのコミットのビルドは、常に `Hermes Agent <sha>` という名前になるためです。

```sh
python scripts/release.py --channel my-commit --branding stable --build-commit SHA --remote origin --publish
```

使い捨ての R2 スコープは任意で、テスト実行専用です。デスクトップの
ワークフローを `disposable_channel` と `build_commit` 付きで起動すると、
`ci-disposable/<repository-id>/<run-id>/` の下に名前空間が割り当てられます。そのあとは、要約に出る
スコープ付きのビルドコマンドをそのまま使ってください。その割り当てをローカルで管理するには、
`R2_DISPOSABLE_RUN` と `GITHUB_REPOSITORY_ID` をコマンドの環境に入れ、
設定した公開 URL はスコープの無いルートのままにします。これらはテスト実行の
入力であって、永続的なアプリの設定ではありません。リポジトリ ID は認証情報を読む前に
選んだリモートと照合されるので、スコープ付きの名前空間も必ず1つのリポジトリに属します。

公開を伴う最初の呼び出しで（CI での割り当て時に）チャンネルが作られ、
以降の呼び出しではその識別情報が保たれます。要求と成果物は
`releases/channel-builds/BUILD_ID/` の下に置かれ、変更可能なポインターは
`releases/channels/NAME.json` です。ビルドが失敗しても、以前の head はそのまま残ります。
条件付き書き込みにより、古い公開や、恒久的に退役したチャンネルへの公開は拒否されます。
再試行では `--channel NAME --build-commit SHA --publish` を起動し直します。これにより
CI で新しい並びの枠が割り当てられます。再開専用のコマンドはありません。

退役させるときは、現在の公式の安定版ビルドを最初の受け手として固定します:

```text
python scripts/release.py --retire-channel pm-preview --to stable --minimum-version VERSION --remote origin
```

`--publish` を付けるのは、ドライランと、厳密なネイティブ受け入れの証跡を確認してからにしてください。
これは安定版をプレビューの識別情報で再ビルドするものでも、クライアントを黙ってアンインストールするものでもありません。
プロトコルに対応したクライアントは、同意を得たうえでアプリ間の引き継ぎを提案します。
プレビューを削除する前に、引き継ぎ先が準備完了を確認しなければなりません。オフラインのクライアントのために、
退役オブジェクトと固定した成果物は取得できる状態に保ってください。
既存の単発ビルドには退役を読み取る仕組みが無いので、置き換えが必要です。

引き継ぎ先のマニフェストは、パッケージの刻印から読み取った受け手としての対応を宣言していなければなりません。
受け入れを担うのは既存の保護されたリリースワークフローで、
別の公開認定文書や、期限切れになる Actions の成果物への依存はありません。
ネイティブの署名と識別情報の確認、受け手の同意、保存状態の事前確認、
引き継ぎ先の準備完了は、引き続き必須です。オフラインのプレビューは、安定版が先へ進んだあとでも
固定された最初の受け手に到達し、そのアプリはそこから安定版経由で更新されます。

R2 だけを読むソースの読み手を出荷する前に、明示的で保護された
ブートストラップ操作で、既存の `main` ソースブランチの記録と、公開済みの安定版/カナリアの記録を投入してください。
実際に受け入れたマニフェストを確認し、`main` のネイティブマニフェストを
でっち上げないでください。本番での投入、CDN のキャッシュ/CAS の検証、ネイティブの
署名済みパッケージの適格性確認はリリース作業であり、ローカルの補助テストスイートが通ったことでは
保証されません。ブートストラップの出力を、リポジトリ内のチャンネル一覧として保存しないでください。

## 署名済みパッケージの基準 {#signed-package-baseline}

バンドルを出荷した最後の成功した安定版リリースが、設定された R2 公開オリジン上に
`releases/stable/release-candidates.json` を記録します。
バンドルを省いたリリースはこれを置き換えません。
基準がまだないときも、リリースではネイティブのスモークテストをすべて実行しますが、
署名済みパッケージの更新の検証は省きます。存在しない `baseline-manifest` 入力を
指定した場合は、リリースを止める問題になります。
この記録は、実際の Windows ユニバーサル MSIX バンドル、macOS の ZIP、パッケージの
来歴を特定します。次の実行では、これらの記録を自身の候補マニフェストと組み合わせ、
既存のネイティブなバンドル更新ドライバーを使います。

このメタデータより前の既存の安定版リリースについては、
`baseline-manifest` として、設定された R2 公開オリジン上の HTTPS URL を指定してください。
指す先は、実際に公開されたパッケージと成功したネイティブの
スモークテスト結果を記した、現行の schema-2 マニフェストです。Schema-1 の候補は拒否され、旧形式を受け入れる
経路はありません。マニフェストのリダイレクトは同じオリジン内にとどまる必要があります。基準タグは
公開済みの安定版リリースでなければならず、パッケージの識別情報は一致し、バージョンは
増えていなければなりません。安定版の Windows
パッケージは electron-builder の `storePackageVersionAt` ポリシーを使い、受け入れたクレームの
タグオブジェクトの変更不可なタグ付け時刻（`year.hourOfYear.secondOfHour.0`）から決まります。
これは SemVer の配布物タグとは独立しています。
基準となる成果物が欠けている場合、それはブロッカーです。
成果物をでっち上げたり、受け入れを飛ばしたりしてよい理由にはなりません。
[バンドル更新の取り決め](https://github.com/NousResearch/hermes-agent/blob/main/tests/install/BUNDLED_UPDATES.md)を参照してください。

## 明示的な除外とポリシー {#explicit-exclusions-and-policy}

- デスクトップの Playwright E2E（`e2e-desktop.yml`）は、不安定なため所有者の依頼で
  見送っています。合格ではなく見送りとして報告されます。この関門に加える前に、安定させて
  CI で繰り返し通ることを示してください。
- インストール/更新 E2E とネイティブの署名済みパッケージ受け入れは見送られて**いません**。
  これらを外せるのは `install-e2e`、`smoke`、`upgrade` のレーンを含まないクレームだけで、その場合、関門は
  それらがスキップされていることを求めます。
- PR 専用の履歴・ラベル・差分レビューの確認は、安定版のタグには適用されません。
  該当するソース CI のジョブは、変更されたパスにかかわらずすべて実行されます。
- OSV の脆弱性検出は、既存の勧告扱いのポリシーを保ちます。必須の
  スキャナーの実行失敗は失敗であり、勧告扱いの検出ではありません。
- 無効にしている Linux デスクトップのパッケージングは、出荷済みとは主張しません。ネイティブ Linux の
  PM バンドル、Docker、Nix、インストール/更新の確認は引き続き必須です。
- ハウスキーピング、自動修正、コメント、スキル索引/デプロイのワークフローは、
  リリースの受け入れスイートではありません。

## 実装の担当範囲 {#implementation-ownership}

Actions は、ジョブの順序、ランナーの選択、権限、環境を担当します。
Python は、`scripts/releases/` と `scripts/bundles/` の下で、共通のリリース受け入れ、マニフェスト、成果物のハッシュ、公開、
チャンネルの昇格を担当します。
electron-builder の設定/フックと、ネイティブの Windows/macOS アダプターは、
引き続き JavaScript か PowerShell で書かれます。これらのアダプターはリリースの事実を受け取るだけで、
リリースの関門を再実装しません。関門のジョブは Python の標準ライブラリだけを使い、
判定を報告するためにアプリや JS ワークスペースをインストールしません。

`scripts.bundles.release_artifacts` は、App Installer の XML とフィードの公開を担当します。
そのシリアライザーは、パッケージの識別情報、発行元、バージョン、購読
URI、成果物 URI を明示的に受け取ります。安定版の昇格では、受け入れ済みの候補のメタデータを使います。
カナリアの公開では、バンドルをアップロードする前に、ネイティブのバンドルマニフェストがアダプターの
想定する識別情報と一致するかを検証し、そのあとで記述子をアップロードします。ネイティブ SDK での
バンドルと署名は `stage-msixbundle.mjs` にとどまります。Store ビルドとコミットビルドは、
そのフィードへの受け渡しの手前で止まります。`stable-store` は、検証済みの候補を再ビルドせずに提出します。
ネイティブの受け入れも同じ Python のシリアライザーを使い、起動時に
12時間ごとに確認するポリシーも含みます。

署名と公開の認証情報は、それぞれ保護されたジョブ環境の中にとどまります。
ソース CI の呼び出しはデプロイ用のシークレットを継承しません。このパイプラインを実行する前に、
既存のリリース署名用とコンテナー公開用の環境を設定してください。
