- 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.
5.4 KiB
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_LOADproduces correct URL for filters.ROUTE_CASEtriggers case header load.CASE_LOAD_OKtriggers evidence load (and the integration layer triggers other nested loads deterministically).DECISION_CREATE_OKcloses drawer and refreshes case header.BUNDLE_EXPORT_OKemitsDOWNLOAD.
Recommended:
- Golden state snapshots to ensure backwards compatibility when the state model evolves.
Document Version: 1.0 Target Platform: Angular v17 + TypeScript