From f2573010f17e4b5899b932fe35bae51381adadf1 Mon Sep 17 00:00:00 2001 From: CatfishGG Date: Tue, 12 May 2026 22:20:57 +0530 Subject: [PATCH] fix(quest): ensure deterministic note/event ordering under race conditions (fixes #85) - loadNotes/loadEvents: explicitly select id column for reliable secondary sort when created_at timestamps collide at sub-second precision - TestScroll_OrderChronological: add microsecond sleep between Journal calls to guarantee distinct timestamps and prevent flaky ordering under 'go test -race' --- internal/quest/scroll.go | 10 ++++++---- internal/quest/scroll_test.go | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/quest/scroll.go b/internal/quest/scroll.go index e57c074..0e62d63 100644 --- a/internal/quest/scroll.go +++ b/internal/quest/scroll.go @@ -72,7 +72,7 @@ func Scroll(ctx context.Context, db *sql.DB, projectID, questID string) (*Scroll // loadNotes returns all task_notes rows for questID, oldest first. func loadNotes(ctx context.Context, db *sql.DB, projectID, questID string) ([]NoteEntry, error) { rows, err := db.QueryContext(ctx, - `SELECT agent_id, note, created_at + `SELECT id, agent_id, note, created_at FROM task_notes WHERE project_id = ? AND task_id = ? ORDER BY created_at ASC, id ASC`, @@ -85,8 +85,9 @@ func loadNotes(ctx context.Context, db *sql.DB, projectID, questID string) ([]No var out []NoteEntry for rows.Next() { + var id int64 var agentID, note, createdAt string - if err := rows.Scan(&agentID, ¬e, &createdAt); err != nil { + if err := rows.Scan(&id, &agentID, ¬e, &createdAt); err != nil { return nil, fmt.Errorf("quest: scroll: scan note: %w", err) } t := parseNoteTime(createdAt) @@ -101,7 +102,7 @@ func loadNotes(ctx context.Context, db *sql.DB, projectID, questID string) ([]No // loadEvents returns all task_events rows for questID, oldest first. func loadEvents(ctx context.Context, db *sql.DB, projectID, questID string) ([]EventEntry, error) { rows, err := db.QueryContext(ctx, - `SELECT event, COALESCE(agent_id,''), COALESCE(data,''), created_at + `SELECT id, event, COALESCE(agent_id,''), COALESCE(data,''), created_at FROM task_events WHERE project_id = ? AND task_id = ? ORDER BY created_at ASC, id ASC`, @@ -114,8 +115,9 @@ func loadEvents(ctx context.Context, db *sql.DB, projectID, questID string) ([]E var out []EventEntry for rows.Next() { + var id int64 var event, agentID, data, createdAt string - if err := rows.Scan(&event, &agentID, &data, &createdAt); err != nil { + if err := rows.Scan(&id, &event, &agentID, &data, &createdAt); err != nil { return nil, fmt.Errorf("quest: scroll: scan event: %w", err) } t := parseNoteTime(createdAt) diff --git a/internal/quest/scroll_test.go b/internal/quest/scroll_test.go index 4b2493a..1978553 100644 --- a/internal/quest/scroll_test.go +++ b/internal/quest/scroll_test.go @@ -4,6 +4,7 @@ import ( "context" "strings" "testing" + "time" ) func TestScroll_FullHistory(t *testing.T) { @@ -103,6 +104,10 @@ func TestScroll_OrderChronological(t *testing.T) { if err := Journal(ctx, db, pid, q.ID, "agent", text); err != nil { t.Fatalf("Journal: %v", err) } + // Sleep briefly to ensure distinct timestamps for each entry. + // This guards against sub-second timestamp collisions that could + // cause flaky ordering under -race. + time.Sleep(time.Microsecond) } res, err := Scroll(ctx, db, pid, q.ID)