Finalize turn-role invariants and legacy migration cutover
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
buildVerifySummary,
|
||||
detectChannelAuth,
|
||||
detectCredentials,
|
||||
loadRegisteredGroupsSummary,
|
||||
loadAssignedRoomsSummary,
|
||||
loadRoleRoutingRequirementsSummary,
|
||||
} from './verify-state.js';
|
||||
|
||||
@@ -69,9 +69,10 @@ describe('verify state helpers', () => {
|
||||
const dbPath = path.join(tempRoot, 'messages.db');
|
||||
const db = new Database(dbPath);
|
||||
db.exec(`
|
||||
CREATE TABLE registered_groups (
|
||||
jid TEXT NOT NULL,
|
||||
agent_type TEXT
|
||||
CREATE TABLE room_settings (
|
||||
chat_jid TEXT PRIMARY KEY,
|
||||
room_mode TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
@@ -82,10 +83,9 @@ describe('verify state helpers', () => {
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT INTO registered_groups (jid, agent_type) VALUES
|
||||
('group-1', 'claude-code'),
|
||||
('group-1', 'codex'),
|
||||
('group-2', 'claude-code');
|
||||
INSERT INTO room_settings (chat_jid, room_mode, updated_at) VALUES
|
||||
('group-1', 'tribunal', '2024-01-01T00:00:00.000Z'),
|
||||
('group-2', 'single', '2024-01-01T00:00:00.000Z');
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT INTO paired_tasks (id, chat_jid, status) VALUES
|
||||
@@ -94,13 +94,48 @@ describe('verify state helpers', () => {
|
||||
`);
|
||||
db.close();
|
||||
|
||||
expect(loadRoleRoutingRequirementsSummary(dbPath)).toEqual({
|
||||
expect(loadRoleRoutingRequirementsSummary({ dbPath })).toEqual({
|
||||
tribunalRooms: 1,
|
||||
activeArbiterTasks: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('loads registered group counts from the sqlite store', () => {
|
||||
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,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: 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');
|
||||
@@ -109,22 +144,307 @@ describe('verify state helpers', () => {
|
||||
CREATE TABLE registered_groups (
|
||||
jid TEXT PRIMARY KEY,
|
||||
agent_type TEXT
|
||||
);
|
||||
)
|
||||
`);
|
||||
db.exec(`
|
||||
INSERT INTO registered_groups (jid, agent_type) VALUES
|
||||
('group-1', 'claude-code'),
|
||||
('group-2', 'codex'),
|
||||
('group-3', 'codex');
|
||||
('legacy-room', 'claude-code')
|
||||
`);
|
||||
db.close();
|
||||
|
||||
expect(loadRegisteredGroupsSummary(dbPath)).toEqual({
|
||||
registeredGroups: 3,
|
||||
groupsByAgent: {
|
||||
'claude-code': 1,
|
||||
codex: 2,
|
||||
expect(loadAssignedRoomsSummary({ dbPath, projectRoot: tempRoot })).toEqual(
|
||||
{
|
||||
assignedRooms: 0,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 1,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: true,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: 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,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: true,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: 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,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('marks legacy registered_groups.json as migration-required', () => {
|
||||
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,
|
||||
hasLegacyRegisteredGroupsJson: true,
|
||||
legacyRoomMigrationRequired: true,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('marks pending legacy json state files as migration-required', () => {
|
||||
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,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: ['router_state.json', 'sessions.json'],
|
||||
legacyJsonStateMigrationRequired: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,14 +470,21 @@ describe('verify state helpers', () => {
|
||||
'discord-review': 'configured',
|
||||
'discord-arbiter': 'configured',
|
||||
},
|
||||
2,
|
||||
{ codex: 1 },
|
||||
{
|
||||
assignedRooms: 2,
|
||||
roomsByOwnerAgent: { codex: 1 },
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'success',
|
||||
configuredChannels: ['discord', 'discord-review', 'discord-arbiter'],
|
||||
codexConfigured: true,
|
||||
reviewConfigured: true,
|
||||
reviewerChannelConfigured: true,
|
||||
arbiterChannelConfigured: true,
|
||||
servicesSummary: { ejclaw: 'running' },
|
||||
});
|
||||
});
|
||||
@@ -171,8 +498,15 @@ describe('verify state helpers', () => {
|
||||
[],
|
||||
'missing',
|
||||
{},
|
||||
0,
|
||||
{},
|
||||
{
|
||||
assignedRooms: 0,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
@@ -190,14 +524,21 @@ describe('verify state helpers', () => {
|
||||
[],
|
||||
'configured',
|
||||
{ 'discord-review': 'configured' },
|
||||
1,
|
||||
{},
|
||||
{
|
||||
assignedRooms: 1,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
configuredChannels: ['discord-review'],
|
||||
codexConfigured: true,
|
||||
reviewConfigured: false,
|
||||
reviewerChannelConfigured: true,
|
||||
arbiterChannelConfigured: false,
|
||||
servicesSummary: { ejclaw: 'running' },
|
||||
});
|
||||
});
|
||||
@@ -211,15 +552,22 @@ describe('verify state helpers', () => {
|
||||
[],
|
||||
'configured',
|
||||
{ discord: 'configured' },
|
||||
1,
|
||||
{},
|
||||
{
|
||||
assignedRooms: 1,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
{ tribunalRooms: 1 },
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
configuredChannels: ['discord'],
|
||||
codexConfigured: false,
|
||||
reviewConfigured: false,
|
||||
reviewerChannelConfigured: false,
|
||||
arbiterChannelConfigured: false,
|
||||
tribunalRooms: 1,
|
||||
});
|
||||
});
|
||||
@@ -236,17 +584,75 @@ describe('verify state helpers', () => {
|
||||
discord: 'configured',
|
||||
'discord-review': 'configured',
|
||||
},
|
||||
1,
|
||||
{},
|
||||
{
|
||||
assignedRooms: 1,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
{ tribunalRooms: 1, activeArbiterTasks: 1 },
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
configuredChannels: ['discord', 'discord-review'],
|
||||
codexConfigured: true,
|
||||
reviewConfigured: false,
|
||||
reviewerChannelConfigured: true,
|
||||
arbiterChannelConfigured: false,
|
||||
activeArbiterTasks: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('fails verification when legacy room migration is still required', () => {
|
||||
const services: ServiceCheck[] = [{ name: 'ejclaw', status: 'running' }];
|
||||
|
||||
expect(
|
||||
buildVerifySummary(
|
||||
services,
|
||||
[],
|
||||
'configured',
|
||||
{ discord: 'configured' },
|
||||
{
|
||||
assignedRooms: 0,
|
||||
roomsByOwnerAgent: {},
|
||||
legacyRegisteredGroupRows: 2,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: true,
|
||||
pendingLegacyJsonStateFiles: [],
|
||||
legacyJsonStateMigrationRequired: false,
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
legacyRoomMigrationRequired: true,
|
||||
legacyRegisteredGroupRows: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('fails verification when legacy json state migration is still required', () => {
|
||||
const services: ServiceCheck[] = [{ name: 'ejclaw', status: 'running' }];
|
||||
|
||||
expect(
|
||||
buildVerifySummary(
|
||||
services,
|
||||
[],
|
||||
'configured',
|
||||
{ discord: 'configured' },
|
||||
{
|
||||
assignedRooms: 1,
|
||||
roomsByOwnerAgent: { codex: 1 },
|
||||
legacyRegisteredGroupRows: 0,
|
||||
hasLegacyRegisteredGroupsJson: false,
|
||||
legacyRoomMigrationRequired: false,
|
||||
pendingLegacyJsonStateFiles: ['router_state.json'],
|
||||
legacyJsonStateMigrationRequired: true,
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
status: 'failed',
|
||||
legacyJsonStateMigrationRequired: true,
|
||||
pendingLegacyJsonStateFiles: ['router_state.json'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user