The simplest design that ships beats the correct one that needs a committee
There's a version of architecture that optimizes only the diagram, and a version that also asks what the organization can actually approve. The second one ships more value, and it's the harder discipline.
There's a version of architecture that optimizes only the diagram — the cleanest separation, the most correct pattern, the textbook answer. And there's a version that also asks what the organization can actually approve and operate. The second one ships more value, and it is the harder discipline, because it requires holding a less-pure design on purpose — and knowing exactly which kinds of purity you're never allowed to trade.
I got a compressed course in the difference over five weeks of wiring an AI agent to an enterprise BI platform, where the whole design question eventually reduced to a single integer: how many app registrations we were going to ask the client's identity team to approve.
The textbook answer was three
The system: a Copilot Studio agent, reached from an existing web front end, calling a custom MCP server — a .NET 8 Azure Function — which queries Power BI semantic models on the user's behalf. The one non-negotiable requirement was row-level security. An executive asking the agent a question must see executive-scoped data and an analyst must see their slice, which means the API cannot call the BI platform as itself. It has to exchange the signed-in user's token for a downstream token via the OAuth on-behalf-of flow, so the user's identity — and their RLS roles — ride the whole chain.
Microsoft's guidance for this shape is unambiguous. The on-behalf-of flow is drawn as a chain of distinct registrations — client, middle-tier API, downstream resource — with tokens re-minted for each audience, and the Zero Trust guidance on app registrations says it directly: avoid using the same app registration for multiple apps, and keep web apps and APIs on separate registrations so a client never inherits an API's permissions. For our topology that meant three registrations: the MCP server as a protected API, a dedicated client for the agent's connector, and the existing web app. Clean audiences, least privilege, tidy diagram.
Every box on that diagram was a packet of forms
Here's what the diagram doesn't show. In this client's tenant, every new app registration was a governed change: a change-advisory-board packet, a written justification, an admin-consent request emailed to the identity team, an approval cycle measured in weeks. I knew the cost precisely because I was the one drafting the packets and consent emails, pasting the change ticket number into each. It teaches you a tactical corollary fast: treat approval round-trips, not registrations, as the scarce resource. Halfway through drafting one consent email I stopped to fold every permission the later phases would need into the same request, purely so I'd never have to run the cycle twice for the same app. And mid-debugging, I wrote the sentence that turned out to be the real design review: the clean setup goes back to a three-registration configuration — but it isn't as easy as flipping settings on; it's a series of forms and documents, which is why I was trying for two. Is there any way to make it work with two?
The moment approval cost entered the conversation it stopped being a compromise of the architecture and became an input to it. And it is not a soft input. The 2019 Accelerate State of DevOps report found that teams requiring approval from an external body such as a CAB were 2.6 times more likely to be low performers, and DORA's analysis of change approval found no evidence that external review reduces change-failure rate at all — heavyweight approval buys delay without buying the stability it exists to buy. A design that's ten percent purer but needs one extra governance cycle ships a month later than the one that fits inside the approvals you already have. For a marketing site, a month is nothing. For a regulated enterprise, it can be the whole engagement. The "correct" architecture sitting in a queue delivers exactly zero value while it waits.
The best architecture isn't the one that's most correct on the whiteboard. It's the one that's correct enough, and that the organization can actually approve and run.
Simplicity has a floor, and Entra will tell you where it is
Having decided two registrations was the target, my first attempt overshot: collapse to one — let the MCP server's registration be both the protected resource and the OAuth client calling it. Entra refused with a memorable error, AADSTS90009: an application "requesting a token for itself." Worse, the two platforms disagreed unsatisfiably about identifier formats — Entra wanted the audience in GUID form for that configuration while the connector platform's validation insisted on the api:// URI form. No setting satisfied both. The one-app design wasn't simpler; it was broken.
That's Gall's law in miniature — "a complex system that works is invariably found to have evolved from a simple system that worked" — with the emphasis on worked. The simplest design that ships is bounded from below by "ships" just as it's bounded from above by the committee. The job is finding the band between.
Two registrations landed inside it, with one doing double duty: the MCP server kept its own registration as a protected API, and the agent's connector borrowed the existing web app's registration as its OAuth client instead of getting a dedicated one. Before accepting that, we walked through the security analysis the third registration would have given us for free. The two client flows mint tokens for different audiences, so neither token can be replayed against the other. The shared surface reduces to a redirect-URI list and one already-consented scope. And each consumer got its own client secret, so credentials rotate and revoke independently. Two days of redirect-URI mismatches and missing-scope errors later — AADSTS50011, scp claims coming back null, tokens decoded one at a time in jwt.ms — the connector finally reported Connected, and a test user's first query came back correctly filtered by her row-level security roles. Registration count: two. Trust boundaries: intact.
Richard Gabriel's Worse Is Better is the thirty-five-year-old version of this argument: the design that privileges simplicity of adoption over formal completeness is the one that actually spreads. In an enterprise, adoption is spelled approval. And Fowler's Yagni prices what we declined to build — the dedicated connector registration was a presumptive feature carrying cost of build and cost of carry, and it remains addable later, for the price of one more form, the day a genuine requirement shows up.
When to insist anyway
The same project supplied the counterexample, which is what keeps this from being a license to cut corners. A later phase automated a privileged operation — granting dataset build permissions — running under a service principal. The tempting shortcut, in exactly the same spirit as the two-registration decision: expose it as an endpoint the web app could call, behind the same user scope as the read tools. No new registration, no new scope, no new paperwork. This time the answer was no, and it didn't bend. When a service principal does the work, the caller's own permissions are no longer what's being checked — your authorization gate is the security boundary. Put a privileged operation behind the scope every agent user already holds, and any read-tier user can trigger a privileged grant. That's privilege escalation with friendly paperwork savings. It needed its own admin gate. For the same reason, the query tool stayed hardcoded to read-only execution, because the write-capable variant runs with permissions that bypass row-level security — the requirement the entire design existed to honor.
The distinction generalizes. Collapsing three registrations into two moved paperwork: audiences stayed distinct, secrets stayed separate, RLS stayed enforced. Collapsing the admin scope into the user scope would have moved a trust boundary. Microsoft's token documentation names the failure class waiting behind that kind of merge — accepting a token minted for a different audience is the confused deputy problem — and the wider history names the stakes: Knight Capital lost over $460 million in 45 minutes to a deployment that skipped a second pair of eyes. The committee is not always wrong. Even DORA's prescription isn't no review — it's peer review, which satisfies segregation of duties without the queue.
So the skill is telling purity that protects something from purity that only satisfies the diagram. Bend cheerfully on the second kind. Spend your hard no on the first. An architect who can't tell them apart either fights every battle or loses the ones that mattered.
The short version
Approval cost is a legitimate architectural input, not a compromise of your standards — and the data backs it: external change approval slows delivery without reducing failures. Simplify freely when the simplification moves paperwork: shared registrations with distinct token audiences, separate secrets, hygiene intact. Refuse flatly when it moves a trust boundary: identity, authorization, data isolation, audit. And remember that simplicity has a floor — the design still has to work, and AADSTS90009 is Entra's way of telling you you've fallen through it.