Files
git.stella-ops.org/docs/ux/TRIAGE_UI_REDUCER_SPEC.md
StellaOps Bot 7503c19b8f Add determinism tests for verdict artifact generation and update SHA256 sums script
- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering.
- Created helper methods for generating sample verdict inputs and computing canonical hashes.
- Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics.
- Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
2025-12-24 02:17:34 +02:00

5.4 KiB

StellaOps Triage UI Reducer Spec (Pure State + Explicit Commands)

0. Purpose

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

  • State transitions are pure functions.
  • Side effects are emitted as explicit commands.
  • Enables UI replay for debugging (aligns with StellaOps determinism and replay ethos).

Target stack: Angular v17 + TypeScript.

1. Core Concepts

  • Action: user/system event (route change, button click, HTTP success).
  • State: all data required to render triage surfaces.
  • Command: side-effect request (HTTP, download, navigation).

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:

  • routing actions (ROUTE_TABLE, ROUTE_CASE)
  • table load and filter actions (TABLE_LOAD, TABLE_LOAD_OK/ERR, filter updates)
  • case load and nested resource actions (header/evidence/decisions/snapshots/diff)
  • decision drawer open/close and decision create/revoke actions
  • bundle export actions

5. Determinism Requirements

  • Reducer must be pure (no global mutation, time access, randomness).
  • All derived URLs must be deterministic for a given state.
  • ETag/If-None-Match should be supported to reduce payload churn and improve sealed-mode behavior.

6. Unit Testing Requirements

Minimum tests:

  • Reducer purity: no external effects.
  • TABLE_LOAD produces correct URL for filters.
  • ROUTE_CASE triggers case header load.
  • CASE_LOAD_OK triggers evidence load (and the integration layer triggers other nested loads deterministically).
  • DECISION_CREATE_OK closes drawer and refreshes case header.
  • BUNDLE_EXPORT_OK emits DOWNLOAD.

Recommended:

  • Golden state snapshots to ensure backwards compatibility when the state model evolves.

Document Version: 1.0 Target Platform: Angular v17 + TypeScript