From 77dc53b424af965c09636f045840128a9eced15a Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 24 Aug 2026 20:26:00 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B6=80=EB=B6=84=20=EC=8B=9C=EA=B3=84?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=A7=8C=EB=A3=8C=EC=9D=98=20?= =?UTF-8?q?=EC=9E=AC=EB=93=B1=EC=9E=A5=EC=9D=84=20=EB=A7=89=EB=8A=94?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-api/src/messages/store.ts | 7 ++++- ...lomacyDocumentMessages.integration.test.ts | 28 +++++++++++++++++-- app/game-api/test/messagesRouter.test.ts | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/game-api/src/messages/store.ts b/app/game-api/src/messages/store.ts index 67ff197a..308f1d80 100644 --- a/app/game-api/src/messages/store.ts +++ b/app/game-api/src/messages/store.ts @@ -206,7 +206,12 @@ export const invalidateMessages = async (db: DatabaseClient, ids: number[]): Pro where: { id: { in: uniqueIds } }, data: { validUntil: gameTime.now, - ...(gameTime.tick === null ? {} : { validUntilTick: BigInt(gameTime.tick) }), + // A partially migrated profile can still carry a legacy logical + // sentinel even while no authoritative clock exists. Replace it + // with an already-expired logical tick when expiring by wall time; + // NULL would fall back to the wall timestamp after clock recovery + // and could make the handled message visible again. + validUntilTick: gameTime.tick === null ? 0n : BigInt(gameTime.tick), }, }); }; diff --git a/app/game-api/test/diplomacyDocumentMessages.integration.test.ts b/app/game-api/test/diplomacyDocumentMessages.integration.test.ts index af301628..2cc8ae99 100644 --- a/app/game-api/test/diplomacyDocumentMessages.integration.test.ts +++ b/app/game-api/test/diplomacyDocumentMessages.integration.test.ts @@ -15,7 +15,7 @@ import { InMemoryFlushStore } from '../src/auth/flushStore.js'; import { InMemoryBattleSimTransport } from '../src/battleSim/inMemoryTransport.js'; import type { GameApiContext } from '../src/context.js'; import { InMemoryTurnDaemonTransport } from '../src/daemon/inMemoryTransport.js'; -import { fetchMessagesFromMailbox } from '../src/messages/store.js'; +import { fetchMessagesFromMailbox, invalidateMessages } from '../src/messages/store.js'; import { appRouter } from '../src/router.js'; const databaseUrl = process.env.INPUT_EVENT_DATABASE_URL; @@ -379,7 +379,7 @@ integration('diplomacy document message persistence', () => { await expectInputEvent(chainedRequestId, 'sendLetter', fixtureUserId); }); - it('keeps permanent messages readable while a profile has no logical clock', async () => { + it('keeps permanent messages readable without a clock and does not resurrect them after invalidation', async () => { const created = await appRouter .createCaller(buildContext('legacy-clock-fallback', fixtureAuth)) .diplomacy.sendLetter({ @@ -412,6 +412,20 @@ integration('diplomacy document message persistence', () => { text: expect.stringContaining(`#${created.id}`), }) ); + + await invalidateMessages(db, [receiver.id]); + await expect( + db.message.findUniqueOrThrow({ where: { id: receiver.id }, select: { validUntilTick: true } }) + ).resolves.toEqual({ validUntilTick: 0n }); + await expect( + fetchMessagesFromMailbox({ + db, + mailbox: receiverMailbox, + msgType: 'diplomacy', + limit: 15, + fromSeq: 0, + }) + ).resolves.not.toContainEqual(expect.objectContaining({ id: receiver.id })); } finally { await db.worldState.update({ where: { id: fixtureWorldStateId }, @@ -422,6 +436,16 @@ integration('diplomacy document message persistence', () => { }, }); } + + await expect( + fetchMessagesFromMailbox({ + db, + mailbox: receiverMailbox, + msgType: 'diplomacy', + limit: 15, + fromSeq: 0, + }) + ).resolves.not.toContainEqual(expect.objectContaining({ id: receiver.id })); }); it('stores diplomacy and national copies for both approval and rejection responses', async () => { diff --git a/app/game-api/test/messagesRouter.test.ts b/app/game-api/test/messagesRouter.test.ts index 5eebe448..c3a3e72f 100644 --- a/app/game-api/test/messagesRouter.test.ts +++ b/app/game-api/test/messagesRouter.test.ts @@ -987,7 +987,7 @@ describe('messages router missing-flow compatibility', () => { ); expect(setup.messageUpdateMany).toHaveBeenCalledWith({ where: { id: { in: [31] } }, - data: { validUntil: expect.any(Date) }, + data: { validUntil: expect.any(Date), validUntilTick: 0n }, }); expect(setup.queryRaw).toHaveBeenCalledTimes(9); });