From f5eb961fc091351766dcfc5daa5daf00d064a3d7 Mon Sep 17 00:00:00 2001 From: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com> Date: Fri, 22 May 2026 07:12:23 +0200 Subject: [PATCH] Add enterprise admin alert escalation guard --- .../README.md | 48 ++++ .../demo.js | 136 ++++++++++ .../index.js | 242 ++++++++++++++++++ .../package.json | 13 + .../admin-alert-escalation-packet.json | 202 +++++++++++++++ .../reports/admin-alert-escalation-report.md | 27 ++ .../reports/demo.mp4 | Bin 0 -> 58187 bytes .../reports/summary.svg | 20 ++ .../sample-data.js | 64 +++++ .../test.js | 98 +++++++ 10 files changed, 850 insertions(+) create mode 100644 enterprise-admin-alert-escalation-guard/README.md create mode 100644 enterprise-admin-alert-escalation-guard/demo.js create mode 100644 enterprise-admin-alert-escalation-guard/index.js create mode 100644 enterprise-admin-alert-escalation-guard/package.json create mode 100644 enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-packet.json create mode 100644 enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-report.md create mode 100644 enterprise-admin-alert-escalation-guard/reports/demo.mp4 create mode 100644 enterprise-admin-alert-escalation-guard/reports/summary.svg create mode 100644 enterprise-admin-alert-escalation-guard/sample-data.js create mode 100644 enterprise-admin-alert-escalation-guard/test.js diff --git a/enterprise-admin-alert-escalation-guard/README.md b/enterprise-admin-alert-escalation-guard/README.md new file mode 100644 index 00000000..8e468008 --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/README.md @@ -0,0 +1,48 @@ +# Enterprise Admin Alert Escalation Guard + +This module is a dependency-free, synthetic-data-only guard for SCIBASE +Enterprise Tooling. It validates whether enterprise admin alerts can safely be +muted, digested, throttled, or routed through webhooks without hiding +compliance, security, export, or integration obligations. + +## What It Checks + +- Critical or high-severity alerts delayed by digest settings. +- Urgent alerts muted by owner preferences. +- Quiet-hours overrides for compliance and security alerts. +- Alerts routed to inactive owners or admins on leave. +- Required webhook routes with unhealthy failure rates. +- Duplicate suppression near compliance or export deadlines. +- Missing delivery channels for admin dashboard events. + +## Local Commands + +```bash +npm run check +npm test +npm run demo +``` + +The demo writes reviewer artifacts under `reports/`: + +- `admin-alert-escalation-packet.json` +- `admin-alert-escalation-report.md` +- `summary.svg` +- `demo.mp4` + +## Requirements Map + +| Issue #19 capability | Coverage in this slice | +| --- | --- | +| Admin dashboards | Emits dashboard badges for digest bypass, owner fallback, webhook fallback, and visible deadlines. | +| Compliance tracking | Keeps compliance, security, IRB, and export obligations visible until acknowledged. | +| API and webhooks | Emits webhook-ready event summaries without delivering live webhooks. | +| Enterprise governance | Enforces owner fallback, quiet-hours override, and duplicate-suppression safety for institutional admins. | + +## Safety Boundaries + +- Uses only synthetic fixtures in `sample-data.js`. +- Does not deliver live email, SMS, Slack, webhook, or external provider + notifications. +- Does not include real institutional alerts, private users, credentials, or + project data. diff --git a/enterprise-admin-alert-escalation-guard/demo.js b/enterprise-admin-alert-escalation-guard/demo.js new file mode 100644 index 00000000..f24d083a --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/demo.js @@ -0,0 +1,136 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { spawnSync } = require('child_process'); +const sampleAlerts = require('./sample-data'); +const { evaluateAlerts } = require('./index'); + +const reportDir = path.join(__dirname, 'reports'); +const asOfDate = '2026-05-22'; + +fs.mkdirSync(reportDir, { recursive: true }); + +const portfolio = evaluateAlerts(sampleAlerts, { asOfDate }); +const jsonPath = path.join(reportDir, 'admin-alert-escalation-packet.json'); +const markdownPath = path.join(reportDir, 'admin-alert-escalation-report.md'); +const svgPath = path.join(reportDir, 'summary.svg'); +const mp4Path = path.join(reportDir, 'demo.mp4'); + +fs.writeFileSync(jsonPath, `${JSON.stringify(portfolio, null, 2)}\n`); +fs.writeFileSync(markdownPath, renderMarkdown(portfolio)); +fs.writeFileSync(svgPath, renderSvg(portfolio)); +renderVideo(portfolio, mp4Path); + +console.log(`Wrote ${path.relative(process.cwd(), jsonPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), markdownPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), svgPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), mp4Path)}`); + +function renderMarkdown(portfolio) { + return [ + '# Enterprise Admin Alert Escalation Guard Report', + '', + `As of: ${portfolio.asOfDate}`, + `Alert digest: \`${portfolio.auditDigest}\``, + '', + '## Summary', + '', + `- Alerts reviewed: ${portfolio.alertCount}`, + `- Findings: ${portfolio.findingCount}`, + `- Escalated alerts: ${portfolio.escalatedAlerts.join(', ') || 'none'}`, + `- Actions: ${Object.entries(portfolio.byAction).map(([action, count]) => `${action}=${count}`).join(', ')}`, + '', + '## Alert Decisions', + '', + '| Alert | Category | Severity | Action | Findings | Dashboard badges |', + '| --- | --- | --- | --- | ---: | --- |', + ...portfolio.packets.map((packet) => `| ${packet.alertId} | ${packet.category} | ${packet.severity} | ${packet.action} | ${packet.findingCount} | ${packet.dashboardBadges.join(', ') || 'none'} |`), + '', + '## Guardrails', + '', + '- Uses synthetic enterprise alert, owner, routing, digest, and webhook metadata only.', + '- Does not deliver live email, SMS, Slack, webhook, or external provider notifications.', + '- Keeps critical compliance/security alerts visible even when muted, digested, quiet-hours, duplicated, or routed to unavailable owners.', + '- Emits deterministic dashboard badges, webhook-ready event summaries, and audit digests.', + '' + ].join('\n'); +} + +function renderSvg(portfolio) { + const actions = Object.entries(portfolio.byAction) + .map(([action, count]) => `${escapeXml(action)} (${count})`) + .join(' / '); + + return ` + + Enterprise admin alert escalation guard summary + Synthetic enterprise alert delivery and escalation decisions. + + + Enterprise admin alert escalation guard + Mute, digest, owner, webhook, and quiet-hours routing checks + + ${portfolio.alertCount} alerts + admin routing + + ${portfolio.escalatedAlerts.length} escalated + mute bypass + + ${portfolio.findingCount} findings + review packets + Actions: ${actions} + Digest: ${portfolio.auditDigest.slice(0, 32)}... + +`; +} + +function renderVideo(portfolio, outputPath) { + const font = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'; + const filters = [ + drawText(font, 'SCIBASE #19 Enterprise Tooling', 64, 72, 40, 'white'), + drawText(font, 'Admin notification escalation guard', 64, 136, 36, 'white'), + drawText(font, `Reviewed ${portfolio.alertCount} synthetic admin alerts`, 84, 238, 33, '0xdbeafe'), + drawText(font, `${portfolio.escalatedAlerts.length} alerts escalated before mute or digest`, 84, 304, 32, '0xfecaca'), + drawText(font, `${portfolio.findingCount} findings with dashboard badges and webhook summaries`, 84, 370, 28, '0xdcfce7'), + drawText(font, 'Checks quiet hours owner fallback webhook health duplicates and digest delays', 84, 462, 25, '0xe0f2fe'), + drawText(font, 'No live notification delivery email SMS Slack webhooks or external providers', 84, 522, 25, '0xfef3c7'), + drawText(font, `Audit digest ${portfolio.auditDigest.slice(0, 24)}`, 84, 596, 24, '0xcbd5e1') + ].join(','); + + const result = spawnSync('ffmpeg', [ + '-y', + '-f', 'lavfi', + '-i', 'color=c=0x233142:s=1280x720:d=4:r=25', + '-vf', filters, + '-c:v', 'libx264', + '-pix_fmt', 'yuv420p', + outputPath + ], { encoding: 'utf8' }); + + if (result.status !== 0) { + const message = [result.stdout, result.stderr].filter(Boolean).join('\n'); + throw new Error(`ffmpeg failed to render demo.mp4:\n${message}`); + } +} + +function drawText(font, text, x, y, size, color) { + return `drawtext=fontfile='${font}':text='${escapeDrawText(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`; +} + +function escapeDrawText(value) { + return String(value) + .replace(/\\/g, '\\\\') + .replace(/:/g, '\\:') + .replace(/'/g, "\\'") + .replace(/,/g, '\\,') + .replace(/&/g, '\\&'); +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/enterprise-admin-alert-escalation-guard/index.js b/enterprise-admin-alert-escalation-guard/index.js new file mode 100644 index 00000000..d4b2e9f1 --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/index.js @@ -0,0 +1,242 @@ +'use strict'; + +const crypto = require('crypto'); + +const DEFAULT_POLICY = Object.freeze({ + asOfDate: '2026-05-22', + criticalSeverities: ['critical', 'high'], + urgentCategories: ['compliance_deadline', 'security_integration', 'export_failure', 'irb_risk'], + maxDigestDelayHours: 4, + maxWebhookFailureRate: 0.15, + maxDuplicateWindowHours: 24, + quietHoursOverrideSeverities: ['critical', 'high'] +}); + +function stableStringify(value) { + if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`; + if (value && typeof value === 'object') { + return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`; + } + return JSON.stringify(value); +} + +function digest(value) { + return crypto.createHash('sha256').update(stableStringify(value)).digest('hex'); +} + +function normalizeToken(value) { + return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); +} + +function addFinding(findings, code, severity, message, remediation) { + findings.push({ code, severity, message, remediation }); +} + +function hoursUntil(dueAt, asOfDate) { + if (!dueAt) return null; + const due = new Date(dueAt); + const asOf = new Date(`${asOfDate}T00:00:00Z`); + if (Number.isNaN(due.getTime())) throw new Error(`Invalid dueAt: ${dueAt}`); + return Math.round((due.getTime() - asOf.getTime()) / (60 * 60 * 1000)); +} + +function evaluateAlert(alert, options = {}) { + const policy = Object.assign({}, DEFAULT_POLICY, options.policy || {}, { + asOfDate: options.asOfDate || (options.policy && options.policy.asOfDate) || DEFAULT_POLICY.asOfDate + }); + const severity = normalizeToken(alert.severity || 'low'); + const category = normalizeToken(alert.category || 'general'); + const routing = alert.routing || {}; + const owner = alert.owner || {}; + const webhook = alert.webhook || {}; + const digestSettings = routing.digest || {}; + const findings = []; + const urgent = policy.criticalSeverities.includes(severity) || policy.urgentCategories.includes(category); + const dueInHours = hoursUntil(alert.dueAt, policy.asOfDate); + + if (urgent && digestSettings.enabled && Number(digestSettings.delayHours || 0) > policy.maxDigestDelayHours) { + addFinding( + findings, + 'urgent_alert_delayed_by_digest', + 'critical', + `${alert.id} is urgent but digest delay is ${digestSettings.delayHours} hours`, + 'bypass digest and deliver immediately to primary plus escalation owners' + ); + } + + if (urgent && routing.muted === true) { + addFinding( + findings, + 'urgent_alert_muted', + 'critical', + `${alert.id} is urgent but owner preferences mute the route`, + 'override mute for compliance-critical alert and log preference bypass' + ); + } + + if (routing.quietHours === true && policy.quietHoursOverrideSeverities.includes(severity)) { + addFinding( + findings, + 'quiet_hours_override_required', + severity === 'critical' ? 'critical' : 'high', + `${alert.id} falls in quiet hours but severity is ${severity}`, + 'use emergency channel and record quiet-hours override justification' + ); + } + + if (owner.active === false || owner.onLeave === true) { + addFinding( + findings, + 'inactive_owner_route', + 'high', + `${alert.id} is routed to inactive or unavailable owner ${owner.id || 'unknown'}`, + 'route to delegated owner group and update dashboard ownership metadata' + ); + } + + if (webhook.required && Number(webhook.failureRate || 0) > policy.maxWebhookFailureRate) { + addFinding( + findings, + 'webhook_health_fallback_required', + urgent ? 'high' : 'medium', + `${alert.id} webhook failure rate ${(Number(webhook.failureRate) * 100).toFixed(1)}% exceeds policy`, + 'fallback to admin inbox and include webhook repair action in the packet' + ); + } + + if (routing.duplicateSuppressed === true && dueInHours !== null && dueInHours <= policy.maxDuplicateWindowHours) { + addFinding( + findings, + 'duplicate_suppression_near_deadline', + 'high', + `${alert.id} is deduplicated with ${dueInHours} hours before deadline`, + 'show one canonical alert and keep deadline badge visible until acknowledged' + ); + } + + if (!routing.channels || routing.channels.length === 0) { + addFinding( + findings, + 'missing_delivery_channel', + urgent ? 'high' : 'medium', + `${alert.id} has no delivery channel`, + 'assign admin inbox channel and require owner acknowledgement' + ); + } + + const selected = selectAction(findings); + const packet = { + alertId: alert.id, + category, + severity, + dueInHours, + urgent, + action: selected.action, + decisionSeverity: selected.severity, + reason: selected.reason, + ownerId: owner.id || null, + routedChannels: routing.channels || [], + webhookRequired: Boolean(webhook.required), + findingCount: findings.length, + findings, + dashboardBadges: buildDashboardBadges(alert, findings), + webhookEvent: buildWebhookEvent(alert, selected, findings) + }; + + packet.auditDigest = digest(Object.assign({}, packet, { auditDigest: undefined })); + return packet; +} + +function selectAction(findings) { + if (findings.some((finding) => finding.severity === 'critical')) { + return { + action: 'deliver_now_and_escalate', + severity: 'critical', + reason: 'urgent enterprise alert would otherwise be muted, delayed, or hidden' + }; + } + + if (findings.some((finding) => finding.severity === 'high')) { + return { + action: 'hold_digest_and_route_owner_fallback', + severity: 'high', + reason: 'alert requires owner fallback or visible escalation before digesting' + }; + } + + if (findings.some((finding) => finding.severity === 'medium')) { + return { + action: 'warn_and_repair_delivery_path', + severity: 'medium', + reason: 'delivery path should be repaired while alert remains visible' + }; + } + + return { + action: 'allow_digest_or_standard_delivery', + severity: 'low', + reason: 'alert routing satisfies admin notification policy' + }; +} + +function buildDashboardBadges(alert, findings) { + const badges = []; + if (findings.some((finding) => finding.code.includes('digest') || finding.code.includes('muted'))) badges.push('bypass-digest'); + if (findings.some((finding) => finding.code.includes('owner'))) badges.push('owner-fallback'); + if (findings.some((finding) => finding.code.includes('webhook'))) badges.push('webhook-fallback'); + if (findings.some((finding) => finding.code.includes('deadline'))) badges.push('deadline-visible'); + if (badges.length === 0 && normalizeToken(alert.severity) === 'low') badges.push('standard-delivery'); + return badges; +} + +function buildWebhookEvent(alert, selected, findings) { + return { + eventType: 'enterprise.alert.routing.reviewed', + alertId: alert.id, + action: selected.action, + findingCodes: findings.map((finding) => finding.code), + requiresAcknowledgement: selected.severity === 'critical' || selected.severity === 'high', + eventDigest: digest({ + alertId: alert.id, + action: selected.action, + findingCodes: findings.map((finding) => finding.code) + }) + }; +} + +function evaluateAlerts(alerts, options = {}) { + const packets = alerts.map((alert) => evaluateAlert(alert, options)); + const summary = packets.reduce((acc, packet) => { + acc.byAction[packet.action] = (acc.byAction[packet.action] || 0) + 1; + acc.bySeverity[packet.decisionSeverity] = (acc.bySeverity[packet.decisionSeverity] || 0) + 1; + if (packet.decisionSeverity === 'critical' || packet.decisionSeverity === 'high') acc.escalatedAlerts.push(packet.alertId); + acc.findingCount += packet.findingCount; + return acc; + }, { + byAction: {}, + bySeverity: {}, + escalatedAlerts: [], + findingCount: 0 + }); + + const portfolio = { + asOfDate: options.asOfDate || DEFAULT_POLICY.asOfDate, + alertCount: packets.length, + findingCount: summary.findingCount, + escalatedAlerts: summary.escalatedAlerts, + byAction: summary.byAction, + bySeverity: summary.bySeverity, + packets + }; + + portfolio.auditDigest = digest(portfolio); + return portfolio; +} + +module.exports = { + DEFAULT_POLICY, + digest, + evaluateAlert, + evaluateAlerts, + hoursUntil +}; diff --git a/enterprise-admin-alert-escalation-guard/package.json b/enterprise-admin-alert-escalation-guard/package.json new file mode 100644 index 00000000..74abe3f3 --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "enterprise-admin-alert-escalation-guard", + "version": "1.0.0", + "private": true, + "description": "Synthetic enterprise admin notification escalation guard for SCIBASE enterprise tooling.", + "main": "index.js", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js", + "test": "node test.js", + "demo": "node demo.js" + }, + "license": "MIT" +} diff --git a/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-packet.json b/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-packet.json new file mode 100644 index 00000000..395ab803 --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-packet.json @@ -0,0 +1,202 @@ +{ + "asOfDate": "2026-05-22", + "alertCount": 4, + "findingCount": 8, + "escalatedAlerts": [ + "ALERT-COMPLIANCE-001", + "ALERT-WEBHOOK-022" + ], + "byAction": { + "deliver_now_and_escalate": 1, + "hold_digest_and_route_owner_fallback": 1, + "warn_and_repair_delivery_path": 1, + "allow_digest_or_standard_delivery": 1 + }, + "bySeverity": { + "critical": 1, + "high": 1, + "medium": 1, + "low": 1 + }, + "packets": [ + { + "alertId": "ALERT-COMPLIANCE-001", + "category": "compliance_deadline", + "severity": "critical", + "dueInHours": 18, + "urgent": true, + "action": "deliver_now_and_escalate", + "decisionSeverity": "critical", + "reason": "urgent enterprise alert would otherwise be muted, delayed, or hidden", + "ownerId": "research-office-primary", + "routedChannels": [ + "admin_digest" + ], + "webhookRequired": true, + "findingCount": 6, + "findings": [ + { + "code": "urgent_alert_delayed_by_digest", + "severity": "critical", + "message": "ALERT-COMPLIANCE-001 is urgent but digest delay is 12 hours", + "remediation": "bypass digest and deliver immediately to primary plus escalation owners" + }, + { + "code": "urgent_alert_muted", + "severity": "critical", + "message": "ALERT-COMPLIANCE-001 is urgent but owner preferences mute the route", + "remediation": "override mute for compliance-critical alert and log preference bypass" + }, + { + "code": "quiet_hours_override_required", + "severity": "critical", + "message": "ALERT-COMPLIANCE-001 falls in quiet hours but severity is critical", + "remediation": "use emergency channel and record quiet-hours override justification" + }, + { + "code": "inactive_owner_route", + "severity": "high", + "message": "ALERT-COMPLIANCE-001 is routed to inactive or unavailable owner research-office-primary", + "remediation": "route to delegated owner group and update dashboard ownership metadata" + }, + { + "code": "webhook_health_fallback_required", + "severity": "high", + "message": "ALERT-COMPLIANCE-001 webhook failure rate 32.0% exceeds policy", + "remediation": "fallback to admin inbox and include webhook repair action in the packet" + }, + { + "code": "duplicate_suppression_near_deadline", + "severity": "high", + "message": "ALERT-COMPLIANCE-001 is deduplicated with 18 hours before deadline", + "remediation": "show one canonical alert and keep deadline badge visible until acknowledged" + } + ], + "dashboardBadges": [ + "bypass-digest", + "owner-fallback", + "webhook-fallback", + "deadline-visible" + ], + "webhookEvent": { + "eventType": "enterprise.alert.routing.reviewed", + "alertId": "ALERT-COMPLIANCE-001", + "action": "deliver_now_and_escalate", + "findingCodes": [ + "urgent_alert_delayed_by_digest", + "urgent_alert_muted", + "quiet_hours_override_required", + "inactive_owner_route", + "webhook_health_fallback_required", + "duplicate_suppression_near_deadline" + ], + "requiresAcknowledgement": true, + "eventDigest": "699c9912707d495d6a9b34fd099859ed00df5a8ced1cbd78fa74d1b738caa1f5" + }, + "auditDigest": "0e910b57e1e875b89e998e647ee6871aa620caabc4422ee34f80d1b170e48f4d" + }, + { + "alertId": "ALERT-WEBHOOK-022", + "category": "security_integration", + "severity": "high", + "dueInHours": 57, + "urgent": true, + "action": "hold_digest_and_route_owner_fallback", + "decisionSeverity": "high", + "reason": "alert requires owner fallback or visible escalation before digesting", + "ownerId": "integration-admin", + "routedChannels": [ + "slack", + "admin_inbox" + ], + "webhookRequired": true, + "findingCount": 1, + "findings": [ + { + "code": "webhook_health_fallback_required", + "severity": "high", + "message": "ALERT-WEBHOOK-022 webhook failure rate 24.0% exceeds policy", + "remediation": "fallback to admin inbox and include webhook repair action in the packet" + } + ], + "dashboardBadges": [ + "webhook-fallback" + ], + "webhookEvent": { + "eventType": "enterprise.alert.routing.reviewed", + "alertId": "ALERT-WEBHOOK-022", + "action": "hold_digest_and_route_owner_fallback", + "findingCodes": [ + "webhook_health_fallback_required" + ], + "requiresAcknowledgement": true, + "eventDigest": "851c86c5b4962302b9f916a8dcbf79812b6bd3a7b0e102f3d0830635e11f0940" + }, + "auditDigest": "3b59e442a407076677b3e19f2b7a9332da97a5f637707eeaf4e22b461ac7fe7a" + }, + { + "alertId": "ALERT-DASHBOARD-077", + "category": "usage_anomaly", + "severity": "medium", + "dueInHours": 180, + "urgent": false, + "action": "warn_and_repair_delivery_path", + "decisionSeverity": "medium", + "reason": "delivery path should be repaired while alert remains visible", + "ownerId": "department-chair", + "routedChannels": [], + "webhookRequired": false, + "findingCount": 1, + "findings": [ + { + "code": "missing_delivery_channel", + "severity": "medium", + "message": "ALERT-DASHBOARD-077 has no delivery channel", + "remediation": "assign admin inbox channel and require owner acknowledgement" + } + ], + "dashboardBadges": [], + "webhookEvent": { + "eventType": "enterprise.alert.routing.reviewed", + "alertId": "ALERT-DASHBOARD-077", + "action": "warn_and_repair_delivery_path", + "findingCodes": [ + "missing_delivery_channel" + ], + "requiresAcknowledgement": false, + "eventDigest": "6d6746d55dc430be4bc450358fc95d10cab304a7959aa2160d26c5151fdf7da2" + }, + "auditDigest": "61e2358f2125cb99be6c435441e7613b1b264faff6bad4b94f899e46e26b798f" + }, + { + "alertId": "ALERT-INFO-100", + "category": "weekly_summary", + "severity": "low", + "dueInHours": 348, + "urgent": false, + "action": "allow_digest_or_standard_delivery", + "decisionSeverity": "low", + "reason": "alert routing satisfies admin notification policy", + "ownerId": "research-ops", + "routedChannels": [ + "admin_digest" + ], + "webhookRequired": false, + "findingCount": 0, + "findings": [], + "dashboardBadges": [ + "standard-delivery" + ], + "webhookEvent": { + "eventType": "enterprise.alert.routing.reviewed", + "alertId": "ALERT-INFO-100", + "action": "allow_digest_or_standard_delivery", + "findingCodes": [], + "requiresAcknowledgement": false, + "eventDigest": "04a40a820e3ad0ed259e8c05153cdb1c53b3e4aec11230d167095bf898da5f02" + }, + "auditDigest": "d50f57932c5a1a13a3ce187053be1b8a88b57cf8546250db2ed605a27ab009c7" + } + ], + "auditDigest": "828b10bdaf88976168cd68d7b4b5506a0565b681bfd2d5d0f25395bfcb4254bc" +} diff --git a/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-report.md b/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-report.md new file mode 100644 index 00000000..bc2340ab --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/reports/admin-alert-escalation-report.md @@ -0,0 +1,27 @@ +# Enterprise Admin Alert Escalation Guard Report + +As of: 2026-05-22 +Alert digest: `828b10bdaf88976168cd68d7b4b5506a0565b681bfd2d5d0f25395bfcb4254bc` + +## Summary + +- Alerts reviewed: 4 +- Findings: 8 +- Escalated alerts: ALERT-COMPLIANCE-001, ALERT-WEBHOOK-022 +- Actions: deliver_now_and_escalate=1, hold_digest_and_route_owner_fallback=1, warn_and_repair_delivery_path=1, allow_digest_or_standard_delivery=1 + +## Alert Decisions + +| Alert | Category | Severity | Action | Findings | Dashboard badges | +| --- | --- | --- | --- | ---: | --- | +| ALERT-COMPLIANCE-001 | compliance_deadline | critical | deliver_now_and_escalate | 6 | bypass-digest, owner-fallback, webhook-fallback, deadline-visible | +| ALERT-WEBHOOK-022 | security_integration | high | hold_digest_and_route_owner_fallback | 1 | webhook-fallback | +| ALERT-DASHBOARD-077 | usage_anomaly | medium | warn_and_repair_delivery_path | 1 | none | +| ALERT-INFO-100 | weekly_summary | low | allow_digest_or_standard_delivery | 0 | standard-delivery | + +## Guardrails + +- Uses synthetic enterprise alert, owner, routing, digest, and webhook metadata only. +- Does not deliver live email, SMS, Slack, webhook, or external provider notifications. +- Keeps critical compliance/security alerts visible even when muted, digested, quiet-hours, duplicated, or routed to unavailable owners. +- Emits deterministic dashboard badges, webhook-ready event summaries, and audit digests. diff --git a/enterprise-admin-alert-escalation-guard/reports/demo.mp4 b/enterprise-admin-alert-escalation-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f765f58e6340d9d2801e4efb7e62404f38cbe281 GIT binary patch literal 58187 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 + + Enterprise admin alert escalation guard summary + Synthetic enterprise alert delivery and escalation decisions. + + + Enterprise admin alert escalation guard + Mute, digest, owner, webhook, and quiet-hours routing checks + + 4 alerts + admin routing + + 2 escalated + mute bypass + + 8 findings + review packets + Actions: deliver_now_and_escalate (1) / hold_digest_and_route_owner_fallback (1) / warn_and_repair_delivery_path (1) / allow_digest_or_standard_delivery (1) + Digest: 828b10bdaf88976168cd68d7b4b5506a... + diff --git a/enterprise-admin-alert-escalation-guard/sample-data.js b/enterprise-admin-alert-escalation-guard/sample-data.js new file mode 100644 index 00000000..d26ac24e --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/sample-data.js @@ -0,0 +1,64 @@ +'use strict'; + +module.exports = [ + { + id: 'ALERT-COMPLIANCE-001', + category: 'compliance_deadline', + severity: 'critical', + dueAt: '2026-05-22T18:00:00Z', + owner: { id: 'research-office-primary', active: false, onLeave: true }, + routing: { + channels: ['admin_digest'], + muted: true, + quietHours: true, + duplicateSuppressed: true, + digest: { enabled: true, delayHours: 12 } + }, + webhook: { required: true, failureRate: 0.32 } + }, + { + id: 'ALERT-WEBHOOK-022', + category: 'security_integration', + severity: 'high', + dueAt: '2026-05-24T09:00:00Z', + owner: { id: 'integration-admin', active: true, onLeave: false }, + routing: { + channels: ['slack', 'admin_inbox'], + muted: false, + quietHours: false, + duplicateSuppressed: false, + digest: { enabled: false, delayHours: 0 } + }, + webhook: { required: true, failureRate: 0.24 } + }, + { + id: 'ALERT-DASHBOARD-077', + category: 'usage_anomaly', + severity: 'medium', + dueAt: '2026-05-29T12:00:00Z', + owner: { id: 'department-chair', active: true, onLeave: false }, + routing: { + channels: [], + muted: false, + quietHours: false, + duplicateSuppressed: false, + digest: { enabled: true, delayHours: 2 } + }, + webhook: { required: false, failureRate: 0 } + }, + { + id: 'ALERT-INFO-100', + category: 'weekly_summary', + severity: 'low', + dueAt: '2026-06-05T12:00:00Z', + owner: { id: 'research-ops', active: true, onLeave: false }, + routing: { + channels: ['admin_digest'], + muted: false, + quietHours: false, + duplicateSuppressed: false, + digest: { enabled: true, delayHours: 2 } + }, + webhook: { required: false, failureRate: 0 } + } +]; diff --git a/enterprise-admin-alert-escalation-guard/test.js b/enterprise-admin-alert-escalation-guard/test.js new file mode 100644 index 00000000..b9883f9a --- /dev/null +++ b/enterprise-admin-alert-escalation-guard/test.js @@ -0,0 +1,98 @@ +'use strict'; + +const assert = require('assert'); +const sampleAlerts = require('./sample-data'); +const { evaluateAlert, evaluateAlerts, hoursUntil } = require('./index'); + +const asOfDate = '2026-05-22'; + +function testHoursUntil() { + assert.equal(hoursUntil('2026-05-22T18:00:00Z', asOfDate), 18); +} + +function testCriticalAlertBypassesMuteDigestAndQuietHours() { + const packet = evaluateAlert(sampleAlerts[0], { asOfDate }); + + assert.equal(packet.action, 'deliver_now_and_escalate'); + assert.equal(packet.decisionSeverity, 'critical'); + assert.ok(packet.findings.some((finding) => finding.code === 'urgent_alert_muted')); + assert.ok(packet.findings.some((finding) => finding.code === 'urgent_alert_delayed_by_digest')); + assert.ok(packet.findings.some((finding) => finding.code === 'quiet_hours_override_required')); + assert.ok(packet.dashboardBadges.includes('bypass-digest')); + assert.equal(packet.webhookEvent.requiresAcknowledgement, true); +} + +function testWebhookFailureFallbackForHighSeverity() { + const packet = evaluateAlert(sampleAlerts[1], { asOfDate }); + + assert.equal(packet.action, 'hold_digest_and_route_owner_fallback'); + assert.equal(packet.decisionSeverity, 'high'); + assert.ok(packet.findings.some((finding) => finding.code === 'webhook_health_fallback_required')); + assert.ok(packet.dashboardBadges.includes('webhook-fallback')); +} + +function testMissingChannelWarnsMediumAlert() { + const packet = evaluateAlert(sampleAlerts[2], { asOfDate }); + + assert.equal(packet.action, 'warn_and_repair_delivery_path'); + assert.equal(packet.decisionSeverity, 'medium'); + assert.ok(packet.findings.some((finding) => finding.code === 'missing_delivery_channel')); +} + +function testStandardDigestAllowed() { + const packet = evaluateAlert(sampleAlerts[3], { asOfDate }); + + assert.equal(packet.action, 'allow_digest_or_standard_delivery'); + assert.equal(packet.findingCount, 0); + assert.ok(packet.dashboardBadges.includes('standard-delivery')); +} + +function testDuplicateSuppressionNearDeadline() { + const packet = evaluateAlert({ + id: 'ALERT-DEDUPE', + category: 'export_failure', + severity: 'high', + dueAt: '2026-05-22T20:00:00Z', + owner: { id: 'export-admin', active: true, onLeave: false }, + routing: { + channels: ['admin_inbox'], + muted: false, + quietHours: false, + duplicateSuppressed: true, + digest: { enabled: false, delayHours: 0 } + }, + webhook: { required: false, failureRate: 0 } + }, { asOfDate }); + + assert.equal(packet.action, 'hold_digest_and_route_owner_fallback'); + assert.ok(packet.findings.some((finding) => finding.code === 'duplicate_suppression_near_deadline')); + assert.ok(packet.dashboardBadges.includes('deadline-visible')); +} + +function testPortfolioSummary() { + const portfolio = evaluateAlerts(sampleAlerts, { asOfDate }); + + assert.equal(portfolio.alertCount, 4); + assert.equal(portfolio.byAction.deliver_now_and_escalate, 1); + assert.equal(portfolio.byAction.hold_digest_and_route_owner_fallback, 1); + assert.equal(portfolio.byAction.warn_and_repair_delivery_path, 1); + assert.equal(portfolio.byAction.allow_digest_or_standard_delivery, 1); + assert.ok(portfolio.escalatedAlerts.includes('ALERT-COMPLIANCE-001')); + assert.equal(portfolio.auditDigest.length, 64); +} + +const tests = [ + testHoursUntil, + testCriticalAlertBypassesMuteDigestAndQuietHours, + testWebhookFailureFallbackForHighSeverity, + testMissingChannelWarnsMediumAlert, + testStandardDigestAllowed, + testDuplicateSuppressionNearDeadline, + testPortfolioSummary +]; + +for (const test of tests) { + test(); +} + +console.log(`${tests.length} enterprise admin alert escalation guard tests passed`);