The client is a Fortune Global 500 automotive manufacturer, one of the world's largest companies by revenue. I cannot name them, so I will not, and everything below stands on its own without it.
An organisation that size does not actually know what its people can do. The platform answers that. An employee rates themselves against the skills their job requires, a peer co-assessor rates them independently, and a manager reconciles the two into an agreed value. Administrators run this in waves across defined slices of the workforce, and HR reads the result as a picture of where the capability gaps really are. Roughly 25,000 employees, 14 languages. It replaced spreadsheets and email.
It is a TypeScript monorepo: a NestJS API on Fastify over Postgres and Prisma, a Next.js front end, and shared packages carrying the API client, the DTOs, the validation schemas and the authorization rules, so a contract change is a compile error in both apps rather than a surprise in production. SAML single sign-on against the client's own identity provider, a nightly feed from their HR warehouse, and a sync with their learning system.
I am one of a small team and I did not build it alone. Before the company hired a CTO I did a lot of the up-front planning for this one, and much of it carried through into what got built. I had not planned an application to that standard before.
What I did
Live production support, which is about half of it. Real users of a live enterprise deployment, and the work spans the whole stack: assessments failing to save, draft state resetting, filters returning the wrong people, permissions behaving in ways nobody expected, terminated employees appearing where they should not. It is unglamorous and it is the work that decides whether a company that size keeps using what you built.
Access control and organisational scoping. The visibility system, roles and permissions, HR versus admin scoping, and making the perimeter agree with campaign targeting.
Campaigns, dashboards and reporting. Targeting and exclusions, the draft lifecycle, progress tracking, and currently a performance push on the campaign and dashboard pages.
Data model and integrations. I owned the sync review and approval work end to end: the data model, gating imports, the API, the admin surface, the assessment-impact count, and the rollout behind a flag. Plus skill-catalogue restructuring and job mapping.
What makes it hard
One perimeter decides who you can see, and it has to be resolved in exactly one place. It is multi-dimensional: regions, countries, org-unit subtrees, job families, and explicit include and exclude lists on top. A second copy of that matcher once existed, drifted from the first, and produced two different answers for anyone under a deactivated sub-unit. The divergence did not fail closed, which is the part that matters. A perimeter built only of exclusions also has to resolve to nobody rather than to everyone-except, because reading it the other way silently invents access no administrator granted.
The nightly HR feed is not an upsert. Three governed fields divert into pending changes instead of being written, and that state machine has seven distinct outcomes, including "the feed reverted while a change was open, so void it" and "an administrator rejected this value, so it is now a pin and must be suppressed on every future sync." A change to someone's standard job carries a count of the in-flight assessments approving it would invalidate. Get one branch wrong and you corrupt employee records quietly, overnight, at scale.
The assessment lifecycle has more invariants than it looks like it has. It is a state machine with per-transition validators, and around it sit reopen requests, disputes, skill removals, delegation, sign-offs, frozen completed assessments and an archive for people who have left. Agreed ratings are protected by a database-level restrict, so an innocent-looking edit to a reopened assessment can hit a foreign key violation. Some of the rules are pure domain knowledge rather than anything you would guess: certain jurisdictions and worker classes may not be assessed but may still act as co-assessors, and that has to be enforced at every eligibility check independently.
Performance intuition was wrong in a specific way. Raising the database connection pool changed nothing, because the cost was never database contention. It was the ORM materialising nested relations and the browser parsing a payload of about 30 MB. The team's performance work is grounded in a findings document that builds a production-scale database, records the before numbers, and marks two of its own author's hypotheses as refuted. That document is the most useful artefact on the project.
What I took from it
- Supporting something in production teaches you things building it cannot. Half my work here is other people's bug reports, and it is where I learned what actually breaks
- If a rule decides who can see what, it gets exactly one implementation. A second copy is not duplication, it is a second answer
- Anything ambiguous should fail closed. The visibility bug was serious because the two answers disagreed silently rather than erroring
- Measure before optimising, and write down the hypotheses you disproved. The refuted ones are worth more than the confirmed ones, because they are what stops the next person spending a week where you already looked
- Enterprise domain rules are rarely derivable. You have to ask, and then encode the answer where it cannot be forgotten