feat(iwooos): add live security control plane
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m22s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-11 19:45:58 +08:00
parent 05a2ee7c32
commit 0107eae239
8 changed files with 2236 additions and 0 deletions

View File

@@ -33,6 +33,7 @@ import {
import { Link } from "@/i18n/routing";
import { AutonomousRuntimeReceiptPanel } from "@/components/awooop/autonomous-runtime-receipt-panel";
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
import { SecurityAssetControlPlaneCockpit } from "@/components/iwooos/security-asset-control-plane-cockpit";
import { isMonotonicPriorityWorkOrderProjection } from "@/lib/priority-work-order-projection";
import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
import {
@@ -13077,6 +13078,8 @@ export default function AwoooPWorkItemsPage() {
locale={locale}
/>
<SecurityAssetControlPlaneCockpit locale={locale} />
<AiAutomationProgramPanel
priority={commanderInsertedRequirementWorkOrder}
loading={priorityWorkOrderLoading && !priorityWorkOrder}

View File

@@ -36,6 +36,7 @@ import Link from 'next/link'
import { useTranslations } from 'next-intl'
import { useEffect, useRef, useState, type ReactNode } from 'react'
import { AppLayout } from '@/components/layout'
import { SecurityAssetControlPlaneCockpit } from '@/components/iwooos/security-asset-control-plane-cockpit'
import { publicBoundaryText } from '@/lib/public-security-redaction'
import {
apiClient,
@@ -25152,6 +25153,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
</div>
</section>
<SecurityAssetControlPlaneCockpit locale={params.locale} className="mb-4" />
<IwoooSSecurityToolClosureBoard />
<IwoooSWorkspaceSwitcher active={workspaceView} onChange={setWorkspaceView} />
<IwoooSManagerCockpit />

View File

@@ -0,0 +1,402 @@
"use client";
import {
Activity,
Bot,
Boxes,
CheckCircle2,
RefreshCw,
Radar,
ShieldAlert,
ShieldCheck,
TriangleAlert,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
isSecurityAssetControlPlanePayload,
securityControlPlaneTone,
topSecurityControlPlaneWorkItems,
type SecurityAssetControlPlanePayload,
type SecurityControlPlaneStage,
} from "@/lib/security-asset-control-plane";
import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
import { cn } from "@/lib/utils";
type Copy = {
title: string;
live: string;
degraded: string;
assets: string;
assetTypes: string;
nist: string;
siem: string;
aiLoop: string;
verified: string;
functions: string;
siemFlow: string;
aiFlow: string;
scope: string;
priority: string;
audit: string;
aiTrace: string;
sameRunMissing: string;
unavailable: string;
refresh: string;
};
const COPY: Record<"zh" | "en", Copy> = {
zh: {
title: "AI 資安控制平面",
live: "LIVE",
degraded: "DEGRADED",
assets: "納管資產",
assetTypes: "資產類型",
nist: "NIST 控制",
siem: "SIEM 閉環",
aiLoop: "AI 閉環",
verified: "24h 已驗證",
functions: "治理六功能",
siemFlow: "SIEM 八段",
aiFlow: "AI Agent 七段",
scope: "全資產範圍",
priority: "優先處理",
audit: "稽核軌跡",
aiTrace: "AI 軌跡",
sameRunMissing: "尚無同 run 閉環證據",
unavailable: "Live readback 無法取得",
refresh: "重新整理資安控制平面",
},
en: {
title: "AI Security Control Plane",
live: "LIVE",
degraded: "DEGRADED",
assets: "Managed assets",
assetTypes: "Asset types",
nist: "NIST control",
siem: "SIEM closure",
aiLoop: "AI closure",
verified: "Verified 24h",
functions: "Six functions",
siemFlow: "SIEM pipeline",
aiFlow: "AI Agent loop",
scope: "Asset scope",
priority: "Priorities",
audit: "Audit trail",
aiTrace: "AI trace",
sameRunMissing: "No same-run closure evidence",
unavailable: "Live readback unavailable",
refresh: "Refresh security control plane",
},
};
const TONE_CLASS = {
healthy: "border-[#8fc39c] bg-[#f2faf4] text-[#17602a]",
warning: "border-[#d9b36f] bg-[#fff8e8] text-[#8a5a08]",
critical: "border-[#e2a29b] bg-[#fff2f0] text-[#9f2f25]",
};
function stageCount(stage: SecurityControlPlaneStage) {
return stage.evidence_count_24h ?? stage.receipt_count_24h ?? 0;
}
async function fetchControlPlane(): Promise<SecurityAssetControlPlanePayload | null> {
const base = getRuntimeApiBaseUrl();
const path = "/api/v1/iwooos/security-asset-control-plane";
const urls = Array.from(new Set([path, base ? `${base}${path}` : ""].filter(Boolean)));
for (const url of urls) {
try {
const response = await fetch(url, { cache: "no-store" });
if (!response.ok) continue;
const payload: unknown = await response.json();
if (isSecurityAssetControlPlanePayload(payload)) return payload;
} catch {
// Try the next same-origin/runtime API candidate.
}
}
return null;
}
function MetricCell({
label,
value,
icon: Icon,
}: {
label: string;
value: string | number;
icon: typeof ShieldCheck;
}) {
return (
<div className="min-w-0 bg-white px-3 py-3">
<div className="flex items-center gap-2 text-[11px] font-medium text-[#6f6d66]">
<Icon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
<span className="truncate">{label}</span>
</div>
<div className="mt-2 font-mono text-xl font-semibold tracking-normal text-[#141413]">
{value}
</div>
</div>
);
}
function StageStrip({
title,
stages,
}: {
title: string;
stages: SecurityControlPlaneStage[];
}) {
return (
<div className="min-w-0">
<div className="mb-2 text-[11px] font-semibold text-[#4f4d47]">{title}</div>
<div className="grid grid-cols-4 gap-1 sm:grid-cols-7 lg:grid-cols-8">
{stages.map((stage) => {
const observed = stage.status === "observed";
return (
<div
key={stage.stage_id}
className={cn(
"min-w-0 border px-1.5 py-2 text-center",
observed
? "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]"
: "border-[#e2a29b] bg-[#fff2f0] text-[#9f2f25]"
)}
title={`${stage.label}: ${stageCount(stage)}`}
>
<div className="truncate text-[10px] font-semibold">{stage.label}</div>
<div className="mt-1 font-mono text-xs">{stageCount(stage)}</div>
</div>
);
})}
</div>
</div>
);
}
export function SecurityAssetControlPlaneCockpit({
locale = "zh-TW",
className,
}: {
locale?: string;
className?: string;
}) {
const copy = COPY[locale.toLowerCase().startsWith("zh") ? "zh" : "en"];
const [payload, setPayload] = useState<SecurityAssetControlPlanePayload | null>(null);
const [loading, setLoading] = useState(true);
const load = useCallback(async () => {
setLoading(true);
const next = await fetchControlPlane();
setPayload(next);
setLoading(false);
}, []);
useEffect(() => {
void load();
const timer = window.setInterval(() => void load(), 60_000);
return () => window.clearInterval(timer);
}, [load]);
const tone = payload ? securityControlPlaneTone(payload) : "critical";
const priorityItems = useMemo(
() => (payload ? topSecurityControlPlaneWorkItems(payload, 3) : []),
[payload]
);
if (!payload && !loading) {
return (
<section
className={cn("border border-[#e2a29b] bg-[#fff2f0] px-4 py-4", className)}
data-testid="security-asset-control-plane-cockpit"
>
<div className="flex items-center gap-2 text-sm font-semibold text-[#9f2f25]">
<ShieldAlert className="h-4 w-4" aria-hidden="true" />
{copy.unavailable}
</div>
</section>
);
}
return (
<section
className={cn(
"overflow-hidden border border-[#d8d3c7] bg-[#f6f4ee]",
className
)}
data-testid="security-asset-control-plane-cockpit"
aria-busy={loading}
>
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[#d8d3c7] bg-[#141413] px-4 py-3 text-white">
<div className="flex min-w-0 items-center gap-2.5">
<ShieldCheck className="h-5 w-5 shrink-0 text-[#7fd29b]" aria-hidden="true" />
<h2 className="truncate text-sm font-semibold tracking-normal">{copy.title}</h2>
<span
className={cn(
"border px-2 py-0.5 font-mono text-[10px] font-semibold",
TONE_CLASS[tone]
)}
>
{payload?.status === "ready" ? copy.live : copy.degraded}
</span>
</div>
<button
type="button"
onClick={() => void load()}
className="inline-flex h-8 w-8 items-center justify-center border border-[#5b5953] text-[#d8d3c7] hover:border-white hover:text-white"
aria-label={copy.refresh}
title={copy.refresh}
>
<RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} aria-hidden="true" />
</button>
</div>
<div className="grid gap-px bg-[#d8d3c7] sm:grid-cols-3 lg:grid-cols-6">
<MetricCell label={copy.assets} value={payload?.summary.managed_asset_count ?? "--"} icon={Boxes} />
<MetricCell
label={copy.assetTypes}
value={payload ? `${payload.summary.asset_type_coverage_percent}%` : "--"}
icon={Activity}
/>
<MetricCell
label={copy.nist}
value={payload ? `${payload.summary.nist_controlled_percent}%` : "--"}
icon={ShieldCheck}
/>
<MetricCell
label={copy.siem}
value={payload ? `${payload.summary.siem_observed_stage_count}/${payload.summary.siem_stage_count}` : "--"}
icon={Radar}
/>
<MetricCell
label={copy.aiLoop}
value={payload ? `${payload.summary.ai_loop_observed_stage_count}/${payload.summary.ai_loop_stage_count}` : "--"}
icon={Bot}
/>
<MetricCell
label={copy.verified}
value={payload?.summary.verified_remediation_receipt_count_24h ?? "--"}
icon={CheckCircle2}
/>
</div>
{payload ? (
<div className="grid gap-4 p-4 xl:grid-cols-[1.1fr_1.4fr]">
<div className="min-w-0 space-y-4">
<div>
<div className="mb-2 text-[11px] font-semibold text-[#4f4d47]">{copy.functions}</div>
<div className="grid grid-cols-3 gap-1 sm:grid-cols-6">
{payload.nist_csf_functions.map((item) => (
<div key={item.function_id} className="border border-[#d8d3c7] bg-white px-2 py-2">
<div className="truncate text-[10px] text-[#6f6d66]">{item.label}</div>
<div className="mt-1 font-mono text-sm font-semibold text-[#141413]">
{item.controlled_percent}%
</div>
<div className="mt-1 h-1 bg-[#ebe8df]">
<div
className={cn(
"h-full",
item.controlled_percent >= 80
? "bg-[#2f8a50]"
: item.controlled_percent > 0
? "bg-[#c58a2e]"
: "bg-[#c9584d]"
)}
style={{ width: `${Math.min(100, Math.max(0, item.controlled_percent))}%` }}
/>
</div>
</div>
))}
</div>
</div>
<div>
<div className="mb-2 flex items-center justify-between gap-3 text-[11px] font-semibold text-[#4f4d47]">
<span>{copy.scope}</span>
<span className="font-mono text-[#77736a]">
{payload.summary.controlled_scope_count}/{payload.summary.scope_count}
</span>
</div>
<div className="grid gap-1 sm:grid-cols-2">
{payload.asset_scopes.map((scope) => (
<div key={scope.scope_id} className="flex items-center gap-2 border border-[#d8d3c7] bg-white px-2 py-2">
<span className="min-w-0 flex-1 truncate text-[10px] text-[#4f4d47]">{scope.label}</span>
<span className="font-mono text-[11px] font-semibold text-[#141413]">
{scope.managed_asset_count}
</span>
<span
className={cn(
"h-2 w-2 shrink-0",
scope.status === "controlled"
? "bg-[#2f8a50]"
: scope.status === "partial"
? "bg-[#c58a2e]"
: "bg-[#c9584d]"
)}
title={`${scope.type_coverage_percent}%`}
/>
</div>
))}
</div>
</div>
</div>
<div className="min-w-0 space-y-4">
<StageStrip title={copy.siemFlow} stages={payload.siem.pipeline} />
<StageStrip title={copy.aiFlow} stages={payload.ai_automation.stages} />
<div className="grid gap-3 lg:grid-cols-[1fr_1.35fr]">
<div className="border border-[#d8d3c7] bg-white p-3">
<div className="mb-2 text-[11px] font-semibold text-[#4f4d47]">{copy.audit}</div>
<div className="grid grid-cols-3 gap-2 text-center">
<div>
<div className="font-mono text-sm font-semibold text-[#141413]">
{payload.security_audit.automation_operation_count_24h}
</div>
<div className="mt-1 text-[9px] text-[#77736a]">OPS</div>
</div>
<div>
<div className="font-mono text-sm font-semibold text-[#141413]">
{payload.security_audit.mcp_tool_audit_count_24h}
</div>
<div className="mt-1 text-[9px] text-[#77736a]">MCP</div>
</div>
<div>
<div className="font-mono text-sm font-semibold text-[#141413]">
{payload.security_audit.ai_trace_count_24h}
</div>
<div className="mt-1 text-[9px] text-[#77736a]">{copy.aiTrace}</div>
</div>
</div>
{!payload.ai_automation.same_run_closed_loop_proven ? (
<div className="mt-3 flex items-center gap-1.5 border-t border-[#ebe8df] pt-2 text-[10px] font-medium text-[#9f2f25]">
<TriangleAlert className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
{copy.sameRunMissing}
</div>
) : null}
</div>
<div className="border border-[#d8d3c7] bg-white p-3">
<div className="mb-2 text-[11px] font-semibold text-[#4f4d47]">{copy.priority}</div>
<div className="space-y-1.5">
{priorityItems.map((item) => (
<div key={item.work_item_id} className="flex min-w-0 items-center gap-2">
<span className="border border-[#e2a29b] bg-[#fff2f0] px-1.5 py-0.5 font-mono text-[9px] font-semibold text-[#9f2f25]">
{item.priority}
</span>
<span className="min-w-0 flex-1 truncate text-[10px] font-medium text-[#34332f]" title={item.title}>
{item.title}
</span>
<span className="font-mono text-[10px] text-[#77736a]">{item.gap_count}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
) : (
<div className="h-64 animate-pulse bg-[#ebe8df]" />
)}
</section>
);
}

View File

@@ -0,0 +1,135 @@
import { describe, expect, it } from "vitest";
import {
isSecurityAssetControlPlanePayload,
securityControlPlaneTone,
topSecurityControlPlaneWorkItems,
type SecurityAssetControlPlanePayload,
} from "../security-asset-control-plane";
function payload(): SecurityAssetControlPlanePayload {
return {
schema_version: "iwooos_security_asset_control_plane_v1",
generated_at: "2026-07-11T04:00:00+00:00",
status: "ready",
source_status: "live_database_aggregate",
summary: {
managed_asset_count: 24,
expected_asset_type_count: 28,
present_asset_type_count: 20,
asset_type_coverage_percent: 71,
scope_count: 6,
controlled_scope_count: 3,
unowned_asset_count: 0,
stale_asset_count: 0,
orphan_asset_count: 0,
relationship_count: 40,
automation_coverage_green_percent: 85,
automation_coverage_unknown_count: 0,
compliance_violation_count: 0,
compliance_unknown_count: 0,
nist_controlled_percent: 82,
siem_stage_count: 8,
siem_observed_stage_count: 8,
siem_stage_coverage_percent: 100,
ai_loop_stage_count: 7,
ai_loop_observed_stage_count: 7,
ai_loop_stage_coverage_percent: 100,
verified_remediation_receipt_count_24h: 2,
active_work_item_count: 2,
},
discovery: {
latest_status: "success",
latest_success_at: "2026-07-11T03:50:00+00:00",
age_seconds: 600,
freshness_slo_seconds: 7200,
fresh: true,
},
asset_scopes: [],
nist_csf_functions: [],
siem: { pipeline: [] },
ai_automation: {
stages: [],
aggregate_stage_coverage_percent: 100,
accepted_ai_trace_count: 2,
verified_remediation_receipt_count: 2,
rollback_receipt_count: 0,
failed_operation_count: 0,
same_run_closed_loop_proven: false,
same_run_closed_loop_count: 0,
},
security_audit: {
k8s_execution_audit_count_24h: 2,
k8s_execution_failure_count_24h: 0,
mcp_tool_audit_count_24h: 4,
mcp_tool_failure_count_24h: 0,
asset_change_event_count_24h: 3,
automation_operation_count_24h: 12,
automation_failure_count_24h: 0,
ai_trace_count_24h: 4,
accepted_ai_trace_count_24h: 2,
immutable_external_audit_store_evidenced: false,
},
work_items: [
{
work_item_id: "AIA-P1-001-01",
priority: "P1",
control_id: "audit",
title: "audit",
gap_count: 1,
status: "open",
next_action: "audit",
},
{
work_item_id: "AIA-P0-007-01",
priority: "P0",
control_id: "siem",
title: "siem",
gap_count: 2,
status: "open",
next_action: "siem",
},
],
boundaries: {
aggregate_only: true,
raw_asset_identity_returned: false,
raw_event_payload_returned: false,
secret_value_collection_allowed: false,
live_scan_triggered: false,
runtime_action_triggered: false,
completion_requires_same_run_receipts: true,
},
};
}
describe("security asset control plane projection", () => {
it("accepts only the public-safe aggregate contract", () => {
const safe = payload();
expect(isSecurityAssetControlPlanePayload(safe)).toBe(true);
const unsafe = {
...safe,
boundaries: { ...safe.boundaries, raw_asset_identity_returned: true },
};
expect(isSecurityAssetControlPlanePayload(unsafe)).toBe(false);
});
it("does not show healthy before same-run closure is proven", () => {
const value = payload();
expect(securityControlPlaneTone(value)).toBe("warning");
value.ai_automation.same_run_closed_loop_proven = true;
expect(securityControlPlaneTone(value)).toBe("healthy");
value.discovery.fresh = false;
expect(securityControlPlaneTone(value)).toBe("critical");
});
it("keeps P0 work ahead of P1 regardless of API order", () => {
const items = topSecurityControlPlaneWorkItems(payload(), 2);
expect(items.map((item) => item.work_item_id)).toEqual([
"AIA-P0-007-01",
"AIA-P1-001-01",
]);
});
});

View File

@@ -0,0 +1,190 @@
export type SecurityControlStatus = "ready" | "degraded";
export type SecurityControlPlaneSummary = {
managed_asset_count: number;
expected_asset_type_count: number;
present_asset_type_count: number;
asset_type_coverage_percent: number;
scope_count: number;
controlled_scope_count: number;
unowned_asset_count: number;
stale_asset_count: number;
orphan_asset_count: number;
relationship_count: number;
automation_coverage_green_percent: number;
automation_coverage_unknown_count: number;
compliance_violation_count: number;
compliance_unknown_count: number;
nist_controlled_percent: number;
siem_stage_count: number;
siem_observed_stage_count: number;
siem_stage_coverage_percent: number;
ai_loop_stage_count: number;
ai_loop_observed_stage_count: number;
ai_loop_stage_coverage_percent: number;
verified_remediation_receipt_count_24h: number;
active_work_item_count: number;
};
export type SecurityControlPlaneFunction = {
function_id: string;
label: string;
controlled_percent: number;
status: string;
};
export type SecurityControlPlaneStage = {
stage_id: string;
label: string;
status: "observed" | "missing";
evidence_count_24h?: number;
receipt_count_24h?: number;
};
export type SecurityControlPlaneScope = {
scope_id: string;
label: string;
managed_asset_count: number;
expected_type_count: number;
present_type_count: number;
missing_type_count: number;
type_coverage_percent: number;
status: string;
};
export type SecurityControlPlaneWorkItem = {
work_item_id: string;
priority: string;
control_id: string;
title: string;
gap_count: number;
status: string;
next_action: string;
};
export type SecurityAssetControlPlanePayload = {
schema_version: "iwooos_security_asset_control_plane_v1";
generated_at: string;
status: SecurityControlStatus;
source_status: string;
reason_code?: string;
summary: SecurityControlPlaneSummary;
discovery: {
latest_status: string;
latest_success_at: string | null;
age_seconds: number | null;
freshness_slo_seconds: number;
fresh: boolean;
};
asset_scopes: SecurityControlPlaneScope[];
nist_csf_functions: SecurityControlPlaneFunction[];
siem: {
pipeline: SecurityControlPlaneStage[];
incident_total_24h?: number;
open_incident_count_24h?: number;
resolved_incident_count_24h?: number;
noisy_rule_count?: number;
mean_time_to_resolve_minutes_24h?: number | null;
};
ai_automation: {
stages: SecurityControlPlaneStage[];
aggregate_stage_coverage_percent: number;
accepted_ai_trace_count: number;
verified_remediation_receipt_count: number;
rollback_receipt_count: number;
failed_operation_count: number;
same_run_closed_loop_proven: boolean;
same_run_closed_loop_count: number;
};
security_audit: {
k8s_execution_audit_count_24h: number;
k8s_execution_failure_count_24h: number;
mcp_tool_audit_count_24h: number;
mcp_tool_failure_count_24h: number;
asset_change_event_count_24h: number;
automation_operation_count_24h: number;
automation_failure_count_24h: number;
ai_trace_count_24h: number;
accepted_ai_trace_count_24h: number;
immutable_external_audit_store_evidenced: boolean;
};
work_items: SecurityControlPlaneWorkItem[];
boundaries: {
aggregate_only: boolean;
raw_asset_identity_returned: boolean;
raw_event_payload_returned: boolean;
secret_value_collection_allowed: boolean;
live_scan_triggered: boolean;
runtime_action_triggered: boolean;
completion_requires_same_run_receipts: boolean;
};
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function isSecurityAssetControlPlanePayload(
value: unknown
): value is SecurityAssetControlPlanePayload {
if (!isRecord(value)) return false;
if (value.schema_version !== "iwooos_security_asset_control_plane_v1") return false;
if (!isRecord(value.summary) || !isRecord(value.boundaries)) return false;
if (!Array.isArray(value.asset_scopes) || !Array.isArray(value.nist_csf_functions)) {
return false;
}
if (!isRecord(value.siem) || !Array.isArray(value.siem.pipeline)) return false;
if (!isRecord(value.ai_automation) || !Array.isArray(value.ai_automation.stages)) {
return false;
}
return (
typeof value.summary.managed_asset_count === "number" &&
typeof value.summary.nist_controlled_percent === "number" &&
value.boundaries.aggregate_only === true &&
value.boundaries.raw_asset_identity_returned === false &&
value.boundaries.raw_event_payload_returned === false &&
value.boundaries.secret_value_collection_allowed === false &&
value.boundaries.live_scan_triggered === false &&
value.boundaries.runtime_action_triggered === false
);
}
export type SecurityControlPlaneTone = "healthy" | "warning" | "critical";
export function securityControlPlaneTone(
payload: SecurityAssetControlPlanePayload
): SecurityControlPlaneTone {
if (payload.status !== "ready" || !payload.discovery.fresh) return "critical";
if (
payload.summary.compliance_violation_count > 0 ||
payload.summary.nist_controlled_percent < 50 ||
payload.summary.siem_stage_coverage_percent < 50 ||
payload.summary.ai_loop_stage_coverage_percent < 50
) {
return "critical";
}
if (
payload.summary.unowned_asset_count > 0 ||
payload.summary.orphan_asset_count > 0 ||
payload.summary.automation_coverage_unknown_count > 0 ||
payload.summary.compliance_unknown_count > 0 ||
!payload.ai_automation.same_run_closed_loop_proven
) {
return "warning";
}
return "healthy";
}
export function topSecurityControlPlaneWorkItems(
payload: SecurityAssetControlPlanePayload,
limit = 3
): SecurityControlPlaneWorkItem[] {
const rank: Record<string, number> = { P0: 0, P1: 1, P2: 2, P3: 3 };
return [...payload.work_items]
.sort(
(left, right) =>
(rank[left.priority] ?? 99) - (rank[right.priority] ?? 99) ||
left.work_item_id.localeCompare(right.work_item_id)
)
.slice(0, Math.max(0, limit));
}