mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
Clamp backward pagination end bound to the event key-order limit
Event keys zero-pad seq to six digits, so an exclusive end bound past MAX_EVENT_SEQ formatted as a seven-digit prefix that sorts before real event keys, producing an inverted scan range. This made the newest page come back empty once a run reached MAX_EVENT_SEQ, and let a client supplied before_seq beyond MAX_EVENT_SEQ garble the range. Clamp the bound and treat anything past MAX_EVENT_SEQ as unbounded; no stored sequence exceeds it, so the results are equivalent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
396f75578a
commit
b886f82622
1 changed files with 56 additions and 2 deletions
|
|
@ -389,10 +389,15 @@ impl RunDatabase {
|
|||
before_seq: Option<u32>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<EventEnvelope>> {
|
||||
// Event keys zero-pad seq to six digits (see `keys::run_event_key`),
|
||||
// so an end bound past `MAX_EVENT_SEQ` would format as a seven-digit
|
||||
// prefix that breaks lexicographic key order. No stored seq exceeds
|
||||
// `MAX_EVENT_SEQ`, so clamp and treat that bound as unbounded.
|
||||
let end_seq = match before_seq {
|
||||
Some(seq) => u64::from(seq),
|
||||
None => u64::from(self.latest_event_seq().await?) + 1,
|
||||
};
|
||||
}
|
||||
.min(u64::from(keys::MAX_EVENT_SEQ) + 1);
|
||||
if end_seq <= 1 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
|
@ -400,7 +405,9 @@ impl RunDatabase {
|
|||
let window_size = u64::try_from(limit.saturating_add(1)).unwrap_or(u64::MAX);
|
||||
let start_seq =
|
||||
u32::try_from(end_seq.saturating_sub(window_size).max(1)).unwrap_or(u32::MAX);
|
||||
let end_seq = u32::try_from(end_seq).ok();
|
||||
let end_seq = u32::try_from(end_seq)
|
||||
.ok()
|
||||
.filter(|end| *end <= keys::MAX_EVENT_SEQ);
|
||||
let mut events = list_events_in_range_with_limit(
|
||||
&self.inner.db,
|
||||
&self.inner.run_id,
|
||||
|
|
@ -1038,6 +1045,53 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_events_before_with_limit_reads_newest_page_at_max_event_seq() {
|
||||
let run = fresh_run().await;
|
||||
let run_id = run.run_id();
|
||||
run.inner
|
||||
.event_seq
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.store(keys::MAX_EVENT_SEQ - 1, Ordering::SeqCst);
|
||||
run.append_event(&stage_prompt_payload(&run_id, 1, Some("alpha")))
|
||||
.await
|
||||
.unwrap();
|
||||
run.append_event(&stage_prompt_payload(&run_id, 2, Some("beta")))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let events = run.list_events_before_with_limit(None, 2).await.unwrap();
|
||||
|
||||
let seqs: Vec<u32> = events.iter().map(|event| event.seq).collect();
|
||||
assert_eq!(seqs, vec![keys::MAX_EVENT_SEQ, keys::MAX_EVENT_SEQ - 1]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_events_before_with_limit_clamps_cursor_beyond_max_event_seq() {
|
||||
let run = fresh_run().await;
|
||||
let run_id = run.run_id();
|
||||
run.inner
|
||||
.event_seq
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.store(keys::MAX_EVENT_SEQ - 1, Ordering::SeqCst);
|
||||
run.append_event(&stage_prompt_payload(&run_id, 1, Some("alpha")))
|
||||
.await
|
||||
.unwrap();
|
||||
run.append_event(&stage_prompt_payload(&run_id, 2, Some("beta")))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let events = run
|
||||
.list_events_before_with_limit(Some(u32::MAX), 2)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let seqs: Vec<u32> = events.iter().map(|event| event.seq).collect();
|
||||
assert_eq!(seqs, vec![keys::MAX_EVENT_SEQ, keys::MAX_EVENT_SEQ - 1]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn append_event_rejects_sequences_beyond_key_order_limit() {
|
||||
let run = fresh_run().await;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue