missions: hard-require team template — block empty-team launches
Root-cause fix for the "mission runs with zero agents" bug. Three
enforcement layers now guarantee a launched mission has a team:
1. mission_orchestrator::on_launch — the previous
\`return Ok(None)\` when both team_id and team_template_id are
None is now \`return Err(...)\`. That branch was never a real
"auto-provision later" path; it was a silent no-op that let
the mission flip to running with nothing to run.
2. routes::missions::set_status — the draft→running transition
now (a) rejects with 400 when team_id + team_template_id are
both null, and (b) runs on_launch BEFORE flipping status +
returns 500 on failure. No more orphan "running" missions
with no materialization.
3. MissionWizard step 3 — removed the misleading "LLM
auto-provision" tile (fake code path). First real template is
pre-selected on mount; canNext requires teamTemplateId set;
empty state surfaces a red warning if no templates loaded.
4. MissionCanvas Launch button — disabled with a "No team" label
and explanatory tooltip when the mission has neither team_id
nor team_template_id (defense-in-depth for legacy rows or
direct-API missions).
Also flipped the mission_orchestrator test that expected
Ok(None) → now expects a specific error message.
Prod cleanup: reset the stuck mission
019f814c-d36f-7d60-8915-1ce100683133 (running with team_id=NULL) back
to draft so the operator can delete or attach a template.
Verified: cargo check --workspace + tsc + eslint all green;
mission_orchestrator test updated to match new contract.
This commit is contained in:
@@ -442,10 +442,14 @@ pub async fn set_status(
|
||||
.await?
|
||||
.ok_or(ApiError::NotFound)?;
|
||||
|
||||
cm_db::repo::missions::set_status(&state.pool, id, user.workspace_id.as_uuid(), &body.status)
|
||||
.await?;
|
||||
|
||||
// Draft→running requires a materializable team. Run the orchestrator
|
||||
// BEFORE flipping status so a materialization failure keeps the
|
||||
// mission in draft (no orphaned "running" mission with no agents).
|
||||
if prior.status == "draft" && body.status == "running" {
|
||||
if prior.team_id.is_none() && prior.team_template_id.is_none() {
|
||||
eprintln!("mission {id}: launch rejected — no team_id and no team_template_id");
|
||||
return Err(ApiError::BadRequest);
|
||||
}
|
||||
if let Err(e) = crate::mission_orchestrator::on_launch(
|
||||
&state.pool,
|
||||
user.workspace_id,
|
||||
@@ -456,9 +460,13 @@ pub async fn set_status(
|
||||
.await
|
||||
{
|
||||
eprintln!("mission {id}: on_launch failed: {e}");
|
||||
return Err(ApiError::Internal);
|
||||
}
|
||||
}
|
||||
|
||||
cm_db::repo::missions::set_status(&state.pool, id, user.workspace_id.as_uuid(), &body.status)
|
||||
.await?;
|
||||
|
||||
let mission = cm_db::repo::missions::get(&state.pool, id, user.workspace_id.as_uuid())
|
||||
.await?
|
||||
.ok_or(ApiError::NotFound)?;
|
||||
|
||||
Reference in New Issue
Block a user