Back to catalogue

Four-eyes sign-off

A maker requests, a different checker approves — separation of duties enforced in the API guard, a DB CHECK constraint, and an immutable append-only audit trail.

Request to immutable sign-off — end to end

Mail = email sent (fail-soft) Bell = in-app alert (fail-soft) Amber diamond = automatic check Amber card = error / trap path
SUBMIT — requestChange · audit · fan-out Maker submits request change request · payload snapshot Has requester role? MAKER gate Request pending requestChange · status = pending Write audit row (create) approval_request.create · fail-closed Fan-out to all approvers all approver-role members notified Checker opens request review queue · payload + rationale field Has approve role? CHECKER gate Still pending? pending | under_review Approver ≠ requester? decided_by <> requested_by Checker decides + rationale approve or reject · reason required Signed off requester notified · immutable audit pass pass yes different person 403 Forbidden role not in requesterRoles fail 403 Forbidden role not in approveRoles fail 422 invalid_status already decided — no re-decide no GovernanceError 403 four_eyes · self-approval blocked DEAD END same person Rejected + reason own audit row · requester notified reject

The four-eyes rule fires TWICE — the API guard in decide() and a DB CHECK (decided_by <> requested_by) — so a code-path bypass still hits the wall. Every mutation writes its own immutable audit row; the table is UPDATE/DELETE-proof at the DB level for every role.

Why this is auditor-grade

  • The audit trail is append-only — UPDATE/DELETE on audit_log is blocked by DB triggers for everyone, including service_role.
  • A decision carries its rationale and a before/after snapshot — the row is the evidence.
  • Notifications are fail-soft: a delivery miss never blocks or reverses the recorded decision.

Live demo

app/approvals
Four-eyes approval — maker submits, a different checker signs off — screen recording
Recorded from the running app. Maker submits → a different checker reviews and signs off → decision + rationale land in the append-only audit log.