From 31ce51d7f02b96ba473d6512ad4cc84c3b51222d Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Wed, 2 Sep 2026 12:27:27 -0700 Subject: [PATCH] fix(server): preserve latest user message identity on replay --- .../Layers/ProjectionPipeline.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 0da9783ffd4b..897202ea3dd8 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -958,9 +958,11 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti return; } - // A message cannot change any summary field except latestUserMessageAt, - // which is a monotonic maximum that folds in directly. The full refresh - // would re-read every message body in the thread per user message. + // A message cannot change any summary field except the latest user + // message identity, which is a monotonic maximum that folds in directly. + // The full refresh would re-read every message body in the thread per + // user message and is unavailable while this projector bootstraps ahead + // of the message projector. case "thread.message-sent": { const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId, @@ -968,15 +970,19 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti if (Option.isNone(existingRow)) { return; } - const previousLatest = existingRow.value.latestUserMessageAt; + const previousLatestAt = existingRow.value.latestUserMessageAt; + const previousLatestId = existingRow.value.latestUserMessageId; + const isLatestUserMessage = + event.payload.role === "user" && + (previousLatestAt === null || + event.payload.createdAt > previousLatestAt || + (event.payload.createdAt === previousLatestAt && + (previousLatestId === null || event.payload.messageId > previousLatestId))); yield* projectionThreadRepository.upsert({ ...existingRow.value, updatedAt: event.occurredAt, - latestUserMessageAt: - event.payload.role === "user" && - (previousLatest === null || event.payload.createdAt > previousLatest) - ? event.payload.createdAt - : previousLatest, + latestUserMessageId: isLatestUserMessage ? event.payload.messageId : previousLatestId, + latestUserMessageAt: isLatestUserMessage ? event.payload.createdAt : previousLatestAt, }); return; }