Skip to content

ADR-059: Maker-checker as a platform-wide workflow primitive

Status Accepted
Date 2026-05-09
Deciders CTO, Head of Architecture, Head of Technology Risk, Chief Compliance Officer
Affects repos bank-platform, bank-core, bank-credit, bank-kyc, bank-payments, bank-app, bank-wiki

Status: Accepted — 2026-05-09

Context

Four-eyes authorisation (maker-checker) is a foundational operational control required by RBNZ DTA governance standards, APRA CPS 220/520, and the bank's own GOV-003 policy. It ensures that no single operator can unilaterally make a consequential state change — a product rate, a GL account definition, a credit limit override, a sanction hold, a fee waiver, or a configuration parameter — without a second authorised reviewer approving it.

The wiki currently satisfies this obligation module-by-module. MOD-127 (product configuration panel) has its own app.product_config_proposals table, proposal API, and approval workflow. MOD-140 (chart of accounts) has its own core.gl_account_proposals table and a parallel workflow. Both were built correctly for their scope, but as the module count grows, every new back-office module with write operations will independently implement the same pattern. That path creates fragmented audit surfaces, inconsistent approval semantics, and repeated engineering effort.

A comparative study of Apache Fineract (see fineract-design-reference.md) confirmed that maker-checker as a built-in first-class primitive — rather than a per-module pattern — is the correct design for a bank platform. Fineract's maker-checker applies to all commands at the API layer and is configurable per command type. The wiki's module-as-service shape requires a slightly different approach: a dedicated enforcement service that any module can delegate to.

Two architectural options were evaluated:

(a) Per-module enforcement — continue the current pattern. Each module owns its proposal table and workflow.

(b) Central enforcement service — one module (MOD-168) owns the proposal store, approval workflow, and audit surface for all four-eyes decisions across the platform.

Decision

1. MOD-168 is the platform-wide maker-checker enforcement engine

MOD-168 will be built as a shared service within SD07 (Data Platform / bank-platform repo). All back-office modules that need four-eyes enforcement on state-changing commands will delegate to MOD-168 rather than implementing their own proposal tables.

MOD-168 provides: - A proposal API: POST /checker/proposals — submit a pending command - An approval API: POST /checker/proposals/{id}/approve — second-party approval - A rejection API: POST /checker/proposals/{id}/reject — second-party rejection - A query API: GET /checker/proposals — list open proposals (paginated, filtered by domain and status) - DB-level enforcement: proposed_by ≠ reviewed_by constraint; no self-approval at any tier - Integration with MOD-047 (agent action logger) and MOD-048 (system decision log) — every approval and rejection is an immutable audit record

2. Two-tier classification

Commands are classified at the module level when they register with MOD-168:

Tier Risk level Examples Requirement
TIER-1 Low risk, easily reversible Read-only operations, draft-only changes No maker-checker required
TIER-2 Medium risk, reversible Product configuration changes, fee schedule updates, interest rate changes Maker-checker via MOD-168
TIER-3 High risk, irreversible or regulatory impact Sanctions holds, account closures, GL account creation, credit limit overrides, customer risk rating changes Maker-checker via MOD-168 + time-locked review window (minimum 15 minutes between proposal and approval)

The classification of each command is declared by the owning module in its registration call to MOD-168. The compliance team reviews and approves the classification register.

3. Policy machinery gains a CHECKER satisfaction mode

The wiki's policy machinery (schema.py SatisfactionMode) gains a new mode: CHECKER. A module that satisfies a policy via maker-checker enforcement declares mode: CHECKER in its policies_satisfied array. This makes four-eyes obligations traceable in the same policy register as GATE, AUTO, CALC, ALERT, and LOG satisfactions.

4. Existing per-module implementations are valid v1; MOD-168 is the v2 pattern

MOD-127 and MOD-140 implemented maker-checker correctly for their scope. These implementations are not migrated away from in v1. When each module undergoes a v2 revision, the proposal table and workflow logic is extracted to MOD-168 and the module becomes a consumer of the checker API. New modules built after ADR-059 must use MOD-168 from day one and must not build their own proposal tables.

5. The ban on self-approval is absolute

No configuration, environment variable, admin override, or role assignment may permit a user to approve their own proposal. This constraint is enforced at the database layer in MOD-168 with a CHECK (proposed_by != reviewed_by) constraint and validated at the API layer. CI token scans in any module that calls MOD-168 must confirm no bypass path exists.

Consequences

  • MOD-168 is added to the delivery plan as a high-priority SD07 module.
  • schema.py is amended to add CHECKER to SatisfactionMode.
  • New back-office modules (MOD-169 and later) use MOD-168 from initial build.
  • MOD-127 and MOD-140 are v2-flagged for checker API migration when capacity allows.
  • The compliance team gains a single audit dashboard for all platform-wide four-eyes decisions, replacing per-module reporting.
  • GOV-003 policy satisfaction evidence is centralised rather than module-scattered.

All ADRs Compiled 2026-05-22 from source/entities/adrs/ADR-059.yaml