Skip to Main Content
AVIATION TECHNOLOGY·10 MIN READ·AUG 19, 2026

Nobody Asks to Build the Waiting Room

Nineteen tables that do nothing an aviation authority is famous for, and without which none of it can happen.

This follows on from Who Are You, and How Do We Know?, which found the tables nobody asks for by asking who is submitting the application.

Here is a sentence nobody has ever said in a requirements workshop: "before anything else, we need a table for people who have started registering but have not confirmed their email yet."

And yet that table is the first thing I built. It has eleven columns, it holds rows for about two days each, and most of those rows are deleted without ever becoming anything. It is the least glamorous object in the system.

It is also the difference between a registration screen and a registration system, and this article is about why.

The problem with putting people straight into the people table

Somebody types an email address, a first name and a last name, and presses Submit. The obvious thing to do is create the person.

Consider what you have just created. A person record for an address that may not exist. A person record for an address belonging to somebody else, typed by mistake or on purpose. A person record for a bot that filled the form four hundred times last night.

Now every count of registered people includes all of that. And when you write the job that cleans it up — because you will — it is deleting rows from the table that every certificate, every application and every licence will eventually point at.

Unverified data does not belong in the table that everything else depends on.

So there is a waiting room. Email, first name, last name, a token, and an expiry. Nothing more. When the link is followed, the person is created properly and the waiting room row is marked as done. When the link is never followed, a job deletes it, and it deletes from a table nothing else references.

One small decision inside that: the used rows are kept rather than deleted, with the new person's id written on them. It costs almost nothing and it answers a question somebody always asks eventually — how did this person first arrive, and when?

Figure 1 — The waiting room and the person table

The waiting room email, name, token, expiry link followed The person everything else points here not followed Deleted by a job nothing references it Put the unverified rows in the person table instead, and that same job is now deleting from under certificates, applications and licences.
The waiting room exists so that the cleanup job never runs against the table everything else depends on.

Then somebody asked how we know it is really them

An email that works proves the address exists. It proves nothing about the person.

For a Saudi address, there is somewhere to check: Absher confirms a national number and a date of birth belong together. When that comes back clean, the account activates by itself. Nobody waits, no officer is involved.

For an address outside the Kingdom, there is nowhere to check, because there is no national register that will answer. So it goes to an officer.

The detail that surprised me is that the branch is on the address, not the nationality. A Saudi national living in London and a Filipino engineer resident in Jeddah take different routes — and that is correct, because the question is not who they are but which register can confirm them.

A rule that emerged from the conversation. The Authority records only what the Authority decides.

A company has ceased trading — that is the Ministry of Commerce's fact. A person has died — that is the national register's. A commercial registration is valid — again, not ours. Each is received, stored with the date it was given, and never becomes a status an officer picks from a dropdown.

Two statuses were removed from the design because of it.

An answer is true on a date, not forever

This one changed the shape of the system.

The obvious design for a verification is a flag: is_verified, set to Y when the Ministry says yes. Simple, and every query gets easier.

It is also unable to answer the only question that will ever matter about it.

Three years from now, a certificate issued to that company is challenged. Somebody asks when the commercial registration was checked, and what exactly came back. A flag says yes. It cannot say when, it cannot say what the Ministry actually returned, and it certainly cannot say whether anything has changed since.

So instead there is one table holding every call to every external authority: what was asked, who answered, what they said, and when. Absher, the Ministry of Commerce, the Ministry of Justice — all three, because all three behave identically. The raw response is kept too.

A verification with no timestamp is a rumour.

That table then turned out to solve a problem I had not connected to it. A power of attorney is checked when a delegate registers a company — but it can be revoked the following week, and the delegate would still be filing applications. So it is checked every time that person requests something. Each check is a row. The history is free.

The one where the endpoints do not exist yet

None of the three integrations are live. There is no Absher endpoint configured, no Ministry of Commerce contract agreed, nothing to call.

The tempting thing is a stub that returns success so development can continue.

What a friendly stub actually does. It lets a company register against a commercial registration that nobody checked. Then the test data looks fine, the screens look finished, the demo goes well — and nothing downstream can tell a real verification from a fake one, because they wrote identical rows.

What these do instead. Record the call, then raise. The system says plainly that identity verification is not available because the endpoint is not configured.

It is more annoying to develop against. It is also impossible to mistake for working.

Two things that look the same and are not

Twice in this phase, two ideas that seemed like one turned out to be two. Both times the distinction only appeared when somebody described a real situation.

An email address is not a username

The person table already has an email. The account needs an email to sign in with. Same value — so one column, surely.

Except a contact email can legitimately be shared. A husband and wife who run a small maintenance company and use one address. A father registering on behalf of his son. That is ordinary and the system should allow it.

A login identifier can never be shared. If two accounts sign in with the same address, the system cannot tell who is who.

Same value, opposite rules. They cannot be the same column, and finding that out after go-live means a migration.

Dormant is not suspended

An account with no activity for a long time gets deactivated. An account belonging to somebody under enforcement gets suspended. Both mean "cannot use the system", so one status looked sufficient.

Then the exit routes turned out to be completely different. A dormant account reactivates when the holder signs in — nobody did anything wrong. A suspended account needs an officer to lift it, because an officer imposed it.

One status could not carry both, and merging them would have meant a suspended operator reactivating themselves by logging in.

Figure 2 — Dormant and suspended end differently

Dormant unused, nothing attached signs in Active no officer involved Suspended enforcement action officer lifts it Active a decision, recorded Same destination. Different door, different key, and only one of them needs a person to open it.
The statuses look interchangeable until you ask how each one ends.

Where the rules live

A status column that anything can update is a status column nobody can explain six months later. Somebody moves a record from Rejected to Active, nobody knows who or why, and the rule about which moves are legal exists only in whichever procedure was written most recently.

So the permitted moves are a table. Thirteen rows. Each says which status you can move from, which you can move to, and who is allowed to do it — the system, an officer, or the holder themselves.

Reading that table produced the most useful sentence of the phase:

StatusWho sets itWhen
Draftthe systemRegistration starts
Pending Verificationthe systemProfile submitted
Activethe system, or an officerIdentity confirmed, or approved
Rejectedan officerRefused, with a reason
Suspendedan officerBarred, with a reason
Dormanta scheduled jobUnused, nothing attached

Only two of six are somebody choosing from a dropdown. Three are automatic and one is a batch job — which means most of the screens I would have built for "managing status" should not exist at all.

Some tables exist to make people findable

One small thing worth mentioning because it cost nothing and will save a great deal.

A company names a Chief Pilot at registration. That person may have no account yet — they may be at another operator, or between jobs, or simply not have registered.

The easy design is to store the name as text. The problem arrives later: when the company applies for its Air Operator Certificate, the Authority has to check that the Chief Pilot holds a valid licence. Against a name in a text field, it cannot. Somebody reads a CV instead.

So the manager record has a nullable link to a person, plus their national identity number. Today the link is empty and the name is just text. The day that person registers, the number matches and the link is made — and from then on the system can see their licences.

The column costs nothing now and is the difference between a check and a conversation later.

What this phase is worth

Nineteen tables. Four packages. Not one certificate, not one licence, not one application for a service. Nothing a civil aviation authority is known for.

What it does provide is the answer to a question everything else assumes: who is asking, and how do we know? Every service in every later phase begins there, and none of them can begin anywhere else.

The parts I would not have predicted at the start:

  • The most important table holds rows for two days and mostly deletes them.
  • Half the design is about what the Authority is not allowed to decide.
  • A boolean flag was the wrong shape for the most consequential fact in the system.
  • The best thing we built is a stub that refuses to work.

Still open. Five things this phase cannot settle by itself: how long "no activity" means, the lockout policy, the three endpoint contracts, the password rules, and which officers may approve what. All five need somebody with the authority to decide, and none of them is a technical question.

Rate this article

Discussion

No comments yet. Be the first.

Join the discussion. Comments are open to anyone with an account.

Create Account or Sign in