update: Controllerで入力のValidationを行っていた部分をPod側に移す

This commit is contained in:
2026-05-15 05:33:33 +09:00
parent a761372a9e
commit 9f9e42ab59
6 changed files with 84 additions and 16 deletions
+10 -13
View File
@@ -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,
}
}
+8
View File
@@ -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()
+10 -3
View File
@@ -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],