From 93684f12fa068d39666ef9f5a42c49b97c2f1a85 Mon Sep 17 00:00:00 2001 From: hided62 Date: Tue, 25 Aug 2026 00:59:15 +0000 Subject: [PATCH] =?UTF-8?q?fix(gateway):=20RESET=20seed=20=EC=82=B0?= =?UTF-8?q?=EC=B6=9C=EB=AC=BC=20=EC=9E=AC=EC=82=AC=EC=9A=A9=EC=9D=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB 보존 DEPLOY가 만든 부분 빌드 worktree를 RESET-ready로 오인하지 않도록 실제 game API와 profile seed entrypoint를 확인한다. seed 전환 전 built CLI도 재검증해 산출물 누락 시 기존 runtime을 먼저 내리지 않는다. --- .../src/orchestrator/gatewayOrchestrator.ts | 29 ++++++++++++++++++- app/gateway-api/test/orchestratorPlan.test.ts | 21 ++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/gateway-api/src/orchestrator/gatewayOrchestrator.ts b/app/gateway-api/src/orchestrator/gatewayOrchestrator.ts index b758423d..f3233b49 100644 --- a/app/gateway-api/src/orchestrator/gatewayOrchestrator.ts +++ b/app/gateway-api/src/orchestrator/gatewayOrchestrator.ts @@ -78,6 +78,25 @@ export interface GatewayProcessConfig { baseEnv?: Record; } +const PROFILE_BUILD_ENTRYPOINTS = [ + ['app', 'game-api', 'dist', 'index.js'], + ['app', 'gateway-api', 'dist', 'index.js'], +] as const; + +export const hasCompleteProfileBuildArtifacts = async ( + workspaceRoot: string, + access: (target: string) => Promise = fs.access +): Promise => { + for (const segments of PROFILE_BUILD_ENTRYPOINTS) { + try { + await access(path.join(workspaceRoot, ...segments)); + } catch { + return false; + } + } + return true; +}; + export interface GatewayOrchestratorOptions { repository: GatewayProfileRepository; processManager: ProcessManager; @@ -2386,7 +2405,9 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle { if (operationId) { await this.appendOperationLog(operationId, 'workspace', `worktree 준비 완료: ${workspace.root}`); } - const activeWorkspaceReusable = canReuseActiveProfileWorkspace(profile, commitSha, workspace); + const activeWorkspaceReusable = + canReuseActiveProfileWorkspace(profile, commitSha, workspace) && + (await hasCompleteProfileBuildArtifacts(workspace.root)); if (activeWorkspaceReusable) { if (operationId) { await this.appendOperationLog( @@ -2452,6 +2473,12 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle { } catch { throw new Error(`Selected commit does not provide the profile seed CLI: ${sourcePath}`); } + const artifactPath = path.join(workspaceRoot, 'app', 'gateway-api', 'dist', 'index.js'); + try { + await fs.access(artifactPath); + } catch { + throw new Error(`Selected commit did not build the profile seed CLI artifact: ${artifactPath}`); + } } private async runProfileMigration( diff --git a/app/gateway-api/test/orchestratorPlan.test.ts b/app/gateway-api/test/orchestratorPlan.test.ts index cff3997e..8e1c526b 100644 --- a/app/gateway-api/test/orchestratorPlan.test.ts +++ b/app/gateway-api/test/orchestratorPlan.test.ts @@ -9,6 +9,7 @@ import { buildProcessDefinitions, buildSharedProfileFrontendCommands, buildWorkspaceCommands, + hasCompleteProfileBuildArtifacts, planProfileReconcile, resolveProfileArchiveServerName, resolveResetLifecycleStatus, @@ -391,6 +392,26 @@ describe('sanitizeManagedProcessEnv', () => { }); describe('buildWorkspaceCommands', () => { + it('does not reuse a DB-preserving deploy workspace that lacks the profile seed artifact', async () => { + const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef'; + const gameApiPath = path.join(workspaceRoot, 'app', 'game-api', 'dist', 'index.js'); + const gatewayApiPath = path.join(workspaceRoot, 'app', 'gateway-api', 'dist', 'index.js'); + const deployOnlyArtifacts = new Set([gameApiPath]); + + await expect( + hasCompleteProfileBuildArtifacts(workspaceRoot, async (target) => { + if (!deployOnlyArtifacts.has(target)) throw new Error('missing'); + }) + ).resolves.toBe(false); + + deployOnlyArtifacts.add(gatewayApiPath); + await expect( + hasCompleteProfileBuildArtifacts(workspaceRoot, async (target) => { + if (!deployOnlyArtifacts.has(target)) throw new Error('missing'); + }) + ).resolves.toBe(true); + }); + it('installs and builds runtime dependencies before the profile processes', () => { const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef'; const commands = buildWorkspaceCommands(workspaceRoot, true, undefined, '/srv/sammo/controller');