マニフェスト改修完了

This commit is contained in:
Keisuke Hirata 2026-04-19 08:05:20 +09:00
parent 4ec8f63482
commit 911d3b8d6c
4 changed files with 18 additions and 161 deletions

View File

@ -3,7 +3,6 @@
- [ ] Bash ツール (Permission 層と統合) → [tickets/bash-tool.md](tickets/bash-tool.md)
- [ ] Compact の改善(要約品質 + 挙動詳細) → [tickets/compact-improvements.md](tickets/compact-improvements.md)
- [ ] Protocol の設計 → [tickets/protocol-design.md](tickets/protocol-design.md)
- [ ] Manifest のパス解決: cwd ベース + manifest ファイル相対 → [tickets/manifest-path-resolution.md](tickets/manifest-path-resolution.md)
- [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
- [ ] Pod オーケストレーション
- [ ] Pod の上流イベント報告 (子 → 親) → [tickets/pod-upstream-events.md](tickets/pod-upstream-events.md)

View File

@ -1,86 +0,0 @@
# Manifest のパス解決: cwd ベース + manifest ファイル相対
レビュー中: [manifest-path-resolution.review.md](manifest-path-resolution.review.md)
## 背景
現状 manifest 内のパス(`pod.pwd` / `provider.api_key_file` / `scope.allow.target` / `scope.deny.target` / `compaction.provider.api_key_file`)は全て絶対必須で、相対パスは `ResolveError::RelativePath` で弾かれる。
これは 4 層builtin / user / project / overlayのカスケードで「相対の基準点が層ごとに違う」曖昧さを避けるための制約だったが、次の点で歪みを生んでいる
- `pod.pwd` フィールドが Unix 慣習cwd はプロセス状態、config には書かない)と乖離
- project manifest で `scope.allow = [{ target = "<repo>" }]` のような絶対パスを強いられ、プロジェクトをどこに置いても動くはずの設定が壊れる
- user manifest で `api_key_file = "~/.config/insomnia/keys/anthropic"` が書けない(`~` 展開もしていない)
cargo / pyproject / npm などに倣い「相対パスは manifest ファイルの位置基準」に切り替える。合わせて `pod.pwd` を廃止し、プロセスの cwd を使う。
## 新しい解決規則
- `pod.pwd` フィールドは削除。Pod の作業ディレクトリ = プロセスの cwd
- 相対パスの基準は層ごとに決める
- user manifest (`~/.config/insomnia/manifest.toml`) の相対 = そのファイルの親ディレクトリ
- project manifest (`<project>/.insomnia/manifest.toml`) の相対 = **プロジェクトルート**`.insomnia/` の親)。`target = "."` がワークスペース全体を指すように
- overlayインライン TOML、ファイル位置なしの相対パスは **プロセスの cwd** 基準
- builtin 層には manifest を埋め込んでいないので対象外
## 解決のタイミング
各層を**マージする前に絶対化**する。層をまたいだ相対パス合成は行わない。
```
user.toml (partial) → resolve_paths(base=user_dir) → absolute partial
project.toml (partial) → resolve_paths(base=prj_dir) ─┤
│── merge → PodManifestConfig
overlay (partial) → resolve_paths(base=cwd) ──────────┤
builtin defaults → ────────────────────────────────── ┘
```
`TryFrom<PodManifestConfig>` の時点では全パスが絶対になっているので、`ensure_absolute` は不変条件のチェックとしてのみ残す。
## 影響範囲
### `crates/manifest`
- `PodManifestConfig` から `pod.pwd` を削除
- 各 partial config を「ベースパス付き」で解決するヘルパーを追加(関数シグネチャ案: `fn resolve_partial(cfg: PodManifestConfigPartial, base: &Path) -> PodManifestConfigPartial`
- 対象フィールド: `provider.api_key_file` / `scope.allow.target` / `scope.deny.target` / `compaction.provider.api_key_file`
### `crates/pod/src/factory.rs`
- `with_user_manifest` / `with_project_manifest_from` は渡された manifest ファイルの親ディレクトリを base として保存、解決時に使う
- `with_overlay_toml` / `with_overlay_config` はプロセス cwd を base として使う
- マージ順は現状のままoverlay が最優先)
### `crates/pod/src/pod.rs`
- `pod.pwd` を参照している箇所を `std::env::current_dir()` に置き換え
- `Pod::from_manifest` / `from_manifest_spawned` のシグネチャから pwd 関連を削除
### `crates/pod/src/spawn_pod.rs`
- overlay TOML 構築から `pod.pwd` を消す
- 子プロセス起動時に `Command::current_dir(spawner_pwd)` で cwd を明示
現状の「spawner の pwd を子に引き継ぐ」挙動を維持するため)
- 将来、LLM が子の cwd を明示的に指定したくなったら `SpawnPod` の入力に `cwd` を足す(本チケット範囲外)
### `crates/pod/src/main.rs`
- `--pwd` フラグは削除cwd が代替)
- 起動スクリプトや TUI 側で `cd` してから `pod` を起動する運用に変更
## 完了条件
- `pod.pwd` フィールドの削除
- 各層のパスが manifest ファイル基準overlay は cwd 基準)で解決される
- マージは絶対化後の値で行う
- 既存テストが通る / 相対パスを使った manifest で起動可能
- `api_key_file = "keys/anthropic"` が user manifest 内で動作
- project manifest で `scope.allow = [{ target = "." }]` が動作
## 範囲外
- `~` 展開(`dirs::home_dir()` ベースの展開は別途。まずは `./keys/anthropic` のような単純相対のみ)
- `SpawnPod` の入力に子 cwd の指定を追加cwd 明示は別チケット)
- `pod` CLI の `--pwd` 廃止後の移行期間対応(一発破壊的変更で行く)

View File

@ -1,64 +0,0 @@
# Review: manifest-path-resolution
実装コミット `aed46e6`(マニフェスト解決の相対パス化)に対するレビュー。`cargo build` clean、`cargo test --workspace` 全 pass。
## 総評
チケット要件(`pod.pwd` 削除、相対パス = manifest ファイル基準、overlay は cwd 基準、マージ前絶対化)を忠実に実装。テストもカスケード各層・不変条件違反・両 manifest レイヤの相対解決まで網羅。ビルド・テスト通過。
指摘 1 の UX 判断だけ合意したら完了可。
## 完了条件の対応
| 要件 | 状態 | 確認箇所 |
|---|---|---|
| `pod.pwd` 削除 | ✅ | `PodMeta``PodMetaConfig` から削除 |
| Pod pwd = cwd | ✅ | `pod.rs:current_pwd()` = `current_dir().canonicalize()` |
| 相対 = manifest ファイル基準 | ✅ | `PodManifestConfig::resolve_paths(base)` + `factory::manifest_base()` |
| overlay は cwd 基準 | ✅ | `factory::resolve_and_merge_overlay``current_dir()` を base |
| マージ前に絶対化 | ✅ | `factory::resolve()` で各層 `.resolve_paths(&base)` → merge |
| `ensure_absolute` は不変条件チェックのみ | ✅ | `TryFrom` に残存、cascade 層で通れば通るだけ |
| `--pwd` 廃止 | ✅ | CLI から削除 |
| `api_key_file = "keys/anthropic"` 動作 | ✅ | `resolve_paths_joins_relative_api_key_file` テスト |
| `scope.allow target = "."` 動作 | ✅ | `project_manifest_relative_paths_resolve_against_insomnia_dir` テスト |
## 指摘と判断
### 1. project manifest の base が `.insomnia/` であることの UX要判断
**状況**: 実装は `<project>/.insomnia/manifest.toml``target = "."` と書いた場合、**`<project>/.insomnia/`** を指す。チケットの「manifest ファイルのあるディレクトリ基準」を忠実に実装した結果。
```rust
// factory.rs:538 テスト
// project manifest 内 target = "." が .insomnia/ ディレクトリに解決される
assert_eq!(manifest.scope.allow[0].target, insomnia_dir);
```
**懸念**: ユーザが「project manifest で `target = "."`」と書いたら自然には「プロジェクトルート」を意図する。`.insomnia/` 下を scope の対象にしたい運用は稀。
**選択肢**:
- **(a)** このまま。規則の一貫性(全層 "manifest と同じ dir")を優先し、ドキュメントで「`target = ".."` でプロジェクトルート」と案内
- **(b)** project manifest のみ base を親 `<project>/` にする。user manifest と挙動が揃わなくなる例外ルール
- **(c)** `.` 表記を使わず「絶対パス or `..` 表記」を案内のみ
**判断**: **(a) を推奨**。user manifest (`$XDG_CONFIG_HOME/insomnia/`) で `keys/anthropic``$XDG_CONFIG_HOME/insomnia/keys/anthropic` を指すのと同じ規則で、シンプル。ただし project で違和感が強いので README / docs で**典型例のテンプレ**を提示する必要あり(例: `target = ".."` で project root
この判断を合意できれば完了条件を満たす。(b) を選ぶなら `factory.rs:manifest_base` から派生した project 専用の親ディレクトリ計算に差し替える小改修が必要1 関数程度)。
### 2. `pod.pwd` を書いた古い manifest の警告が埋もれる
**状況**: `from_toml_accepts_unknown_field` テストで確認されている通り、`pod.pwd = "/obsolete"` は `serde_ignored` の warn でスキップされる。`tracing_subscriber` が WARN 有効になっていないと出ない。
**判断**: 範囲外だが、移行ユーザが「設定したのに効かない」と混乱するリスクあり。README / CHANGELOG にマイ採用しないション注記を入れたい(本チケットに含めるか、別 issue を切るかは任意)。本チケットの完了条件には影響しない。
### 3. 軽微
- `PodManifestConfig::resolve_paths``debug_assert!(base.is_absolute())` は release で落ちないが、現状の呼び出し側(`manifest_base` / `current_dir`)が絶対を保証するので許容
- `spawn_pod.rs` で overlay TOML から `pod.pwd` を消し、`Command::current_dir(spawner_pwd)` で子に cwd を伝える構造へ正しく移行
- `Scope::from_config` の signature 変更(`base` 引数削除)が全呼出箇所に反映されている
- 不変条件違反(`ResolveError::RelativePath` / `ScopeError::RelativeTarget`)が両層でチェックされていて、万一 cascade 解決漏れがあっても catch される二重防衛になっている
## 完了に向けた作業
- 指摘 1 について (a) で合意 → このままマージ
- ドキュメントでの「`target = ".."` で project root」ガイドは別タスク化可本チケット範囲外扱い

View File

@ -22,7 +22,7 @@ spawned Podのライフサイクルに親 Pod が反応する仕組み
```rust
pub enum Method {
Run { input: String },
Notify { message: String }, // 既存: 人間・tool → LLM 文脈、副作用なし
Notify { message: String }, // 人間・tool → LLM 文脈、副作用なし(本チケットで source を削除)
PodEvent(PodEvent), // 新: 子 → 親、typed なライフサイクル報告
Resume,
Cancel,
@ -34,7 +34,7 @@ pub enum PodEvent {
/// 子が1ターン終えて IDLE になった
TurnEnded { pod_name: String },
/// 子でエラーが発生した(ターンは続行されるとは限らない)
/// 子で Worker 実行エラーが発生した
Errored { pod_name: String, message: String },
/// 子が停止した
@ -45,12 +45,24 @@ pub enum PodEvent {
parent_pod: String, // 又貸し元(= 子自身)
sub_pod: String, // 孫 Pod の名前
sub_socket: PathBuf, // 孫 Pod の socket path
scope: Vec<ScopeRule>, // 又貸しされた scope
scope: Vec<ScopeRule>, // 又貸しされた scope`protocol` crate に移動)
},
}
```
`Method::Notify` は触らない。`message: String` のまま。
### `Method::Notify``source` 削除
現状 `Method::Notify { source: String, message: String }``source` フィールドを削除して `Method::Notify { message: String }` にする。`PodEvent` が typed な子 → 親報告を担うようになったことで、`Notify` は本来の「人間・tool が LLM 文脈に自由テキストを注入する」役割に純化する(発信者の識別は不要になる)。
影響範囲:
- `protocol::Method::Notify` の定義変更、serde round-trip テストの更新
- Controller main loop の `Method::Notify { source, message } => pod.push_notification(source, message)``pod.push_notification(message)` 形へ変更
- `Pod::push_notification(source, message)` のシグネチャから `source` を落とす(呼び出し元を追って整理)
- 既存テスト(`controller_test.rs` の Notify ケース、`pod_comm_tools_test.rs` など)の入力を新シグネチャに揃える
### `ScopeRule` / `Permission` の移動
`ScopeRule``Permission` は wire 型として `PodEvent::ScopeSubDelegated` で protocol を経由する必要があるため、現状の `manifest` crate から `protocol` crate へ移動する。`manifest` は `protocol::ScopeRule` を re-export するか、単に `protocol::ScopeRule` を直接参照する形に切り替える。移動により `protocol``manifest` の逆依存が発生しないようにする。
### 子(発信側)
@ -123,17 +135,14 @@ variant 別の (1) の中身:
- 親が再起動した場合や送信漏れた場合は、親の `ListPods` ツール(既存)による health check + `scope_lock::reclaim_stale` の stale 回収で不整合を解消する
- これは「コールバックは最適化、ポーリングが真のフォールバック」という方針の継続
## 設計で決めること
- **送信の接続タイムアウト**: `SpawnPod` / pod-comm-tools と揃える5 秒想定)
### 決定事項
## 決定事項
- **順序保証は求めず、ハンドラを冪等・遅延到着に強くする**: fire-and-forget の unix socket 接続は順序を保証しない。`TurnEnded` 直後に `ShutDown` が届いても、逆順で到着しても親側で成立するように `apply_event_side_effects` を設計する。具体的には:
- `ShutDown` 受信時、すでに registry から削除済みでもエラーにしない(`release_pod` の `UnknownPod` を swallow する既存挙動を踏襲)
- `TurnEnded``ShutDown` より後に届いても、該当 Pod が既に registry にいなければ render だけして終えるLLM 向け通知は出る、system 処理は no-op
- `ScopeSubDelegated` で孫が既に registry にいたら上書きせず no-op`DuplicatePodName` を swallow
- **`ScopeSubDelegated` の親連鎖は直接の親のみ + 再発射**: 上記「又貸しの親連鎖」セクション参照。曾孫以上は再帰的に再発射で root まで届く
- **送信の接続タイムアウト**: `SpawnPod` / pod-comm-tools と揃えて 5 秒固定
## 完了条件
@ -151,4 +160,3 @@ variant 別の (1) の中身:
- リモート親への送信SSH 越し)。ローカル Unix socket のみ
- 配信保証at-least-once / exactly-once
- 親再起動時の「見逃したイベント」の再送。ポーリングで補う前提
- `Method::Notify``source` フィールド削除(別チケット)