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
+5 -1
View File
@@ -97,6 +97,7 @@ const buildContext = (options: {
metaDevelCost?: number;
auctionTargets?: string[];
clockTick?: number;
requestId?: string;
}) => {
const auth = options.auth === undefined ? buildAuth() : options.auth;
const general = options.general === undefined ? buildGeneral() : options.general;
@@ -202,6 +203,7 @@ const buildContext = (options: {
battleSim: {} as GameApiContext['battleSim'],
profile: { id: 'che', scenario: 'default', name: 'che:default' },
auth,
...(options.requestId ? { requestId: options.requestId } : {}),
uploadDir: 'uploads',
uploadPath: '/uploads',
uploadPublicUrl: null,
@@ -247,13 +249,15 @@ describe('vote router actor and permission boundaries', () => {
it('uses only the general owned by the authenticated user for voting and reward dispatch', async () => {
const owned = buildGeneral({ id: 7, userId: 'user-1', name: '유비' });
const fixture = buildContext({ general: owned, clockTick: 100 });
const fixture = buildContext({ general: owned, clockTick: 100, requestId: 'http-vote-submit' });
await expect(
appRouter.createCaller(fixture.context).vote.submitVote({ voteId: 1, selection: [0] })
).resolves.toEqual({ ok: true, wonLottery: false });
expect(fixture.requestCommand).toHaveBeenCalledWith({
type: 'voteReward',
requestId: 'http-vote-submit:vote.submitVote:engine:0:voteReward',
userId: 'user-1',
voteId: 1,
generalId: 7,
selection: [0],