summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-03-05 23:16:25 +0000
committersoryu <soryu@soryu.co>2026-03-07 02:27:41 +0000
commit745b6f1b794e3d18f0ed42b1d261fc2bbcddb27e (patch)
tree30f20b861e33adcab7258c8212e901c7d8accb45
parent0c844a312933e035de2dff8bda769db485d79fe5 (diff)
downloadsoryu-745b6f1b794e3d18f0ed42b1d261fc2bbcddb27e.tar.gz
soryu-745b6f1b794e3d18f0ed42b1d261fc2bbcddb27e.zip
WIP: heartbeat checkpoint
-rw-r--r--makima/migrations/20260303000000_create_directive_order_groups.sql19
-rw-r--r--makima/src/db/models.rs53
-rw-r--r--makima/src/db/repository.rs187
-rw-r--r--makima/src/server/handlers/directives.rs3
4 files changed, 259 insertions, 3 deletions
diff --git a/makima/migrations/20260303000000_create_directive_order_groups.sql b/makima/migrations/20260303000000_create_directive_order_groups.sql
new file mode 100644
index 0000000..8a382e5
--- /dev/null
+++ b/makima/migrations/20260303000000_create_directive_order_groups.sql
@@ -0,0 +1,19 @@
+-- Directive Order Groups (DOGs): Epic-like groupings of orders within a directive.
+CREATE TABLE directive_order_groups (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ owner_id UUID NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
+ name VARCHAR(500) NOT NULL,
+ description TEXT,
+ status VARCHAR(32) NOT NULL DEFAULT 'open'
+ CHECK (status IN ('open', 'in_progress', 'done', 'archived')),
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX IF NOT EXISTS idx_dog_directive_id ON directive_order_groups(directive_id);
+CREATE INDEX IF NOT EXISTS idx_dog_owner_id ON directive_order_groups(owner_id);
+
+-- Add optional dog_id to orders
+ALTER TABLE orders ADD COLUMN dog_id UUID REFERENCES directive_order_groups(id) ON DELETE SET NULL;
+CREATE INDEX IF NOT EXISTS idx_orders_dog_id ON orders(dog_id);
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 32e55f0..97657dc 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -2925,6 +2925,8 @@ pub struct Order {
pub directive_name: Option<String>,
/// Repository context
pub repository_url: Option<String>,
+ /// Optional DOG (Directive Order Group) this order belongs to
+ pub dog_id: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -2943,6 +2945,8 @@ pub struct CreateOrderRequest {
/// Directive ID is required for new orders.
pub directive_id: Uuid,
pub repository_url: Option<String>,
+ /// Optional DOG (Directive Order Group) to assign this order to.
+ pub dog_id: Option<Uuid>,
}
/// Default empty JSON array for labels.
@@ -2963,6 +2967,8 @@ pub struct UpdateOrderRequest {
pub directive_id: Option<Uuid>,
pub directive_step_id: Option<Uuid>,
pub repository_url: Option<String>,
+ /// Optional DOG (Directive Order Group) to assign/reassign this order to.
+ pub dog_id: Option<Uuid>,
}
/// Response for order list endpoint.
@@ -2986,6 +2992,8 @@ pub struct OrderListQuery {
pub priority: Option<String>,
/// Filter by linked directive ID
pub directive_id: Option<Uuid>,
+ /// Filter by DOG (Directive Order Group) ID
+ pub dog_id: Option<Uuid>,
/// Text search across title, description, and directive_name (case-insensitive)
pub search: Option<String>,
}
@@ -2997,4 +3005,49 @@ pub struct LinkDirectiveRequest {
pub directive_id: Uuid,
}
+// =============================================================================
+// Directive Order Group (DOG) Types
+// =============================================================================
+
+/// A Directive Order Group (DOG) — an epic-like grouping of orders within a directive.
+/// DOGs allow organizing related orders under a common theme or goal.
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveOrderGroup {
+ pub id: Uuid,
+ pub directive_id: Uuid,
+ pub owner_id: Uuid,
+ pub name: String,
+ pub description: Option<String>,
+ /// Status: open, in_progress, done, archived
+ pub status: String,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Request to create a new Directive Order Group (DOG).
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreateDirectiveOrderGroupRequest {
+ pub name: String,
+ pub description: Option<String>,
+}
+
+/// Request to update a Directive Order Group (DOG).
+#[derive(Debug, Default, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateDirectiveOrderGroupRequest {
+ pub name: Option<String>,
+ pub description: Option<String>,
+ pub status: Option<String>,
+}
+
+/// Response for DOG list endpoint.
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveOrderGroupListResponse {
+ pub dogs: Vec<DirectiveOrderGroup>,
+ pub total: i64,
+}
+
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index f14bc66..57e8a78 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -15,6 +15,7 @@ use super::models::{
CreateDirectiveRequest, CreateDirectiveStepRequest, DirectiveGoalHistory,
UpdateDirectiveRequest, UpdateDirectiveStepRequest,
CreateOrderRequest, Order, UpdateOrderRequest,
+ CreateDirectiveOrderGroupRequest, DirectiveOrderGroup, UpdateDirectiveOrderGroupRequest,
File, FileSummary, FileVersion, HistoryEvent, HistoryQueryFilters,
MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, PhaseConfig,
PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState,
@@ -6122,8 +6123,8 @@ pub async fn create_order(
sqlx::query_as::<_, Order>(
r#"
- INSERT INTO orders (owner_id, title, description, priority, status, order_type, labels, directive_id, repository_url)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
+ INSERT INTO orders (owner_id, title, description, priority, status, order_type, labels, directive_id, repository_url, dog_id)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *
"#,
)
@@ -6136,6 +6137,7 @@ pub async fn create_order(
.bind(&req.labels)
.bind(req.directive_id)
.bind(&req.repository_url)
+ .bind(req.dog_id)
.fetch_one(pool)
.await
}
@@ -6148,6 +6150,7 @@ pub async fn list_orders(
type_filter: Option<&str>,
priority_filter: Option<&str>,
directive_id_filter: Option<Uuid>,
+ dog_id_filter: Option<Uuid>,
search_filter: Option<&str>,
) -> Result<Vec<Order>, sqlx::Error> {
// Build dynamic query with optional filters
@@ -6170,6 +6173,10 @@ pub async fn list_orders(
query.push_str(&format!(" AND directive_id = ${}", param_idx));
param_idx += 1;
}
+ if dog_id_filter.is_some() {
+ query.push_str(&format!(" AND dog_id = ${}", param_idx));
+ param_idx += 1;
+ }
if search_filter.is_some() {
query.push_str(&format!(
" AND (title ILIKE ${p} OR description ILIKE ${p} OR directive_name ILIKE ${p})",
@@ -6193,6 +6200,9 @@ pub async fn list_orders(
if let Some(d) = directive_id_filter {
q = q.bind(d);
}
+ if let Some(d) = dog_id_filter {
+ q = q.bind(d);
+ }
if let Some(s) = search_filter {
q = q.bind(format!("%{}%", s));
}
@@ -6244,13 +6254,14 @@ pub async fn update_order(
let directive_id = req.directive_id.or(current.directive_id);
let directive_step_id = req.directive_step_id.or(current.directive_step_id);
let repository_url = req.repository_url.as_deref().or(current.repository_url.as_deref());
+ let dog_id = req.dog_id.or(current.dog_id);
sqlx::query_as::<_, Order>(
r#"
UPDATE orders
SET title = $3, description = $4, priority = $5, status = $6,
order_type = $7, labels = $8, directive_id = $9, directive_step_id = $10,
- repository_url = $11, updated_at = NOW()
+ repository_url = $11, dog_id = $12, updated_at = NOW()
WHERE id = $1 AND owner_id = $2
RETURNING *
"#,
@@ -6266,6 +6277,7 @@ pub async fn update_order(
.bind(directive_id)
.bind(directive_step_id)
.bind(repository_url)
+ .bind(dog_id)
.fetch_optional(pool)
.await
}
@@ -6517,3 +6529,172 @@ pub async fn reconcile_directive_orders(
Ok(rows.len() as i64)
}
+// =============================================================================
+// Directive Order Group (DOG) CRUD
+// =============================================================================
+
+/// Create a new Directive Order Group (DOG) for the given owner and directive.
+pub async fn create_directive_order_group(
+ pool: &PgPool,
+ directive_id: Uuid,
+ owner_id: Uuid,
+ req: CreateDirectiveOrderGroupRequest,
+) -> Result<DirectiveOrderGroup, sqlx::Error> {
+ sqlx::query_as::<_, DirectiveOrderGroup>(
+ r#"
+ INSERT INTO directive_order_groups (directive_id, owner_id, name, description)
+ VALUES ($1, $2, $3, $4)
+ RETURNING *
+ "#,
+ )
+ .bind(directive_id)
+ .bind(owner_id)
+ .bind(&req.name)
+ .bind(&req.description)
+ .fetch_one(pool)
+ .await
+}
+
+/// List all DOGs for a given directive (owner-scoped).
+pub async fn list_directive_order_groups(
+ pool: &PgPool,
+ directive_id: Uuid,
+ owner_id: Uuid,
+) -> Result<Vec<DirectiveOrderGroup>, sqlx::Error> {
+ sqlx::query_as::<_, DirectiveOrderGroup>(
+ r#"
+ SELECT * FROM directive_order_groups
+ WHERE directive_id = $1 AND owner_id = $2
+ ORDER BY created_at DESC
+ "#,
+ )
+ .bind(directive_id)
+ .bind(owner_id)
+ .fetch_all(pool)
+ .await
+}
+
+/// Get a single DOG by ID (owner-scoped).
+pub async fn get_directive_order_group(
+ pool: &PgPool,
+ id: Uuid,
+ owner_id: Uuid,
+) -> Result<Option<DirectiveOrderGroup>, sqlx::Error> {
+ sqlx::query_as::<_, DirectiveOrderGroup>(
+ r#"SELECT * FROM directive_order_groups WHERE id = $1 AND owner_id = $2"#,
+ )
+ .bind(id)
+ .bind(owner_id)
+ .fetch_optional(pool)
+ .await
+}
+
+/// Update a DOG (owner-scoped). Uses fetch-then-update pattern for partial updates.
+pub async fn update_directive_order_group(
+ pool: &PgPool,
+ id: Uuid,
+ owner_id: Uuid,
+ req: UpdateDirectiveOrderGroupRequest,
+) -> Result<Option<DirectiveOrderGroup>, sqlx::Error> {
+ let current = sqlx::query_as::<_, DirectiveOrderGroup>(
+ r#"SELECT * FROM directive_order_groups WHERE id = $1 AND owner_id = $2"#,
+ )
+ .bind(id)
+ .bind(owner_id)
+ .fetch_optional(pool)
+ .await?;
+
+ let current = match current {
+ Some(c) => c,
+ None => return Ok(None),
+ };
+
+ let name = req.name.as_deref().unwrap_or(&current.name);
+ let description = req.description.as_deref().or(current.description.as_deref());
+ let status = req.status.as_deref().unwrap_or(&current.status);
+
+ sqlx::query_as::<_, DirectiveOrderGroup>(
+ r#"
+ UPDATE directive_order_groups
+ SET name = $3, description = $4, status = $5, updated_at = NOW()
+ WHERE id = $1 AND owner_id = $2
+ RETURNING *
+ "#,
+ )
+ .bind(id)
+ .bind(owner_id)
+ .bind(name)
+ .bind(description)
+ .bind(status)
+ .fetch_optional(pool)
+ .await
+}
+
+/// Delete a DOG (owner-scoped). Returns true if a row was deleted.
+pub async fn delete_directive_order_group(
+ pool: &PgPool,
+ id: Uuid,
+ owner_id: Uuid,
+) -> Result<bool, sqlx::Error> {
+ let result = sqlx::query(
+ r#"DELETE FROM directive_order_groups WHERE id = $1 AND owner_id = $2"#,
+ )
+ .bind(id)
+ .bind(owner_id)
+ .execute(pool)
+ .await?;
+
+ Ok(result.rows_affected() > 0)
+}
+
+/// List orders belonging to a specific DOG (owner-scoped).
+pub async fn list_orders_by_dog(
+ pool: &PgPool,
+ dog_id: Uuid,
+ owner_id: Uuid,
+) -> Result<Vec<Order>, sqlx::Error> {
+ sqlx::query_as::<_, Order>(
+ r#"
+ SELECT * FROM orders
+ WHERE dog_id = $1 AND owner_id = $2
+ ORDER BY created_at DESC
+ "#,
+ )
+ .bind(dog_id)
+ .bind(owner_id)
+ .fetch_all(pool)
+ .await
+}
+
+/// Get available orders for pickup filtered to a specific DOG.
+/// Like `get_available_orders_for_pickup` but only returns orders belonging to the given DOG.
+pub async fn get_available_orders_for_dog_pickup(
+ pool: &PgPool,
+ owner_id: Uuid,
+ directive_id: Uuid,
+ dog_id: Uuid,
+) -> Result<Vec<Order>, sqlx::Error> {
+ sqlx::query_as::<_, Order>(
+ r#"
+ SELECT *
+ FROM orders
+ WHERE owner_id = $1
+ AND dog_id = $3
+ AND status IN ('open', 'in_progress')
+ AND (directive_id IS NULL OR directive_id = $2)
+ ORDER BY CASE priority
+ WHEN 'critical' THEN 0
+ WHEN 'high' THEN 1
+ WHEN 'medium' THEN 2
+ WHEN 'low' THEN 3
+ ELSE 4
+ END ASC, created_at ASC
+ "#,
+ )
+ .bind(owner_id)
+ .bind(directive_id)
+ .bind(dog_id)
+ .fetch_all(pool)
+ .await
+}
+
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
index 992affe..e6ccfa3 100644
--- a/makima/src/server/handlers/directives.rs
+++ b/makima/src/server/handlers/directives.rs
@@ -14,6 +14,9 @@ use crate::db::models::{
DirectiveStep, DirectiveWithSteps, PickUpOrdersResponse,
UpdateDirectiveRequest, UpdateDirectiveStepRequest, UpdateGoalRequest,
UpdateOrderRequest,
+ CreateDirectiveOrderGroupRequest, DirectiveOrderGroup,
+ DirectiveOrderGroupListResponse, UpdateDirectiveOrderGroupRequest,
+ OrderListResponse,
};
use crate::db::repository;
use crate::orchestration::directive::{build_cleanup_prompt, build_order_pickup_prompt};