Skip to main content

Plan: Authority-First UX Refactor (2026-02-22 Feedback)

Context

The founder over-rotated on execution telemetry metrics. The CTO pushed back: "We collect CONFIGURATION (authority paths) first. Execution proof is best-effort. We can't guarantee execution counts like Datadog can." The founder agreed and updated Stitch designs. Three new UX feedback docs reflect this corrected direction. This plan implements those changes.

Part 1: Critical Review

Alignment Assessment

All three docs align with the "authority-first, execution-secondary" correction:

DocKey SignalVerdict
Overview PageHero KPI = "AUTONOMOUS AUTHORITY PATHS" (config discovery), not "Total Executions"Correct pivot
Authority Path DashboardRemove 30D Trend column, rename "30D Runs" → "Observed Executions (30d)" — execution is secondary, labeled as "observed" (not guaranteed)Correct
Authority Path DetailsGraph (authority chain) becomes first card; RuntimeActivity block removed; execution demoted to 2 compact lines in risk stripCorrect

Contradictions Between Documents

None found. Consistent direction: remove all deltas/trends, de-emphasize execution counts, promote authority paths as primary.

One naming consistency note: sidebar should become "Risk Clusters" (Doc 1), Overview section becomes "Top Risk Clusters" (Doc 1), page title stays "Risk Clusters" — all consistent.

Backend Data Gaps

Gap 1: Overview Card B — "EXECUTED PATHS WITH INVALID OWNERSHIP" Current backend has ownership_invalid_count (all invalid paths) and total_executions_30d (all executions). Neither gives "count of paths where execution_30d > 0 AND ownership is invalid."

  • Fix: Add counter in existing activePaths loop in posture-service.ts (line 64).
  • Semantic precision: "Invalid ownership" means orphaned + ambiguous + connector-vocabulary "invalid" — but NOT "unknown". Unknown means insufficient metadata, which is a data gap, not a governance failure. The UX brief's Card B label ("INVALID OWNERSHIP") implies a definitive status, so unknown should be excluded. Filter: ownership_status IN ('orphaned', 'ambiguous', 'invalid').
  • Truncation risk: The activePaths loop is fed by a capped query (PATHS_CAP = 5000, posture-service.ts line 40). If the tenant exceeds 5000 active paths, the counter undercounts silently. Mitigation: when truncated === true, append "~" prefix to the count in the UI (e.g., "~38") and add truncated to PathPostureSummary type so the UI can surface it. This is already flagged by console.warn on line 58 — just need to propagate the signal to the API response.

Gap 2: "Export Report" button No export infra exists. Stub as disabled button with "Coming soon" tooltip.

Gap 3: Detail page ownership decomposition labels The feedback wants "Automation owner" + "Runtime identity" rows. Current code already has "Automation / Agent" + "Service Principal" rows reading owner_name from entity properties. This is a label-only rename — data is already there.

Delta Feature Tension — Critical Decision

The problem: We just implemented a full delta system (PostureSnapshotDoc, prior_execution_30d, DeltaBadge, DeltaTrendCell). Now all 3 docs say "remove all deltas."

Decision: KEEP backend infrastructure, REMOVE from UI rendering.

Rationale:

  • Backend delta infra (snapshots, prior counts) is cheap to maintain, architecturally clean, and may serve future needs (admin views, PDF reports, API consumers)
  • UI component files (DeltaBadge.tsx, delta.ts) are small and well-tested — keep files, remove imports from these 3 pages
  • API response shapes stay additive (delta fields remain, UI ignores them)
  • Seed script posture_snapshots remain for dev/testing

Part 2: Implementation Plan

Stream A: Overview Page

Files: ui/src/pages/OverviewPage.tsx, ui/src/components/PathRiskClusterCard.tsx, ui/src/components/Layout.tsx, src/services/posture-service.ts, ui/src/api/api-types.ts

A1. Backend: add executed_invalid_ownership_count

src/services/posture-service.ts — inside existing activePaths loop (line 64):

const INVALID_STATUSES = new Set(["orphaned", "ambiguous", "invalid"]);
let executed_invalid_ownership_count = 0;
for (const path of activePaths) {
// ...existing code...
if (path.current_state.execution_30d > 0 && INVALID_STATUSES.has(path.current_state.ownership_status)) {
executed_invalid_ownership_count++;
}
}

Excludes unknown — "unknown" is a data gap, not an ownership violation. Only orphaned/ambiguous/invalid (connector vocab) count as "invalid ownership" per the UX brief's intent.

Add executed_invalid_ownership_count and truncated: boolean to return object and PostureSummaryResult type. Add both to PathPostureSummary in ui/src/api/api-types.ts. UI renders "~{N}" when truncated.

A2. OverviewPage.tsx

ChangeBeforeAfter
Title (line 73)"Autonomous Authority Posture""Autonomous Authority Surface"
SubtitleNone"Deterministic mapping of standing autonomous authority derived from observed execution." (muted text below title)
Section header (line 86)"Observed Autonomous Execution (30d)"Remove entirely
KPI Card 1 label"Total Executions""AUTONOMOUS AUTHORITY PATHS"
KPI Card 1 valuetotalExecutionsposture.active_paths
KPI Card 1 subtextDeltaBadge + "vs prior 30d""Deterministically mapped" (muted)
KPI Card 2 label"Executed Autonomous Paths""EXECUTED PATHS WITH INVALID OWNERSHIP"
KPI Card 2 valuetotalPathsposture.executed_invalid_ownership_count
KPI Card 2 subtextDeltaBadge + "vs prior 30d""Observed runtime activity (30d)" (muted)
Section heading (line 123)"Priority Exposures""Top Risk Clusters"
Section right side"View all" link only"Export Report" button (disabled stub) + "View all" link
Cluster card propstotalExecutions={totalExecutions} totalPaths={totalPaths}Remove both props

Remove imports: DeltaBadge. Remove totalExecutions/totalPaths variables.

A3. PathRiskClusterCard.tsx — complete restructure

Before:

Title
{N} Executions + DeltaBadge
X% of total autonomous executions
{Y} Paths (Z% of total paths)
Oldest finding: Nd | +N New Findings

After:

Title
{Y} Paths ← primary metric, large
{X} Observed runtime executions (30d) ← secondary, smaller muted
+N New ← green pill, bottom-right
  • Remove props: totalExecutions, totalPaths
  • Remove imports: DeltaBadge, computeDeltaPct
  • Remove: runtimeSharePct/pathSharePct calculations, "% of total" lines, "Oldest finding" row
  • Primary: cluster.path_count Paths (text-3xl font-bold)
  • Secondary: cluster.total_execution_30d Observed runtime executions (30d) (text-sm text-gray-500)
  • Pill: "+N New" (green, if new_findings_30d > 0) or "0 New" (gray muted)

A4. Sidebar rename

ui/src/components/Layout.tsx line 24: "Clusters""Risk Clusters"

A5. ClustersListPage.tsx

Remove totalExecutions/totalPaths props from PathRiskClusterCard usage. Remove usePostureSummary hook if no longer needed on this page.


Stream B: Authority Path Dashboard (table)

Files: ui/src/pages/AuthorityPathsListPage.tsx, ui/src/components/badges.tsx

B1. Remove "30D Trend" column

  • Remove column header <th> for "30d Trend"
  • Remove <DeltaTrendCell> cell in row
  • Remove DeltaTrendCell import
  • Update colSpan in expanded row (11 → 10)

B2. Rename "30d Runs" → "Observed Executions (30d)"

Column header text change only.

B3. Fix OwnershipBadge — make all "Invalid" statuses red

ui/src/components/badges.tsx — update the colors map:

Before:
orphaned: "bg-red-100 text-red-800" → "Invalid"
ambiguous: "bg-yellow-100 text-yellow-800" → "Invalid"
unknown: "bg-gray-100 text-gray-500" → "Unknown"

After:
orphaned: "bg-red-100 text-red-800" → "Invalid" (unchanged)
ambiguous: "bg-red-100 text-red-800" → "Invalid" (yellow → red)
invalid: "bg-red-100 text-red-800" → "Invalid" (NEW — connector vocab)
unknown: "bg-gray-100 text-gray-500" → "Unknown" (unchanged)

The backend uses "invalid" as a raw status from connector vocabulary (posture-service.ts line 79 queries for it explicitly). Without a mapping, it falls through to the default gray — making an actual invalid status look neutral. Add it to both colors and labels maps.


Stream C: Authority Path Detail Page

Files: ui/src/pages/AuthorityPathDetailPage.tsx

C1. Move graph to position 3 (after header, before risk strip)

Reorder JSX: move the Execution Path <div> block (currently lines 669-677) to right after <PathHeader>.

Add small label above graph: "Execution-derived authority path" (muted, small text).

New section order:

  1. Breadcrumb
  2. PathHeader
  3. Execution Path (graph) ← moved up
  4. Active Risk Conditions strip ← merged component
  5. Ownership ← simplified (side-by-side with Authority State per mockup)
  6. Authority State (AutonomousExecutionModel) ← VISIBLE (secondary to Ownership, not collapsed)
  7. Deterministic Identity Linkage ← collapsed by default
  8. Automation Metadata ← collapsed by default (rename from "Autonomous Execution Model" details if separate)
  9. Audit Metadata ← already collapsed

Note: The UX brief (line 111-115) explicitly says "Keep Authority state card... ensure it is secondary to Ownership... No extra telemetry language inside it." It does NOT say collapse it. Only "deeper sections" below Ownership + Authority State get collapsed (line 118): "Deterministic linkage proof, Automation metadata, Audit metadata, Any identity linkage details."

C2. Remove RuntimeActivityBar, merge runtime data into RiskConditionsStrip

Delete RuntimeActivityBar function entirely (lines 156-215).

Modify RiskConditionsStrip to become "Active risk conditions" with a split layout:

┌─────────────────────────────────────────────────────────────────┐
│ ACTIVE RISK CONDITIONS │
│ │
│ [Invalid owner ·Since 12d] [Sensitive: fin ·Since 12d] ... │ ← left: finding tiles
│ │
│ Last execution: 3d ago │ ← right: compact runtime
│ Observed executions (30d): 47 │
└─────────────────────────────────────────────────────────────────┘

Changes to RiskConditionsStrip:

  • Add detail: AuthorityPathDetail prop alongside pathId
  • Rename heading: "Risk Conditions" → "Active risk conditions"
  • Wrap in a single card container (rounded-lg border bg-white p-5)
  • Inside: flex row — left side is finding tiles, right side is compact execution stats from detail.current_state (last_execution_at + execution_30d)
  • NO delta, NO "vs prior 30d"
  • When no active findings, still show the right-side runtime stats (don't return null)
  • CRITICAL: Remove the orphaned_ownership exclusion filter (currently line 311: f.finding_type !== "orphaned_ownership"). The UX brief explicitly requires "Invalid owner" as a risk condition tile (ux-feedback line 77). Show ALL active findings including orphaned_ownership.

C2a. Risk tile label normalization

Current code renders finding.finding_type.replace(/_/g, " ") (line 250), producing "orphaned ownership", "reachable sensitive domain", etc. The UX brief requires specific labels: "Invalid owner", "Sensitive: fin", "LLM egress".

Add a label map:

const RISK_TILE_LABELS: Record<string, string> = {
orphaned_ownership: "Invalid owner",
reachable_sensitive_domain: "Sensitive: fin",
llm_egress: "LLM egress",
external_egress: "External egress",
dormant_authority: "Dormant",
scope_drift: "Scope drift",
unknown_identity_binding: "Unbound identity",
ownership_ambiguous: "Ambiguous owner",
};

Fallback: finding_type.replace(/_/g, " ") for unmapped types.

C3. Simplify Ownership to 2 rows

Remove the "Path Ownership" aggregate first row (lines 385-399). Keep only:

RowLabel (before)Label (after)Value
1"Automation / Agent" + "Business owner""Automation owner"ownerName ?? "Not assigned"
2"Service Principal" + "Runtime identity""Runtime identity"identityOwner ?? "Not assigned" (styled invalid if missing)

Remove: "Aggregate status across all objects in this path" text, top-level badge.

For "Not assigned" on runtime identity when ownership is invalid: add text-red-600 styling + optional reason like "Service principal owner departed" if available from entity properties.

C4. Collapse deep sections by default (NOT Authority State)

Per UX brief (line 118): collapse "Deterministic linkage proof, Automation metadata, Audit metadata, Any identity linkage details" by default. Authority State stays visible.

Sections to collapse:

  • IdentityLinkageSection — collapsed by default (currently always open)
  • AuditMetadata — already collapsed by default (no change)

AutonomousExecutionModel (Authority State) stays always visible — it is secondary to Ownership but not hidden.

Collapsible pattern — same as existing AuditMetadata (line 539):

const [open, setOpen] = useState(false);
return (
<div className="rounded-lg border border-gray-200 bg-white">
<button onClick={() => setOpen(!open)} className="flex w-full items-center gap-2 px-5 py-3 text-left ...">
{open ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
Section Title
</button>
{open && <div className="border-t border-gray-100 px-5 py-4">...content...</div>}
</div>
);

Part 3: Files Changed Summary

FileStreamChange
src/services/posture-service.tsA1Add executed_invalid_ownership_count in existing loop
ui/src/api/api-types.tsA1Add field to PathPostureSummary
ui/src/pages/OverviewPage.tsxA2Title, subtitle, KPI cards, section heading, remove deltas
ui/src/components/PathRiskClusterCard.tsxA3Restructure: paths primary, executions secondary, remove deltas
ui/src/components/Layout.tsxA4Sidebar: "Clusters" → "Risk Clusters"
ui/src/pages/ClustersListPage.tsxA5Remove posture props from card
ui/src/pages/AuthorityPathsListPage.tsxB1-B2Remove 30D Trend column, rename 30d Runs
ui/src/components/badges.tsxB3OwnershipBadge ambiguous: yellow → red
ui/src/pages/AuthorityPathDetailPage.tsxC1-C4Reorder graph, remove RuntimeActivityBar, merge risk strip (include orphaned_ownership + label map), simplify ownership, Authority State stays visible, collapse IdentityLinkage
test/services/posture-service.test.tsA1Add tests for executed_invalid_ownership_count semantics + truncation

NOT changed (delta infra kept):

  • src/domain/posture/types.ts, src/storage/mongo/adapters/posture-snapshot-adapter.ts
  • ui/src/components/DeltaBadge.tsx, ui/src/utils/delta.ts
  • scripts/seed-demo-w1.ts
  • All backend delta computation in posture/risk-cluster services

Part 4: Verification

Build & Type Safety

cd sv0-platform && npm run typecheck    # zero errors
cd ui && npm run build # production build succeeds
npm test # all unit tests pass

API Contract Verification

# Seed demo data and check the new field
npx tsx scripts/seed-demo-w1.ts --reset
curl -s http://localhost:3000/api/v1/posture/summary -H "X-Tenant-Id: demo-w1" | jq '.data.executed_invalid_ownership_count, .data.truncated'
# Should return a number (not null) and false

Posture Service Unit Test Updates

Add test cases to test/services/posture-service.test.ts:

  1. executed_invalid_ownership_count excludes paths with ownership_status: "unknown" and paths with execution_30d: 0
  2. executed_invalid_ownership_count includes paths with ownership_status: "orphaned" AND execution_30d > 0
  3. truncated is true when path count hits PATHS_CAP

Visual Verification (with seed-demo-w1 data)

  1. Overview: title = "Autonomous Authority Surface" with subtitle; Card A = path count + "Deterministically mapped"; Card B = executed invalid ownership count + "Observed runtime activity (30d)"; NO deltas anywhere; "Top Risk Clusters" heading; cluster cards show "{N} Paths" primary
  2. Authority Paths table: 10 columns (not 11); "Observed Executions (30d)" header; no 30D Trend column; orphaned AND ambiguous AND raw "invalid" all render as red "Invalid" badge
  3. Authority Path Detail: graph is first card after header; no RuntimeActivityBar; "Active risk conditions" strip shows "Invalid owner" tile (NOT filtered out) + other tiles with human labels ("Sensitive: fin", "LLM egress"); compact execution summary on right; ownership = 2 rows only; Authority State visible (not collapsed); IdentityLinkage collapsed by default
  4. Sidebar: "Risk Clusters" (not "Clusters")

Regression

  • ClustersListPage still renders without posture data
  • Cluster drill-down (click card → filtered authority paths) still works
  • Expanded row in table uses correct colSpan (10)
  • Graph renders correctly in new position
  • OwnershipBadge with raw "invalid" status (from connector vocab) renders red "Invalid", not gray fallback