Inventory And Oversell
Published
Overselling is the failure a warehouse notices last and a customer notices first. The controls that prevent it are a stock number, a ceiling on how far past it a sale may go, a rule about whether carts in flight count, and a per-line prohibition on backordering anything perishable. All four are property names. We protected a file that configures them, renamed the names a group at a time, and read the number of units promised.
Four units on the shelf and two genuinely sellable
The sample models a reservation service the way a careful team configures one. There are four widgets in the warehouse. Two are held by a live cart a minute old, one is held by a cart abandoned an hour ago, and the reservation TTL is fifteen minutes, so the abandoned hold has lapsed and its unit is back on sale. Reservations are counted, the oversell ceiling is zero, and the application supplies its own stock source that reads the warehouse record.
Unprotected, the engine computes two sellable units. An order for one is promised. An order for three is refused with over-oversell-limit(free=2 limit=0). An order for forty is refused the same way. A second line -- a refrigerated product carrying a noBackorder flag -- is refused separately when ordered short, because that line may never be promised beyond what is physically present.
Every one of those numbers is the value that crosses the boundary between the file and the installed service, which is the only place a measurement like this is worth taking.
The stock count does not become zero
Renaming available, the field on the warehouse record holding the number of units, is the sharpest arm in this area and the outcome is not the intuitive one. A stock count that cannot be read does not become zero, which would refuse everything loudly. It becomes undefined, which enters the arithmetic and propagates as NaN.
The engine prints sellable-widget=NaN. Then it does what it was written to do: it tests whether the requested quantity exceeds what is free. 3 > NaN is false. 40 > NaN is false. Every refusal test against NaN is false, so every refusal test fails open. The order for three was promised. The order for forty was promised, with the engine cheerfully reporting promised(free=NaN after=NaN).
This is a failure shape worth naming because it is distinct from the two most people expect. It is not a value reverting to a default -- the value is present. It is not a throw -- nothing throws. It prints, it survives serialisation, and it reaches a dashboard as a stock figure that no threshold will ever exceed.
Two layers, one pattern, and a refusal reason that moves on its own
The refrigerated line is protected twice on purpose. It is covered by the oversell ceiling like every other line, and it carries its own noBackorder prohibition because promising a vaccine you do not have is a different kind of problem from promising a widget.
Renaming noBackorder alone changes nothing a customer sees. The order for five is still refused; the reason moves from no-backorder(free=2) to over-oversell-limit(free=2 limit=0). The surviving layer caught it. Renaming oversellLimit alone leaves the prohibition intact and the perishable line safe.
Renaming both in one pattern promised five units of a two-unit refrigerated line, and forty units of a two-unit widget, reporting after=-38 without comment. The two layers were separate controls with separate purposes, but they lived on the same options object in the same file, so one pattern reached both. Redundancy is not independence when a single transformation can touch both copies; a second layer only survives if it lives somewhere the transformation cannot reach, which in practice means the server.
The rest of the surface
Renaming countReservations made the engine ignore carts in flight entirely, so all four units read as sellable and the live cart's two were sold twice. Renaming reserveTtlMs reverted the TTL to zero, which in this library means holds never lapse -- the abandoned cart's unit stayed locked away forever. That one fails closed: stock quietly stops being sellable, which costs sales rather than creating oversells, and shows up as a warehouse full of product the site says is unavailable.
Renaming stockSource is the quietest of the group. The application's own reader is replaced by the library's built-in, which has no opinion and treats every SKU as plentiful. The reported source changes from caller-supplied(reads the warehouse record) to library-builtin(every SKU is plentiful), and every order is promised. The guard is not switched off; it is substituted, and a substitution reads exactly like the healthy state unless you print which implementation is in use.
The control arm behaved as predicted. holdForPaymentMs was pinned to the library's own default value, so renaming it changed nothing -- there was nothing to lose. Every option whose value differs from the default moved; the one that matched it did not. That is worth knowing before reading any of these matrices: an arm can measure clean because of the value you passed rather than because the name is safe.
What protection alone did
Nothing at all. Five profiles, byte-identical output, every quantity and every refusal identical to the unprotected run. Renaming identifiers, encoding strings and compressing output do not disturb a reservation service.
Everything above required member renaming pointed at names an installed library reads. That is a configuration choice, not a side effect, and it is reversible in one line.
What to do about it
Treat every key on a reservation configuration, and every field on the records you hand it, as a contract with code that is not rebuilt when you rebuild. Keep MemberRegexp off them.
Separately, and regardless of obfuscation: require a stock figure to be a finite number before comparing it. One line -- refuse the reservation when the count is not finite -- converts the worst result in this article from forty units promised into a loud refusal. A malformed record from an upstream service produces exactly the same NaN, so this is worth having whether or not you obfuscate anything.
Finally, reconcile. A reservation ledger that is only ever checked against itself cannot detect that it has drifted; comparing promised units against physical stock on a schedule catches every failure in this article, including the ones that never throw.
Frequently asked questions
Does obfuscation cause overselling?
Not on its own. The sample was protected on five profiles and every quantity promised was identical to the unprotected run. The failures in this article required member renaming aimed at the names the reservation service reads.
What happens when the field holding a stock count is renamed?
It becomes NaN rather than zero. The engine printed sellable=NaN, and because every comparison against NaN is false, the refusal test never fired: an order for forty units against two units of stock was promised.
Why is NaN worse than zero here?
Zero refuses everything loudly and someone notices within the hour. NaN passes every threshold test, prints as a value, survives serialisation and reaches dashboards, so the system reports that it is working while promising stock that does not exist.
We have two separate oversell controls. Are we covered?
Only if they live in different places. In the measurement both controls sat on the same options object in the same file, so one renaming pattern reached both and the order was promised. Each control alone moved only the reported refusal reason, which is exactly the finding that gets closed as cosmetic.
What does renaming the caller-supplied stock reader do?
The library substitutes its own built-in, which treats every SKU as plentiful. Nothing throws and nothing logs an error; the reported source string is the only visible difference, which is why printing which implementation is in use is worth doing.
Which of these failures is the safe one?
Renaming the reservation TTL. It reverts to a value meaning holds never lapse, so stock is locked away rather than oversold. It costs sales instead of creating promises you cannot keep, and it is visible as unavailable stock that is physically present.
What is the single cheapest fix?
Require the stock figure to be finite before comparing it, and refuse the reservation if it is not. That converts the worst outcome here into a loud refusal, and it protects you against a malformed upstream record as well.
Related reading