감사 정책 head 유실을 복구하고 데몬 준비 상태로 배포를 검증한다
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { GamePrisma, type InputJsonValue } from '@sammo-ts/infra';
|
||||
import { auditPolicyHash, type PendingAuditPolicy } from './policy.js';
|
||||
import { asRecord } from '@sammo-ts/common';
|
||||
import { AUDIT_POLICY_AREAS, auditPolicyHash, type PendingAuditPolicy } from './policy.js';
|
||||
import type { InMemoryTurnWorld } from '../turn/inMemoryWorld.js';
|
||||
|
||||
export const persistAuditPolicies = async (
|
||||
tx: GamePrisma.TransactionClient,
|
||||
@@ -43,3 +45,48 @@ export const persistAuditPolicies = async (
|
||||
throw new Error('Play audit policy replay payload conflict');
|
||||
}
|
||||
};
|
||||
|
||||
/** 원장은 그대로 두고, 구버전 NPC 생성이 유실한 head projection만 복구한다. */
|
||||
export const restoreMissingAuditPolicyHeads = async (
|
||||
tx: GamePrisma.TransactionClient,
|
||||
world: InMemoryTurnWorld
|
||||
): Promise<boolean> => {
|
||||
const serverId = world.getState().meta.serverId;
|
||||
if (typeof serverId !== 'string' || !serverId.trim()) return false;
|
||||
const missing = world.listNations().flatMap((nation) => {
|
||||
const heads = asRecord(nation.meta._playAuditPolicy);
|
||||
return AUDIT_POLICY_AREAS.filter((area) => heads[area] === undefined).map(
|
||||
(area) => GamePrisma.sql`(nation_id = ${nation.id} AND area = ${area})`
|
||||
);
|
||||
});
|
||||
if (!missing.length) return false;
|
||||
// DISTINCT ON으로 각 국가/영역의 마지막 revision만 읽는다. 과거 기수는 복구하지 않는다.
|
||||
const rows = await tx.$queryRaw<
|
||||
{ id: string; nationId: number; area: string; revision: number; after: InputJsonValue }[]
|
||||
>(GamePrisma.sql`
|
||||
SELECT DISTINCT ON (nation_id, area)
|
||||
id, nation_id AS "nationId", area, revision, "after"
|
||||
FROM play_audit_policy
|
||||
WHERE server_id = ${serverId} AND (${GamePrisma.join(missing, ' OR ')})
|
||||
ORDER BY nation_id, area, revision DESC
|
||||
`);
|
||||
for (const row of rows) {
|
||||
if (row.revision < 1 || row.id !== auditPolicyHash([serverId, row.nationId, row.area, row.revision])) {
|
||||
throw new Error('Invalid persisted play audit policy head');
|
||||
}
|
||||
const nation = world.getNationById(row.nationId)!;
|
||||
const heads = nation.meta._playAuditPolicy;
|
||||
world.updateNation(nation.id, {
|
||||
meta: {
|
||||
...nation.meta,
|
||||
_playAuditPolicy: {
|
||||
...(heads && typeof heads === 'object' && !Array.isArray(heads) ? heads : {}),
|
||||
[row.area]: { id: row.id, revision: row.revision, hash: auditPolicyHash(row.after), serverId },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
// 실제 정책이 원장 after와 다르면 후속 initializeAuditPolicies가 OBSERVED_GAP을 남긴다.
|
||||
// 이 메타데이터와 새 이력은 기존 lease/fencing을 거친 startup flush에서 함께 commit된다.
|
||||
return rows.length > 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user