SystemsGENERALCONTESTEDILLUSTRATIVE

Source of Truth

The current price of a product and the price a customer paid are two different facts with two different owners. Naming the source of truth for each fact — and noticing when one fact is actually two — is how the same number stops appearing in two versions.

The moveWorked exampleNext questions

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.

The question

For each fact the system shows, which single place is authoritative — and is "the price" one fact or two?

The situation

The cart says one total, the confirmation email says another, and the admin order view says a third. Each was computed from a price the code found somewhere. I have been asked "which one is right?" and I realise the system has no answer, because nobody ever said which place *is* the price.

The reflex

Pick the database. Product.price is the real price, the others are copies, so make everything read Product.price and the three numbers will agree. It is a clear rule and it removes the duplicates.

Why it stalls

The three numbers now agree — and the confirmation email for last month's order shows this month's price. The rule "one place is authoritative" was right; the assumption that there was one fact was wrong, and the fix turned a display inconsistency into a false record of what was paid.

What the reflex produces — and fails to produce
  • The three numbers now agree — and the confirmation email for last month's order shows this month's price. The rule "one place is authoritative" was right; the assumption that there was one fact was wrong, and the fix turned a display inconsistency into a false record of what was paid.
  • The payment provider has its own record of what was charged, and it disagrees with Product.price as soon as a discount or a currency rounding is involved. Now the database is "authoritative" for a number it did not actually charge.
  • Because "source of truth" was decided once, globally, it is never revisited per fact. Inventory follows the same rule and reads the live count everywhere, including in the order that was placed when the count was different.
  • Every future disagreement is settled by which system is "the" source of truth, which becomes a political question between teams, rather than by asking which fact each reader actually wanted.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Start from the reader's question, not from the storage. "What is this product's price?" and "what did this customer pay?" sound like one question with one answer; they are two facts. The first is owned by whoever can change it — the product record. The second is owned by whoever witnessed it — the order, at the moment it was created, and behind it the payment provider's record of the charge.
  • For each fact, name exactly one authoritative place, and say what makes it authoritative: it is where the fact is *decided*, not where it is most convenient to read. Everything else that holds the value is a copy, and copies are allowed only if they say what they are a copy of and when they were taken.
  • When two facts have the same name, give them different names. Product.price and OrderItem.unitPrice are not the same field in two tables; they are different facts that happen to be equal for an instant at checkout. The rename is the design decision.
  • For facts decided outside your system — whether a payment succeeded, whether a parcel was delivered — the source of truth is outside, and your record is a copy with a timestamp. Design the copy to be re-derivable from the owner (Which Dependency Must Answer Before the User Can Be Told Anything?).

One word, two facts

The compare is the whole lesson in one pair. On the left, "price" is one column read from everywhere; the totals agree and the history is false. On the right, the reader's two questions have two owners with two names, and the totals are allowed to differ where they should.

One price, read live by everyone
Cart, checkout, order history, invoice and refund all read Product.price. Every screen agrees. Change the price and every historical order silently changes what the customer "paid".
Two facts, two owners
Product.price is what the admin decides and what the catalog and cart show. OrderItem.unitPrice is what checkout witnessed and what history, invoice and refund show. They are equal at the instant of checkout and independent afterwards.

The readers were asking two different questions. A single owner can only answer one of them; the second answer needs a witness at a moment, which is what the captured column is.

The ownership table for the store

The table is what the move produces. The "decided by" column is the test for the owner: the admin decides the list price, checkout decides the order total, the provider decides whether a charge happened, and each of those is where the fact lives. Everything in the last column is a copy and says so.

Fact (the reader's question)Decided byOwnerCopies, and what they know
Current list price of product Pthe adminProduct.pricecatalog cache (invalidated on edit)
Unit price charged on order O for Pcheckout, at creationOrderItem.unitPriceinvoice PDF (rendered from the order, re-renderable)
Units of P available nowstock changesInventory.quantityproduct page badge (live read, no copy)
Units reserved by order OcheckoutOrderItem.quantitynone
Whether order O is paidthe payment providerthe provider's charge recordOrder.paymentStatus + timestamp of the last webhook; re-checkable by API
Whether order O was deliveredthe shipping providerthe carrier's tracking recordOrder.shippingStatus, polled or pushed, with the time it was last seen

Where the question leads

The question ladder shows the same confusion asked three ways. The vague form is unanswerable because it assumes one fact; the best form names the reader's question and asks for an owner, which is a question the schema can be made to answer.

Question quality
vagueWhich price is right?
betterShould order history read the price from the product or store its own copy?
bestFor "what did this customer pay for this item", which component witnessed that fact at the moment it was decided, and does it record it under a name that cannot be confused with the current list price?

why The best form separates the two facts, names the moment the second one is decided, and asks for a distinct name — so the answer is a column and a capture step, not an opinion about which table is more trustworthy. The vague form has no answer because it is asking one question about two things.

How to do it

Most important first.

  • For every number the disputed screens show, write the reader's question in full: "the current list price of P" versus "the amount charged to C for P on order O". If two screens ask different questions, they are allowed to show different numbers.
  • For each question, name one owner and why: it is decided there. Product price: the product record, because the admin edits it there. Amount paid: the order item, because checkout decided it — with the provider's charge as the external witness.
  • Rename until no two facts share a name. Then audit every reader against the table: each should read exactly the owner of the fact it asked about.
  • For every copy that survives, write down what it copies and when it was taken, and whether it can be re-derived from the owner if it turns out wrong (Snapshots vs References).

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The three totals. Cart: "the sum of the current prices of the items in the cart" — owner Product, so the cart reads live, and the total may legitimately differ from a total seen yesterday. Confirmation email: "what was this order's total when it was placed" — owner OrderItem.unitPrice × quantity, captured by checkout. Admin order view: the same question as the email, so the same owner. The bug was that the email and the admin view were reading the cart's owner.
  • Inventory, the same move: "how many units are available now" is owned by the stock record; "how many did this order reserve" is owned by the order line. Reading the live count in the order view gives a number that changes as other customers buy — correct for the stock page and meaningless on an order.
  • Payment status: "has this order been paid" is *decided* by the provider, so the provider is the source of truth and Order.paymentStatus is a copy taken when the webhook arrived. Which means the order can say "paid" while the provider has since reversed the charge; the copy needs a timestamp and a path to re-check (What If Payment Fails?).

How you know it worked

What now exists that did not before, and what question you can now ask.

  • The disputed screens have been sorted into groups by the question they ask, and screens in different groups are allowed to disagree by design.
  • No two facts share a name. Someone reading the schema can tell a live price from a captured one without reading code.
  • For each copy you can say what it copies, when, and how it would be re-derived; for each externally-decided fact you can say who decides it.

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.

Next questions
  • ?What question is this reader actually asking — and do two readers with the same word mean the same fact?
  • ?Where is this fact decided, as opposed to where is it convenient to read?
  • ?Which of the places holding this value are copies, what are they copies of, and when were they taken?
  • ?Which facts in this system are decided outside it, and what does my record of them claim to be?

What can go wrong

How the move itself fails
  • Everything becomes a distinct fact. "The price on the product page" and "the price on the catalog card" are the same question, and splitting them creates the duplication the move exists to remove.
  • The owner is chosen for convenience — the place the code already reads — rather than for where the fact is decided. A cache is never the source of truth, however fast it is.
  • The move is applied to the schema only. The frontend keeps its own copy of the cart total and computes it differently; source of truth is a property of the whole system, including the browser (Who Owns This State?).
  • External sources of truth are ignored because they are inconvenient. Declaring the database authoritative for "paid" does not make it so; the provider still decides, and the day they disagree the provider wins.
What the move costs
  • Splitting one field into two facts adds a column, a capture step and a rename, and every future reader has to choose. The cost is real; it is paid once per fact and saved on every subsequent bug.
  • Naming the provider as the source of truth for payment means your own database can never fully answer "is this paid?" without a caveat — which is honest, and also slower and more code.
  • A source-of-truth table is a document, and documents drift. It stays true only if the schema names carry the distinction, so that the table is readable from the code.
Misreads
  • "Single source of truth means one database." It means one owner per fact; a system can have many facts with many owners, some of them outside. One database holding two versions of the price has no single source of truth for anything.
  • "Copies are bad." Copies with a stated origin and time are how history works; the order item *is* a copy of the price, deliberately. The bad copy is the one that does not know it is a copy.
  • "The provider is authoritative, so trust the webhook." The provider is authoritative for the charge; the webhook is a message about it that can arrive late, twice or out of order. Authority and the channel that reports it are different things (Duplicate Requests).

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.

  • GENERALEvery system with more than one place a fact is held needs an owner per fact. In a chat app "the last message in this conversation" and "the last message this user has read" are the pair; in a dashboard "the metric" and "the metric as of the report".
  • CONTESTEDThe strongest opposing view: choosing one physical store as authoritative for everything and deriving all other views from it — an event log, a canonical table — is simpler to reason about than a per-fact ownership table, because there is exactly one place to look and every copy is provably derived. That view is right when the system is small enough that derivation is cheap and no fact is decided outside it; it stops being right the moment a payment provider decides something your log can only record.
  • ILLUSTRATIVEThe three disagreeing totals and the reversed charge are invented; the ownership table is one reasonable assignment for a small store, not the only one.

Where the depth lives

This domain asks the question and hands the answer off by name.