From 7e0d61eb084717a26d9a97f520978ef0e27180a3 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 3 May 2026 18:56:39 +0900 Subject: [PATCH] =?UTF-8?q?docs(tickets):=20resume-scope-claim=20=E5=AE=8C?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 - tickets/resume-scope-claim.md | 46 --------------------------- tickets/resume-scope-claim.review.md | 47 ---------------------------- 3 files changed, 94 deletions(-) delete mode 100644 tickets/resume-scope-claim.md delete mode 100644 tickets/resume-scope-claim.review.md diff --git a/TODO.md b/TODO.md index 4e3b07f6..d6227708 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,6 @@ - [ ] 内部 Worker / 内部 Pod の Workflow 化 → [tickets/internal-worker-workflow.md](tickets/internal-worker-workflow.md) - [ ] Agent Skills を Workflow として ingest → [tickets/agent-skills.md](tickets/agent-skills.md) - [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md) -- [ ] Resume 時の Scope claim の改善 → [tickets/resume-scope-claim.md](tickets/resume-scope-claim.md) - [ ] Pod CLI: マニフェスト関連フラグの整理 → [tickets/pod-cli-manifest-flags.md](tickets/pod-cli-manifest-flags.md) - [ ] OpenAI Responses: sampling パラメータの取り扱い → [tickets/responses-sampling-params.md](tickets/responses-sampling-params.md) - [ ] OpenAI Responses: prompt_cache_key 送出 → [tickets/responses-prompt-cache-key.md](tickets/responses-prompt-cache-key.md) diff --git a/tickets/resume-scope-claim.md b/tickets/resume-scope-claim.md deleted file mode 100644 index d140c18a..00000000 --- a/tickets/resume-scope-claim.md +++ /dev/null @@ -1,46 +0,0 @@ -# Resume 時の Scope Claim の改善 - -## 背景 - -`tickets/dynamic-scope.md` で in-process Scope の縮小(SpawnPod による委譲時の Write revoke)と pod-registry 上の delegation 記録が揃った。これにより「セッション中に scope が縮む」状態を Pod / registry の双方が一貫して表現できる。 - -一方で `tui -r` 経由の resume は、`crates/tui/src/spawn.rs` の `build_overlay_toml` を通じて fresh spawn と同じロジックで overlay を合成する。manifest cascade に scope 宣言が無い場合、cwd 直下に `write` 再帰の rule を毎回付ける挙動。 - -このため次のような衝突が起きる: - -- セッション S が稼働中に SpawnPod で子 C を作り、cwd 配下のサブパスを委譲した -- 親が exit、子 C は registry 上にエントリが残存(あるいはまだ稼働中) -- ユーザーが S を resume しようとすると、新しい Pod が cwd 全体に `write` を claim → 委譲された部分と overlap して registry が拒否 - -resume の意図は「過去のセッションの続きを取る」であって「過去の effective scope より広い範囲を新たに掴み直す」ではない。現状は後者になっており、過去に手放した scope を resume が勝手に取り戻そうとする形になっている。 - -## ゴール - -セッション resume 時に claim する scope が、当該セッションが最後に持っていた effective scope に揃う。委譲済み・他 Pod が保持中の部分は claim 対象から外れ、resume された Pod は当時と同じ範囲だけで動作する。 - -## 要件 - -- resume 時の overlay 合成は cwd 盲信ではなく、当該セッションが過去に持っていた scope を反映する。情報源は session log / registry / その他のいずれでも良いが、何らかの永続情報から復元できること -- 過去の scope 情報が取得できないセッション(旧形式 / 破損)は、明示的なエラーで止めるか、ユーザーに確認させてから fresh claim にフォールバックする(黙って広げない) -- claim 試行が registry の既存 allocation と衝突した場合、エラーメッセージで衝突相手の Pod 名 と target rule の双方が伝わる(現状は Pod 名のみ) -- 委譲済みエントリ(`delegated_from` を持つ allocation)が同じセッションの委譲チェーンに属する場合、resume はその範囲を claim せずに進行する - -## 完了条件 - -- 「親 Pod がセッション中に SpawnPod を実行 → 子に委譲 → 親 exit → 親セッションを resume」のフローが、既存子 allocation を残したまま衝突なしで成功する -- 既存の無関係な Pod と衝突するケースは、衝突 rule と相手 Pod 名を含む明確なエラーで失敗する -- 単体テスト or 統合テストで上記 2 ケースが検証される -- 既存の fresh spawn (resume なし) の挙動には変化なし - -## 範囲外 - -- 過去スコープの永続化スキーマを新規導入するかの判断は実装時に決める(session log の既存フィールドで足りるなら追加しない) -- 自動的に既存 Pod を kill / reclaim して claim を通す挙動 -- protocol 経由の外部からの GrantScope / RevokeScope(`tickets/dynamic-scope.md` の範囲外宣言を継承) -- registry 側のエラー型の全面再設計(rule 情報を含めるための最小限の拡張のみで足りる想定) - -## Review - -- 状態: Approve -- レビュー詳細: [./resume-scope-claim.review.md](./resume-scope-claim.review.md) -- 日付: 2026-05-03 diff --git a/tickets/resume-scope-claim.review.md b/tickets/resume-scope-claim.review.md deleted file mode 100644 index 0e851459..00000000 --- a/tickets/resume-scope-claim.review.md +++ /dev/null @@ -1,47 +0,0 @@ -# Review: Resume 時の Scope Claim の改善 - -## 前提・要件の確認 - -### R1: resume 時の overlay 合成は cwd 盲信ではなく、当該セッションが過去に持っていた scope を反映する。情報源は session log / registry / その他のいずれでも良いが、何らかの永続情報から復元できること -- **満たされている。** 情報源は session log の `LogEntry::Extension { domain: "pod.scope", .. }` (`crates/session-store/src/session_log.rs:200-207`)。`PodScopeSnapshot { allow, deny }` を `RestoredState.pod_scope` で最新分のみ保持 (`session_log.rs:227-229`, `308-315`)。tui resume では `load_resume_scope` が `restore` を呼んで snapshot を取り出し overlay の `[scope]` に注入する (`crates/tui/src/spawn.rs:401-417`)。pod 側は `restore_from_manifest` が同じ snapshot から `ScopeConfig` を再構築する (`crates/pod/src/pod.rs:2100-2113`)。 - -### R2: 過去の scope 情報が取得できないセッションは、明示的なエラーで止めるか、ユーザー確認後に fresh claim にフォールバック (黙って広げない) -- **満たされている。** snapshot 無しは tui 側で `SpawnError::MissingResumeScope` (`spawn.rs:55, 410-413`)、pod 側で `PodError::SessionScopeMissing` (`pod.rs:2103, 2405-2408`) として明示的にエラー化する。`restore_from_manifest_rejects_session_without_scope_snapshot` (`crates/pod/tests/restore_test.rs:84-110`) で経路をカバー。 - -### R3: claim 試行が registry の既存 allocation と衝突した場合、エラーで衝突相手の Pod 名と target rule の双方が伝わる -- **満たされている。** `ScopeLockError::WriteConflict` に `competitor_rule: ScopeRule` を追加 (`crates/pod-registry/src/error.rs:16-21`)、`find_conflict_owner(s)` を `ConflictOwner { pod_name, rule }` を返す形に再設計 (`conflict.rs:84-155`)。`Display` 文言にも competitor の rule.target が含まれる。`partial_deny_does_not_hide_parent_conflict` (`conflict.rs:259-295`) で双方の値を確認。 - -### R4: 委譲済みエントリが同じセッションの委譲チェーンに属する場合、resume はその範囲を claim せずに進行する -- **満たされている (実装方針には注記あり)。** `register_pod_with_deny` 内の `all_denied` 判定 (`mutate.rs:62-84`) で、登録者の deny セットが衝突相手のルールを完全に覆う場合は衝突を無視する。snapshot に持っていた deny がそのまま委譲済み領域なので、結果として「自分が手放した範囲」は claim せずに進める。 -- 注: 「同じセッションの委譲チェーン」かどうかを registry 側で構造的に検証してはおらず、deny マスクへの信頼で運用している。実用上は parent の release_pod が child を reparent し、registry 側の整合性が保たれているため安全。後述の Non-blocking 参照。 - -## 完了条件の確認 - -- 親 → 子委譲 → 親 exit → 親セッション resume の成功フロー: `denied_write_region_is_not_claimed_by_restored_parent` (`conflict.rs:233-256`) で確認。 -- 衝突する無関係 Pod に対する明確なエラー: `partial_deny_does_not_hide_parent_conflict` (`conflict.rs:259-295`) で確認 (rule + 名前を error から取り出してアサート)。 -- 既存 fresh spawn の挙動非変化: `from_manifest` 経路は引き続き `prepare_pod_common` (cwd default scope) を通る (`pod.rs:2439-2445`)。`overlay_uses_resume_scope_snapshot` の対と既存 `cascade_merge_detects_scope_from_any_layer` 等で fresh パスは温存。 - -## アーキテクチャ・スコープ - -- 永続化は session-store の既存の `LogEntry::Extension` ドメイン枠に乗せる選択。新スキーマ追加せず、`pod.scope` ドメインの opaque payload として運用しており、ticket の「過去スコープの永続化スキーマを新規導入するかは実装時に決める」に沿う。crate 越境なし、低レベル基盤の汚染なし。 -- `pod-registry` 側の追加は、既存 API を `*_with_deny` への薄いラッパーに置き換える形で互換性を維持。`Allocation::scope_deny` は `#[serde(default)]` で旧 `pods.json` も読める。最小限の拡張で「rule 情報を含めるための最小限の拡張のみ」という ticket 範囲外宣言と整合。 -- `Pod::scope_change_sink` + `flush_pending_scope_snapshot` で同期 tool callback と非同期 store の隙間を埋める設計は妥当。flush ポイント (初回 SessionStart 後 / SpawnPod 後 turn 完了 / compact 完了) も網羅されている。 -- snapshot 永続のタイミングは「revoke 直後ではなく次の `persist_turn`」。process crash と revoke の間に挟まれるレースは存在するが、その状態でも registry の `WriteConflict` で resume が落ちる (= 黙って広げない) ため、ticket 要件は損なわれていない。 - -## 指摘事項 - -### Blocking -- なし。 - -### Non-blocking / Follow-up -- **deny マスク経由での衝突回避は「自分の deny が覆う」全衝突に効く** (`mutate.rs:67-76`)。実装上は委譲チェーン外の偶発的な所有者にも適用される。現状の registry 整合性を前提にすれば安全だが、要件 4 の文言と実装ロジックの差は将来の混乱の種になりうる。コメント or doctest で「deny にあれば誰の所有でも譲る」という意図を明記しておくとよい (`register_pod_with_deny` の doc に一文)。 -- **payload 破損時のメッセージ**。`collect_state` の `serde_json::from_value(...).ok()` (`session_log.rs:313`) は破損 payload を `None` に丸める。結果として resume 側のエラーは「snapshot 無し」と同じ表記になる (`pod.rs:2406`)。要件 2 は満たすが、運用時の切り分けには「破損」と「未保存」の区別が欲しくなる場合がある。Follow-up でログ警告を出すか、エラー variant を分けるかは選択肢。 -- **revoke 〜 次 turn の crash race**。`SpawnPodTool` で revoke した直後に親が落ちると、最新 snapshot は revoke 前のもの。resume 時は古い (広い) allow で claim しようとし、registry が `WriteConflict` で阻止する。挙動として安全側だが、エラー文言は「resume too wide」ではなく「pod 衝突」になり、ユーザー視点で原因が分かりにくい。気になるようであれば revoke 直後に同期で snapshot を queue → 次の保存可能ポイントまで bridging する設計の見直し。優先度は低い。 - -### Nits -- `crates/pod/src/pod.rs:262-263` の空白行差分は noise。 -- `prepare_pod_common*` 系の三段呼び出し (`prepare_pod_common`, `_with_scope`, `_from_scope`) はわずかにラップが多い。`_from_scope` だけ private にして `Option` で分岐する形でも構わないが、現状でも読みやすさは確保されているので強い指摘ではない。 - -## 判断 - -**Approve (完了可)** — ticket の前提・要件・完了条件はすべて根拠付きで満たされており、`cargo check --workspace` / `cargo test --workspace` も成功する (warnings は既存の dead_code のみ)。アーキテクチャ的にも既存の Extension ドメイン枠 + registry 互換ラッパーで最小限に収まっており、コードベースを歪めていない。Non-blocking の指摘は次フェーズ以降で個別に処理すれば足りる。