Skip to main content

ADR-0172: The SAR assessment needs a distinct assessor, enforced at the write

Date: 2026-08-03 Status: Proposed Deciders: Adrian (project owner), Claude Opus 5, Codex (review, PR #1013) Amends: ADR-0071 (SAR/STR lifecycle & tipping-off) — the four-eyes scope

Context

ADR-0071 gave the SAR a lifecycle and placed four-eyes on filing: the approved transition reuses assert_distinct_approver (ADR-0070), so the MLRO authorising a filing cannot be the officer who raised it. That ADR describes record_assessment as the MLRO's own professional judgment, and deliberately did not gate it.

The assessment has since acquired a second job. Under AMLD Art. 39 the platform blocks REJECT and FOLLOW_UP on a criminal/enforcement predicate until a completed, signed MLRO assessment exists — the customer-contact gate. That gate is the reason sar_assessment_complete exists, and it is what an officer must satisfy before contacting a customer about a case with a filing predicate.

So the assessment is no longer only a judgment. It is the artefact that unlocks a tipping-off control. And the MLRO role inherits SAR_RAISE, which means the same person can raise a SAR and then record the assessment that lifts the block on their own case. Nothing in ADR-0071 prevents that, because when it was written the assessment did not unlock anything.

Two further facts constrain the fix, both found by Codex on PR #1013 against an implementation that enforced distinctness in the predicate rather than at the write:

  1. The assessment endpoint accepted and persisted a self-assessment, and the predicate then refused to honour it. SarAssessmentSection treats a persisted row as complete, hides the form, and reports the gate satisfied. The case was blocked with no dashboard path for a second MLRO to replace the assessment — a control that strands the work it governs.
  2. A terminal SAR with no assessment is stuck the same way: the form renders only for draft and pending_mlro, so once an MLRO approves or rejects without one, the record the gate now demands can never be produced.

A gate that blocks correctly but leaves the officer no route forward is worse than the defect it fixes, because the officer's only remaining option is to work around the system.

Decision

Extend four-eyes from filing to the assessment, and enforce it at the write.

  1. record_assessment requires an assessor distinct from the raiser. The check runs in the service, before persisting, and rejects with the existing SelfApprovalError / AmbiguousApproverError semantics — the same fail-closed pair ADR-0071 already uses on approved, so the two gates behave identically to a caller. An assessment that would not unlock the gate is never written in the first place.

  2. The recorded assessor is the identity that matters, not the lifecycle state. sar_assessment_complete requires a decisive, signed assessment whose recorded assessor differs from the raiser. A SAR reaching approved, rejected, submitted or acknowledged no longer independently unlocks the gate: those are lifecycle facts about a filing, and the gate is about whether a second qualified person examined the predicate. Lifecycle state remains corroboration, never the sole evidence.

  3. An assessment stays replaceable while it does not satisfy the gate. The form must render whenever no gate-satisfying assessment exists — including on a terminal SAR, and including when a non-qualifying assessment was recorded before this ADR. Recovery is part of the control, not an afterthought: the system must always be able to reach a compliant state through its own UI.

  4. Enforcement point is the decision. Validating in the predicate and not at the write is what produced the stranding, so "where" is recorded here as normative rather than left to the implementation.

Consequences

Positive

  • The person who raises a SAR can no longer lift the tipping-off block on their own case. That was reachable in the shipped code, by an MLRO, with no error.
  • The two four-eyes gates (filing, assessment) now use one mechanism and one error vocabulary, so a caller cannot learn two behaviours.
  • Rejecting before the write means the persisted state is always one the UI can interpret correctly — no row that is simultaneously "complete" and "does not count".

Negative

  • A single-MLRO tenant cannot satisfy this gate at all. That is the intended meaning of four-eyes and matches ADR-0070's existing posture on filing, but it is a real operational constraint and small deployments will meet it. There is no override, deliberately: an override on a tipping-off control would be the control.
  • Existing SARs whose assessment was recorded by the raiser stop satisfying the gate on the next read. They are recoverable through the re-opened form (decision 3), but they will surface as newly-blocked cases, and that surfacing is correct rather than a regression.
  • One more state the UI must represent: "assessment present but not gate-satisfying". Rendering it as simply absent would repeat the stranding in a new place.

Neutral

  • No schema change: the assessor identity is already recorded on the assessment.
  • ADR-0071 stays Accepted. Its filing gate is unchanged and correct; this ADR extends the scope its own tipping-off section created.

Alternatives Considered

Alternative 1: Leave the assessment ungated (status quo ante)

Keep four-eyes on filing only, as ADR-0071 wrote it.

Why rejected: it was defensible when the assessment decided nothing. It stopped being defensible when the assessment became the key to the AMLD Art. 39 gate — a single MLRO can then raise, assess, and contact the customer, which is the sequence the control exists to prevent.

Alternative 2: Enforce distinctness in the predicate only

Persist whatever is submitted; decide at read time whether it unlocks.

Why rejected: measured on PR #1013 — it strands the case. The row reads as complete to every consumer, the form disappears, and no second MLRO can act. It also splits one rule across a writer that permits and a reader that forbids, which is how the two disagree later.

Alternative 3: Enforce it, with an audited officer override

Mirror the dissolved-entity / insufficient-data overrides (ADR-0065, ADR-0125).

Why rejected: those overrides let a human accept a risk the system has surfaced. This gate exists because a specific person must not be the only one to examine a filing predicate — an override would let exactly that person waive it, and an override on a tipping-off boundary reintroduces the tipping-off risk in full. AMLD Art. 39 admits no such discretion.

Decision context:

  • Latency: none measurable — an identity comparison on an already-loaded row.
  • Dependency surface: no new packages; reuses assert_distinct_approver.
  • Debuggability: failure is an explicit SelfApprovalError at the write with the two actor ids in the audit record, rather than a case that silently will not progress.
  • Reversibility: hours. One service-level guard plus a UI render condition; no migration, no persisted format change.
  • Blast radius: record_assessment, sar_assessment_complete, and the SAR panel render conditions. Existing non-qualifying assessments change meaning on read.
  • Alternative considered: predicate-only enforcement — rejected, it strands the case (measured).