On trying to mash up SQLite with ideas stolen from Accountants, Clojure, Datomic, XTDB, Rama, and Local-first-ers, to satisfy Henderson's Tenth Law. Viz., to make a sufficiently complicated data system containing an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a bitemporal database. Because? Because laying about on a hammock, contemplating hopelessly complected objects like Current Databases isn't just for the Rich man. Don't try this at work! The "Poor Man's Bitemporal Database", in the safety of my local box. No servers were harmed. Yet. Especially fellow Clojurians trying to realise their Indie B2B SaaS dreams (translation: income and time-poor). Please use a proper professional time-oriented data system. The following are (pithy descriptions mine); and they are available gratis for fledgling commercial use. Datomic… "the DB as a value" over an immutable log of all facts. XTDB… "the DB as a value" over an immutable log of all bitemporal facts. Rama… "any DB as dirt-cheap view" over an immutable log of all events. Reading Guide / Thinky Thoughts Alert (same thing) Solitary over-caffeinated temporal database rumination went out of hand. Even The Voices are fed up and want someone to stop us. Furthermore; Sage friends already gently shook their heads after hearing The Voices. Their hard-won advice—"Just Use Postgres.", and "Please, for everyone's sake, stick with the relational models."—fell on deaf ears. Obviously, I am also incapable of following my own advice. Hence this post. Take what is useful, discard the rest… The key take-away is: the accountants were right all along. Software engineers will do well, to cleverly copy the accountants . Now you may… Or, grab a big beverage to help ingest the ten thousand tokens to follow… Unless you are a Large Language Model. You can't drink. Sucks to be you. But beware. Once you see, you cannot un-see the fact that… Any sufficiently complicated data system contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a bitemporal database. — Henderson's Tenth Law. Factual and Temporal World-Building Recommended reading (ages 10 to 1,000) for the aspiring temporal data engineer. Accountants are our exemplary archetype The cashier at Temporal Convenience Store K9, just handed us our bill. Oi; where is that 10% discount applicable to our bulk purchase of provisions as loyal customers (it's going to be a long trip)?! Now we think that, but we ask politely, because we know there are many civil ways to sort this snafu without shoplifting or violence. Two universally accepted remedies are: The cashier has direct authority to fix it, and they may gladly oblige. The cashier's hands are sadly tied. For ERP reasons, accounts alone has authority to issue refunds for bills over a certain value. But we asked nicely so the cashier kindly nods us to accounts, in the backroom. Odds are that the store people will fix it by issuing two new transactions. One transaction to cancel the last bill and reverse the related charge to our spacecard. Another transaction issuing the corrected bill, including the discounted amount, with a fresh charge made to our spacecard. Meanwhile, Temporal Convenience Store K9's various ledgers have received corresponding debits and credits too, of course. But enough. A programmer, though Poor, is no Fool. One does not simply trespass The Field of Accountants. There be dragons. So… Back to the DB. One way or another, the store's accounting database must tell these facts: At TxTime-7543, Cashier-Adric at Store-K9 ISSUED bill ID-13579 having value 100 spacecoin, and charged it to SpaceCard-1337. At TxTime-7587, Cashier-Adric at Store-K9 REVERSED bill ID-13579 having value 100 spacecoin, and refunded it to SpaceCard-1337. Maaaybe a note about why it was reversed. At TxTime-7715, Accounts-Nyssa at Store-K9 ISSUED bill ID-13579-v2 for 90 spacecoin, with a total value of 100 spacecoin minus 10 spacecoin going to discount, and charged 90 spacecoin to SpaceCard-1337. We call this a temporal data system because it incorporates the passage of time. No information is ever modified in-place or deleted. New information is always appended. To grok the latest state of the accounts, one must read the sequence of all facts recorded in the database. Reading a fact updates a separate, current view of the accounts… our "as of now" understanding of the world. The "current view" can be rebuilt from scratch, up to any point in time, whether it is "as of now", or "as of last week", or "as of next quarter" (which will be useful only if we add synthetic projected-future events into the database). So… What to think about in order to design a general-purpose temporal data system that does this for us? All databases record state of entities People, things, processes etc. State is the discrete value of some attribute of an entity at a specific point in time. Values are timeless and context free (17) . are timeless and context free . Attributes provide context ('age') , which we use to suggest and interpret the meaning of a value (= age 17) . provide context , which we use to suggest and interpret the meaning of a value . Entities are real or imaginary objects ( Adric ) having attributes ( age ). Thus, the State of Adric can be stated as: Adric's age is 17 as of now. In a current database—which is just a fancy way of saying database—the as of now is implicit. So is the concept of " age is an attribute of the entity Adric ". We just call it Schema, in the abstract. entity age Adric 17 Let's re-state our traditional table as Entity-Attribute-Value (EAV) triplets. Let's also add a column for time (as we often do) to answer questions like "when was Adric's age last updated in our database?". entity attribute value time Adric age 17 as-of-date-time From this kernel shall spring forth our world, wrought of facts and time itself. But first, one must acknowledge that… All the world’s a stage, And all the men and women merely players; They have their exits and their entrances, And one man in his time plays many parts, His acts being seven ages. — William Shakespeare, As You Like It, Act-II, Scene-VII, Lines 139-143 As my theater gentlefriends like to say… Everything is Process We understand the world in terms of processes. All of Reality is a live process which we want to participate in—control, influence, react, adapt. Ergo, all information is part of some process. Yes, even universal constants like c and π , which we can confidently assume to be constant only in our observable universe. Because even these came to be after the moment of the big bang, and will remain only until the eventual heat death of the universe (assuming our universe is ever-expanding, and not a bouncing singularity). It follows that, to understand the world, we must observe and respond to data; information about various attributes of various meaningful aspects of reality, as we perceive it. Said another way, we understand the world by observing and modifying the state of entities over time—the past, the now, and the later. A person's address, a valve's current position, the remaining free volume of a container, the trajectory of a comet, one's fast-emptying savings account. entity attribute value time Adric age 17 as-of-date-time Adric address Foo as-of-date-time Adric bitemporal belief 1 as-of-date-time The more sophisticated a being is, the more context about entities and entity-relationships it is able to keep alive and/or use simultaneously . The identity of an entity is the complete life it lives Never-ending process is the beating heart, the whistling wind, the pulsing quasar, the furious procreation, the tectonic Subduction, the whispered good-bye, the thermodynamic survival instinct of all things. Process is the why of being. One could even say that an entity without id can have no identity. This is why, to properly identify an entity, we must egolessly maintain an up-to-date mental-model about it. For that, we must continually observe, record, and aggregate a succession of states of the entity in question. Consequently, knowledge of entity-attributes alone is not sufficient (Adric has age, address, belief). Knowledge of attribute-values is required too (age is x, address is y, belief is z). And without a sense of time, we simply cannot complete the picture. To make it concrete: Every person's life revolves around their address and we can guess different things about them based on how their address changes. You know which Adric is being spoken about because you know Adric's age was 17 last year. Adric's age is 18 as of now. Adric's age will be 319 on . Adric's address was Foo last year. Adric's address is Baz as of now. Adric's address will be Bar after December 2025. Adric's belief in bitemporality was 1% last year. Adric's belief in bitemporality is 99% as of now. Adric's temporal innocence level was 99% last year. Adric's temporal innocence level is 1% as of now. A reader of this set of facts can confidently determine: As-of-now, Adric is an eighteen year old entity that lives at 'Baz', believes strongly in bitemporality, and has nearly no temporal innocence. E A V as-of-time Adric {:age [:time :years]} 17 date-last-year Adric {:age [:time :years]} 18 date-now Adric {:age [:time :years]} 319 date-future Adric {:address [:text :string]} Foo date-last-year Adric {:address [:text :string]} Baz date-now Adric {:address [:text :string]} Bar date-future Adric {:belief [:bitemporality :%]} 1 date-last-year Adric {:belief [:bitemporality :%]} 99 date-now Adric {:innocence [:temporal :%]} 99 date-last-year Adric {:innocence [:temporal :%]} 1 date-now KEY: E(ntity), A(ttribute), V(alue) Having gained this factual understanding, a dear reader may be tempted to further theorise; Adric lost his temporal innocence and eventually ended up living at 'Bar', where he always is these days. Of course, to prove such an allegation, the dear reader would have to piece together many more facts about Adric, and show causation, not mere correlation. The dear reader may happily play temporal sleuth. However, the temporal database and temporal data engineer are not here to judge. Our role is simply to record the facts as presented, without ego, without prejudice, with integrity, so that the temporal data sleuth may use it productively to figure out what happened, when, and why. For there is more to facts than meets the eye. "I'm not in the judgment business, Mr. Orr. I'm after facts. And the events of the mind, believe me, to me are facts. When you see another man's dream as he dreams it recorded in black and white on the electroencephalograph, as I've done ten thousand times, you don't speak of dreams as 'unreal.' They exist; they are events; they leave a mark behind them." — Dr. William Haber The Lathe of Heaven, Ursula K. Le Guin. A fact can be true or false The temporal sleuth knows that one must resolve the reality of a fact by asserting whether it is true or false. Our facts table can be expressed as something like the table below. Aspiring temporal data engineers will do well to avoid speculating why a fact might have been asserted true or false. Our ilk must simply realise that we can assert facts this way; is as of