From 2426cd51fc0c2df9c6ac30600fe05c2732f4710f Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 20:07:56 -0500 Subject: [PATCH 1/7] add 30-second platform explainer --- README.md | 7 +++++++ dashboard/src/App.tsx | 23 +++++++++++++++++++++-- dashboard/src/readability.css | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e238987..ce20f03 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,13 @@ live mode reports actual Ready pod counts from the authorized kubectl context. E failure includes its evidence and an exact remediation. The platform demo commands will not declare success unless this panel reports **Ready to present**. +Select **How it works** in Sentinel's header for the unified 30-second explanation. The +full-screen flow introduces Argus observation, the SOG shared model, Sentinel/OpenAI +decision support, human governance, and Phoenix recovery plus verification. It includes +a ready-to-speak judge narration, explicit autonomous/high-risk boundaries, and all four +evidence-provenance labels. The overlay is responsive and closes with its button, the +backdrop, or `Escape`. + ### Sentinel-only development Run the Sentinel Operations Graph service first, then: diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index c1a2bbc..478af10 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react' import { - Activity, AlertTriangle, ArrowRight, BrainCircuit, CheckCircle2, ChevronRight, + Activity, AlertTriangle, ArrowRight, BookOpen, BrainCircuit, CheckCircle2, ChevronRight, CircleDot, Clock3, GitBranch, Layers3, Network, RefreshCw, Shield, Sparkles, Timer, UserCheck, X, Zap, } from 'lucide-react' @@ -222,6 +222,23 @@ function ReadinessPanel({ readiness, expanded, onToggle, onRefresh }: { readines } +function HowItWorks({ onClose }: { onClose: () => void }) { + useEffect(() => { const close = (event: KeyboardEvent) => event.key === 'Escape' && onClose(); document.addEventListener('keydown', close); return () => document.removeEventListener('keydown', close) }, [onClose]) + const steps = [ + { number: '01', name: 'ARGUS', role: 'Observe', icon: Shield, tone: 'argus', text: 'Falco, Cilium, and Kyverno evidence becomes one reasoned security finding.', output: 'Threat + blast radius' }, + { number: '02', name: 'SOG', role: 'Connect', icon: GitBranch, tone: 'sentinel', text: 'The Sentinel Operations Graph connects services, dependencies, evidence, incidents, and trust.', output: 'Shared operational truth' }, + { number: '03', name: 'SENTINEL + OPENAI', role: 'Decide', icon: BrainCircuit, tone: 'sentinel', text: 'Sentinel explains what changed, ranks risk, and chooses what requires attention.', output: 'Evidence-grounded decision' }, + { number: '04', name: 'HUMAN GOVERNANCE', role: 'Authorize', icon: UserCheck, tone: 'human', text: 'Low-risk, proven actions can run autonomously. High-impact actions wait for explicit approval.', output: 'Accountable control' }, + { number: '05', name: 'PHOENIX', role: 'Recover + verify', icon: Zap, tone: 'phoenix', text: 'Phoenix exercises resilience, executes the approved recovery, and verifies service health.', output: 'Measured recovery evidence' }, + ] + return
+
{steps.map(({ icon: Icon, ...step }, index) =>
{step.number}
{step.role}

{step.name}

{step.text}

{step.output}{index < steps.length - 1 && }
)}
+
Say this to a judge≈ 24 seconds at a natural pace
“Argus turns live security telemetry into evidence. The SOG connects that evidence to the affected services. Sentinel uses OpenAI to explain the risk and decide what needs attention. Proven, low-risk recovery can run autonomously; high-risk action requires a human. Phoenix restores the service, verifies the outcome, and feeds that evidence back into Sentinel.”
+
AUTONOMOUSDetection · correlation · diagnosis · bounded recovery · verification
HUMAN-GOVERNEDHigh blast radius · ambiguous evidence · destructive or unproven actions
EVERY RECORD SAYS WHAT IT ISLIVE OBSERVEDLIVE CHAOS MESHSYNTHETIC SIMULATORREPLAYED EVIDENCE
+
SOG means Sentinel Operations GraphAutonomous resilience through AI—with humans governing high-risk actions.
+
+} + function MetricDetail({ metric, data, onSignal, onComponent, onIncident }: { metric: MetricKind; data: Overview | null; onSignal: (signal: Timeline) => void; onComponent: (component: Component) => void; onIncident: (incident: Incident) => void }) { const definitions: Record = { signals: { title: 'Evidence records', description: 'Every finding in the current Sentinel Operations Graph window, separated by exact provenance.' }, @@ -266,6 +283,7 @@ export default function App() { const [brief, setBrief] = useState(''), [briefing, setBriefing] = useState(false), [detail, setDetail] = useState(null) const [previousRisk, setPreviousRisk] = useState(null), [, setClock] = useState(0) const [readiness, setReadiness] = useState(null), [showReadiness, setShowReadiness] = useState(true) + const [showHow, setShowHow] = useState(false) const load = async () => { setLoading(true); try { const response = await fetch(`${API}/overview`); if (!response.ok) throw Error(await response.text()); const next: Overview = await response.json(); setData(current => { if (current) setPreviousRisk(current.fleet_risk); return next }); setError('') } catch (caught: any) { setError(caught.message) } finally { setLoading(false) } } const loadReadiness = async () => { try { const response = await fetch(`${API}/readiness`); if (!response.ok) throw Error(await response.text()); setReadiness(await response.json()) } catch { setReadiness(null) } } useEffect(() => { load(); const refreshTimer = setInterval(load, 15000); const clockTimer = setInterval(() => setClock(value => value + 1), 1000); return () => { clearInterval(refreshTimer); clearInterval(clockTimer) } }, []) @@ -275,7 +293,7 @@ export default function App() { const selectTopology = (id: string) => { const component = componentsById.get(id); if (component) setDetail({ kind: 'component', component }) } const urgent = (data?.counts.critical || 0) + (data?.counts.high || 0) return
-

SENTINEL

OPENAI-NATIVE · SENTINEL OPERATIONS GRAPH

{error ? 'SOG DEGRADED' : data?.status === 'degraded' ? 'PARTIAL DATA' : 'SOG LIVE'}{data ? `refreshed ${age(data.generated_at)}` : 'connecting'}
+

SENTINEL

OPENAI-NATIVE · SENTINEL OPERATIONS GRAPH

{error ? 'SOG DEGRADED' : data?.status === 'degraded' ? 'PARTIAL DATA' : 'SOG LIVE'}{data ? `refreshed ${age(data.generated_at)}` : 'connecting'}
UNIFIED COMMAND CENTER

One fleet. Two specialist agents.
One accountable decision.

Sentinel combines Argus security evidence and Phoenix resilience outcomes inside the Sentinel Operations Graph (SOG)—the shared, live evidence layer that explains exactly what requires attention and why.

SOGSentinel Operations GraphOne connected model of services, dependencies, evidence, incidents, and trust.
{urgent ? `${urgent} urgent signals need review` : 'No urgent evidence in the current window'}Polling the Operations Graph every 15 secondsSOG evidence stream active
setDetail({ kind: 'component', component })} />
{error &&
Sentinel cannot reach the Sentinel Operations Graph (SOG) gateway. {error}
} @@ -290,6 +308,7 @@ export default function App() {
SENTINEL PLATFORMARGUS PHOENIX SOG · SENTINEL OPERATIONS GRAPH15s live refresh
{detail && setDetail(null)} onDetail={setDetail} />} + {showHow && setShowHow(false)} />}
} diff --git a/dashboard/src/readability.css b/dashboard/src/readability.css index bd7d6b6..8e13a81 100644 --- a/dashboard/src/readability.css +++ b/dashboard/src/readability.css @@ -14,3 +14,4 @@ .incident-facts{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin:15px 0}.incident-facts span{padding:11px;border:1px solid #27364d;border-radius:8px;background:#0c1522}.incident-facts small{display:block;color:#687a92;font:700 9px JetBrains Mono}.incident-facts b{display:block;margin-top:5px;color:#e3e9f2;font:700 12px JetBrains Mono;text-transform:uppercase}.timeline-help{color:#8191a6;font-size:11px;line-height:1.55}.lifecycle{position:relative;margin-top:11px}.lifecycle:before{content:"";position:absolute;left:12px;top:15px;bottom:15px;width:1px;background:#34445e}.lifecycle>button{position:relative;display:grid;width:100%;grid-template-columns:25px 1fr 13px;gap:9px;align-items:center;padding:12px 5px;border:0;border-bottom:1px solid #1e2c42;background:transparent;color:inherit;text-align:left;cursor:pointer}.lifecycle>button:hover{background:#111c2b}.lifecycle>button>i{z-index:1;width:10px;height:10px;justify-self:center;border:2px solid #0b1420;border-radius:50%;box-shadow:0 0 8px currentColor}.lifecycle>button>div{display:flex;min-width:0;flex-direction:column;gap:5px}.lifecycle header{display:flex;align-items:center;gap:9px}.lifecycle header span,.lifecycle header strong,.lifecycle time{font:700 9px JetBrains Mono;text-transform:uppercase}.lifecycle header strong{color:#8999af}.lifecycle time{margin-left:auto;color:#66778f;text-transform:none}.lifecycle>button b{color:#dbe3ef;font-size:12px}.lifecycle>button small{color:#73849a;font:500 10px JetBrains Mono;text-transform:capitalize}.lifecycle>button>svg{width:12px;color:#61738b}@media(max-width:520px){.incident-facts{grid-template-columns:1fr 1fr}} .proof-section{margin-top:18px}.proof-timeline{display:grid;grid-template-columns:repeat(7,minmax(105px,1fr));gap:7px;margin-top:14px;overflow-x:auto;padding-bottom:8px}.proof-stage{position:relative;min-height:142px;padding:13px 11px;border:1px solid #26364e;border-radius:9px;background:#0b1421}.proof-stage:not(:last-child):after{content:"→";position:absolute;right:-9px;top:20px;z-index:2;color:#697c98;font:700 13px JetBrains Mono}.proof-stage>span{display:grid;width:22px;height:22px;place-items:center;border-radius:50%;background:#55e6a518;color:#55e6a5;font:700 9px JetBrains Mono}.proof-stage>div{display:flex;flex-direction:column;gap:7px;margin-top:11px}.proof-stage b{color:#edf3fb;font-size:11px;text-transform:capitalize}.proof-stage small{min-height:32px;color:#91a1b6;font-size:9px;line-height:1.45}.proof-stage em{color:#667a94;font:600 8px JetBrains Mono;font-style:normal}.proof-stage>strong{position:absolute;bottom:11px;left:11px;color:#55e6a5;font:700 8px JetBrains Mono;text-transform:uppercase}.proof-stage.missing{border-style:dashed;opacity:.6}.proof-stage.missing>span{background:#71809816;color:#718098}.proof-stage.missing>strong{color:#718098}.proof-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:10px}.proof-metrics>span{padding:12px;border:1px solid #283952;border-radius:8px;background:#0c1624}.proof-metrics small{display:block;color:#71849e;font:700 8px JetBrains Mono}.proof-metrics b{display:block;margin-top:7px;color:#e8eef7;font-size:12px}.supporting-evidence{margin-top:12px;border:1px solid #263750;border-radius:8px;padding:10px}.supporting-evidence summary{cursor:pointer;color:#91a4be;font:700 9px JetBrains Mono}.provenance{white-space:nowrap}.provenance.observed,.provenance-text.observed{color:#55e6a5!important}.provenance.live_chaos,.provenance-text.live_chaos{color:#4de7c7!important}.provenance.simulator,.provenance-text.simulator{color:#ffad55!important}.provenance.replayed,.provenance-text.replayed{color:#b68cff!important}.provenance.unknown,.provenance-text.unknown{color:#ff5874!important}@media(max-width:900px){.proof-timeline{grid-template-columns:repeat(7,140px)}.proof-metrics{grid-template-columns:1fr 1fr}} .readiness-panel{margin:0 0 14px;border:1px solid #2c4059;border-radius:12px;background:linear-gradient(110deg,#0c1725,#0b1320);overflow:hidden}.readiness-panel.ready{border-color:#55e6a552}.readiness-panel.blocked{border-color:#ff587466}.readiness-summary{display:grid;width:100%;grid-template-columns:38px 1fr auto;gap:13px;align-items:center;padding:15px 17px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.readiness-icon{display:grid;width:35px;height:35px;place-items:center;border:1px solid #55e6a55c;border-radius:9px;color:#55e6a5;background:#55e6a50e}.blocked .readiness-icon{border-color:#ff58745c;color:#ff5874;background:#ff58740e}.readiness-icon svg{width:18px}.readiness-summary>div{display:flex;flex-direction:column;gap:4px}.readiness-summary small{color:#7d91aa;font:700 8px JetBrains Mono;letter-spacing:.8px}.readiness-summary b{color:#e8eef7;font-size:15px}.readiness-summary p{color:#71849c;font-size:10px}.readiness-summary>em{display:flex;align-items:center;gap:7px;color:#8ea0b8;font:700 9px JetBrains Mono;font-style:normal;text-transform:uppercase}.readiness-summary>em svg{width:13px}.readiness-body{padding:0 17px 15px;border-top:1px solid #223249}.readiness-head{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:11px 0}.readiness-head p{color:#8294aa;font-size:10px}.readiness-head button{display:flex;align-items:center;gap:6px;padding:7px 10px;border:1px solid #34506a;border-radius:7px;background:#111e2d;color:#a9b8ca;font:700 9px JetBrains Mono;cursor:pointer}.readiness-head button svg{width:12px}.readiness-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:7px}.readiness-grid article{min-height:112px;padding:10px;border:1px solid #27384f;border-radius:8px;background:#09121e}.readiness-grid article.ready{border-color:#55e6a533}.readiness-grid article.not_ready,.readiness-grid article.not_configured{border-color:#ff587455}.readiness-grid article.not_applicable{opacity:.62}.readiness-grid header{display:flex;gap:7px;align-items:center}.readiness-grid header>span{color:#55e6a5}.readiness-grid .not_ready header>span,.readiness-grid .not_configured header>span{color:#ff5874}.readiness-grid header svg{width:13px}.readiness-grid header div{display:flex;flex-direction:column}.readiness-grid header b{color:#dce5f0;font-size:11px}.readiness-grid header small{color:#71839a;font:600 7px JetBrains Mono;text-transform:uppercase}.readiness-grid article p{margin-top:9px;color:#8394aa;font-size:9px;line-height:1.45;word-break:break-word}.readiness-grid article>em{display:block;margin-top:7px;color:#ffad55;font-size:8px;font-style:normal;line-height:1.4}.readiness-body>footer{display:flex;justify-content:space-between;margin-top:11px;color:#74869d;font:600 8px JetBrains Mono}.readiness-body>footer strong{color:#55e6a5}.blocked .readiness-body>footer strong{color:#ffad55}@media(max-width:1100px){.readiness-grid{grid-template-columns:repeat(3,1fr)}}@media(max-width:700px){.readiness-summary{grid-template-columns:35px 1fr}.readiness-summary>em{grid-column:2}.readiness-grid{grid-template-columns:1fr 1fr}} +.how-link{border:1px solid #b68cff44!important;background:#b68cff0e!important;color:#d2bcff!important;cursor:pointer}.how-wrap{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:22px}.how-backdrop{position:absolute;inset:0;border:0;background:#030712e8;backdrop-filter:blur(12px)}.how-modal{position:relative;width:min(1500px,97vw);max-height:95vh;overflow:auto;border:1px solid #b68cff55;border-radius:17px;background:radial-gradient(circle at 50% -20%,#2b174b77,transparent 42%),linear-gradient(145deg,#0c1320,#080d17);box-shadow:0 30px 100px #000c;color:#dce5f0}.how-modal>header{display:flex;justify-content:space-between;gap:30px;padding:28px 32px 20px;border-bottom:1px solid #26344a}.how-modal>header span{color:#bfa3ff;font:700 9px JetBrains Mono;letter-spacing:1.8px}.how-modal>header h2{margin-top:7px;color:#f0f3fa;font:700 30px Space Grotesk}.how-modal>header p{margin-top:6px;color:#91a0b4;font-size:13px}.how-modal>header>button{display:grid;width:35px;height:35px;place-items:center;border:1px solid #33445d;border-radius:8px;background:#101927;color:#91a3ba;cursor:pointer}.how-modal>header>button svg{width:17px}.how-flow{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;padding:24px 30px}.how-step{--step:#b68cff;position:relative;min-height:245px;padding:16px;border:1px solid color-mix(in srgb,var(--step) 36%,#25344c);border-radius:12px;background:linear-gradient(160deg,color-mix(in srgb,var(--step) 8%,#0b1421),#09111c)}.how-step.argus{--step:#45d9ff}.how-step.phoenix{--step:#55e6a5}.how-step.human{--step:#ffd166}.how-step-top{display:flex;align-items:center;justify-content:space-between}.how-step-top>span{color:#71839c;font:700 9px JetBrains Mono}.how-step-top>i{display:grid;width:34px;height:34px;place-items:center;border:1px solid color-mix(in srgb,var(--step) 45%,transparent);border-radius:9px;background:color-mix(in srgb,var(--step) 9%,transparent);color:var(--step)}.how-step-top svg{width:17px}.how-step>small{display:block;margin-top:19px;color:var(--step);font:700 9px JetBrains Mono;text-transform:uppercase;letter-spacing:1px}.how-step h3{margin-top:5px;color:#edf2f9;font:700 15px Space Grotesk}.how-step p{margin-top:12px;color:#94a3b7;font-size:11px;line-height:1.62}.how-step>strong{position:absolute;right:15px;bottom:15px;left:15px;display:flex;align-items:center;gap:6px;padding-top:10px;border-top:1px solid #27364c;color:var(--step);font:600 9px JetBrains Mono}.how-step>strong svg{width:12px}.how-arrow{position:absolute;right:-19px;top:50%;z-index:3;width:24px;padding:4px;border-radius:50%;background:#111a29;color:#8295ae}.how-narration{display:grid;grid-template-columns:210px 1fr;gap:20px;margin:0 30px;padding:18px 20px;border:1px solid #b68cff45;border-radius:12px;background:#b68cff0a}.how-narration>div{display:flex;align-items:center;gap:11px;color:#c7aaff}.how-narration>div svg{width:22px}.how-narration>div span{display:flex;flex-direction:column}.how-narration b{font-size:12px}.how-narration small{margin-top:3px;color:#71829a;font:600 8px JetBrains Mono}.how-narration blockquote{color:#c9d3e1;font-size:13px;line-height:1.7}.how-truth{display:grid;grid-template-columns:.8fr 1fr 1.6fr;gap:10px;padding:14px 30px 24px}.how-truth>div{padding:12px 14px;border:1px solid #26364d;border-radius:9px;background:#09121e}.how-truth b{display:block;color:#8295ae;font:700 8px JetBrains Mono;letter-spacing:.7px}.how-truth>div>span{display:block;margin-top:6px;color:#aebaca;font-size:10px;line-height:1.45}.how-provenance>span{display:flex!important;flex-wrap:wrap;gap:5px}.how-provenance em{padding:4px 6px;border:1px solid currentColor;border-radius:5px;font:700 7px JetBrains Mono;font-style:normal}.how-provenance .observed{color:#55e6a5}.how-provenance .live_chaos{color:#4de7c7}.how-provenance .simulator{color:#ffad55}.how-provenance .replayed{color:#b68cff}.how-modal>footer{display:grid;grid-template-columns:1fr 1.7fr auto;gap:18px;align-items:center;padding:15px 30px;border-top:1px solid #26354b;background:#080e18}.how-modal>footer span{display:flex;align-items:center;gap:6px;color:#7d8fa7;font:600 8px JetBrains Mono}.how-modal>footer span svg{width:12px}.how-modal>footer strong{color:#d8c8ff;font-size:12px;text-align:center}.how-modal>footer button{display:flex;align-items:center;gap:7px;padding:9px 12px;border:1px solid #b68cff66;border-radius:8px;background:#b68cff15;color:#d5c1ff;font:700 9px JetBrains Mono;cursor:pointer}.how-modal>footer button svg{width:12px}@media(max-width:1050px){.how-flow{grid-template-columns:1fr 1fr}.how-step{min-height:210px}.how-arrow{display:none}.how-flow .how-step:last-child{grid-column:1/-1}.how-truth{grid-template-columns:1fr}.how-modal>footer{grid-template-columns:1fr}.how-modal>footer strong{text-align:left}}@media(max-width:650px){.how-wrap{padding:0}.how-modal{width:100%;height:100%;max-height:none;border-radius:0}.how-modal>header,.how-flow,.how-truth{padding-right:17px;padding-left:17px}.how-modal>header h2{font-size:23px}.how-flow{grid-template-columns:1fr}.how-flow .how-step:last-child{grid-column:auto}.how-narration{grid-template-columns:1fr;margin:0 17px}.how-modal>footer{padding:14px 17px}} From 43f68d05a4cd228d3fb47c0d4eb1b6b50151180c Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 20:11:59 -0500 Subject: [PATCH 2/7] improve explainer readability --- dashboard/src/readability.css | 176 ++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/dashboard/src/readability.css b/dashboard/src/readability.css index 8e13a81..8d36d0b 100644 --- a/dashboard/src/readability.css +++ b/dashboard/src/readability.css @@ -15,3 +15,179 @@ .proof-section{margin-top:18px}.proof-timeline{display:grid;grid-template-columns:repeat(7,minmax(105px,1fr));gap:7px;margin-top:14px;overflow-x:auto;padding-bottom:8px}.proof-stage{position:relative;min-height:142px;padding:13px 11px;border:1px solid #26364e;border-radius:9px;background:#0b1421}.proof-stage:not(:last-child):after{content:"→";position:absolute;right:-9px;top:20px;z-index:2;color:#697c98;font:700 13px JetBrains Mono}.proof-stage>span{display:grid;width:22px;height:22px;place-items:center;border-radius:50%;background:#55e6a518;color:#55e6a5;font:700 9px JetBrains Mono}.proof-stage>div{display:flex;flex-direction:column;gap:7px;margin-top:11px}.proof-stage b{color:#edf3fb;font-size:11px;text-transform:capitalize}.proof-stage small{min-height:32px;color:#91a1b6;font-size:9px;line-height:1.45}.proof-stage em{color:#667a94;font:600 8px JetBrains Mono;font-style:normal}.proof-stage>strong{position:absolute;bottom:11px;left:11px;color:#55e6a5;font:700 8px JetBrains Mono;text-transform:uppercase}.proof-stage.missing{border-style:dashed;opacity:.6}.proof-stage.missing>span{background:#71809816;color:#718098}.proof-stage.missing>strong{color:#718098}.proof-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:10px}.proof-metrics>span{padding:12px;border:1px solid #283952;border-radius:8px;background:#0c1624}.proof-metrics small{display:block;color:#71849e;font:700 8px JetBrains Mono}.proof-metrics b{display:block;margin-top:7px;color:#e8eef7;font-size:12px}.supporting-evidence{margin-top:12px;border:1px solid #263750;border-radius:8px;padding:10px}.supporting-evidence summary{cursor:pointer;color:#91a4be;font:700 9px JetBrains Mono}.provenance{white-space:nowrap}.provenance.observed,.provenance-text.observed{color:#55e6a5!important}.provenance.live_chaos,.provenance-text.live_chaos{color:#4de7c7!important}.provenance.simulator,.provenance-text.simulator{color:#ffad55!important}.provenance.replayed,.provenance-text.replayed{color:#b68cff!important}.provenance.unknown,.provenance-text.unknown{color:#ff5874!important}@media(max-width:900px){.proof-timeline{grid-template-columns:repeat(7,140px)}.proof-metrics{grid-template-columns:1fr 1fr}} .readiness-panel{margin:0 0 14px;border:1px solid #2c4059;border-radius:12px;background:linear-gradient(110deg,#0c1725,#0b1320);overflow:hidden}.readiness-panel.ready{border-color:#55e6a552}.readiness-panel.blocked{border-color:#ff587466}.readiness-summary{display:grid;width:100%;grid-template-columns:38px 1fr auto;gap:13px;align-items:center;padding:15px 17px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.readiness-icon{display:grid;width:35px;height:35px;place-items:center;border:1px solid #55e6a55c;border-radius:9px;color:#55e6a5;background:#55e6a50e}.blocked .readiness-icon{border-color:#ff58745c;color:#ff5874;background:#ff58740e}.readiness-icon svg{width:18px}.readiness-summary>div{display:flex;flex-direction:column;gap:4px}.readiness-summary small{color:#7d91aa;font:700 8px JetBrains Mono;letter-spacing:.8px}.readiness-summary b{color:#e8eef7;font-size:15px}.readiness-summary p{color:#71849c;font-size:10px}.readiness-summary>em{display:flex;align-items:center;gap:7px;color:#8ea0b8;font:700 9px JetBrains Mono;font-style:normal;text-transform:uppercase}.readiness-summary>em svg{width:13px}.readiness-body{padding:0 17px 15px;border-top:1px solid #223249}.readiness-head{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:11px 0}.readiness-head p{color:#8294aa;font-size:10px}.readiness-head button{display:flex;align-items:center;gap:6px;padding:7px 10px;border:1px solid #34506a;border-radius:7px;background:#111e2d;color:#a9b8ca;font:700 9px JetBrains Mono;cursor:pointer}.readiness-head button svg{width:12px}.readiness-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:7px}.readiness-grid article{min-height:112px;padding:10px;border:1px solid #27384f;border-radius:8px;background:#09121e}.readiness-grid article.ready{border-color:#55e6a533}.readiness-grid article.not_ready,.readiness-grid article.not_configured{border-color:#ff587455}.readiness-grid article.not_applicable{opacity:.62}.readiness-grid header{display:flex;gap:7px;align-items:center}.readiness-grid header>span{color:#55e6a5}.readiness-grid .not_ready header>span,.readiness-grid .not_configured header>span{color:#ff5874}.readiness-grid header svg{width:13px}.readiness-grid header div{display:flex;flex-direction:column}.readiness-grid header b{color:#dce5f0;font-size:11px}.readiness-grid header small{color:#71839a;font:600 7px JetBrains Mono;text-transform:uppercase}.readiness-grid article p{margin-top:9px;color:#8394aa;font-size:9px;line-height:1.45;word-break:break-word}.readiness-grid article>em{display:block;margin-top:7px;color:#ffad55;font-size:8px;font-style:normal;line-height:1.4}.readiness-body>footer{display:flex;justify-content:space-between;margin-top:11px;color:#74869d;font:600 8px JetBrains Mono}.readiness-body>footer strong{color:#55e6a5}.blocked .readiness-body>footer strong{color:#ffad55}@media(max-width:1100px){.readiness-grid{grid-template-columns:repeat(3,1fr)}}@media(max-width:700px){.readiness-summary{grid-template-columns:35px 1fr}.readiness-summary>em{grid-column:2}.readiness-grid{grid-template-columns:1fr 1fr}} .how-link{border:1px solid #b68cff44!important;background:#b68cff0e!important;color:#d2bcff!important;cursor:pointer}.how-wrap{position:fixed;inset:0;z-index:80;display:grid;place-items:center;padding:22px}.how-backdrop{position:absolute;inset:0;border:0;background:#030712e8;backdrop-filter:blur(12px)}.how-modal{position:relative;width:min(1500px,97vw);max-height:95vh;overflow:auto;border:1px solid #b68cff55;border-radius:17px;background:radial-gradient(circle at 50% -20%,#2b174b77,transparent 42%),linear-gradient(145deg,#0c1320,#080d17);box-shadow:0 30px 100px #000c;color:#dce5f0}.how-modal>header{display:flex;justify-content:space-between;gap:30px;padding:28px 32px 20px;border-bottom:1px solid #26344a}.how-modal>header span{color:#bfa3ff;font:700 9px JetBrains Mono;letter-spacing:1.8px}.how-modal>header h2{margin-top:7px;color:#f0f3fa;font:700 30px Space Grotesk}.how-modal>header p{margin-top:6px;color:#91a0b4;font-size:13px}.how-modal>header>button{display:grid;width:35px;height:35px;place-items:center;border:1px solid #33445d;border-radius:8px;background:#101927;color:#91a3ba;cursor:pointer}.how-modal>header>button svg{width:17px}.how-flow{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;padding:24px 30px}.how-step{--step:#b68cff;position:relative;min-height:245px;padding:16px;border:1px solid color-mix(in srgb,var(--step) 36%,#25344c);border-radius:12px;background:linear-gradient(160deg,color-mix(in srgb,var(--step) 8%,#0b1421),#09111c)}.how-step.argus{--step:#45d9ff}.how-step.phoenix{--step:#55e6a5}.how-step.human{--step:#ffd166}.how-step-top{display:flex;align-items:center;justify-content:space-between}.how-step-top>span{color:#71839c;font:700 9px JetBrains Mono}.how-step-top>i{display:grid;width:34px;height:34px;place-items:center;border:1px solid color-mix(in srgb,var(--step) 45%,transparent);border-radius:9px;background:color-mix(in srgb,var(--step) 9%,transparent);color:var(--step)}.how-step-top svg{width:17px}.how-step>small{display:block;margin-top:19px;color:var(--step);font:700 9px JetBrains Mono;text-transform:uppercase;letter-spacing:1px}.how-step h3{margin-top:5px;color:#edf2f9;font:700 15px Space Grotesk}.how-step p{margin-top:12px;color:#94a3b7;font-size:11px;line-height:1.62}.how-step>strong{position:absolute;right:15px;bottom:15px;left:15px;display:flex;align-items:center;gap:6px;padding-top:10px;border-top:1px solid #27364c;color:var(--step);font:600 9px JetBrains Mono}.how-step>strong svg{width:12px}.how-arrow{position:absolute;right:-19px;top:50%;z-index:3;width:24px;padding:4px;border-radius:50%;background:#111a29;color:#8295ae}.how-narration{display:grid;grid-template-columns:210px 1fr;gap:20px;margin:0 30px;padding:18px 20px;border:1px solid #b68cff45;border-radius:12px;background:#b68cff0a}.how-narration>div{display:flex;align-items:center;gap:11px;color:#c7aaff}.how-narration>div svg{width:22px}.how-narration>div span{display:flex;flex-direction:column}.how-narration b{font-size:12px}.how-narration small{margin-top:3px;color:#71829a;font:600 8px JetBrains Mono}.how-narration blockquote{color:#c9d3e1;font-size:13px;line-height:1.7}.how-truth{display:grid;grid-template-columns:.8fr 1fr 1.6fr;gap:10px;padding:14px 30px 24px}.how-truth>div{padding:12px 14px;border:1px solid #26364d;border-radius:9px;background:#09121e}.how-truth b{display:block;color:#8295ae;font:700 8px JetBrains Mono;letter-spacing:.7px}.how-truth>div>span{display:block;margin-top:6px;color:#aebaca;font-size:10px;line-height:1.45}.how-provenance>span{display:flex!important;flex-wrap:wrap;gap:5px}.how-provenance em{padding:4px 6px;border:1px solid currentColor;border-radius:5px;font:700 7px JetBrains Mono;font-style:normal}.how-provenance .observed{color:#55e6a5}.how-provenance .live_chaos{color:#4de7c7}.how-provenance .simulator{color:#ffad55}.how-provenance .replayed{color:#b68cff}.how-modal>footer{display:grid;grid-template-columns:1fr 1.7fr auto;gap:18px;align-items:center;padding:15px 30px;border-top:1px solid #26354b;background:#080e18}.how-modal>footer span{display:flex;align-items:center;gap:6px;color:#7d8fa7;font:600 8px JetBrains Mono}.how-modal>footer span svg{width:12px}.how-modal>footer strong{color:#d8c8ff;font-size:12px;text-align:center}.how-modal>footer button{display:flex;align-items:center;gap:7px;padding:9px 12px;border:1px solid #b68cff66;border-radius:8px;background:#b68cff15;color:#d5c1ff;font:700 9px JetBrains Mono;cursor:pointer}.how-modal>footer button svg{width:12px}@media(max-width:1050px){.how-flow{grid-template-columns:1fr 1fr}.how-step{min-height:210px}.how-arrow{display:none}.how-flow .how-step:last-child{grid-column:1/-1}.how-truth{grid-template-columns:1fr}.how-modal>footer{grid-template-columns:1fr}.how-modal>footer strong{text-align:left}}@media(max-width:650px){.how-wrap{padding:0}.how-modal{width:100%;height:100%;max-height:none;border-radius:0}.how-modal>header,.how-flow,.how-truth{padding-right:17px;padding-left:17px}.how-modal>header h2{font-size:23px}.how-flow{grid-template-columns:1fr}.how-flow .how-step:last-child{grid-column:auto}.how-narration{grid-template-columns:1fr;margin:0 17px}.how-modal>footer{padding:14px 17px}} + +/* Presentation overlay: prioritize legibility at recording and laptop sizes. */ +.how-modal { + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + scrollbar-gutter: stable; +} + +.how-modal > header { + min-height: 130px; + align-items: flex-start; + padding: 26px 32px 24px; +} + +.how-modal > header > div { + min-width: 0; +} + +.how-modal > header span { + display: block; + margin-bottom: 10px; + font-size: 10px; + line-height: 1.4; +} + +.how-modal > header h2 { + margin: 0; + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + font-size: clamp(27px, 2vw, 34px); + line-height: 1.16; + letter-spacing: -.025em; +} + +.how-modal > header p { + margin-top: 10px; + font-size: 14px; + line-height: 1.55; +} + +.how-flow { + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 14px; +} + +.how-step { + min-width: 0; + min-height: 270px; + padding: 18px; +} + +.how-step > small { + font-size: 10px; + line-height: 1.35; +} + +.how-step h3 { + min-height: 42px; + margin-top: 7px; + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + font-size: 16px; + line-height: 1.3; + overflow-wrap: anywhere; +} + +.how-step p { + margin-top: 8px; + color: #aeb9c9; + font-size: 12px; + line-height: 1.6; +} + +.how-step > strong { + right: 18px; + bottom: 16px; + left: 18px; + font-size: 9px; + line-height: 1.45; +} + +.how-narration { + grid-template-columns: minmax(180px, 220px) minmax(0, 1fr); + align-items: center; + padding: 20px 22px; +} + +.how-narration b { + font-size: 13px; +} + +.how-narration small { + font-size: 9px; + line-height: 1.45; +} + +.how-narration blockquote { + font-size: 14px; + line-height: 1.7; +} + +.how-truth b, +.how-modal > footer span { + font-size: 9px; + line-height: 1.4; +} + +.how-truth > div > span { + font-size: 11px; + line-height: 1.55; +} + +.how-provenance em { + padding: 5px 7px; + font-size: 8px; + line-height: 1.2; +} + +.how-modal > footer { + min-height: 72px; + padding-top: 16px; + padding-bottom: 16px; +} + +.how-modal > footer strong { + font-family: Inter, ui-sans-serif, system-ui, sans-serif; + font-size: 13px; + line-height: 1.4; +} + +@media (max-width: 1280px) { + .how-flow { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .how-flow .how-step:nth-child(4), + .how-flow .how-step:nth-child(5) { + min-height: 235px; + } + + .how-flow .how-step:nth-child(4) { + grid-column: 1 / 2; + } + + .how-flow .how-step:nth-child(5) { + grid-column: 2 / 4; + } + + .how-arrow { + display: none; + } +} + +@media (max-width: 760px) { + .how-modal > header { + min-height: 0; + padding-top: 22px; + } + + .how-flow, + .how-truth { + grid-template-columns: 1fr; + } + + .how-flow .how-step:nth-child(4), + .how-flow .how-step:nth-child(5) { + grid-column: auto; + } + + .how-step, + .how-flow .how-step:nth-child(4), + .how-flow .how-step:nth-child(5) { + min-height: 230px; + } + + .how-narration { + grid-template-columns: 1fr; + } +} From 909c7a9fd4ec5ad01ef634e4ffe161af7b8c2d00 Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 20:29:35 -0500 Subject: [PATCH 3/7] surface incidents beside evidence --- dashboard/src/App.tsx | 2 +- dashboard/src/readability.css | 64 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 478af10..3bf0b47 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -300,7 +300,7 @@ export default function App() { setShowReadiness(value => !value)} onRefresh={loadReadiness} />
setDetail({ kind: 'metric', metric: 'signals' })} /> setDetail({ kind: 'metric', metric: 'urgent' })} /> setDetail({ kind: 'metric', metric: 'affected' })} /> setDetail({ kind: 'metric', metric: 'namespaces' })} /> setDetail({ kind: 'metric', metric: 'incidents' })} />
setDetail({ kind: 'source', source })} />
-
{data?.timeline.length || 0} records} /> setDetail({ kind: 'signal', signal })} />
+
{data?.timeline.length || 0} records} /> setDetail({ kind: 'signal', signal })} />
{data?.counts.findings || 0} signals mapped} />
setDetail({ kind: 'component', component })} />
{data?.counts.entities || 0} nodes · {data?.counts.edges || 0} edges} />
{data?.counts.namespaces || 0} namespaces} />
diff --git a/dashboard/src/readability.css b/dashboard/src/readability.css index 8d36d0b..398dc07 100644 --- a/dashboard/src/readability.css +++ b/dashboard/src/readability.css @@ -191,3 +191,67 @@ grid-template-columns: 1fr; } } + +.evidence-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 12px; +} + +.incident-entry { + display: flex; + align-items: center; + gap: 8px; + padding: 9px 11px; + border: 1px solid #55e6a55c; + border-radius: 8px; + background: linear-gradient(135deg, #55e6a512, #101b28); + color: #7ff0bd; + cursor: pointer; + transition: border-color .15s ease, background .15s ease, transform .15s ease; +} + +.incident-entry:hover { + border-color: #55e6a5; + background: #55e6a51c; + transform: translateY(-1px); +} + +.incident-entry:focus-visible { + outline: 2px solid #8ff5c8; + outline-offset: 3px; +} + +.incident-entry > svg { + width: 14px; + flex: 0 0 auto; +} + +.incident-entry > svg:last-child { + width: 12px; + color: #5f9f82; +} + +.incident-entry > span { + display: flex; + align-items: baseline; + gap: 5px; + white-space: nowrap; + font: 700 9px JetBrains Mono, monospace; + letter-spacing: .7px; + text-transform: uppercase; +} + +.incident-entry b { + color: #e9fff5; + font-size: 13px; +} + +@media (max-width: 700px) { + .evidence-actions { + align-items: flex-end; + flex-direction: column; + gap: 7px; + } +} From 58d31ea3053fedc76d1b8bdd1aafe1dc9b4e493b Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 20:46:14 -0500 Subject: [PATCH 4/7] highlight proactive resilience loop --- dashboard/src/App.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 3bf0b47..7281ad7 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -225,17 +225,17 @@ function ReadinessPanel({ readiness, expanded, onToggle, onRefresh }: { readines function HowItWorks({ onClose }: { onClose: () => void }) { useEffect(() => { const close = (event: KeyboardEvent) => event.key === 'Escape' && onClose(); document.addEventListener('keydown', close); return () => document.removeEventListener('keydown', close) }, [onClose]) const steps = [ - { number: '01', name: 'ARGUS', role: 'Observe', icon: Shield, tone: 'argus', text: 'Falco, Cilium, and Kyverno evidence becomes one reasoned security finding.', output: 'Threat + blast radius' }, + { number: '01', name: 'ARGUS', role: 'Detect + contain', icon: Shield, tone: 'argus', text: 'Kyverno guards admission while eBPF, Falco, and Cilium watch the kernel, runtime, and network—stopping threats close to where they start.', output: 'Early containment + evidence' }, { number: '02', name: 'SOG', role: 'Connect', icon: GitBranch, tone: 'sentinel', text: 'The Sentinel Operations Graph connects services, dependencies, evidence, incidents, and trust.', output: 'Shared operational truth' }, { number: '03', name: 'SENTINEL + OPENAI', role: 'Decide', icon: BrainCircuit, tone: 'sentinel', text: 'Sentinel explains what changed, ranks risk, and chooses what requires attention.', output: 'Evidence-grounded decision' }, { number: '04', name: 'HUMAN GOVERNANCE', role: 'Authorize', icon: UserCheck, tone: 'human', text: 'Low-risk, proven actions can run autonomously. High-impact actions wait for explicit approval.', output: 'Accountable control' }, - { number: '05', name: 'PHOENIX', role: 'Recover + verify', icon: Zap, tone: 'phoenix', text: 'Phoenix exercises resilience, executes the approved recovery, and verifies service health.', output: 'Measured recovery evidence' }, + { number: '05', name: 'PHOENIX', role: 'Break + recover', icon: Zap, tone: 'phoenix', text: 'Phoenix injects bounded faults before customers find them, recovers the service, and verifies that health is actually restored.', output: 'Proven resilience before impact' }, ] - return
+ return
{steps.map(({ icon: Icon, ...step }, index) =>
{step.number}
{step.role}

{step.name}

{step.text}

{step.output}{index < steps.length - 1 && }
)}
-
Say this to a judge≈ 24 seconds at a natural pace
“Argus turns live security telemetry into evidence. The SOG connects that evidence to the affected services. Sentinel uses OpenAI to explain the risk and decide what needs attention. Proven, low-risk recovery can run autonomously; high-risk action requires a human. Phoenix restores the service, verifies the outcome, and feeds that evidence back into Sentinel.”
+
Say this to a judge≈ 25 seconds at a natural pace
“Argus watches admission, the kernel, runtime, and network to stop threats close to where they start. Phoenix tests bounded failures before customers find them. The SOG connects both, and Sentinel uses OpenAI to explain what matters next. Safe recovery can run automatically; risky action waits for a human. Nothing is called fixed until recovery is verified.”
AUTONOMOUSDetection · correlation · diagnosis · bounded recovery · verification
HUMAN-GOVERNEDHigh blast radius · ambiguous evidence · destructive or unproven actions
EVERY RECORD SAYS WHAT IT ISLIVE OBSERVEDLIVE CHAOS MESHSYNTHETIC SIMULATORREPLAYED EVIDENCE
-
SOG means Sentinel Operations GraphAutonomous resilience through AI—with humans governing high-risk actions.
+
SOG means Sentinel Operations GraphDetect early. Recover before impact. Keep humans in control of high-risk actions.
} From 26fb6c94ea16a57abf8799856266678a2f466fa5 Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 20:54:34 -0500 Subject: [PATCH 5/7] fix explainer modal overflow --- dashboard/src/readability.css | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/dashboard/src/readability.css b/dashboard/src/readability.css index 398dc07..0b93ce8 100644 --- a/dashboard/src/readability.css +++ b/dashboard/src/readability.css @@ -248,10 +248,49 @@ font-size: 13px; } +/* The app header is globally sticky and 70px tall. Reset those rules inside the modal. */ +.how-wrap { + display: flex; + align-items: flex-start; + justify-content: center; + overflow-y: auto; + padding: clamp(10px, 2.2vh, 22px); +} + +.how-backdrop { + position: fixed; +} + +.how-modal { + flex: 0 0 auto; + max-height: none; + overflow: visible; +} + +.how-modal > header { + position: relative; + top: auto; + z-index: auto; + height: auto; + min-height: 130px; + overflow: visible; + background: transparent; + backdrop-filter: none; +} + +.how-step { + min-height: 292px; + padding-bottom: 62px; +} + @media (max-width: 700px) { .evidence-actions { align-items: flex-end; flex-direction: column; gap: 7px; } + + .how-modal { + min-height: 100%; + } } From a0bd0c6d66a7ba101b4fa0308f39433b81ef2851 Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 21:05:42 -0500 Subject: [PATCH 6/7] add operator-ready incident reports --- backend/src/main.py | 36 +++++++++- backend/tests/test_main.py | 6 ++ dashboard/src/App.tsx | 19 +++++- dashboard/src/readability.css | 123 ++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/backend/src/main.py b/backend/src/main.py index 7816906..74773d4 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -166,8 +166,10 @@ def _correlated_incidents(timeline: list[dict]) -> list[dict]: proof_record = next((item for item in reversed(ordered) if isinstance(item.get("payload", {}).get("lifecycle"), list)), None) proof = None + proof_payload = {} if proof_record: payload = proof_record.get("payload", {}) + proof_payload = payload proof = { "lifecycle": payload.get("lifecycle", []), "metrics": payload.get("metrics", {}), @@ -175,6 +177,38 @@ def _correlated_incidents(timeline: list[dict]) -> list[dict]: "experiment_id": payload.get("scenario_id"), } entity = next((item.get("entity_name") for item in ordered if item.get("entity_name")), None) + entity_id = next((item.get("entity_id") for item in ordered if item.get("entity_id")), None) + argus_record = next((item for item in ordered if _source(item) == "argus"), {}) + phoenix_record = next((item for item in reversed(ordered) if _source(item) == "phoenix"), {}) + argus_payload = argus_record.get("payload", {}) + phoenix_payload = phoenix_record.get("payload", {}) + lifecycle = proof_payload.get("lifecycle", []) + + def stage_evidence(stage: str, fallback: str) -> str: + record = next((item for item in lifecycle if item.get("stage") == stage), None) + return str(record.get("evidence")) if record and record.get("evidence") else fallback + + detection = str(argus_record.get("summary") or _summary(argus_record) or "Detection detail not supplied") + recovery = str(phoenix_record.get("summary") or _summary(phoenix_record) or "Recovery detail not supplied") + resource = str(entity or entity_id or "Unmapped resource") + root_cause = (argus_payload.get("root_cause") or argus_payload.get("causal_chain") + or phoenix_payload.get("root_cause") or "Not established by the supplied evidence") + report = { + "executive_summary": f"{detection.rstrip('.')}. Phoenix reported: {recovery.rstrip('.')}.", + "detection": detection, + "affected_resource": resource, + "impact": str(argus_payload.get("impact") or + f"{severity.capitalize()} evidence was attached to {resource}; no wider impact is claimed without supporting evidence."), + "root_cause": str(root_cause), + "decision": stage_evidence("decision", "No explicit decision record was supplied"), + "governance": stage_evidence("human_approval", "Approval evidence was not supplied"), + "recovery": stage_evidence("recovery", recovery), + "verification": stage_evidence("verification", "Verification evidence was not supplied"), + "operator_next_step": str(phoenix_payload.get("operator_next_step") or + "Review the supporting evidence and keep the resource under observation."), + "evidence_source": str(proof_payload.get("evidence_source") or + " + ".join(sorted({str(item.get("provenance") or "observed") for item in ordered}))), + } incidents.append({ "incident_id": f"corr:{correlation_id}", "correlation_id": correlation_id, "title": f"Argus → Phoenix lifecycle{f' for {entity}' if entity else ''}", @@ -182,7 +216,7 @@ def _correlated_incidents(timeline: list[dict]) -> list[dict]: "started_at": ordered[0].get("timestamp"), "updated_at": ordered[-1].get("timestamp"), "sources": sorted(sources), "evidence_count": len(ordered), "timeline": ordered, "provenance": sorted({str(item.get("provenance") or "observed") for item in ordered}), - "proof": proof, + "proof": proof, "report": report, }) return sorted(incidents, key=lambda item: str(item.get("updated_at") or ""), reverse=True) diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index 1b537b4..0c9e05f 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -36,6 +36,10 @@ def test_incident_requires_explicit_cross_agent_correlation(): assert incidents[0]["status"] == "resolved" assert incidents[0]["sources"] == ["argus", "phoenix"] assert [item["id"] for item in incidents[0]["timeline"]] == ["a", "p"] + assert incidents[0]["report"]["detection"] == "detected" + assert incidents[0]["report"]["recovery"] == "recovered" + assert incidents[0]["report"]["root_cause"] == "Not established by the supplied evidence" + assert "no wider impact is claimed" in incidents[0]["report"]["impact"] def test_incident_exposes_only_supplied_proof_stages_and_metrics(): @@ -45,6 +49,8 @@ def test_incident_exposes_only_supplied_proof_stages_and_metrics(): assert [item["stage"] for item in incident["proof"]["lifecycle"]] == ["healthy", "verification"] assert incident["proof"]["metrics"]["availability_percent"] == 99.8 assert incident["proof"]["experiment_id"] == "chaos-1" + assert incident["report"]["verification"] == "Verification evidence was not supplied" + assert incident["report"]["evidence_source"] == "Falco + HTTP probe" @pytest.mark.asyncio diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 7281ad7..10f7aac 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -27,6 +27,7 @@ type Incident = { started_at?: string; updated_at?: string; sources?: string[]; evidence_count?: number provenance?: string[]; timeline?: Timeline[] proof?: { lifecycle?: ProofStage[]; metrics?: Record; evidence_source?: string; experiment_id?: string } + report?: { executive_summary?: string; detection?: string; affected_resource?: string; impact?: string; root_cause?: string; decision?: string; governance?: string; recovery?: string; verification?: string; operator_next_step?: string; evidence_source?: string } } type ProofStage = { stage: string; timestamp?: string; source?: string; evidence?: string; status?: string } type Overview = { @@ -146,6 +147,22 @@ function LiveSignalFeed({ events, onSelect }: { events: Timeline[]; onSelect: (e } const proofStages = ['healthy', 'fault_injected', 'detection', 'decision', 'human_approval', 'recovery', 'verification'] +function IncidentReport({ incident }: { incident: Incident }) { + const report = incident.report + if (!report) return

Incident report unavailable

This correlation predates structured reporting. Supporting evidence remains available below.

+ const sections = [ + { label: 'What happened', value: report.detection, tone: 'argus' }, + { label: 'Affected resource', value: report.affected_resource, tone: 'sentinel' }, + { label: 'Impact assessment', value: report.impact, tone: 'high' }, + { label: 'Root cause', value: report.root_cause, tone: 'medium' }, + { label: 'Decision + containment', value: report.decision, tone: 'sentinel' }, + { label: 'Human governance', value: report.governance, tone: 'human' }, + { label: 'Recovery performed', value: report.recovery, tone: 'phoenix' }, + { label: 'Verification result', value: report.verification, tone: 'low' }, + ] + return
OPERATOR-READY REPORT

Incident understood and recovery accounted for

{(incident.provenance || ['observed']).map(label).join(' + ')}

{report.executive_summary}

{sections.map(section =>
{section.label}

{section.value || 'Not supplied by the available evidence'}

)}
OPERATOR FOLLOW-UP

{report.operator_next_step}

Evidence source · {report.evidence_source || 'not recorded'}
+} + function ResilienceProof({ incident, onSignal }: { incident: Incident; onSignal: (signal: Timeline) => void }) { const supplied = new Map((incident.proof?.lifecycle || []).map(item => [item.stage, item])) const start = incident.proof?.lifecycle?.find(item => item.timestamp)?.timestamp @@ -264,7 +281,7 @@ function DetailDrawer({ detail, data, onClose, onDetail }: { detail: Detail; dat } if (detail.kind === 'incident') { const incident = detail.incident - return
STATUS{incident.status || 'open'}SEVERITY{incident.severity || 'info'}SOURCES{(incident.sources || []).join(' + ') || 'unknown'}EVIDENCE{incident.evidence_count || incident.timeline?.length || 0}
onDetail({ kind: 'signal', signal })} />
+ return
STATUS{incident.status || 'open'}SEVERITY{incident.severity || 'info'}SOURCES{(incident.sources || []).join(' + ') || 'unknown'}EVIDENCE{incident.evidence_count || incident.timeline?.length || 0}
onDetail({ kind: 'signal', signal })} /> } const signal = detail.kind === 'signal' ? detail.signal : undefined, component = detail.kind === 'component' ? detail.component : undefined const evidence = component?.evidence || (signal ? [signal] : []) diff --git a/dashboard/src/readability.css b/dashboard/src/readability.css index 0b93ce8..d709ddf 100644 --- a/dashboard/src/readability.css +++ b/dashboard/src/readability.css @@ -294,3 +294,126 @@ min-height: 100%; } } + +.incident-report { + margin: 18px 0 22px; + padding: 17px; + border: 1px solid #b68cff45; + border-radius: 11px; + background: linear-gradient(145deg, #b68cff0b, #0a1421 48%, #09111c); +} + +.incident-report.missing { + border-style: dashed; + color: #91a1b6; +} + +.report-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 18px; +} + +.report-title > div > span, +.report-grid article > span, +.report-next span { + color: #8f7dbd; + font: 700 8px JetBrains Mono, monospace; + letter-spacing: .8px; +} + +.report-title h3 { + display: flex; + align-items: center; + gap: 7px; + margin-top: 6px; + color: #edf2f9; + font-size: 14px; +} + +.report-title h3 svg { + width: 15px; + color: #55e6a5; +} + +.report-executive { + margin: 14px 0; + padding: 13px 14px; + border-left: 3px solid #b68cff; + border-radius: 0 8px 8px 0; + background: #b68cff0c; + color: #d5ddea; + font-size: 12px; + line-height: 1.65; +} + +.report-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.report-grid article { + min-width: 0; + padding: 11px 12px; + border: 1px solid #27384f; + border-left: 2px solid #b68cff; + border-radius: 8px; + background: #09131f; +} + +.report-grid article.argus { border-left-color: #45d9ff; } +.report-grid article.phoenix, +.report-grid article.low { border-left-color: #55e6a5; } +.report-grid article.high { border-left-color: #ffad55; } +.report-grid article.medium, +.report-grid article.human { border-left-color: #ffd166; } + +.report-grid article p { + margin-top: 6px; + color: #b7c3d2; + font-size: 10px; + line-height: 1.55; + overflow-wrap: anywhere; +} + +.report-next { + display: flex; + gap: 10px; + margin-top: 9px; + padding: 12px; + border: 1px solid #55e6a53b; + border-radius: 8px; + background: #55e6a508; +} + +.report-next > svg { + width: 17px; + flex: 0 0 auto; + color: #55e6a5; +} + +.report-next p { + margin-top: 5px; + color: #c2ccda; + font-size: 10px; + line-height: 1.5; +} + +.report-next small { + display: block; + margin-top: 6px; + color: #71849d; + font: 600 8px JetBrains Mono, monospace; +} + +@media (max-width: 640px) { + .report-title { + flex-direction: column; + } + + .report-grid { + grid-template-columns: 1fr; + } +} From 389915550bd9484a7289c919ac3636a76847491f Mon Sep 17 00:00:00 2001 From: kaushik-kumaran Date: Sat, 18 Jul 2026 22:31:26 -0500 Subject: [PATCH 7/7] make layered defense explicit --- dashboard/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 10f7aac..8e40815 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -248,11 +248,11 @@ function HowItWorks({ onClose }: { onClose: () => void }) { { number: '04', name: 'HUMAN GOVERNANCE', role: 'Authorize', icon: UserCheck, tone: 'human', text: 'Low-risk, proven actions can run autonomously. High-impact actions wait for explicit approval.', output: 'Accountable control' }, { number: '05', name: 'PHOENIX', role: 'Break + recover', icon: Zap, tone: 'phoenix', text: 'Phoenix injects bounded faults before customers find them, recovers the service, and verifies that health is actually restored.', output: 'Proven resilience before impact' }, ] - return
+ return
{steps.map(({ icon: Icon, ...step }, index) =>
{step.number}
{step.role}

{step.name}

{step.text}

{step.output}{index < steps.length - 1 && }
)}
Say this to a judge≈ 25 seconds at a natural pace
“Argus watches admission, the kernel, runtime, and network to stop threats close to where they start. Phoenix tests bounded failures before customers find them. The SOG connects both, and Sentinel uses OpenAI to explain what matters next. Safe recovery can run automatically; risky action waits for a human. Nothing is called fixed until recovery is verified.”
AUTONOMOUSDetection · correlation · diagnosis · bounded recovery · verification
HUMAN-GOVERNEDHigh blast radius · ambiguous evidence · destructive or unproven actions
EVERY RECORD SAYS WHAT IT ISLIVE OBSERVEDLIVE CHAOS MESHSYNTHETIC SIMULATORREPLAYED EVIDENCE
-
SOG means Sentinel Operations GraphDetect early. Recover before impact. Keep humans in control of high-risk actions.
+
}