diff --git a/docs/ENFORCEMENT_AUDIT.md b/docs/ENFORCEMENT_AUDIT.md new file mode 100644 index 00000000..3929f352 --- /dev/null +++ b/docs/ENFORCEMENT_AUDIT.md @@ -0,0 +1,150 @@ +# Governance Enforcement Audit + +## Problem Statement +Claude missed "ffs" trigger (inst_082) and token checkpoint monitoring (inst_075) despite both being active HIGH persistence instructions. This reveals a **voluntary compliance failure pattern**. + +## Root Cause +Instructions marked MANDATORY but lacking architectural enforcement mechanisms rely on voluntary compliance, which fails under cognitive load or context pressure. + +## Enforcement Analysis: 54 HIGH Persistence Instructions + +### ✅ ALREADY ENFORCED (8 instructions) +These have architectural hooks/scripts that BLOCK or ALERT automatically: + +1. **inst_038**: Pre-action checks → `.claude/hooks/framework-audit-hook.js` +2. **inst_065**: Session initialization → `scripts/session-init.js` (mandatory) +3. **inst_070**: Secret detection → Git pre-commit hook (if installed) +4. **inst_071**: Pre-deployment checklist → Partially automated +5. **inst_075**: Token checkpoints → `.claude/hooks/check-token-checkpoint.js` ✨ NEW +6. **inst_077**: Session closedown → `scripts/session-closedown.js` +7. **inst_078**: "ff" trigger → `.claude/hooks/trigger-word-checker.js` ✨ NEW +8. **inst_082**: "ffs" trigger → `.claude/hooks/trigger-word-checker.js` ✨ NEW + +### ⚠️ NEEDS ENFORCEMENT (12 critical instructions) + +#### **HIGH PRIORITY - Implement Now** + +**inst_027**: NEVER modify instruction-history.json without approval +- **Risk**: HIGH - Could corrupt governance system +- **Enforcement**: PreToolUse hook to block Write/Edit on .claude/instruction-history.json +- **Implementation**: Add to existing framework-audit-hook.js + +**inst_064**: Framework components MUST be actively used +- **Risk**: HIGH - Framework fade = governance collapse +- **Enforcement**: session-state.json tracking + periodic verification +- **Implementation**: Add activity report to session-init output + +**inst_066**: Conventional commit format required +- **Risk**: MEDIUM - Makes git history hard to parse +- **Enforcement**: Git commit-msg hook to validate format +- **Implementation**: Create .git/hooks/commit-msg + +**inst_068**: Test requirements before commits/deployments +- **Risk**: HIGH - Broken code reaches production +- **Enforcement**: Pre-commit hook to run relevant tests +- **Implementation**: Add to git pre-commit hook + +**inst_023**: Background process management required +- **Risk**: MEDIUM - Orphaned processes waste resources +- **Enforcement**: Track spawned processes in session-state.json +- **Implementation**: Hook on Bash tool with run_in_background=true + +**inst_046**: Security events MUST be logged +- **Risk**: HIGH - Undetected attacks +- **Enforcement**: Audit log verification in pre-deployment checklist +- **Implementation**: Add automated check to deployment script + +#### **MEDIUM PRIORITY - Implement Later** + +**inst_040**: "all" command verification (needs workflow enforcement) +**inst_056**: Batch operation validation (needs workflow tracking) +**inst_019**: ContextPressureMonitor must account for total context (already doing this) + +### 📋 INFORMATIONAL/GUIDELINES (34 instructions) +These provide context, standards, or principles that don't require automated enforcement: + +- **SYSTEM quadrant** (9): Database ports, CSP rules, security standards, credential handling +- **STRATEGIC quadrant** (17): Quality standards, approval requirements, values principles, architectural guidance +- **OPERATIONAL quadrant** (8): Best practices, deployment procedures, file management + +These are enforced through: +- Code review +- Human approval gates (inst_005) +- Documentation and training +- Framework service checks (BoundaryEnforcer, CrossReferenceValidator) + +## Enforcement Tiers + +### Tier 1: Architectural Blocks (MANDATORY) +Cannot proceed without compliance. Examples: +- session-init.js blocks without local server +- Pre-action hooks block dangerous operations +- Secret detection prevents commits with credentials + +### Tier 2: Architectural Alerts (CRITICAL) +Warns immediately but allows override with explicit approval. Examples: +- Token checkpoint warnings +- Trigger word reminders +- CSP compliance notifications + +### Tier 3: Audit Trails (MONITORING) +Logs violations for review but doesn't block. Examples: +- Framework component usage tracking +- Security event logging +- Background process inventory + +### Tier 4: Human Gates (APPROVAL) +Requires explicit human decision. Examples: +- Architectural changes (inst_005) +- Values-sensitive content (BoundaryEnforcer) +- Production deployments (inst_071) + +## Implementation Priority Queue + +### Immediate (This Session) +1. ✅ Token checkpoint monitoring hook +2. ✅ Trigger word detection hook +3. ⏳ instruction-history.json write protection +4. ⏳ Framework activity verification +5. ⏳ Conventional commit format hook + +### Next Session +6. Test requirement enforcement (pre-commit) +7. Background process tracking +8. Security event audit verification +9. Batch operation workflow tracking + +### Ongoing +10. Continuous monitoring of framework fade +11. Periodic enforcement effectiveness review +12. New instruction classification and enforcement design + +## Meta-Enforcement System + +Create `scripts/audit-enforcement.js` that: +1. Scans all HIGH persistence instructions +2. Identifies those with MUST/NEVER/MANDATORY language +3. Checks if corresponding enforcement mechanism exists +4. Alerts when new imperative instructions lack enforcement +5. Generates enforcement gap report + +Run automatically: +- At session start (via session-init.js) +- When instruction-history.json changes +- On demand via "ffs" or dedicated trigger + +## Philosophy +**"If it's MANDATORY, it must be ENFORCED architecturally, not documented."** + +Voluntary compliance works under ideal conditions but fails under: +- Cognitive load (complex multi-step tasks) +- Context pressure (approaching token limits) +- Auto-compact recovery (instruction injection timing) +- Session handoffs (knowledge discontinuity) + +Architectural enforcement (hooks, scripts, blocks) is reliable regardless of conditions. + +--- +**Created**: 2025-10-25 +**Trigger**: Missed "ffs" code word recognition +**Outcome**: Systematic enforcement architecture design diff --git a/scripts/audit-enforcement.js b/scripts/audit-enforcement.js new file mode 100755 index 00000000..f9803bb7 --- /dev/null +++ b/scripts/audit-enforcement.js @@ -0,0 +1,135 @@ +#!/usr/bin/env node +/** + * Meta-Enforcement Monitoring System + * Scans instructions for MUST/NEVER/MANDATORY language and verifies enforcement + * + * Per ENFORCEMENT_AUDIT.md: "If it's MANDATORY, it must be ENFORCED architecturally" + */ + +const fs = require('fs'); +const path = require('path'); + +const INSTRUCTION_FILE = path.join(__dirname, '../.claude/instruction-history.json'); + +// Known enforcement mechanisms +const ENFORCEMENT_MAP = { + inst_008: ['.git/hooks/pre-commit', 'scripts/check-csp-violations.js'], + inst_023: ['scripts/track-background-process.js', 'scripts/session-init.js', 'scripts/session-closedown.js'], + inst_027: ['.claude/hooks/framework-audit-hook.js'], + inst_038: ['.claude/hooks/framework-audit-hook.js'], + inst_046: ['scripts/verify-security-logging.js'], + inst_064: ['scripts/session-init.js'], // Framework activity verification + inst_065: ['scripts/session-init.js'], + inst_066: ['.git/hooks/commit-msg'], + inst_068: ['.git/hooks/pre-commit'], + inst_070: ['.git/hooks/pre-commit'], + inst_071: ['scripts/deploy.sh'], + inst_075: ['.claude/hooks/check-token-checkpoint.js'], + inst_077: ['scripts/session-closedown.js'], + inst_078: ['.claude/hooks/trigger-word-checker.js'], + inst_082: ['.claude/hooks/trigger-word-checker.js'] +}; + +function loadInstructions() { + const data = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8')); + return data.instructions.filter(i => i.active); +} + +function hasImperativeLanguage(text) { + const imperatives = [ + /\bMUST\b/i, + /\bNEVER\b/i, + /\bMANDATORY\b/i, + /\bREQUIRED\b/i, + /\bBLOCK(S|ED)?\b/i, + /\bCRITICAL\b.*\bFAILURE\b/i, + /\bALWAYS\b/i, + /\bSHOULD NOT\b/i + ]; + + return imperatives.some(pattern => pattern.test(text)); +} + +function checkEnforcementExists(instId, enforcementPaths) { + const missing = []; + const exists = []; + + enforcementPaths.forEach(p => { + const fullPath = path.join(__dirname, '..', p); + if (fs.existsSync(fullPath)) { + exists.push(p); + } else { + missing.push(p); + } + }); + + return { exists, missing }; +} + +function main() { + console.log('\n🔍 Meta-Enforcement Audit\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + const instructions = loadInstructions(); + const highPersistence = instructions.filter(i => i.persistence === 'HIGH'); + + console.log(`Total active instructions: ${instructions.length}`); + console.log(`HIGH persistence instructions: ${highPersistence.length}\n`); + + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + const imperativeInstructions = highPersistence.filter(i => hasImperativeLanguage(i.text)); + + console.log(`Instructions with imperative language: ${imperativeInstructions.length}\n`); + + let enforced = 0; + let unenforced = 0; + const gaps = []; + + imperativeInstructions.forEach(inst => { + const hasEnforcement = ENFORCEMENT_MAP[inst.id]; + + if (hasEnforcement) { + const check = checkEnforcementExists(inst.id, hasEnforcement); + + if (check.missing.length === 0) { + console.log(`✅ ${inst.id}: ENFORCED`); + console.log(` Mechanisms: ${check.exists.join(', ')}`); + enforced++; + } else { + console.log(`⚠️ ${inst.id}: PARTIALLY ENFORCED`); + console.log(` Exists: ${check.exists.join(', ')}`); + console.log(` Missing: ${check.missing.join(', ')}`); + gaps.push({ id: inst.id, missing: check.missing, text: inst.text.substring(0, 80) + '...' }); + unenforced++; + } + } else { + console.log(`❌ ${inst.id}: NO ENFORCEMENT`); + console.log(` Text: ${inst.text.substring(0, 80)}...`); + gaps.push({ id: inst.id, missing: ['No enforcement mechanism defined'], text: inst.text.substring(0, 80) + '...' }); + unenforced++; + } + console.log(''); + }); + + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + console.log('Summary:\n'); + console.log(` Imperative instructions: ${imperativeInstructions.length}`); + console.log(` Enforced: ${enforced} (${Math.round(enforced/imperativeInstructions.length*100)}%)`); + console.log(` Unenforced/Partial: ${unenforced} (${Math.round(unenforced/imperativeInstructions.length*100)}%)`); + + if (gaps.length > 0) { + console.log(`\n⚠️ ${gaps.length} enforcement gap(s) detected\n`); + console.log('Gaps should be addressed to prevent voluntary compliance failures.\n'); + } else { + console.log('\n✅ All imperative instructions have enforcement mechanisms!\n'); + } + + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + if (gaps.length > 0) { + process.exit(1); // Exit with error if gaps exist + } +} + +main(); diff --git a/scripts/session-closedown.js b/scripts/session-closedown.js index ebb13920..ec8bde8f 100755 --- a/scripts/session-closedown.js +++ b/scripts/session-closedown.js @@ -136,11 +136,27 @@ async function cleanup() { sync_verified: false }; - // 1. Kill ALL background processes (including Claude Code shells) + // 1. Kill tracked background processes (inst_023) try { - info('Checking for ALL background processes...'); + info('Cleaning up tracked background processes (inst_023)...'); - // Get all node/npm processes + // Use the new tracking script + try { + const cleanupOutput = execSync('node scripts/track-background-process.js cleanup', { + encoding: 'utf8', + stdio: 'pipe' + }); + const killedMatch = cleanupOutput.match(/(\d+) killed/); + if (killedMatch) { + cleanupResults.processes_killed = parseInt(killedMatch[1]); + success(`Killed ${cleanupResults.processes_killed} tracked background process(es)`); + } + } catch (trackErr) { + warning(` Tracked process cleanup failed: ${trackErr.message}`); + } + + // Also check for untracked node/npm processes + info('Checking for untracked background processes...'); let processes = []; try { const psOutput = execSync('ps aux | grep -E "npm|jest|node" | grep -v grep', { encoding: 'utf8' }); @@ -151,7 +167,7 @@ async function cleanup() { } if (processes.length === 0) { - success('No background processes to clean up'); + success('No untracked background processes found'); } else { info(`Found ${processes.length} background process(es)`); diff --git a/scripts/session-init.js b/scripts/session-init.js index 3a1a69a4..50dbe89f 100755 --- a/scripts/session-init.js +++ b/scripts/session-init.js @@ -332,6 +332,45 @@ async function main() { success(`Token budget: ${checkpoints.budget.toLocaleString()}`); success(`Next checkpoint: ${checkpoints.next_checkpoint.toLocaleString()} tokens (25%)`); + // Check for orphaned background processes (inst_023) + section('2a. Checking Background Processes (inst_023)'); + try { + const bgCheckOutput = execSync('node scripts/track-background-process.js list', { + encoding: 'utf8', + stdio: 'pipe' + }); + + if (bgCheckOutput.includes('No tracked processes')) { + success('No background processes tracked'); + } else { + log(bgCheckOutput, 'cyan'); + + // Check for orphaned processes from previous sessions + const state = JSON.parse(fs.readFileSync('.claude/session-state.json', 'utf8')); + const currentSession = state.session_id; + const procs = state.background_processes || []; + const orphaned = procs.filter(p => { + try { + process.kill(p.pid, 0); // Check if running + return p.session !== currentSession; + } catch (e) { + return false; // Not running + } + }); + + if (orphaned.length > 0) { + warning(`Found ${orphaned.length} orphaned process(es) from previous sessions`); + orphaned.forEach(p => { + log(` • PID ${p.pid}: ${p.description} (from ${p.session})`, 'yellow'); + }); + console.log(''); + log(' Run "node scripts/track-background-process.js cleanup" to clean up', 'cyan'); + } + } + } catch (err) { + log(` Could not check background processes: ${err.message}`, 'yellow'); + } + // Load instruction history section('3. Loading Instruction History'); const instructions = loadInstructionHistory(); @@ -462,6 +501,69 @@ async function main() { warning(`Could not load framework statistics: ${statsErr.message}`); } + // Framework fade detection (inst_064) + section('5b. Framework Activity Verification (inst_064)'); + try { + const sessionState = JSON.parse(fs.readFileSync('.claude/session-state.json', 'utf8')); + const currentMessage = sessionState.message_count || 0; + const stalenessThreshold = sessionState.staleness_thresholds?.messages || 20; + + const componentActivity = sessionState.last_framework_activity || {}; + const fadeAlerts = []; + const activeComponents = []; + + // Check each component for staleness + const requiredComponents = [ + 'ContextPressureMonitor', + 'InstructionPersistenceClassifier', + 'CrossReferenceValidator', + 'BoundaryEnforcer', + 'MetacognitiveVerifier', + 'PluralisticDeliberationOrchestrator' + ]; + + requiredComponents.forEach(component => { + const activity = componentActivity[component]; + if (!activity || !activity.timestamp) { + // Never used + fadeAlerts.push(`${component}: Never used this session`); + } else { + const messagesSinceUse = currentMessage - (activity.message || 0); + if (messagesSinceUse > stalenessThreshold) { + fadeAlerts.push(`${component}: ${messagesSinceUse} messages since last use (threshold: ${stalenessThreshold})`); + } else { + activeComponents.push(component); + } + } + }); + + if (fadeAlerts.length === 0) { + success('All framework components active - no fade detected'); + log(` Active components: ${activeComponents.length}/6`, 'cyan'); + } else if (fadeAlerts.length === requiredComponents.length) { + // Fresh session - all components unused + log(' Fresh session - framework components not yet triggered', 'cyan'); + log(' Components will activate during session operations', 'cyan'); + } else { + // Partial fade detected + warning(`Framework fade detected: ${fadeAlerts.length}/6 components stale`); + fadeAlerts.forEach(alert => { + log(` ⚠ ${alert}`, 'yellow'); + }); + console.log(''); + warning('CRITICAL: Framework fade = governance collapse (inst_064)'); + log(' Ensure components are used per their triggers:', 'yellow'); + log(' • ContextPressureMonitor: Session start, checkpoints, complex ops', 'cyan'); + log(' • CrossReferenceValidator: Schema changes, config mods, architecture', 'cyan'); + log(' • BoundaryEnforcer: Privacy, ethics, values-sensitive decisions', 'cyan'); + log(' • MetacognitiveVerifier: 3+ file mods or 5+ sequential steps', 'cyan'); + log(' • InstructionPersistenceClassifier: Explicit instructions given', 'cyan'); + log(' • PluralisticDeliberationOrchestrator: Values conflicts flagged', 'cyan'); + } + } catch (err) { + error(`Framework fade check failed: ${err.message}`); + } + // Run framework tests section('6. Running Framework Tests'); try { diff --git a/scripts/track-background-process.js b/scripts/track-background-process.js new file mode 100755 index 00000000..e62f83fb --- /dev/null +++ b/scripts/track-background-process.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +/** + * Background Process Tracker - Enforces inst_023 + */ + +const fs = require('fs'); +const path = require('path'); + +const SESSION_STATE = path.join(__dirname, '../.claude/session-state.json'); + +function loadState() { + if (!fs.existsSync(SESSION_STATE)) return { background_processes: [] }; + return JSON.parse(fs.readFileSync(SESSION_STATE, 'utf8')); +} + +function saveState(state) { + fs.writeFileSync(SESSION_STATE, JSON.stringify(state, null, 2)); +} + +function isRunning(pid) { + try { + process.kill(pid, 0); + return true; + } catch (e) { + return false; + } +} + +function register(pid, description, persistent = false) { + const state = loadState(); + if (!state.background_processes) state.background_processes = []; + + state.background_processes.push({ + pid: parseInt(pid), + description, + persistent, + started_at: new Date().toISOString(), + session: state.session_id + }); + + saveState(state); + console.log(`✅ Registered: PID ${pid} - ${description}${persistent ? ' [PERSISTENT]' : ''}`); +} + +function list() { + const state = loadState(); + const procs = state.background_processes || []; + + if (procs.length === 0) { + console.log('No tracked processes'); + return; + } + + console.log('\n📋 Background Processes:\n'); + procs.forEach(p => { + const status = isRunning(p.pid) ? '✓' : '✗'; + console.log(`${status} PID ${p.pid}: ${p.description} ${p.persistent ? '[PERSISTENT]' : ''}`); + }); + console.log(''); +} + +function cleanup(force = false) { + const state = loadState(); + const procs = state.background_processes || []; + let killed = 0; + const remaining = []; + + procs.forEach(p => { + if (!isRunning(p.pid)) return; + if (p.persistent && !force) { + remaining.push(p); + return; + } + try { + process.kill(p.pid, 'SIGTERM'); + console.log(`✓ Killed PID ${p.pid}`); + killed++; + } catch (err) { + remaining.push(p); + } + }); + + state.background_processes = remaining; + saveState(state); + console.log(`\n✅ Cleanup: ${killed} killed, ${remaining.length} remaining\n`); +} + +const cmd = process.argv[2]; +if (cmd === 'register') { + register(process.argv[3], process.argv[4], process.argv.includes('--persistent')); +} else if (cmd === 'list') { + list(); +} else if (cmd === 'cleanup') { + cleanup(process.argv.includes('--force')); +} else { + console.log('Usage: register [--persistent] | list | cleanup [--force]'); +} diff --git a/scripts/verify-security-logging.js b/scripts/verify-security-logging.js new file mode 100755 index 00000000..14fa6868 --- /dev/null +++ b/scripts/verify-security-logging.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Security Logging Verification - Enforces inst_046 + * Checks that security event logging is properly configured + */ + +const fs = require('fs'); +const path = require('path'); +const mongoose = require('mongoose'); + +async function verify() { + console.log('\n🔒 Security Logging Verification (inst_046)\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + let allPassed = true; + + // Check 1: Security audit log file or database + console.log('1. Checking security audit trail...'); + try { + await mongoose.connect('mongodb://localhost:27017/tractatus_dev', { + serverSelectionTimeoutMS: 2000 + }); + + const AuditLog = mongoose.model('AuditLog'); + const securityCount = await AuditLog.countDocuments({ + service: { $in: ['BoundaryEnforcer', 'AuthMiddleware', 'SecurityMonitor'] } + }); + + if (securityCount > 0) { + console.log(` ✅ ${securityCount} security events logged to database`); + } else { + console.log(' ⚠️ No security events in database (may be fresh install)'); + } + + mongoose.connection.close(); + } catch (dbErr) { + console.log(` ⚠️ Could not connect to audit database: ${dbErr.message}`); + } + + // Check 2: Security middleware present + console.log('\n2. Checking security middleware...'); + const middlewarePath = path.join(__dirname, '../src/middleware/auth.middleware.js'); + if (fs.existsSync(middlewarePath)) { + const content = fs.readFileSync(middlewarePath, 'utf8'); + if (content.includes('security') || content.includes('audit')) { + console.log(' ✅ Security middleware found'); + } else { + console.log(' ⚠️ Security middleware may not include audit logging'); + } + } else { + console.log(' ❌ Security middleware not found'); + allPassed = false; + } + + // Check 3: CSP violation detection + console.log('\n3. Checking CSP compliance tools...'); + const cspCheckPath = path.join(__dirname, '../scripts/check-csp-violations.js'); + if (fs.existsSync(cspCheckPath)) { + console.log(' ✅ CSP violation checker present'); + } else { + console.log(' ❌ CSP violation checker missing'); + allPassed = false; + } + + // Summary + console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + if (allPassed) { + console.log('✅ Security logging verification PASSED\n'); + process.exit(0); + } else { + console.log('❌ Security logging verification FAILED\n'); + console.log('Action required: Ensure all security logging components are in place'); + console.log('See inst_046 for full requirements\n'); + process.exit(1); + } +} + +verify().catch(err => { + console.error(`\n❌ Verification failed: ${err.message}\n`); + process.exit(1); +});