/**
* M3 Persistence Test
*
* Tests that conversation state survives server restart.
*
* Protocol:
* 1. Start server
* 2. Make MCP calls (identity query, approval grant, action execution)
* 3. Verify state persisted to SQLite
* 4. Restart server
* 5. Make MCP call with same conversation_id
* 6. Verify WHO/WHAT/HOW dimensions restored
*/
const { execSync } = require('child_process');
const Database = require('better-sqlite3');
const path = require('path');
const DB_PATH = path.join(__dirname, 'data', 'conversations.db');
console.log('M3 Persistence Test');
console.log('===================\n');
/**
* Test Phase 1: Verify database structure
*/
function testDatabaseSchema() {
console.log('Phase 1: Database Schema Verification');
console.log('--------------------------------------');
try {
const db = new Database(DB_PATH);
// Check table exists
const tableCheck = db.prepare(`
SELECT name FROM sqlite_master
WHERE type='table' AND name='conversations'
`).get();
if (!tableCheck) {
console.log('❌ FAIL: conversations table does not exist');
db.close();
return false;
}
console.log('✅ Table exists: conversations');
// Check schema
const schema = db.prepare('PRAGMA table_info(conversations)').all();
const expectedColumns = [
'conversation_id',
'identity',
'intent_history',
'permissions',
'created_at',
'updated_at',
];
const actualColumns = schema.map(col => col.name);
for (const col of expectedColumns) {
if (actualColumns.includes(col)) {
console.log(`✅ Column exists: ${col}`);
} else {
console.log(`❌ FAIL: Missing column: ${col}`);
db.close();
return false;
}
}
db.close();
console.log('\n✅ Phase 1: PASS\n');
return true;
} catch (error) {
console.log(`❌ FAIL: ${error.message}\n`);
return false;
}
}
/**
* Test Phase 2: Verify conversation data persisted
*/
function testConversationPersistence() {
console.log('Phase 2: Conversation Persistence Verification');
console.log('----------------------------------------------');
try {
const db = new Database(DB_PATH);
// Check for 'default' conversation (created on first MCP call)
const conversation = db.prepare(`
SELECT * FROM conversations WHERE conversation_id = ?
`).get('default');
if (!conversation) {
console.log('⚠️ No conversations persisted yet (server not started)');
db.close();
return 'pending';
}
console.log(`✅ Conversation found: ${conversation.conversation_id}`);
// Parse WHO dimension (identity)
const identity = JSON.parse(conversation.identity);
console.log(`✅ WHO (identity): ${identity.toolName} v${identity.version}`);
console.log(` Capabilities: ${identity.capabilities.join(', ')}`);
// Parse WHAT dimension (intent history)
const intentHistory = JSON.parse(conversation.intent_history);
console.log(`✅ WHAT (intent history): ${intentHistory.length} actions recorded`);
if (intentHistory.length > 0) {
const latest = intentHistory[intentHistory.length - 1];
console.log(` Latest: ${latest.action} (${latest.alignment})`);
}
// Parse HOW dimension (permissions)
const permissions = JSON.parse(conversation.permissions);
console.log(`✅ HOW (permissions): ${permissions.length} grants`);
if (permissions.length > 0) {
permissions.forEach(p => {
console.log(` - Level ${p.level}: ${p.scope}`);
});
}
db.close();
console.log('\n✅ Phase 2: PASS\n');
return true;
} catch (error) {
console.log(`❌ FAIL: ${error.message}\n`);
return false;
}
}
/**
* Test Phase 3: Verify state restoration after restart
*/
function testStateRestoration() {
console.log('Phase 3: State Restoration Verification');
console.log('----------------------------------------');
console.log('⚠️ This requires manual testing:');
console.log(' 1. Start MCP server');
console.log(' 2. Make MCP call (e.g., identity query)');
console.log(' 3. Make approval request');
console.log(' 4. Grant approval');
console.log(' 5. Stop server');
console.log(' 6. Restart server');
console.log(' 7. Make same action again');
console.log(' 8. Verify approval persisted (no re-approval needed)\n');
return 'manual';
}
/**
* Main test runner
*/
function runTests() {
const results = {
schema: testDatabaseSchema(),
persistence: testConversationPersistence(),
restoration: testStateRestoration(),
};
console.log('Test Summary');
console.log('============');
console.log(`Schema Verification: ${results.schema === true ? '✅ PASS' : '❌ FAIL'}`);
console.log(`Persistence Verification: ${results.persistence === true ? '✅ PASS' : results.persistence === 'pending' ? '⚠️ PENDING' : '❌ FAIL'}`);
console.log(`Restoration Verification: ${results.restoration === 'manual' ? '⚠️ MANUAL' : results.restoration === true ? '✅ PASS' : '❌ FAIL'}`);
const allPassed = results.schema === true && results.persistence === true;
if (allPassed) {
console.log('\n✅ M3 INFRASTRUCTURE COMPLETE');
console.log(' Conversation state persists to SQLite');
console.log(' WHO/WHAT/HOW dimensions stored correctly');
console.log(' Ready for server restart testing\n');
} else {
console.log('\n❌ M3 INFRASTRUCTURE INCOMPLETE\n');
}
}
// Run tests
runTests();