Engineering governance · Head of Development · Feb 2026
One approval module, two designs: a rebuild and a standard
A pair spent two weeks on an approval design that forced one account per role — caught in review before launch, rebuilt in a week with a pattern three other projects already used. The team never knew it existed, and that gap became a 40-standard analyst handbook.
Executive Summary
Three months before launch, an analyst–developer pair delivered an approval module for the learning-management SaaS — two weeks of work on a design that made every approver carry a second account, where a headmaster-teacher had to log out and back in to approve their own submission. The review caught it before launch. A week rebuilt the module on the pattern three or four other projects already ran; the pair had simply never worked on any of them. That gap — not the design — became the standard: a numbered analyst handbook, 40 entries and counting.
The Problem & Context
- The project: a school/learning-management SaaS three months from launch, finalizing the approval module among the last few before release.
- The team: one analyst and one backend developer — both with solid history on other modules of the same SaaS, and no exposure to how approval problems had already been solved elsewhere.
- The process: I run several projects at once and lean on the review process of the analyst assigned to each. Two weeks in, the pair reported the module done in alpha and testing. I reviewed it and found a design that worked only on paper.
Two Designs
My idea of the module was configuration, not workflow:
- One
approval_typestable — transaction approvals, report approvals, month-closing approvals. A new kind of approval is a row, not a code path. - A many-to-many to users, split by intent: who can submit each type, who can approve it.
- The approval list then shows each user exactly what they may act on: pick from your queue, approve. Done.
The pair’s design put the choice at creation time: whoever submits picks the person who must approve it. Not entirely wrong — but every submission now carried permission logic, and it leaned on a hard platform constraint: at the time, one user had one role.
That constraint produced the nightmare:
sequenceDiagram
participant H as Headmaster (teacher account)
participant S as LMS
H->>S: Submits an approval
Note over H,S: In this design a submitter cannot approve, and an approver cannot submit
H->>S: Logs out
H->>S: Logs back in (headmaster account)
S-->>H: The same submission waits in this account's queue
H->>S: Approves the request they filed themselves
Note over H: One person, two accounts, because role and account were the same thing
The origin was real: one school required that a user cannot approve their own submission — a reasonable maker–checker rule. But the pair implemented it as a law of the product, and let it reshape the whole workflow around it.
The Rewrite
I stopped the build and pivoted a second analyst — someone who had worked on the other projects — to transfer knowledge, not code: the approach, a working example, and the traps. The original pair implemented the rewrite themselves.
One week. Same pair. The schema settled into this shape:
erDiagram
USERS }o--o{ ROLES : "holds"
USERS ||--o{ USER_APPROVAL_ACCESS : "is granted"
APPROVAL_TYPES ||--o{ USER_APPROVAL_ACCESS : "grants on"
USERS ||--o{ APPROVALS : "submits"
APPROVAL_TYPES ||--o{ APPROVALS : "classifies"
APPROVAL_TYPES {
int id
string name
}
USER_APPROVAL_ACCESS {
int user_id
int approval_type_id
bool can_submit
bool can_approve
}
APPROVALS {
int id
int approval_type_id
int submitted_by
int approved_by
string state
}
The school’s rule survived — as a filter, not a workflow:
Simplified, sanitized mock of the approval-list scope. Laravel, not production code.
// The approval list: the types this user may approve, minus their own// submissions. The maker-checker rule stays a filter on the list, so// one account can submit and approve across roles - no logout dance.public function scopeApprovableBy(Builder $query, User $user): Builder{ return $query ->whereHas('type.accessUsers', fn (Builder $q) => $q ->where('user_id', $user->id) ->where('can_approve', true)) ->where('submitted_by', '!=', $user->id);}The Standard
I run multiple projects at the same time, so I lean on the review process of the analyst assigned to each. That trust is what failed here — not the pair’s effort and not their skill. A review system has to stand on several fronts and several people before one analyst’s judgment can carry it alone. So the lead analyst and I wrote the missing piece down.
The result is a numbered analyst handbook — 40 standards, grouped in three sections, last updated July 2026, with an e-commerce annex so far limited to cart and checkout:
| Section | Range | Count |
|---|---|---|
| Features — auth, access rights, master data and CRUD, transactions, uploads, exports and reports, notifications, audit trail | STD-BAS-001 – 024 | 24 |
| Design and flow — lists and tables, forms and inputs, messages and feedback, terminology and format | STD-BAS-025 – 037 | 13 |
| Documentation — requirement writing | STD-BAS-038 – 040 | 3 |
Three rules hold it together: analysts document standardized modules from every project in one source; a new problem goes through a peer consult first — analyst to analyst, developer to developer — to check whether it has already been solved; and documents pass analyst-lead plus peer review before any handoff to development.
The handbook was overdue in a second way. Months later, the recurring gaps on other projects were exactly the class of thing it is written to catch before handoff: a login form with no forget-password flow; a manual-transfer flow with no admin step to accept and verify the payment; a history page that broke when its item had since been archived.
Results & Impact
The rewrite shipped, and so did the suite around it — accounting and the rest of the modules launched on schedule. The approval module has been in production since.
The single-role constraint is gone: one user, many roles. I asked for it, the pair built it, and the confirmation came through the project lead after a May 2026 reorganization — delegation working as intended, and a reminder to see the important things with my own eyes when I can.
The handbook is now a maintained artefact: 40 numbered standards, updated July 2026, with the e-commerce annex for cart and checkout.
What I’d do differently
- Review at the first hour, not at “done”. Two weeks of solid work went into a design that should not have survived day one of review. The handbook’s review layer now makes a design checkpoint explicit before development starts.
- Client-specific rules live in data. One school’s maker–checker rule became everyone’s workflow. The rewritten version keeps the same rule as a filter on the list. Any rule that can differ per client is a setting, never a code path.
- Knowledge travels with exposure, not reminders. The pair didn’t lack skill — they lacked exposure. Crossing people between projects earlier is the real fix; the handbook is the safety net for the times they haven’t crossed yet.
- Trusting a process is not the same as checking it. Review now comes from several people — analyst lead and peer — before a design reaches development.