diff --git a/TODO.md b/TODO.md index ac8b6460..f5628d1a 100644 --- a/TODO.md +++ b/TODO.md @@ -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) diff --git a/tickets/manifest-path-resolution.md b/tickets/manifest-path-resolution.md deleted file mode 100644 index 94d7fe65..00000000 --- a/tickets/manifest-path-resolution.md +++ /dev/null @@ -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 = "" }]` のような絶対パスを強いられ、プロジェクトをどこに置いても動くはずの設定が壊れる -- 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 (`/.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` の時点では全パスが絶対になっているので、`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` 廃止後の移行期間対応(一発破壊的変更で行く) diff --git a/tickets/manifest-path-resolution.review.md b/tickets/manifest-path-resolution.review.md deleted file mode 100644 index 842ec8b2..00000000 --- a/tickets/manifest-path-resolution.review.md +++ /dev/null @@ -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(要判断) - -**状況**: 実装は `/.insomnia/manifest.toml` に `target = "."` と書いた場合、**`/.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 を親 `/` にする。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」ガイドは別タスク化可(本チケット範囲外扱い) diff --git a/tickets/pod-upstream-events.md b/tickets/pod-upstream-events.md index ac80b5a6..ae657ff8 100644 --- a/tickets/pod-upstream-events.md +++ b/tickets/pod-upstream-events.md @@ -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, // 又貸しされた scope + scope: Vec, // 又貸しされた 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` フィールド削除(別チケット)