---
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/shared-bundle-builds.md
upstream_blob: 64033875c2c8f60d9b1ca353b77e3b6fd868de52
sources:
  - https://hermes-agent.nousresearch.com/docs/developer-guide/shared-bundle-builds
---

# 共有バンドルビルド {#shared-bundle-builds}

Hermes は、依存関係の準備、プロダクトのビルド、配布物のパッケージングを分けています。
コンパイラーとエージェント組み立てのインターフェースは
[`scripts/build/README.md`](https://github.com/NousResearch/hermes-agent/blob/main/scripts/build/README.md) にあります。これらは現行の
インターフェースであって、すべての配布物がネイティブ環境での受け入れ検査に通ったことを示すものではありません。

## プロバイダー、プロダクト、配布物 {#providers-products-and-distributions}

| 層 | 役割 | 実装 |
|---|---|---|
| 依存関係プロバイダー | ツール、Python 環境、JavaScript の依存関係、ネイティブバインディングを準備する | PM（`pm.build_environment`）、`scripts/build/node-deps.mjs`、Nix、Termux |
| プロダクトビルダー | アイコン・TUI・Web・デスクトップ UI をコンパイルする、または準備済みの入力から実行可能なエージェントを組み立てる | `scripts/generate_icons.py` と `scripts/build/` |
| 配布アダプター | プロダクトを選び、それぞれの対象向けにパッケージする | `scripts/bundles/`、`Dockerfile`、`nix/`、`scripts/termux/` |

プロバイダーは、それぞれのパッケージマネージャーとロックファイルを持ち続けます。プロダクトビルダーは、
足りない依存関係をインストールしたり、入力をダウンロードしたりしません。Node の依存関係プロバイダーは、
ロックされた1つのワークスペースの和集合を使います。Nix は自前の npm と Python の入力を用意します。
Termux は bionic 用のツールと wheel を用意します。レシピを共有していても、対象ごとにバイト列、
選ばれる依存関係、Python 環境が同一になるわけではありません。

| プロダクト | 実装 | 入力 |
|---|---|---|
| アイコン | `scripts/generate_icons.py` | 元の画像と、ランタイム依存関係を入れた Python |
| TUI | `scripts/build/tui.mjs` | 準備済みの TUI ワークスペース |
| ダッシュボード | `scripts/build/web.mjs` | 準備済みの Web ワークスペースと、生成したアイコン |
| デスクトップ UI | `scripts/build/desktop.mjs` | 準備済みのデスクトップワークスペース、アイコン、スタンプ、ネイティブバインディング |
| 実行可能なエージェント | `scripts/build/agent.py` | コード、インタープリター、依存関係、独立した PM ランタイム、リソース、選んだフロントエンド |

デスクトップ UI のコンパイラーはダッシュボードに依存しません。バンドル版のデスクトップは、
TUI とダッシュボードのプロダクトを含むエージェントを選びます。ライト版のデスクトップは
エージェントのペイロードを選びません。

## ビルドとパッケージングの入口 {#build-and-packaging-entrypoints}

ソースからの更新と、ソースからの UI 起動は、同じ依存関係プロバイダーとプロダクトビルダーを使います。
`hermes_cli/source_build.py` がワークスペースの和集合を選んで一度だけ準備し、そのあと共有レシピを呼び出します。
更新では TUI と Web をビルドし、更新前にローカルのデスクトップアプリがあった場合はそれもビルドします。
デスクトップのパッケージングは、引き続きローカルのデスクトップアダプターの担当です。

現在のチェックアウトでの再試行、pull したチェックアウト、ZIP による更新のどの経路も
`update_cmd_maint._prepare_updated_checkout` を使います。PM の依存関係同期を1回行い、そのあと選んだ
インタープリターで新しいプロセスを起こしてフロントエンドをビルドします。ビルドに失敗すると完了処理は中止されます。
古いプロダクトが残っていても、更新が成功したことにはなりません。更新処理専用の npm キャッシュ、代替のインストール、
追加の再読み込み、メモリープロバイダーの再インストールはありません。Python の依存関係の和集合はすべて PM が持ちます。

デスクトップの配布物は、リリースタグの時点のクリーンなチェックアウトからビルドします。まず、入口のスクリプトと Git を
実行できる Python をホストに用意します。準備の段階で、固定された版の Python、Node、npm を PM が取得します。
ネイティブのコンパイラーや SDK は、引き続きホスト側で用意する入力です。

```sh
python -m scripts.bundles.desktop --tag=vX.Y.Z
```

`--prepared` を付けない場合、`desktop.py` には `--tag` と `--commit` のどちらか一方だけが必要です。コミットを指定したビルドでは、
完全なコミット SHA と、それに一致するチェックアウトの HEAD が必要です。`--variant` には
`bundled`、`store`、`light` のいずれかを指定でき、既定は `bundled` です。`--repo` でチェックアウトを
選びます。`--` より後ろの引数は、準備済みの Electron Builder ラッパーに渡されます。
ただし、許可済みのターゲット、ツール、出力先、パッケージング設定を置き換えることはできません。

ドライバーはまず、依存関係一式を準備します。管理対象のツール、ロックされた Node ワークスペースの和集合、
アイコン用の環境、Electron のネイティブバインディング、パッケージング用のユーティリティ、そして（ライト版以外では）
アプリケーションの環境と独立した PM 環境です。
それが済んでから、プロダクトをコンパイルし、ペイロードを組み立て、Electron をパッケージします。
MSIX のメタデータ、署名、公証、パッケージ形式は、引き続きアダプター側の作業です。

### デスクトップの準備と消費を分ける {#split-desktop-preparation-and-consumption}

1コマンドでのビルドと CI は、同じ準備処理を使います。プロダクトのコンパイルとパッケージングの手前で
止めるには、次のようにします。

```sh
python scripts/bundles/desktop.py --tag vX.Y.Z --variant bundled --prepare-only \
  --work "$PWD/.build/desktop-job" --cache "$PWD/.cache/desktop-inputs"
python scripts/bundles/desktop.py --prepared "$PWD/.build/desktop-job/prepared.json"
```

`--work` と `--cache` は、ビルドが所有する別々のディレクトリです。作業ディレクトリは準備処理が自分で
確保するので、事前に作らないでください。`prepared.json` は、すべてのプロバイダーが成功したあとで最後に書き出されます。
このファイルに入っているのはこのジョブの絶対パスとソースの識別情報で、持ち運べるキャッシュの受領証ではありません。
チェックアウトを移動したとき、ソースやロックファイルを変えたとき、入力が失われたときは、準備をやり直してください。
準備を繰り返すと、許可済みの依存関係のバイト列は再利用しつつ、パスに結び付いた環境を作り直すことがあります。

`--prepared` には、新しい tag・commit・work・cache の引数を指定できません。クリーンなチェックアウトと
準備済みの入力を検証し、入力が古いか欠けている場合は、インストールせずに失敗します。安定版のタグであれば、
バンドル版と Store 版は同じ準備を使えます。ライト版とコミット指定のビルドは Store 版に切り替えられません。
プロダクトのビルドは毎回実行されます。ネイティブのステージングは、この組み立てに向けて `prepare_native` と
`finish_native` を公開しています。`hermes pm bundle` は、ネイティブのステージングを一通り行うコマンドのままで、
デスクトップの準備用のインターフェースではありません。

Windows と macOS のリリースジョブは **restore → prepare → save → build** の順で動きます。
キャッシュのアクションはプロバイダーのパスを導き出し、候補を運びます。準備用の作業ディレクトリを作ることも、
キャッシュのヒットを有効と宣言することもしません。保存は準備が成功したあと、プロダクトのコンパイルや署名の前に行います。
準備に失敗した場合、完了したプロバイダーのデータはローカルに残りますが、全体のスナップショットは保存されません。
汎用の `setup-pm` アクションは、ほかのワークフロー向けに引き続き使えます。

ネイティブの wheel キャッシュは、`python/runtime` の下にある 128 ビットの短いパーティションを使います。
キーは、ターゲットと、観測したコンパイラー・SDK の入力です。識別情報が欠けている場合も、新しいパーティションが割り当てられます。
uv はソース配布物をこのキャッシュの中でビルドするので、パーティション名は、その下に入れ子になるコンパイラーの出力が
Windows の `MAX_PATH` に収まるだけの余裕を残す必要があります。
独自の `--cache` のルートも短くしてください。OS の長いパス名を有効にしても、
すべてのネイティブコンパイラーが長いパスに対応するわけではありません。この構成では、共有のキャッシュ転送と、
Silk を含むターゲット互換のすべての追加機能をそのまま使えます。

この厳格な境界で禁じているのは消費の段階での依存関係の取得であって、ネットワークへのアクセス全般ではありません。
署名、タイムスタンプ、公証、公開は、引き続きオンラインで行う役割を持ちます。この境界が守られていることを
リリース対象ごとに証明するには、ネットワークを遮断した状態での未署名のネイティブビルドが引き続き必要です。

Electron のパッケージングを行わずに、両方のフロントエンドのプロダクトを含むネイティブのエージェントをステージングするには、次のようにします。

```sh
python -m scripts.bundles.stage --out /work/agent-payload --ref HEAD
```

このコマンドは、TUI と Web のプロダクトが渡されなければ、それらを準備してビルドします。
また、`--tui` と `--web` を両方指定すれば、準備済みのプロダクトのルートとして受け付けます。
どちらか一方だけの指定は拒否されます。単体のプロダクトの入出力の取り決めは、
ビルダーの README に明記されています。

`--ref` は、エージェントのソースのスナップショットを選びます。フロントエンドを自動でコンパイルする場合は、
その同じリビジョンの一時スナップショットを使います。フロントエンドのプロダクトを明示的に渡す場合は、
選んだリビジョンから作られたものでなければなりません。

`hermes pm bundle --out /work/agent-payload --ref HEAD` は、ネイティブのツール、
アプリケーションの環境、エージェントをステージングします。この PM のコマンドはフロントエンドをビルドしません。
`npm run payload --workspace apps/desktop` は、フロントエンドを含めるステージングのドライバーを使います。
どちらのコマンドも Electron のインストーラーは作りません。

### PM Bundle の準備と組み立てを分ける {#split-pm-bundle-preparation-and-assembly}

PM Bundle は、同じ隔離されたツールのブートストラップと、ネイティブの準備の一部分を使います。
デスクトップのワークスペース、アイコン、Electron のバインディング、パッケージャーはインストールしません。

```sh
python -S -B scripts/bundles/native_build.py --source "$PWD" \
  --work "$PWD/.build/payload-job" --cache "$PWD/.cache/payload-inputs" \
  --out "$PWD/build/agent-payload" --ref HEAD --prepare-only
python -S -B scripts/bundles/native_build.py \
  --prepared "$PWD/build/agent-payload.prepared.json"
```

選んだリビジョンのクリーンなチェックアウトを使ってください。`--ref` の既定は `HEAD` です。
代わりに `--commit` で完全な SHA を正確に指定することもできます。作業用、キャッシュ用、出力用のディレクトリは
別々でなければなりません。準備処理は、それまで存在しなかった作業ディレクトリを確保します。
`--prepare-only` を付けない場合、ドライバーは組み立てまで行います。`--prepared` には新しい選択や
パスの引数を指定できず、組み立ての前にネイティブの受領証を検証します。依存関係のブートストラップや修復は決して行いません。
受領証とロックは出力と同じ階層に置かれ（`agent-payload.prepared.json`、`agent-payload.prepare.lock`）、配布物には含まれません。
`hermes pm bundle` と同じく、組み立てでは意図的にフロントエンドのプロダクトを渡しません。

既存のキャッシュアクションにある `payload-test` プロデューサーは、`tools`、
`python/runtime`、`native` だけを選びます。ソースの `node_modules`、npm のキャッシュ、
アイコン用の環境、パッケージャーは除外します。PM が管理するツールのブートストラップには Node と
npm が引き続き含まれますが、デスクトップの `npm ci` は実行しません。ネイティブのコンパイラーの識別情報と Windows
ARM64 の前提条件は、共有の担当部分を一度だけ使います。キャッシュがヒットしても、必ずプロバイダーの
許可判定を通ります。ネイティブの SDK やコンパイラーの前提条件は、引き続きホスト側の担当です。PR のジョブは
復元だけを行います。保存には、別のソース ref を持たない、既定ブランチの信頼されたレーンでの準備の成功が必要です。
署名や公開は追加されません。

Termux では、ツールと wheelhouse が前提条件です。

```sh
python scripts/termux/build.py --repo /work/source --payload /work/termux-payload \
  --out /work/packages --tag vX.Y.Z
```

Termux のドライバーには、`--tag` と `--commit` のどちらか一方だけを指定します。TUI のワークスペースを準備し、
共有の TUI ビルダーを呼び出して、そのプロダクトを `build_deb.sh` に渡します。
ダッシュボードは追加せず、bionic 向けの準備も置き換えません。

CLI の取り決めは `scripts/bundles/desktop.py`、
`scripts/bundles/stage.py`、`pm/cli.py`、`scripts/termux/build.py` にあります。

## エージェントとランチャーの共通の取り決め {#shared-agent-and-launcher-contract}

`AgentInputs` は、明示的なパスと配置モードを渡します。

- `contained` は、ランタイムの入力をネイティブのペイロードの中に収めます。
- `fixed` は、Docker や Termux のインストール先のプレフィックスを保ちます。
- `references` は、インストール済みのコードと依存関係を、別々の Nix ストアのパスに置いたままにします。

アセンブラーは、`[project.scripts]` からコンソールコマンドを導き出します。ソースの配置どおりのリソースと、
選んだフロントエンドをコピーするか、明示的に渡された参照先の入力にリンクを張ります。構造の組み立てが済んだあとに
完了マニフェストを書き出します。Nix には、ネイティブのラッパー向けのコマンドと環境の対応表も渡されます。

ランチャーの実装は `scripts/build/launchers.py` にあります。
`launcher_wrapper.py` と `mint_launchers.py` も同じディレクトリにあります。
`scripts/bundles/payload.py` は、git のスナップショット、PM のツール情報、PM ランタイムの封印、
持ち運べるリンクの扱いを担当します。フロントエンドの配置やランチャーの生成は、もう担当していません。

デスクトップのスタンプには、宣言された起動パスが入っています。Electron はこの取り決めをそのまま使い、
ペイロードの取り込みや修復は行いません。バンドル版以外のビルドには、仮置きのエージェントのペイロードは入りません。
Store のパッケージは、更新の仕組みとして `microsoft-store` を宣言します。
サイドロード用のバンドルは `app-installer` を宣言します。

Windows のランチャーの生成は、ペイロードのインタープリターで実行します。POSIX のランチャーは、
明示的なインタープリター、ソース、依存関係のパスを使います。構造の組み立ては、ターゲットのネイティブ環境での
起動テストの代わりにはなりません。マニフェストがあるだけでは、再配置された成果物や署名された成果物が
動くことの証明にはなりません。

## 独立した PM ランタイム {#independent-pm-runtime}

PM の依存関係は、アプリケーションの依存関係グラフではなく、`pm/pyproject.toml` と `pm/uv.lock` から来ます。
アプリケーションの環境と `pm-runtime` は、エージェントの組み立てに渡される別々の入力です。

ネイティブと Docker の準備では `pm.runtime_stage.stage_runtime` を使います。Termux も、
オフラインの wheelhouse と組み合わせてこの関数を使います。Nix は独立した
`nix/pm-runtime.nix` の derivation を使います。ネイティブの封印では、ペイロードのインタープリターと
PM 専用の site ディレクトリを `pm-runtime.json` に記録します。

常駐する PM ワーカーは、宣言されたインタープリターを `-I -S -B` 付きで実行します。ワーカーのスクリプトの前に
追加するのは、宣言された PM の依存関係のディレクトリだけです。アプリケーションの依存関係を借りたり、
呼び出し元のインタープリターに PM のパッケージを追加したりはしません。Docker と Nix のスタンプは、
それぞれ宣言された PM ランタイムを指します。
これらの取り決めは `pm/runtime.py:55–88,171–182` と
`scripts/bundles/payload.py:58–90` にあります。

通常のネイティブのステージングでは、ユーザーのプラグインのツリーを走査しません。プロビジョニングは子プロセスで実行し、
一時的なホームと PM のルート、それに明示的に指定した永続的なビルドキャッシュを使います。
Windows ARM64 の前提条件の準備は、HOME を隔離する前に実行し、ソースからのセットアップや CI と同じプロバイダーを使います。
稼働中の PM は、プラグインの許可判定、世代の選択、トランザクションの状態を引き続き持ちます。どちらの経路でも、
uv の環境を明示的に組み立てるために `pm.environment.PythonEnvironment` を使います。

## 配布物の境界と出力先 {#distribution-boundaries-and-output-paths}

| 配布物 | 選ぶプロダクト | 現在の出力の配置 |
|---|---|---|
| デスクトップのバンドル版・Store 版 | アイコン、TUI、Web、エージェント、デスクトップ UI | プロダクト: `apps/desktop/build/products/`。エージェント: `apps/desktop/build/agent-payload/`。UI: `apps/desktop/dist/`。パッケージ: `apps/desktop/release/` |
| デスクトップのライト版 | アイコンとデスクトップ UI | `apps/desktop/dist/` と `apps/desktop/release/`。エージェントのペイロードは含まない |
| Docker | アイコン、TUI、Web、エージェント | エージェントは `/opt/hermes`、依存関係は `.venv`、PM は `pm-runtime`、生成したコマンドは `libexec` |
| Nix の TUI | TUI | `$out/lib/hermes-tui/{dist/entry.js,package.json}` |
| Nix の Web | Web | `$out/index.html` とアセット |
| Nix のエージェント | TUI・Web を参照するエージェント | `$out/bin`、`$out/share/hermes-agent`、`$out/ui-tui`、`manifest.json`、`command-map.json` |
| Nix のデスクトップ | アイコンとデスクトップ UI、Nix のエージェント付き | `$out/share/hermes-desktop` と `$out/bin/hermes-desktop` |
| Termux | TUI とエージェント | `OUT/hermes-agent_<version>_aarch64.deb`。インストール先は `$PREFIX/lib/hermes-agent/` |

ネイティブのペイロードには、`hermes-agent`、`tools`、`venv`、`pm-runtime`、`bin`、
`uv-cache` と、それらのマニフェストや情報ファイルが入ります。コピーされたフロントエンドのアセットは
`hermes-agent/hermes_cli/tui_dist/` と `hermes-agent/hermes_cli/web_dist/` に置かれます。
TUI のアセットのディレクトリには、`entry.js` と、モジュールモードのパッケージのメタデータが入っています。

Docker は、既存の TUI のプロダクトを `/opt/hermes/ui-tui` に、Web の出力を
`/opt/hermes/hermes_cli/web_dist` に置いたままにします。アセンブラーは、共有の TUI の
アセット配置も `hermes_cli/tui_dist` の下に置きます。venv のコマンドのシンボリックリンクは、
s6 と権限を下げるための shim が使うパスを保ちます。

Docker のフロントエンドのステージが、npm の依存関係とコンパイルを担当します。ランタイムの
ステージがコピーするのは、フロントエンドのプロダクトと、実行時の lint に使うロックされた TypeScript のパッケージだけです。
Node・npm と、Photon の別途インストールされるサイドカーの依存関係は、引き続きランタイムの入力です。
Python の依存関係の準備は、アプリケーションのソースのコピーより前に行います。キャッシュのヒットやレイヤーのサイズについて
実際に主張するには、ビルドの証拠が必要です。

Nix は、`importNpmLock`、uv2nix、プラットフォームごとの上書き、ストアへの参照を引き続き使います。
ビルダーは評価の段階ではなく、derivation のビルドの段階で実行されます。プロダクトごとのソースの
フィルターで、フロントエンド、Python、リソースの入力を分けています。Nix のラッパーは、
アセンブラーのコマンドの対応表を使い、PATH と追加の Python の衝突に関する方針を保ちます。

**Nix の wheel の方針:** `nix/python.nix:128–135` は、Hermes の derivation に対してだけ `HERMES_NIX_BUILD=1` を
設定します。`setup.py:34–72` は、一般的な Hermes の wheel・sdist のビルドを拒否します。
共有のアセンブラーは、インストール済みの Nix のコードを、もう1つコピーを作らずにそのまま使います。
ほかのプロバイダーは、公開された Hermes の wheel ではなく、ソースの配置どおりのコードとメタデータを使います。

Termux は、bionic 向けの wheel のコンパイル、オフラインでのインストール、ネイティブライブラリのパス、
固定のプレフィックスを引き続き使います。インストールされたルートには、`app`、`tools`、
`runtime-libs`、`venv`、`pm-runtime`、`bin` と、マニフェストや情報ファイルが入ります。APT のメンテナー
フックは、`$PREFIX/bin` の下にある宣言済みの CLI のシンボリックリンクを管理し、ほかのパッケージとの
衝突は拒否します。インストール中に依存関係をコンパイルすることはありません。

`stage_apt_repo.py` は、リポジトリのメタデータと署名を担当します。安定版とカナリア版の
スイートは `hermes-stable` と `hermes-canary` です。パッケージのファイルは、署名付きのメタデータより先に
公開されます。[安定版リリースの受け入れ](/hermes/docs/developer-guide/stable-releases/) が、
配布物をまたいだ受け入れと公開を取りまとめます。

## 公開成果物の受け渡し {#public-artifact-handoffs}

デスクトップのリリースのワークフローは、サポートしているバンドル版の各形式を、新しく用意したネイティブのランナーに
ダウンロードしてインストールし、install-e2e と同じ composer・provider・reply の確認を実行します。
Windows のユニバーサル版の組み立てでは、スモークテストの前にバイト列をステージングします。
そのカナリア版のフィードは、両方のネイティブのスモークテストのマトリクスが通ったあとでだけ、別途公開されます。
macOS のフィードの公開と、安定版の候補の受け入れにも、同じようにゲートがあります。
対象の形式、チェックポイントの証拠、Store・Linux・アップロードなしの場合の制限については、
[インストールとチャットの受け入れ検査](https://github.com/NousResearch/hermes-agent/blob/main/tests/install/README.md#post-build-artifact-smoke)
を参照してください。

`scripts.releases.handoff fetch --public-base URL` は、R2 の認証情報なしで、ステージングされた成果物をダウンロードします。
`--tag TAG --commit SHA` か `--commit-build SHA` のどちらかに加えて、
プロデューサーの `--name`、保存先の `--root`、必要に応じて `--include` のセレクターを指定します。
認証付きの受け渡しと同じ、受領証の識別情報、パス、サイズ、SHA-256 の検査を使います。
タグ付きのアーカイブとコミットだけのアーカイブは別々のままで、このコマンドは変わりうる最新版のフィードを解決しません。

公開された成果物の読み取りには HTTPS が必要です。ただし、ループバックのテスト用サーバーは例外です。
URL に含まれる認証情報、安全でないパス、リダイレクトは拒否します。ダウンロードが不完全だったり壊れていたりしても、
すでに検証済みの保存先を置き換えることはありません。受領証のファイルは、選んだすべての成果物の検証が済んでから保存されます。
デスクトップの形式を1つ選ぶ場合も、インストールの前に、一致するものがない場合やあいまいな場合は拒否しなければなりません。

## キャッシュの所有 {#cache-ownership}

PM のバイナリのダウンロードアーカイブと、ネイティブの uv の wheel キャッシュは、使う側が異なります。
互いに入れ替えのきく片付けの対象ではありません。

- PM は、完了したダウンロードを、パッケージの検証と公開が済むまで残します。
  インストールに成功すると、そのとき取得したアーカイブそのものを削除します。失敗した場合は、
  再試行のためにダウンロードを残します。片付けでは、関係のない途中のファイルには手を付けません。
- ネイティブのステージングが古いエントリーを削除するのは、ビルドが所有するツールストアの中だけです。
  ユーザーのマシン全体のストアは削除しません。
- ネイティブのステージングは、変更可能な環境をオフラインで作り直すために `uv-cache/` を残します。
  展開済みの wheel と、インデックス・リビジョンのメタデータはコピーしますが、重複する
  wheel の ZIP と、キャッシュされた sdist の `src/` ツリー（Rust の `target/` の出力を含む）は
  コピーの前に除外します。プロバイダーのキャッシュは変わりません。ネイティブの署名は、ビルド専用の成果物を
  走査せずに、展開済みのバイナリに届きます。
- PM ランタイムとアプリケーションのビルドは、プロバイダーの永続的な uv キャッシュを共有します。
  CI が復元・保存するのはそのキャッシュで、一時的なビルド用の HOME ではありません。ビルドに失敗しても、
  完了した wheel は残ります。一般的な PM のステージングは v2 のキャッシュの名前空間を使い、
  デスクトップの準備は、入力のスナップショット専用の名前空間を使います。
  通常の `uv cache prune` は、参照されなくなったエントリーを削除しますが、オフライン用の
  wheel の入力は捨てません。過去のすべての版を削除したり、サイズの上限を設けたりはしません。
- ソースからのビルドでは、実行時のインタープリターでアイコンを描画します。デスクトップの準備と
  プロダクトのステージングにはそのインタープリターがないので、ロックされたランタイム依存関係を
  （アプリケーションは含めずに）ジョブの作業ディレクトリの下に準備します。デスクトップの wheel キャッシュは
  `CACHE/python/build` です。この環境が、配布されるランタイムに入ることはありません。
- フロントエンドの `node_modules` はプロバイダーの入力であって、フロントエンドのプロダクトではありません。
  Docker の実行時用の TypeScript と Photon の選択は、それぞれ別の例外です。

ネイティブのキャッシュの動作は `scripts/bundles/native.py` にあります。
ペイロードからすべてのキャッシュを取り除くと、オフラインでの環境の再構築ができなくなることがあります。

デスクトップのキャッシュ転送では、PM のツールのエントリー、Python のダウンロード・wheel のキャッシュ、npm の
内容アドレス方式のダウンロード、準備済みのワークスペースの依存関係、ネイティブ・パッケージ用の
ツールの入力を、`scripts/ci/desktop_build_cache.py` で選びます。作業ディレクトリ、仮想環境、
稼働中のユーザーのホーム、署名用のトークン、プロダクトは選びません。
署名結果のキャッシュは別にあります。ネイティブの wheel のパーティションは、観測した
コンパイラー・SDK・OpenSSL の識別情報に依存します。識別情報が不完全な場合は、あえて温まったキャッシュを再利用しません。
これは、ホストの SDK を完全に固定することとは違います。

キャッシュのキーは探すための手がかりであって、許可ではありません。ネイティブのビルダーには、
リリース用とコミット用の別々の実行ジョブがあり、それぞれ `cache-mode: write` と
`cache-mode: read` がそのまま書かれています。GitHub はこの権限をスコープ付きの
キャッシュトークンに強制するので、コミットのビルドは、自分のスクリプトからであってもキャッシュを保存できません。
どちらの分岐も、YAML のアンカーでマトリクス、環境、ステップを共有しています。
元の `build-win32` と `build-darwin` の ID は、2つの分岐をまとめます。検証と入力のアーカイブが
成功したあと、選ばれた分岐だけが成功し、もう一方はスキップされていなければなりません。
選ばれたビルドが失敗、キャンセル、または想定外にスキップされた場合、まとめ役のジョブは失敗し、公開は許可されません。
既存の公開の依存関係は、引き続きこれらの ID を使います。保存をスキップしたり、キーの
プレフィックスを変えたりするだけでは、スクリプトが書き込み側の名前空間を汚染するのを防げません。
既定ブランチのカナリア版のディスパッチは、既定ブランチのキャッシュのスコープを保ちます。
チェックアウトの SHA を変えても、ワークフローの ref のキャッシュのスコープは変わりません。GitHub の
[キャッシュアクセスの取り決め](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#controlling-cache-access-with-cache-mode)を参照してください。

## 固定されたバイナリの入力 {#pinned-binary-inputs}

`python -m scripts.ci.archive_inputs` は、`pm/lock.json` にあるすべての HTTPS の成果物を、
すべてのターゲットについて保存します。Termux のランタイムライブラリとライセンスの固定も対象です。
公開ミラーの場所は `pm/artifact-mirror.json` が持ちます。オブジェクトのキーは
`upstream/sha256/HASH` で、ファイル名やリリースタグとは無関係です。

CI はまず R2 に問い合わせます。上流からのダウンロードが許されるのは 404 のときだけです。
既存の SHA256 を検証してから、変更不可の `If-None-Match: *` でアップロードし、そのあと保存された
オブジェクトをダウンロードして検証します。壊れたバイト列、アクセスの拒否、アップロードの失敗があると、ビルドは止まります。
同時に書き込む処理が同じバイト列を再利用することはあっても、既存のオブジェクトを置き換えることはできません。

全ターゲット向けのワークフローは、main で固定内容が変わったとき、手動のディスパッチのとき、
そして許可されたリリースの前提条件として実行されます。固定されたツールチェーンが使えるようになる前は、
ランナーの Python を使います。デスクトップのジョブはこの前提条件に依存し、そのあとは PM に、
アーカイブ用の書き込み認証情報なしで、検証済みの入力を取得させます。キャッシュを仕込むための
2本目のレーンは必要ありません。ほかの保護されたワークフローは、`setup-pm` の `archive-inputs` の入力で
アーカイブを有効にできます。対象を絞ったアーカイブでは、`--target` と `--store` で使い捨ての
取得エントリーを仕込めます。Termux では、ランタイムライブラリのために `--payload` も渡します。
キャッシュがヒットしても保存は省略されません。デスクトップの R2 の認証情報は、アップロードのステップだけに限定されています。
信頼されていない PR のジョブには、公開用の認証情報は渡されません。

インストール済みの PM クライアント、ブートストラップのインストーラー、Nix の固定内容を使う側は、
主の URL を使い、ダウンロードできなければ公開ミラーを使います。
固定されたハッシュは引き続き拘束力を持ち、クライアントには認証情報もアップロードも必要ありません。
PM は取得元ごとに再開のための状態を持ち、試した URL を報告します。
上流ですでに削除された Termux のファイルについては、上流が 404 や 410 を返したあと、CI の公開処理が
コミュニティの Internet Archive から同一のバイト列を回収できます。
最新のパッケージに固定し直すことは決してありません。

このアーカイブには、有効期限のライフサイクルルールを設定してはいけません。リリースの削除処理は、
このプレフィックスには及びません。対象は PM のバイナリの固定内容と Termux のランタイムの入力で、
固定されていない apt パッケージ、OCI イメージ、言語のパッケージレジストリ、ビルドツールが
独自にダウンロードするネイティブの Electron・SDK のアーカイブは含みません。

## 検証の境界 {#verification-boundary}

次の検査は、プロダクトのテストと、配布物の受け入れ検査を区別しています。
署名付きのインストーラーと Android 端末での実行には、それぞれのネイティブのランナーが必要です。

macOS の dmgbuild の取得を担当する部分は、厳格なビルドではなく、パッケージングの準備に属します。
ツールのプロバイダーを変更したあとは、ネイティブの DMG の作成、切り離しの診断、署名、公証を
実際に動かして確かめる必要があります。Windows の x64・ARM64 と、macOS の x64・ARM64 について、
コールド・ウォームの状態と署名済みの成果物の受け入れ検査は、Linux のヘルパーのテストとは別のままです。
Linux のデスクトップのリリースレーンは無効のままです。

範囲を絞ったヘルパーのテストでは、次の要件をすべて確かめることはできません。

- 準備済みの変更不可の入力からの、オフラインでのフロントエンドのコンパイル。
- 単体での TUI の操作と、ダッシュボードのバックエンド・アセットの動作。
- 各ターゲットでのネイティブの Electron バインディングと、最終的な署名付きのランチャー。
- ネイティブのペイロードを再配置したあとの PM の隔離。
- 残したキャッシュを使った、変更可能なアプリケーション環境のオフラインでの再構築。
- Docker での root 以外のユーザーによる CLI・TUI・Web・ブラウザーの動作と、すべてのイメージレイヤーの中身。
- 参照ベースのラッパーを通した、Nix パッケージの実際の実行。
- ネットワークを無効にした新しい環境での Termux パッケージのインストールと、Android 端末での動作。

既存の受け入れ検査として、`tests/install/BUNDLED_UPDATES.md`、
`tests/docker/`、Nix のチェック、`scripts/termux/check_deb.sh` と
`validate_installed.py` を使ってください。コンテナーでの受け入れ検査は、Android 端末での受け入れ検査の
代わりにはなりません。この文書から、ビルドが通るという主張は導けません。
