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.pyis amended to addCHECKERtoSatisfactionMode.- 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