Two audiences on one system. Suppliers see public market information and their own private, executable quotes on a phone. Traders run the internal book. The line between public and private is an authorisation boundary, and accept is binding. A quote reaching a supplier without a person reading it first is the whole point of the product, and it is also the whole risk.
When a price goes out without a person reading it, the person's judgement has to become rules.
Before a platform like this exists, a trader reads each supplier's request and decides whether to quote. Most of that decision happens in a glance. Is this a grade we buy right now? Does the price still reflect today's market? Is this supplier good for the amount they are describing? Does the request look like what this supplier usually sends? None of these checks is written anywhere, because the trader never needed to write them. They are part of being good at the job.
An executable quote removes the glance. It is a price a supplier can accept on their phone, and acceptance binds the operator immediately, with nobody reviewing it. Every check the trader used to make either exists as a rule the system enforces before the quote is issued, or it does not exist at all. Automation does not make the checks unnecessary. It makes them invisible unless someone deliberately writes them down.
The second idea this kind of system rests on is authorisation. Authentication establishes who someone is. Authorisation decides what they may see and do. Object-level authorisation checks the specific record being requested: this quote belongs to this supplier. The classic failure is a screen that only ever shows a supplier their own quotes, sitting on an interface that will return any quote whose identifier it is sent. Change one number in the request and another supplier's price comes back. The OWASP list of API security risks puts this failure, broken object level authorisation, at the top.
Once a quote is executable without a trader looking at it, every check that trader used to make by eye has to exist as an enforced rule, and both surfaces have to share one consistent state. Two applications over one database produce double acceptance, quotes that are live on one screen and stale on the other, and permission bleed across the public and private line. In this shape that bleed is an object-level authorisation failure, which means one supplier reading another supplier's price.
Two applications over one database fail at the seam between them.
Two audiences look like two applications, so teams often build two: a supplier app and an admin panel, each with its own idea of a quote's status, its own caching and its own access checks, sharing a database underneath. Each works on its own. Between them, a quote can be withdrawn in the admin panel and still accepted in the supplier app, because the supplier app read it a moment earlier and never looked again.
The second failure is where the boundary lives. When access is enforced by which screen a user can reach, the interface underneath answers anyone who asks it directly. It is among the most common serious flaws in web interfaces, and it is invisible in normal use because the screens never ask for anything they should not.
The reflex, and the fix.
A supplier app and an admin panel over one database
Two audiences look like two applications, and each one is simpler to build alone.
Shared state diverges between them, acceptance can fire twice, quotes go stale on one surface while live on the other, and the public and private boundary leaks because it is enforced in two places by two pieces of code.
One state core with two views
A shared core to design before either surface, and authorisation modelled at the level of each object, not each screen.
Supplier journeys and the trader book reading one consistent state, acceptance that is idempotent, and a public and private line enforced in exactly one place.
Enumerate every check the trader made and encode each as a rule, because automating the gatekeeper away does not remove the gate.
One state core, two views
The supplier journeys and the trader workspace are views over a single core. They are not two applications that happen to share tables, so they cannot disagree about whether a quote is live.
Authorisation checked on the object
Every request is checked against the record it asks for, not the screen it came from. That is what stops a supplier reaching another supplier's private quote by changing an identifier in a request.
Idempotent acceptance
Accept is binding, so it carries a key. A retry or a double tap on a phone resolves to one execution and never to two trades.
Every by-eye check written down as a rule
The checks the trader performed before sending a price were enumerated and encoded. The platform now makes that decision, and it cannot do so by judgement.
The uncovered case is flagged
A quote request that no encoded rule covers is held for a person. The platform does not issue an executable price for a situation nobody has described to it.
What each choice cost, and what it bought.
ADesigning the shared core before either surface
Two audiences invite two teams and two applications, and either could have shipped sooner alone. Designing one state core first delayed both surfaces. It meant a quote has one status, read by everyone, and a withdrawal in the trader workspace takes effect on the supplier's phone because there is nothing else for the phone to read.
BEnforcing authorisation on the object, in one place
Per-screen access checks are quicker to write and they look complete in a demo. Checking ownership on every object request, in the core, required modelling who owns what before writing any interface. It made the public and private line a property of the data, so a new screen or a new endpoint inherits it without anyone remembering to add it.
CMaking binding acceptance idempotent
Disabling the accept button after a tap stops most double acceptances from people and none from networks. A key on each acceptance, checked by the core, makes the second request return the first result. It cost a little design on the most important action in the product and removed the possibility of one decision producing two trades.
DInterviewing the trader for their checks
The tempting assumption is that the specification lists the rules. It listed the obvious ones. Sitting with the trader and enumerating what they looked at before sending a price took time and produced rules nobody had thought to specify. Requests no encoded rule covers are held for a person, so a missing rule shows up as a held quote, never as a binding mistake.
A single headline number hides where a system fails. This work was scored on the dimensions that actually decide whether it holds in production, measured on real, held-out cases rather than the demo path.
No measured figures are published for this engagement. Outcomes are described qualitatively.
Two applications over one database, where a double tap executes twice, a quote is live on one surface and stale on the other, and a changed identifier reaches another supplier's private price.
A single state core with supplier journeys and a trader book as views, idempotent binding acceptance, and object-level authorisation enforcing the public and private boundary in one place.
What it owns, and what it hands to a person.
A clean build proves the loop for the launch grades. Adding grades is rule work, not a rebuild, but it is still work, and the specification's own wording made clear the list would grow. Trader judgement that has never been written down is outside the platform until it is.
Any product that automates a human gatekeeper away has the same job list: enumerate every check that person made, turn each into an enforced rule, make the binding action idempotent, and keep every audience on one state. Automated credit decisions, self-service refunds and instant insurance quotes all follow it.
Automating a person out of a decision does not remove the checks they made. It removes them silently unless each one is written down and enforced.
Authentication says who someone is. Authorisation says what they may touch. Check it against the specific record on every request, never against the screen.
Broken object level authorisation tops the OWASP list of API risks because it is invisible in normal use. Test it with hostile identifiers on purpose.
Two audiences do not need two systems of record. Build one state core and give each audience a view of it.
When an automated decision meets a case no rule covers, hold it for a person. A held quote is recoverable and a binding mistake is not.