feat(bi): add business intelligence backend infrastructure

Implements core BI analytics capabilities for governance ROI measurement:

- Activity classifier utility for automatic event categorization
  * Detects activity type (client communication, infrastructure, etc.)
  * Calculates risk level, stakeholder impact, data sensitivity
  * Computes business impact scores (0-100)

- Enhanced audit controller with BI analytics endpoints
  * Cost avoidance calculator with user-configurable factors
  * Framework maturity scoring (0-100 scale)
  * Team performance comparison (AI vs human)
  * Activity type breakdown and ROI projections

- New API routes for cost configuration (GET/POST /api/admin/cost-config)

- Hook validator enhancement
  * Automatic activity classification on governance decisions
  * MongoDB audit logging with BI context fields
  * Business impact scoring for blocked actions

Status: Research prototype v1.0
Note: Cost factors are illustrative placeholders requiring validation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-27 10:07:33 +13:00
parent bbed39623c
commit ae12888eb4
4 changed files with 704 additions and 20 deletions

View file

@ -274,7 +274,7 @@ function checkCredentialProtection() {
}
const { validate } = require(validatorPath);
const result = validate(FILE_PATH, 'edit');
const result = validate(FILE_PATH, 'edit', HOOK_INPUT);
if (!result.valid) {
return {
@ -313,10 +313,11 @@ function updateSessionState() {
}
/**
* Log metrics for hook execution
* Log metrics for hook execution (both file-based and MongoDB)
*/
function logMetrics(result, reason = null) {
async function logMetrics(result, reason = null) {
try {
// 1. File-based metrics (legacy)
const METRICS_PATH = path.join(__dirname, '../../.claude/metrics/hooks-metrics.json');
const METRICS_DIR = path.dirname(METRICS_PATH);
@ -367,11 +368,94 @@ function logMetrics(result, reason = null) {
// Write metrics
fs.writeFileSync(METRICS_PATH, JSON.stringify(metrics, null, 2));
// 2. MongoDB audit log (for dashboard visibility)
await logToAuditDatabase(result, reason);
} catch (err) {
// Non-critical - don't fail on metrics logging
}
}
/**
* Log hook execution to MongoDB AuditLog for dashboard visibility
*/
async function logToAuditDatabase(result, reason) {
try {
const mongoose = require('mongoose');
// Skip if not connected (hook runs before DB init)
if (mongoose.connection.readyState !== 1) {
return;
}
const AuditLog = mongoose.model('AuditLog');
// Get session ID from state file
let sessionId = 'unknown';
try {
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
sessionId = sessionState.session_id || 'unknown';
} catch (e) {
// Ignore
}
// Determine rule violations based on reason
const violations = [];
if (result === 'blocked' && reason) {
// Parse reason to extract rule ID if present
const ruleMatch = reason.match(/inst_\d+/);
violations.push({
ruleId: ruleMatch ? ruleMatch[0] : 'hook-validator',
ruleText: reason,
severity: 'HIGH',
details: reason
});
}
// Classify activity for business intelligence
const activityClassifier = require('../../src/utils/activity-classifier.util');
const classification = activityClassifier.classifyActivity('file_edit_hook', {
filePath: FILE_PATH,
reason: reason,
service: 'FileEditHook'
});
const businessImpact = activityClassifier.calculateBusinessImpact(
classification,
result === 'blocked'
);
// Create audit log entry
await AuditLog.create({
sessionId: sessionId,
action: 'file_edit_hook',
allowed: result !== 'blocked',
rulesChecked: ['inst_072', 'inst_084', 'inst_038'], // Common hook rules
violations: violations,
metadata: {
filePath: FILE_PATH,
hook: 'validate-file-edit',
reason: reason
},
domain: 'SYSTEM',
service: 'FileEditHook',
timestamp: new Date(),
// Business intelligence context
activityType: classification.activityType,
riskLevel: classification.riskLevel,
stakeholderImpact: classification.stakeholderImpact,
dataSensitivity: classification.dataSensitivity,
reversibility: classification.reversibility,
businessImpact: businessImpact
});
} catch (err) {
// Non-critical - don't fail on audit logging
// console.error('Audit log error:', err.message);
}
}
/**
* Read hook input from stdin
*/
@ -400,7 +484,7 @@ async function main() {
if (!HOOK_INPUT || !HOOK_INPUT.tool_input || !HOOK_INPUT.tool_input.file_path) {
error('No file path provided in hook input');
logMetrics('error', 'No file path in input');
await logMetrics('error', 'No file path in input');
process.exit(2); // Exit code 2 = BLOCK
}
@ -414,7 +498,7 @@ async function main() {
if (cspCheck.output) {
console.log(cspCheck.output);
}
logMetrics('blocked', cspCheck.reason);
await logMetrics('blocked', cspCheck.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('CSP compliance validated on content after edit');
@ -426,7 +510,7 @@ async function main() {
if (preActionCheck.output) {
console.log(preActionCheck.output);
}
logMetrics('blocked', preActionCheck.reason);
await logMetrics('blocked', preActionCheck.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('Pre-action-check recency (inst_038) passed');
@ -438,7 +522,7 @@ async function main() {
conflicts.conflicts.forEach(c => {
log(`${c.id}: ${c.instruction} [${c.quadrant}]`, 'yellow');
});
logMetrics('blocked', conflicts.reason);
await logMetrics('blocked', conflicts.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('No instruction conflicts detected');
@ -447,7 +531,7 @@ async function main() {
const boundary = checkBoundaryViolation();
if (!boundary.passed) {
error(boundary.reason);
logMetrics('blocked', boundary.reason);
await logMetrics('blocked', boundary.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('No boundary violations detected');
@ -459,7 +543,7 @@ async function main() {
if (credentials.output) {
console.log(credentials.output);
}
logMetrics('blocked', credentials.reason);
await logMetrics('blocked', credentials.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('No protected credential changes detected');
@ -470,7 +554,7 @@ async function main() {
if (githubUrlCheck.output) {
console.error(githubUrlCheck.output);
}
logMetrics('blocked', githubUrlCheck.reason);
await logMetrics('blocked', githubUrlCheck.reason);
process.exit(2); // Exit code 2 = BLOCK
}
success('No unauthorized GitHub URL modifications detected (inst_084)');
@ -480,15 +564,15 @@ async function main() {
updateSessionState();
// Log successful execution
logMetrics('passed');
await logMetrics('passed');
success('File edit validation complete\n');
process.exit(0);
}
main().catch(err => {
main().catch(async (err) => {
error(`Hook validation error: ${err.message}`);
logMetrics('error', err.message);
await logMetrics('error', err.message);
process.exit(2); // Exit code 2 = BLOCK
});

View file

@ -23,6 +23,16 @@ const fs = require('fs').promises;
const path = require('path');
const logger = require('../utils/logger.util');
// Default cost factors (user-configurable)
const DEFAULT_COST_FACTORS = {
CRITICAL: { amount: 50000, currency: 'USD', rationale: 'Average cost of critical security incident' },
HIGH: { amount: 10000, currency: 'USD', rationale: 'Average cost of high-impact client-facing error' },
MEDIUM: { amount: 2000, currency: 'USD', rationale: 'Average cost of medium-impact issue requiring hotfix' },
LOW: { amount: 500, currency: 'USD', rationale: 'Average developer time cost to fix low-impact issue' }
};
let userCostFactors = { ...DEFAULT_COST_FACTORS };
/**
* Get audit logs for analytics
* GET /api/admin/audit-logs
@ -79,12 +89,24 @@ async function getAuditAnalytics(req, res) {
const decisions = auditLogsResponse.decisions;
// Calculate analytics
const blockedDecisions = decisions.filter(d => !d.allowed);
const allowedDecisions = decisions.filter(d => d.allowed);
const analytics = {
total: decisions.length,
allowed: decisions.filter(d => d.allowed).length,
blocked: decisions.filter(d => !d.allowed).length,
allowed: allowedDecisions.length,
blocked: blockedDecisions.length,
violations: decisions.filter(d => d.violations && d.violations.length > 0).length,
// Block metrics
blockRate: decisions.length > 0 ? ((blockedDecisions.length / decisions.length) * 100).toFixed(1) : 0,
allowedRate: decisions.length > 0 ? ((allowedDecisions.length / decisions.length) * 100).toFixed(1) : 0,
// Block breakdown
blocksByReason: {},
blocksBySeverity: {},
frameworkSaves: [], // High severity blocks
byAction: {},
byService: {},
bySession: {},
@ -93,6 +115,14 @@ async function getAuditAnalytics(req, res) {
timeline: [],
topViolations: [],
// Business intelligence metrics
byActivityType: {},
byRiskLevel: {},
byStakeholderImpact: {},
riskHeatmap: [],
businessImpactTotal: 0,
roiProjections: {},
dateRange: auditLogsResponse.dateRange
};
@ -134,21 +164,279 @@ async function getAuditAnalytics(req, res) {
});
}
// Top violations
// Analyze blocks by reason and severity
blockedDecisions.forEach(d => {
// Extract reason from violations or metadata
let reason = 'Unknown';
let severity = 'MEDIUM';
if (d.violations && d.violations.length > 0) {
// Use first violation's rule ID as reason
reason = d.violations[0].ruleId || d.violations[0].ruleText || 'Unknown';
severity = d.violations[0].severity || 'MEDIUM';
// Count by severity
analytics.blocksBySeverity[severity] = (analytics.blocksBySeverity[severity] || 0) + 1;
// Track high severity blocks as "Framework Saves"
if (severity === 'HIGH' || severity === 'CRITICAL') {
analytics.frameworkSaves.push({
timestamp: d.timestamp,
reason: d.violations[0].ruleText || d.violations[0].details,
ruleId: d.violations[0].ruleId,
severity: severity,
file: d.metadata?.filePath || d.metadata?.file || 'N/A',
service: d.service
});
}
} else if (d.metadata?.reason) {
reason = d.metadata.reason;
}
// Count by reason
analytics.blocksByReason[reason] = (analytics.blocksByReason[reason] || 0) + 1;
});
// Sort framework saves by timestamp (most recent first)
analytics.frameworkSaves.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
analytics.frameworkSaves = analytics.frameworkSaves.slice(0, 10); // Top 10 saves
// Top violations (enhanced with more detail)
const violationCounts = {};
decisions.forEach(d => {
if (d.violations && d.violations.length > 0) {
d.violations.forEach(v => {
violationCounts[v] = (violationCounts[v] || 0) + 1;
const key = v.ruleId || v.ruleText || 'unknown';
if (!violationCounts[key]) {
violationCounts[key] = {
ruleId: v.ruleId,
ruleText: v.ruleText,
severity: v.severity,
count: 0
};
}
violationCounts[key].count++;
});
}
});
analytics.topViolations = Object.entries(violationCounts)
.map(([violation, count]) => ({ violation, count }))
analytics.topViolations = Object.values(violationCounts)
.sort((a, b) => b.count - a.count)
.slice(0, 10);
// === BUSINESS INTELLIGENCE METRICS ===
// Activity type breakdown
decisions.forEach(d => {
const activityType = d.activityType || 'Unknown';
if (!analytics.byActivityType[activityType]) {
analytics.byActivityType[activityType] = {
total: 0,
allowed: 0,
blocked: 0,
blockRate: 0
};
}
analytics.byActivityType[activityType].total++;
if (d.allowed) {
analytics.byActivityType[activityType].allowed++;
} else {
analytics.byActivityType[activityType].blocked++;
}
});
// Calculate block rates for each activity type
Object.keys(analytics.byActivityType).forEach(type => {
const data = analytics.byActivityType[type];
data.blockRate = data.total > 0 ? ((data.blocked / data.total) * 100).toFixed(1) : '0.0';
});
// Risk level distribution
decisions.forEach(d => {
const riskLevel = d.riskLevel || 'low';
if (!analytics.byRiskLevel[riskLevel]) {
analytics.byRiskLevel[riskLevel] = {
total: 0,
blocked: 0
};
}
analytics.byRiskLevel[riskLevel].total++;
if (!d.allowed) {
analytics.byRiskLevel[riskLevel].blocked++;
}
});
// Stakeholder impact distribution
decisions.forEach(d => {
const impact = d.stakeholderImpact || 'individual';
if (!analytics.byStakeholderImpact[impact]) {
analytics.byStakeholderImpact[impact] = {
total: 0,
blocked: 0,
businessImpact: 0
};
}
analytics.byStakeholderImpact[impact].total++;
if (!d.allowed) {
analytics.byStakeholderImpact[impact].blocked++;
analytics.byStakeholderImpact[impact].businessImpact += (d.businessImpact || 0);
}
});
// Risk heatmap: Activity Type vs Risk Level
const heatmapData = {};
decisions.forEach(d => {
const activityType = d.activityType || 'Unknown';
const riskLevel = d.riskLevel || 'low';
const key = `${activityType}|${riskLevel}`;
if (!heatmapData[key]) {
heatmapData[key] = {
activityType,
riskLevel,
total: 0,
blocked: 0
};
}
heatmapData[key].total++;
if (!d.allowed) {
heatmapData[key].blocked++;
}
});
analytics.riskHeatmap = Object.values(heatmapData);
// Business impact total
analytics.businessImpactTotal = blockedDecisions.reduce((sum, d) => sum + (d.businessImpact || 0), 0);
// === COST AVOIDANCE CALCULATIONS ===
// Calculate cost avoided based on user-configured factors
let totalCostAvoided = 0;
const costBreakdown = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
blockedDecisions.forEach(d => {
if (d.violations && d.violations.length > 0) {
d.violations.forEach(v => {
const severity = v.severity || 'LOW';
const costFactor = userCostFactors[severity];
if (costFactor) {
totalCostAvoided += costFactor.amount;
costBreakdown[severity] += costFactor.amount;
}
});
}
});
analytics.costAvoidance = {
total: totalCostAvoided,
currency: userCostFactors.CRITICAL.currency,
breakdown: costBreakdown,
costFactors: userCostFactors
};
// === AI vs HUMAN PERFORMANCE ===
// Detect AI vs Human based on service patterns
// AI = FileEditHook, Framework services
// Human = Manual overrides, direct database operations
const aiDecisions = decisions.filter(d =>
d.service === 'FileEditHook' ||
d.service === 'BoundaryEnforcer' ||
d.service === 'ContextPressureMonitor' ||
d.service === 'MetacognitiveVerifier'
);
const humanDecisions = decisions.filter(d =>
!aiDecisions.includes(d)
);
const aiBlocked = aiDecisions.filter(d => !d.allowed).length;
const humanBlocked = humanDecisions.filter(d => !d.allowed).length;
analytics.teamComparison = {
ai: {
total: aiDecisions.length,
blocked: aiBlocked,
blockRate: aiDecisions.length > 0 ? ((aiBlocked / aiDecisions.length) * 100).toFixed(1) : '0.0',
label: 'AI Assistant'
},
human: {
total: humanDecisions.length,
blocked: humanBlocked,
blockRate: humanDecisions.length > 0 ? ((humanBlocked / humanDecisions.length) * 100).toFixed(1) : '0.0',
label: 'Human Direct'
}
};
// Framework Maturity Score (0-100)
// Based on: block rate trend, severity distribution, learning curve
const recentDays = 7;
const recentDate = new Date();
recentDate.setDate(recentDate.getDate() - recentDays);
const recentDecisions = decisions.filter(d => new Date(d.timestamp) >= recentDate);
const recentBlocked = recentDecisions.filter(d => !d.allowed).length;
const recentBlockRate = recentDecisions.length > 0 ? (recentBlocked / recentDecisions.length) : 0;
// Lower block rate = higher maturity (framework teaching good habits)
const blockRateScore = Math.max(0, 100 - (recentBlockRate * 1000));
// More AI decisions with low block rate = higher maturity
const aiAdoptionScore = aiDecisions.length > 0 ? ((aiDecisions.length / decisions.length) * 100) : 0;
// Fewer critical/high severity blocks = higher maturity
const criticalCount = blockedDecisions.filter(d =>
d.violations && d.violations.some(v => v.severity === 'CRITICAL' || v.severity === 'HIGH')
).length;
const severityScore = Math.max(0, 100 - (criticalCount / decisions.length * 1000));
analytics.frameworkMaturity = {
score: Math.round((blockRateScore + aiAdoptionScore + severityScore) / 3),
components: {
blockRateScore: Math.round(blockRateScore),
aiAdoptionScore: Math.round(aiAdoptionScore),
severityScore: Math.round(severityScore)
},
trend: recentBlockRate < avgBlockRate ? 'improving' : 'stable',
message: recentBlockRate < 0.05 ? 'Excellent - Framework teaching good practices' :
recentBlockRate < 0.10 ? 'Good - Team adapting well to governance' :
'Learning - Framework actively preventing violations'
};
// ROI Projections
const totalDecisions = decisions.length;
const totalBlocks = blockedDecisions.length;
const avgBlockRate = totalDecisions > 0 ? (totalBlocks / totalDecisions) : 0;
analytics.roiProjections = {
// Current period metrics
decisionsPerDay: days > 0 ? (totalDecisions / days).toFixed(1) : '0',
blocksPerDay: days > 0 ? (totalBlocks / days).toFixed(1) : '0',
// Projections for scaled deployment
projections: [
{
userCount: 1000,
decisionsPerMonth: Math.round((totalDecisions / days) * 30 * 1000 / decisions.length * 10), // Estimated
blocksPerMonth: Math.round((totalBlocks / days) * 30 * 1000 / decisions.length * 10),
highSeverityBlocks: Math.round(analytics.frameworkSaves.length / days * 30 * 1000 / decisions.length * 10),
businessImpactSaved: Math.round(analytics.businessImpactTotal / days * 30 * 1000 / decisions.length * 10)
},
{
userCount: 10000,
decisionsPerMonth: Math.round((totalDecisions / days) * 30 * 10000 / decisions.length * 10),
blocksPerMonth: Math.round((totalBlocks / days) * 30 * 10000 / decisions.length * 10),
highSeverityBlocks: Math.round(analytics.frameworkSaves.length / days * 30 * 10000 / decisions.length * 10),
businessImpactSaved: Math.round(analytics.businessImpactTotal / days * 30 * 10000 / decisions.length * 10)
},
{
userCount: 70000,
decisionsPerMonth: Math.round((totalDecisions / days) * 30 * 70000 / decisions.length * 10),
blocksPerMonth: Math.round((totalBlocks / days) * 30 * 70000 / decisions.length * 10),
highSeverityBlocks: Math.round(analytics.frameworkSaves.length / days * 30 * 70000 / decisions.length * 10),
businessImpactSaved: Math.round(analytics.businessImpactTotal / days * 30 * 70000 / decisions.length * 10)
}
]
};
res.json({
success: true,
analytics
@ -163,7 +451,69 @@ async function getAuditAnalytics(req, res) {
}
}
/**
* Get cost configuration
* GET /api/admin/cost-config
*/
async function getCostConfig(req, res) {
try {
res.json({
success: true,
costFactors: userCostFactors
});
} catch (error) {
logger.error('Error fetching cost config:', error);
res.status(500).json({
success: false,
error: error.message
});
}
}
/**
* Update cost configuration
* POST /api/admin/cost-config
*/
async function updateCostConfig(req, res) {
try {
const { costFactors } = req.body;
if (!costFactors) {
return res.status(400).json({
success: false,
error: 'costFactors required'
});
}
// Validate and update
['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].forEach(severity => {
if (costFactors[severity]) {
userCostFactors[severity] = {
amount: parseFloat(costFactors[severity].amount) || userCostFactors[severity].amount,
currency: costFactors[severity].currency || userCostFactors[severity].currency,
rationale: costFactors[severity].rationale || userCostFactors[severity].rationale
};
}
});
logger.info('Cost factors updated by', req.user?.username || 'admin');
res.json({
success: true,
costFactors: userCostFactors
});
} catch (error) {
logger.error('Error updating cost config:', error);
res.status(500).json({
success: false,
error: error.message
});
}
}
module.exports = {
getAuditLogs,
getAuditAnalytics
getAuditAnalytics,
getCostConfig,
updateCostConfig
};

View file

@ -38,4 +38,18 @@ router.get('/audit-analytics',
auditController.getAuditAnalytics
);
// Get cost configuration (admin only)
router.get('/cost-config',
authenticateToken,
requireRole('admin'),
auditController.getCostConfig
);
// Update cost configuration (admin only)
router.post('/cost-config',
authenticateToken,
requireRole('admin'),
auditController.updateCostConfig
);
module.exports = router;

View file

@ -0,0 +1,236 @@
/*
* Copyright 2025 John G Stroh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Activity Classifier
* Provides business intelligence context for governance decisions
* Enables ROI analysis and use-case tracking
*/
// Activity type definitions
const ACTIVITY_TYPES = {
HUMAN_INITIATED: 'Human-Initiated Command',
AUTONOMOUS_PROCESSING: 'Autonomous Processing',
CODE_GENERATION: 'Code Generation',
DOCUMENTATION: 'Documentation',
CLIENT_COMMUNICATION: 'Client Communication',
DEPLOYMENT: 'Deployment',
COMPLIANCE_REVIEW: 'Compliance Review',
DATA_MANAGEMENT: 'Data Management'
};
// Risk context levels
const RISK_LEVELS = {
MINIMAL: 'minimal', // No external impact
LOW: 'low', // Internal team only
MEDIUM: 'medium', // Organization-wide
HIGH: 'high', // Client/public facing
CRITICAL: 'critical' // Legal/financial/regulatory
};
// Stakeholder impact
const STAKEHOLDER_IMPACT = {
INDIVIDUAL: 'individual', // Single developer
TEAM: 'team', // Development team
ORGANIZATION: 'organization', // Entire organization
CLIENT: 'client', // External clients
PUBLIC: 'public' // Public audience
};
// Data sensitivity levels
const DATA_SENSITIVITY = {
PUBLIC: 'public',
INTERNAL: 'internal',
CONFIDENTIAL: 'confidential',
RESTRICTED: 'restricted'
};
/**
* Classify activity based on action and metadata
*/
function classifyActivity(action, metadata = {}) {
const filePath = metadata.filePath || metadata.file || '';
const description = metadata.description || metadata.reason || '';
const service = metadata.service || '';
let activityType = ACTIVITY_TYPES.AUTONOMOUS_PROCESSING;
let riskLevel = RISK_LEVELS.LOW;
let stakeholderImpact = STAKEHOLDER_IMPACT.INDIVIDUAL;
let dataSensitivity = DATA_SENSITIVITY.INTERNAL;
// Detect activity type
if (action === 'file_edit_hook' || action === 'file_write_hook') {
// Check file path patterns
if (filePath.includes('public/') && !filePath.includes('admin/')) {
activityType = ACTIVITY_TYPES.CLIENT_COMMUNICATION;
stakeholderImpact = STAKEHOLDER_IMPACT.PUBLIC;
riskLevel = RISK_LEVELS.HIGH;
dataSensitivity = DATA_SENSITIVITY.PUBLIC;
} else if (filePath.includes('docs/')) {
activityType = ACTIVITY_TYPES.DOCUMENTATION;
stakeholderImpact = STAKEHOLDER_IMPACT.ORGANIZATION;
riskLevel = RISK_LEVELS.MEDIUM;
} else if (filePath.includes('.js') || filePath.includes('.css') || filePath.includes('.html')) {
activityType = ACTIVITY_TYPES.CODE_GENERATION;
stakeholderImpact = STAKEHOLDER_IMPACT.TEAM;
riskLevel = RISK_LEVELS.LOW;
} else if (filePath.includes('deploy') || filePath.includes('production')) {
activityType = ACTIVITY_TYPES.DEPLOYMENT;
stakeholderImpact = STAKEHOLDER_IMPACT.ORGANIZATION;
riskLevel = RISK_LEVELS.HIGH;
}
} else if (service === 'BoundaryEnforcer') {
activityType = ACTIVITY_TYPES.COMPLIANCE_REVIEW;
riskLevel = RISK_LEVELS.MEDIUM;
} else if (service === 'ContextPressureMonitor' || service === 'MetacognitiveVerifier') {
activityType = ACTIVITY_TYPES.AUTONOMOUS_PROCESSING;
}
// Detect client-facing content
if (filePath.includes('/components/footer') ||
filePath.includes('/components/navbar') ||
filePath.includes('contact') ||
filePath.includes('email')) {
activityType = ACTIVITY_TYPES.CLIENT_COMMUNICATION;
stakeholderImpact = STAKEHOLDER_IMPACT.CLIENT;
riskLevel = RISK_LEVELS.HIGH;
}
// Detect credentials and sensitive data
if (description.toLowerCase().includes('credential') ||
description.toLowerCase().includes('password') ||
description.toLowerCase().includes('api key') ||
description.toLowerCase().includes('secret')) {
dataSensitivity = DATA_SENSITIVITY.RESTRICTED;
riskLevel = RISK_LEVELS.CRITICAL;
}
// Detect copyright/legal
if (description.toLowerCase().includes('copyright') ||
description.toLowerCase().includes('license') ||
filePath.includes('LICENSE')) {
riskLevel = RISK_LEVELS.CRITICAL;
stakeholderImpact = STAKEHOLDER_IMPACT.PUBLIC;
}
return {
activityType,
riskLevel,
stakeholderImpact,
dataSensitivity,
reversibility: calculateReversibility(activityType, stakeholderImpact)
};
}
/**
* Calculate reversibility of action
*/
function calculateReversibility(activityType, stakeholderImpact) {
// Public-facing and client communication is harder to reverse
if (stakeholderImpact === STAKEHOLDER_IMPACT.PUBLIC ||
stakeholderImpact === STAKEHOLDER_IMPACT.CLIENT) {
return 'difficult';
}
// Deployment is harder to reverse
if (activityType === ACTIVITY_TYPES.DEPLOYMENT) {
return 'moderate';
}
// Code and documentation are easily reversible
if (activityType === ACTIVITY_TYPES.CODE_GENERATION ||
activityType === ACTIVITY_TYPES.DOCUMENTATION) {
return 'easy';
}
return 'moderate';
}
/**
* Get human-readable activity description
*/
function getActivityDescription(classification) {
const { activityType, stakeholderImpact, riskLevel } = classification;
const descriptions = {
[ACTIVITY_TYPES.CODE_GENERATION]: 'Internal development work',
[ACTIVITY_TYPES.CLIENT_COMMUNICATION]: 'Client-facing content',
[ACTIVITY_TYPES.DOCUMENTATION]: 'Documentation updates',
[ACTIVITY_TYPES.DEPLOYMENT]: 'Production deployment',
[ACTIVITY_TYPES.COMPLIANCE_REVIEW]: 'Compliance validation',
[ACTIVITY_TYPES.AUTONOMOUS_PROCESSING]: 'Background processing',
[ACTIVITY_TYPES.DATA_MANAGEMENT]: 'Data operations'
};
return descriptions[activityType] || 'Unknown activity';
}
/**
* Calculate business impact score (0-100)
*/
function calculateBusinessImpact(classification, wasBlocked) {
let score = 0;
// Risk level contribution (0-40)
const riskScores = {
[RISK_LEVELS.MINIMAL]: 5,
[RISK_LEVELS.LOW]: 10,
[RISK_LEVELS.MEDIUM]: 20,
[RISK_LEVELS.HIGH]: 30,
[RISK_LEVELS.CRITICAL]: 40
};
score += riskScores[classification.riskLevel] || 10;
// Stakeholder impact contribution (0-30)
const stakeholderScores = {
[STAKEHOLDER_IMPACT.INDIVIDUAL]: 5,
[STAKEHOLDER_IMPACT.TEAM]: 10,
[STAKEHOLDER_IMPACT.ORGANIZATION]: 20,
[STAKEHOLDER_IMPACT.CLIENT]: 25,
[STAKEHOLDER_IMPACT.PUBLIC]: 30
};
score += stakeholderScores[classification.stakeholderImpact] || 10;
// Data sensitivity contribution (0-20)
const sensitivityScores = {
[DATA_SENSITIVITY.PUBLIC]: 5,
[DATA_SENSITIVITY.INTERNAL]: 10,
[DATA_SENSITIVITY.CONFIDENTIAL]: 15,
[DATA_SENSITIVITY.RESTRICTED]: 20
};
score += sensitivityScores[classification.dataSensitivity] || 5;
// Reversibility contribution (0-10)
const reversibilityScores = {
'easy': 2,
'moderate': 5,
'difficult': 10
};
score += reversibilityScores[classification.reversibility] || 5;
// If blocked, this represents value saved
return wasBlocked ? score : 0;
}
module.exports = {
ACTIVITY_TYPES,
RISK_LEVELS,
STAKEHOLDER_IMPACT,
DATA_SENSITIVITY,
classifyActivity,
getActivityDescription,
calculateBusinessImpact
};