Stella Ops Triage UI Reducer Spec (Pure State + Explicit Commands)

Audience: Console (Angular) engineers implementing or testing the triage state layer. This spec defines the reducer contract; the Triage UX Guide defines the experience it serves.

0. Purpose

Define a deterministic, testable UI state machine for the triage surfaces:

Target stack: Angular v17 + TypeScript.

1. Core Concepts

Reducer signature:

type ReduceResult = { state: TriageState; cmd: Command };
function reduce(state: TriageState, action: Action): ReduceResult;

2. State Model

export type Lane =
  | "ACTIVE"
  | "BLOCKED"
  | "NEEDS_EXCEPTION"
  | "MUTED_REACH"
  | "MUTED_VEX"
  | "COMPENSATED";

export type Verdict = "SHIP" | "BLOCK" | "EXCEPTION";

export interface MutedCounts {
  reach: number;
  vex: number;
  compensated: number;
}

export interface FindingRow {
  id: string; // caseId == findingId
  lane: Lane;
  verdict: Verdict;
  score: number;
  reachable: "YES" | "NO" | "UNKNOWN";
  vex: "affected" | "not_affected" | "under_investigation" | "unknown";
  exploit: "YES" | "NO" | "UNKNOWN";
  asset: string;
  updatedAt: string; // ISO-8601 UTC
}

export interface CaseHeader {
  id: string;
  verdict: Verdict;
  lane: Lane;
  score: number;
  policyId: string;
  policyVersion: string;
  inputsHash: string;
  why: string; // short narrative
  chips: Array<{ key: string; label: string; value: string; evidenceIds?: string[] }>;
}

export type EvidenceType =
  | "SBOM_SLICE"
  | "VEX_DOC"
  | "PROVENANCE"
  | "CALLSTACK_SLICE"
  | "REACHABILITY_PROOF"
  | "REPLAY_MANIFEST"
  | "POLICY"
  | "SCAN_LOG"
  | "OTHER";

export interface EvidenceItem {
  id: string;
  type: EvidenceType;
  title: string;
  issuer?: string;
  signed: boolean;
  signedBy?: string;
  contentHash: string;
  createdAt: string;
  previewUrl?: string;
  rawUrl: string;
}

export type DecisionKind = "MUTE_REACH" | "MUTE_VEX" | "ACK" | "EXCEPTION";

export interface DecisionItem {
  id: string;
  kind: DecisionKind;
  reasonCode: string;
  note?: string;
  ttl?: string;
  actor: { subject: string; display?: string };
  createdAt: string;
  revokedAt?: string;
  signatureRef?: string;
}

export type SnapshotTrigger =
  | "FEED_UPDATE"
  | "VEX_UPDATE"
  | "SBOM_UPDATE"
  | "RUNTIME_TRACE"
  | "POLICY_UPDATE"
  | "DECISION"
  | "RESCAN";

export interface SnapshotItem {
  id: string;
  trigger: SnapshotTrigger;
  changedAt: string;
  fromInputsHash: string;
  toInputsHash: string;
  summary: string;
}

export interface SmartDiff {
  fromInputsHash: string;
  toInputsHash: string;
  inputsChanged: Array<{ key: string; before?: string; after?: string; evidenceIds?: string[] }>;
  outputsChanged: Array<{ key: string; before?: string; after?: string; evidenceIds?: string[] }>;
}

export interface TriageState {
  route: { page: "TABLE" | "CASE"; caseId?: string };
  filters: {
    showMuted: boolean;
    lane?: Lane;
    search?: string;
    page: number;
    pageSize: number;
  };

  table: {
    loading: boolean;
    rows: FindingRow[];
    mutedCounts?: MutedCounts;
    error?: string;
    etag?: string;
  };

  caseView: {
    loading: boolean;
    header?: CaseHeader;
    evidenceLoading: boolean;
    evidence?: EvidenceItem[];
    decisionsLoading: boolean;
    decisions?: DecisionItem[];
    snapshotsLoading: boolean;
    snapshots?: SnapshotItem[];
    diffLoading: boolean;
    activeDiff?: SmartDiff;
    error?: string;
    etag?: string;
  };

  ui: {
    decisionDrawerOpen: boolean;
    diffPanelOpen: boolean;
    toast?: { kind: "success" | "error" | "info"; message: string };
  };
}

3. Commands

export type Command =
  | { type: "NONE" }
  | { type: "HTTP_GET"; url: string; headers?: Record<string, string>; onSuccess: Action; onError: Action }
  | { type: "HTTP_POST"; url: string; body: unknown; headers?: Record<string, string>; onSuccess: Action; onError: Action }
  | { type: "HTTP_DELETE"; url: string; headers?: Record<string, string>; onSuccess: Action; onError: Action }
  | { type: "DOWNLOAD"; url: string }
  | { type: "NAVIGATE"; route: TriageState["route"] };

4. Actions (Minimum Set)

Action unions will evolve, but should include:

5. Determinism Requirements

6. Unit Testing Requirements

Minimum tests:

Recommended:


Document Version: 1.0 Target Platform: Angular v17 + TypeScript