From 062d91bcba2b763add2390c978789b9e43a14c2d Mon Sep 17 00:00:00 2001 From: Moskovkin Ilya Date: Sun, 19 Jul 2026 20:10:32 +0700 Subject: [PATCH] feat: add incident operator handoff --- scripts/test-home-navigation.mjs | 139 ++++++++++- src/components/incident-detail.tsx | 374 +++++++++++++++++++++++++---- src/lib/incident-handoff.ts | 62 +++++ 3 files changed, 532 insertions(+), 43 deletions(-) create mode 100644 src/lib/incident-handoff.ts diff --git a/scripts/test-home-navigation.mjs b/scripts/test-home-navigation.mjs index 06fb5be..e2dbc80 100644 --- a/scripts/test-home-navigation.mjs +++ b/scripts/test-home-navigation.mjs @@ -1,12 +1,21 @@ import assert from "node:assert/strict"; -import { readFile, access } from "node:fs/promises"; +import { access, readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { + buildCanonicalIncidentUrl, + buildIncidentHandoff, +} from "../src/lib/incident-handoff.ts"; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, ".."); const homeSourcePath = path.join(repoRoot, "src/components/product-home.tsx"); const validatePagePath = path.join(repoRoot, "src/app/validate/page.tsx"); +const incidentDetailSourcePath = path.join( + repoRoot, + "src/components/incident-detail.tsx", +); +const layoutSourcePath = path.join(repoRoot, "src/app/layout.tsx"); const homeSource = await readFile(homeSourcePath, "utf8"); @@ -21,4 +30,130 @@ assert.match( await access(validatePagePath); -console.log("Home navigation contract OK: homepage links to /validate and page exists."); +const canonicalIncidentUrl = buildCanonicalIncidentUrl("inc_411"); +assert.equal( + canonicalIncidentUrl, + "https://signalops.ilyamoskovkin.com/incidents/inc_411#handoff", +); + +const layoutSource = await readFile(layoutSourcePath, "utf8"); +const escapedIncidentOrigin = new URL(canonicalIncidentUrl).origin.replace( + /[.*+?^${}()|[\]\\]/g, + "\\$&", +); +assert.match( + layoutSource, + new RegExp( + `metadataBase: new URL\\(["']${escapedIncidentOrigin}["']\\)`, + ), + "Expected the incident handoff origin to match the app metadata base.", +); + +const handoff = buildIncidentHandoff({ + incident: { + id: "inc_411", + title: "Qwen Image timeout cluster", + severity: "critical", + }, + provider: { + name: "Alibaba", + region: "ap-southeast", + }, + affectedJobCount: 72, + guard: "latency", + trafficShare: 68, + decisionState: "simulated", + projected: { + movedJobs: 196, + p95Ms: 14_452, + failureRate: 5.9, + }, + canonicalUrl: canonicalIncidentUrl, +}); + +assert.equal( + handoff, + [ + "# Incident handoff — inc_411", + "", + "**Qwen Image timeout cluster** · CRITICAL", + "Scope: 72 affected jobs on Alibaba (ap-southeast)", + "Decision: SIMULATED — drain 68% of Alibaba traffic when p95 > 12s", + "Projected: 196 jobs moved · p95 14.5s · failure rate 5.9%", + "State: PREVIEW ONLY — no routing change has been applied", + `Canonical incident: ${canonicalIncidentUrl}`, + ].join("\n"), +); + +const appliedHandoff = buildIncidentHandoff({ + incident: { + id: "inc_411", + title: "Qwen Image timeout cluster", + severity: "critical", + }, + provider: { + name: "Alibaba", + region: "ap-southeast", + }, + affectedJobCount: 72, + guard: "failure", + trafficShare: 90, + decisionState: "applied", + appliedAt: "2026-07-19T12:00:00.000Z", + projected: { + movedJobs: 259, + p95Ms: 13_048, + failureRate: 4.8, + }, + canonicalUrl: canonicalIncidentUrl, +}); + +assert.match( + appliedHandoff, + /Decision: APPLIED — drain 90% of Alibaba traffic when failure > 5%/, +); +assert.match( + appliedHandoff, + /State: ACTIVE — applied at 2026-07-19T12:00:00.000Z in the current 24h snapshot/, +); + +const incidentDetailSource = await readFile(incidentDetailSourcePath, "utf8"); +assert.match( + incidentDetailSource, + /]*\bid=["']handoff["'][^>]*>/, + "Expected incident detail to expose a linkable handoff section.", +); +assert.match( + incidentDetailSource, + />\s*Copy handoff\s*("latency"); const [trafficShare, setTrafficShare] = useState(68); - const [simulated, setSimulated] = useState(false); + const [simulatedRuleKey, setSimulatedRuleKey] = useState(null); const [selectedGeneration, setSelectedGeneration] = useState(null); + const handoffScrollHandled = useRef(false); + const draftRuleKey = `${incidentId}:${guard}:${trafficShare}`; + const simulated = simulatedRuleKey === draftRuleKey; const { data, isLoading } = useQuery({ - queryKey: ["incident-detail", incidentId], + queryKey: ["ops-snapshot", "24h"], queryFn: () => fetchOpsSnapshot("24h"), }); @@ -50,6 +60,29 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) { const provider = data?.providers.find( (item) => item.id === incident?.providerId, ); + + useEffect(() => { + if ( + handoffScrollHandled.current || + !incident || + window.location.hash !== "#handoff" + ) { + return; + } + + const frame = requestAnimationFrame(() => { + const handoffSection = document.getElementById("handoff"); + if (!handoffSection) { + return; + } + + handoffSection.scrollIntoView({ block: "start" }); + handoffScrollHandled.current = true; + }); + + return () => cancelAnimationFrame(frame); + }, [incident]); + const affectedJobs = useMemo(() => { if (!data || !provider) { return []; @@ -109,7 +142,52 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) { ); } - const timeline = buildIncidentTimeline(provider, simulated); + const activeIncidentRule = + data.activeRoutingRule?.status === "active" && + data.activeRoutingRule.incidentId === incident.id && + data.activeRoutingRule.providerId === provider.id + ? data.activeRoutingRule + : null; + const decisionState: DecisionState = activeIncidentRule + ? "applied" + : simulated + ? "simulated" + : "proposed"; + const decisionGuard = activeIncidentRule?.guard ?? guard; + const decisionTrafficShare = + activeIncidentRule?.trafficShare ?? trafficShare; + const decisionProjection = activeIncidentRule + ? { + movedJobs: activeIncidentRule.movedJobs, + p95Ms: Math.max(900, provider.p95Ms - activeIncidentRule.p95Delta), + failureRate: Number( + Math.max( + 0.1, + provider.failureRate - activeIncidentRule.failureDelta, + ).toFixed(1), + ), + saved: activeIncidentRule.spendDelta, + } + : { + movedJobs: impacted.jobs, + p95Ms: impacted.p95, + failureRate: impacted.failures, + saved: impacted.saved, + }; + const decisionComplete = decisionState !== "proposed"; + const timeline = buildIncidentTimeline(provider, decisionState); + const canonicalUrl = buildCanonicalIncidentUrl(incident.id); + const handoff = buildIncidentHandoff({ + incident, + provider, + affectedJobCount: affectedJobs.length, + guard: decisionGuard, + trafficShare: decisionTrafficShare, + decisionState, + appliedAt: activeIncidentRule?.appliedAt, + projected: decisionProjection, + canonicalUrl, + }); return (
@@ -178,7 +256,8 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) { - +
Rule condition
{provider.name} - {guard === "latency" ? "p95 > 12s" : "failure > 5%"} - {trafficShare}% drain + + {decisionGuard === "latency" ? "p95 > 12s" : "failure > 5%"} + + {decisionTrafficShare}% drain
setGuard("latency")} > Latency guard setGuard("failure")} > Failure guard @@ -271,62 +361,85 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) {
Traffic drain - {trafficShare}% + {decisionTrafficShare}%
setTrafficShare(Number(event.target.value))} type="range" - className="mt-3 w-full accent-[var(--accent)]" + className="mt-3 w-full accent-[var(--accent)] disabled:cursor-not-allowed disabled:opacity-60" />
- - - - + + + +
+ +
`${value.toFixed(1)}%`} - simulated={simulated} + hasDecisionImpact={decisionComplete} />
@@ -337,10 +450,16 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) { ["Incident opened", `${incident.title} assigned to provider ops.`], ["Affected scope calculated", `${affectedJobs.length} risky jobs matched the guard.`], [ - simulated ? "Impact simulated" : "Rule draft pending", - simulated - ? `${impacted.jobs} jobs would be moved from ${provider.name}.` - : "Run the simulation to produce a reviewable audit event.", + decisionState === "applied" + ? "Routing rule applied" + : decisionState === "simulated" + ? "Impact simulated" + : "Rule draft pending", + decisionState === "applied" + ? `${decisionProjection.movedJobs} jobs are routed away from ${provider.name} in the current snapshot.` + : decisionState === "simulated" + ? `${impacted.jobs} jobs would be moved from ${provider.name}.` + : "Run the simulation to produce a reviewable audit event.", ], ].map(([title, detail], index) => (
- {index === 2 && !simulated ? ( + {index === 2 && decisionState === "proposed" ? ( ) : ( @@ -388,6 +507,155 @@ export function IncidentDetail({ incidentId }: { incidentId: string }) { ); } +function IncidentHandoff({ + content, + canonicalUrl, + decisionState, +}: { + content: string; + canonicalUrl: string; + decisionState: DecisionState; +}) { + const [copyState, setCopyState] = useState<{ + status: "idle" | "copied" | "failed"; + content: string; + }>({ status: "idle", content }); + const resetTimer = useRef | null>(null); + const copyAttempt = useRef(0); + const visibleCopyState = + copyState.content === content ? copyState.status : "idle"; + + useEffect(() => { + return () => { + copyAttempt.current += 1; + if (resetTimer.current) { + clearTimeout(resetTimer.current); + } + }; + }, []); + + async function copyHandoff() { + const attempt = ++copyAttempt.current; + const copiedContent = content; + + if (resetTimer.current) { + clearTimeout(resetTimer.current); + resetTimer.current = null; + } + setCopyState({ status: "idle", content: copiedContent }); + + try { + await navigator.clipboard.writeText(copiedContent); + + if (copyAttempt.current !== attempt) { + return; + } + + setCopyState({ status: "copied", content: copiedContent }); + } catch { + if (copyAttempt.current !== attempt) { + return; + } + + setCopyState({ status: "failed", content: copiedContent }); + } + + resetTimer.current = setTimeout(() => { + if (copyAttempt.current === attempt) { + setCopyState({ status: "idle", content: copiedContent }); + } + }, 2200); + } + + return ( +
+
+
+
+ + Operator handoff +
+

+ Decision, scope, canonical incident +

+

+ A deterministic incident brief from the loaded snapshot and the + state above. It separates draft, previewed, and applied routing + decisions without inventing production state. +

+
+ + + {visibleCopyState === "copied" + ? "Copied to clipboard" + : visibleCopyState === "failed" + ? "Clipboard unavailable" + : ""} + +
+ + {canonicalUrl} + +
+ +
+
+ + Markdown · ready to paste + + + {decisionState === "applied" + ? "applied · current snapshot" + : decisionState === "simulated" + ? "simulated · not applied" + : "proposed"} + +
+
+            {content}
+          
+
+
+
+ ); +} + function Panel({ title, eyebrow, @@ -434,17 +702,21 @@ function Chip({ children }: { children: React.ReactNode }) { function GuardButton({ active, children, + disabled, onClick, }: { active: boolean; children: React.ReactNode; + disabled?: boolean; onClick: () => void; }) { return (