= core-risk == purpose Assesses operational risk at decision points by evaluating validation reports, orchestration state, and inference history. Produces a `RiskSummary` that drives approval tier selection and determines whether an operation may proceed, requires user prompting, or must be blocked. == responsibilities * Evaluate validation errors, cycle detection, repeated failures, and inference timeouts * Map risk signals to a risk level (LOW, MEDIUM, HIGH, CRITICAL) * Derive a recommended action (PROCEED, PROMPT_USER, BLOCK) from the risk level * Provide a no-op implementation for deterministic replay paths == non-responsibilities * Does not enforce approvals — that belongs to `core:approvals` * Does not define policy rules — that belongs to `core:policies` * Does not emit events directly (signals are consumed by the approval layer) * Does not mutate state == key types === RiskAssessor * **kind**: interface * **purpose**: Contract for evaluating a `RiskContext` and producing a `RiskSummary`. === DefaultRiskAssessor * **kind**: class * **purpose**: Production implementation that inspects validation errors, cycles, retry exhaustion, and inference timeouts from the context. === NoOpRiskAssessor * **kind**: class * **purpose**: Returns `RiskSummary(LOW, emptyList(), PROCEED)` unconditionally. Used during deterministic replay to avoid running live risk assessment. === RiskContext * **kind**: data class * **purpose**: Immutable snapshot of all signals available for risk assessment at a decision point. * **fields**: `validationReport` (validation issues for the current stage), `orchestrationState` (retry count, failure reason, retry policy), `inferenceState` (inference history for timeout detection) === TierMapping * **kind**: file-level extension functions * **purpose**: Maps `RiskLevel` to `Tier` (LOW→T1, MEDIUM→T2, HIGH→T3, CRITICAL→T4) and maps individual `RiskSignal` instances to their `RiskLevel`. * **variants**: * `RiskLevel.toApprovalTier()` — maps to `core:approvals` Tier * `RiskLevel.toRiskAction()` — maps to `RiskAction` (PROCEED, PROMPT_USER, BLOCK); marked `internal` * `RiskSignal.toRiskLevel()` — per-signal severity (ValidationErrors→MEDIUM, CycleWithoutExit→MEDIUM, InferenceTimeout→MEDIUM, RepeatedFailure→HIGH); marked `internal` == event flow *Inbound:* None directly. The `RiskContext` is assembled from: * `ValidationReport` (produced by `core:validation`) * `OrchestrationState` (from `core:kernel`) * `InferenceState` (from `core:inference`) *Outbound:* None. `RiskSummary` is returned synchronously to the caller (approval or orchestration layer). == integration points * `core:events.risk` — `RiskSummary`, `RiskLevel`, `RiskSignal`, `RiskAction` (event types) * `core:approvals` — `Tier` enum * `core:validation.model` — `ValidationReport`, `ValidationSeverity` * `core:inference` — `InferenceState`, `InferenceStatus` * `core:kernel` — `OrchestrationState` == invariants * `NoOpRiskAssessor` must be used on replay paths — live assessment on replay would produce non-deterministic results * `RiskContext.orchestrationState` is populated from config before the first assessment call, not from `StageOutcome` (which lives in `core:kernel` and cannot be imported due to dependency direction) == PlantUML diagram [plantuml, core-risk, "png"] ---- include::../../diagrams/core-risk.puml[] ---- == known issues * Signal-to-level mapping is hardcoded in `TierMapping.kt` and not configurable * No support for custom or external risk signals == open questions * Should risk signal mapping become configurable or rule-driven? * Should risk assessment emit events for audit trail purposes?