refactor: remove legacy config and state aliases (PR5)

This commit is contained in:
ejclaw
2026-04-10 22:09:04 +09:00
parent 312d19c0d1
commit e5bd721ac4
18 changed files with 155 additions and 980 deletions

View File

@@ -108,7 +108,7 @@ describe('config/env loading', () => {
const { loadConfig } = await import('./config/load-config.js');
expect(() => loadConfig()).toThrow(
/Legacy env aliases are no longer supported; rename them to canonical keys \(DISCORD_BOT_TOKEN -> DISCORD_OWNER_BOT_TOKEN\)/,
/Legacy env aliases are no longer supported; remove or rename \(DISCORD_BOT_TOKEN\) to the canonical keys/,
);
});
@@ -118,7 +118,7 @@ describe('config/env loading', () => {
const { loadConfig } = await import('./config/load-config.js');
expect(() => loadConfig()).toThrow(
/Legacy env aliases are no longer supported; rename them to canonical keys \(SESSION_COMMAND_USER_IDS -> SESSION_COMMAND_ALLOWED_SENDERS\)/,
/Legacy env aliases are no longer supported; remove or rename \(SESSION_COMMAND_USER_IDS\) to the canonical keys/,
);
});
});

View File

@@ -3,19 +3,19 @@ import path from 'path';
import type { MoaConfig, MoaModelConfig } from '../moa.js';
import type { AgentType } from '../types.js';
import { getEnv } from '../env.js';
import { getEnv, listConfiguredEnvKeys } from '../env.js';
import type { AppConfig, RoleModelConfig } from './schema.js';
const LEGACY_ENV_ALIAS_MAP = {
DISCORD_BOT_TOKEN: 'DISCORD_OWNER_BOT_TOKEN',
DISCORD_CLAUDE_BOT_TOKEN: 'DISCORD_OWNER_BOT_TOKEN',
DISCORD_CODEX_BOT_TOKEN: 'DISCORD_REVIEWER_BOT_TOKEN',
DISCORD_CODEX_MAIN_BOT_TOKEN: 'DISCORD_REVIEWER_BOT_TOKEN',
DISCORD_REVIEW_BOT_TOKEN: 'DISCORD_ARBITER_BOT_TOKEN',
DISCORD_CODEX_REVIEW_BOT_TOKEN: 'DISCORD_ARBITER_BOT_TOKEN',
SESSION_COMMAND_USER_IDS: 'SESSION_COMMAND_ALLOWED_SENDERS',
} as const;
const CANONICAL_DISCORD_CHANNEL_TOKEN_KEYS = new Set([
'DISCORD_OWNER_BOT_TOKEN',
'DISCORD_REVIEWER_BOT_TOKEN',
'DISCORD_ARBITER_BOT_TOKEN',
]);
const CANONICAL_SESSION_COMMAND_KEYS = new Set([
'SESSION_COMMAND_ALLOWED_SENDERS',
]);
function readText(key: string): string | undefined {
return getEnv(key);
@@ -111,22 +111,22 @@ function normalizeServiceId(
}
function assertNoLegacyEnvAliasesConfigured(): void {
const configuredAliases = Object.entries(LEGACY_ENV_ALIAS_MAP).filter(
([legacyKey]) => {
const value = getEnv(legacyKey);
return value != null && value !== '';
},
);
const configuredAliases = listConfiguredEnvKeys().filter((key) => {
if (key.startsWith('DISCORD_') && key.endsWith('_BOT_TOKEN')) {
return !CANONICAL_DISCORD_CHANNEL_TOKEN_KEYS.has(key);
}
if (key.startsWith('SESSION_COMMAND_')) {
return !CANONICAL_SESSION_COMMAND_KEYS.has(key);
}
return false;
});
if (configuredAliases.length === 0) {
return;
}
const aliasList = configuredAliases
.map(([legacyKey, canonicalKey]) => `${legacyKey} -> ${canonicalKey}`)
.join(', ');
throw new Error(
`Legacy env aliases are no longer supported; rename them to canonical keys (${aliasList})`,
`Legacy env aliases are no longer supported; remove or rename (${configuredAliases.join(', ')}) to the canonical keys DISCORD_OWNER_BOT_TOKEN, DISCORD_REVIEWER_BOT_TOKEN, DISCORD_ARBITER_BOT_TOKEN, SESSION_COMMAND_ALLOWED_SENDERS`,
);
}

27
src/data-state-files.ts Normal file
View File

@@ -0,0 +1,27 @@
import fs from 'fs';
import path from 'path';
const ALLOWED_DATA_JSON_PATTERNS = [
/^token-rotation-state\.json$/,
/^codex-rotation-state\.json$/,
/^claude-usage-cache\.json$/,
/^restart-context\..+\.json$/,
];
function isAllowedDataStateJson(filename: string): boolean {
return ALLOWED_DATA_JSON_PATTERNS.some((pattern) => pattern.test(filename));
}
export function listUnexpectedDataStateFiles(dataDir: string): string[] {
if (!fs.existsSync(dataDir)) {
return [];
}
return fs
.readdirSync(dataDir, { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.filter((filename) => path.extname(filename) === '.json')
.filter((filename) => !isAllowedDataStateJson(filename))
.sort();
}

View File

@@ -2482,7 +2482,7 @@ describe('room assignment writes', () => {
);
});
it('fails init when legacy router_state DB keys remain without canonical keys', () => {
it('fails init when unsupported router_state DB keys remain without canonical keys', () => {
const tempDir = fs.mkdtempSync('/tmp/ejclaw-legacy-router-state-db-');
const dbPath = path.join(tempDir, 'messages.db');
const legacyDb = new Database(dbPath);
@@ -2497,7 +2497,7 @@ describe('room assignment writes', () => {
legacyDb.close();
expect(() => _initTestDatabaseFromFile(dbPath)).toThrow(
/Legacy router_state DB migration required before startup \(keys=last_agent_timestamp,last_timestamp\)/,
/Unsupported router_state DB keys remain before startup \(keys=last_agent_timestamp,last_timestamp\)/,
);
const rawDb = new Database(dbPath, { readonly: true });

View File

@@ -13,6 +13,7 @@ import {
SERVICE_ID,
SERVICE_SESSION_SCOPE,
} from './config.js';
import { listUnexpectedDataStateFiles } from './data-state-files.js';
import {
isValidGroupFolder,
resolveTaskRuntimeIpcPath as resolveTaskRuntimeIpcPathFromGroup,
@@ -155,7 +156,7 @@ import {
} from './db/service-handoffs.js';
import {
getLastRespondingAgentTypeFromDatabase,
getLegacyRouterStateKeysFromDatabase,
getUnsupportedRouterStateKeysFromDatabase,
getRouterStateForServiceFromDatabase,
getRouterStateFromDatabase,
setRouterStateForServiceInDatabase,
@@ -242,8 +243,8 @@ export function initDatabase(): void {
db = openPersistentDatabase();
initializeDatabaseSchema(db);
assertNoPendingLegacyRoomMigration();
assertNoPendingLegacyJsonStateMigration();
assertNoPendingLegacyRouterStateDbMigration();
assertNoUnexpectedDataStateFiles();
assertNoUnsupportedRouterStateDbKeys();
clearPairedTaskExecutionLeasesForServiceInDatabase(
db,
normalizeServiceId(SERVICE_ID),
@@ -267,7 +268,7 @@ export function _initTestDatabaseFromFile(dbPath: string): void {
db = openDatabaseFromFile(dbPath);
initializeDatabaseSchema(db);
assertNoPendingLegacyRoomMigration();
assertNoPendingLegacyRouterStateDbMigration();
assertNoUnsupportedRouterStateDbKeys();
clearPairedTaskExecutionLeasesForServiceInDatabase(
db,
normalizeServiceId(SERVICE_ID),
@@ -1001,39 +1002,34 @@ function getStoredRoomModeRow(chatJid: string): StoredRoomModeRow | undefined {
function assertNoPendingLegacyRoomMigration(): void {
const pendingLegacyRows = countPendingLegacyRegisteredGroupRows(db);
const hasLegacyRegisteredGroupsJson = fs.existsSync(
path.join(DATA_DIR, 'registered_groups.json'),
);
if (pendingLegacyRows === 0 && !hasLegacyRegisteredGroupsJson) {
if (pendingLegacyRows === 0) {
return;
}
throw new Error(
`Legacy room migration required before startup (pending_rows=${pendingLegacyRows}, legacy_json=${hasLegacyRegisteredGroupsJson})`,
`Legacy room migration required before startup (pending_rows=${pendingLegacyRows})`,
);
}
function assertNoPendingLegacyJsonStateMigration(): void {
const pendingFiles = ['router_state.json', 'sessions.json'].filter(
(filename) => fs.existsSync(path.join(DATA_DIR, filename)),
);
function assertNoUnexpectedDataStateFiles(): void {
const pendingFiles = listUnexpectedDataStateFiles(DATA_DIR);
if (pendingFiles.length === 0) {
return;
}
throw new Error(
`Legacy JSON state migration required before startup (files=${pendingFiles.join(',')})`,
`Unexpected data state files detected before startup (files=${pendingFiles.join(',')})`,
);
}
function assertNoPendingLegacyRouterStateDbMigration(): void {
const legacyKeys = getLegacyRouterStateKeysFromDatabase(db);
function assertNoUnsupportedRouterStateDbKeys(): void {
const legacyKeys = getUnsupportedRouterStateKeysFromDatabase(db);
if (legacyKeys.length === 0) {
return;
}
throw new Error(
`Legacy router_state DB migration required before startup (keys=${legacyKeys.join(',')})`,
`Unsupported router_state DB keys remain before startup (keys=${legacyKeys.join(',')})`,
);
}

View File

@@ -65,16 +65,19 @@ export function setRouterStateForServiceInDatabase(
.run(prefixedKey, value);
}
export function getLegacyRouterStateKeysFromDatabase(
export function getUnsupportedRouterStateKeysFromDatabase(
database: Database,
): string[] {
const rows = database
.prepare(
`SELECT key
FROM router_state
WHERE key IN ('last_timestamp', 'last_agent_timestamp')
OR key LIKE '%:last_timestamp'
OR key LIKE '%:last_agent_timestamp'
WHERE (
CASE
WHEN instr(key, ':') > 0 THEN substr(key, instr(key, ':') + 1)
ELSE key
END
) NOT IN ('last_seq', 'last_agent_seq')
ORDER BY key`,
)
.all() as Array<{ key: string }>;

View File

@@ -74,3 +74,11 @@ export function readEnvFile(keys: string[]): Record<string, string> {
}
return result;
}
export function listConfiguredEnvKeys(): string[] {
if (!_cache) loadEnvFile();
return Array.from(
new Set([...Object.keys(_cache!), ...Object.keys(process.env)]),
).sort();
}