yoi/tickets/scope-exclusion.md
2026-04-14 12:09:18 +09:00

102 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 複数 Pod 間の Scope 排他制御
## 背景
[scope-redesign.md](scope-redesign.md) で Scope は「allow / deny の領域リスト + permission レベル」に再設計された。
これにより複数 Pod が同じファイルツリーに対して異なる権限を宣言する状況が日常化する。
現状、複数 Pod が同じ pwd や重なる allow を持っても何のチェックも警告もない。
両者が同じファイルに `write` を持っていた場合、ツール経由で同時に書き換えて
内容を破壊しうる。Git のコミット粒度より細かい競合は履歴からも復元しづらい。
Pod を「並行作業の単位」として扱う以上、scope の重なりは設計時に検知・制御
できる必要がある。
## 要件
- **R1: write の重複を検出する**
同じファイルが 2 つ以上の Pod から `write` 権限で見えている状態を作らせない。
片方の Pod が起動中なら、もう片方の起動を拒否する(または昇格を拒否する)。
- **R2: read は共有可**
`read` のみの重なりは許す。複数の閲覧者は問題ない。
- **R3: deny は重なりに影響しない**
`deny` で write が落とされている path は、その Pod にとって write を持たないと
みなして他 Pod との重複判定を行う。
- **R4: 解放の確実性**
Pod の異常終了で排他が永久に残らないこと。lock の所有者プロセスが死んだら
自動解放されるか、stale lock として検知できる。
- **R5: 観測可能性**
ユーザーが「今どの Pod がどこを write 占有しているか」を見られる。
競合で起動が拒否されたとき、競合相手をエラーメッセージで示す。
## 設計上の論点
### 排他の粒度
選択肢:
- **A. ファイル単位** — 厳密だが lock 数が膨大になる
- **B. allow rule 単位** — 宣言された target ごとに lock。粒度が荒いが宣言と一致して直感的
- **C. パス prefix の最小被覆** — write 領域を最小の prefix に縮約して lock。中間
おすすめは **B**。Pod の宣言と lock の単位が一致する方が説明しやすく、
ユーザーが「この Pod は src を握っている」と理解しやすい。
### lock の保管場所
選択肢:
- **A. lock file** — `~/.insomnia/locks/<hash>.lock` 等。各 Pod プロセスが直接握る
- **B. 中央レジストリプロセス** — 常駐デーモンが scope の貸し借りを管理
- **C. session-store の拡張** — 既存の永続化基盤に lock テーブルを足す
おすすめは **A**。デーモンを増やしたくないし、Pod は短命〜中時間生存の想定なので
ファイルロック (`flock(2)` / `fcntl`) で十分。stale lock 検知は PID 死活で行う。
### 競合発生時の挙動
選択肢:
- **A. 起動失敗 (fail-fast)** — エラーで起動拒否、ユーザーが手で解決
- **B. 待機 (block)** — 競合相手が解放するまで起動を待つ
- **C. 自動降格** — 競合する write を read に降格して起動
おすすめは **A**。並行作業の単位として Pod を起動するなら、競合は意図しない
状況であることが多い。明示エラーで気づける方が安全。`--wait` フラグや `--read-only`
フラグで B / C を選べるようにするのは将来拡張で十分。
### 取得タイミング
- Pod 起動時に scope 全体を一括取得し、終了時に解放
- 起動中に scope を変えることは現状想定しないmanifest 編集 → 再起動)
### resume との関係
`Pod::restore` でも同じ scope が要求される。前回終了時に解放されているはずなので
取得が成功するのが正常。失敗する場合は別 Pod が起動している = ユーザーが意図的に
2 つ動かそうとしている。fail-fast に乗せる。
## 影響範囲(想定)
実装時に詰める。現時点での見立て:
- 新規モジュール `crates/pod/src/scope_lock.rs`(または独立クレート `scope-lock`
- lock の取得 / 解放
- stale lock 検知PID 確認)
- 競合情報の収集(誰がどの allow を握っているか)
- `Pod::new` / `Pod::restore` の前段で lock 取得
- `Pod` の Drop / 明示解放で lock 返却
- `Controller` 層でのエラー伝搬とユーザー向けメッセージ
- CLI に「現在のロック一覧」を見るコマンド(観測性 R5
## 非ゴール
- **同一 Pod 内の並行性制御** — 1 Pod 内のツール並行実行は本 ticket では扱わない
- **ネットワーク越しの排他** — ローカルファイルロックのみ。複数マシンで同じワーキングコピーを共有する想定はしない
- **read-write lock の細かな格上げ/格下げ** — 取得時に確定、起動中の変更はしない
## 依存
- [scope-redesign.md](scope-redesign.md) — allow / deny / permission レベルの構造が前提