# ビルドと出力の保証

Canonical page: https://ankiforge.dev/ja/docs/build-guarantees/
Language: ja
Description: 一時成果物、パスの保護、検査の上限、確認済み候補の公開を理解します。

`Project::build` は操作が成功した場合にのみ `BuildOutput` を返し、APKG 成果物の存在を保証します。失敗した操作は、構造化された kind/code、観測結果、原因、実際の公開状況を持つ `BuildError` を返します。

## 一時成果物と永続成果物

所有権のモデルを明示的に選びます。

| 要求 | 所有権 |
| --- | --- |
| `BuildOptions::to(path)` | 永続的な保存先。ハンドルの解放後も残る |
| `BuildOptions::temporary()` | 一時ファイル。最後の成果物所有者とともに削除される |
| `artifact.persist_to(path)` | 新たな永続所有者。元の所有者も引き続き使用できる |

一時パスを読んでいる間は、`BuildOutput` または成果物のクローンを保持してください。レポートや JSON スナップショットはファイルの生存期間を延ばしません。Rust は通常の仕組みで所有者をドロップします。Node には決まったタイミングで解放するための `artifact.close()` があります。パスをアプリケーションの状態にコピーするだけでは、所有者は作成されません。

Rust の成果物ハンドルは、ビルドや `persist_to` で相対パスを指定した場合でも絶対パスを保持します。その後にプロセスの作業ディレクトリが変わっても、既存ハンドルが読み取りやコピーに使う元ファイルは変わりません。新しい `persist_to` の保存先は、呼び出し時のディレクトリを基準に解決されます。

`BuildReport::snapshot()` は観測結果だけを含みます。`BuildOutput::snapshot()` と `BuildError::snapshot()` は、さらに実際の成功・失敗を示します。成功したスナップショットにも警告が含まれる場合があります。診断の重大度、パスの存在、レポートに比較結果があるかどうかから、成功・失敗を推測しないでください。

## 基準パッケージと公開

更新時には、前回配布時のオリジナル APKG を不変の基準パッケージとして保存し、出力先には別のパスを選びます。基準の証拠と候補の内容は公開前に検査されます。ポリシーが拒否した場合は出力を置き換えません。証拠の欠落・破損や検査上限の超過は、許可できない失敗です。

公開には APKG のアトミックな置き換えを使います。保証はファイル単位です。アプリケーションの別の JSON レポートを書き出す処理は、別の操作です。通常のビルドは識別情報の別ファイルを出力しません。バージョン付きの完全な証拠は配布パッケージに埋め込まれます。

永続化の確認中など、公開後の遅い段階でエラーが発生することがあります。そのため、エラーのスナップショットは `PublicationSnapshot` の事実を記録します。対象は `path`、`stage`（`not_published` または `published`）、`temporary`、耐久性（`confirmed` または `unconfirmed`）です。再試行したり、ファイルが書き出されていないと通知したりする前に、これらを確認してください。`PersistError` も公開状況と実際の I/O エラー原因を保持します。アトミックな置き換えだけで、クラッシュ後の耐久性まで確立されるわけではありません。

スナップショットのシリアライズはファイルシステムにアクセスせず、ネイティブのパスを保持します。Unicode パスは JSON 文字列になります。UTF-8 でない Unix パスは `{"encoding":"unix_bytes","bytes":[...]}`、対になっていない UTF-16 サロゲートを持つ Windows パスは `{"encoding":"windows_wide","units":[...]}` を使います。配列には正確なネイティブのバイト列またはコード単位が入ります。成功時の成果物パス、失敗時の公開パス、バインディングのエラーパス詳細はすべてこの表現を使います。これらの値はパスを記述するだけで、成果物の所有権は保持しません。特定のネイティブパスをファイルシステムが拒否しても、公開に失敗した事実はシリアライズできます。

## 検査の上限

アーカイブ、エントリー数、展開、デコード後のコレクション・メディア、zstd ウィンドウには有限の上限があり、基準パッケージと候補に独立して適用されます。`InspectLimits::default()` から始め、必要な上限だけを意図的に増やしてください。0は無制限を意味しません。これらのカウンターは、プロセス全体のメモリや CPU のサンドボックスではありません。メディアのインポートには、それより前の段階で適用する別のアセット単位の上限があります。

## 並行処理

Rust のビルドは不変のプロジェクト状態を借用します。データを所有するモデルとメディアは、プロジェクト間で共有できます。Node バインディングは build または compare の呼び出し時にプロジェクトのスナップショットを取得するため、後からノートを追加してもその要求は変わりません。すべての読み取り側が終わるまで成果物の所有者を保持してください。別の利用側がまだパスを必要としている間に最後の一時所有者を閉じることは、アプリケーション側の生存期間管理の誤りです。

パッケージの検査だけでは、クライアント側のすべての動作を確認できません。対象の Anki バージョンと設定で、描画、再生、更新のインポートをテストしてください。復習スケジュールとスキーマ変更の制約は[更新](https://ankiforge.dev/ja/docs/updates/)を参照してください。

## 候補ファイルを準備し、確認後に保存する

候補を事前に生成し、確認後に保存する機能は、公開 `0.3.1` パッケージに含まれています。

`prepare_publication` は1つの非公開の候補をビルド・検査・比較します。`publish` は同じファイルを使います。オプションと相対保存先は準備時に確定します。プロジェクトを編集しても候補は変わりません。準備だけで保存先が置き換わることはありません。未公開の所有者をドロップまたは閉じると候補が削除されます。

公開の試行は、ポリシー拒否や I/O 失敗を含めて、必ず所有者を消費します。再試行したり、ポリシーや保存先を変えたりするには、再び準備してください。レポートは引き続き読めますが、ファイルは所有しません。Node と Python は冪等な `close()` と、理由が `closed` または `consumed` の利用不可エラーを提供します。公開開始後に閉じてもキャンセルはしません。所要時間にはレビュー待ち時間を含みません。置き換え成功後に耐久性の確認が失敗する場合があるため、実際の公開状況を確認してください。

[完全な例](https://ankiforge.dev/ja/docs/updates/#build-once-review-then-publish)と各言語の API リファレンスで、型付きエラーとワーカーの動作を確認できます。
