Implementation Plan: Overview Page + Authority Exposure Brief UX Changes
Date: 2026-03-07 Source: Sergey's feedback — UX Details: Overview page, Clarity: so what? Status: Draft — not yet reviewed by CISO, CEO, or architect
Feedback Validation (Analyst Review)
Before planning implementation, each feedback item is assessed for validity, feasibility, and potential concerns.
1. Risk Badge: P0/P1/P2 → Critical/High/Moderate/Low
Verdict: Valid and correct. Implement as-is.
Current P0/P1/P2 labels are opaque internal shorthand. A CISO reading "P0" must mentally decode it. The proposed labels (Critical/High/Moderate/Low) are self-explanatory and align with how security teams already think.
The proposed logic is also sound and maps almost exactly to what we already compute:
| Current Logic | Proposed Label | Match? |
|---|---|---|
| critical severity + execution > 0 → P0 | Critical: Sensitive domain + active execution + governance failure | ~Yes — cluster definitions already require governance failure (orphaned/unbound) for critical severity |
| critical severity (no exec) OR high + execution → P1 | High: Sensitive domain + active execution | ~Yes |
| everything else → P2 | Moderate: Governance failure but limited/no execution | ~Yes (dormant + governance failure) |
| (no current equivalent) | Low: Dormant authority | Partially new — currently no cluster purely for dormant-without-egress |
Concerns:
- The current P0/P1/P2 logic uses only
severity + execution_30d > 0. Sergey's proposed logic introduces a third axis: governance failure presence. This is more precise but requires the backend to check whether the cluster's finding_types include a governance condition (orphaned_ownership, unknown_identity_binding, scope_drift). This is feasible — thefinding_typesarray on each cluster already contains this information. - The "Low" tier has no current cluster mapping. Today,
dormant_externalis the closest but it's rated "high" because it includes external egress. Pure dormant-without-egress paths don't form a cluster. Recommendation: Keep the four-tier labeling but only apply "Low" todormant_externalwhenexecution_30d === 0. Don't create a new cluster for dormant-only — that's a separate product decision.
2. Cluster Names: Attribute-based → Functional Labels
Verdict: Valid. Implement with one addition.
Current names like "Orphaned + Sensitive" read as filter intersections, not threat descriptions. "Unowned Sensitive Access" communicates the risk immediately. This is a pure label change — no logic impact.
| Current | Proposed | Assessment |
|---|---|---|
| Orphaned + Sensitive | Unowned Sensitive Access | Good |
| Orphaned + Sensitive + LLM | Unowned Sensitive Access with LLM | Good |
| Unbound + Sensitive | Unbound Sensitive Access | Good |
| LLM Egress | LLM Data Egress | Good — adds "Data" to clarify what's egressing |
| Orphaned + External Egress | Unowned External Egress | Good |
| Dormant + External | Dormant External Access | Good |
Concern: Missing cluster. The current system has 7 clusters but Sergey's mapping only covers 6. scope_drift_sensitive ("Scope Drift + Sensitive") is not in the proposed mapping table. This is likely an oversight rather than intentional removal.
Recommendation: Add a 7th row: Scope Drift + Sensitive → "Drifted Sensitive Access" or "Scope-Drifted Sensitive Access". Flag to Sergey for confirmation.
3. Verdict Sentence: New Template
Verdict: Valid with one structural concern.
Current format:
"3 autonomous paths exercised restricted/confidential-scoped authority and invoked endpoints 1,245 times in the last 30 days — all under orphaned ownership."
Proposed format:
"13 autonomous identities accessed sensitive systems 681 times in the last 30 days — none have an assigned owner."
The proposed version is significantly clearer. Key improvements:
- "identities" instead of "paths" — more intuitive for CISOs (they think in identities, not graph paths)
- "accessed sensitive systems" instead of "exercised restricted/confidential-scoped authority" — plain language
- "none have an assigned owner" instead of "all under orphaned ownership" — explains the condition, doesn't use jargon
Concern: Data model mismatch. The current system counts paths as the primary unit. identity_count is available on the cluster response but is a distinct-count derived field. The sentence says <N> autonomous identities — this number must come from cluster.identity_count, NOT cluster.path_count. Currently identity_count is computed correctly in RiskClusterService (unique identity entities across matched paths), so the data is available.
Concern: "accessed verdict_template or action_phrase per cluster definition, not a generic builder function.
Recommendation: Define per-cluster action phrases in RISK_CLUSTER_DEFS:
| Cluster | Action Phrase |
|---|---|
| orphaned_sensitive | "accessed sensitive systems" |
| orphaned_sensitive_llm | "sent sensitive data to an LLM" |
| unbound_sensitive | "accessed sensitive systems" |
| llm_egress | "sent data to LLM endpoints" |
| orphaned_external | "sent data outside the organization" |
| dormant_external | "retain external access but have not executed" |
| scope_drift_sensitive | "accessed sensitive systems with expanded scope" |
Concern: Governance clause mapping. The "— governance condition" suffix varies per cluster:
- orphaned → "none have an assigned owner"
- unbound → "without a bound automation"
- dormant → (no suffix — the verb phrase itself covers it)
- scope_drift → needs a phrase like "with permissions beyond baseline"
This also needs to be per-cluster, not derived generically.
4. Authority Exposure Brief (New Page, Replaces Cluster Detail)
Verdict: Valid direction. Phased implementation recommended.
The current RiskClusterDetailPage is indeed a "header + table" — the header has stats, then immediately dumps a 9-column table. Sergey's proposed structure (Section A-D narrative → then table) is a significant UX improvement for CISO-level consumption.
Section-by-section assessment:
Section A — What Happened: Requires generating a natural-language narrative from cluster data. We have all the inputs (identity_count, execution_30d, sensitive_domains, egress_category). The challenge is making this sound natural, not templated. Feasible — we can use per-cluster narrative templates.
Section B — Am I Exposed? This is structured data we already have:
- Paths:
path_count✓ - Executions (30d):
total_execution_30d✓ - Domains exercised:
sensitive_domains[]✓ - Endpoint type: partially available (egress_category gives external/llm/internal, but not specific endpoint types)
- Active vs dormant: derivable from
execution_30d > 0per path, but not currently aggregated at cluster level
Concern: "Endpoint type" is ambiguous. We track egress_category (external/llm/internal) but not the specific endpoint protocol (REST, SOAP, webhook). If Sergey means egress category, we're fine. If he means the actual endpoint URL type, we'd need connector changes. Recommendation: Clarify with Sergey. For now, treat this as egress category.
Section C — Governance Condition: We need to surface which governance failures apply to this cluster. Currently, finding_types on the cluster tell us this (orphaned_ownership → "Orphaned", scope_drift → "Drift", etc.). We need:
- Orphaned? →
finding_types.includes("orphaned_ownership")✓ - Drift? →
finding_types.includes("scope_drift")✓ - Identity reuse? → Not currently tracked. We'd need a new finding type or derivation (same identity_id across multiple paths). This is feasible but requires evaluator work.
- Long-lived? → Not currently tracked as a finding. Long-lived tokens/credentials aren't surfaced as findings yet. This would need a new evaluator rule checking credential age.
Concern: Two of four governance conditions in Section C don't exist in the system yet:
- Identity reuse — derivable (count paths per identity, flag if > 1), but no finding type exists
- Long-lived tokens — requires credential age data from connectors, which we may not have
Recommendation: Implement Section C with the two conditions we have (orphaned, drift), show "identity reuse" if identity_count < path_count (simple heuristic that's still deterministic), and mark "long-lived" as "Not assessed" until connector data supports it.
Section D — How Do I Fix It? Remediation guidance needs to be per-cluster. This is purely a content/copy exercise — no backend logic needed. We define 1-3 bullets per cluster_key.
Section Table: "View Authority Paths (N)" button → reveals the existing table. This is a straightforward UI change — wrap the table in a collapsible/expandable section, default collapsed.
Implementation Plan
Phase 1: Overview Page Updates (Low risk, high impact)
Scope: Risk badges, cluster names, verdict sentences on the Overview page and Clusters list.
1.1 — Rename Cluster Labels
Files:
sv0-platform/src/services/risk-cluster-service.ts— UpdateRISK_CLUSTER_DEFS[].label
Changes:
orphaned_sensitive: "Orphaned + Sensitive" → "Unowned Sensitive Access"
orphaned_sensitive_llm: "Orphaned + Sensitive + LLM" → "Unowned Sensitive Access with LLM"
unbound_sensitive: "Unbound + Sensitive" → "Unbound Sensitive Access"
llm_egress: "LLM Egress" → "LLM Data Egress"
orphaned_external: "Orphaned + External Egress" → "Unowned External Egress"
dormant_external: "Dormant + External" → "Dormant External Access"
scope_drift_sensitive: "Scope Drift + Sensitive" → "Drifted Sensitive Access" (TBD — confirm with Sergey)
Also update description fields to use functional language instead of attribute-style.
Risk: Low — pure label change, no logic impact. API consumers see the new labels immediately.
Tests: Update any snapshot/unit tests that assert on cluster labels.
1.2 — Replace P0/P1/P2 with Deterministic Risk Labels
Files:
sv0-platform/src/services/risk-cluster-service.ts— ChangeRiskClusterPrioritytype and computationsv0-platform/ui/src/api/api-types.ts— UpdateRiskClusterPrioritytypesv0-platform/ui/src/components/PathRiskClusterCard.tsx— UpdatePRIORITY_STYLES
Backend changes:
Replace:
export type RiskClusterPriority = "P0" | "P1" | "P2";
With:
export type RiskClusterPriority = "critical" | "high" | "moderate" | "low";
Update priority computation (lines 296-300):
// Governance failure = cluster requires orphaned_ownership, unknown_identity_binding, or scope_drift
const hasGovernanceFailure = def.finding_types.some(ft =>
["orphaned_ownership", "unknown_identity_binding", "scope_drift"].includes(ft)
);
const priority: RiskClusterPriority =
def.severity === "critical" && total_execution_30d > 0 && hasGovernanceFailure ? "critical" :
(def.severity === "critical" || def.severity === "high") && total_execution_30d > 0 ? "high" :
hasGovernanceFailure && total_execution_30d === 0 ? "moderate" :
"low";
Frontend changes:
Replace P0/P1/P2 styles with:
const PRIORITY_STYLES: Record<RiskClusterPriority, { label: string; className: string }> = {
critical: { label: "Critical", className: "bg-red-100 text-red-700" },
high: { label: "High", className: "bg-orange-100 text-orange-700" },
moderate: { label: "Moderate", className: "bg-amber-100 text-amber-700" },
low: { label: "Low", className: "bg-blue-100 text-blue-700" },
};
Risk: Low-medium — changes the API contract for priority field. Any external consumers (none currently) would break. Internal UI is updated simultaneously.
Tests: Update priority computation tests. Add test cases for the governance-failure axis.
1.3 — Rewrite Verdict Sentence Builder
Files:
sv0-platform/src/services/risk-cluster-service.ts— Addaction_phraseandgovernance_clauseto cluster defssv0-platform/ui/src/components/PathRiskClusterCard.tsx— RewritebuildVerdictSentence()
Backend changes:
Extend RiskClusterDef with new fields:
export interface RiskClusterDef {
cluster_key: string;
label: string;
description: string;
severity: string;
finding_types: FindingType[];
action_phrase: string; // NEW: "accessed sensitive systems"
governance_clause: string; // NEW: "none have an assigned owner"
dormant_verb?: boolean; // NEW: true for dormant clusters (different sentence structure)
}
Add to each cluster definition:
{
cluster_key: "orphaned_sensitive",
label: "Unowned Sensitive Access",
// ...
action_phrase: "accessed sensitive systems",
governance_clause: "none have an assigned owner",
},
{
cluster_key: "dormant_external",
label: "Dormant External Access",
// ...
action_phrase: "retain external access but have not executed",
governance_clause: "",
dormant_verb: true,
},
// ... etc for all 7 clusters
Serve action_phrase, governance_clause, and dormant_verb in the API response so the frontend can build the sentence.
Frontend changes:
Replace buildVerdictSentence():
function buildVerdictSentence(cluster: PathRiskCluster): string {
const count = cluster.identity_count ?? cluster.path_count;
const noun = count === 1 ? "autonomous identity" : "autonomous identities";
if (cluster.dormant_verb) {
return `${count} ${noun} ${cluster.action_phrase} in the last 30 days.`;
}
const exec = cluster.total_execution_30d.toLocaleString();
const base = `${count} ${noun} ${cluster.action_phrase} ${exec} times in the last 30 days`;
const suffix = cluster.governance_clause ? ` — ${cluster.governance_clause}` : "";
return `${base}${suffix}.`;
}
Risk: Medium — changes the primary text users read. Must be reviewed for copy quality.
Tests: Unit test buildVerdictSentence() with mock cluster data for each cluster type.
1.4 — Update Cluster Sorting (Priority Order)
Files:
sv0-platform/ui/src/pages/ClustersListPage.tsx— Update sort comparatorsv0-platform/ui/src/pages/OverviewPage.tsx— Update sort if applicable
Currently sorts by P0 → P1 → P2. Update to sort by critical → high → moderate → low.
Risk: Low.
Phase 2: Authority Exposure Brief (Medium risk, high impact)
Scope: Replace RiskClusterDetailPage with the new narrative-first layout.
2.1 — Section A: What Happened Narrative
Files:
sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx— Replace header section
Replace the current stats line (N paths | X executions/30d | Y lack valid ownership) with a full narrative paragraph. Use the same action_phrase + governance_clause from the cluster definition, but expand into a richer format:
What Happened: 13 autonomous identities accessed sensitive systems (restricted, confidential) 681 times in the last 30 days. None have an assigned owner. Authority was exercised via REST API endpoints against ServiceNow data domains.
Data sources:
identity_count→ count of identitiesaction_phrase→ verbsensitive_domains[]→ domain namestotal_execution_30d→ execution countgovernance_clause→ ownership/governance condition- Endpoint types → derive from paths' egress_category (aggregate)
- Source systems → derive from paths' source_system (aggregate)
Concern: Source system and endpoint type aggregation aren't in the current cluster API response. We need to add egress_categories: string[] and source_systems: string[] to RiskClusterResult.
Backend changes:
risk-cluster-service.ts— Addegress_categoriesandsource_systemsaggregation togetRiskClusters()and the detail endpoint meta
Risk: Medium — new API fields, new narrative copy.
2.2 — Section B: Am I Exposed?
Files:
sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx— Add structured data section
Display as a compact grid/card layout:
| Metric | Source | Available? |
|---|---|---|
| Authority Paths | path_count | ✓ |
| Executions (30d) | total_execution_30d | ✓ |
| Domains Exercised | sensitive_domains[] | ✓ |
| Endpoint Type | egress_category aggregation | ✓ (after 2.1 backend change) |
| Active vs Dormant | Derive: paths with execution_30d > 0 vs = 0 | Needs new aggregation |
Backend changes:
- Add
active_path_countanddormant_path_countto cluster detail response
Risk: Low — structured data display, straightforward.
2.3 — Section C: Governance Condition
Files:
sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx— Add governance checklist
Display as a checklist with status indicators:
| Condition | Detection | Available? |
|---|---|---|
| Orphaned? | finding_types.includes("orphaned_ownership") | ✓ |
| Drift? | finding_types.includes("scope_drift") | ✓ |
| Identity reuse? | identity_count < path_count (same identity used across multiple paths) | ✓ (derivable) |
| Long-lived? | Credential age > threshold | Not yet — needs connector data |
For each condition, show:
- ✓ green check if not present (healthy)
- ⚠ amber warning if present
- — gray dash if "Not assessed"
Risk: Low for available conditions. "Long-lived" will show "Not assessed" until connector support lands.
2.4 — Section D: Remediation Guidance
Files:
sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx— Add remediation sectionsv0-platform/src/services/risk-cluster-service.ts— Addremediation_guidance: string[]to cluster defs
Per-cluster remediation bullets (advisory only):
| Cluster | Guidance |
|---|---|
| Unowned Sensitive Access | 1. Assign an active owner and revalidate approval. 2. Review whether all sensitive domain access is still required. 3. Restrict scope to exercised domains only. |
| Unowned Sensitive Access with LLM | 1. Assign an active owner immediately. 2. Audit LLM endpoint data flows for sensitive content. 3. Restrict or remove LLM egress until ownership is confirmed. |
| Unbound Sensitive Access | 1. Bind automation to a deterministic identity. 2. Review sensitive domain access scope. 3. Enable runtime logging for unbound execution paths. |
| LLM Data Egress | 1. Verify LLM endpoint authorization and data classification. 2. Restrict outbound payloads to non-sensitive data. 3. Enable content inspection logging. |
| Unowned External Egress | 1. Assign an active owner. 2. Restrict external endpoint invocation to approved destinations. 3. Enable egress logging. |
| Dormant External Access | 1. Revoke external access for dormant authority. 2. Convert long-lived tokens to just-in-time access. 3. If access is still needed, assign an owner and reactivate. |
| Drifted Sensitive Access | 1. Review scope expansion and remove unnecessary roles. 2. Re-baseline authority to current exercised scope. 3. Investigate root cause of drift. |
Risk: Low — static content, no logic. Copy should be reviewed for accuracy.
2.5 — Table Behind "View Authority Paths" Button
Files:
sv0-platform/ui/src/pages/RiskClusterDetailPage.tsx— Wrap existing table in collapsible section
Replace the immediate table render with:
[Button: "View Authority Paths (N)"]
↓ (click to expand)
[Existing table]
Default state: collapsed. This prevents cognitive overload on first load.
Risk: Low — pure UI change, wraps existing table component.
Phase 3: Path Detail Refinement (Low effort)
Scope: Minor changes to the Authority Path Detail page.
3.1 — Observed vs Standing Authority Separation
Files:
sv0-platform/ui/src/pages/AuthorityPathDetailPage.tsx— Add collapsed panel
Current view shows all authority in one view. Add:
- Default view: observed authority only (paths with execution_30d > 0)
- Collapsed panel: "Additional Standing Authority (Not Exercised)" — paths with execution_30d === 0
Risk: Low — UI layout change only.
Sequencing
| Order | Item | Effort | Dependencies |
|---|---|---|---|
| 1 | 1.1 Cluster label rename | ~1 hour | None |
| 2 | 1.2 Risk badge replacement | ~2 hours | None |
| 3 | 1.3 Verdict sentence rewrite | ~3 hours | 1.1 (needs new labels for context) |
| 4 | 1.4 Sort order update | ~30 min | 1.2 (needs new priority type) |
| 5 | 2.5 Table behind button | ~1 hour | None |
| 6 | 2.2 Section B (Am I Exposed?) | ~2 hours | Backend aggregation |
| 7 | 2.3 Section C (Governance) | ~2 hours | None |
| 8 | 2.4 Section D (Remediation) | ~2 hours | None |
| 9 | 2.1 Section A (Narrative) | ~3 hours | 1.3, 2.2 backend fields |
| 10 | 3.1 Observed vs Standing | ~2 hours | None |
Total estimated scope: ~18-20 hours of implementation across frontend and backend.
Phase 1 (items 1-4) can ship independently and provides immediate value. Phase 2 (items 5-9) should ship together as the Authority Exposure Brief. Phase 3 (item 10) can ship independently.
Open Questions for Sergey
- Scope Drift + Sensitive cluster — Missing from the renaming table. Proposed: "Drifted Sensitive Access". Confirm or provide preferred name.
- "Endpoint type" in Section B — Does this mean egress category (external/llm/internal) or the actual protocol/URL type (REST, webhook, etc.)?
- Identity reuse — Is the simple derivation (
identity_count < path_count) sufficient, or does this need a formal finding type with its own evaluator rule? - Long-lived tokens — This governance condition isn't tracked yet. Is it a Phase 1 requirement or can it be deferred?
- Verdict sentence for clusters with 0 identities — If
identity_countis 0 (edge case with unbound paths), should we fall back to path count?
Files Changed (Summary)
Backend (sv0-platform/src/)
| File | Change |
|---|---|
services/risk-cluster-service.ts | Cluster labels, priority logic, new fields (action_phrase, governance_clause, remediation_guidance), aggregation additions |
api/routes/posture.ts | Serve new fields in response |
api/routes/risk-clusters.ts | Serve new fields in detail response |
domain/authority-paths/types.ts | No changes needed |
Frontend (sv0-platform/ui/src/)
| File | Change |
|---|---|
api/api-types.ts | Update RiskClusterPriority, add new fields to PathRiskCluster |
components/PathRiskClusterCard.tsx | Priority styles, verdict sentence rewrite |
pages/OverviewPage.tsx | Sort order update |
pages/ClustersListPage.tsx | Sort order update |
pages/RiskClusterDetailPage.tsx | Full restructure → Authority Exposure Brief layout |
pages/AuthorityPathDetailPage.tsx | Observed vs standing separation |
Tests (sv0-platform/test/)
| File | Change |
|---|---|
| Tests for risk-cluster-service | Update label assertions, add priority logic tests |
| UI component tests (if any) | Update snapshot/assertion tests |