From 0e2c69edf759a9b9bd45e4c68a29b5ec37b1a296 Mon Sep 17 00:00:00 2001 From: oabrivard Date: Tue, 24 Mar 2026 18:59:39 +0100 Subject: [PATCH] feat: save job_id on syntheses for provenance lookup Co-Authored-By: Claude Sonnet 4.6 --- backend/src/db/syntheses.rs | 14 ++++++++------ backend/src/models/synthesis.rs | 6 ++++++ backend/src/services/synthesis.rs | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/backend/src/db/syntheses.rs b/backend/src/db/syntheses.rs index f8bcb18..b5b4c26 100644 --- a/backend/src/db/syntheses.rs +++ b/backend/src/db/syntheses.rs @@ -20,7 +20,7 @@ pub async fn list_for_user( ) -> Result, AppError> { let rows = sqlx::query_as::<_, Synthesis>( r#" - SELECT id, user_id, week, sections, status, created_at + SELECT id, user_id, week, sections, status, created_at, job_id FROM syntheses WHERE user_id = $1 ORDER BY created_at DESC @@ -42,7 +42,7 @@ pub async fn list_for_user( pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result, AppError> { let row = sqlx::query_as::<_, Synthesis>( r#" - SELECT id, user_id, week, sections, status, created_at + SELECT id, user_id, week, sections, status, created_at, job_id FROM syntheses WHERE id = $1 "#, @@ -64,7 +64,7 @@ pub async fn get_by_id_for_user( ) -> Result, AppError> { let row = sqlx::query_as::<_, Synthesis>( r#" - SELECT id, user_id, week, sections, status, created_at + SELECT id, user_id, week, sections, status, created_at, job_id FROM syntheses WHERE id = $1 AND user_id = $2 "#, @@ -86,17 +86,19 @@ pub async fn create( user_id: Uuid, week: &str, sections_json: &serde_json::Value, + job_id: Uuid, ) -> Result { let row = sqlx::query_as::<_, Synthesis>( r#" - INSERT INTO syntheses (user_id, week, sections, status) - VALUES ($1, $2, $3, 'completed') - RETURNING id, user_id, week, sections, status, created_at + INSERT INTO syntheses (user_id, week, sections, status, job_id) + VALUES ($1, $2, $3, 'completed', $4) + RETURNING id, user_id, week, sections, status, created_at, job_id "#, ) .bind(user_id) .bind(week) .bind(sections_json) + .bind(job_id) .fetch_one(pool) .await?; diff --git a/backend/src/models/synthesis.rs b/backend/src/models/synthesis.rs index 9fd7b77..42f005a 100644 --- a/backend/src/models/synthesis.rs +++ b/backend/src/models/synthesis.rs @@ -32,6 +32,7 @@ pub struct Synthesis { pub sections: serde_json::Value, pub status: String, pub created_at: DateTime, + pub job_id: Option, } /// Response shape for `GET /api/v1/syntheses/:id`. @@ -254,6 +255,7 @@ mod tests { sections, status: "completed".into(), created_at: Utc::now(), + job_id: None, }; let list_item = SynthesisListItem::try_from(synthesis).unwrap(); @@ -270,6 +272,7 @@ mod tests { sections: serde_json::json!([]), status: "completed".into(), created_at: Utc::now(), + job_id: None, }; let list_item = SynthesisListItem::try_from(synthesis).unwrap(); @@ -295,6 +298,7 @@ mod tests { sections, status: "completed".into(), created_at: Utc::now(), + job_id: None, }; let response = SynthesisResponse::try_from(synthesis).unwrap(); @@ -312,6 +316,7 @@ mod tests { sections: serde_json::json!("not an array"), status: "completed".into(), created_at: Utc::now(), + job_id: None, }; assert!(SynthesisResponse::try_from(synthesis).is_err()); @@ -326,6 +331,7 @@ mod tests { sections: serde_json::Value::Null, status: "completed".into(), created_at: Utc::now(), + job_id: None, }; let response = SynthesisResponse::try_from(synthesis).unwrap(); diff --git a/backend/src/services/synthesis.rs b/backend/src/services/synthesis.rs index b3db2f2..bb64afa 100644 --- a/backend/src/services/synthesis.rs +++ b/backend/src/services/synthesis.rs @@ -250,7 +250,7 @@ pub async fn run_generation( /// Inner implementation of the generation pipeline, returning a Result. async fn run_generation_inner( - _job_id: Uuid, + job_id: Uuid, state: &AppState, user_id: Uuid, tx: &watch::Sender, @@ -808,7 +808,7 @@ async fn run_generation_inner( let sections_json = sanitize_json_null_bytes(sections_json); let synthesis = - db::syntheses::create(&state.pool, user_id, &week, §ions_json).await?; + db::syntheses::create(&state.pool, user_id, &week, §ions_json, job_id).await?; // Record article URLs in history for cross-synthesis dedup if settings.article_history_days > 0 {