feat: dashboard source coverage를 안전하게 활성화
엔진의 부대·예약턴·집계 변화와 mailbox writer를 저널에 연결하고 dashboard.global 및 인증 아이콘 source를 dependency vector에 포함한다. post-deploy coverage v1 활성화는 shared head seed와 CAS를 한 transaction으로 수행한다.
This commit is contained in:
@@ -463,6 +463,31 @@ export const createReadModelChangeJournal = (changes: RealtimeReadModelChanges):
|
||||
return journal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Conservative transitive dependency for the private dashboard projections.
|
||||
* Context reads troop/leader-turn state and command options scan the complete
|
||||
* general/city/nation sets, so entity-local heads alone are insufficient.
|
||||
*/
|
||||
export const hasDashboardSourceMutation = (
|
||||
changes: TurnWorldChanges,
|
||||
readModelChanges: RealtimeReadModelChanges
|
||||
): boolean =>
|
||||
readModelChanges.worldChanged ||
|
||||
readModelChanges.reservedGeneralIds.length > 0 ||
|
||||
changes.generals.length > 0 ||
|
||||
changes.createdGenerals.length > 0 ||
|
||||
changes.deletedGenerals.length > 0 ||
|
||||
changes.lifecycleEvents.length > 0 ||
|
||||
changes.cities.length > 0 ||
|
||||
changes.nations.length > 0 ||
|
||||
changes.createdNations.length > 0 ||
|
||||
changes.deletedNations.length > 0 ||
|
||||
changes.troops.length > 0 ||
|
||||
changes.createdTroops.length > 0 ||
|
||||
changes.deletedTroops.length > 0 ||
|
||||
changes.diplomacy.length > 0 ||
|
||||
changes.createdDiplomacy.length > 0;
|
||||
|
||||
export const excludeDeletedReservedTurnQueues = (
|
||||
changes: ReservedTurnChanges,
|
||||
deletedGeneralIds: readonly number[],
|
||||
@@ -1463,13 +1488,15 @@ export const createDatabaseTurnHooks = async (
|
||||
for (const snapshot of pendingYearbookSnapshots) {
|
||||
await persistYearbookSnapshot(prisma, snapshot);
|
||||
}
|
||||
const persistedMessageMailboxes: number[] = [];
|
||||
for (const finalization of pendingUnificationFinalizations) {
|
||||
if (options?.profileName && finalization.profileName !== options.profileName) {
|
||||
throw new Error(
|
||||
`Unification profile mismatch: pending=${finalization.profileName}, daemon=${options.profileName}.`
|
||||
);
|
||||
}
|
||||
await persistUnificationFinalization(prisma, finalization, world);
|
||||
const result = await persistUnificationFinalization(prisma, finalization, world);
|
||||
persistedMessageMailboxes.push(...result.messageMailboxes);
|
||||
}
|
||||
for (const message of messages) {
|
||||
await sendMessage(
|
||||
@@ -1506,6 +1533,7 @@ export const createDatabaseTurnHooks = async (
|
||||
if (!id) {
|
||||
throw new Error('Failed to persist turn message.');
|
||||
}
|
||||
persistedMessageMailboxes.push(draft.mailbox);
|
||||
return id;
|
||||
},
|
||||
},
|
||||
@@ -1555,6 +1583,10 @@ export const createDatabaseTurnHooks = async (
|
||||
readModelChanges.worldChanged = true;
|
||||
}
|
||||
const journal = createReadModelChangeJournal(readModelChanges);
|
||||
if (hasDashboardSourceMutation(changes, readModelChanges)) {
|
||||
journal.mark('dashboard.global');
|
||||
}
|
||||
markIds(journal, 'messages.mailbox', uniqueSortedIds(persistedMessageMailboxes));
|
||||
markIds(journal, 'access.general', accessScoreResetGeneralIds);
|
||||
if (pendingNationBettingOpens.length > 0 || pendingNationBettingFinishes.length > 0) {
|
||||
journal.mark('betting');
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface UnificationFinalizationInput {
|
||||
export interface UnificationFinalizationResult {
|
||||
status: 'APPLIED' | 'ALREADY_APPLIED';
|
||||
generationKey: string;
|
||||
messageMailboxes: number[];
|
||||
}
|
||||
|
||||
const readNumber = (value: unknown): number => (typeof value === 'number' && Number.isFinite(value) ? value : 0);
|
||||
@@ -140,7 +141,8 @@ const cancelPendingUniqueAuctions = async (
|
||||
transaction: GamePrisma.TransactionClient,
|
||||
input: UnificationFinalizationInput,
|
||||
world: InMemoryTurnWorld
|
||||
): Promise<void> => {
|
||||
): Promise<number[]> => {
|
||||
const messageMailboxes: number[] = [];
|
||||
const lockedRows = await transaction.$queryRaw<LockedUnificationAuctionRow[]>`
|
||||
SELECT
|
||||
auction.id AS "auctionId",
|
||||
@@ -245,9 +247,17 @@ const cancelPendingUniqueAuctions = async (
|
||||
time: input.completedAt,
|
||||
validUntil: new Date('9999-12-31T00:00:00.000Z'),
|
||||
};
|
||||
await sendMessage({ insertMessage: (draft) => insertMessage(transaction, draft) }, message, {
|
||||
sendDestOnly: true,
|
||||
});
|
||||
await sendMessage(
|
||||
{
|
||||
insertMessage: async (draft) => {
|
||||
const messageId = await insertMessage(transaction, draft);
|
||||
messageMailboxes.push(draft.mailbox);
|
||||
return messageId;
|
||||
},
|
||||
},
|
||||
message,
|
||||
{ sendDestOnly: true }
|
||||
);
|
||||
}
|
||||
|
||||
await transaction.auction.update({
|
||||
@@ -255,6 +265,7 @@ const cancelPendingUniqueAuctions = async (
|
||||
data: { status: 'CANCELED', finishedAt: input.completedAt },
|
||||
});
|
||||
}
|
||||
return messageMailboxes;
|
||||
};
|
||||
|
||||
export const persistUnificationFinalization = async (
|
||||
@@ -267,7 +278,7 @@ export const persistUnificationFinalization = async (
|
||||
}
|
||||
const claim = await claimGeneration(transaction, input);
|
||||
if (claim === 'ALREADY_APPLIED') {
|
||||
return { status: 'ALREADY_APPLIED', generationKey: input.generationKey };
|
||||
return { status: 'ALREADY_APPLIED', generationKey: input.generationKey, messageMailboxes: [] };
|
||||
}
|
||||
|
||||
const state = world.getState();
|
||||
@@ -298,7 +309,7 @@ export const persistUnificationFinalization = async (
|
||||
|
||||
// Ref cancels and refunds every unfinished unique auction before it merges
|
||||
// inheritance. Keep that order inside the generation transaction.
|
||||
await cancelPendingUniqueAuctions(transaction, input, world);
|
||||
const messageMailboxes = await cancelPendingUniqueAuctions(transaction, input, world);
|
||||
|
||||
const pointRows = eligibleGenerals.length
|
||||
? await transaction.inheritancePoint.findMany({
|
||||
@@ -655,5 +666,5 @@ export const persistUnificationFinalization = async (
|
||||
},
|
||||
});
|
||||
|
||||
return { status: 'APPLIED', generationKey: input.generationKey };
|
||||
return { status: 'APPLIED', generationKey: input.generationKey, messageMailboxes };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user