fix: 커맨드 차등 생명주기와 로그 그래프를 보강

장수 55종과 수뇌 35종의 상태, 로그, 메시지, 예약 턴 비교를 닫습니다.

실제 시나리오 프로필과 대표 PostgreSQL 수명주기, 즉시 외교와 출병 회귀를 추가하고 발견된 Ref 로그 및 생성 장수 저장 차이를 교정합니다.
This commit is contained in:
2026-08-23 21:48:29 +00:00
parent 85591c68ad
commit c63a49bd07
128 changed files with 8615 additions and 848 deletions
@@ -0,0 +1,102 @@
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { createCoreTurnCommandProfile, runCoreTurnCommandTrace } from '../src/turn-differential/coreCommandTrace.js';
import { runReferenceFullLifecycleTrace } from '../src/turn-differential/fullLifecycleTrace.js';
import {
addedFullLifecycleReferenceLogs,
fullLifecycleTurnCommandRequest as request,
projectFullLifecycleSnapshotGraph,
} from '../src/turn-differential/fullLifecycleFixture.js';
import { normalizeStoredTurnLogText, orderedSemanticLogStreams } from '../src/turn-differential/logProjection.js';
import { findTurnDifferentialWorkspaceRoot } from '../src/turn-differential/referenceSnapshot.js';
const workspaceRoot = process.env.TURN_DIFFERENTIAL_WORKSPACE_ROOT ?? findTurnDifferentialWorkspaceRoot(process.cwd());
const referenceSourceRoot = workspaceRoot
? path.resolve(process.env.REF_COMPARE_SOURCE_ROOT ?? path.join(workspaceRoot, 'ref/sam'))
: null;
const hasFullLifecycleRunner =
referenceSourceRoot !== null &&
fs.existsSync(path.join(referenceSourceRoot, 'hwe/compare/turn_full_lifecycle_trace.php'));
const integration = describe.skipIf(
!workspaceRoot || !hasFullLifecycleRunner || process.env.TURN_DIFFERENTIAL_REFERENCE !== '1'
);
const asRecord = (value: unknown): Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
describe('full lifecycle fixture profile closure', () => {
it('keeps both the queued nation and general command executable', () => {
const profile = createCoreTurnCommandProfile(request);
expect(profile.general).toContain('che_훈련');
expect(profile.nation).toContain('che_국호변경');
});
});
integration('Ref/Core full reserved-turn lifecycle topology', () => {
it('runs nation then general and persists both queue shifts in the same actor turn', async () => {
const reference = runReferenceFullLifecycleTrace(workspaceRoot!, {
...request,
generalAction: request.action,
generalArgs: request.args,
nationAction: 'che_국호변경',
nationArgs: { nationName: '수명주기국' },
} as unknown as Record<string, unknown>);
const core = await runCoreTurnCommandTrace(request, reference.before);
const referencePhases = asRecord(reference.execution.outcome).phases as Array<Record<string, unknown>>;
expect(referencePhases.map((entry) => entry.phase)).toEqual([
'preprocess',
'block',
'nation_command',
'nation_command_resolved',
'general_command',
'general_command_resolved',
'queues_shifted',
'turn_state_advanced',
'persisted',
]);
expect(
referencePhases
.filter((entry) => entry.phase === 'nation_command' || entry.phase === 'general_command')
.map((entry) => [entry.phase, entry.action])
).toEqual([
['nation_command', 'che_국호변경'],
['general_command', 'che_훈련'],
]);
expect(referencePhases.find((entry) => entry.phase === 'queues_shifted')).toMatchObject({
generalAction: '휴식',
nationAction: '휴식',
});
const coreLifecycleActions = asRecord(core.execution.outcome).lifecycleActions as Array<
Record<string, unknown>
>;
expect(coreLifecycleActions.map((entry) => [entry.kind, entry.requestedAction, entry.usedFallback])).toEqual([
['nation', 'che_국호변경', false],
['general', 'che_훈련', false],
]);
expect(projectFullLifecycleSnapshotGraph(core.after)).toEqual(
projectFullLifecycleSnapshotGraph(reference.after)
);
const referenceLogs = addedFullLifecycleReferenceLogs(reference.before, reference.after);
expect(orderedSemanticLogStreams(core.after.logs)).toEqual(orderedSemanticLogStreams(referenceLogs));
const generalActionTexts = referenceLogs
.filter((entry) => String(entry.category).toLowerCase() === 'action')
.map((entry) => normalizeStoredTurnLogText(entry.text));
expect(generalActionTexts.findIndex((text) => text.includes('국호를'))).toBeLessThan(
generalActionTexts.findIndex((text) => text.includes('훈련치가'))
);
const persisted = referencePhases.find((entry) => entry.phase === 'persisted');
const referenceActor = reference.after.generals.find((entry) => entry.id === 1);
expect(persisted).toMatchObject({
killTurn: referenceActor?.killTurn,
mySet: referenceActor?.mySet,
});
}, 180_000);
});