slice 9 cleanup: drop legacy research/loops backend + tables

Retires the legacy research/loops backend after the missions arc
(slices 1-9) fully replaced it. Frontend cutover was 4663348; this
commit finishes the job on the backend + database.

Migration:
  - 0053_drop_legacy_research_loops.sql — drops the 8 legacy tables
    (research_topics, research_topic_agents, research_outcomes,
    research_publish_approvals, loops, loop_agents, loop_orgs,
    loop_teams) and the 3 topology_runs FK columns
    (research_topic_id, loop_id, iteration). parent_run_id stays;
    recursive_exec still uses it.

Files deleted (11):
  - crates/cm-api/src/routes/{research,loops,research_setup,
    research_pipeline,wizard_repo,probe}.rs
  - crates/cm-api/src/research_container.rs
  - crates/cm-db/src/repo/{research_topics,research_outcomes,
    research_publish_approvals,loops}.rs
  - crates/cm-runtime/src/loops.rs
  - crates/cm-api/tests/research_publish_role.rs

Files edited:
  - crates/cm-api/src/lib.rs — dropped 20 legacy route registrations
    (all /api/research/* + /api/loops/* + /webhooks/loops + probe)
    and module decls
  - crates/cm-api/src/topology_worker.rs — deleted legacy dispatch
    (freeze_research_outcome, advance_loop_after_completion,
    continue_initial_burst, maybe_transition_research_topic,
    parse_reorder_rationale, per-topic/loop gateway resolver).
    reap_stuck_runs now keys on mission_id (not topic_id).
    Executor path unconditionally uses ZeroClawDriveExecutor::from_env
    — mission_orchestrator provisions each claw as an agent inside
    the shared runtime via RuntimeProvisioner, so per-team gateway
    resolution is no longer applicable.
  - crates/cm-api/src/routes/topology.rs — deleted container-log SSE
    endpoint (research/loop-specific), dropped loop_id filter and
    iteration field from ListRunsQuery/RunSummary
  - crates/cm-api/src/routes/world.rs — removed
    active_research_topics/active_loops/preseed_repo_paths;
    World SSE no longer emits repo:{topic}/loop:{id} landmark orbs
    (follow-up task #21 tracks adding mission:{id} equivalents)
  - crates/cm-api/src/runtime_provision.rs — removed now-unused
    mint_workspace_service_token
  - crates/cm-db/src/repo/topology_runs.rs — removed 9 legacy
    helpers (research_topic_id lookup, loop_id_for_run,
    iteration_for_run, active_runs_for_research_topic, etc.)
  - crates/cm-db/src/repo/teams.rs — removed 4 dead helpers
    (team_for_loop, team_for_research_topic + setters)
  - crates/cm-api/tests/topology_jobs.rs — removed loop/topic
    tests, dropped enqueue_run_with_topic helper
  - crates/bins/clawmates-server/src/main.rs — removed
    spawn_loop_scheduler call
  - crates/cm-api/src/routes/mod.rs, crates/cm-db/src/repo/mod.rs,
    crates/cm-runtime/src/lib.rs — module decls stripped

sqlx cache: regenerated against post-migration schema
  (71 files changed, ~+70 / -8896 net)

Test/build: SQLX_OFFLINE=true cargo check --workspace clean;
cargo test --workspace --no-run clean.

Follow-up (task #21): World view lost the in-flight-work landmarks
when repo:{topic} / loop:{id} orbs disappeared. Add mission:{id}
orbs as the missions-era replacement.
This commit is contained in:
Omar Sobh
2026-07-19 18:37:24 -07:00
parent 56201a6985
commit fdb8cfeecc
71 changed files with 70 additions and 8896 deletions
-55
View File
@@ -318,61 +318,6 @@ pub async fn set_team_runtime_config(
Ok(())
}
/// Resolve the team attached to a loop (via loops.team_id, added in
/// 0045). Returns `None` when the loop has no team bound — the runtime
/// then uses whatever fallback rules apply (paired research topic's
/// team, or the template default).
pub async fn team_for_loop(pool: &PgPool, loop_id: Uuid) -> Result<Option<Uuid>, DbError> {
use sqlx::Row;
let row: Option<sqlx::postgres::PgRow> = sqlx::query("SELECT team_id FROM loops WHERE id = $1")
.bind(loop_id)
.fetch_optional(pool)
.await?;
Ok(row.and_then(|r| r.try_get::<Option<Uuid>, _>("team_id").ok().flatten()))
}
/// Symmetric to `team_for_loop` but for research topics.
pub async fn team_for_research_topic(
pool: &PgPool,
topic_id: Uuid,
) -> Result<Option<Uuid>, DbError> {
use sqlx::Row;
let row: Option<sqlx::postgres::PgRow> =
sqlx::query("SELECT team_id FROM research_topics WHERE id = $1")
.bind(topic_id)
.fetch_optional(pool)
.await?;
Ok(row.and_then(|r| r.try_get::<Option<Uuid>, _>("team_id").ok().flatten()))
}
/// Bind a team to a loop (or clear the binding by passing None).
pub async fn set_team_for_loop(
pool: &PgPool,
loop_id: Uuid,
team_id: Option<Uuid>,
) -> Result<(), DbError> {
sqlx::query("UPDATE loops SET team_id = $2 WHERE id = $1")
.bind(loop_id)
.bind(team_id)
.execute(pool)
.await?;
Ok(())
}
/// Bind a team to a research topic (or clear).
pub async fn set_team_for_research_topic(
pool: &PgPool,
topic_id: Uuid,
team_id: Option<Uuid>,
) -> Result<(), DbError> {
sqlx::query("UPDATE research_topics SET team_id = $2 WHERE id = $1")
.bind(topic_id)
.bind(team_id)
.execute(pool)
.await?;
Ok(())
}
/// 0046: read the team's per-container coordinates. Both fields NULL
/// means the team has never spawned; the runtime provisions on first
/// iteration.