Skip to main content

ADR-0067: Fail-Closed Compliance Outputs & the "Not Assessed" Contract

Status: Accepted Date: 2026-06-26 Deciders: Adrian (Soft4U) + Claude Opus 4.8 Milestone: M1 — Data Honesty & Fail-Closed (see docs/plans/2026-06-26-world-class-product-roadmap.md)

Decision-context

A 2026-06-26 officer review + regression audit (docs/research/2026-06-26-regression-audit-findings.md) found multiple paths that report a benign result when no check actually ran — a live /scan wired with an empty sanctions list returning "clear"; person_screening unconditionally returning sanctions_clear=True; screening exceptions swallowed into silence; CDD coverage computed from node presence. For an MLRO personally liable for the decision, a false "clear" is worse than no answer: it manufactures unwarranted comfort that survives into the audit file.

Decision

Every compliance-facing output (screening, coverage, score, monitoring check) MUST be fail-closed:

  1. No benign-by-default. A check that cannot run (unconfigured source, empty list, exception, missing tenant context, absent data) MUST NOT return clear / covered / low / no change. It MUST return an explicit indeterminate / gap / not_screened / status="error" signal.
  2. Indeterminate reads as scrutiny, never comfort. The indeterminate state surfaces as a HIGH / requires_review data-gap finding (or an equivalent non-benign status), is recorded for audit (EU AI Act Art. 12), and never silently advances a case toward approval.
  3. One canonical gap shape. Reuse the existing sanctions_screening_gap finding shape and the check_ubo_screening indeterminate pattern; do not invent per-call-site variants. New determinism categories are registered in app/models/finding_determinism.py.
  4. Presence ≠ evidence (companion rule, docs/calibration-review-checklist.md #1): a coverage/ "verified" metric asserts substance, not that a flag/node/field exists.

Consequences

  • Positive: the officer can trust that "clear/covered/low" means checked-and-clean, not never-checked. Honest gaps are visible and triageable. Directly satisfies the calibration checklist's "no silent suppression" + "honest empty-states".
  • Negative / accepted: more HIGH data-gap findings surface (some cases that previously read "clean" now show "not assessed"). This is intended — the noise is honest signal the officer must see; it is mitigated by clear gap categorisation, not by hiding it.

Implementation (M1)

  • W1 (this ADR's first delivery): H3 /scan empty-list, M2 person_screening, M7/M18 swallowed screening exceptions. Subsequent waves extend the contract to coverage honesty (H1/H2) and honest scores/defaults (M14/M6/H6).