Implements architectural enforcement of governance rules (inst_016/017/018/079) for all external communications. Publication blocked at API level if violations detected. New Features: - Framework content checker script with pattern matching for prohibited terms - Admin UI displays framework violations with severity indicators - Manual "Check Framework" button for pre-publication validation - API endpoint /api/blog/check-framework for real-time content analysis Governance Rules Added: - inst_078: "ff" trigger for manual framework invocation in conversations - inst_079: Dark patterns prohibition (sovereignty principle) - inst_080: Open source commitment enforcement (community principle) - inst_081: Pluralism principle with indigenous framework recognition Session Management: - Fix session-init.js infinite loop (removed early return after tests) - Add session-closedown.js for comprehensive session handoff - Refactor check-csp-violations.js to prevent parent process exit Framework Services: - Enhanced PluralisticDeliberationOrchestrator with audit logging - Updated all 6 services with consistent initialization patterns - Added framework invocation scripts for blog content validation Files: blog.controller.js:1211-1305, blog.routes.js:77-82, blog-curation.html:61-72, blog-curation.js:320-446 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
216 lines
6.1 KiB
JavaScript
Executable file
216 lines
6.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Framework Blog Content Checker
|
|
* Scans blog posts for governance violations before publication
|
|
*
|
|
* Checks:
|
|
* - inst_016: Fabricated statistics
|
|
* - inst_017: Absolute guarantees
|
|
* - inst_018: Unverified production claims
|
|
* - inst_079: Dark patterns
|
|
* - inst_080: Open source violations
|
|
* - inst_081: Values framework impositions
|
|
*
|
|
* Usage:
|
|
* node scripts/framework-check-blog-content.js --post-id <id>
|
|
* node scripts/framework-check-blog-content.js --content "blog text..."
|
|
*/
|
|
|
|
const mongoose = require('mongoose');
|
|
const path = require('path');
|
|
|
|
function parseArgs() {
|
|
const args = process.argv.slice(2);
|
|
const parsed = {};
|
|
for (let i = 0; i < args.length; i += 2) {
|
|
const key = args[i].replace(/^--/, '');
|
|
const value = args[i + 1];
|
|
parsed[key] = value;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
// Pattern definitions from inst_016/017/018
|
|
const contentPatterns = {
|
|
inst_016_fabricated_stats: {
|
|
patterns: [
|
|
/\b\d+%\s+(?:faster|better|improvement|increase|decrease|reduction|more|less)\b(?!\s*\[NEEDS VERIFICATION\]|\s*\(source:|\s*\[source:)/gi,
|
|
/\b(?:faster|better|improvement)\s+of\s+\d+%\b(?!\s*\[NEEDS VERIFICATION\]|\s*\(source:|\s*\[source:)/gi
|
|
],
|
|
severity: 'HIGH',
|
|
message: 'Statistics require citation or [NEEDS VERIFICATION] marker'
|
|
},
|
|
inst_017_absolute_guarantees: {
|
|
patterns: [
|
|
/\bguarantee(?:s|d|ing)?\b/gi,
|
|
/\b100%\s+(?:secure|safe|reliable|effective)\b/gi,
|
|
/\bcompletely\s+prevents?\b/gi,
|
|
/\bnever\s+fails?\b/gi,
|
|
/\balways\s+works?\b/gi,
|
|
/\beliminates?\s+all\b/gi
|
|
],
|
|
severity: 'HIGH',
|
|
message: 'Absolute guarantees prohibited - use evidence-based language'
|
|
},
|
|
inst_018_unverified_claims: {
|
|
patterns: [
|
|
/\bproduction-ready\b(?!\s+development\s+tool|\s+proof-of-concept)/gi,
|
|
/\bbattle-tested\b/gi,
|
|
/\benterprise-proven\b/gi,
|
|
/\bwidespread\s+adoption\b/gi
|
|
],
|
|
severity: 'MEDIUM',
|
|
message: 'Production claims require evidence (this is proof-of-concept)'
|
|
},
|
|
inst_079_dark_patterns: {
|
|
patterns: [
|
|
/\bclick\s+here\s+now\b/gi,
|
|
/\blimited\s+time\s+offer\b/gi,
|
|
/\bonly\s+\d+\s+spots?\s+left\b/gi,
|
|
/\bact\s+fast\b/gi
|
|
],
|
|
severity: 'MEDIUM',
|
|
message: 'Possible manipulative urgency detected'
|
|
}
|
|
};
|
|
|
|
async function scanContent(text) {
|
|
const violations = [];
|
|
|
|
for (const [ruleId, config] of Object.entries(contentPatterns)) {
|
|
for (const pattern of config.patterns) {
|
|
const matches = text.match(pattern);
|
|
if (matches) {
|
|
matches.forEach(match => {
|
|
violations.push({
|
|
rule: ruleId,
|
|
severity: config.severity,
|
|
match,
|
|
message: config.message,
|
|
position: text.indexOf(match)
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs();
|
|
const { 'post-id': postId, content, title } = args;
|
|
|
|
if (!postId && !content) {
|
|
console.error('Error: --post-id or --content required');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Connect to MongoDB
|
|
try {
|
|
await mongoose.connect('mongodb://localhost:27017/tractatus_dev', {
|
|
serverSelectionTimeoutMS: 2000
|
|
});
|
|
} catch (err) {
|
|
console.error('MongoDB connection failed:', err.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
let postContent = content;
|
|
let postTitle = title || '';
|
|
let postMeta = null;
|
|
|
|
// Fetch from DB if post-id provided
|
|
if (postId) {
|
|
const { ObjectId } = require('mongodb');
|
|
const db = require('../src/utils/db.util');
|
|
const collection = await db.getCollection('blog_posts');
|
|
|
|
const post = await collection.findOne({ _id: new ObjectId(postId) });
|
|
|
|
if (!post) {
|
|
console.error('Error: Post not found');
|
|
await mongoose.disconnect();
|
|
process.exit(1);
|
|
}
|
|
|
|
postContent = post.content;
|
|
postTitle = post.title;
|
|
postMeta = {
|
|
id: postId,
|
|
status: post.status,
|
|
author: post.author
|
|
};
|
|
}
|
|
|
|
// Scan content
|
|
const contentViolations = await scanContent(postContent + ' ' + postTitle);
|
|
|
|
// Invoke framework services
|
|
const BoundaryEnforcer = require('../src/services/BoundaryEnforcer.service');
|
|
const MetacognitiveVerifier = require('../src/services/MetacognitiveVerifier.service');
|
|
const ContextPressureMonitor = require('../src/services/ContextPressureMonitor.service');
|
|
|
|
await BoundaryEnforcer.initialize();
|
|
await MetacognitiveVerifier.initialize();
|
|
await ContextPressureMonitor.initialize('blog-content-check');
|
|
|
|
const sessionId = `blog-check-${Date.now()}`;
|
|
|
|
// BoundaryEnforcer: Check if content makes values claims
|
|
const action = {
|
|
type: 'blog_post_publication',
|
|
description: `Publish blog post: ${postTitle}`,
|
|
content_length: postContent.length,
|
|
has_violations: contentViolations.length > 0
|
|
};
|
|
|
|
const context = {
|
|
sessionId,
|
|
tool: 'blog_publication',
|
|
post_id: postMeta?.id,
|
|
author_type: postMeta?.author?.type
|
|
};
|
|
|
|
const boundaryResult = BoundaryEnforcer.enforce(action, context);
|
|
|
|
// MetacognitiveVerifier: Check reasoning quality in arguments
|
|
const reasoning = postContent.substring(0, 500);
|
|
const verifyResult = MetacognitiveVerifier.verify(action, reasoning, context);
|
|
|
|
// Wait for async audit logging
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
await mongoose.disconnect();
|
|
|
|
// Output results
|
|
const output = {
|
|
success: contentViolations.length === 0,
|
|
post: postMeta || { content_length: postContent.length },
|
|
violations: contentViolations,
|
|
framework_checks: {
|
|
boundary: {
|
|
allowed: boundaryResult.allowed,
|
|
message: boundaryResult.message
|
|
},
|
|
verification: {
|
|
decision: verifyResult.decision,
|
|
confidence: verifyResult.confidence
|
|
}
|
|
},
|
|
summary: {
|
|
total_violations: contentViolations.length,
|
|
high_severity: contentViolations.filter(v => v.severity === 'HIGH').length,
|
|
publication_recommended: contentViolations.length === 0 && boundaryResult.allowed
|
|
}
|
|
};
|
|
|
|
console.log(JSON.stringify(output, null, 2));
|
|
process.exit(output.success ? 0 : 1);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Fatal error:', err.message);
|
|
process.exit(1);
|
|
});
|