From 30dda74621b5a157b8e01377184f0d079a84903c Mon Sep 17 00:00:00 2001 From: ejclaw Date: Sat, 11 Apr 2026 05:28:26 +0900 Subject: [PATCH] setup: narrow legacy room registration surface --- setup/environment.test.ts | 73 +----- setup/environment.ts | 12 +- setup/room-registration-state.test.ts | 299 ++++++++++++++++++++++ setup/room-registration-state.ts | 6 +- setup/verify-state.test.ts | 343 -------------------------- setup/verify.ts | 10 +- src/db.test.ts | 19 ++ src/db/room-registration.ts | 2 + src/types.ts | 2 +- 9 files changed, 336 insertions(+), 430 deletions(-) create mode 100644 setup/room-registration-state.test.ts diff --git a/setup/environment.test.ts b/setup/environment.test.ts index 39971f8..183c012 100644 --- a/setup/environment.test.ts +++ b/setup/environment.test.ts @@ -4,7 +4,7 @@ import path from 'path'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { Database } from 'bun:sqlite'; -import { detectAssignedRooms, run as runEnvironment } from './environment.js'; +import { run as runEnvironment } from './environment.js'; /** * Tests for the environment check step. @@ -20,77 +20,6 @@ describe('environment detection', () => { }); }); -describe('assigned rooms detection', () => { - const tempDirs: string[] = []; - - afterEach(() => { - for (const dir of tempDirs.splice(0)) { - fs.rmSync(dir, { recursive: true, force: true }); - } - }); - - it('returns false when the database does not exist', () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-')); - tempDirs.push(tempDir); - - expect( - detectAssignedRooms({ dbPath: path.join(tempDir, 'messages.db') }), - ).toBe(false); - }); - - it('returns false for an empty room_settings table', () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-')); - tempDirs.push(tempDir); - const dbPath = path.join(tempDir, 'messages.db'); - const db = new Database(dbPath); - db.exec(`CREATE TABLE room_settings (chat_jid TEXT PRIMARY KEY)`); - db.close(); - - expect(detectAssignedRooms({ dbPath })).toBe(false); - }); - - it('returns true when canonical room assignments exist', () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-')); - tempDirs.push(tempDir); - const dbPath = path.join(tempDir, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE room_settings ( - chat_jid TEXT PRIMARY KEY, - room_mode TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - `); - db.prepare( - `INSERT INTO room_settings (chat_jid, room_mode, updated_at) - VALUES (?, ?, ?)`, - ).run('dc:room-1', 'single', '2024-01-01T00:00:00.000Z'); - db.close(); - - expect(detectAssignedRooms({ dbPath })).toBe(true); - }); - - it('returns false when only legacy registered_groups rows exist', () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-env-')); - tempDirs.push(tempDir); - const dbPath = path.join(tempDir, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE registered_groups ( - jid TEXT PRIMARY KEY, - agent_type TEXT - ) - `); - db.exec(` - INSERT INTO registered_groups (jid, agent_type) - VALUES ('dc:legacy-room', 'claude-code') - `); - db.close(); - - expect(detectAssignedRooms({ dbPath })).toBe(false); - }); -}); - describe('environment step legacy-room handling', () => { const originalCwd = process.cwd(); const tempDirs: string[] = []; diff --git a/setup/environment.ts b/setup/environment.ts index 4f9e2c9..f6276b8 100644 --- a/setup/environment.ts +++ b/setup/environment.ts @@ -16,7 +16,7 @@ import { } from './platform.js'; import { detectRoomRegistrationState, - getLegacyMigrationGuidance, + getRoomRegistrationGateFailure, } from './room-registration-state.js'; import { emitStatus } from './status.js'; @@ -51,11 +51,11 @@ export async function run(_args: string[]): Promise { projectRoot, dbPath: path.join(projectRoot, 'store', 'messages.db'), }); - const legacyMigrationGuidance = getLegacyMigrationGuidance( + const roomRegistrationGateFailure = getRoomRegistrationGateFailure( roomState, 'setup', ); - const status = legacyMigrationGuidance ? 'failed' : 'success'; + const status = roomRegistrationGateFailure ? 'failed' : 'success'; logger.info( { @@ -94,10 +94,10 @@ export async function run(_args: string[]): Promise { apparmorRestrictUnprivilegedUserns ?? 'n/a', HAS_BWRAP_READONLY_SANDBOX_CAPABILITY: hasBubblewrapReadonlySandboxCapability, - ...(legacyMigrationGuidance + ...(roomRegistrationGateFailure ? { - ERROR: legacyMigrationGuidance.error, - NEXT_STEP: legacyMigrationGuidance.nextStep, + ERROR: roomRegistrationGateFailure.error, + NEXT_STEP: roomRegistrationGateFailure.nextStep, } : {}), STATUS: status, diff --git a/setup/room-registration-state.test.ts b/setup/room-registration-state.test.ts new file mode 100644 index 0000000..4bb5cba --- /dev/null +++ b/setup/room-registration-state.test.ts @@ -0,0 +1,299 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { Database } from 'bun:sqlite'; + +import { + detectRoomRegistrationState, + getRoomRegistrationGateFailure, +} from './room-registration-state.js'; + +describe('room registration state', () => { + const tempRoots: string[] = []; + + afterEach(() => { + for (const root of tempRoots.splice(0)) { + fs.rmSync(root, { recursive: true, force: true }); + } + }); + + it('reports no assigned rooms when the database does not exist', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-')); + tempRoots.push(tempRoot); + + expect( + detectRoomRegistrationState({ + projectRoot: tempRoot, + dbPath: path.join(tempRoot, 'messages.db'), + }), + ).toEqual({ + assignedRooms: 0, + roomsByOwnerAgent: {}, + legacyRegisteredGroupRows: 0, + legacyRoomMigrationRequired: false, + unexpectedDataStateFiles: [], + unexpectedDataStateDetected: false, + }); + }); + + it('counts canonical room_settings rows and owner-agent breakdowns', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-')); + tempRoots.push(tempRoot); + const dbPath = path.join(tempRoot, 'messages.db'); + const db = new Database(dbPath); + db.exec(` + CREATE TABLE room_settings ( + chat_jid TEXT PRIMARY KEY, + room_mode TEXT NOT NULL, + owner_agent_type TEXT, + updated_at TEXT NOT NULL + ); + `); + db.exec(` + INSERT INTO room_settings (chat_jid, room_mode, owner_agent_type, updated_at) VALUES + ('group-1', 'single', 'claude-code', '2024-01-01T00:00:00.000Z'), + ('group-2', 'single', 'codex', '2024-01-01T00:00:00.000Z'), + ('group-3', 'tribunal', 'codex', '2024-01-01T00:00:00.000Z'); + `); + db.close(); + + expect( + detectRoomRegistrationState({ projectRoot: tempRoot, dbPath }), + ).toEqual({ + assignedRooms: 3, + roomsByOwnerAgent: { + 'claude-code': 1, + codex: 2, + }, + legacyRegisteredGroupRows: 0, + legacyRoomMigrationRequired: false, + unexpectedDataStateFiles: [], + unexpectedDataStateDetected: false, + }); + }); + + it('marks legacy-only sqlite registrations as migration-required', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-')); + tempRoots.push(tempRoot); + const dbPath = path.join(tempRoot, 'messages.db'); + const db = new Database(dbPath); + db.exec(` + CREATE TABLE registered_groups ( + jid TEXT PRIMARY KEY, + agent_type TEXT + ) + `); + db.exec(` + INSERT INTO registered_groups (jid, agent_type) VALUES + ('legacy-room', 'claude-code') + `); + db.close(); + + expect( + detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }), + ).toEqual({ + assignedRooms: 0, + roomsByOwnerAgent: {}, + legacyRegisteredGroupRows: 1, + legacyRoomMigrationRequired: true, + unexpectedDataStateFiles: [], + unexpectedDataStateDetected: false, + }); + }); + + it('ignores legacy projection rows when matching canonical room_settings already exist', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-')); + tempRoots.push(tempRoot); + const dbPath = path.join(tempRoot, 'messages.db'); + const db = new Database(dbPath); + db.exec(` + CREATE TABLE room_settings ( + chat_jid TEXT PRIMARY KEY, + room_mode TEXT NOT NULL, + mode_source TEXT NOT NULL DEFAULT 'explicit', + name TEXT, + folder TEXT, + trigger_pattern TEXT, + requires_trigger INTEGER DEFAULT 1, + is_main INTEGER DEFAULT 0, + owner_agent_type TEXT, + work_dir TEXT, + updated_at TEXT NOT NULL + ); + CREATE TABLE room_role_overrides ( + chat_jid TEXT NOT NULL, + role TEXT NOT NULL, + agent_type TEXT NOT NULL, + agent_config_json TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + PRIMARY KEY (chat_jid, role) + ); + CREATE TABLE registered_groups ( + jid TEXT NOT NULL, + name TEXT NOT NULL, + folder TEXT NOT NULL, + trigger_pattern TEXT NOT NULL, + added_at TEXT NOT NULL, + agent_config TEXT, + requires_trigger INTEGER DEFAULT 1, + is_main INTEGER DEFAULT 0, + agent_type TEXT NOT NULL, + work_dir TEXT, + PRIMARY KEY (jid, agent_type) + ) + `); + db.exec(` + INSERT INTO room_settings ( + chat_jid, + room_mode, + mode_source, + name, + folder, + trigger_pattern, + requires_trigger, + is_main, + owner_agent_type, + work_dir, + updated_at + ) VALUES ( + 'same-room', + 'single', + 'explicit', + 'Same Room', + 'same-room', + '@Andy', + 1, + 0, + 'codex', + NULL, + '2024-01-01T00:00:00.000Z' + ) + `); + db.exec(` + INSERT INTO registered_groups ( + jid, + name, + folder, + trigger_pattern, + added_at, + agent_config, + requires_trigger, + is_main, + agent_type, + work_dir + ) VALUES ( + 'same-room', + 'Same Room', + 'same-room', + '@Andy', + '2024-01-01T00:00:00.000Z', + NULL, + 1, + 0, + 'codex', + NULL + ) + `); + db.exec(` + INSERT INTO room_role_overrides ( + chat_jid, + role, + agent_type, + agent_config_json, + created_at, + updated_at + ) VALUES ( + 'same-room', + 'owner', + 'codex', + NULL, + '2024-01-01T00:00:00.000Z', + '2024-01-01T00:00:00.000Z' + ) + `); + db.close(); + + expect( + detectRoomRegistrationState({ dbPath, projectRoot: tempRoot }), + ).toEqual({ + assignedRooms: 1, + roomsByOwnerAgent: { + codex: 1, + }, + legacyRegisteredGroupRows: 0, + legacyRoomMigrationRequired: false, + unexpectedDataStateFiles: [], + unexpectedDataStateDetected: false, + }); + }); + + it('marks unexpected data-state files as blockers', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-room-reg-')); + tempRoots.push(tempRoot); + fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true }); + fs.writeFileSync( + path.join(tempRoot, 'data', 'registered_groups.json'), + '{"dc:legacy":{"name":"Legacy","folder":"legacy","trigger":"@Andy","added_at":"2024-01-01T00:00:00.000Z"}}', + ); + fs.writeFileSync( + path.join(tempRoot, 'data', 'router_state.json'), + JSON.stringify({ last_timestamp: '1234' }), + ); + + expect(detectRoomRegistrationState({ projectRoot: tempRoot })).toEqual({ + assignedRooms: 0, + roomsByOwnerAgent: {}, + legacyRegisteredGroupRows: 0, + legacyRoomMigrationRequired: false, + unexpectedDataStateFiles: ['registered_groups.json', 'router_state.json'], + unexpectedDataStateDetected: true, + }); + }); + + it('returns no gate failure when room-registration state is clean', () => { + expect( + getRoomRegistrationGateFailure( + { + legacyRoomMigrationRequired: false, + unexpectedDataStateDetected: false, + }, + 'setup', + ), + ).toBeUndefined(); + }); + + it('returns a targeted gate failure when legacy room migration is pending', () => { + expect( + getRoomRegistrationGateFailure( + { + legacyRoomMigrationRequired: true, + unexpectedDataStateDetected: false, + }, + 'verification', + ), + ).toEqual({ + error: 'legacy_room_migration_required', + nextStep: + 'Run `bun setup/index.ts --step migrate-room-registrations` before continuing with verification', + }); + }); + + it('returns a combined gate failure when legacy migration and unexpected data files are both present', () => { + expect( + getRoomRegistrationGateFailure( + { + legacyRoomMigrationRequired: true, + unexpectedDataStateDetected: true, + }, + 'setup', + ), + ).toEqual({ + error: 'legacy_migration_and_unexpected_data_state_detected', + nextStep: + 'Run `bun setup/index.ts --step migrate-room-registrations` and remove or archive unexpected data state files before continuing with setup', + }); + }); +}); diff --git a/setup/room-registration-state.ts b/setup/room-registration-state.ts index decc43b..62aa371 100644 --- a/setup/room-registration-state.ts +++ b/setup/room-registration-state.ts @@ -16,7 +16,7 @@ export interface AssignedRoomsSummary { unexpectedDataStateDetected: boolean; } -export interface LegacyMigrationGuidance { +export interface RoomRegistrationGateFailure { error: string; nextStep: string; } @@ -25,13 +25,13 @@ function getDataDir(projectRoot: string): string { return process.env.EJCLAW_DATA_DIR || path.join(projectRoot, 'data'); } -export function getLegacyMigrationGuidance( +export function getRoomRegistrationGateFailure( summary: Pick< AssignedRoomsSummary, 'legacyRoomMigrationRequired' | 'unexpectedDataStateDetected' >, target: 'setup' | 'verification', -): LegacyMigrationGuidance | undefined { +): RoomRegistrationGateFailure | undefined { if ( !summary.legacyRoomMigrationRequired && !summary.unexpectedDataStateDetected diff --git a/setup/verify-state.test.ts b/setup/verify-state.test.ts index ed89843..5a9739a 100644 --- a/setup/verify-state.test.ts +++ b/setup/verify-state.test.ts @@ -11,7 +11,6 @@ import { buildVerifySummary, detectChannelAuth, detectCredentials, - loadAssignedRoomsSummary, loadRoleRoutingRequirementsSummary, } from './verify-state.js'; @@ -100,348 +99,6 @@ describe('verify state helpers', () => { }); }); - it('loads assigned room counts from the sqlite store', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - const dbPath = path.join(tempRoot, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE room_settings ( - chat_jid TEXT PRIMARY KEY, - room_mode TEXT NOT NULL, - owner_agent_type TEXT, - updated_at TEXT NOT NULL - ); - `); - db.exec(` - INSERT INTO room_settings (chat_jid, room_mode, owner_agent_type, updated_at) VALUES - ('group-1', 'single', 'claude-code', '2024-01-01T00:00:00.000Z'), - ('group-2', 'single', 'codex', '2024-01-01T00:00:00.000Z'), - ('group-3', 'tribunal', 'codex', '2024-01-01T00:00:00.000Z'); - `); - db.close(); - - expect(loadAssignedRoomsSummary({ dbPath })).toEqual({ - assignedRooms: 3, - roomsByOwnerAgent: { - 'claude-code': 1, - codex: 2, - }, - legacyRegisteredGroupRows: 0, - legacyRoomMigrationRequired: false, - unexpectedDataStateFiles: [], - unexpectedDataStateDetected: false, - }); - }); - - it('marks legacy-only sqlite registrations as migration-required', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - const dbPath = path.join(tempRoot, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE registered_groups ( - jid TEXT PRIMARY KEY, - agent_type TEXT - ) - `); - db.exec(` - INSERT INTO registered_groups (jid, agent_type) VALUES - ('legacy-room', 'claude-code') - `); - db.close(); - - expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual( - { - assignedRooms: 0, - roomsByOwnerAgent: {}, - legacyRegisteredGroupRows: 1, - legacyRoomMigrationRequired: true, - unexpectedDataStateFiles: [], - unexpectedDataStateDetected: false, - }, - ); - }); - - it('marks mixed canonical and pending legacy sqlite registrations as migration-required', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - const dbPath = path.join(tempRoot, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE room_settings ( - chat_jid TEXT PRIMARY KEY, - room_mode TEXT NOT NULL, - mode_source TEXT NOT NULL DEFAULT 'explicit', - name TEXT, - folder TEXT, - trigger_pattern TEXT, - requires_trigger INTEGER DEFAULT 1, - is_main INTEGER DEFAULT 0, - owner_agent_type TEXT, - work_dir TEXT, - updated_at TEXT NOT NULL - ); - CREATE TABLE room_role_overrides ( - chat_jid TEXT NOT NULL, - role TEXT NOT NULL, - agent_type TEXT NOT NULL, - agent_config_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (chat_jid, role) - ); - CREATE TABLE registered_groups ( - jid TEXT NOT NULL, - name TEXT NOT NULL, - folder TEXT NOT NULL, - trigger_pattern TEXT NOT NULL, - added_at TEXT NOT NULL, - agent_config TEXT, - requires_trigger INTEGER DEFAULT 1, - is_main INTEGER DEFAULT 0, - agent_type TEXT NOT NULL, - work_dir TEXT, - PRIMARY KEY (jid, agent_type) - ) - `); - db.exec(` - INSERT INTO room_settings ( - chat_jid, - room_mode, - mode_source, - name, - folder, - trigger_pattern, - requires_trigger, - is_main, - owner_agent_type, - work_dir, - updated_at - ) VALUES ( - 'canonical-room', - 'single', - 'explicit', - 'Canonical Room', - 'canonical-room', - '@Andy', - 1, - 0, - 'codex', - NULL, - '2024-01-01T00:00:00.000Z' - ) - `); - db.exec(` - INSERT INTO registered_groups ( - jid, - name, - folder, - trigger_pattern, - added_at, - agent_config, - requires_trigger, - is_main, - agent_type, - work_dir - ) VALUES ( - 'legacy-room', - 'Legacy Room', - 'legacy-room', - '@Andy', - '2024-01-01T00:00:00.000Z', - NULL, - 1, - 0, - 'claude-code', - NULL - ) - `); - db.close(); - - expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual( - { - assignedRooms: 1, - roomsByOwnerAgent: { - codex: 1, - }, - legacyRegisteredGroupRows: 1, - legacyRoomMigrationRequired: true, - unexpectedDataStateFiles: [], - unexpectedDataStateDetected: false, - }, - ); - }); - - it('ignores legacy projection rows when matching canonical room_settings already exist', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - const dbPath = path.join(tempRoot, 'messages.db'); - const db = new Database(dbPath); - db.exec(` - CREATE TABLE room_settings ( - chat_jid TEXT PRIMARY KEY, - room_mode TEXT NOT NULL, - mode_source TEXT NOT NULL DEFAULT 'explicit', - name TEXT, - folder TEXT, - trigger_pattern TEXT, - requires_trigger INTEGER DEFAULT 1, - is_main INTEGER DEFAULT 0, - owner_agent_type TEXT, - work_dir TEXT, - updated_at TEXT NOT NULL - ); - CREATE TABLE room_role_overrides ( - chat_jid TEXT NOT NULL, - role TEXT NOT NULL, - agent_type TEXT NOT NULL, - agent_config_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL, - PRIMARY KEY (chat_jid, role) - ); - CREATE TABLE registered_groups ( - jid TEXT NOT NULL, - name TEXT NOT NULL, - folder TEXT NOT NULL, - trigger_pattern TEXT NOT NULL, - added_at TEXT NOT NULL, - agent_config TEXT, - requires_trigger INTEGER DEFAULT 1, - is_main INTEGER DEFAULT 0, - agent_type TEXT NOT NULL, - work_dir TEXT, - PRIMARY KEY (jid, agent_type) - ) - `); - db.exec(` - INSERT INTO room_settings ( - chat_jid, - room_mode, - mode_source, - name, - folder, - trigger_pattern, - requires_trigger, - is_main, - owner_agent_type, - work_dir, - updated_at - ) VALUES ( - 'same-room', - 'single', - 'explicit', - 'Same Room', - 'same-room', - '@Andy', - 1, - 0, - 'codex', - NULL, - '2024-01-01T00:00:00.000Z' - ) - `); - db.exec(` - INSERT INTO registered_groups ( - jid, - name, - folder, - trigger_pattern, - added_at, - agent_config, - requires_trigger, - is_main, - agent_type, - work_dir - ) VALUES ( - 'same-room', - 'Same Room', - 'same-room', - '@Andy', - '2024-01-01T00:00:00.000Z', - NULL, - 1, - 0, - 'codex', - NULL - ) - `); - db.exec(` - INSERT INTO room_role_overrides ( - chat_jid, - role, - agent_type, - agent_config_json, - created_at, - updated_at - ) VALUES ( - 'same-room', - 'owner', - 'codex', - NULL, - '2024-01-01T00:00:00.000Z', - '2024-01-01T00:00:00.000Z' - ) - `); - db.close(); - - expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual( - { - assignedRooms: 1, - roomsByOwnerAgent: { - codex: 1, - }, - legacyRegisteredGroupRows: 0, - legacyRoomMigrationRequired: false, - unexpectedDataStateFiles: [], - unexpectedDataStateDetected: false, - }, - ); - }); - - it('marks unexpected data-state files as setup blockers', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true }); - fs.writeFileSync( - path.join(tempRoot, 'data', 'registered_groups.json'), - '{"dc:legacy":{"name":"Legacy","folder":"legacy","trigger":"@Andy","added_at":"2024-01-01T00:00:00.000Z"}}', - ); - - expect(loadAssignedRoomsSummary({ projectRoot: tempRoot })).toEqual({ - assignedRooms: 0, - roomsByOwnerAgent: {}, - legacyRegisteredGroupRows: 0, - legacyRoomMigrationRequired: false, - unexpectedDataStateFiles: ['registered_groups.json'], - unexpectedDataStateDetected: true, - }); - }); - - it('marks unexpected router/session json files as setup blockers', () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-verify-')); - tempRoots.push(tempRoot); - fs.mkdirSync(path.join(tempRoot, 'data'), { recursive: true }); - fs.writeFileSync( - path.join(tempRoot, 'data', 'router_state.json'), - JSON.stringify({ last_timestamp: '1234' }), - ); - fs.writeFileSync( - path.join(tempRoot, 'data', 'sessions.json'), - JSON.stringify({ 'group-a': 'session-123' }), - ); - - expect(loadAssignedRoomsSummary({ projectRoot: tempRoot })).toEqual({ - assignedRooms: 0, - roomsByOwnerAgent: {}, - legacyRegisteredGroupRows: 0, - legacyRoomMigrationRequired: false, - unexpectedDataStateFiles: ['router_state.json', 'sessions.json'], - unexpectedDataStateDetected: true, - }); - }); - it('builds a successful verification summary when all gates pass', () => { const services: ServiceCheck[] = [{ name: 'ejclaw', status: 'running' }]; const serviceDefs: ServiceDef[] = [ diff --git a/setup/verify.ts b/setup/verify.ts index b4fbf80..b1fd291 100644 --- a/setup/verify.ts +++ b/setup/verify.ts @@ -11,7 +11,7 @@ import path from 'path'; import { logger } from '../src/logger.js'; import { getServiceManager } from './platform.js'; -import { getLegacyMigrationGuidance } from './room-registration-state.js'; +import { getRoomRegistrationGateFailure } from './room-registration-state.js'; import { getServiceDefs } from './service-defs.js'; import { emitStatus } from './status.js'; import { @@ -66,7 +66,7 @@ export async function run(_args: string[]): Promise { activeArbiterTasks, }, ); - const legacyMigrationGuidance = getLegacyMigrationGuidance( + const roomRegistrationGateFailure = getRoomRegistrationGateFailure( roomSummary, 'verification', ); @@ -100,10 +100,10 @@ export async function run(_args: string[]): Promise { UNEXPECTED_DATA_STATE_DETECTED: roomSummary.unexpectedDataStateDetected, REVIEWER_CHANNEL_CONFIGURED: reviewerChannelConfigured, ARBITER_CHANNEL_CONFIGURED: arbiterChannelConfigured, - ...(legacyMigrationGuidance + ...(roomRegistrationGateFailure ? { - ERROR: legacyMigrationGuidance.error, - NEXT_STEP: legacyMigrationGuidance.nextStep, + ERROR: roomRegistrationGateFailure.error, + NEXT_STEP: roomRegistrationGateFailure.nextStep, } : {}), STATUS: status, diff --git a/src/db.test.ts b/src/db.test.ts index ddd62df..af0efa6 100644 --- a/src/db.test.ts +++ b/src/db.test.ts @@ -2271,6 +2271,25 @@ describe('message query LIMIT', () => { // --- RegisteredGroup isMain round-trip --- describe('registered group isMain', () => { + it('projects trigger metadata from canonical room settings into registered groups', () => { + _setRegisteredGroupForTests('dc:triggered', { + name: 'Triggered Room', + folder: 'triggered-room', + trigger: '@Andy', + added_at: '2024-01-01T00:00:00.000Z', + requiresTrigger: true, + }); + + expect(getRegisteredGroup('dc:triggered')).toMatchObject({ + trigger: '@Andy', + requiresTrigger: true, + }); + expect(getAllRoomBindings()['dc:triggered']).toMatchObject({ + trigger: '@Andy', + requiresTrigger: true, + }); + }); + it('persists isMain=true through set/get round-trip', () => { _setRegisteredGroupForTests('dc:main', { name: 'Main Chat', diff --git a/src/db/room-registration.ts b/src/db/room-registration.ts index 12b8b04..775bebb 100644 --- a/src/db/room-registration.ts +++ b/src/db/room-registration.ts @@ -244,8 +244,10 @@ export function buildRegisteredGroupFromStoredSettings( jid: stored.chatJid, name: stored.name || stored.chatJid, folder: stored.folder, + trigger: stored.trigger, added_at: capabilityMetadata?.createdAt || new Date(0).toISOString(), agentConfig: capabilityMetadata?.agentConfig, + requiresTrigger: stored.requiresTrigger ?? false, isMain: stored.isMain ? true : undefined, agentType: resolvedAgentType, workDir: stored.workDir, diff --git a/src/types.ts b/src/types.ts index 3f1e091..ff64efa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -151,7 +151,7 @@ export interface RegisteredGroup { trigger?: string; added_at: string; agentConfig?: AgentConfig; - requiresTrigger?: boolean; // Compatibility-only legacy field + requiresTrigger?: boolean; // Whether non-paired messages must match the room trigger isMain?: boolean; // True for the main control group (no trigger, elevated privileges) agentType?: AgentType; workDir?: string; // Working directory for the agent (defaults to group folder)