diff --git a/app/game-engine/src/turn/reservedTurnStore.ts b/app/game-engine/src/turn/reservedTurnStore.ts index c739b0d..bc73cd9 100644 --- a/app/game-engine/src/turn/reservedTurnStore.ts +++ b/app/game-engine/src/turn/reservedTurnStore.ts @@ -373,10 +373,12 @@ export class InMemoryReservedTurnStore { acquiredFreshNationLease = await this.acquireNationLease(nation.nationId, nation.officerLevel); } await Promise.all([ - // A newly acquired lease starts a fresh API/daemon ownership boundary. - // Re-read PostgreSQL even if a prior run left a stale dirty marker; - // repeated access under the same held lease keeps local mutations. - this.refreshGeneralTurns(generalId, acquiredFreshGeneralLease), + // A general queue is owned by only this execution. Once its lease + // is held, PostgreSQL is authoritative even if an interrupted + // acknowledgement left stale dirty/lease markers in memory. + // Nation queues remain conditional because multiple officers can + // consume the same shared queue in one daemon batch. + this.refreshGeneralTurns(generalId, true), nation ? this.refreshNationTurns(nation.nationId, nation.officerLevel, acquiredFreshNationLease) : Promise.resolve(), diff --git a/app/game-engine/test/reservedTurnLease.test.ts b/app/game-engine/test/reservedTurnLease.test.ts index 8dc8b44..981fa82 100644 --- a/app/game-engine/test/reservedTurnLease.test.ts +++ b/app/game-engine/test/reservedTurnLease.test.ts @@ -215,6 +215,20 @@ describe('reserved turn daemon lease', () => { expect(harness.generalFindMany).toHaveBeenCalledTimes(2); }); + it('re-reads a general queue while retaining its execution lease', async () => { + const harness = buildHarness(); + await harness.store.prepareTurnsForExecution(7); + harness.store.shiftGeneralTurns(7, -1); + + expect(harness.getRevision()).toMatchObject({ leaseOwner: 'daemon-1' }); + expect(harness.store.getGeneralTurn(7, 0).action).toBe('휴식'); + + await harness.store.prepareTurnsForExecution(7); + + expect(harness.store.getGeneralTurn(7, 0).action).toBe('che_훈련'); + expect(harness.generalFindMany).toHaveBeenCalledTimes(2); + }); + it('rejects an active foreign lease before reading the queue', async () => { const harness = buildHarness({ revision: 4,