← Rivering · beecork.com/rivering
For people who write code and want to write it well
Nobody can hold a database in their head, and no table tells you what it is for. Rivering is the work of deciding — every table is one of two kinds, a noun or a river, it takes one of a few named shapes, and the shape carries its own rules.
Do it once and you stop needing to remember the system. You look at a table, ask what it is, and the rules follow.
By the end you will be able to look at any table in any system — one you have never seen, written by someone who left years ago — and say what kind of thing it is, what rules apply to it, and what would break if you changed it.
Not by memorising the system. By asking four or five short questions.
Forty-two steps, each one short, in six parts. Every step uses only what came before it, so reading in order costs less than skipping and doubling back.
Parts one and two are about why — no database words at all. By the end of part three you will understand the whole idea. Parts four to six make it precise enough to use on something real.
Nothing here assumes a particular language, framework, or database. It is about shape, not tools.
Three steps. What the difficulty actually is, before we do anything about it.
You open a codebase and there are two hundred tables. You need to know where something gets recorded. You search, and you find three places that look plausible. One has the field you want but it's empty half the time. Another has it filled in, but you can't tell whether anything still writes to it.
So you ask someone, or you read the code that writes it, or you give up and add a new column. All three are reasonable. None of them scales, and after a year nobody in the building can say with confidence where anything lives.
That is not a failure of memory, and it is not a sign you should try harder. Two hundred tables is more than anyone holds in their head. The problem is that nothing about a table tells you what it is for.
Nobody sat down and designed the confusion. Each table was added by someone solving a real problem that week, and each decision was sensible on its own.
But a table doesn't announce itself. A table that records what happened and a table that holds a current total look identical — rows and columns. Nothing in the shape says "I am a record, never change me" or "I am a total, throw me away and recompute me." That knowledge lives in the head of whoever wrote it, and it leaves when they do.
So every new person rediscovers the same things by breaking them. Not because they were careless — because the information was never written down anywhere except in behaviour.
Not documentation of every table. That goes stale, and nobody reads two hundred entries.
What helps is much smaller: a short list of kinds, so that any table is one of them. Two, as it turns out — a noun or a river — each taking one of a handful of named shapes. And each shape carries its own rules — what may be written to it, what may be deleted from it, what breaks if you get it wrong.
Then you stop needing to know the system. You look at one table, ask which kind it is, and the rules follow. A stranger's code becomes readable, and — the part that matters more — your own new table gets built right the first time, because you decided what kind it was before you wrote it.
Two kinds of table, the shapes they take, the rules attached to each, and enough of the reasoning that you can tell when they apply and when they don't.
The work has a name in these pages: rivering. To river a database is to go through it deciding what each table is, and to write that down where the next person will find it. A table that has been rivered answers for itself. An unrivered one can only be guessed at.
That last word is the useful one. Most databases have large unrivered stretches, and nobody says so — because until now there was no word for the state, and a problem with no name is hard to point at.
Four steps. Almost everything that goes wrong is a version of this, so it is worth getting properly.
A small shop writes every sale in one notebook. Date, item, amount. Nothing clever.
If anyone asks what was sold today, you add up today's lines. Slow — but there is only one place to look, and it cannot be wrong, because it is the record.
Hold on to that last part. It comes back.
Adding up a hundred lines every time is tedious, so somebody starts writing the running total on a whiteboard by the till.
This is a good idea, and worth saying so plainly — the whiteboard is not the villain here. Reading one number beats adding up a hundred, and for a long time the two agree perfectly.
Eventually a sale goes into the notebook while the whiteboard is being wiped. Now the two say different things.
Here is the part that matters, and it is not the mistake. Nobody in that shop can tell which one is wrong. Both were written by hand. Both look equally official. There is no way to settle it — not by looking harder, not by asking anyone. The information needed to decide does not exist.
The problem was never the mistake. It was that the shop had no way to find out which side made it.
If you have ever stared at two tables holding slightly different numbers and had no idea which to believe — that is this, and it is the reason for everything that follows.
You do not have to throw the whiteboard away. You have to change one thing: nobody may ever write on it by hand. The only permitted action is to wipe it and copy the notebook's total across.
Now a disagreement has a cure. Whiteboard looks wrong? Wipe it, recopy. No investigation, no argument, no meeting. And the notebook is never in doubt, because nothing else was ever allowed to be the source.
Keep one record that everything is worked out from. Anything else holding the same fact may only ever be recopied — never written directly.
There is a second cure, and it is worth knowing because sometimes it is the only one you can have. The shop is stuck because nothing wrote down the writing. If every entry — into the notebook and onto the whiteboard — were itself recorded, the disagreement stops being symmetric: you can see which one landed and which didn't. That is what an audit trail is for, and it is why a system that keeps a complete one can survive a mess that would otherwise be unfixable. It costs more. Prefer one record where you can have one.
Everything in the rest of this booklet is a consequence of that sentence, or a case where applying it needs care.
Seven steps. The notebook becomes something with a name, and we work out which way things move. By the end of this part you have the whole idea.
The notebook has a name in a database: a log. Things get added to the end, in order, and they stay there.
Through this booklet it is called a river — because it flows one way, and because you can stand at any point and see what came past. That image does more work than it looks like it should, so it is worth adopting.
A system usually has several. One recording what happened to a person. One recording what happened to a message. Each is a separate river.
This is what makes a river worth trusting, and it is the first rule people break. If something changes, you do not go back and correct an old line — you add a new one underneath.
So when somebody withdraws their consent to be emailed, you do not edit the line where they agreed. You write a new line saying they withdrew. Both are now true, in order, forever.
A line that can be overwritten cannot prove what was true last March. And proving what was true is the only reason to keep a record at all.
Look at three lines from a company's log of a person:
Levan paid £240. Levan opened an email. Levan booked a call.
Three completely different events — money, email, a calendar. What makes them one river is that every line is about the same person. That is the river's subject, and it does not change from line to line.
This sounds obvious. It is the single most useful thing in the booklet, and part four is mostly about getting it right.
The word gets used for two unrelated ideas, and keeping them apart will save you an argument later.
The first is what a river is about — its subject. One river points at people, another at messages. A direction in the sense of aiming somewhere.
The second is which way copies move — from the river outward, never back. That is the whiteboard rule from step 7. A rule about copies, not about subjects.
Both matter. They are simply not the same thing, and this booklet will always say which one it means.
Things get worked out from a river — today's total, who counts as a customer, how far someone got in a course. Those results can be stored so they are fast to read. That is fine and normal.
But nothing may flow the other way. No result may ever be edited directly and then treated as truth, because the moment it can be, you are back in the shop with two answers and no way to settle them.
If copies only ever flow one way, every disagreement has a cure: throw the copy away and work it out again.
In real systems you will often find two tables holding what looks like the same fact, and you have to decide which is the source. It feels like a judgement call — the kind of thing two engineers argue about for an hour. It is not.
Compare these two sentences:
“Levan entered the welcome sequence on Tuesday.” — has a time, has a person, describes something that occurred.
“Levan is currently in the welcome sequence.” — has no time, describes no occurrence. It is a state: the answer you get by reading the events.
Events produce state. State never produces events, and the reason is simple: a state cannot tell you when, and it cannot tell you how many times. Those facts exist only in the events.
So if you ever decide the state is the original, the events start to look like a redundant copy — and once they are deleted, nothing can rebuild them. It only works in one direction, which is what makes it a rule rather than a preference.
And it works in one direction for one reason: working it out throws something away. “Is currently enrolled” gives the same answer for someone who joined once and for someone who joined, left and rejoined. Two different stories, one answer — so nothing can run it backwards. Which means something you would not guess: if a copy loses nothing, it is not really a copy. A table that keeps everything the log kept is a log wearing different clothes, and the log's rules apply to it.
Two. You can rebuild the first list from the second by reading it through. You cannot rebuild the dates from the list.
That is the whole idea. Everything after this is precision — and precision is what turns a nice principle into something you can actually apply on a Tuesday afternoon to a table you did not write.
Nine steps. Five decisions nobody makes on purpose, and the specific trouble each one causes when it's skipped.
You can follow every rule so far and still build a log that is useless. It happens often, and it is worth knowing why before it happens to you.
Five decisions have to be made: a subject (what every line is about), a unit (how big one line is), a vocabulary (what a line may be called), the rules (what may be written in it at all), and retention (how long it is kept).
That last one is the decision nobody makes on purpose, and three other kinds quietly depend on it. A summary can only be rebuilt as far back as the river still goes. A heartbeat needs its own lifespan, never shared with a river. And anyone shortening how long a log is kept is making a correctness decision while believing they are making a storage one.
It needs a default, or it will be skipped — a decision nobody is forced to make is a decision nobody makes. So a river with no stated retention is kept forever, and counts as unrivered until somebody states it. Forever is safe; it destroys nothing. Calling it unrivered is what stops “forever” becoming a decision by accident.
Skip one and you get a log that looks perfectly healthy, fills up nicely, and cannot answer a single question anybody asks it.
A rule you will hear is “one subject, one river” — if two logs are about the same thing, they should be one log. Sensible.
But that rule can be steered by choosing a word. Call the subject “a message” and your email log and your text-message log are one thing. Call them “an email” and “a text” and they are two. Same tables, opposite answers, depending on which word you happened to pick.
A rule that gives whatever answer you name into it is not a rule. It is a way of dressing up a decision you already made.
Every line in a log refers to something — a person, a message, an order. That something lives in its own table. So instead of naming the subject, look at which table the lines point to.
If the email log and the text log both point at rows in one messages table, then the system already decided: the subject is a message. Nobody had to choose a word, and there is nothing left to argue about.
A river's subject is whatever its lines point at. Two rivers pointing at the same thing are about the same subject.
Suppose you think two logs should stay separate. Then you have to argue that the thing they point at should be two tables, not one — and win that argument on its own terms, about the things, not about the logs.
Which is why two message logs merge but two campaign types might not: there is one messages table, and there are two campaign tables. Same rule, opposite answers, and no special pleading needed in either direction.
The log follows the thing it points at. One thing, one log.
This also answers the question people usually get wrong the other way round: when do you need a new river? Only when the subject changes. Wanting to know more about a person — which lesson they opened, not just which course — is never a reason for a second log about them. It is a reason for more kinds of line in the one you already have.
“Levan paid £240 for the coaching package” touches a person and a product. Two subjects? No — and here is the test, which takes about four seconds to run.
Take one away and read it again. Remove the product: “Levan paid £240” still says something. So the product was just mentioned. Remove Levan: “paid £240” says nothing — nobody paid. So the person is the subject.
It is genuinely two rivers only when that test gives different answers for different lines — when some collapse without a person and others collapse without something else entirely.
Somebody has a support conversation with you. Forty messages back and forth. Does the log get forty lines, or one?
Both extremes are wrong, in ways that are easy to miss until it is too late to change. Forty lines and that person's history is flooded — you cannot see anything else that ever happened to them. No lines at all and you lose that they ever spoke to you.
A good answer is three: started, handed to a human, resolved — each carrying a count of how many messages there were. You keep the shape of the conversation without drowning everything around it.
The unit is the smallest thing a person would actually want to see on a timeline. For a conversation, that is the conversation — not the keystroke.
And it does not have to stay a matter of taste. A unit is too fine when one subject's rows crowd out everything else on that subject's timeline — which is a number, not an argument.
Here is the scale, measured rather than guessed. Across 77 logs in one real system, the 99th percentile of rows-per-subject came out: median 19, three-quarters under 72, nine in ten under 202. 83% sit under 100. Only three of the seventy-seven passed 1,000 — and every one of those three turned out to be a heartbeat rather than a log.
So: under 100 is ordinary, 100–1,000 is worth a look, and over 1,000 is almost certainly a heartbeat wearing a log's clothes.
One trap, and it is easy to fall into. Measure against the subject, never against something every row happens to point at. Every row here also points at the tenant — and “rows per tenant” read 595,089 and made a 1.3-million-line diary look like a heartbeat. Against its real subject, the person, it reads 83. A pointer that sits on most tables in the database is a scope, not a subject.
A good log has a short, fixed list of names — paid, registered, joined, cancelled. Each one deliberately added by someone who thought about it.
A log with no list ends up holding free text, and it goes wrong in two ways at once. Here is a real line from a real system:
POST /support/tickets/e4f5b5cc-fabd-485d-834e-3887fa2d66c0/typing
The name came from whatever the web address happened to be. And the identity of the ticket is buried inside the name, while the column that exists to hold it sits empty.
You cannot count. Every ticket has a different identity, so every line is a different sentence. “How many times did anyone do this kind of thing?” has no answer at all.
And you cannot filter. “Show me everything that happened to this ticket” has no answer either, because the column that would let you ask is empty.
A log you can neither count nor filter is just a pile. It will grow, it will look busy, and it will never once tell you anything.
Names come from a fixed list. The identity of the thing goes in its own column, never inside the name.
The fourth of the five decisions, and the one everything in part five leans on. (The fifth — retention — is in step 15.)
One. If it can be worked out from the river, do not store it. A second copy is not a convenience — it is a second answer.
Two. A copy has no front door. It is only ever replaced by working it out again — never edited, never nudged up by one.
Three. “It cannot be worked out” is a symptom, not a verdict. Usually the river is missing something. Fix the river.
Four. Measurement is not story. The same thing sampled a hundred times belongs somewhere else; the river gets the conclusion.
Five. Only what happened to the person goes in. Ask whether they could have told you it happened themselves. “I registered” — yes. “I arrived at stage 3” — no; nobody arrives anywhere. That line is a conclusion the system drew, wearing an event's clothes.
Rule five is a fence around rule three. Rule three says that if a fact never reached the river, write it — and that is exactly how a stage-arrival log got into a real contact diary. So write the missing fact only if it is a fact about the person; a conclusion the system drew is not a missing fact.
The cost of getting this wrong is not subtle. With those lines included, the thickest thread on that system's funnel map was arrived → left at 33,917 people, and its reverse right behind it at 31,169 — the engine talking to itself, drawn as though it were customer behaviour.
Rule three is the one people skip, and step 41 shows what it looks like to apply it properly.
Fifteen steps. This is the part you came for — and it only works because of everything before it.
Every table answers one question: does a row describe something that is, or something that happened? That gives the only two kinds there are.
The noun — what is. A thing; current; may be replaced. The river — what happened. A verb; ordered; never edited.
Everything else is an archetype: a named, recognisable shape that one of the two takes, carrying rules of its own. A river can take the shape of a branch or a heartbeat. A noun can take the shape of a state, a summary or a lock.
Three of these are one subject, seen three ways. The noun is what a person is — their name, their phone number. The river is what happened to them. The state is where they stand now. And the arrow from step 14 runs straight through the middle: the river produces the state, never the reverse.
The steps below come in a teaching order, so each shape is defined using only what came before. The noun depends on nothing. The river needs a subject, and a subject is a noun. The branch and the heartbeat are both defined by what they are split off from, so the river comes first. The state is worked out from a river and usually lives on a noun. The summary is state's sibling across a time boundary. And the lock is last, because every one of its rules mentions another shape — which is also why it is the one people put in the wrong place.
Two kinds, and they are not expected to grow. Archetypes are open — a new one may be named when one is found. Every archetype carries exactly the rules it always carried; only where the rules live is new.
A thing that exists. A contact, a page, a course, a product. The test: does a row describe something that is, rather than something that happened?
A noun has an identity that survives change. Rename a course and it is still the same course, with the same students and the same history.
The grammar is not decoration, and it pairs: a noun is a thing; every line in a river is a verb. Paid. Registered. Cancelled. If a row reads as a thing, it belongs here; if it reads as something done, it belongs in a river. That test settles most tables before you have looked at a single column.
A noun holds what it is, never what happened to it.
When you find date columns like paused-at, approved-at, rejected-at stacked inside a noun, that is a diary crushed into a single row. Each slot holds only the most recent one. Get rejected twice and the first reason no longer exists anywhere — and nobody will notice until somebody asks.
The same defect has a larger form, and document databases encourage it. An array of events stored inside the parent document is a river living inside a noun. That is recommended practice there when the set is small, bounded and always read together with its parent — and it becomes the crushed diary the moment it grows. The tell never changes: can you count and filter those events without loading every parent? If not, the river needs to come out.
One thing this rule does not say: overwriting is not itself the defect. Someone changes their phone number and you overwrite it — the old one is gone and nobody minds. Warehouse modelling has a name for doing that on purpose (a “type 1” dimension) and treats it as correct where the history has no value. What made the sending-domain case a defect was that nobody decided: the second rejection reason vanished before anyone knew they wanted it. Overwrite on purpose and it is a decision. Overwrite by default and it is a loss you find out about much later.
A record of something that happened. Parts three and four were entirely about this kind, so there is little to add — except two rules that only make sense now.
A line carries two times: when it happened, and when you wrote it down. With only one you can obey “never edit a row” perfectly and still lose the ability to say what you believed last March — because a backdated line added today silently changes the answer to a question about the past, and nothing was edited to do it. Banking, insurance and health records all keep both; the formal name is bitemporal. The test is one sentence: “what did this system believe on the 5th?” If your log can't answer that, it has one time where it needs two.
A line may only be removed if it duplicates something that still exists. Not something that existed once.
That sounds pedantic and is the difference between tidying and destroying. The copy you were deferring to may have been deleted years ago, leaving the line you are about to remove as the only record there is. Step 39 is exactly this, with numbers.
The same water, in a separate channel. The test: is every row a fact that stands alone, about the same subject as the river — and is there simply far too much of it for a timeline?
“Levan viewed /pricing at 14:32” is a complete fact. It passes every test a river line passes. It is not a sample of anything. And there are 188 of them per person, which is why it cannot live in the timeline: one afternoon's browsing would bury a year of somebody's history.
A branch is still the river. Same subject, same kind of content, ordered, never edited. It has been split off for volume and nothing else. The water is the same; only the channel is separate. So you write to a branch directly, exactly the way you write to the main river.
This shape was named on 10 September 2026, and its absence had caused a real misfiling. Page views, section views and content views sat in a production timeline at 188, 106 and 24 rows per person — about 29% of the whole river. Nobody could call them noise, because each row plainly is a fact. There was no other word, so they stayed where they did not belong. A thing with no name does not get moved.
Same subject as its river — split by volume, never by subject. Its rows keep a river's rules. The main river may want a conclusion (“visited the pricing page”), decided on purpose. Its retention is its own decision.
The same thing, measured over and over. The test: does one row mean anything on its own?
“Levan paid £240 on Tuesday” is a complete fact — it stands up alone. “Levan still watching at 14:32:05” tells you almost nothing alone; only the pile of them says “he watched for forty minutes.” A river line is a fact. A heartbeat row is a dot on a graph. That question is better than “is it the same situation sampled over and over”, because you can answer it by reading one row cold, without already understanding the table.
Someone watching a video sends a signal every few seconds saying “still here.” That is not fifty events. It is one event — they watched — sampled fifty times.
Typing indicators are heartbeats. So are background health checks, cost measurements, and anything that pings on a timer.
And here is the thing worth holding on to: a heartbeat is a river. It has a subject, it has a unit, its rows are never edited, they arrive in order. It passes every test for one. Its subject is even the same — take Levan out of “Levan still watching at 14:32:05” and the line collapses, so the contact is the subject, exactly as in the main river. Take the viewing session out and the line still means something, so that was only a mention.
It was pulled out for volume, not subject. Its unit is finer than a timeline wants — which is decision two, not a different river. It is the same river, split off so it does not drown the person's history.
Branch or heartbeat — one question settles it. Does a single row state a fact? Yes, and only the quantity is the problem: a branch. No, only the pile means anything: a heartbeat. A branch is the river's own water in another channel; a heartbeat is a different substance taken out of it.
Which explains a rule that otherwise looks arbitrary. Pulling the detail out is what creates the need for a conclusion. Had the pings stayed in the river, nobody would write a summary line at all — “30% watched to the end” would simply be worked out from them, like any other question. That line exists only because you extracted. So writing it is the debt the extraction incurs, and skipping it destroys the answer quietly.
And one correction to an earlier draft of this booklet, because it mattered. It said a heartbeat must have a lifespan and that deleting is correct rather than a loss. That over-claims. Typing indicators are binned and nobody misses them — but webinar watch data is kept deliberately and forever, because the graph of who watched and when they left is what the analytics is for. Same shape, opposite retention, both right. Retention is decision five: you make it, you don't inherit it.
A heartbeat never enters the river it was split from — that river gets the conclusion, “watched for 40 minutes”, not the samples.
A real system had a log of who did what to the business — the kind of record you open when something has gone wrong and you need to know who touched it. It was being filled automatically with every web request, including typing indicators.
Out of 85,983 lines, 149 were real entries. The rest was noise.
And the expensive part was not the clutter. The whole table was deleted after 90 days, which is right for typing indicators — so the 149 real records inherited a deletion schedule that had been set for noise. An administrator logging into a customer's account is forgotten after three months, because it was filed in a bin sized for something else entirely.
A heartbeat's lifespan is its own. Never share a bin with a river.
Where something stands right now — worked out, not decided. Who is enrolled in what. A person's last email open. Where someone has got to in a course.
It is worked out from a river, it is never written to by hand, and it can always be thrown away and rebuilt. Those three are shared with the summary, and they matter more than anything specific to either one.
Why is it a noun? Because a row describes something that is — “Levan is on lesson four” — not something that happened. Which shows that “noun or river” and “original or derived” are two separate questions. contacts is a noun and an original. A state row is a noun and derived. Asking both questions as one is how second originals get mistaken for caches, and caches for originals.
And it points at a rule that turns out to cover every noun, not just this one: no noun is ever written directly. Every write goes to a river; every noun follows from it. One shape breaks that rule, and step 38 shows which, and why that is what earns it a name.
One widening, because it catches people out: derived data does not only come from a river. A search index, an embedding, a thumbnail, a computed slug — those are worked out from a thing, and they are exactly as deletable and rebuildable as anything worked out from events. Chunks of a document's text, with the AI's numeric representation of each, are textbook state: edit the document and every chunk is instantly wrong and must be regenerated. Always current, always replaceable.
Can a thing have only one of these? Then it is a column on the noun. A person has one “last email opened”, the way they have one phone number. Same shelf, no separate table.
Can it have many? Then it needs its own table. Someone enrolled in five courses is at a different point in each, and there is no way to write five answers in one column.
Which makes the separate table the exception. Normally a noun carries its own state, as columns — and every rule here still applies to those columns. They just live on the noun rather than beside it.
State arrives two ways, and the difference tells you exactly where to go looking for trouble.
Produced state is safe by nature. Nobody decided “Levan is on lesson four” — finishing lesson three put him there. The events exist whether anyone planned for them or not, so the answer can always be rebuilt.
Chosen state has no such guarantee. Somebody picked 500 from a dropdown. Unless that choice was deliberately written down, nothing recorded it, and the previous value is simply gone. Ask “who changed this, and when?” and there is no answer — not a hard one to find. None at all.
Chosen state is where the log tends to be missing. Produced state is where it cannot be.
Settings are read constantly, and when someone changes one they must see it change immediately. So state cannot wait to be worked out later — it has to be written at the moment of the change. Which raises the obvious worry: haven't we just rebuilt the whiteboard?
No, and the difference is small enough to miss.
Writing both means the change goes to the log and to the settings, side by side. Two originals — and if one succeeds while the other fails, they disagree with no way to tell which is right.
Writing one and letting the other follow means the change goes to the log, and the settings row is updated as a consequence of that, in the same indivisible action. Both are instant. Only the second can ever be repaired.
And there is a third shape, which is also fine. Write the log, and update the copy a moment later in a separate step. That is still one original — the copy is only ever written from the log; it just arrives a second late. It is what most systems do for anything nobody is staring at: a dashboard count, a search index, a nightly report.
So what separates the second shape from the third is not the mechanism. It is who is waiting. Someone setting their send cap to 500 is looking at the screen, so the copy follows in the same breath. Nobody is watching a dashboard in that same second, so a moment later is fine and cheaper. Only writing them side by side is ever wrong.
A copy may be written in the same breath as the log. It may never be written beside it. Consequence, not copy.
Picture a football scoreboard. During the match it reads 2–1. True right now, and about to be replaced — someone scores and it becomes 2–2. That is state: one answer, always current, always replaceable.
Then the referee blows the whistle at 3–1. The number has not changed, but something about it has: it can never be replaced again. That match ended 3–1, forever.
That is a summary — what happened during a period that has finished. Views per page per day. Revenue per month. Once the day is over, that row is final.
State and summary are not a list somebody collected and hoped was complete. They are exhaustive, and the argument is short.
Anything worked out from a river describes some moment. A moment is either finished or it is not. There is no third kind of moment. Not finished means the answer can still change — state. Finished means it is fixed forever — summary.
And the case that proves the line is real: “views this month, so far.” It looks like a summary, but the month is not over, so the number still moves. It is state. The instant the month closes, the same row becomes a summary and will never change again. Same data, different shape, because time passed.
“Not finished” also covers moments that have not started. A forecast — projected revenue for next month — is state, because it will be replaced as data arrives. So describing state as “about now” is narrower than the rule really is. The line is closed or not closed, and the future is not closed.
A summary can only be rebuilt while the river it came from still exists. If the log is deleted after 30 days, then any summary older than 30 days is permanent rather than derived — nothing can recompute it.
Which turns a storage decision into a correctness one: shortening how long you keep a log silently freezes every summary built from it. Worth asking before anyone shortens a retention window.
You send a campaign to 10,000 people. Halfway through, the server restarts. The job wakes up and starts again, not knowing it already emailed 5,000 people. Those 5,000 get it twice.
The obvious fix is to check first — before emailing someone, look whether you already did. That works with one worker. Most systems run several, for speed. So two of them reach the same person at the same instant, both ask “emailed yet?”, both are told no, and both send.
Checking cannot work, because there is always a gap between asking and acting. No amount of care closes it.
Instead of asking a question, you make the database enforce a rule: for this campaign, each person may appear exactly once.
Now the workers do not ask anything. They each try to write a row. The first is accepted; the second is refused. The refused worker learns from the refusal that somebody beat them to it, and skips.
There is no gap any more, because asking and claiming have become the same single action.
A row you write in order to find out whether you are allowed to. Its existence is the point — not its contents.
And the name is not borrowed from software. A lock on a river is a real thing: a chamber with gates at both ends that lets one vessel through at a time, then closes behind it. That is exactly this shape's job — admit one claim, refuse the second.
“Two people cannot share an email address” looks like a lock and is not. That is identity: a statement about what a contact is, permanent, part of its definition. It belongs on the noun, and moving it elsewhere would break it. A lock is about what may happen, and it is released when the situation ends.
And a lock must never sit on a river, or on anything derived. Rivers get pruned — and the water word is the truer one, because a river loses its old water at the far end continuously, and nobody decides to keep the water from last March. Logs are the same: old lines deleted to save space, or because keeping personal data forever is a liability. Derived tables get rebuilt, which means deleted first.
Both of those are correct, responsible decisions. And both are decisions about records. Nobody in that conversation is thinking about guarantees — so a lock sitting there dies of somebody else's storage judgement, and nothing announces it.
A lock lives alone and carries nothing. It never sits on a river or a derived table.
And this is the rule the lock breaks. Step 30 said no noun is ever written directly. A lock has to refuse, and refusing happens at the moment of writing — while anything derived arrives after the thing has already happened, so it can never refuse anything. So a lock is claimed by a direct write: the one noun-shaped thing that is written rather than worked out. A shape is worth naming exactly when it breaks a rule the others keep, and this is the one that does. (Put the uniqueness rule on a river instead and the refusing write is a write to a river — the same fact seen from the other end.)
One honest note about this rule. A rebuild that locks out everything the guarantee protects leaves no gap, and a careful rebuild that spares the lock rows avoids it too. So “a lock never sits on a rebuildable table” is not a statement about what is impossible — it is a statement about what decays. Both careful versions work, and both fail silently the first time somebody empties the table the ordinary way: no error, no failing test, double-booking back with nothing to announce it. That is reason enough for the rule. It is just a different kind of reason, and worth knowing which one you are relying on.
When one thing is both — “we scheduled a call” genuinely happened, and must not be scheduled twice — that is two rows in two tables. The fact goes in the river where it stays. The guarantee goes somewhere nothing will prune by accident.
Four steps. One worked example that nearly went badly, and then the questions that rivering actually consists of.
An automation puts people through a sequence of emails. Two lines go in the person's log — entered and exited. There are 41,765 of them. Separately, a table records who is currently enrolled.
Same fact in two places, so by rule one, one has to go. All 41,765 log lines were marked for deletion. Everyone involved was applying the rules correctly.
Then somebody checked. 35,541 of them — 85% — had no enrolment record left. The automations had been deleted years earlier and took their enrolment rows with them. So for most of these lines there was nothing left to be a copy of. The arrow had been drawn backwards: the log line was the original all along.
“Entered on Tuesday” is an event. “Is currently enrolled” is a state. The events are the original, always — and that was never a judgement call.
What looked like one simple table — a list of who is in which automation — was quietly doing three separate jobs.
One: who is currently enrolled. Entirely derivable — everyone who entered and has not exited. That is state.
Two: which step of the sequence they are on. Not derivable, because nothing writes a line when someone moves from step three to step four.
Three: a lock, stopping one person being enrolled twice by the same trigger.
Before you simplify a table, count its jobs. Seeing only the first and saying “this is derivable, we can rebuild it” would have silently destroyed the other two.
And there is a procedure for it — it is not a matter of noticing. List three things. Every write site: two write paths that never overlap are two jobs until proven otherwise. Every read site, and what it filters on: a query filtering on a column most rows leave empty is reading a different job from the one that fills it. Every constraint, unique indexes especially — a uniqueness rule is always a job, and it is the one most often invisible in the code. The enrolment table gave three writers, two readers filtering on different columns, and one unique index: exactly the three jobs it had.
And that is the practical value of the kinds and their shapes: not that they label things, but that they make you look for the jobs you were about to miss.
“Which step are they on” genuinely cannot be worked out from the river. The tempting conclusion is so we must keep this table as an original.
Rule three says that is the wrong conclusion. It is a symptom, not a verdict — and the question to ask is why is the river missing this? The answer turned out to be unremarkable: nothing records a step move. Not impossible. Just never done.
So the fix is to start recording step moves, after which job two becomes derivable like job one. And job three — the lock — has to move to its own table first, because until it does, the table cannot safely be rebuilt at all.
“It can't be derived” is almost never a fact about the world. It is a fact about what you chose to write down.
This is the whole method reduced to questions. Run them on a table and it is rivered — you know what it is, what may be done to it, and what would break.
Name the database you are rivering. A copy of somebody else's original is derived and external, whatever shape it has.
A kind is not a property of a thing in the world — it is a property of a table in a system. The same customer is a noun in the operational database and derived state in the warehouse that copies it. So a warehouse dimension carrying its own valid-from and valid-to dates is not a noun breaking the no-history rule; it was never a noun here. Same for a read replica, a search index, a cache. Settle that first, or you will apply a noun's rules to something that is really a mirror.
To find its kind and its shape, in this order — the first “yes” answers it, with one exception at the top.
One. Does a row describe something that happened? A river — but do not stop here. Go on to question two. A heartbeat answers yes at question one correctly, because it really is a river; question one was never wrong about it, only incomplete and then halting.
Two. Is it the same situation repeated — does one row mean nothing on its own? A heartbeat — a river's shape.
No — but every row is a fact, and there are far too many per subject for a timeline? A branch. That is a person's call, not a mechanical one: “too many” is a judgement about the subject's timeline, not something the schema can say.
Three. Would deleting a row cause something to happen twice? A lock — a noun's shape. A second thing is identity and belongs on the noun; a second action is a lock.
Four. Can you name the line that would have to be written? Not “should this be derivable” — that is a question about ambition, and almost anything could be derived if you were willing to write a log for it. Name the actual line. For a table holding a user and a team you can: “added to team”. For a person's surname you cannot, because no event produces it — it simply is. If you can name it, it is derived: then ask whether that line gets written, and the rebuild exists, today. If not, this is derived with a gap, and you have already named the repair. If you cannot name any line, ask why not: when the source of truth is outside this database — another company, your own seed data, or the physical world — mark it external and carry on.
Five. Has the moment finished? Finished is a summary; still moving is state. Both are shapes of the noun.
Six. Is it a thing in its own right, with an identity of its own? A plain noun. A pairing of two other things is not.
And if the answer is no every time, the table is unrivered. That is not a third kind, not a shape, and not a category of table at all — it is a fact about the work. Nobody has rivered it yet. It goes on the to-do list, never into the register as an answer. A kind is what a table is, and every table has one; a table is either rivered or waiting for someone. Give it a name like “undecided” and you have invented a resting place. Unrivered says out loud that work is owed.
It asked could you rebuild this — and when the log was missing it answered no and walked on, which is exactly what rule three forbids. The first repair asked should you be able to, and that was worse: a question you can steer by how ambitious you feel is not a question. So it points instead. Name the line.
(That order is deliberately different from the one the shapes were taught in. Teaching needs each idea to rest on the last. Identifying wants the sharpest question first, so most tables are settled in a step or two.)
Before deleting or rebuilding anything, two more. Does anything rely on this row merely existing? And what points at this row — not what it points at?
And two that catch almost everything else. If this row stopped existing, what would break — not who reads it, but what stops working? And if this cannot be worked out from a river, why not?
If that list reads as obvious now, that is the point. It was not obvious at step one, and it is not obvious to a codebase that grew without it.
external — the source of truth is outside this database: another company's system, your own seed data, or the physical world. Classify by shape as normal, but rebuild becomes refetch, and a refetch is not guaranteed to agree with what you had.
sealed — a summary frozen by an act outside the database: a signature, an audit, a filing. Once an accounting period closes its figures may not be recomputed even though they still could be; a correction is posted as an adjusting entry in the next period. A sealed summary has stopped being derived and become an original.
Both attach to kinds and shapes that already exist, and each changes exactly one rule. If you want a third, check first that it isn't a shape you have mislabelled.
Five of them do the same thing — give every table a name from a short fixed list. Kimball (fact, dimension), Data Vault (hub, link, satellite), Anchor Modeling (anchor, attribute, tie, knot), event sourcing (event store, read model), and the business split of master / transaction / reference data. This booklet is the sixth attempt, not the first.
That two of them independently reinvented the noun's first rule is the good news. Data Vault's satellite and Anchor's attribute table both exist to keep a thing's history out of the thing itself — reached by people solving audit trails and schema changes, not this. A rule three unrelated groups land on separately is doing real work.
What is different here is the job. All five tell you how to build a new warehouse. This tells you what you already have, in the database your app actually writes to. And none of them has a heartbeat, a lock as a named shape rather than a constraint, a unit rule, or the habit of treating “it can't be derived” as a symptom.
Where they are better, and what to do about it. Anchor has formal proofs — the answer is not to chase proofs but to make every rule checkable by someone who is not an expert. Anchor handles time by construction — borrow that, and put “two times” into the subject decision rather than leaving it a rule to remember. Data Vault has schema generators — ours diagnoses instead, which is a different and better-suited fight. And on query performance this method says nothing at all, deliberately: use a dimensional model for that. Refusing the fourth is what makes the first three honest.
The nearest competitor is not any of them. It is the folk list from beginner tutorials — entity, lookup, junction, transaction, audit/history. It fits neatly inside these two kinds and their shapes, and is missing heartbeat, summary and lock entirely. The deeper difference: that list sorts tables by shape (“a junction table has two foreign keys”), and these sort them by job. A shape carries no rules — which is exactly why nothing hangs off theirs.
Two kinds is a claim, and claims need a denominator. The list came from — and was stress-tested against — one relational database of 270 tables, in one company, built by one team's habits.
It has since been run against seven domains nobody involved designed: double-entry accounting (which produced the sealed mark), perpetual inventory (which confirmed it — a physical count is an event, so you write a line rather than editing the total), bitemporal records in banking, insurance and health (which produced the two-times rule), time-series rollups (which confirmed that a summary outliving its log must be a decision), graph databases — independent confirmation, since Neo4j's own guidance splits “a thing that exists independently” from “an event between two things”, which is noun and river in other words — warehouse star schemas (which produced the scoping rule in step 42), and document stores (where the idea holds but the machinery degrades: nothing enforces a reference, and events can be embedded inside their parent). None of them needed a third kind.
The shapes did grow, once, and on evidence. An earlier version of this booklet listed six kinds side by side. On 10 September 2026 a production river was found carrying 317,000 rows of page views, section views and content views — facts, not samples — that fitted none of them, so they had never been moved. That produced the branch. It also showed the list had been arguing with itself: it said a heartbeat is a river and then listed it separately. Two kinds with open shapes settled both, and no rule changed.
What that does not license: it has not been run against anything shaped for a regulator rather than an application, nor against a system where the same fact lives in two stores at once. The honest sentence is that two kinds held across one relational system of 270 tables and seven outside domains; the shapes are open and grew once, with the branch; and everything else that did not fit became a mark, a rule, or a scoping question — never a kind. What would refute it is a third kind: a settled table that is neither something that happened nor something that is.
The reference sheet holds the same material arranged for looking things up rather than reading through: every kind and shape with its own rules, the diagrams, the checklists, and what rivering found when it was run against a real production database of 262 tables, measured on 5 September 2026.
And two more, for anyone who wants the arguments checked rather than explained: What Rivering Proves — five of these ideas stated as propositions with proofs, and the rest labelled honestly as empirical, heuristic or preference — and The Falsification Record, which is what happened when the claims were actually tested against 270 real tables. Neither is a place to start, and neither is written for the reader of this booklet. The proofs earned their keep by changing four things in the other documents.
These four are meant to stay in step. If a rule changes in one, it changes in all of them — a booklet that teaches something the reference no longer says is worse than no booklet.