fix: resolve 17 P2 defects and 5 P3 defects from pre-launch audit
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Batch fix covering multiple modules:
- P2-01: HandRegistry Semaphore-based max_concurrent enforcement
- P2-03: Populate toolCount/metricCount from Hand trait methods
- P2-06: heartbeat_update_config minimum interval validation
- P2-07: ReflectionResult used_fallback marker for rule-based fallback
- P2-08/09: identity_propose_change parameter naming consistency
- P2-10: ClassroomMetadata is_placeholder flag for LLM failure
- P2-11: classroomStore userDidCloseDuringGeneration intent tracking
- P2-12: workflowStore pipeline_create sends actionType
- P2-13/14: PipelineInfo step_count + PipelineStepInfo for proper step mapping
- P2-15: Pipe transform support in context.resolve (8 transforms)
- P2-16: Mustache {{...}} → \${...} auto-normalization
- P2-17: SaaSLogin password placeholder 6→8
- P2-19: serialize_skill_md + update_skill preserve tools field
- P2-22: ToolOutputGuard sensitive patterns from warn→block
- P2-23: Mutex::unwrap() → unwrap_or_else in relay/service.rs
- P3-01/03/07/08/09: Various P3 fixes
- DEFECT_LIST.md: comprehensive status sync (43/51 fixed, 8 remaining)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-06 00:49:16 +08:00
parent f9e1ce1d6e
commit 26a833d1c8
25 changed files with 408 additions and 143 deletions

View File

@@ -40,10 +40,18 @@ pub struct UpdatePipelineRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowStepInput {
/// Action type discriminator (P2-12: enables non-Hand action types)
pub action_type: Option<String>,
pub hand_name: String,
pub name: Option<String>,
pub params: Option<HashMap<String, Value>>,
pub condition: Option<String>,
/// LLM generation template (for action_type = "llm_generate")
pub template: Option<String>,
/// Parallel collection path (for action_type = "parallel")
pub each: Option<String>,
/// Condition branches (for action_type = "condition")
pub branches: Option<HashMap<String, Value>>,
}
/// Create a new pipeline as a YAML file
@@ -74,18 +82,57 @@ pub async fn pipeline_create(
return Err(format!("Pipeline file already exists: {}", file_path.display()));
}
// Build Pipeline struct
// P2-12: Build PipelineSteps with proper action type from WorkflowStepInput
let steps: Vec<PipelineStep> = request.steps.into_iter().enumerate().map(|(i, s)| {
let step_id = s.name.clone().unwrap_or_else(|| format!("step-{}", i + 1));
PipelineStep {
id: step_id,
action: Action::Hand {
let params_map: HashMap<String, String> = s.params
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k, v.to_string()))
.collect();
let action = match s.action_type.as_deref().unwrap_or("hand") {
"llm_generate" => Action::LlmGenerate {
template: s.template.unwrap_or_default(),
input: params_map,
model: None,
temperature: None,
max_tokens: None,
json_mode: false,
},
"parallel" => Action::Parallel {
each: s.each.unwrap_or_else(|| "item".to_string()),
step: Box::new(PipelineStep {
id: format!("{}-body", step_id),
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: params_map,
},
description: None,
when: None,
retry: None,
timeout_secs: None,
}),
max_workers: None,
},
"condition" => Action::Condition {
condition: s.condition.unwrap_or_default(),
branches: vec![],
default: None,
},
_ => Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: s.params.unwrap_or_default().into_iter().map(|(k, v)| (k, v.to_string())).collect(),
params: params_map,
},
};
PipelineStep {
id: step_id,
action,
description: s.name,
when: s.condition,
when: None,
retry: None,
timeout_secs: None,
}
@@ -156,18 +203,58 @@ pub async fn pipeline_update(
..existing.metadata.clone()
};
// P2-12: Build PipelineSteps with proper action type (mirrors pipeline_create logic)
let updated_steps = match request.steps {
Some(steps) => steps.into_iter().enumerate().map(|(i, s)| {
let step_id = s.name.clone().unwrap_or_else(|| format!("step-{}", i + 1));
PipelineStep {
id: step_id,
action: Action::Hand {
let params_map: HashMap<String, String> = s.params
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k, v.to_string()))
.collect();
let action = match s.action_type.as_deref().unwrap_or("hand") {
"llm_generate" => Action::LlmGenerate {
template: s.template.unwrap_or_default(),
input: params_map,
model: None,
temperature: None,
max_tokens: None,
json_mode: false,
},
"parallel" => Action::Parallel {
each: s.each.unwrap_or_else(|| "item".to_string()),
step: Box::new(PipelineStep {
id: format!("{}-body", step_id),
action: Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: params_map,
},
description: None,
when: None,
retry: None,
timeout_secs: None,
}),
max_workers: None,
},
"condition" => Action::Condition {
condition: s.condition.unwrap_or_default(),
branches: vec![],
default: None,
},
_ => Action::Hand {
hand_id: s.hand_name.clone(),
hand_action: "execute".to_string(),
params: s.params.unwrap_or_default().into_iter().map(|(k, v)| (k, v.to_string())).collect(),
params: params_map,
},
};
PipelineStep {
id: step_id,
action,
description: s.name,
when: s.condition,
when: None,
retry: None,
timeout_secs: None,
}