Compare commits
2 Commits
b5069a9f82
...
61b4c0f5cd
| Author | SHA1 | Date | |
|---|---|---|---|
| 61b4c0f5cd | |||
| d076258d30 |
15
KNOWN_ISSUES.md
Normal file
15
KNOWN_ISSUES.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Known Issues
|
||||
|
||||
Ticket を切るほどではないが、次に近所を触るときに合わせて拾いたい小粒な所見の置き場。
|
||||
|
||||
## 運用
|
||||
|
||||
- 1 項目 = 出典 (file:line) + 症状 (一文) + トリガー (いつ拾うか、一文)
|
||||
- 関連 ticket があれば `→ [tickets/foo.md]` でリンク
|
||||
- 修正したら同じコミットで該当エントリを削除する (履歴は git)
|
||||
- ここに溜める基準: 「ticket は重い」「だが忘れたら次の触り手が踏む」もの。明確に作業すべきものは ticket 化する
|
||||
|
||||
## エントリ
|
||||
|
||||
- `crates/tui/src/app.rs:478-485` — bad workflow slug を含む `Method::Run` 送信時、`Event::UserMessage` の早期 broadcast で `turn_index += 1` されターンヘッダだけ残る ("ghost turn header")。次に TUI のターンヘッダ / エラー表示周りを触るときに整理。→ [tickets/pod-input-validate-internalize.md] の review 由来。
|
||||
- `crates/pod/src/controller.rs:944` — `worker_error_code` で `PodError::WorkflowResolve(_) => InvalidRequest` が post-commit な resolve エラー (`KnowledgeNotFound` 等) にも適用される。意味論的には妥当方向だが、resolve 系のエラー粒度を分けたくなったタイミングで再評価。
|
||||
1
TODO.md
1
TODO.md
|
|
@ -7,7 +7,6 @@
|
|||
- Pod: 空応答ターン (Submit 後 AI 応答ゼロで Pause/Cancel) を自動巻き戻し → [tickets/pod-empty-turn-rollback.md](tickets/pod-empty-turn-rollback.md)
|
||||
- Pod: 任意ターンからの Fork(複数ターン巻き戻しを汎用化) → [tickets/pod-session-fork.md](tickets/pod-session-fork.md)
|
||||
- Pod: Paused→Run の interrupt 前処理を Pod 内部に閉じる → [tickets/pod-interrupt-prep-internalize.md](tickets/pod-interrupt-prep-internalize.md)
|
||||
- Pod: Run 入力の事前 validate を Pod 内部に閉じる → [tickets/pod-input-validate-internalize.md](tickets/pod-input-validate-internalize.md)
|
||||
- Pod: Inbound PodEvent ハンドリングの重複を統合 → [tickets/pod-inbound-pod-event-dedup.md](tickets/pod-inbound-pod-event-dedup.md)
|
||||
- Pod: セッションログをバックエンドにした Pod 単位の永続化 → [tickets/pod-persistent-state.md](tickets/pod-persistent-state.md)
|
||||
- 永続化層のセマンティック整理 → [tickets/persistence-semantics.md](tickets/persistence-semantics.md)
|
||||
|
|
|
|||
|
|
@ -610,19 +610,15 @@ async fn controller_loop<C, St>(
|
|||
});
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = pod.validate_workflow_invocations(&input) {
|
||||
let _ = event_tx.send(Event::Error {
|
||||
code: ErrorCode::InvalidRequest,
|
||||
message: e.to_string(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// Broadcast the accepted user message so every
|
||||
// subscriber (including the submitter) can render the
|
||||
// turn header + user line from a single source of
|
||||
// truth. shared_state's `user_segments` is re-synced
|
||||
// from `pod` after the run completes, so we don't push
|
||||
// here.
|
||||
// Broadcast the user message so every subscriber
|
||||
// (including the submitter) can render the turn header
|
||||
// + user line from a single source of truth.
|
||||
// shared_state's `user_segments` is re-synced from
|
||||
// `pod` after the run completes, so we don't push
|
||||
// here. Workflow-invocation validation happens inside
|
||||
// `Pod::run` / `Pod::interrupt_and_run`; on failure the
|
||||
// turn errors out via `Event::Error { InvalidRequest }`
|
||||
// before any UserInput is committed.
|
||||
let _ = event_tx.send(Event::UserMessage {
|
||||
segments: input.clone(),
|
||||
});
|
||||
|
|
@ -945,6 +941,7 @@ fn worker_error_code(e: &PodError) -> ErrorCode {
|
|||
_ => ErrorCode::Internal,
|
||||
},
|
||||
PodError::Provider(_) => ErrorCode::ProviderError,
|
||||
PodError::WorkflowResolve(_) => ErrorCode::InvalidRequest,
|
||||
_ => ErrorCode::Internal,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,14 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
|||
&mut self,
|
||||
input: Vec<Segment>,
|
||||
) -> Result<PodRunResult, PodError> {
|
||||
// Validate before any side effects so a bad workflow slug does
|
||||
// not leave half-applied interrupt prep (orphan closure +
|
||||
// system note) in worker history. `Pod::run` validates again at
|
||||
// its own entry; the duplicate call is cheap (read-only) and
|
||||
// collapses naturally once `interrupt_and_run` folds into
|
||||
// `Pod::run` (see ticket pod-interrupt-prep-internalize).
|
||||
self.validate_workflow_invocations(&input)?;
|
||||
|
||||
let tool_result_summary = self
|
||||
.prompts()
|
||||
.interrupt_tool_result_summary()
|
||||
|
|
|
|||
|
|
@ -1136,6 +1136,12 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
|||
/// the Worker is aborted, history is compacted, and execution resumes
|
||||
/// automatically.
|
||||
pub async fn run(&mut self, input: Vec<Segment>) -> Result<PodRunResult, PodError> {
|
||||
// Validate workflow invocations up front so an invalid slug
|
||||
// never commits a UserInput entry, never triggers pre-run
|
||||
// compaction, and never half-applies interrupt prep when run
|
||||
// from `interrupt_and_run`. Read-only against `workflow_registry`.
|
||||
self.validate_workflow_invocations(&input)?;
|
||||
|
||||
self.prepare_for_run().await?;
|
||||
|
||||
// Persist the user input as typed segments before the worker
|
||||
|
|
@ -1394,9 +1400,10 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
|||
}
|
||||
|
||||
/// Validate explicit workflow invocations without reading dependency
|
||||
/// bodies. Used by the controller before broadcasting `UserMessage` so
|
||||
/// user-invocation errors are returned immediately and never reach the
|
||||
/// Worker or client history.
|
||||
/// bodies. Called from `Pod::run` / `Pod::interrupt_and_run` entry so
|
||||
/// an invalid slug aborts the turn before any session-log commit or
|
||||
/// interrupt-prep side effects; `pub` so completion / preview paths
|
||||
/// can also dry-check inputs.
|
||||
pub fn validate_workflow_invocations(
|
||||
&self,
|
||||
segments: &[Segment],
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
# Run 入力の事前 validate を Pod 内部に閉じる
|
||||
|
||||
## 背景
|
||||
|
||||
`controller_loop` は `Method::Run { input }` を受けた直後、`drive_turn` に流す前に `pod.validate_workflow_invocations(&input)` を呼んで invalid なら `ErrorCode::InvalidRequest` を返している (`crates/pod/src/controller.rs:613`):
|
||||
|
||||
```rust
|
||||
if let Err(e) = pod.validate_workflow_invocations(&input) {
|
||||
let _ = event_tx.send(Event::Error {
|
||||
code: ErrorCode::InvalidRequest,
|
||||
message: e.to_string(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
```
|
||||
|
||||
これは Pod 内部の `workflow_registry` を覗いてバリデーション判定する責務漏れになっている。`pod.validate_workflow_invocations` は read-only 関数で、`Pod::run` の冒頭でも当然呼べる。
|
||||
|
||||
なぜ controller が肩代わりしているかというと、`Pod::run` (`crates/pod/src/pod.rs:1138`) は冒頭で:
|
||||
|
||||
```rust
|
||||
self.commit_entry(LogEntry::UserInput { ts: ..., segments: input.clone() })?;
|
||||
self.user_segments.push(input.clone());
|
||||
// ...
|
||||
attachments.extend(self.resolve_workflow_invocations(&input)?);
|
||||
```
|
||||
|
||||
の順で動く。`resolve_workflow_invocations` が失敗を返すころには既に UserInput が session log に commit されてしまっており、「invalid な input が history に残る」状態になる。それを避けるために controller 側で先打ち validate している。
|
||||
|
||||
[[pod-interrupt-prep-internalize]] と同じパターン: Pod 内部の整合性を保つ前処理が Controller 層に染み出している。
|
||||
|
||||
## 要件
|
||||
|
||||
- `Pod::run` の冒頭、`commit_entry(LogEntry::UserInput { .. })` よりも前に `validate_workflow_invocations` を呼び、エラー時は session log に何も commit せず `PodError` で early return する。
|
||||
- `controller_loop` の `Method::Run` ハンドリングから事前 validate 呼び出しを削除する。
|
||||
- `Pod::run` から返る `PodError`(新規に追加する workflow validation 失敗のバリアントを含む)を `worker_error_code` 等の controller 側エラーコードマッピングで `ErrorCode::InvalidRequest` にマップする。
|
||||
- `PodError` に既存の InvalidRequest 相当バリアントがあればそれを使う。無ければ `WorkflowResolveError` をラップする最小限のバリアントを追加する。
|
||||
- `Pod::validate_workflow_invocations` メソッドの可視性は `pub` のままでよい(IPC `ListCompletions` 経路や他テストから参照される可能性があるので外向き API を狭めない)。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- `controller.rs` の `Method::Run` ブランチに `pod.validate_workflow_invocations` の呼び出しが残っていない。
|
||||
- 不正な workflow slug を含む `Method::Run` を投げると、UserInput が session log に commit されないまま `Event::Error { code: InvalidRequest, .. }` が flow する(既存の挙動と同等)。
|
||||
- 既存の workflow invocation 関連テスト(成功 / NotFound / NotUserInvocable / InvalidSlug)が通る。
|
||||
|
||||
## 範囲外
|
||||
|
||||
- `resolve_workflow_invocations` 側のロジック変更。
|
||||
- `validate_workflow_invocations` の判定基準変更(user_invocable / slug parse 等)。
|
||||
- `Method::Run` 以外の経路(Resume / RunForNotification)からの入力検証。Resume は入力を取らず、RunForNotification の入力は notify buffer drain で system message として入るため workflow invocation の経路に乗らない。
|
||||
Loading…
Reference in New Issue
Block a user