Closes the follow-up gap flagged when task #23 landed. security_scan
and benchmark_runner now exec against $CLAWMATES_MISSIONS_ROOT/
{mission_id}/repo — this commit is what actually puts a repo there.
- crates/cm-api/src/mission_workspace.rs — new module.
ensure_checkout(pool, workspace_id, mission_id):
* mission with no repo_id → Ok(None), no-op
* repo cloned into $ROOT/{id}/repo (--depth 1)
* dir already a git repo → fetch + reset --hard origin/{branch}
(idempotent — every launch brings the tree in sync with the
remote default_branch)
Auth uses the process's ambient git credential setup (SSH agent /
.netrc / helper). Tokens deliberately not embedded in URLs.
- crates/cm-api/src/mission_orchestrator.rs — on_launch calls
ensure_checkout after team materialization + team_id bind.
Non-fatal: clone failures log and continue so research_only
missions (no repo needed) don't get blocked.
- deploy/compose/docker-compose.yml — new named volume
missions_workspaces mounted at /var/lib/clawmates-missions on
both the server (writer) and where the clawmates-runtime
container will mount it (reader for docker exec). CLAWMATES_
MISSIONS_ROOT + CLAWMATES_RUNTIME_CONTAINER env vars set on
the server so mission_workspace + exec_target read the same
canonical values.
The scan/bench trigger buttons now actually produce findings once
you (a) run a mission whose repo_id is set, (b) have the
clawmates-runtime container bind-mounting missions_workspaces at
/var/lib/clawmates-missions.
Verified: SQLX_OFFLINE=true cargo check -p cm-api +
cargo test -p cm-api --test mission_orchestrator both green.