Introduce the unified `missions` tier that will replace the current
research_topics + loops split. This slice ships the data model +
backfill + skeleton REST surface; the old wizards keep working in
parallel until Slice 9's big-bang cutover.
Migration 0047 adds:
- missions (top-level workflow: template_kind + team +
schedule + status + config)
- mission_phases (ordered {research|coding|benchmark|
security_scan} phases per mission)
- mission_tasks (typed units of work, e.g. INT-XX cards,
UPSERT-keyed on (phase_id, external_id))
- mission_artifacts (MD/PDF/benchmark/security/diff files with
a pending queue for the PDF renderer worker)
- benchmark_snapshots (before/after pairs per iteration)
Backfill copies existing research_topics + loops rows into the new
tables as one-shot missions with the appropriate template_kind, so
Slice 2's UI can render the full history immediately.
New Rust surface:
- cm_domain: MissionId, MissionPhaseId, MissionTaskId, MissionArtifactId
- cm_db::repo::missions: Mission/MissionPhase/MissionTask/
MissionArtifact structs + insert (txn-wrapped)/get/list/set_status/
phases_for/set_phase_status/upsert_task/tasks_for/register_artifact/
artifacts_for/next_pdf_pending/set_pdf_result
- cm_api::routes::missions: skeleton list/create/get/set_status
routes registered at /api/missions/*
Follow-up slices layer richer behavior (template dispatch, phase
execution, task parsing, artifact rendering) on this foundation.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
97 lines
2.4 KiB
Rust
97 lines
2.4 KiB
Rust
use std::fmt;
|
|
use std::str::FromStr;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
/// Defines a strongly-typed UUID wrapper so ids of different entities can
|
|
/// never be swapped for one another at compile time. New ids are UUIDv7 so
|
|
/// they sort by creation time in Postgres indexes.
|
|
macro_rules! define_id {
|
|
($(#[$doc:meta])* $name:ident) => {
|
|
$(#[$doc])*
|
|
#[derive(
|
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
|
)]
|
|
#[serde(transparent)]
|
|
pub struct $name(Uuid);
|
|
|
|
impl $name {
|
|
#[allow(clippy::new_without_default)]
|
|
pub fn new() -> Self {
|
|
Self(Uuid::now_v7())
|
|
}
|
|
|
|
pub fn as_uuid(&self) -> Uuid {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl From<Uuid> for $name {
|
|
fn from(value: Uuid) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for $name {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl FromStr for $name {
|
|
type Err = uuid::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(Self(Uuid::parse_str(s)?))
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
define_id!(
|
|
/// A tenant workspace (team).
|
|
WorkspaceId
|
|
);
|
|
define_id!(
|
|
/// A human member of a workspace.
|
|
UserId
|
|
);
|
|
define_id!(
|
|
/// An AI agent ("claw").
|
|
AgentId
|
|
);
|
|
define_id!(
|
|
/// A chat session between humans and one agent.
|
|
SessionId
|
|
);
|
|
define_id!(
|
|
/// A single message within a session.
|
|
MessageId
|
|
);
|
|
define_id!(
|
|
/// A connected fleet node (a user's local-hardware host running the daemon).
|
|
NodeId
|
|
);
|
|
define_id!(
|
|
/// A user-driven workflow composed of one or more mission_phases.
|
|
/// Unifies research_topics + loops behind a single tier (Slice 1
|
|
/// of the missions unification).
|
|
MissionId
|
|
);
|
|
define_id!(
|
|
/// A single phase within a mission (research | coding | benchmark |
|
|
/// security_scan). Ordered via `order_idx` on the row.
|
|
MissionPhaseId
|
|
);
|
|
define_id!(
|
|
/// A typed unit of work within a phase — e.g. an INT-XX item, a
|
|
/// CVE finding, a research outcome iteration.
|
|
MissionTaskId
|
|
);
|
|
define_id!(
|
|
/// A file artifact produced by a mission phase — MD, PDF,
|
|
/// benchmark result, security report, code diff.
|
|
MissionArtifactId
|
|
);
|