herdr phase 1a: missions runtime_kind + target_node schema
First slice of the second-runtime path. Missions now carry
runtime_kind ('zeroclaw' | 'local_herdr') + target_node_id (FK to
nodes) so the mission_orchestrator + phase executors can dispatch
differently depending on where the operator wants execution.
Migration:
- 0055_missions_runtime_kind.sql — adds runtime_kind (NOT NULL
DEFAULT 'zeroclaw' + CHECK), target_node_id (nullable FK ON
DELETE SET NULL). All existing missions backfill to 'zeroclaw'
so behavior is unchanged.
- topology_runs also grows herdr_workspace_id / herdr_tab_id /
herdr_pane_id text columns so a resumed run can reattach to the
same Herdr pane instead of spawning a duplicate.
Code:
- cm-db::repo::missions — Mission + NewMission carry the two new
fields; all SELECTs updated; INSERT COALESCE-defaults
runtime_kind to 'zeroclaw' when unspecified.
- routes::missions::create — validates runtime_kind and requires
target_node_id when kind='local_herdr' (400 otherwise).
- lib/api/missions.ts — RuntimeKind type; Mission carries both;
CreateMissionRequest optional fields.
Behavior is opt-in: no path exists yet to actually create a
local_herdr mission — that lands in Phase 1c (wizard picker). This
commit just makes the schema + validation in place so Phase 1b's
fleet_herdr dispatch module can key on it.
Tests: mission_orchestrator integration test still green.
This commit is contained in:
@@ -30,6 +30,10 @@ pub struct Mission {
|
||||
pub status: String,
|
||||
pub description: Option<String>,
|
||||
pub config: Value,
|
||||
/// 'zeroclaw' (default, headless) | 'local_herdr' (attended, fleet node)
|
||||
pub runtime_kind: String,
|
||||
/// FK → nodes(id); only relevant when runtime_kind = 'local_herdr'
|
||||
pub target_node_id: Option<Uuid>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub created_at: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
@@ -106,6 +110,9 @@ pub struct NewMission<'a> {
|
||||
pub schedule: Value,
|
||||
pub description: Option<&'a str>,
|
||||
pub config: Value,
|
||||
/// Defaults to 'zeroclaw' when None.
|
||||
pub runtime_kind: Option<&'a str>,
|
||||
pub target_node_id: Option<Uuid>,
|
||||
pub phases: Vec<NewMissionPhase>,
|
||||
}
|
||||
|
||||
@@ -127,8 +134,10 @@ pub async fn insert(pool: &PgPool, m: NewMission<'_>) -> Result<Uuid, DbError> {
|
||||
sqlx::query(
|
||||
"INSERT INTO missions
|
||||
(id, workspace_id, title, template_kind, team_id,
|
||||
team_template_id, repo_id, schedule, status, description, config)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'draft',$9,$10)",
|
||||
team_template_id, repo_id, schedule, status, description, config,
|
||||
runtime_kind, target_node_id)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'draft',$9,$10,
|
||||
COALESCE($11,'zeroclaw'),$12)",
|
||||
)
|
||||
.bind(mission_id)
|
||||
.bind(m.workspace_id)
|
||||
@@ -140,6 +149,8 @@ pub async fn insert(pool: &PgPool, m: NewMission<'_>) -> Result<Uuid, DbError> {
|
||||
.bind(&m.schedule)
|
||||
.bind(m.description)
|
||||
.bind(&m.config)
|
||||
.bind(m.runtime_kind)
|
||||
.bind(m.target_node_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -169,7 +180,8 @@ pub async fn get(pool: &PgPool, id: Uuid, workspace_id: Uuid) -> Result<Option<M
|
||||
let row = sqlx::query(
|
||||
"SELECT id, workspace_id, title, template_kind, team_id,
|
||||
team_template_id, repo_id, schedule, status,
|
||||
description, config, created_at, updated_at, completed_at
|
||||
description, config, runtime_kind, target_node_id,
|
||||
created_at, updated_at, completed_at
|
||||
FROM missions WHERE id = $1 AND workspace_id = $2",
|
||||
)
|
||||
.bind(id)
|
||||
@@ -188,6 +200,8 @@ pub async fn get(pool: &PgPool, id: Uuid, workspace_id: Uuid) -> Result<Option<M
|
||||
status: r.get("status"),
|
||||
description: r.get("description"),
|
||||
config: r.get("config"),
|
||||
runtime_kind: r.get("runtime_kind"),
|
||||
target_node_id: r.get("target_node_id"),
|
||||
created_at: r.get("created_at"),
|
||||
updated_at: r.get("updated_at"),
|
||||
completed_at: r.get("completed_at"),
|
||||
@@ -204,7 +218,8 @@ pub async fn list_by_workspace(
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, workspace_id, title, template_kind, team_id,
|
||||
team_template_id, repo_id, schedule, status,
|
||||
description, config, created_at, updated_at, completed_at
|
||||
description, config, runtime_kind, target_node_id,
|
||||
created_at, updated_at, completed_at
|
||||
FROM missions WHERE workspace_id = $1
|
||||
ORDER BY created_at DESC LIMIT $2",
|
||||
)
|
||||
@@ -226,6 +241,8 @@ pub async fn list_by_workspace(
|
||||
status: r.get("status"),
|
||||
description: r.get("description"),
|
||||
config: r.get("config"),
|
||||
runtime_kind: r.get("runtime_kind"),
|
||||
target_node_id: r.get("target_node_id"),
|
||||
created_at: r.get("created_at"),
|
||||
updated_at: r.get("updated_at"),
|
||||
completed_at: r.get("completed_at"),
|
||||
|
||||
Reference in New Issue
Block a user