checkpoint

⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
Fabro 2026-05-23 06:39:55 -04:00
parent 0d60bc6e04
commit e0fdf0f04d
4 changed files with 517 additions and 111 deletions

468
run.json

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,149 @@
diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs
index 304acf8cc..72105abdd 100644
--- a/lib/crates/fabro-store/src/run_state.rs
+++ b/lib/crates/fabro-store/src/run_state.rs
@@ -949,9 +949,9 @@ mod tests {
AgentAcpCancelledProps, AgentAcpCompletedProps, AgentAcpStartedProps,
AgentAcpTimedOutProps, AgentMessageProps, AgentSessionActivatedProps,
AgentSessionEndedProps, AgentSessionStartedProps, CheckpointCompletedProps,
- InterviewCompletedProps, InterviewOption, InterviewStartedProps, RunControlEffectProps,
- StageCompletedProps, StageFailedProps, StagePromptProps, StageRetryingProps,
- StageStartedProps,
+ InterviewCompletedProps, InterviewOption, InterviewStartedProps, RunCompletedProps,
+ RunControlEffectProps, StageCompletedProps, StageFailedProps, StagePromptProps,
+ StageRetryingProps, StageStartedProps,
};
use fabro_types::{
AgentBackend, BilledModelUsage, BilledTokenCounts, BlockedReason, Checkpoint,
@@ -1127,6 +1127,81 @@ mod tests {
);
}
+ #[test]
+ fn live_run_timing_matches_conclusion_timing_at_conclusion_moment() {
+ let mut state = initialized_projection();
+ let started_at = test_dt("2026-04-07T12:00:00Z");
+ let completed_at = test_dt("2026-04-07T12:00:10Z");
+ state
+ .apply_event(&test_raw_event_at(
+ 1,
+ "2026-04-07T12:00:00Z",
+ "run.started",
+ &json!({ "name": "Test run" }),
+ None,
+ ))
+ .unwrap();
+ state
+ .apply_event(&test_raw_event_at(
+ 2,
+ "2026-04-07T12:00:00Z",
+ "run.starting",
+ &json!({}),
+ None,
+ ))
+ .unwrap();
+ state
+ .apply_event(&test_raw_event_at(
+ 3,
+ "2026-04-07T12:00:01Z",
+ "run.running",
+ &json!({}),
+ None,
+ ))
+ .unwrap();
+ state.stage_entry("plan", 1, first_event_seq(4)).timing =
+ Some(fabro_types::StageTiming::new(2_000, 700, 300));
+ state.stage_entry("code", 1, first_event_seq(5)).timing =
+ Some(fabro_types::StageTiming::new(3_000, 50, 200));
+
+ let conclusion_timing = fabro_types::RunTiming::new(
+ u64::try_from(
+ completed_at
+ .signed_duration_since(started_at)
+ .num_milliseconds(),
+ )
+ .unwrap(),
+ 750,
+ 500,
+ );
+ let mut completed = test_event(
+ 6,
+ EventBody::RunCompleted(RunCompletedProps {
+ timing: conclusion_timing,
+ artifact_count: 0,
+ status: "succeeded".to_string(),
+ reason: SuccessReason::Completed,
+ total_usd_micros: None,
+ final_git_commit_sha: None,
+ final_patch: None,
+ diff_summary: None,
+ billing: None,
+ }),
+ None,
+ );
+ completed.event.ts = completed_at;
+ state.apply_event(&completed).unwrap();
+
+ assert_eq!(
+ state
+ .conclusion
+ .as_ref()
+ .map(|conclusion| conclusion.timing),
+ Some(conclusion_timing)
+ );
+ assert_eq!(state.live_run_timing(completed_at), Some(conclusion_timing));
+ }
+
#[test]
fn last_event_at_tracks_most_recent_event_timestamp() {
let mut state = initialized_projection();
diff --git a/lib/crates/fabro-store/src/slate/projection_cache.rs b/lib/crates/fabro-store/src/slate/projection_cache.rs
index 9e4f41a34..aa4974c2f 100644
--- a/lib/crates/fabro-store/src/slate/projection_cache.rs
+++ b/lib/crates/fabro-store/src/slate/projection_cache.rs
@@ -96,9 +96,10 @@ impl RunProjectionCacheState {
/// Apply read-time overlays to a cached entry. Pure: does not touch the cache
/// state, so it can run outside the cache mutex.
fn apply_read_overlays(entry: &mut CachedRunProjection, now: DateTime<Utc>) {
- // `Conclusion::timing` is the authoritative terminal snapshot; only fill
- // in a derived live timing for runs that have not yet concluded.
- if entry.projection.conclusion.is_none() {
+ // `Conclusion::timing` is the authoritative terminal snapshot and is
+ // already present in cached terminal summaries. Only fill missing timing
+ // with the best-effort live projection.
+ if entry.summary.timing.is_none() {
entry.summary.timing = entry.projection.live_run_timing(now);
}
}
@@ -117,7 +118,7 @@ impl RunProjectionCache {
query: &ListRunsQuery,
now: DateTime<Utc>,
) -> Vec<CachedRunProjection> {
- let mut entries = {
+ let entries = {
let state = self.state.lock().await;
let raw = match query.parent_id {
Some(parent_id) => state
@@ -133,10 +134,6 @@ impl RunProjectionCache {
.map(|entry| state.with_children_count(entry))
.collect::<Vec<_>>()
};
- // Apply per-entry live overlays outside the cache mutex.
- for entry in &mut entries {
- apply_read_overlays(entry, now);
- }
let mut entries = entries
.into_iter()
.filter(|entry| {
@@ -150,6 +147,11 @@ impl RunProjectionCache {
true
})
.collect::<Vec<_>>();
+ // Apply per-entry live overlays outside the cache mutex, after any
+ // date filtering so skipped entries do not sum stage timings.
+ for entry in &mut entries {
+ apply_read_overlays(entry, now);
+ }
entries.sort_by(|left, right| {
right
.run_id

View file

@ -0,0 +1,6 @@
{
"outcome": "succeeded",
"notes": "Stage completed: simplify_gpt",
"failure_reason": null,
"timestamp": "2026-05-23T10:35:39.112641Z"
}

View file

@ -0,0 +1,5 @@
{
"script": "cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1",
"command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1",
"language": "shell"
}