AI risk register
The residual is computed from likelihood and impact; an operator can override the judgment, but never rewrite the score that came before it.
What it is
risk-register is a framework-agnostic risk model: defineRiskEntry derives a branded likelihood by impact residual that callers cannot supply, recordResidualOverride writes a separate exception to the tenant audit chain, and buildRiskTreatmentPlan emits a deterministic, crosswalk-linked evidence artifact. Computed and effective scores remain side by side, so an operator judgment never rewrites the original rating.
What ships in the module
Strict authored risk rows
defineRiskEntry() parses RiskEntryInput without accepting a residual field, then returns a fully checked RiskEntry carrying subject, likelihood, impact, treatment plan, owner, SHA-256 evidence digest, and framework crosswalk pointers. Unknown fields and malformed digests fail before a row is admitted.
Residuals are derived, not typed in
computeResidual() is the only function that returns the nominal Residual type, multiplying the fixed Likelihood and Impact ordinals into an integer from 1 through 25. The runtime RiskEntry schema re-computes that value as a second check, so a hand-assembled mismatch is rejected too.
Overrides stay separate and accountable
recordResidualOverride() leaves the RiskEntry residual untouched and appends a RiskResidualOverrideRecord carrying the computed score, override score, who, why, and when. If the chain append fails, the function throws an evidence-gap InternalError instead of returning an override no audit trail can prove.
Deterministic treatment-plan evidence
buildRiskTreatmentPlan() re-derives every computed score, applies the supplied latest override only to effectiveResidual, sorts rows by riskId, and returns canonicalize(toJson(plan)) as canonicalPlan. Identical register state produces identical bytes regardless of input order, filesystem, locale, or clock.
Summary counts cannot be fabricated
riskTreatmentPlanSchema derives totalRisks, overriddenCount, and unmitigatedCount from the actual rows and rejects any mismatched summary. RISK_TREATMENT_PLAN_FORMAT_VERSION pins the artifact contract, while the posture stays readiness-style, recording treatment coverage without claiming compliance.
export function defineRiskEntry(input: RiskEntryInput): RiskEntry {
const parsed = parseStrict(RiskEntryInput, input);
const residual = computeResidual(parsed.likelihood, parsed.impact);
return parseStrict(RiskEntry, { ...parsed, residual });
}- defineRiskEntry parses RiskEntryInput before it derives anything, so callers cannot smuggle a residual or an unknown field into the authored row.
- computeResidual is the sole residual mint in this path; the finished object is parsed through RiskEntry again, including its likelihood-by-impact cross-check.