missions: hard-require team template — block empty-team launches
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 3m6s
ci / e2e (push) Skipped
ci / publish (push) Successful in 4m4s

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:
Omar Sobh
2026-07-20 15:50:24 -07:00
parent 3ba0485e7d
commit 0ee689f590
5 changed files with 85 additions and 47 deletions
+9 -4
View File
@@ -51,10 +51,15 @@ pub async fn on_launch(
return Ok(mission.team_id);
}
let Some(template_id) = mission.team_template_id else {
// No template + no team = phase execution will auto-provision
// via the LLM path (Slice 2's fallback), or run against the
// shared runtime. Nothing to do here.
return Ok(None);
// Hard fail — a mission with no team AND no template can't run:
// there are no agents to execute phases. The wizard requires
// a template pick; this branch guards against direct API
// callers or legacy rows.
return Err(
"mission has no team_id and no team_template_id — pick a template in the wizard \
before launching, or attach an existing team via the API"
.to_string(),
);
};
let template = cm_db::repo::team_templates::get(pool, template_id)