Problem: - Blog publishing has governance checks (inst_016/017/018/079) - Media responses and templates had NO checks - Inconsistent: same risks, different enforcement Solution - Unified Framework Enforcement: 1. Created ContentGovernanceChecker.service.js (shared service) 2. Enforced in media responses (blocks at API level) 3. Enforced in response templates (scans on create) 4. Scanner for existing templates Impact: ✅ Blog posts: Framework checks (existing) ✅ Media inquiry responses: Framework checks (NEW) ✅ Response templates: Framework checks (NEW) ✅ Future: Newsletter content ready for checks Files Changed: 1. src/services/ContentGovernanceChecker.service.js (NEW) - Unified content scanner for all external communications - Checks: inst_016 (stats), inst_017 (guarantees), inst_018 (claims), inst_079 (dark patterns) - Returns detailed violation reports with context 2. src/controllers/media.controller.js - Added governance check in respondToInquiry() - Blocks responses with violations (400 error) - Logs violations with media outlet context 3. src/models/ResponseTemplate.model.js - Added governance check in create() - Stores check results in template record - Prevents violating templates from being created 4. scripts/scan-response-templates.js (NEW) - Scans all existing templates for violations - Displays detailed violation reports - --fix flag to mark violating templates as inactive Testing: ✅ ContentGovernanceChecker: All pattern tests pass ✅ Clean content: Passes validation ✅ Fabricated stats: Detected (inst_016) ✅ Absolute guarantees: Detected (inst_017) ✅ Dark patterns: Detected (inst_079) ✅ Template scanner: Works (0 templates in DB) Enforcement Points: - Blog posts: publishPost() → blocked at API - Media responses: respondToInquiry() → blocked at API - Templates: create() → checked before insertion - Newsletter: ready for future implementation Architectural Consistency: If blog needs governance, ALL external communications need governance. References: - inst_016: No fabricated statistics - inst_017: No absolute guarantees - inst_018: No unverified production claims - inst_079: No dark patterns/manipulative urgency - inst_063: External communications consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
5.1 KiB
JavaScript
Executable file
155 lines
5.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Scan Response Templates for Governance Violations
|
|
* Checks all existing templates for inst_016/017/018/079 violations
|
|
*
|
|
* Usage:
|
|
* node scripts/scan-response-templates.js
|
|
* node scripts/scan-response-templates.js --fix # Mark violating templates as inactive
|
|
*/
|
|
|
|
const { getCollection } = require('../src/utils/db.util');
|
|
const ContentGovernanceChecker = require('../src/services/ContentGovernanceChecker.service');
|
|
|
|
async function main() {
|
|
try {
|
|
const args = process.argv.slice(2);
|
|
const fixMode = args.includes('--fix');
|
|
|
|
console.log('╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ Response Template Governance Scanner ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝');
|
|
console.log('');
|
|
|
|
const collection = await getCollection('response_templates');
|
|
|
|
// Get all active templates
|
|
const templates = await collection.find({ active: true }).toArray();
|
|
|
|
console.log(`Found ${templates.length} active templates to scan\n`);
|
|
|
|
const results = {
|
|
scanned: 0,
|
|
passed: 0,
|
|
failed: 0,
|
|
violations: []
|
|
};
|
|
|
|
for (const template of templates) {
|
|
results.scanned++;
|
|
|
|
// Scan template content
|
|
const fullText = [template.subject, template.content].filter(Boolean).join('\n');
|
|
const check = await ContentGovernanceChecker.scanContent(fullText, {
|
|
type: 'response_template',
|
|
context: {
|
|
template_id: template._id.toString(),
|
|
name: template.name,
|
|
category: template.category
|
|
}
|
|
});
|
|
|
|
if (check.success) {
|
|
results.passed++;
|
|
console.log(`✅ ${template.name} (${template.category})`);
|
|
} else {
|
|
results.failed++;
|
|
console.log(`\n❌ ${template.name} (${template.category})`);
|
|
console.log(` ID: ${template._id}`);
|
|
console.log(` Violations: ${check.violations.length}`);
|
|
|
|
check.violations.forEach((v, idx) => {
|
|
console.log(` ${idx + 1}. [${v.severity}] ${v.rule}`);
|
|
console.log(` "${v.match}"`);
|
|
console.log(` ${v.message}`);
|
|
});
|
|
|
|
results.violations.push({
|
|
template_id: template._id,
|
|
name: template.name,
|
|
category: template.category,
|
|
violations: check.violations
|
|
});
|
|
|
|
// Update template with governance check results
|
|
await collection.updateOne(
|
|
{ _id: template._id },
|
|
{
|
|
$set: {
|
|
'governance_check.passed': false,
|
|
'governance_check.scanned_at': check.scannedAt,
|
|
'governance_check.violations': check.violations,
|
|
'governance_check.summary': check.summary,
|
|
updated_at: new Date()
|
|
}
|
|
}
|
|
);
|
|
|
|
// Mark as inactive if in fix mode
|
|
if (fixMode) {
|
|
await collection.updateOne(
|
|
{ _id: template._id },
|
|
{
|
|
$set: {
|
|
active: false,
|
|
deactivated_reason: 'Governance violations detected',
|
|
deactivated_at: new Date()
|
|
}
|
|
}
|
|
);
|
|
console.log(` 🔒 Template marked as inactive`);
|
|
}
|
|
|
|
console.log('');
|
|
}
|
|
}
|
|
|
|
// Summary
|
|
console.log('');
|
|
console.log('╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ Scan Summary ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝');
|
|
console.log('');
|
|
console.log(`Total Scanned: ${results.scanned}`);
|
|
console.log(`✅ Passed: ${results.passed}`);
|
|
console.log(`❌ Failed: ${results.failed}`);
|
|
console.log('');
|
|
|
|
if (results.failed > 0) {
|
|
console.log('Violations by Rule:');
|
|
const violationsByRule = {};
|
|
results.violations.forEach(v => {
|
|
v.violations.forEach(violation => {
|
|
violationsByRule[violation.instruction] = (violationsByRule[violation.instruction] || 0) + 1;
|
|
});
|
|
});
|
|
|
|
Object.entries(violationsByRule).forEach(([rule, count]) => {
|
|
console.log(` • ${rule}: ${count}`);
|
|
});
|
|
|
|
console.log('');
|
|
|
|
if (fixMode) {
|
|
console.log(`🔒 ${results.failed} violating template(s) marked as inactive`);
|
|
} else {
|
|
console.log('💡 Run with --fix to mark violating templates as inactive');
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
process.exit(results.failed > 0 ? 1 : 0);
|
|
|
|
} catch (error) {
|
|
console.error('Error scanning templates:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = { main };
|