mirror of
https://github.com/thegeneralist01/archivr
synced 2026-10-09 21:03:17 +02:00
fix: retain completed summary during regeneration display
This commit is contained in:
parent
13ae315d99
commit
33cdb21926
4 changed files with 110 additions and 31 deletions
|
|
@ -51,9 +51,12 @@ pub struct EntryDetail {
|
|||
pub source_metadata_json: String,
|
||||
pub display_metadata_json: Option<String>,
|
||||
pub artifacts: Vec<EntryArtifactSummary>,
|
||||
/// Most recently updated summary for this entry, if any has ever been
|
||||
/// requested. Always `None` on a fresh capture — summarization is manual.
|
||||
/// Most recent completed summary for this entry. Always `None` on a fresh
|
||||
/// capture — summarization is manual.
|
||||
pub latest_summary: Option<EntrySummaryView>,
|
||||
/// Latest non-completed generation attempt, kept separate so a replacement
|
||||
/// never displaces readable completed content.
|
||||
pub summary_attempt: Option<EntrySummaryView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
|
||||
|
|
@ -354,7 +357,8 @@ pub fn get_entry_detail(
|
|||
})?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||
|
||||
let latest_summary = database::latest_entry_summary(conn, entry_id)?;
|
||||
let latest_summary = database::latest_completed_entry_summary(conn, entry_id)?;
|
||||
let summary_attempt = database::latest_entry_summary_attempt(conn, entry_id)?;
|
||||
|
||||
Ok(Some(EntryDetail {
|
||||
summary,
|
||||
|
|
@ -363,6 +367,7 @@ pub fn get_entry_detail(
|
|||
display_metadata_json,
|
||||
artifacts,
|
||||
latest_summary,
|
||||
summary_attempt,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1644,7 +1644,8 @@ pub fn find_entry_summary(
|
|||
}
|
||||
|
||||
/// Most recently touched summary for an entry, whatever its status.
|
||||
/// Backs `EntryDetail.latest_summary` and the GET summary route.
|
||||
/// Used where a caller explicitly needs the most recent attempt regardless of
|
||||
/// whether it has completed.
|
||||
pub fn latest_entry_summary(
|
||||
conn: &Connection,
|
||||
entry_id: i64,
|
||||
|
|
@ -1679,6 +1680,24 @@ pub fn latest_completed_entry_summary(
|
|||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// The newest replacement attempt that has not completed. This includes failed
|
||||
/// rows so authenticated callers can show the failure beside a retained result.
|
||||
pub fn latest_entry_summary_attempt(
|
||||
conn: &Connection,
|
||||
entry_id: i64,
|
||||
) -> Result<Option<EntrySummaryRecord>> {
|
||||
conn.query_row(
|
||||
&format!(
|
||||
"{ENTRY_SUMMARY_COLS} WHERE s.entry_id = ?1 AND s.status != 'completed'
|
||||
ORDER BY s.updated_at DESC, s.id DESC LIMIT 1"
|
||||
),
|
||||
[entry_id],
|
||||
map_entry_summary,
|
||||
)
|
||||
.optional()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn create_archive_run(
|
||||
conn: &Connection,
|
||||
created_by_user_id: i64,
|
||||
|
|
@ -4719,6 +4738,36 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_summary_attempt_is_separate_from_the_retained_completed_summary() {
|
||||
let c = conn();
|
||||
let entry = create_entry_fixture(&c, "private", None, None);
|
||||
let completed =
|
||||
upsert_pending_entry_summary(&c, entry.id, "claude_cli", None, "v1", "old").unwrap();
|
||||
update_entry_summary_status(&c, &completed, "completed", Some("previous"), None).unwrap();
|
||||
let pending =
|
||||
upsert_pending_entry_summary(&c, entry.id, "claude_cli", None, "v1", "new").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
latest_completed_entry_summary(&c, entry.id).unwrap().unwrap().summary_uid,
|
||||
completed
|
||||
);
|
||||
assert_eq!(
|
||||
latest_entry_summary_attempt(&c, entry.id).unwrap().unwrap().summary_uid,
|
||||
pending
|
||||
);
|
||||
|
||||
update_entry_summary_status(&c, &pending, "failed", None, Some("boom")).unwrap();
|
||||
assert_eq!(
|
||||
latest_completed_entry_summary(&c, entry.id).unwrap().unwrap().summary_text.as_deref(),
|
||||
Some("previous")
|
||||
);
|
||||
assert_eq!(
|
||||
latest_entry_summary_attempt(&c, entry.id).unwrap().unwrap().status,
|
||||
"failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_stalled_entry_summaries_marks_pending_and_running_with_restart_message() {
|
||||
let c = conn();
|
||||
|
|
|
|||
|
|
@ -519,6 +519,7 @@ async fn entry_detail(
|
|||
let entry_id = database::entry_id_for_uid(&conn, &entry_uid)?
|
||||
.ok_or(ApiError::not_found("entry not found"))?;
|
||||
detail.latest_summary = database::latest_completed_entry_summary(&conn, entry_id)?;
|
||||
detail.summary_attempt = None;
|
||||
}
|
||||
Ok(Json(detail))
|
||||
}
|
||||
|
|
@ -552,14 +553,19 @@ async fn entry_summary_handler(
|
|||
}
|
||||
let entry_id = database::entry_id_for_uid(&conn, &entry_uid)?
|
||||
.ok_or(ApiError::not_found("entry not found"))?;
|
||||
let summary = if matches!(auth_user, AuthUser::Guest) {
|
||||
database::latest_completed_entry_summary(&conn, entry_id)?
|
||||
let (summary, attempt) = if matches!(auth_user, AuthUser::Guest) {
|
||||
(database::latest_completed_entry_summary(&conn, entry_id)?, None)
|
||||
} else {
|
||||
database::latest_entry_summary(&conn, entry_id)?
|
||||
(
|
||||
database::latest_completed_entry_summary(&conn, entry_id)?,
|
||||
database::latest_entry_summary_attempt(&conn, entry_id)?,
|
||||
)
|
||||
};
|
||||
Ok(Json(
|
||||
serde_json::json!({ "entry_uid": entry_uid, "summary": summary }),
|
||||
))
|
||||
let mut body = serde_json::json!({ "entry_uid": entry_uid, "summary": summary });
|
||||
if !matches!(auth_user, AuthUser::Guest) {
|
||||
body["attempt"] = serde_json::to_value(attempt)?;
|
||||
}
|
||||
Ok(Json(body))
|
||||
}
|
||||
|
||||
/// `POST /api/archives/:id/entries/:uid/summary`
|
||||
|
|
@ -3209,6 +3215,14 @@ mod tests {
|
|||
let entry = make_test_entry(&archive_path);
|
||||
let session = make_test_session(&auth_path);
|
||||
let conn = database::open_or_initialize(&archive_path).unwrap();
|
||||
let completed_uid = database::upsert_pending_entry_summary(
|
||||
&conn, entry.id, "codex_cli", None, summarizer::PROMPT_VERSION, "completed-public-test",
|
||||
)
|
||||
.unwrap();
|
||||
database::update_entry_summary_status(
|
||||
&conn, &completed_uid, "completed", Some("previous completed summary"), None,
|
||||
)
|
||||
.unwrap();
|
||||
let summary_uid = database::upsert_pending_entry_summary(
|
||||
&conn, entry.id, "codex_cli", None, summarizer::PROMPT_VERSION, "failed-public-test",
|
||||
)
|
||||
|
|
@ -3233,7 +3247,9 @@ mod tests {
|
|||
.body(Body::empty()).unwrap())
|
||||
.await.unwrap();
|
||||
assert_eq!(public_summary.status(), StatusCode::OK);
|
||||
assert!(body_json(public_summary).await["summary"].is_null());
|
||||
let public_summary = body_json(public_summary).await;
|
||||
assert_eq!(public_summary["summary"]["summary_uid"], completed_uid);
|
||||
assert!(public_summary.get("attempt").is_none());
|
||||
|
||||
let public_detail = app(registry.clone(), auth_path.clone())
|
||||
.oneshot(Request::builder()
|
||||
|
|
@ -3241,7 +3257,9 @@ mod tests {
|
|||
.body(Body::empty()).unwrap())
|
||||
.await.unwrap();
|
||||
assert_eq!(public_detail.status(), StatusCode::OK);
|
||||
assert!(body_json(public_detail).await["latest_summary"].is_null());
|
||||
let public_detail = body_json(public_detail).await;
|
||||
assert_eq!(public_detail["latest_summary"]["summary_uid"], completed_uid);
|
||||
assert!(public_detail["summary_attempt"].is_null());
|
||||
|
||||
let authenticated_summary = app(registry.clone(), auth_path.clone())
|
||||
.oneshot(Request::builder()
|
||||
|
|
@ -3250,8 +3268,10 @@ mod tests {
|
|||
.body(Body::empty()).unwrap())
|
||||
.await.unwrap();
|
||||
let authenticated_summary = body_json(authenticated_summary).await;
|
||||
assert_eq!(authenticated_summary["summary"]["status"], "failed");
|
||||
assert_eq!(authenticated_summary["summary"]["error_text"], "provider secret: raw diagnostic");
|
||||
assert_eq!(authenticated_summary["summary"]["summary_uid"], completed_uid);
|
||||
assert_eq!(authenticated_summary["summary"]["summary_text"], "previous completed summary");
|
||||
assert_eq!(authenticated_summary["attempt"]["status"], "failed");
|
||||
assert_eq!(authenticated_summary["attempt"]["error_text"], "provider secret: raw diagnostic");
|
||||
|
||||
let authenticated_detail = app(registry, auth_path)
|
||||
.oneshot(Request::builder()
|
||||
|
|
@ -3260,8 +3280,10 @@ mod tests {
|
|||
.body(Body::empty()).unwrap())
|
||||
.await.unwrap();
|
||||
let authenticated_detail = body_json(authenticated_detail).await;
|
||||
assert_eq!(authenticated_detail["latest_summary"]["status"], "failed");
|
||||
assert_eq!(authenticated_detail["latest_summary"]["error_text"], "provider secret: raw diagnostic");
|
||||
assert_eq!(authenticated_detail["latest_summary"]["summary_uid"], completed_uid);
|
||||
assert_eq!(authenticated_detail["latest_summary"]["summary_text"], "previous completed summary");
|
||||
assert_eq!(authenticated_detail["summary_attempt"]["status"], "failed");
|
||||
assert_eq!(authenticated_detail["summary_attempt"]["error_text"], "provider secret: raw diagnostic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue