- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
433 lines
13 KiB
JavaScript
Executable file
433 lines
13 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Framework Enforcement Test Suite
|
||
*
|
||
* Systematically tests all framework enforcement mechanisms to verify
|
||
* they prevent governance violations as designed.
|
||
*
|
||
* Tests:
|
||
* 1. Bash write redirect blocking (inst_064)
|
||
* 2. Deployment directory structure (inst_025)
|
||
* 3. CrossReferenceValidator integration
|
||
* 4. Token checkpoint monitoring
|
||
* 5. Instruction database integrity
|
||
*
|
||
* Copyright 2025 Tractatus Project
|
||
* Licensed under Apache License 2.0
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { execSync } = require('child_process');
|
||
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
bright: '\x1b[1m',
|
||
green: '\x1b[32m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
red: '\x1b[31m',
|
||
cyan: '\x1b[36m'
|
||
};
|
||
|
||
function log(message, color = 'reset') {
|
||
console.log(`${colors[color]}${message}${colors.reset}`);
|
||
}
|
||
|
||
function header(message) {
|
||
console.log('');
|
||
log('═'.repeat(70), 'cyan');
|
||
log(` ${message}`, 'bright');
|
||
log('═'.repeat(70), 'cyan');
|
||
console.log('');
|
||
}
|
||
|
||
function section(message) {
|
||
console.log('');
|
||
log(`▶ ${message}`, 'blue');
|
||
}
|
||
|
||
function success(message) {
|
||
log(` ✓ ${message}`, 'green');
|
||
}
|
||
|
||
function failure(message) {
|
||
log(` ✗ ${message}`, 'red');
|
||
}
|
||
|
||
function info(message) {
|
||
log(` ℹ ${message}`, 'cyan');
|
||
}
|
||
|
||
const testResults = {
|
||
passed: 0,
|
||
failed: 0,
|
||
skipped: 0,
|
||
tests: []
|
||
};
|
||
|
||
function recordTest(name, passed, reason = '') {
|
||
testResults.tests.push({ name, passed, reason });
|
||
if (passed) {
|
||
testResults.passed++;
|
||
success(`PASS: ${name}`);
|
||
} else {
|
||
testResults.failed++;
|
||
failure(`FAIL: ${name}`);
|
||
if (reason) {
|
||
log(` Reason: ${reason}`, 'yellow');
|
||
}
|
||
}
|
||
}
|
||
|
||
function skipTest(name, reason) {
|
||
testResults.tests.push({ name, passed: null, reason });
|
||
testResults.skipped++;
|
||
log(` ⊘ SKIP: ${name}`, 'yellow');
|
||
log(` Reason: ${reason}`, 'yellow');
|
||
}
|
||
|
||
// Test helper: Run bash validator hook
|
||
function testBashValidator(command, shouldBlock) {
|
||
const hookScript = path.join(__dirname, 'hook-validators/validate-bash-command.js');
|
||
|
||
if (!fs.existsSync(hookScript)) {
|
||
return { error: 'Bash validator not found' };
|
||
}
|
||
|
||
try {
|
||
// Create hook input
|
||
const hookInput = JSON.stringify({
|
||
tool_name: 'Bash',
|
||
tool_input: { command }
|
||
});
|
||
|
||
// Run validator
|
||
const result = execSync(`echo '${hookInput}' | node ${hookScript}`, {
|
||
encoding: 'utf8',
|
||
stdio: 'pipe'
|
||
});
|
||
|
||
return { blocked: false, output: result };
|
||
} catch (err) {
|
||
// Non-zero exit = blocked
|
||
return { blocked: true, output: err.stdout || err.stderr };
|
||
}
|
||
}
|
||
|
||
header('Framework Enforcement Test Suite');
|
||
|
||
// TEST SUITE 1: Bash Write Redirect Blocking
|
||
section('1. Bash Write Redirect Blocking (inst_064)');
|
||
|
||
info('Testing bash commands that should be BLOCKED...');
|
||
|
||
const blockedCommands = [
|
||
{ cmd: 'cat > /tmp/test.txt', desc: 'cat > file' },
|
||
{ cmd: 'cat >> /tmp/test.txt', desc: 'cat >> file' },
|
||
{ cmd: 'cat > /tmp/test.txt << EOF\ntest\nEOF', desc: 'cat heredoc' },
|
||
{ cmd: 'echo "test" > /tmp/test.txt', desc: 'echo > file' },
|
||
{ cmd: 'echo "test" >> /tmp/test.txt', desc: 'echo >> file' },
|
||
{ cmd: 'printf "test" > /tmp/test.txt', desc: 'printf > file' },
|
||
{ cmd: 'tee /tmp/test.txt', desc: 'tee file' }
|
||
];
|
||
|
||
blockedCommands.forEach(({ cmd, desc }) => {
|
||
const result = testBashValidator(cmd, true);
|
||
|
||
if (result.error) {
|
||
skipTest(`Block ${desc}`, result.error);
|
||
} else if (result.blocked) {
|
||
recordTest(`Block ${desc}`, true);
|
||
} else {
|
||
recordTest(`Block ${desc}`, false, 'Command was not blocked but should have been');
|
||
}
|
||
});
|
||
|
||
info('Testing bash commands that should be ALLOWED...');
|
||
|
||
const allowedCommands = [
|
||
{ cmd: 'ls -la', desc: 'ls command' },
|
||
{ cmd: 'git status', desc: 'git status' },
|
||
{ cmd: 'echo "test" > /dev/null', desc: 'redirect to /dev/null' },
|
||
{ cmd: 'npm test 2>&1', desc: 'stderr redirect' },
|
||
{ cmd: 'curl https://example.com', desc: 'curl command' }
|
||
];
|
||
|
||
allowedCommands.forEach(({ cmd, desc }) => {
|
||
const result = testBashValidator(cmd, false);
|
||
|
||
if (result.error) {
|
||
skipTest(`Allow ${desc}`, result.error);
|
||
} else if (!result.blocked) {
|
||
recordTest(`Allow ${desc}`, true);
|
||
} else {
|
||
recordTest(`Allow ${desc}`, false, 'Command was blocked but should have been allowed');
|
||
}
|
||
});
|
||
|
||
// TEST SUITE 2: Deployment Pattern Validation
|
||
section('2. Deployment Pattern Validation (inst_025)');
|
||
|
||
info('Testing rsync commands...');
|
||
|
||
// Test directory flattening detection
|
||
const rsyncFlatten = 'rsync -avz /path/to/file1.txt /path/to/sub/file2.txt ubuntu@server:/var/www/';
|
||
const rsyncResult = testBashValidator(rsyncFlatten, true);
|
||
|
||
if (rsyncResult.error) {
|
||
skipTest('Detect directory flattening', rsyncResult.error);
|
||
} else if (rsyncResult.blocked) {
|
||
recordTest('Detect directory flattening', true);
|
||
} else {
|
||
recordTest('Detect directory flattening', false, 'Directory flattening not detected');
|
||
}
|
||
|
||
// Test single file rsync (should pass)
|
||
const rsyncSingle = 'rsync -avz /path/to/file.txt ubuntu@server:/var/www/';
|
||
const rsyncSingleResult = testBashValidator(rsyncSingle, false);
|
||
|
||
if (rsyncSingleResult.error) {
|
||
skipTest('Allow single-file rsync', rsyncSingleResult.error);
|
||
} else if (!rsyncSingleResult.blocked) {
|
||
recordTest('Allow single-file rsync', true);
|
||
} else {
|
||
recordTest('Allow single-file rsync', false, 'Single file rsync was blocked');
|
||
}
|
||
|
||
// TEST SUITE 3: Instruction Database Integrity
|
||
section('3. Instruction Database Integrity');
|
||
|
||
const instructionPath = path.join(__dirname, '../.claude/instruction-history.json');
|
||
|
||
try {
|
||
const history = JSON.parse(fs.readFileSync(instructionPath, 'utf8'));
|
||
|
||
// Test 1: Active instruction count
|
||
const active = history.instructions.filter(i => i.active);
|
||
recordTest(
|
||
'Active instruction count <50',
|
||
active.length < 50,
|
||
active.length >= 50 ? `Too many active: ${active.length}` : ''
|
||
);
|
||
|
||
// Test 2: HIGH persistence ratio
|
||
const highPersistence = active.filter(i => i.persistence === 'HIGH');
|
||
const ratio = (highPersistence.length / active.length) * 100;
|
||
recordTest(
|
||
'HIGH persistence ratio >90%',
|
||
ratio > 90,
|
||
ratio <= 90 ? `Ratio too low: ${ratio.toFixed(1)}%` : ''
|
||
);
|
||
|
||
// Test 3: No duplicate IDs
|
||
const ids = history.instructions.map(i => i.id);
|
||
const uniqueIds = new Set(ids);
|
||
recordTest(
|
||
'No duplicate instruction IDs',
|
||
ids.length === uniqueIds.size,
|
||
ids.length !== uniqueIds.size ? 'Duplicate IDs found' : ''
|
||
);
|
||
|
||
// Test 4: All active instructions have required fields
|
||
const requiredFields = ['id', 'text', 'quadrant', 'persistence', 'temporal_scope'];
|
||
const incomplete = active.filter(inst =>
|
||
requiredFields.some(field => !inst[field])
|
||
);
|
||
recordTest(
|
||
'All active instructions complete',
|
||
incomplete.length === 0,
|
||
incomplete.length > 0 ? `${incomplete.length} incomplete instructions` : ''
|
||
);
|
||
|
||
// Test 5: inst_075 exists and active (token checkpoints)
|
||
const inst_075 = history.instructions.find(i => i.id === 'inst_075' && i.active);
|
||
recordTest(
|
||
'inst_075 (token checkpoints) active',
|
||
!!inst_075,
|
||
!inst_075 ? 'inst_075 not found or inactive' : ''
|
||
);
|
||
|
||
// Test 6: inst_024_CONSOLIDATED exists (series consolidation)
|
||
const inst_024_consolidated = history.instructions.find(i => i.id === 'inst_024_CONSOLIDATED' && i.active);
|
||
recordTest(
|
||
'inst_024_CONSOLIDATED active',
|
||
!!inst_024_consolidated,
|
||
!inst_024_consolidated ? 'Consolidation not found' : ''
|
||
);
|
||
|
||
} catch (err) {
|
||
skipTest('Instruction database tests', `Error reading database: ${err.message}`);
|
||
}
|
||
|
||
// TEST SUITE 4: Token Checkpoint Monitoring
|
||
section('4. Token Checkpoint Monitoring');
|
||
|
||
const checkpointPath = path.join(__dirname, '../.claude/token-checkpoints.json');
|
||
|
||
try {
|
||
const checkpoints = JSON.parse(fs.readFileSync(checkpointPath, 'utf8'));
|
||
|
||
// Test 1: Checkpoints defined
|
||
recordTest(
|
||
'Token checkpoints defined',
|
||
checkpoints.checkpoints && checkpoints.checkpoints.length === 3,
|
||
checkpoints.checkpoints?.length !== 3 ? 'Unexpected checkpoint count' : ''
|
||
);
|
||
|
||
// Test 2: Checkpoint thresholds correct
|
||
const expectedThresholds = [50000, 100000, 150000];
|
||
const actualThresholds = checkpoints.checkpoints.map(c => c.tokens);
|
||
const thresholdsMatch = expectedThresholds.every((t, i) => t === actualThresholds[i]);
|
||
recordTest(
|
||
'Checkpoint thresholds correct (50k, 100k, 150k)',
|
||
thresholdsMatch,
|
||
!thresholdsMatch ? `Actual: ${actualThresholds.join(', ')}` : ''
|
||
);
|
||
|
||
// Test 3: Next checkpoint tracking
|
||
recordTest(
|
||
'Next checkpoint tracked',
|
||
checkpoints.next_checkpoint !== undefined,
|
||
!checkpoints.next_checkpoint ? 'next_checkpoint field missing' : ''
|
||
);
|
||
|
||
// Test 4: Checkpoint script exists
|
||
const checkpointScript = path.join(__dirname, 'check-token-checkpoint.js');
|
||
recordTest(
|
||
'Checkpoint monitor script exists',
|
||
fs.existsSync(checkpointScript),
|
||
!fs.existsSync(checkpointScript) ? 'Script not found' : ''
|
||
);
|
||
|
||
} catch (err) {
|
||
skipTest('Token checkpoint tests', `Error reading checkpoints: ${err.message}`);
|
||
}
|
||
|
||
// TEST SUITE 5: Framework Component Files
|
||
section('5. Framework Component Files');
|
||
|
||
const componentFiles = [
|
||
'src/services/ContextPressureMonitor.service.js',
|
||
'src/services/InstructionPersistenceClassifier.service.js',
|
||
'src/services/CrossReferenceValidator.service.js',
|
||
'src/services/BoundaryEnforcer.service.js',
|
||
'src/services/MetacognitiveVerifier.service.js',
|
||
'src/services/PluralisticDeliberationOrchestrator.service.js'
|
||
];
|
||
|
||
componentFiles.forEach(file => {
|
||
const filepath = path.join(__dirname, '..', file);
|
||
const exists = fs.existsSync(filepath);
|
||
recordTest(
|
||
`${path.basename(file, '.service.js')} exists`,
|
||
exists,
|
||
!exists ? 'File not found' : ''
|
||
);
|
||
});
|
||
|
||
// TEST SUITE 6: Hook Validators
|
||
section('6. Hook Validator Scripts');
|
||
|
||
const hookFiles = [
|
||
'scripts/hook-validators/validate-file-edit.js',
|
||
'scripts/hook-validators/validate-file-write.js',
|
||
'scripts/hook-validators/validate-bash-command.js'
|
||
];
|
||
|
||
hookFiles.forEach(file => {
|
||
const filepath = path.join(__dirname, '..', file);
|
||
const exists = fs.existsSync(filepath);
|
||
|
||
if (exists) {
|
||
// Check if executable
|
||
try {
|
||
fs.accessSync(filepath, fs.constants.X_OK);
|
||
recordTest(`${path.basename(file)} executable`, true);
|
||
} catch (err) {
|
||
recordTest(`${path.basename(file)} executable`, false, 'Not executable');
|
||
}
|
||
} else {
|
||
recordTest(`${path.basename(file)} exists`, false, 'File not found');
|
||
}
|
||
});
|
||
|
||
// TEST SUITE 7: Settings Configuration
|
||
section('7. Settings Configuration');
|
||
|
||
const settingsPath = path.join(__dirname, '../.claude/settings.local.json');
|
||
|
||
try {
|
||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||
|
||
// Test 1: Hooks defined
|
||
recordTest(
|
||
'PreToolUse hooks defined',
|
||
settings.hooks && settings.hooks.PreToolUse,
|
||
!settings.hooks?.PreToolUse ? 'PreToolUse hooks missing' : ''
|
||
);
|
||
|
||
// Test 2: Bash hook configured
|
||
const bashHook = settings.hooks?.PreToolUse?.find(h => h.matcher === 'Bash');
|
||
recordTest(
|
||
'Bash validator hook configured',
|
||
!!bashHook,
|
||
!bashHook ? 'Bash hook not found in settings' : ''
|
||
);
|
||
|
||
// Test 3: Edit hook configured
|
||
const editHook = settings.hooks?.PreToolUse?.find(h => h.matcher === 'Edit');
|
||
recordTest(
|
||
'Edit validator hook configured',
|
||
!!editHook,
|
||
!editHook ? 'Edit hook not found in settings' : ''
|
||
);
|
||
|
||
// Test 4: Write hook configured
|
||
const writeHook = settings.hooks?.PreToolUse?.find(h => h.matcher === 'Write');
|
||
recordTest(
|
||
'Write validator hook configured',
|
||
!!writeHook,
|
||
!writeHook ? 'Write hook not found in settings' : ''
|
||
);
|
||
|
||
} catch (err) {
|
||
skipTest('Settings configuration tests', `Error reading settings: ${err.message}`);
|
||
}
|
||
|
||
// FINAL SUMMARY
|
||
header('Test Summary');
|
||
|
||
const total = testResults.passed + testResults.failed + testResults.skipped;
|
||
const passRate = total > 0 ? ((testResults.passed / total) * 100).toFixed(1) : 0;
|
||
|
||
console.log('');
|
||
log(` Total Tests: ${total}`, 'cyan');
|
||
log(` Passed: ${testResults.passed}`, 'green');
|
||
log(` Failed: ${testResults.failed}`, testResults.failed === 0 ? 'green' : 'red');
|
||
log(` Skipped: ${testResults.skipped}`, 'yellow');
|
||
log(` Pass Rate: ${passRate}%`, parseFloat(passRate) >= 90 ? 'green' : parseFloat(passRate) >= 70 ? 'yellow' : 'red');
|
||
|
||
console.log('');
|
||
|
||
if (testResults.failed === 0 && testResults.skipped === 0) {
|
||
success('✓ ALL TESTS PASSED - Framework enforcement working perfectly!');
|
||
} else if (testResults.failed === 0) {
|
||
log(` ✓ All executed tests passed (${testResults.skipped} skipped)`, 'yellow');
|
||
} else {
|
||
failure(`✗ ${testResults.failed} test(s) failed - review enforcement mechanisms`);
|
||
console.log('');
|
||
log(' Failed tests:', 'red');
|
||
testResults.tests
|
||
.filter(t => t.passed === false)
|
||
.forEach(t => {
|
||
log(` - ${t.name}: ${t.reason}`, 'red');
|
||
});
|
||
}
|
||
|
||
console.log('');
|
||
log('═'.repeat(70), 'cyan');
|
||
|
||
// Exit code based on results
|
||
process.exit(testResults.failed === 0 ? 0 : 1);
|