Skip to content
Hanna.
← All work

Approvals · specification through rollout · 2025–2026

One hard-coded approver did not survive the second customer

The obvious fix was a branch per customer, which compounds forever. Instead the business configures its own ladders, spending authority and price guards, and a new customer costs no code at all.

How it was reported

Approvals are wrong for this customer.

What it actually was

The first customer had one approver, hard-coded. The second had a hierarchy, and the request arrived as a bug about their approvals being wrong.

Branching per customer works today and compounds forever: every fleet adds a branch, none can safely be removed, and nobody can answer "what are this customer's rules" without reading code.

The rule underneath was not about approvals at all. A customer's hierarchy, spending authority and chart of accounts are their operating procedure — they change without asking us, so they belong in configuration, not in a release.

Getting that right meant sitting with operations, purchasing and finance staff to find the real rule before specifying anything.

What shipped

  • An approval engine the business configures itself: sequential and parallel ladders, per-approver spending authority, mandatory price checks, guards above a budget owner's limit, and super-approver override.
  • Bypass states rendered honestly. When a ladder is short-circuited the screen says so, because a trail that hides its own exceptions is worse than no trail.
  • Specification and decision records first, then implementation across shared contracts, the API, the dashboard and the app — one change landing coherently across four surfaces instead of four drifting.

Evidence

customer organisations on their own hierarchies
3
open design questions resolved
50+
to rebuild it
< 3 weeks
escalations handled directly
100+

Named decision owner on the decision records. Every customer since has configured their own hierarchy without a code change.

  • NestJS
  • Next.js
  • React Native
  • MySQL
  • TypeScript
  • shared contracts