Adia
Back to work

Enterprise MRO Workflow Platform

From a maintenance request to a signed, regulator-recognized release certificate — held in software, not memory.

My role
Lead backend engineer · primary author of the core platform (~2 yrs)
Domain
Aerospace MRO — AS9110-regulated maintenance, repair & overhaul
Scope
Modular monolith · ~10 React/TS apps · approvals, signatures, scheduling
Stack
.NET · CQRS/MediatR · EF Core + Dapper · WorkflowCore · SQL Server · SignalR

A regulated aerospace MRO platform — a .NET modular monolith behind a suite of ~10 React apps — that carries a component from intake through repair to a signed release, with every approval and document state explicit and auditable. I was lead backend engineer and primary author of the core the whole suite runs on.

The job-order queue as it arrives from SAP each morning — every row showing which of nine processes it has reached, and six that carry no route at all
The job-order queue as it arrives from SAP each morning — every row showing which of nine processes it has reached, and six that carry no route at all

It exists to kill double entry.

A technician used to record the same tool twice — once against the job, once in the equipment log — and the two copies drifted, which stops being clerical the moment a regulated recall has to prove which tool touched which component. Tagging a tool to a job now generates the log entry from it, so there is one record instead of two that can disagree.

One deployable core, eight engineers.

A modular monolith over microservices — MediatR, EF Core + Dapper — for the transactional integrity and end-to-end traceability a regulated shop needs, without the operational weight a lean team would have to run in production. The module boundaries are strict and modules talk through integration events instead of each other's entities, so eight engineers could ship ten apps against one core without colliding inside it.

One deployable core — and the only legal way between two modules

one module — the request pathevery other moduleControllers850 endpointsMediatR handlers766 across 29 modulesApplicationvalidation · DTOsDomainentities · invariantsInfrastructureEF Core + DapperSQL Serverintegration events only
integration eventshared entities — nevermodule boundary

The signature is a card, not a password.

Certification decides who may perform work and who may sign it off, and the stamp recording it is shape-coded to the level of authority behind it — scoped to an ATA chapter, so authority covers one aircraft system rather than the whole aircraft. Signing means putting your eID card in the reader: the key lives on the card, not in our database, so a release is bound to someone who was physically there and can't be lent out like a login. An unqualified sign-off isn't caught later in review — the state machine will not let you submit it.

Why an unqualified sign-off can't be submitted

authority data — owned by other modulescertificationsscope ratingspart masterintegration eventssubmitcertification guardacceptedsignature bound to the cardrefusedat submit, not in review
the guardaccepted pathrefusedmodule boundary

A permission code is never renamed.

Only deprecated. Renaming one silently changes what a role means for everyone already holding it, and no migration catches that — so new capability gets a new code and the old one stays until nothing references it. A cross-application activity log records who changed what, where, and when, which is the difference between believing the access rules held and being able to show it.

A blocked job names what blocked it.

Three different things stop a process, and the schedule writes each one into the block it stopped: a technician already committed elsewhere, a plating tank that is out of service, a slot that lands past the customer's due date. Shop capacity sits above the jobs as its own row, so an over-subscribed Wednesday is visible before anyone opens a single order — and the same week re-pivots by technician or by machine when the question changes. Nobody has to go and find out why a job didn't move; the reason is already written on the thing that failed.

mro-schedule.png
A week on the process schedule: shop capacity per day above, job orders below, and the two blocked processes each naming what blocked them
  1. 01Capacity is stated above the jobs, not inside them. Committed against available, per day, before a single order is open — and Wednesday already reads 74 against 72. The shop being over-subscribed is a property of the week, so it is reported at the week's level rather than discovered one job at a time.
  2. 02Two different refusals, both written where the work sits. A technician already committed elsewhere reads "D. Kusuma double-booked"; a plating tank out of service reads "Tank 71 unavailable". Neither is a colour to decode or a detail filed one page away — the cause travels with the process it stopped.
  3. 03A late finish is measured against the row's own promise. The job order carries EDD 19 Jun in amber, and the process that overruns it says "After EDD 19 Jun" in place. The date being missed and the thing missing it are on screen together, which is the difference between visible and actionable.

A SAP sync that fails safely.

Bidirectional over scheduled jobs and SFTP, timezone-safe, batch-recovering — because in a single source of truth, data that silently drifts is far worse than a job that stops, flags itself, and retries.

The same timeout, committed as you go versus committed on a fence

timeoutcommit as you go — the version that drifts12345batch 3 left half-applied · nothing flagged · the totals just stop matchingcommit on the batch fence — what actually shipped12345next run resumes at the fencestops and flags itself · a job that stops is visible; drift is not
batch fence & resumecommittednever run

The certificate is generated, not typed.

The release document renders from live process state and QR-stamps straight off the record: fields populate from the job, the signatory and stamp number come from the authenticated session rather than a text box, and the date is locked against back-dating. The window shuts for good once the job closes — so what the regulator reads and what the system holds can't drift apart.

Every document is somebody's turn.

A technical document in review is never merely open: every row names the person whose turn it is — the librarian who hasn't assigned it yet, the author who has to revise and resubmit, the head of engineering who has to sign. Age rides on the row and turns red past the five-day target, and the queue lists its breaches by document number, so the improving median sitting beside them cannot quietly absorb the two that are late. Revisions never overwrite either: Rev 2 of a manual can sit in evaluation while Rev 6 of the same manual sits rejected, because what the approved procedure said in May is a question an auditor is entitled to ask.

mro-tdr.png
The technical-document review queue: nine revisions across five stages, each row naming the person it is waiting on and how long it has waited
  1. 01The queue reports its own health, and names the breaches. Three documents unassigned with the oldest at six days, six in evaluation, and two past the five-day SLA listed as 2026/0001 and 2025/0183. The median sitting beside them is improving; naming the two late ones is what stops that median quietly absorbing them.
  2. 02Every row names whose turn it is, and how long it has been. Librarian to assign, author to revise and resubmit, head of engineering to sign. A stage on its own would say a document is open without saying who owes it — and the age rides on the same row, turning red past five days.
  3. 03One manual, two revisions, neither overwritten. CMM 32-11-04 appears at Rev 2 in evaluation and again at Rev 6 rejected. Superseding a procedure adds a row rather than editing one, so what the approved text said in May is still there to be read.

In aerospace maintenance, a wrong signature isn't a bug — it's a failed audit or a grounded plane. So on this platform, correctness is something the software enforces, not something people are trusted to remember.

[ Stack ]
.NET / C#CQRS · MediatRWorkflowCoreEF Core + DapperSQL ServerSignalReID smart-card signingSAP integrationReact / TypeScript