+
{payload.security_audit.automation_operation_count_24h}
@@ -407,7 +411,30 @@ export function SecurityAssetControlPlaneCockpit({
{copy.aiTrace}
+
+
+ {payload.summary.ready_source_count}/{payload.summary.source_count}
+
+
{copy.sources}
+
+ {payload.summary.unavailable_source_count > 0 ? (
+
+ {payload.source_health
+ .filter((source) => source.status !== "ready")
+ .slice(0, 4)
+ .map((source) => (
+
+
+ {source.source_id}
+
+ ))}
+
+ ) : null}
{!payload.ai_automation.same_run_closed_loop_proven ? (
diff --git a/apps/web/src/lib/__tests__/security-asset-control-plane.test.ts b/apps/web/src/lib/__tests__/security-asset-control-plane.test.ts
index 9f058c4ad..12782cf42 100644
--- a/apps/web/src/lib/__tests__/security-asset-control-plane.test.ts
+++ b/apps/web/src/lib/__tests__/security-asset-control-plane.test.ts
@@ -14,6 +14,9 @@ function payload(): SecurityAssetControlPlanePayload {
status: "ready",
source_status: "live_database_aggregate",
summary: {
+ source_count: 9,
+ ready_source_count: 9,
+ unavailable_source_count: 0,
managed_asset_count: 24,
expected_asset_type_count: 28,
present_asset_type_count: 20,
@@ -74,6 +77,9 @@ function payload(): SecurityAssetControlPlanePayload {
accepted_ai_trace_count_24h: 2,
immutable_external_audit_store_evidenced: false,
},
+ source_health: [
+ { source_id: "asset_inventory", status: "ready", reason_code: null },
+ ],
work_items: [
{
work_item_id: "AIA-P1-001-01",
diff --git a/apps/web/src/lib/security-asset-control-plane.ts b/apps/web/src/lib/security-asset-control-plane.ts
index 88eb2b0a2..4a13304a4 100644
--- a/apps/web/src/lib/security-asset-control-plane.ts
+++ b/apps/web/src/lib/security-asset-control-plane.ts
@@ -1,6 +1,9 @@
export type SecurityControlStatus = "ready" | "degraded";
export type SecurityControlPlaneSummary = {
+ source_count: number;
+ ready_source_count: number;
+ unavailable_source_count: number;
managed_asset_count: number;
expected_asset_type_count: number;
present_asset_type_count: number;
@@ -119,6 +122,11 @@ export type SecurityAssetControlPlanePayload = {
accepted_ai_trace_count_24h: number;
immutable_external_audit_store_evidenced: boolean;
};
+ source_health: Array<{
+ source_id: string;
+ status: "ready" | "unavailable";
+ reason_code: string | null;
+ }>;
work_items: SecurityControlPlaneWorkItem[];
boundaries: {
aggregate_only: boolean;
@@ -154,6 +162,8 @@ export function isSecurityAssetControlPlanePayload(
}
return (
typeof value.summary.managed_asset_count === "number" &&
+ typeof value.summary.source_count === "number" &&
+ Array.isArray(value.source_health) &&
typeof value.summary.nist_controlled_percent === "number" &&
value.boundaries.aggregate_only === true &&
value.boundaries.raw_asset_identity_returned === false &&