Remove legacy Discord token aliases

This commit is contained in:
Eyejoker
2026-04-05 01:24:07 +09:00
parent f5e9400b9b
commit 10e7f2c563
7 changed files with 64 additions and 115 deletions

View File

@@ -5,7 +5,7 @@
Single unified service (`ejclaw`) manages all three Discord bots in one process: Single unified service (`ejclaw`) manages all three Discord bots in one process:
- `ejclaw.service` — Single unified process - `ejclaw.service` — Single unified process
- Discord bots: `DISCORD_BOT_TOKEN` (Claude), `DISCORD_CODEX_BOT_TOKEN` (Codex-main), `DISCORD_REVIEW_BOT_TOKEN` (Codex-review) - Discord bots: `DISCORD_OWNER_BOT_TOKEN` (owner), `DISCORD_REVIEWER_BOT_TOKEN` (reviewer), `DISCORD_ARBITER_BOT_TOKEN` (arbiter)
- Paired review: owner ↔ reviewer (agent types configurable per role) - Paired review: owner ↔ reviewer (agent types configurable per role)
- Reviewer fallback: Claude exhaustion → codex-review auto-handoff - Reviewer fallback: Claude exhaustion → codex-review auto-handoff
- Shared dirs: `store/`, `groups/`, `data/` - Shared dirs: `store/`, `groups/`, `data/`

View File

@@ -5,9 +5,9 @@ All configuration in a single `.env` file.
## Discord Bots ## Discord Bots
```bash ```bash
DISCORD_BOT_TOKEN= # Claude bot DISCORD_OWNER_BOT_TOKEN= # Owner bot
DISCORD_CODEX_BOT_TOKEN= # Codex-main bot (owner) DISCORD_REVIEWER_BOT_TOKEN= # Reviewer bot
DISCORD_REVIEW_BOT_TOKEN= # Codex-review bot (arbiter) DISCORD_ARBITER_BOT_TOKEN= # Arbiter bot
``` ```
## Agent Types & Models ## Agent Types & Models

View File

@@ -11,7 +11,6 @@ import {
buildVerifySummary, buildVerifySummary,
detectChannelAuth, detectChannelAuth,
detectCredentials, detectCredentials,
detectLegacyDiscordTokenKeys,
loadRegisteredGroupsSummary, loadRegisteredGroupsSummary,
loadRoleRoutingRequirementsSummary, loadRoleRoutingRequirementsSummary,
} from './verify-state.js'; } from './verify-state.js';
@@ -53,37 +52,17 @@ describe('verify state helpers', () => {
}); });
}); });
it('does not treat legacy service-based channel auth names as configured channels', () => { it('does not treat unknown discord token names as configured channels', () => {
expect( expect(
detectChannelAuth( detectChannelAuth(
{}, {},
{ {
DISCORD_CLAUDE_BOT_TOKEN: 'legacy-owner-token', DISCORD_UNUSED_BOT_TOKEN: 'unknown-token',
DISCORD_CODEX_MAIN_BOT_TOKEN: 'legacy-reviewer-token',
DISCORD_CODEX_REVIEW_BOT_TOKEN: 'legacy-arbiter-token',
}, },
), ),
).toEqual({}); ).toEqual({});
}); });
it('detects legacy service-based discord token names from env file and process env', () => {
expect(
detectLegacyDiscordTokenKeys(
{
DISCORD_BOT_TOKEN: 'legacy-owner-token',
},
{
DISCORD_CODEX_MAIN_BOT_TOKEN: 'legacy-reviewer-token',
DISCORD_CODEX_REVIEW_BOT_TOKEN: 'legacy-arbiter-token',
},
),
).toEqual([
'DISCORD_BOT_TOKEN',
'DISCORD_CODEX_MAIN_BOT_TOKEN',
'DISCORD_CODEX_REVIEW_BOT_TOKEN',
]);
});
it('loads paired-room routing requirements from the sqlite store', () => { it('loads paired-room routing requirements from the sqlite store', () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-'));
tempRoots.push(tempRoot); tempRoots.push(tempRoot);
@@ -270,26 +249,4 @@ describe('verify state helpers', () => {
}); });
}); });
it('fails verification when legacy discord token names are still configured', () => {
const services: ServiceCheck[] = [{ name: 'ejclaw', status: 'running' }];
expect(
buildVerifySummary(
services,
[],
'configured',
{
discord: 'configured',
'discord-review': 'configured',
'discord-arbiter': 'configured',
},
1,
{},
{ legacyDiscordTokenKeys: ['DISCORD_BOT_TOKEN'] },
),
).toMatchObject({
status: 'failed',
legacyDiscordTokenKeys: ['DISCORD_BOT_TOKEN'],
});
});
}); });

View File

@@ -11,15 +11,6 @@ import type { ServiceCheck } from './verify-services.js';
export type CredentialsStatus = 'configured' | 'missing'; export type CredentialsStatus = 'configured' | 'missing';
export type VerifyStatus = 'success' | 'failed'; export type VerifyStatus = 'success' | 'failed';
const LEGACY_DISCORD_TOKEN_KEYS = [
'DISCORD_BOT_TOKEN',
'DISCORD_CODEX_BOT_TOKEN',
'DISCORD_REVIEW_BOT_TOKEN',
'DISCORD_CLAUDE_BOT_TOKEN',
'DISCORD_CODEX_MAIN_BOT_TOKEN',
'DISCORD_CODEX_REVIEW_BOT_TOKEN',
];
export interface RegisteredGroupsSummary { export interface RegisteredGroupsSummary {
registeredGroups: number; registeredGroups: number;
groupsByAgent: Record<string, number>; groupsByAgent: Record<string, number>;
@@ -35,7 +26,6 @@ export interface VerifySummary extends RegisteredGroupsSummary {
servicesSummary: Record<string, string>; servicesSummary: Record<string, string>;
configuredChannels: string[]; configuredChannels: string[];
channelAuth: Record<string, string>; channelAuth: Record<string, string>;
legacyDiscordTokenKeys: string[];
tribunalRooms: number; tribunalRooms: number;
activeArbiterTasks: number; activeArbiterTasks: number;
// Legacy status fields kept for backward-compatible setup output. // Legacy status fields kept for backward-compatible setup output.
@@ -80,15 +70,6 @@ export function detectChannelAuth(
return channelAuth; return channelAuth;
} }
export function detectLegacyDiscordTokenKeys(
envVars = readEnvFile(LEGACY_DISCORD_TOKEN_KEYS),
processEnv: NodeJS.ProcessEnv = process.env,
): string[] {
return LEGACY_DISCORD_TOKEN_KEYS.filter(
(key) => !!(processEnv[key] || envVars[key]),
);
}
export function loadRegisteredGroupsSummary( export function loadRegisteredGroupsSummary(
dbPath = path.join(STORE_DIR, 'messages.db'), dbPath = path.join(STORE_DIR, 'messages.db'),
): RegisteredGroupsSummary { ): RegisteredGroupsSummary {
@@ -211,7 +192,6 @@ export function buildVerifySummary(
registeredGroups: number, registeredGroups: number,
groupsByAgent: Record<string, number>, groupsByAgent: Record<string, number>,
options: { options: {
legacyDiscordTokenKeys?: string[];
tribunalRooms?: number; tribunalRooms?: number;
activeArbiterTasks?: number; activeArbiterTasks?: number;
} = {}, } = {},
@@ -224,7 +204,6 @@ export function buildVerifySummary(
const hasOwnerCapableChannel = 'discord' in channelAuth; const hasOwnerCapableChannel = 'discord' in channelAuth;
const codexConfigured = 'discord-review' in channelAuth; const codexConfigured = 'discord-review' in channelAuth;
const reviewConfigured = 'discord-arbiter' in channelAuth; const reviewConfigured = 'discord-arbiter' in channelAuth;
const legacyDiscordTokenKeys = options.legacyDiscordTokenKeys ?? [];
const tribunalRooms = options.tribunalRooms ?? 0; const tribunalRooms = options.tribunalRooms ?? 0;
const activeArbiterTasks = options.activeArbiterTasks ?? 0; const activeArbiterTasks = options.activeArbiterTasks ?? 0;
const reviewerConfigured = tribunalRooms === 0 || codexConfigured; const reviewerConfigured = tribunalRooms === 0 || codexConfigured;
@@ -234,7 +213,6 @@ export function buildVerifySummary(
allConfiguredServicesRunning && allConfiguredServicesRunning &&
credentials === 'configured' && credentials === 'configured' &&
hasOwnerCapableChannel && hasOwnerCapableChannel &&
legacyDiscordTokenKeys.length === 0 &&
reviewerConfigured && reviewerConfigured &&
arbiterConfigured && arbiterConfigured &&
registeredGroups > 0 registeredGroups > 0
@@ -251,7 +229,6 @@ export function buildVerifySummary(
servicesSummary, servicesSummary,
configuredChannels, configuredChannels,
channelAuth, channelAuth,
legacyDiscordTokenKeys,
tribunalRooms, tribunalRooms,
activeArbiterTasks, activeArbiterTasks,
registeredGroups, registeredGroups,

View File

@@ -21,7 +21,6 @@ import {
buildVerifySummary, buildVerifySummary,
detectChannelAuth, detectChannelAuth,
detectCredentials, detectCredentials,
detectLegacyDiscordTokenKeys,
loadRegisteredGroupsSummary, loadRegisteredGroupsSummary,
loadRoleRoutingRequirementsSummary, loadRoleRoutingRequirementsSummary,
} from './verify-state.js'; } from './verify-state.js';
@@ -58,7 +57,6 @@ export async function run(_args: string[]): Promise<void> {
const credentials = detectCredentials(projectRoot); const credentials = detectCredentials(projectRoot);
const channelAuth = detectChannelAuth(); const channelAuth = detectChannelAuth();
const legacyDiscordTokenKeys = detectLegacyDiscordTokenKeys();
const { registeredGroups, groupsByAgent } = loadRegisteredGroupsSummary(); const { registeredGroups, groupsByAgent } = loadRegisteredGroupsSummary();
const { tribunalRooms, activeArbiterTasks } = const { tribunalRooms, activeArbiterTasks } =
loadRoleRoutingRequirementsSummary(); loadRoleRoutingRequirementsSummary();
@@ -66,7 +64,6 @@ export async function run(_args: string[]): Promise<void> {
status: baseStatus, status: baseStatus,
servicesSummary, servicesSummary,
configuredChannels, configuredChannels,
legacyDiscordTokenKeys: legacyDiscordTokens,
tribunalRooms: detectedTribunalRooms, tribunalRooms: detectedTribunalRooms,
activeArbiterTasks: detectedActiveArbiterTasks, activeArbiterTasks: detectedActiveArbiterTasks,
codexConfigured, codexConfigured,
@@ -79,7 +76,6 @@ export async function run(_args: string[]): Promise<void> {
registeredGroups, registeredGroups,
groupsByAgent, groupsByAgent,
{ {
legacyDiscordTokenKeys,
tribunalRooms, tribunalRooms,
activeArbiterTasks, activeArbiterTasks,
}, },
@@ -93,7 +89,6 @@ export async function run(_args: string[]): Promise<void> {
{ {
status, status,
channelAuth, channelAuth,
legacyDiscordTokens,
tribunalRooms: detectedTribunalRooms, tribunalRooms: detectedTribunalRooms,
activeArbiterTasks: detectedActiveArbiterTasks, activeArbiterTasks: detectedActiveArbiterTasks,
servicesSummary, servicesSummary,
@@ -101,16 +96,6 @@ export async function run(_args: string[]): Promise<void> {
}, },
'Verification complete', 'Verification complete',
); );
if (legacyDiscordTokens.length > 0) {
logger.error(
{
legacyDiscordTokens,
migration:
'Rename Discord bot tokens to DISCORD_OWNER_BOT_TOKEN / DISCORD_REVIEWER_BOT_TOKEN / DISCORD_ARBITER_BOT_TOKEN',
},
'Verification failed due to legacy service-based Discord bot token names',
);
}
if (legacyServiceIssues.length > 0) { if (legacyServiceIssues.length > 0) {
logger.error( logger.error(
{ {
@@ -133,7 +118,6 @@ export async function run(_args: string[]): Promise<void> {
CREDENTIALS: credentials, CREDENTIALS: credentials,
CONFIGURED_CHANNELS: configuredChannels.join(','), CONFIGURED_CHANNELS: configuredChannels.join(','),
CHANNEL_AUTH: JSON.stringify(channelAuth), CHANNEL_AUTH: JSON.stringify(channelAuth),
LEGACY_DISCORD_TOKENS: legacyDiscordTokens.join(','),
TRIBUNAL_ROOMS: detectedTribunalRooms, TRIBUNAL_ROOMS: detectedTribunalRooms,
ACTIVE_ARBITER_TASKS: detectedActiveArbiterTasks, ACTIVE_ARBITER_TASKS: detectedActiveArbiterTasks,
REGISTERED_GROUPS: registeredGroups, REGISTERED_GROUPS: registeredGroups,

View File

@@ -3,7 +3,15 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
// --- Mocks --- // --- Mocks ---
// Mock registry (registerChannel runs at import time) // Mock registry (registerChannel runs at import time)
vi.mock('./registry.js', () => ({ registerChannel: vi.fn() })); const registeredChannelFactories = vi.hoisted(
() => new Map<string, (...args: any[]) => any>(),
);
vi.mock('./registry.js', () => ({
registerChannel: vi.fn((name: string, factory: (...args: any[]) => any) => {
registeredChannelFactories.set(name, factory);
}),
}));
// Mock env reader (used by the factory, not needed in unit tests) // Mock env reader (used by the factory, not needed in unit tests)
vi.mock('../env.js', () => ({ vi.mock('../env.js', () => ({
@@ -113,6 +121,8 @@ vi.mock('discord.js', () => {
}); });
import { DiscordChannel, DiscordChannelOpts } from './discord.js'; import { DiscordChannel, DiscordChannelOpts } from './discord.js';
import { registerChannel } from './registry.js';
import { getEnv } from '../env.js';
import { logger } from '../logger.js'; import { logger } from '../logger.js';
// --- Test helpers --- // --- Test helpers ---
@@ -214,6 +224,31 @@ describe('DiscordChannel', () => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
describe('channel registration', () => {
it('warns when only legacy owner token names are configured', () => {
const ownerFactory = registeredChannelFactories.get('discord');
vi.mocked(getEnv).mockImplementation((key: string) => {
if (key === 'DISCORD_BOT_TOKEN') return 'legacy-owner-token';
return undefined;
});
expect(ownerFactory).toBeTypeOf('function');
expect(ownerFactory?.(createTestOpts() as any)).toBeNull();
expect(logger.warn).toHaveBeenCalledWith(
{
role: 'owner',
canonicalKey: 'DISCORD_OWNER_BOT_TOKEN',
legacyKeys: ['DISCORD_BOT_TOKEN'],
},
'Discord: legacy bot token names detected; rename them to canonical role-based names',
);
expect(logger.warn).toHaveBeenCalledWith(
'Discord: DISCORD_OWNER_BOT_TOKEN not set',
);
});
});
// --- Connection lifecycle --- // --- Connection lifecycle ---
describe('connection lifecycle', () => { describe('connection lifecycle', () => {

View File

@@ -43,31 +43,24 @@ const DISCORD_ARBITER_LEGACY_TOKEN_KEYS = [
'DISCORD_CODEX_REVIEW_BOT_TOKEN', 'DISCORD_CODEX_REVIEW_BOT_TOKEN',
]; ];
function getConfiguredLegacyDiscordTokenKeys(keys: string[]): string[] { function warnIfLegacyDiscordTokenConfigured(
return keys.filter((key) => !!getEnv(key));
}
function getRoleTokenOrLogMigrationError(
canonicalKey: string, canonicalKey: string,
legacyKeys: string[], legacyKeys: string[],
role: 'owner' | 'reviewer' | 'arbiter', role: 'owner' | 'reviewer' | 'arbiter',
): string { ): void {
const canonicalValue = getEnv(canonicalKey) || ''; if (getEnv(canonicalKey)) return;
if (canonicalValue) return canonicalValue;
const configuredLegacyKeys = getConfiguredLegacyDiscordTokenKeys(legacyKeys); const configuredLegacyKeys = legacyKeys.filter((key) => !!getEnv(key));
if (configuredLegacyKeys.length > 0) { if (configuredLegacyKeys.length === 0) return;
logger.error(
{
role,
canonicalKey,
legacyKeys: configuredLegacyKeys,
},
'Discord: legacy service-based bot token names are no longer supported; rename them to canonical role-based names',
);
}
return ''; logger.warn(
{
role,
canonicalKey,
legacyKeys: configuredLegacyKeys,
},
'Discord: legacy bot token names detected; rename them to canonical role-based names',
);
} }
/** /**
@@ -834,11 +827,12 @@ export class DiscordChannel implements Channel {
} }
registerChannel(DISCORD_OWNER_CHANNEL, (opts: ChannelOpts) => { registerChannel(DISCORD_OWNER_CHANNEL, (opts: ChannelOpts) => {
const token = getRoleTokenOrLogMigrationError( warnIfLegacyDiscordTokenConfigured(
DISCORD_OWNER_TOKEN_KEY, DISCORD_OWNER_TOKEN_KEY,
DISCORD_OWNER_LEGACY_TOKEN_KEYS, DISCORD_OWNER_LEGACY_TOKEN_KEYS,
'owner', 'owner',
); );
const token = getEnv(DISCORD_OWNER_TOKEN_KEY) || '';
if (!token) { if (!token) {
logger.warn('Discord: DISCORD_OWNER_BOT_TOKEN not set'); logger.warn('Discord: DISCORD_OWNER_BOT_TOKEN not set');
return null; return null;
@@ -847,12 +841,13 @@ registerChannel(DISCORD_OWNER_CHANNEL, (opts: ChannelOpts) => {
}); });
registerChannel(DISCORD_REVIEWER_CHANNEL, (opts: ChannelOpts) => { registerChannel(DISCORD_REVIEWER_CHANNEL, (opts: ChannelOpts) => {
const ownerToken = getEnv(DISCORD_OWNER_TOKEN_KEY) || ''; warnIfLegacyDiscordTokenConfigured(
const token = getRoleTokenOrLogMigrationError(
DISCORD_REVIEWER_TOKEN_KEY, DISCORD_REVIEWER_TOKEN_KEY,
DISCORD_REVIEWER_LEGACY_TOKEN_KEYS, DISCORD_REVIEWER_LEGACY_TOKEN_KEYS,
'reviewer', 'reviewer',
); );
const ownerToken = getEnv(DISCORD_OWNER_TOKEN_KEY) || '';
const token = getEnv(DISCORD_REVIEWER_TOKEN_KEY) || '';
if (!token) return null; if (!token) return null;
if (token === ownerToken) { if (token === ownerToken) {
logger.warn( logger.warn(
@@ -871,13 +866,14 @@ registerChannel(DISCORD_REVIEWER_CHANNEL, (opts: ChannelOpts) => {
}); });
registerChannel(DISCORD_ARBITER_CHANNEL, (opts: ChannelOpts) => { registerChannel(DISCORD_ARBITER_CHANNEL, (opts: ChannelOpts) => {
const ownerToken = getEnv(DISCORD_OWNER_TOKEN_KEY) || ''; warnIfLegacyDiscordTokenConfigured(
const reviewerToken = getEnv(DISCORD_REVIEWER_TOKEN_KEY) || '';
const token = getRoleTokenOrLogMigrationError(
DISCORD_ARBITER_TOKEN_KEY, DISCORD_ARBITER_TOKEN_KEY,
DISCORD_ARBITER_LEGACY_TOKEN_KEYS, DISCORD_ARBITER_LEGACY_TOKEN_KEYS,
'arbiter', 'arbiter',
); );
const ownerToken = getEnv(DISCORD_OWNER_TOKEN_KEY) || '';
const reviewerToken = getEnv(DISCORD_REVIEWER_TOKEN_KEY) || '';
const token = getEnv(DISCORD_ARBITER_TOKEN_KEY) || '';
if (!token) return null; if (!token) return null;
if (token === ownerToken || token === reviewerToken) { if (token === ownerToken || token === reviewerToken) {
logger.warn( logger.warn(