fix: 특수 유저 커맨드의 Ref 호환 경계를 보강한다

수뇌 국가 설정과 NPC 정책을 actor-bound ENGINE mutation으로 옮기고, 추방·등용·점령·멸망·아이템 폐기의 특수 분기를 Ref와 맞춘다.

요청 ID를 사용자·프로필별로 격리하고 토너먼트 손상 projection을 fail-closed하며 실제 DB 및 Ref 차등 회귀를 보강한다.
This commit is contained in:
2026-08-24 12:10:57 +00:00
parent 5389f8ed94
commit 630bc29100
74 changed files with 3893 additions and 1141 deletions
@@ -32,10 +32,7 @@ const payloadHasGeneral = (payload: unknown, generalId: number): boolean => {
const changes = (payload as { changes?: unknown }).changes;
return (
Array.isArray(changes) &&
changes.some(
(change) =>
Array.isArray(change) && change[0] === 'front.general' && change[1] === generalId
)
changes.some((change) => Array.isArray(change) && change[0] === 'front.general' && change[1] === generalId)
);
};
@@ -134,10 +131,7 @@ integration('API input event boundary', () => {
).resolves.toEqual({ ok: true });
expect(wakeSnapshot).toBeDefined();
const [event, revision] = (await wakeSnapshot) as [
{ status: string },
{ revision: bigint },
];
const [event, revision] = (await wakeSnapshot) as [{ status: string }, { revision: bigint }];
expect(event.status).toBe('SUCCEEDED');
expect(revision.revision).toBe(1n);
expect(redisPublish).not.toHaveBeenCalled();
@@ -158,9 +152,7 @@ integration('API input event boundary', () => {
} as unknown as GameApiContext;
await expect(
journalBoundaryRouter
.createCaller(context)
.mutate({ generalId: journalGeneralIds[1], fail: true })
journalBoundaryRouter.createCaller(context).mutate({ generalId: journalGeneralIds[1], fail: true })
).rejects.toThrow('injected journal rollback');
await expect(
@@ -257,21 +249,24 @@ integration('API input event boundary', () => {
const transport = new DatabaseTurnDaemonTransport(db, 100);
const requestId = 'integration:api:engine-child';
const acceptedWindowStart = Date.now();
await transport.sendCommand({ type: 'vacation', requestId, generalId: 7 });
await transport.sendCommand({ type: 'vacation', requestId, userId: 'user-7', generalId: 7 });
const acceptedWindowEnd = Date.now();
const event = await db.inputEvent.findUniqueOrThrow({ where: { requestId } });
expect(event.actorUserId).toBe('user-7');
expect(event.createdAt.getTime()).toBeGreaterThanOrEqual(acceptedWindowStart);
expect(event.createdAt.getTime()).toBeLessThanOrEqual(acceptedWindowEnd);
await expect(transport.sendCommand({ type: 'vacation', requestId, generalId: 7 })).resolves.toBe(requestId);
await expect(transport.sendCommand({ type: 'vacation', requestId, generalId: 8 })).rejects.toBeInstanceOf(
ConflictingTurnDaemonCommandError
);
await expect(
transport.sendCommand({ type: 'vacation', requestId, userId: 'user-7', generalId: 7 })
).resolves.toBe(requestId);
await expect(
transport.sendCommand({ type: 'vacation', requestId, userId: 'user-7', generalId: 8 })
).rejects.toBeInstanceOf(ConflictingTurnDaemonCommandError);
});
it('distinguishes a stored terminal engine failure from a result timeout', async () => {
const transport = new DatabaseTurnDaemonTransport(db, 100);
const requestId = 'integration:api:engine-failed';
const command = { type: 'vacation' as const, requestId, generalId: 7 };
const command = { type: 'vacation' as const, requestId, userId: 'user-7', generalId: 7 };
await transport.sendCommand(command);
await db.inputEvent.update({
where: { requestId },