215 lines
6.4 KiB
TypeScript
215 lines
6.4 KiB
TypeScript
import {
|
|
OperationsHealth,
|
|
SafeAuditEvent,
|
|
ComponentHealthStatus,
|
|
HealthComponentKey,
|
|
} 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,
|
|
};
|
|
}
|