Project Management System
Sole engineer on an internal platform that runs an eleven-state project lifecycle and has to be right about money. Built in 2025; the attendance and leave module followed in 2026.
- ARCHITECTURE
- Granular designation-scoped permission engine · Query-layer hierarchical authorization
- CONSTRAINT
- Zero cross-role leakage of financial data across 11 lifecycle states under Indian GST statutory rules
- TRADEOFF
- Persisting immutable tax snapshots at issue time over dynamic runtime derivation to prevent historical financial drift
- STACK & SCOPE
- OpenAPI11-state project lifecycle governing visibility across 12 roles via 31 data-driven permission flags
The problem
Every project moved through eleven states, from unassigned to payment received. Each state changed who was allowed to see what — and those rules lived in people’s heads, not anywhere in the system.
That is survivable with five users. It is not survivable with twelve roles across a real client base, where the wrong person seeing a billing figure is not a bug report, it is a phone call.
The decisions
I built the permission engine before any featureREJECTEDWriting role checks inside each route as I wentWHYIt never survives the tenth route. The rules drift apart one endpoint at a time, and nobody notices until somebody sees a number they shouldn't have. — thirty-one granular flags scoped by designation rather than by role name, so adding a role is data, not a deployment.
Invoicing had to resolve IGST against CGST/SGST from GSTIN state codes, under per-series, per-financial-year numbering. I made the tax split immutable at issue timeREJECTEDComputing the tax split when an invoice is renderedWHYAn invoice is a legal record. It has to still be true a year later, not recomputed against whatever the rules are on the day someone opens the PDF., stored alongside the invoice rather than derived from it.
The attendance and leave module followed in 2026, once the core platform had been running for a year. It enforces manager hierarchy at the query layerREJECTEDFiltering results in the application layer after fetching themWHYIf authorisation happens after the query, then every new query is a new chance to forget it. Pushing it into the query makes the safe path the only path..
The outcome
The core platform shipped in 2025, behind an integration test suite and OpenAPI documentation that the frontend is generated against. The attendance and leave module isn’t a footnote on the same release — it came a year later, once the platform had been in real use long enough to know what it actually needed.
I was the only engineer on it. Every one of those decisions was mine to get wrong.