What leaves the store intact
Every tiramemsu record is (e, s, p, o, a, d, v1, v2): an occurrence id, a triple, a transaction-time lifetime [a, d) and a valid-time interval [v1, v2). The subject or object can be another record's id. Gelling, Fletcher and Schmidt's Statement Graphs, the formal model behind Amazon Neptune's OneGraph, have statement vertices with exactly three outgoing edges: subject, predicate and object. Statement graphs and tiramemsu argued that one record is one statement vertex.
So is the store a Statement Graph? Sometimes. The paper says exactly when, and what an exporter should do in the other cases.
The rule: export, don't invent
An export is identity-preserving when it obeys two rules:
- Every exported record becomes exactly one statement vertex, and there are no others.
- A reference to record
xbecomes an edge to the vertex ofx, and is never rewritten into something else.
No new occurrences and no renamed ones. Under those rules the first theorem is short: a set of records exports exactly when it is closed and acyclic. Closed means every record it points at is in the set. Acyclic means no chain of references comes back to where it started, because a Statement Graph is a DAG. The export is then unique, and reading its role edges gives back every triple with its id.
A tempting shortcut breaks the first rule. You can turn a dangling reference into a plain IRI leaf and still get a valid graph. But a consumer can no longer tell a reference to a missing occurrence from an ordinary term, so the shortcut is not allowed.
The running example
At transaction 1 we write a route and its distance:
e1 = (:FRA, :route, :LHR)
e2 = (e1, :distance, 655) # an annotation on e1
At transaction 2 we correct the route's valid time with supersede. The engine retracts e1 and its dependent e2, replays them under fresh ids e1' and e2', and records the lineage ℓ = (e1', sys:supersedes, e1).
- The current snapshot
{e1', e2', ℓ}is not closed:ℓpoints at the retractede1. - The full history
{e1, e2, e1', e2', ℓ}is closed and acyclic. It exports as is.
A different correction, one whose patched object names the annotation e2, replays into e1' = (:FRA, :route, e2') and e2' = (e1', :distance, 655). Neither record refers to itself, but together they form a two-cycle. Neither the snapshot nor the history can export it whole.
The kernel: the most you can export
When a set is not exportable, the exporter has to drop whole records. It cannot patch endpoints or invent referents. Which records should go? The paper's answer is the export kernel. Start from nothing and repeatedly add every record whose references are all already in. Records that reference nothing go in first, then the records that point only at those, and so on, until nothing changes.
The kernel is the unique greatest closed, acyclic subset, so no other choice exports more. It drops exactly the records that reach a problem: a missing target, or a cycle. Unrelated facts stay. It is Kahn's topological sort, so a peel computes it in linear time and outputs records in an order where every record comes after its targets. That is the order a streaming RDF-star writer needs. In the cyclic example, the history kernel keeps e1, e2 and any unrelated fact. The snapshot kernel keeps only the unrelated fact, because ℓ also dangles there.
The kernel has one weakness: it costs provenance. In the first example it exports {e1', e2'} from the snapshot and silently drops the lineage ℓ. The paper's dual, the reference closure, goes the other way. It keeps every selected record and adds the historical records they point at, marked as retracted. That exports {e1, e1', e2', ℓ} whole. Either way, an exporter should report every omitted record with a reason: missing target x, on a cycle, or depends on omitted y.
Export the history, not the present
A temporal store has a property most graph stores lack: records are immutable and never deleted, and a write may point only at records that already exist. The paper proves that under these rules the full history is always closed. Snapshots can dangle, but history cannot. The only cycles history can contain are the ones a single correction creates inside its own replay batch.
Two practical consequences follow:
- One bit per record. When a transaction commits, run the peel over just its new records, treating references to older records as resolved if their bit is set. The bit then marks the history kernel exactly, and it never changes later. Full-history export becomes a filter scan.
- A rule that removes the last loss. Before a correction writes anything, peel its replay batch. If something is left over, abort with the offending member as the reason. This rejects exactly the patches that name a member of the correction's own cascade. Under this acyclic-replay rule the history is always closed and acyclic, and all of it exports.
Both clocks, into RDF-star
RDF-star's branch of OneGraph needs one more thing: a statement nothing points at must be asserted by a membership statement (e, in, G). The paper's temporal encoding adds, for each record:
- time statements
(e, τa, a),(e, τd, d),(e, τ1, v1)and(e, τ2, v2), leaving out a clock that is open; - a membership for each time statement;
- a membership for the record itself, but only if you want to present it as asserted.
The roundtrip is lossless: decoding returns the same records with all eight fields, and the same asserted set. Asserting only the current snapshot and quoting the rest is a natural choice. A time-unaware RDF-star consumer then sees exactly the snapshot, and the history hangs off it as statements about quoted triples.
Queries come with one condition. A conjunctive query over the asserted part returns the same answers as the store, up to quoting, when no two records share the same triple. That condition is not idle. A valid-time correction creates a new occurrence with the old content, so in RDF-star e1 and e1' quote to the same triple. A join can then merge annotations the store keeps apart.
Where the boundary moves
| Target | What exports | What it costs |
|---|---|---|
| Statement Graph / RDF-star | The kernel: closed and acyclic records | Equal contents merge on quoting |
| RDF 1.2 reifiers | Every store, cycles and dangling references included | Assertion is per triple, not per occurrence |
| Property-graph branch | Only edges, labels and properties nothing points at | Reference depth at most one; both clocks for edges only |
RDF 1.2 names an occurrence with a reifier IRI instead of nesting it, so a reference is just a name. Closure and acyclicity become checks for consumers that dereference, not conditions of the format. In exchange, an asserted triple cannot say which occurrence it asserts when two share content, so the asserted set has to come from the clocks.
Evidence and limits
- Lean. The PDF's proofs are by hand. A Lean 4 development in the validation archive re-proves the criterion, the kernel and its obstructions, the reference closure, the history results, the acyclic-replay rule, the exportable bit, the temporal roundtrip, the query view and the reifier roundtrip. The property-graph result and the cost bounds are not mechanized.
- Validator. A standard-library Python validator checks 217 625 exhaustive stores, 72 567 four-record reference structures, 20 000 random stores and 3 000 random protocol histories. It compares three kernel implementations with an exhaustive oracle.
- Engine traces. Both counterexamples were traced through the engine and recomputed independently.
No performance claims are made.
Read the paper
When Is a Temporal Statement Store a Statement Graph? Export Boundaries for Occurrence Records with Two Clocks has every definition, theorem and proof, the encodings, the query view and the comparison with RDF 1.2 and property graphs. It is a draft preprint under CC BY 4.0, archived on Zenodo with its validation archive.
Download the PDF About the paper DOI 10.5281/zenodo.23335652
The series in one page → · ← Previous: cheap to write, careful to read