Case 63/0: the bitemporal store written in a single time

A store with two columns of time, written for two months using one. Sixty-three days of drift, zero reconciliations, and a validator that answered "0 findings" the whole way through — truthfully

Jesús Pérez
The bitemporal model was built, tested and declared in an accepted ADR: retraction, both times, facts superseded and not overwritten. The code feeding it emitted not one retraction, left the second time empty every pass, and restarted the clock each run, filing July underneath May. They coexisted for months in the same repository, contradicting each other, and nothing had the authority to read them in the same sentence. There was no symptom: graph validation answered 61 nodes, 175 edges, 0 findings — and it was true, because it measures something else. The first real reconciliation uncovered 426 pending retractions.
Case 63/0: the bitemporal store written in a single time

🩺 Show the full clinical history → 📋 Session protocol →

Clinical record · Department of Software Pathology

A store built to keep two times for every fact — when it was recorded, and since when it is true — written for two months using one. The result is not half a store: it is a file that grows. Sixty-three days between the state it held and the real graph. Five commits changed the graph in that window and none of them reached it. Zero retractions emitted in the entire prior history, because the code did not have that word. And the clock that orders the operations started at 1 on every pass, so July was filed underneath May. None of this tripped anything. validate ontology answered 61 nodes, 175 edges, 0 findings — and it was telling the truth, because the question it asks is a different one.

History No. 63/0Diagnosis: ANTI-PAP · THE BITEMPORAL STORE WRITTEN IN A SINGLE TIMEStatus: RESOLVED · WITH DECLARED DEBT
"Everything is green." And it was. No check lied, no figure was inflated, no warning was ignored: `validate ontology` answered 61 nodes, 175 edges, 0 findings, and all three were true. The store was growing on disk. The file was versioned. Nothing in the clinical picture suggested the system had spent two months writing the present on top of itself. The patient did not complain because the ailment consisted, exactly, in not being able to remember.
Show clinical picture
bitemporal
A store that keeps two times for every fact: when it was recorded (transaction time) and since when it is true in the world (valid time). It buys exactly one thing, and it is enormous: being able to ask "what did the system believe on Tuesday?" without today's answer having erased it.
supersession
Replacing a fact without destroying the previous one: the new fact stacks on top and the old one stays reachable by its date. It is the opposite of overwriting. A store that supersedes accumulates history; one that overwrites produces a present that gets lonelier every pass.
retraction
The operation that declares a fact no longer holds. It does not delete it: it closes it. Without it a store only knows how to add, so what disappears from the source does not disappear from the projection — it merely stops being mentioned, and nobody can tell "it was withdrawn" from "it was not said again".
logical clock
The ordering stamp every operation carries. Its single obligation is never to go backwards. A clock that starts at 1 on every pass satisfies the type, satisfies the compiler, orders consistently within its pass — and files July underneath May without complaining.

The protocol to declare, version and verify this → ontoref.dev

The double ledger — what it cost, and what it left

The figures on the left are the picture; those on the right, the discharge. But the one that decides the case is in neither column, it is between them: throughout the sixty-three days, validation answered zero findings. It is the exact measure of the distance between measuring structure and measuring freshness, and that distance had been, until today, invisible by construction — because neither question knew the other existed.

What the crime cost

  • Days between the state the projection held and the real graph 63
  • Reconciliations in that window 0
  • Commits that changed the graph in that window, unnoticed by the projection 5
  • Age of the file the day it entered git 45 days stale
  • The only commit to the store in the whole window fef21f4 · "init repo" · 2026-07-10
  • Retraction operations emitted in the entire prior history 0
  • Value of the clock at the start of EVERY extraction pass 1
  • What validation answered while all of this was happening 61 nodes · 175 edges · 0 findings
  • Cells asserted in the first real reconciliation 1097
  • Cells retracted in that same pass 426
  • Entities withdrawn 91
  • The store, before and after 3,686,400 → 4,747,264 bytes
  • Distinct graph identities in one afternoon 79cf7a96… → 60702123… → aa7967c8…

What the case left behind

  • The rule a bitemporal store written in a single time is not half a store: it is a large file
  • The extraction reconciles — asserts, retracts and withdraws; a pass with no changes appends nothing
  • The clock anchored to wall time · one pass is ONE instant
  • The gate a commit that touches the graph without reconciling is refused — and it only reads
  • The corpus 13 tests in extract.rs · 3 go red if the clock is returned to 1
  • The fix, measured 740+ / 78− across 7 files · 142+ / 21− in the clock alone
  • The practice, declared substrate-reconciliation, queryable in the ontology
  • The debt, written down nothing yet binds the extractor to the contract of ADR-041

The point of abandonment — what the table doesn't show

The point of abandonment came early, and it was the initial diagnosis: “ontoref has no edge supersession”. It was a reasonable answer, and it was false. The comfortable exit was right there — build the supersession that already existed, on top of the one that already existed — and it would have produced a second mechanism running in parallel with the first, each with its own shape, neither aware of the other. That is: this case file’s own defect, committed by hand while investigating it. What prevented it was not prudence: it was opening the file before writing anything, and finding tx_time, valid_from and as_of in the first six lines.

Differential diagnosis — what was ruled out

"ontoref has no edge supersession"“"ontoref has no edge supersession." False, and ruled out by opening the file: `tx_time`, `valid_from` and `as_of` had been there for months. The hypothesis was reasonable for a reason that is itself the case's second finding — the capability was built and never documented, so the only way to know it existed was to read the code of whoever wrote it. An initial misdiagnosis about one's own capabilities is not carelessness: it is the predictable consequence of not publishing them.”ruled out — but it exposes an unpublished capability
"re-extracting would fail: the clocks would not be monotonic"“"Re-extracting would fail: the clocks would not be monotonic." It sounds like a serious blocker, and it is ruled out by reading `OpLog::append`: the monotonicity check looks at the operation's parents, and the extraction emitted them empty. The check existed, it was correct, and it never fired. A guard watching a door nobody uses.”ruled out — a guard with no door
"the gate has to re-extract in order to know"“"The gate will have to re-extract in order to know whether there is drift." Ruled out by looking at the cost: the store lives in git, weighs roughly a megabyte per reconciliation, and rewriting a versioned file mid-commit forces the index to be redone. The gate that runs today does not write: it only reads. That constraint was not imposed by taste, it was imposed by arithmetic.”ruled out — by arithmetic, not by taste
"a dry run does not append, so it does not touch the file"“"A dry run does not append, so the file is untouched." **Ruled out by measuring bytes**, and it is the best of the group: the file changed anyway. `OpLog::open` uses `Database::create`, which commits a write transaction just by opening. The defect is not the point; what almost happened is. A test that counted operations would have passed green over a gate that dirtied the tree on every run. It was caught because the artifact was measured, not the intention.”ruled out by measuring — and there the trap showed
"the contract is in an accepted ADR, therefore the code obeys it"“"The contract is in an accepted ADR, therefore the code obeys it." GUILTY, and it is nobody's. ADR-041 declares the bitemporal model in so many words: retraction, both times, facts superseded and not overwritten. It is accepted. `extract.rs` did not emit a single retraction, passed the second time empty every time, and restarted the clock on every pass. The two statements coexisted for months in the same repository, contradicting each other, and nothing ever put them in the same sentence. An accepted ADR reads as a description of the system. It is a description of the intention.”GUILTY — and nobody's: it belongs to the gap between the ADR and the code

Etiology — the cause — extract.rs — the second time, always empty; the clock, always at 1

// extract.rs — before

let mut next_ts = 1u64;          // ← on EVERY pass. May and July, both at 1.

for node in nodes {
    let op = build_signed_op(…, Hlc::new(next_ts, 0));
    log.append(&op)?;            // ← append. Only append. Always append.
    next_ts += 1;
}

fn build_signed_op(…) -> Operation {
    OpBody {
        parents: vec![],                 // ← no parents: the monotonicity
        payload: OpPayload::EntityAssert {   //   check never fires
            attrs,
            entity: EntityId::new(entity_id),
            valid_from: None,            // ← the second time, always empty
        },
        timestamp,
    }
}

// And in the whole file, not once: OpPayload::Retract

It is worth seeing the whole shape, because it is the one that repeats: the contract describing all of this correctly — ADR-041, with retraction, both times and supersession — was written, accepted and in force. It was not ignored under pressure, and it was not disputed. Simply, nothing in the system had the authority to compare the ADR’s text against the body of extract.rs, so the contradiction was not a fault anyone could see: it was a fact about two files that were never read together.

Treatment — Reconcile instead of overwriting, anchor the clock to the world, and put up a gate that only reads

# 1. Reconcile: read what is already there, emit ONLY the difference.

  1097 cells asserted · 426 retracted · 91 entities withdrawn
  # a re-extraction with no changes appends 0 operations. That is a test.

# 2. Anchor the clock to the world. A pass is ONE instant, not a count.

  Hlc::new(wall_clock_millis, seq)   # and a later pass sorts above an
                                     # earlier one BY DATE

# 3. The gate. Touch the graph without reconciling and the commit does not exist.

  substrate-freshness · `sync substrate --check` · READ-only

# 4. What the gate does NOT check, written into the case file itself:
#    freshness ≠ bitemporal conformance. An extractor rewritten
#    monotemporally tomorrow would pass green.

The confession belongs in the case file because it is the evidence, not the decoration: the new gate checks freshness, not bitemporal conformance. It asks “was it reconciled?” and not “was it reconciled emitting retractions and preserving superseded facts?”. An extractor rewritten monotemporally tomorrow would pass check-substrate green — that is, this very case, again, on top of its own cure. And there is one boundary that can no longer be repaired: operations predating the anchoring keep their sequence numbers. They are reachable by position and not by date, permanently, and that is visible in the data rather than glossed over.

Prognosis

The series asks: with ontoref, would it not have gone like this? And this case forces the uncomfortable answer, because the mechanism was not missing: it was built, tested and accepted. The bitemporal model existed with all its pieces — both times, retraction, supersession — and it was declared in an accepted ADR anyone could read. The code feeding it used none of those pieces. And the two things coexisted for months in the same repository without ever touching, because nothing in the system had the authority to read an ADR and a source file in the same sentence. What makes the case worth keeping — what separates it from a plumbing fault — is that at no point did anything look broken. Graph validation answered zero findings, and that was true: it measures structural integrity, not projection freshness. The store grew on disk, which reads exactly like health. The file was versioned, which reads like custody. The day it entered the repository it was forty-five days stale and the commit message said “init repo”, which is what you write when something begins, not when something has been stalled for six weeks. Every available signal pointed at a healthy system. The disease consisted precisely in leaving no trace: a store that overwrites accumulates no evidence of having overwritten. And so the finding is not the defect but the belief that sustained it — that a declared, accepted contract describes the system. It does not. It describes the intention, and the distance between the two only becomes visible when somebody builds the mechanism that forces them to agree. Here that mechanism does not exist yet: the gate that went up measures freshness, not conformance. It is written above, in the debt, with the address of what would settle it.

Code contradicting a declared, accepted contractreview-against-invariant. ADR-041 declares the model in full: Retract, tx_time versus valid_from, facts superseded and not overwritten. It is accepted. Its constraints do not reach extract.rs, so contract and code coexisted for months saying opposite things and nothing ever put them in the same sentence.
A projection 63 days behind its sourcedeclared state. No declared state said "the projection is fresh", so the question did not exist either. What a system does not declare it cannot query, and what it cannot query it counts as good.
A commit importing a stale file without saying sogate. Today substrate-freshness refuses on the spot. On 10 July nothing was looking, so a file 45 days stale entered the repository under the message "init repo" and read as an initialisation.
A capability built and never documentedqa/howto. as_of had been working for months in the store and there was no way to know without reading the code, so the session's first diagnosis was "this does not exist". A capability only its author knows about is not the project's capability: it is the author's.
A gate that checks the thing adjacent to what it promisesDeclare it. The new gate measures freshness, not conformance, and that is written into this case file's own lesson_debt. A gate that does not declare its reach gets read by its name, and its name always promises more than it looks at.

Prophylaxis — what enforces the lesson today

  • Extraction reconciles instead of overwriting: it emits Retract, preserves superseded facts, and a re-extraction with no changes appends nothingtest:code/crates/ontoref-ontology-content/src/extract.rs
  • A commit that touches the graph without reconciling the substrate is refused before it existsgate:just check-substrate
  • …and in the chain, not as a habit: the gate runs on every commit and only ever READSgate:.pre-commit-config.yaml#substrate-freshness
  • An extraction run is ONE instant of the wall clock, and a later run sorts above an earlier one by datetest:code/crates/ontoref-ontology-content/src/extract.rs#a_run_is_stamped_with_the_wall_clock_and_is_one_instant
  • Substrate reconciliation is a declared practice of the project, queryable, not a habit of whoever remembersontology:.ontoref/ontology/core.ncl#substrate-reconciliation

Declared debt: No typed constraint binds extract.rs to the contract ADR-041 declares. The new gate checks FRESHNESS — was it reconciled? — and not BITEMPORAL CONFORMANCE — was it reconciled emitting Retract and preserving superseded facts? An extractor rewritten monotemporally tomorrow would pass check-substrate green, which is this exact case repeated on top of its own cure. What would settle it: a constraint in ADR-041 whose oracle is the test corpus this session left behind, and the entity-identity decision (edge:{from}:{to}) that was never taken. And there is a second, larger and more uncomfortable debt: the bitemporal history exists on disk and no binary or endpoint exposes as_of. Nobody can look at it. It waits on deciding whether a surface may answer above its tier.

Discharge and prophylaxis

Discharge is signed on one fact and one reservation. The fact: the first real reconciliation emitted four hundred and twenty-six retractions, and every one of them is a change that had been true in the graph for months and never recorded in its projection — nobody found them by reading, the mechanism found them on the first day it existed. The reservation is the same size: the gate that protects this today measures whether it was reconciled, not whether it was reconciled properly. It is an honest gate with a narrower reach than its name, and the only way that does not become the next case file is for it to be written down here, with the address of what would settle it, instead of resting on somebody remembering.

Glosario

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.