What Took the Longest
I assumed the matching engine would be the hard part. It was not. What took the time was everything I had never done before, which in hindsight is the most predictable outcome imaginable.
When I started, I assumed the matching engine would consume most of the effort. It seemed like the hard part. Disqualification logic, weighted scoring, the vertical pack structure, the entire question of how you turn a buyer's operation into something a machine can compare against a hundred vendors.
It was not the hard part. It went faster than anything else, and the reason is not that it was simple. It is that I already knew the material. Every decision in that engine was a decision about how buyers actually choose and how vendors actually differ, and I have spent thirty years accumulating opinions about both. The work was translation rather than discovery.
What took the time was everything I had never done before, which in hindsight is the most predictable outcome imaginable and did not feel that way at all while it was happening.
Identity and access consumed more of this build than any other single area. Not close.
It is worth understanding why, because it is not that the identity provider is difficult. It is excellent, and authentication itself worked almost immediately. What consumed the time was everything downstream of it.
The system could tell who somebody was and had no idea where to put them. A vendor would sign up successfully and land in the buyer onboarding flow, because the post-signup destination had been written when there was only one kind of user. Signed-in people kept arriving back at the marketing homepage. Administrators reached dead ends inside an interface built for somebody else entirely. None of that is an authentication problem. It is the discovery that identity is not really a question of whether you are allowed in, it is a question of what you are, and every surface in the application has to hold an opinion about that.
Permission failures turned out to be a normal path, and I had built them as an error path. A page would crash when the API correctly refused a request. Not on a bug, on the system working exactly as designed: somebody without rights to a thing asked for it, was properly denied, and the interface treated the denial as impossible and fell over. That pattern was in several places at once, because I had written every screen assuming success and treating refusal as exceptional, when refusal is an ordinary daily event in any system with more than one kind of user.
Then there are the edges you only find by staring at them. If a person changes their email address to one that already exists in your records as a leftover from some earlier state, the update violates a uniqueness rule, the incoming message from the identity provider fails, and the provider retries it indefinitely while nothing recovers on its own. Low probability, and the consequence is a loop that runs until somebody intervenes by hand. I found that one by reasoning about it rather than by hitting it, which is the only pleasant sentence in this section.
And underneath all of it, two systems both believe they know who your users are. Records existed in my database with no matching account at the provider. Neither side reports that as an error, because from each side everything looks correct.
None of these were interesting problems. All of them took real time to find and fix.
Configuration drift belongs on the list too, and it is not confined to identity.
At one point I pushed several commits, redeployed repeatedly, and watched the same stale build go live every time. Automatic deployment on one service had been switched off in a dashboard, so pushing to the main branch did nothing at all and every redeploy faithfully shipped the same old commit. Nothing failed. The deployment history showed a column of successful deploys, each one identical to the last. I lost most of a session to it before checking a setting I had no reason to suspect, because configuration living in somebody else's dashboard is state you do not own, cannot version, and will not see change.
The permissions matrix was the second one, and it is the one I most underestimated.
Here is the thing about multi-party access that does not become obvious until you are inside it. Each new party you add does not add a case. It multiplies the cases. A buyer has a team. A consultant may act on that buyer's behalf, under their own brand, with their own configuration. A vendor sees a scoped slice of the buyer's assessment, but only after accepting the match, and only the parts the buyer did not mark confidential. An operator sees a different slice again. Every one of those parties can be in several states, and the correct answer to "can this person see this thing right now" depends on the intersection of all of it.
I do not think there is a way to know that is going to be hard before you build it. It looks like a permissions problem. It is really a multiplication problem, and the multiplying happens faster than you expect.
The demonstration environment was the third, and it is the one nobody would guess.
Building a demo sounds like an afternoon of fake data. It was not, because of a rule I set early and would set again: no data shape may exist in the demo that production could not actually produce. A demo that shows something the real system cannot generate is a lie with a good interface, and I was not willing to build one.
Holding that line means the demo has to be produced by the real machinery rather than assembled by hand, which means every containment question becomes real. Demo data must never reach production analytics, or every number I report is wrong. Production data must never reach the demo, or I have exposed a real buyer to make a point. That containment has to hold in both directions, permanently, with no path where a tired person on a Thursday can cross it by accident.
Then there is the fourth thing, which is not an area at all.
Fixing one thing surfaces problems in three others, and this happens constantly.
You correct a defect. The correction changes behavior somewhere downstream. That change exposes an assumption that was quietly wrong all along, and now you have two new problems that were always there and were simply invisible. At one point I ran a full pass against production and came out with roughly twenty defects. The triage produced fifteen pieces of work. The deeper look that followed surfaced thirteen more issues that had not been in the original twenty at all.
For a long stretch I read that as evidence I was doing this badly.
It is not. A system is a graph, and you only ever see the edges you have walked. Every fix walks new edges. The defects were not created by the fix, they were revealed by it, and the alternative to revealing them is not having fewer defects, it is having the same defects and not knowing. A build where nothing new surfaces when you change something is a build nobody is actually exercising.
The setbacks are not a signal that you should not be building. They are what building is.
That is the part I most want to pass along, because I read it wrong for a long stretch and spent a lot of energy second-guessing work that was going fine.
Everybody I have spoken to who has built something real describes some version of the week where three things broke because they fixed one, and of sitting there wondering whether the whole thing was beyond them. The difference between the people who finish and the people who do not is almost never talent. It is whether they understood that the bad week was normal.
Nobody tells you this, because the people who have been through it mostly write about the outcome. So here it is from someone still in the middle of it: it is supposed to feel like this.
If you are considering building something, the useful expectation to set is that the ratio will be inverted from what you assume.
The part you know cold, the domain you have lived in for years, will move faster than you expect, because you are not learning it, you are only encoding it. The plumbing you have never touched, the identity and the permissions and the configuration and the environments, will take far longer than any estimate you would have made, because you are learning it at the same time as you are building it.
Budget for that, and then do not read the slowness as failure. It is the price of entry, it is paid once per unfamiliar thing, and it gets cheaper every time.
Selecting supply chain technology?
PreShiftIQ matches buyers to vendors on measured fit across TMS, dock scheduling, ELD, carrier vetting, and fleet management. Vendors pay for outcomes, never for influence, and the buyer match is free.
See how matching works at preshiftiq.com/how-it-works.
Next in the series: how quickly the unfamiliar stops being unfamiliar.

