tractatus/src/controllers/missedBreach.controller.js
TheFlow 97ed03d180 feat(research): add missed breach tracking system for framework effectiveness measurement
Implements comprehensive system for tracking governance framework false negatives:

Backend:
- src/models/MissedBreach.model.js - Schema with severity, cost tracking, miss reasons
- src/controllers/missedBreach.controller.js - CRUD operations and statistics
- src/routes/missedBreach.routes.js - Admin-only API endpoints
- src/routes/index.js - Route integration at /api/admin/missed-breaches

Functionality:
- Report missed breaches with classification (NO_RULE_EXISTS, RULE_TOO_NARROW, etc.)
- Track actual/estimated costs of missed violations
- Calculate effectiveness rate: detected / (detected + missed)
- Breakdown by miss reason with examples
- Link to original audit logs where available

Statistics:
- Total missed breaches by severity
- Average time to detection
- Cost impact analysis
- Effectiveness comparison vs audit logs

Purpose:
- Measure true framework detection rate (not just blocked actions)
- Identify blind spots in governance rules
- Calculate realistic cost avoidance (avoiding "framework theater")
- Support research integrity claims with empirical data

Related: Cross-environment audit sync (production metrics)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 12:26:53 +13:00

249 lines
5.7 KiB
JavaScript

/**
* Missed Breach Controller
*
* Tracks governance framework false negatives for research integrity
*/
const MissedBreach = require('../models/MissedBreach.model');
const AuditLog = require('../models/AuditLog.model');
/**
* Report a new missed breach
* POST /api/admin/missed-breaches
*/
async function reportMissedBreach(req, res) {
try {
const {
title,
incidentDate,
severity,
description,
activityType,
missReason,
missReasonDetails,
linkedAuditLog,
sessionId,
actualCost,
estimatedCost,
environment,
tags
} = req.body;
// Validate required fields
if (!title || !incidentDate || !severity || !description || !activityType || !missReason) {
return res.status(400).json({
success: false,
error: 'Missing required fields: title, incidentDate, severity, description, activityType, missReason'
});
}
const missedBreach = new MissedBreach({
title,
incidentDate: new Date(incidentDate),
severity,
description,
activityType,
missReason,
missReasonDetails,
linkedAuditLog,
sessionId,
actualCost,
estimatedCost,
environment: environment || process.env.NODE_ENV || 'development',
tags: tags || [],
reportedBy: req.user?._id || null,
status: 'REPORTED'
});
await missedBreach.save();
res.status(201).json({
success: true,
missedBreach: missedBreach.toObject()
});
} catch (error) {
console.error('Error reporting missed breach:', error);
res.status(500).json({
success: false,
error: 'Failed to report missed breach'
});
}
}
/**
* Get all missed breaches
* GET /api/admin/missed-breaches?status=VERIFIED&severity=HIGH
*/
async function getMissedBreaches(req, res) {
try {
const {
status,
severity,
missReason,
environment,
startDate,
endDate,
limit = 100
} = req.query;
const query = {};
if (status) query.status = status;
if (severity) query.severity = severity;
if (missReason) query.missReason = missReason;
if (environment && environment !== 'all') query.environment = environment;
if (startDate && endDate) {
query.incidentDate = {
$gte: new Date(startDate),
$lte: new Date(endDate)
};
}
const breaches = await MissedBreach.find(query)
.sort({ incidentDate: -1 })
.limit(parseInt(limit))
.populate('reportedBy', 'name email')
.populate('verifiedBy', 'name email')
.lean();
res.json({
success: true,
count: breaches.length,
breaches
});
} catch (error) {
console.error('Error fetching missed breaches:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch missed breaches'
});
}
}
/**
* Get missed breach statistics
* GET /api/admin/missed-breaches/statistics
*/
async function getMissedBreachStatistics(req, res) {
try {
const { startDate, endDate } = req.query;
const start = startDate ? new Date(startDate) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const end = endDate ? new Date(endDate) : new Date();
// Get missed breach stats
const missedStats = await MissedBreach.getStatistics(start, end);
// Get audit log stats for comparison
const auditStats = await AuditLog.getStatistics(start, end);
// Calculate effectiveness rate
const effectiveness = await MissedBreach.calculateEffectivenessRate(
start,
end,
auditStats
);
// Get breakdown by reason
const byReason = await MissedBreach.getBreakdownByReason(start, end);
res.json({
success: true,
dateRange: { start, end },
missedBreaches: missedStats,
auditLogs: auditStats,
effectiveness,
breakdownByReason: byReason
});
} catch (error) {
console.error('Error fetching missed breach statistics:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch statistics'
});
}
}
/**
* Update a missed breach (verify, remediate, etc.)
* PATCH /api/admin/missed-breaches/:id
*/
async function updateMissedBreach(req, res) {
try {
const { id } = req.params;
const updates = req.body;
// If verifying, record who verified it
if (updates.status === 'VERIFIED' && req.user) {
updates.verifiedBy = req.user._id;
}
const missedBreach = await MissedBreach.findByIdAndUpdate(
id,
updates,
{ new: true, runValidators: true }
);
if (!missedBreach) {
return res.status(404).json({
success: false,
error: 'Missed breach not found'
});
}
res.json({
success: true,
missedBreach: missedBreach.toObject()
});
} catch (error) {
console.error('Error updating missed breach:', error);
res.status(500).json({
success: false,
error: 'Failed to update missed breach'
});
}
}
/**
* Delete a missed breach (if it was a false positive)
* DELETE /api/admin/missed-breaches/:id
*/
async function deleteMissedBreach(req, res) {
try {
const { id } = req.params;
const missedBreach = await MissedBreach.findByIdAndDelete(id);
if (!missedBreach) {
return res.status(404).json({
success: false,
error: 'Missed breach not found'
});
}
res.json({
success: true,
message: 'Missed breach deleted'
});
} catch (error) {
console.error('Error deleting missed breach:', error);
res.status(500).json({
success: false,
error: 'Failed to delete missed breach'
});
}
}
module.exports = {
reportMissedBreach,
getMissedBreaches,
getMissedBreachStatistics,
updateMissedBreach,
deleteMissedBreach
};