fix(runtime): bound PM2 process restarts and identity
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildEnvMap } from '../src/orchestrator/orchestratorFactory.js';
|
||||
|
||||
describe('buildEnvMap', () => {
|
||||
it('removes PM2 child identity before creating Git and profile process environments', () => {
|
||||
expect(
|
||||
buildEnvMap({
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
GATEWAY_ROLE: 'orchestrator',
|
||||
pm_id: '2',
|
||||
pm_exec_path: '/workspace/core2026/app/gateway-api/dist/index.js',
|
||||
name: 'sammo:gateway-orchestrator',
|
||||
axm_dynamic: '{}',
|
||||
})
|
||||
).toEqual({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
|
||||
});
|
||||
});
|
||||
@@ -174,6 +174,14 @@ describe('GatewayOrchestrator first-class operations', () => {
|
||||
'sammo:che:2:battle-sim-worker',
|
||||
'sammo:che:2:tournament-worker',
|
||||
]);
|
||||
expect(harness.deleted).toEqual([
|
||||
'sammo:che:2:game-frontend',
|
||||
'sammo:che:2:game-api',
|
||||
'sammo:che:2:turn-daemon',
|
||||
'sammo:che:2:auction-worker',
|
||||
'sammo:che:2:battle-sim-worker',
|
||||
'sammo:che:2:tournament-worker',
|
||||
]);
|
||||
expect(harness.completions).toEqual(['SUCCEEDED']);
|
||||
});
|
||||
|
||||
@@ -236,6 +244,12 @@ describe('GatewayOrchestrator first-class operations', () => {
|
||||
|
||||
expect(harness.completions).toEqual(['FAILED']);
|
||||
expect(harness.deleted).toEqual([
|
||||
'sammo:che:2:game-frontend',
|
||||
'sammo:che:2:game-api',
|
||||
'sammo:che:2:turn-daemon',
|
||||
'sammo:che:2:auction-worker',
|
||||
'sammo:che:2:battle-sim-worker',
|
||||
'sammo:che:2:tournament-worker',
|
||||
'sammo:che:2:turn-daemon',
|
||||
'sammo:che:2:game-api',
|
||||
'sammo:che:2:game-frontend',
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
buildWorkspaceCommands,
|
||||
planProfileReconcile,
|
||||
} from '../src/orchestrator/gatewayOrchestrator.js';
|
||||
import { sanitizeManagedProcessEnv } from '../src/orchestrator/processManager.js';
|
||||
import type { GatewayProfileRecord } from '../src/orchestrator/profileRepository.js';
|
||||
|
||||
const buildProfile = (buildWorkspace?: string): GatewayProfileRecord => ({
|
||||
@@ -116,6 +117,9 @@ describe('buildProcessDefinitions', () => {
|
||||
const buildWorkspace = '/srv/sammo/worktrees/0123456789abcdef';
|
||||
const definitions = buildProcessDefinitions(buildProfile(buildWorkspace), processConfig);
|
||||
|
||||
expect(Object.values(definitions)).toHaveLength(6);
|
||||
expect(new Set(Object.values(definitions).map((definition) => definition.name)).size).toBe(6);
|
||||
|
||||
expect(definitions.frontend).toMatchObject({
|
||||
cwd: path.join(buildWorkspace, 'app', 'game-frontend'),
|
||||
script: path.join(buildWorkspace, 'app', 'game-frontend', 'node_modules', 'vite', 'bin', 'vite.js'),
|
||||
@@ -167,6 +171,50 @@ describe('buildProcessDefinitions', () => {
|
||||
expect(definitions.battleSim.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
|
||||
expect(definitions.tournament.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
|
||||
});
|
||||
|
||||
it('does not forward PM2 identity or parent runtime roles to profile processes', () => {
|
||||
const definitions = buildProcessDefinitions(buildProfile(), {
|
||||
...processConfig,
|
||||
baseEnv: {
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
GATEWAY_ROLE: 'orchestrator',
|
||||
NODE_APP_INSTANCE: '2',
|
||||
name: 'sammo:gateway-orchestrator',
|
||||
pm_id: '2',
|
||||
pm_exec_path: '/srv/controller.js',
|
||||
},
|
||||
});
|
||||
|
||||
for (const definition of Object.values(definitions)) {
|
||||
expect(definition.env).toMatchObject({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
|
||||
expect(definition.env).not.toHaveProperty('pm_id');
|
||||
expect(definition.env).not.toHaveProperty('pm_exec_path');
|
||||
expect(definition.env).not.toHaveProperty('name');
|
||||
expect(definition.env).not.toHaveProperty('NODE_APP_INSTANCE');
|
||||
expect(definition.env).not.toHaveProperty('GATEWAY_ROLE');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeManagedProcessEnv', () => {
|
||||
it('keeps application configuration while removing PM2 metadata and role selectors', () => {
|
||||
expect(
|
||||
sanitizeManagedProcessEnv({
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
PATH: '/usr/local/bin:/usr/bin',
|
||||
GATEWAY_ROLE: 'orchestrator',
|
||||
GAME_API_ROLE: 'server',
|
||||
NODE_APP_INSTANCE: '2',
|
||||
name: 'sammo:gateway-orchestrator',
|
||||
pm_id: '2',
|
||||
pm_cwd: '/srv/controller',
|
||||
axm_monitor: '{}',
|
||||
})
|
||||
).toEqual({
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
PATH: '/usr/local/bin:/usr/bin',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildWorkspaceCommands', () => {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildPm2StartOptions } from '../src/orchestrator/pm2ProcessManager.js';
|
||||
|
||||
describe('buildPm2StartOptions', () => {
|
||||
it('enforces bounded restart policy and strips inherited PM2 identity at the PM2 boundary', () => {
|
||||
const options = buildPm2StartOptions({
|
||||
name: 'sammo:che:2:game-api',
|
||||
script: '/srv/sammo/app/game-api/dist/index.js',
|
||||
cwd: '/srv/sammo/app/game-api',
|
||||
env: {
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
GAME_API_ROLE: 'server',
|
||||
pm_id: '2',
|
||||
pm_exec_path: '/srv/sammo/app/gateway-api/dist/index.js',
|
||||
name: 'sammo:gateway-orchestrator',
|
||||
NODE_APP_INSTANCE: '2',
|
||||
},
|
||||
});
|
||||
|
||||
expect(options).toMatchObject({
|
||||
name: 'sammo:che:2:game-api',
|
||||
autorestart: true,
|
||||
max_restarts: 5,
|
||||
min_uptime: 10_000,
|
||||
restart_delay: 2_000,
|
||||
kill_timeout: 15_000,
|
||||
env: {
|
||||
DATABASE_URL: 'postgresql://integration.invalid/sammo',
|
||||
GAME_API_ROLE: 'server',
|
||||
},
|
||||
});
|
||||
expect(options.env).not.toHaveProperty('pm_id');
|
||||
expect(options.env).not.toHaveProperty('pm_exec_path');
|
||||
expect(options.env).not.toHaveProperty('name');
|
||||
expect(options.env).not.toHaveProperty('NODE_APP_INSTANCE');
|
||||
expect(options.env).toHaveProperty('GAME_API_ROLE', 'server');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user