teams: per-team runtime posture (risk_profile + mcp_bundles) + FK from loops/topics
Foundation slice for letting a coding loop bring its own team instead
of reusing the paired research topic's team. Turns out the teams
table already exists (0010_teams.sql) with full CRUD — this scales
back to the minimal missing bits:
Schema (0045_teams.sql)
- ALTER TABLE teams ADD risk_profile TEXT (NULL = template default)
- ALTER TABLE teams ADD mcp_bundles JSONB DEFAULT '[]'
- ALTER TABLE loops ADD team_id UUID REFERENCES teams ON DELETE SET NULL
- ALTER TABLE research_topics ADD team_id UUID REFERENCES teams
- Two partial indexes (team_id NOT NULL) for the future cascade queries
cm-db (dynamic sqlx::query so the existing get_team's compile-time
cache doesn't need regenerating):
- TeamRuntimeConfig struct
- get_team_runtime_config / set_team_runtime_config
- team_for_loop / team_for_research_topic (resolvers)
- set_team_for_loop / set_team_for_research_topic (binders)
cm-api
- GET /api/teams/{id} now surfaces risk_profile + mcp_bundles
- PATCH /api/teams/{id}/runtime-config sets them
Not touched (comes in follow-up slices):
- Wizard picker exposing 'reuse research team' vs 'fresh coding team'
- Runtime container spawn keyed on team_id
- Migration of existing paired coding loops onto their own team
This commit is contained in:
@@ -321,6 +321,45 @@ pub struct TeamDetail {
|
||||
pub created_at: String,
|
||||
pub graph: Value,
|
||||
pub members: Vec<TeamMemberOut>,
|
||||
/// Per-team runtime posture (0045). `None` risk_profile ⇒
|
||||
/// container inherits the template default at spawn time.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub risk_profile: Option<String>,
|
||||
#[serde(default)]
|
||||
pub mcp_bundles: Vec<String>,
|
||||
}
|
||||
|
||||
/// `PATCH /api/teams/{id}/runtime-config` — set the per-team
|
||||
/// risk_profile + mcp_bundles. Wizards call this after creating a team
|
||||
/// to bind it as either "read-heavy research" or "write-capable coding"
|
||||
/// without weakening the sibling team's posture.
|
||||
#[derive(Deserialize)]
|
||||
pub struct RuntimeConfigRequest {
|
||||
#[serde(default)]
|
||||
pub risk_profile: Option<String>,
|
||||
#[serde(default)]
|
||||
pub mcp_bundles: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn set_runtime_config(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(body): Json<RuntimeConfigRequest>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
// Workspace-scope check via the existing get_team.
|
||||
let _ = cm_db::repo::teams::get_team(&state.pool, id, user.workspace_id).await?;
|
||||
cm_db::repo::teams::set_team_runtime_config(
|
||||
&state.pool,
|
||||
id,
|
||||
user.workspace_id,
|
||||
&cm_db::repo::teams::TeamRuntimeConfig {
|
||||
risk_profile: body.risk_profile,
|
||||
mcp_bundles: body.mcp_bundles,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
/// `GET /api/teams/{id}` — a team's graph + node→claw bindings.
|
||||
@@ -331,6 +370,11 @@ pub async fn get_team(
|
||||
) -> Result<Json<TeamDetail>, ApiError> {
|
||||
let team = cm_db::repo::teams::get_team(&state.pool, id, user.workspace_id).await?;
|
||||
let members = cm_db::repo::teams::members_for_team(&state.pool, id).await?;
|
||||
let runtime = cm_db::repo::teams::get_team_runtime_config(&state.pool, id, user.workspace_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
Ok(Json(TeamDetail {
|
||||
id: team.id.to_string(),
|
||||
name: team.name,
|
||||
@@ -346,6 +390,8 @@ pub async fn get_team(
|
||||
role: m.role,
|
||||
})
|
||||
.collect(),
|
||||
risk_profile: runtime.risk_profile,
|
||||
mcp_bundles: runtime.mcp_bundles,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user