MotorSignal-App/website/lib/ops-data.ts

474 lines
17 KiB
TypeScript

import {
OperationsHealth,
SafeAuditEvent,
ComponentHealthStatus,
HealthComponentKey,
OperationsOverviewData,
OverviewSecurityErrorItem,
SafeIncidentSummary,
} from '../components/ops/types';
export const HEALTH_COMPONENT_METADATA: Record<
HealthComponentKey,
{ name: string; description: string }
> = {
operations_api: {
name: 'Operations API',
description: 'FastAPI core endpoint runtime & ingress',
},
operations_database: {
name: 'Operations Database',
description: 'PostgreSQL ops schema relational persistence',
},
session_validation: {
name: 'Session Validation',
description: '__Host-motorsignal_ops token verification & authority',
},
audit_persistence: {
name: 'Audit Persistence',
description: 'Immutable operational security activity persistence',
},
};
export const APPROVED_AUDIT_METADATA_KEYS = new Set([
'component',
'status_code',
'subsystem',
'duration_ms',
'policy',
'action_scope',
'session_valid',
'mfa_verified',
'migration_version',
'records_affected',
]);
export function isValidHealthStatus(status: unknown): status is ComponentHealthStatus {
return (
status === 'available' ||
status === 'degraded' ||
status === 'unavailable' ||
status === 'unknown'
);
}
export function normalizeHealthResponse(raw: unknown): OperationsHealth {
const fallbackCheckedAt = new Date().toISOString();
if (!raw || typeof raw !== 'object') {
return {
status: 'unavailable',
checked_at: fallbackCheckedAt,
components: {
operations_api: { status: 'unavailable', description: HEALTH_COMPONENT_METADATA.operations_api.description },
operations_database: { status: 'unavailable', description: HEALTH_COMPONENT_METADATA.operations_database.description },
session_validation: { status: 'unavailable', description: HEALTH_COMPONENT_METADATA.session_validation.description },
audit_persistence: { status: 'unavailable', description: HEALTH_COMPONENT_METADATA.audit_persistence.description },
},
};
}
const obj = raw as Record<string, unknown>;
const rawStatus = obj.status;
const overallStatus: ComponentHealthStatus = isValidHealthStatus(rawStatus)
? rawStatus
: 'unknown';
const checkedAt =
typeof obj.checked_at === 'string' && obj.checked_at.length > 0
? obj.checked_at
: fallbackCheckedAt;
const rawComponents =
obj.components && typeof obj.components === 'object'
? (obj.components as Record<string, unknown>)
: {};
const keys: HealthComponentKey[] = [
'operations_api',
'operations_database',
'session_validation',
'audit_persistence',
];
const components: Record<
HealthComponentKey,
{ status: ComponentHealthStatus; description: string }
> = {
operations_api: { status: 'unknown', description: HEALTH_COMPONENT_METADATA.operations_api.description },
operations_database: { status: 'unknown', description: HEALTH_COMPONENT_METADATA.operations_database.description },
session_validation: { status: 'unknown', description: HEALTH_COMPONENT_METADATA.session_validation.description },
audit_persistence: { status: 'unknown', description: HEALTH_COMPONENT_METADATA.audit_persistence.description },
};
for (const key of keys) {
const rawComp = rawComponents[key] as Record<string, unknown> | undefined;
const compStatus = rawComp && isValidHealthStatus(rawComp.status)
? rawComp.status
: isValidHealthStatus(obj[key])
? (obj[key] as ComponentHealthStatus)
: overallStatus;
components[key] = {
status: compStatus,
description: (rawComp?.description as string) || HEALTH_COMPONENT_METADATA[key].description,
};
}
return {
status: overallStatus,
checked_at: checkedAt,
components,
};
}
export function sanitizeAuditEvent(raw: unknown): SafeAuditEvent | null {
if (!raw || typeof raw !== 'object') {
return null; // Fail closed
}
const obj = raw as Record<string, unknown>;
// Required: timestamp (ISO string)
if (typeof obj.timestamp !== 'string' || !obj.timestamp.trim()) {
return null;
}
const timestamp = obj.timestamp.trim();
// Required: action
if (typeof obj.action !== 'string' || !obj.action.trim()) {
return null;
}
const action = obj.action.trim();
// Required: result
if (typeof obj.result !== 'string' || !obj.result.trim()) {
return null;
}
const result = obj.result.trim();
// Required: actor_identity (exact OPS-3B contract, no actor_email alias)
if (typeof obj.actor_identity !== 'string' || !obj.actor_identity.trim()) {
return null; // Fail closed: missing verified identity
}
const actor_identity = obj.actor_identity.trim();
// Optional transport ID (unrendered in UI)
const id =
typeof obj.id === 'string' && obj.id.trim()
? obj.id.trim()
: undefined;
// Optional: actor_role
const actor_role =
typeof obj.actor_role === 'string' && obj.actor_role.trim()
? obj.actor_role.trim()
: undefined;
// Optional: target_resource (exact OPS-3B contract, no target alias)
const target_resource =
typeof obj.target_resource === 'string' && obj.target_resource.trim()
? obj.target_resource.trim()
: undefined;
// Optional: reason_code
const reason_code =
typeof obj.reason_code === 'string' && obj.reason_code.trim()
? obj.reason_code.trim()
: undefined;
// Optional: case_reference
const case_reference =
typeof obj.case_reference === 'string' && obj.case_reference.trim()
? obj.case_reference.trim()
: undefined;
// Explicit Allowlist Metadata Projection only
let metadata: Record<string, string | number | boolean> | undefined = undefined;
if (obj.metadata && typeof obj.metadata === 'object' && !Array.isArray(obj.metadata)) {
const rawMeta = obj.metadata as Record<string, unknown>;
const safeMeta: Record<string, string | number | boolean> = {};
for (const [k, v] of Object.entries(rawMeta)) {
if (!APPROVED_AUDIT_METADATA_KEYS.has(k)) {
continue; // Drop unapproved metadata key
}
if (typeof v === 'string' && v.length <= 100) {
safeMeta[k] = v;
} else if (typeof v === 'number' || typeof v === 'boolean') {
safeMeta[k] = v;
}
}
if (Object.keys(safeMeta).length > 0) {
metadata = safeMeta;
}
}
return {
id,
timestamp,
action,
result,
actor_identity,
actor_role,
target_resource,
reason_code,
case_reference,
metadata,
};
}
// ============================================================
// Incident Sanitizers & Validators (OPS-4)
// ============================================================
export function isValidIncidentSeverity(severity: unknown): severity is import('../components/ops/types').IncidentSeverity {
return (
severity === 'low' ||
severity === 'medium' ||
severity === 'high' ||
severity === 'critical'
);
}
export function isValidIncidentStatus(status: unknown): status is import('../components/ops/types').IncidentStatus {
return (
status === 'open' ||
status === 'investigating' ||
status === 'monitoring' ||
status === 'resolved' ||
status === 'closed' ||
status === 'archived'
);
}
const ALLOWED_INCIDENT_TRANSITIONS: Record<string, Set<string>> = {
open: new Set(['investigating', 'resolved', 'closed']),
investigating: new Set(['monitoring', 'resolved', 'closed']),
monitoring: new Set(['investigating', 'resolved', 'closed']),
resolved: new Set(['closed', 'investigating']),
closed: new Set(['investigating']), // Reopen (Owner only)
archived: new Set(), // Terminal
};
export function isValidStatusTransition(
current: import('../components/ops/types').IncidentStatus,
target: import('../components/ops/types').IncidentStatus,
role: import('../components/ops/types').OperationsRole
): boolean {
if (current === target) return false;
if (current === 'archived') return false; // Terminal
if (target === 'archived') return role === 'owner'; // Owner only
if (current === 'closed' && target === 'investigating') return role === 'owner'; // Owner only
const allowed = ALLOWED_INCIDENT_TRANSITIONS[current];
return allowed ? allowed.has(target) : false;
}
export function sanitizeIncidentSummary(raw: unknown): import('../components/ops/types').SafeIncidentSummary | null {
if (!raw || typeof raw !== 'object') return null;
const obj = raw as Record<string, unknown>;
if (typeof obj.case_reference !== 'string' || !obj.case_reference.trim()) return null;
const case_reference = obj.case_reference.trim();
if (typeof obj.title !== 'string' || !obj.title.trim()) return null;
const title = obj.title.trim();
if (!isValidIncidentSeverity(obj.severity)) return null;
const severity = obj.severity;
if (!isValidIncidentStatus(obj.status)) return null;
const status = obj.status;
if (typeof obj.created_at !== 'string' || !obj.created_at.trim()) return null;
const created_at = obj.created_at.trim();
const updated_at = typeof obj.updated_at === 'string' && obj.updated_at.trim()
? obj.updated_at.trim()
: created_at;
return {
case_reference,
title,
severity,
status,
created_at,
updated_at,
};
}
export function sanitizeIncidentDetail(raw: unknown): import('../components/ops/types').SafeIncidentDetail | null {
if (!raw || typeof raw !== 'object') return null;
const obj = raw as Record<string, unknown>;
const summaryData = sanitizeIncidentSummary(raw);
if (!summaryData) return null;
if (typeof obj.summary !== 'string' || !obj.summary.trim()) return null;
const summary = obj.summary.trim();
if (typeof obj.created_by_identity !== 'string' || !obj.created_by_identity.trim()) return null;
const created_by_identity = obj.created_by_identity.trim();
const closed_at = typeof obj.closed_at === 'string' && obj.closed_at.trim()
? obj.closed_at.trim()
: null;
const resolution = typeof obj.resolution === 'string' && obj.resolution.trim()
? obj.resolution.trim()
: null;
const closed_by_identity = typeof obj.closed_by_identity === 'string' && obj.closed_by_identity.trim()
? obj.closed_by_identity.trim()
: null;
return {
...summaryData,
summary,
created_by_identity,
closed_at,
resolution,
closed_by_identity,
};
}
export function sanitizeIncidentNote(raw: unknown): import('../components/ops/types').SafeIncidentNote | null {
if (!raw || typeof raw !== 'object') return null;
const obj = raw as Record<string, unknown>;
if (typeof obj.created_at !== 'string' || !obj.created_at.trim()) return null;
const created_at = obj.created_at.trim();
if (typeof obj.created_by_identity !== 'string' || !obj.created_by_identity.trim()) return null;
const created_by_identity = obj.created_by_identity.trim();
if (typeof obj.body !== 'string' || !obj.body.trim()) return null;
const body = obj.body.trim();
return {
created_at,
created_by_identity,
body,
};
}
export function normalizeOverviewResponse(raw: unknown): OperationsOverviewData {
const fallbackGeneratedAt = new Date().toISOString();
const defaultHealth = normalizeHealthResponse(null);
if (!raw || typeof raw !== 'object') {
return {
generated_at: fallbackGeneratedAt,
users: {
total_registered: 0,
active_24h: 0,
active_7d: 0,
new_this_week: 0,
},
membership: {
total_subscribers: 0,
premium_members: 0,
free_members: 0,
conversion_rate_pct: 0,
},
activity: {
web_active_sessions_24h: 0,
web_logins_24h: 0,
web_signups_7d: 0,
mobile_active_sessions_24h: 0,
mobile_rides_today: 0,
mobile_rides_active: 0,
mobile_datapoints_24h: 0,
},
incidents: {
total_open: 0,
critical_count: 0,
high_count: 0,
medium_count: 0,
low_count: 0,
recent_open: [],
},
errors: {
errors_24h: 0,
denials_24h: 0,
recent: [],
},
health: defaultHealth,
};
}
const obj = raw as Record<string, unknown>;
const rawUsers = obj.users && typeof obj.users === 'object' ? (obj.users as Record<string, unknown>) : {};
const rawMembership = obj.membership && typeof obj.membership === 'object' ? (obj.membership as Record<string, unknown>) : {};
const rawActivity = obj.activity && typeof obj.activity === 'object' ? (obj.activity as Record<string, unknown>) : {};
const rawIncidents = obj.incidents && typeof obj.incidents === 'object' ? (obj.incidents as Record<string, unknown>) : {};
const rawErrors = obj.errors && typeof obj.errors === 'object' ? (obj.errors as Record<string, unknown>) : {};
const rawRecentOpen = Array.isArray(rawIncidents.recent_open) ? rawIncidents.recent_open : [];
const safeRecentOpen: SafeIncidentSummary[] = rawRecentOpen
.map(sanitizeIncidentSummary)
.filter((i): i is SafeIncidentSummary => i !== null);
const rawRecentErrors = Array.isArray(rawErrors.recent) ? rawErrors.recent : [];
const safeRecentErrors: OverviewSecurityErrorItem[] = rawRecentErrors
.map((e): OverviewSecurityErrorItem | null => {
if (!e || typeof e !== 'object') return null;
const errObj = e as Record<string, unknown>;
if (typeof errObj.timestamp !== 'string' || !errObj.timestamp.trim()) return null;
if (typeof errObj.action !== 'string' || !errObj.action.trim()) return null;
if (typeof errObj.actor_identity !== 'string' || !errObj.actor_identity.trim()) return null;
return {
id: typeof errObj.id === 'string' && errObj.id.trim() ? errObj.id.trim() : undefined,
timestamp: errObj.timestamp.trim(),
action: errObj.action.trim(),
actor_identity: errObj.actor_identity.trim(),
reason_code: typeof errObj.reason_code === 'string' && errObj.reason_code.trim() ? errObj.reason_code.trim() : undefined,
result: typeof errObj.result === 'string' && errObj.result.trim() ? errObj.result.trim() : 'failure',
target_resource: typeof errObj.target_resource === 'string' && errObj.target_resource.trim() ? errObj.target_resource.trim() : undefined,
};
})
.filter((e): e is OverviewSecurityErrorItem => e !== null);
return {
generated_at: typeof obj.generated_at === 'string' && obj.generated_at.trim() ? obj.generated_at.trim() : fallbackGeneratedAt,
users: {
total_registered: typeof rawUsers.total_registered === 'number' ? rawUsers.total_registered : 0,
active_24h: typeof rawUsers.active_24h === 'number' ? rawUsers.active_24h : 0,
active_7d: typeof rawUsers.active_7d === 'number' ? rawUsers.active_7d : 0,
new_this_week: typeof rawUsers.new_this_week === 'number' ? rawUsers.new_this_week : 0,
},
membership: {
total_subscribers: typeof rawMembership.total_subscribers === 'number' ? rawMembership.total_subscribers : 0,
premium_members: typeof rawMembership.premium_members === 'number'
? rawMembership.premium_members
: typeof rawMembership.pro_members === 'number'
? rawMembership.pro_members
: 0,
free_members: typeof rawMembership.free_members === 'number' ? rawMembership.free_members : 0,
conversion_rate_pct: typeof rawMembership.conversion_rate_pct === 'number' ? rawMembership.conversion_rate_pct : 0,
},
activity: {
web_active_sessions_24h: typeof rawActivity.web_active_sessions_24h === 'number' ? rawActivity.web_active_sessions_24h : 0,
web_logins_24h: typeof rawActivity.web_logins_24h === 'number' ? rawActivity.web_logins_24h : 0,
web_signups_7d: typeof rawActivity.web_signups_7d === 'number' ? rawActivity.web_signups_7d : 0,
mobile_active_sessions_24h: typeof rawActivity.mobile_active_sessions_24h === 'number' ? rawActivity.mobile_active_sessions_24h : 0,
mobile_rides_today: typeof rawActivity.mobile_rides_today === 'number' ? rawActivity.mobile_rides_today : 0,
mobile_rides_active: typeof rawActivity.mobile_rides_active === 'number' ? rawActivity.mobile_rides_active : 0,
mobile_datapoints_24h: typeof rawActivity.mobile_datapoints_24h === 'number' ? rawActivity.mobile_datapoints_24h : 0,
},
incidents: {
total_open: typeof rawIncidents.total_open === 'number' ? rawIncidents.total_open : safeRecentOpen.length,
critical_count: typeof rawIncidents.critical_count === 'number' ? rawIncidents.critical_count : 0,
high_count: typeof rawIncidents.high_count === 'number' ? rawIncidents.high_count : 0,
medium_count: typeof rawIncidents.medium_count === 'number' ? rawIncidents.medium_count : 0,
low_count: typeof rawIncidents.low_count === 'number' ? rawIncidents.low_count : 0,
recent_open: safeRecentOpen,
},
errors: {
errors_24h: typeof rawErrors.errors_24h === 'number' ? rawErrors.errors_24h : 0,
denials_24h: typeof rawErrors.denials_24h === 'number' ? rawErrors.denials_24h : 0,
recent: safeRecentErrors,
},
health: normalizeHealthResponse(obj.health),
};
}