When Assumptions Change
"Multiple warehouses" arrives. If "one warehouse" was written down with its dependents, you know why the inventory model, the checkout and the shipping estimate all have to change, in what order, and which slice proves the new shape before the rest is rebuilt. If it was not, you find out one bug at a time.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
A requirement arrives that contradicts an assumption the design was built on. How do you turn "this changes everything" into a list of what changes, an order, and a first slice that proves the new shape?
The second warehouse is confirmed. I know from the list that "one warehouse" has six dependents. Now everyone wants an estimate and I am torn between "it's just a column on inventory" and "this touches everything" — and I do not know which is true until I start.
Estimate the visible change: add a warehouse table, put a warehouse id on inventory, migrate, done. It is a concrete plan with a concrete size, it is what the word "warehouse" suggests, and it produces a number the founder can hear.
The visible change is the schema, and the schema was the smallest dependent. The shipping origin, the reservation invariant, the sold-out check and the admin editor each assumed one pool of stock; the migration finishes, and every one of them still works — on the wrong data.
- The visible change is the schema, and the schema was the smallest dependent. The shipping origin, the reservation invariant, the sold-out check and the admin editor each assumed one pool of stock; the migration finishes, and every one of them still works — on the wrong data.
- Because the change was estimated as a column, it is built as a column, and the questions the assumption was hiding are answered by default: which warehouse fulfils an order? The first one. What if it has none but the second does? Sold out. Those are product decisions, made by a migration script.
- The estimate is wrong by the number of dependents nobody counted, which is exactly the number of bugs that arrive in the weeks after the "done" announcement.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Start from the assumption's record, not from the requirement's wording. The dependents list says what the assumption shaped; each dependent is a separate change with its own question. "Inventory as a product column" becomes "inventory as (product, location, quantity)"; "shipping origin constant" becomes "which origin, decided how?"; "per-product reservation invariant" becomes "per location, or across locations?" (Change Propagation).
- Notice that the assumption was hiding decisions, and surface them as questions before design: which warehouse fulfils an order when both have stock? Can one order ship from two? Does the customer see per-location availability? These are requirements that did not exist while the assumption held, and they need an owner's answer, not a migration's default (Requirements Emerge During Implementation).
- Re-derive the invariants. "Stock never negative" becomes "stock at each location never negative" and gains a sibling — "an order's units are fulfilled from locations that had them". The concurrency work from Invariants Under Concurrency has to be re-checked against the new row: the atomic decrement was per product and is now per (product, location).
- Order the changes by dependency and pick a slice that proves the new shape end to end before the rest is rebuilt: one product in two locations, one order, the allocation decision, the decrement, the shipping quote. The slice answers "does the new model hold the invariants and the decisions?" — and does not prove that the reports, the admin editor or the migration of existing data work (Vertical Slices).
How the change propagates from the assumption
The diagram is the assumption's record drawn as a graph: the retired assumption in the middle, its dependents around it, and the decisions it was hiding hanging off the dependents that hid them. Reading it is the estimate. The nodes that are not connected — catalog, cart, payment — are the parts the change leaves alone, which is as important to say as the parts it touches.
The slice that proves the new shape
Before the six changes are built in full, one thin path through all of them shows whether the new model, the allocation decision and the re-derived invariant hold together. The slice is small on purpose, and its "does not prove" list is the plan for the slices after it.
- DataInventory rows for one product at two locations; the order records its fulfilling location.
- AllocationGiven the customer's address and the order's items, pick the nearest location that has all of them, or report sold out.
- Inventory updateAtomic conditional decrement on the (product, location) row — the concurrency mechanism re-applied to the new key.
- ShippingQuote from the fulfilling location's origin instead of the old constant.
- ResponseConfirmation shows the order; the customer does not see which location, per the founder's answer.
One order of work for the change
The sequence follows dependency: nothing downstream can be built on a model that does not exist, and no surface should show data whose invariants are not yet held. It is one order; the alternative is the one a team with a hard launch date for the second location tends to prefer, and the device says when that is right.
- 1Get the hidden decisions answered: fulfilment rule, split policy, visibility
because They decide the allocation layer and the sold-out semantics; building before they are answered means a migration script decides them.
- 2New inventory model and the re-derived invariants, with the interleaving test on the new row
because Everything downstream depends on the model, and the concurrency mechanism must be proven on the new key before it is trusted.
- 3The proving slice: allocation, decrement, shipping quote for one product in two locations
because Shows that the model, the decisions and the invariants hold together before the surfaces are rebuilt.
- 4Migration of existing stock into the first location, behind a flag
because The riskiest dependent on a system with real data; done after the model is proven and before any surface depends on the new rows (Expand and Contract).
- 5Admin editor, reorder report, sold-out messaging
because Surfaces come last because they show whatever the model holds; building them earlier means rebuilding them.
- 6Retire the assumption in the register; record the new assumptions (two locations, no splits, no transfers)
because The next change will start from this record the way this one started from the last.
How to do it
Most important first.
- Open the assumption's record and turn each dependent into a sentence beginning "this becomes…". A dependent without a "becomes" is a change you have not understood.
- Write the questions the assumption hid — every place a default would otherwise decide — and get them answered by whoever owns the product, before designing.
- Re-derive every invariant that mentioned the assumed thing; each gets a per-location form and possibly a new sibling.
- Order by dependency: the model first, then the invariants on it, then the decisions that use it, then the surfaces that show it. Then build the thinnest slice through all of them.
- Leave the old assumption's entry in the register marked as retired, with the date and what replaced it (The Decision Journal).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Dependents, turned into "becomes": inventory column → (product, location, quantity) rows. Shipping origin constant → origin chosen per order from the fulfilling location. Admin stock editor → edits per location. Per-product reservation invariant → per location, plus "an order is fulfilled only from locations that had stock". Sold-out check → "no location has enough", which is a product decision in disguise. Reorder report → per location. Six dependents, six changes, two of them hiding decisions.
- Questions the assumption hid, answered by the founder: an order ships from the location nearest the customer that has all its items; if no single location has all, split it — but not in V2, so for now "sold out" means "no single location has enough". The customer does not see per-location stock. Those three answers decided the allocation logic and closed the first slice's scope.
- The slice: one product stocked in two locations, one order from a customer near the second; the allocation picks the second, decrements its row atomically, the shipping quote uses its origin, the reservation invariant holds per location. It proved the model, the allocation and the concurrency mechanism on the new row. It did not prove the migration of existing stock, the admin editor, the reports, or what happens when the nearest location has partial stock — each of which was written down as the next slice rather than assumed away.
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every dependent on the assumption's record has a "becomes", and the count of changes matches the count of dependents rather than the count of tables.
- The decisions the assumption hid are written as questions with answers from an owner, not as defaults in a migration.
- The invariants have been re-derived, and at least one gained a per-location form or a sibling.
- A slice exists that proves the new shape end to end, with an honest list of what it does not prove.
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?For each dependent of the assumption that just changed, what does it become — and can I say so without reading the code?
- ?Which decisions was this assumption making by default, and who should actually make them?
- ?Which invariants mentioned the assumed thing, and what are their new forms and siblings?
- ?What is the thinnest slice that proves the new shape holds the invariants and the decisions, and what does it deliberately not prove?
- ?Which parts of the system never depended on this assumption, and can I leave them alone?
What can go wrong
- The re-derivation becomes a redesign of the whole store. Only the dependents change; the catalog, the cart and the payment flow never assumed one warehouse and should not be reopened.
- The hidden decisions are answered by the engineer to avoid a conversation, and the founder discovers the allocation rule from a customer complaint.
- The slice is skipped because "we know what to build now", and the migration of existing data — the one part the slice explicitly did not prove — is where the launch fails.
- The old assumption is deleted from the register rather than retired, and next year nobody can explain why inventory is per location when there is a warehouse that has never been used.
- Working from the dependents list produces a larger, more honest estimate than the column-and-migration reflex, and the honest number is harder to deliver to a founder who heard "just add a warehouse".
- Surfacing hidden decisions puts product questions in front of people who wanted to hear "done"; the alternative is deciding them by migration script.
- The proving slice delays the visible change by the time it takes to build; on a change whose shape is genuinely obvious it is time spent confirming.
- "If the assumption was explicit, the change is small." It is *known*. Six dependents is six changes, and the inventory model still goes from a column to a relationship. The register tells you the size before you start; it does not shrink it.
- "The new model should support N warehouses so this never happens again." The requirement is two. Designing for arbitrary locations with transfers and split shipments is a new assumption — "we will need those" — made silently in the other direction (Add Complexity Only When Required).
- "Re-deriving invariants is only for inventory." Any assumption that shaped an invariant reshapes it when it changes. One currency shaped "total equals sum of prices"; two currencies split it into "total per currency" and "never sum across currencies" (Dangerous Assumptions).
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALWhenever a requirement contradicts a recorded assumption, the same sequence applies — dependents become changes, hidden decisions become questions, invariants are re-derived, a slice proves the shape — whether the assumption was one warehouse, one tenant, one region or one file format.
- STAGE-SPECIFICOn a system with real data the migration of existing state is a dependent in its own right and usually the riskiest; on a prototype with no data the same change is mostly the model and the decisions, and the slice can be the whole change.
- ILLUSTRATIVEThe six dependents, the nearest-location rule and the "not in V2" answer on split shipments are invented to show the sequence; a real store's allocation policy is a product decision with its own trade-offs.
Where the depth lives
This domain asks the question and hands the answer off by name.