refactor: remove legacy config and state aliases (PR5)
This commit is contained in:
@@ -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/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
27
src/data-state-files.ts
Normal 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();
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
30
src/db.ts
30
src/db.ts
@@ -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(',')})`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user