# contract — 素材の参照モデル v0（共有ライブラリ参照の台帳と解決規則）

ライブラリの置き場は既定で作業場の `library/`。作業場が無いときは従来の `~/.akari/assets/` を使う。
`akari-assets list`（または `akari assets list`）の先頭行で実際の置き場を確認する。
以下の `<ライブラリの置き場>` はその表示先を指し、音源はその下の `audio/` に入る。

- 状態: 実装済み（機械層 2026-09-02。シェル UI の採用 = 取り込みフローの reference 既定化・プロジェクト面の「参照」札・
  プレビュー / タイムラインの経路・「素材をまとめる」は 2026-09-22 `task/2026-09-21-library-reference-in-shell` で採用）
- 決定日: 2026-09-02
- 実装: `packages/asset-resolver`（記帳・実体化）/ `packages/render-cut`・`packages/edit-lint`（解決）

## 1. 目的

カタログ素材をプロジェクトごとに実体コピーすると、同じ素材が何度もダウンロード・複製されて
プロジェクトが肥大する。実体は**マシン単位の共有ライブラリ**（`<ライブラリの置き場>/<category>/<id>/`）に
1 部だけ置き、プロジェクトには**参照だけを記録**できるようにする。

## 2. 設計の要点

- **edit.json は変えない**。参照素材も従来どおり `assets/<category>/<id>/<file>` の
  プロジェクト相対パスで宣言される。実体がプロジェクトに無いことは参照台帳が説明する。
- 参照台帳 = プロジェクトの `.akari/asset-references.json`:

  ```json
  { "version": 0, "references": [ { "id": "<素材 id>", "category": "<カテゴリ>" } ] }
  ```

  references は category → id の安定ソート・重複なし。読み手は寛容（無い / 壊れは空扱い）、
  書き込みは tmp + rename の原子的更新。`version` は台帳自身のスキーマ版数であり
  edit.json の version とは無関係。
- **解決規則**: 宣言されたプロジェクト相対パスが `assets/<category>/<id>/<rest>` の形で、
  (1) プロジェクト実体が存在せず、(2) 台帳に `{category, id}` があるとき、
  `resolveAssetLibraryRoots().read` の順（新しい置き場 → 従来の置き場）にフォールバックする。
  解決先は各ルートで字句・realpath containment を満たす正規ファイルで
  あること（`..` 等の脱出は fail-closed で拒否）。
- render-cut は解決した入力を render inputs 記録に `scope: "library"` として残す
  （既存 `scope: "akari"` と同列の additive 記録）。採用した実ルートを `library_root` に保存して後段でも検査する。edit-lint は解決できる参照を欠落と報告せず、
  台帳にあるが実体が無い参照は「共有ライブラリ参照（未取得）」として欠落報告する。
- 素材ライブラリ参照の解決ロジックの正本は `packages/creator-root/src/library-reference.mjs` に置く。
  render-cut / edit-lint は各 `src/library-reference.mjs` から正本を再 export する。

## 3. 使い方（CLI）

```sh
# 参照モードで取得（コピーせず台帳へ記帳。既定は従来どおりコピー）
akari-assets fetch <id> --project <dir> --reference

# 「素材をまとめる」— 参照の実体化（持ち出し・アーカイブ用）
akari-assets bundle --project <dir> [--dry-run]
```

bundle は台帳の各参照をキャッシュから `assets/<category>/<id>/` へ実体化して台帳から除去する。
未取得の参照は resolve（取得）を試み、取得できないものは台帳に残して部分成功（exit 非 0）で報告する。冪等。

## 4. 書き出しエンジンの配信

OSR / GPU の共通配信サーバーは、render-cut が解決した入力のうち `scope: "library"` の
全件を許可表として受け取る。映像だけでなく、音・静止画・オーバーレイ内の素材も対象。
キーは宣言パス `assets/<category>/<id>/<rest>`（区切りは `/`）、値は
`{ absolute: <実体の絶対パス>, library_root: <採用ルート> }`。
配信側は置き場の探索や台帳からの再解決を行わない。

ffmpeg の計画には、同じ render inputs の解決結果で素材パス欄だけを絶対パスへ置き換えた
edit の写しを渡す。BGM・SFX・ナレーション・分離音声・レイヤー音声の入力と尺の probe が対象。
映像ソースの cut 音声は既存の解決済み capabilities を使い、contact sheet は書き出した動画を読む。
元の edit.json、配信用の宣言パス、receipt の入力パスは書き換えない。

別プロセスへの受け渡しには
`<projectRoot>/.akari/render-tmp/media-references-<render-cut の PID>.json` を使う。
表の形式は `{ "token": "<合言葉>", "references": { "<宣言パス>": { "absolute": "<実体の絶対パス>", "library_root": "<採用ルート>" } } }`。
render-cut は実行ごとに `crypto.randomBytes(32)` の合言葉を hex 文字列として生成し、
同じ値を環境変数 `AKARI_RENDER_MEDIA_REFERENCES_TOKEN` に設定してから書き出しを起動する。
この変数は Electron 子プロセスへ継承される。終了時は `finally` で元の値へ戻し、元が未定義なら削除する。
合言葉はログ・エラー文・render.json に出さない。
render-cut は子プロセスの起動前に排他的に作成し、OSR / GPU（GPU から OSR への再試行を含む）の
終了時に `finally` で成功・失敗とも削除する。Electron は親 PID からパスを決め、サーバー生成時に
一度だけ読む。別 CLI の並行実行は表を共有しない。プロセス内の使用中パス集合で
同じプロジェクトの重複実行（ネスト呼び出しを含む）を拒否する。集合に無い自 PID の既存表は、
クラッシュや PID 再利用による残存表として削除してから排他的に作り直す。
使用中の記録は作成・書き出し・後片付けの失敗時にも解除する。他 PID の表は回収しない。
ファイル経由の表は、環境変数が無い・空、表の token が文字列でない、UTF-8 バイト長が異なる、
または `crypto.timingSafeEqual` で一致しない場合は一切使わない。token の無い旧形式や壊れた JSON も空の表として扱う。
これにより単体 CLI など render-cut を通らない起動では、プロジェクトに植え込まれた表から外部ファイルを配信しない。
配信 API の明示引数 `mediaReferences` がある場合は従来どおりそちらを優先し、
表が無い場合や認証できない場合はプロジェクト内だけを配信する。

`/media/<宣言パス>` は次の順で検査する。

1. デコード後に `..` セグメント（`/` と `\` の両区切り）を含む要求は 403。
2. プロジェクト内の実体があれば優先する。字句・realpath の両方でプロジェクト内に収まることを
   検査し、symlink による脱出は 403、正規ファイルでなければ 404。
3. プロジェクトに実体が無い場合のみ、許可表の完全一致キーを調べる。表に無いファイルは
   ライブラリ配下に存在しても 404。
4. 表の実体が採用ルート内にあることを字句で確認し、さらに実体と採用ルートを毎回 realpath で
   解決して包含を再検査する。脱出は 403、欠落・非正規ファイルは 404。
5. 許可した実体は既存のファイル配信を使い、通常取得は 200、Range 取得は 206 とする。

台帳にあっても置き場に無い必須素材は、従来の render inputs / 計画段階のエラーで停止する。
配信ページの 404 を待ってから失敗させない。

## 5. スコープ外（後続）

- ~~シェル UI の採用（取り込みフローの reference 既定化・プロジェクト面の「参照」バッジ・
  プレビュー経路のフォールバック）~~ → 2026-09-22 に採用済み（「状態」行を参照）。残るのは置き場側の変化で札を自動更新する件
- 共有キャッシュの容量管理 UI

出自: 2026-09-02 の素材パネル再設計ラウンドの裁定 3（実体 = 共有キャッシュ・プロジェクトには参照・
持ち出しは「素材をまとめる」で閉じる）。
