Modes of Use¶
This page is the diagnostic that decides what the rest of the
chapter means for you — whether you are building a Commit Application,
a service over the Commit Database, a CLI tool, or a batch script.
The architectural choice between single-stream and multi-stream usage
— and within multi-stream, between local and strong invariants — is
expensive to reverse once a DSM model is sealed. The engine itself
carries no migration: switching regimes after the fact means rewriting
existing documents into the new shape, outside the runtime.
dsviper-database-tools
does definitions-directed document rewriting and database migration for
exactly that, in pure Python over the binding — it is in beta, and it is
a whole-database pass, not a schema patch. Decide as if you decide once;
do not assume you cannot unmake it.
The asymmetry runs one way: a multi-stream-friendly model is harmless in single-stream usage; a single-stream model has no safe path to multi-stream. In doubt, model for multi-stream — advice about the shape of the model, not about operating multi-stream: a multi-stream-friendly model costs nothing if you never write concurrently, and buys an option you cannot obtain later.
Two kinds of version DAG¶
Two distinct families of versioning systems share vocabulary (commit, merge, head, parent) and topology (a DAG of nodes with one or more parents), but differ on what a node contains.
Some systems (git, hg, fossil) are snapshot DAGs: each node
contains a full state of the tree. Their merge reconciles
states; when states disagree, the algorithm cannot pick and
surfaces a conflict for human arbitration.
The Commit Database is a mutation DAG: each node contains a
sequence of path-based mutations. Its commitMerge records a join
of two heads, and reconstruction then composes their mutation
sequences; when mutations target the same path,
deterministic composition rules apply (last-writer-wins by
linearisation order). No conflict is ever surfaced.
This is what makes mechanical reduction possible — and what makes the
Dual-Layer Contract necessary.
Habits built in snapshot DAGs do not transfer cleanly. Read on with this distinction in mind.
Two orthogonal axes¶
How you exercise the mutation DAG splits along two independent axes, and conflating them is the most common source of confusion.
What you can do — the operation axis. Time travel, undo / redo and multi-head exploration are always available, in every context.
Where you do it — the context axis. Single-stream, multi-stream with local invariants, or multi-stream with strong invariants. The context alone decides whether the engine ever has to pick a winner, and therefore whether the Dual-Layer Contract applies to you.
The Dual-Layer Contract applies along the context axis, not the
operation axis. An undo is trivial in single-stream; the same undo
in multi-stream reads through a state an earlier commitMerge
produced. The operation did not change — the context did.
Read both sections as a diagnostic, not a catalogue.
What you can do — the operation axis¶
These three operations are available regardless of context. What
changes with context is not whether you can perform them, but whether
the state they read has already passed through commitMerge.
Time travel¶
Read-only. Reconstruct any past state from a commitId. No writes,
so only the read-side structural guarantees apply: determinism,
immutability, content-addressing.
Undo / redo¶
Append-only. Step back along the chain, diverge, redo. Undo never rewrites history — it masks its target with an Enable/Disable commit. All structural guarantees apply.
Append-only does not mean the mask is harmless: it changes the trace
every later commit is replayed on. Walked as a stack — undo the last
active mutation, redo in reverse order, as CommitStore.undo /
redo do — it changes nothing beneath anyone. Disabling or
re-enabling a commit that later mutations were written on top of
replays them against a state their authors never read: a read is then
an import, with one head and no
merge in the history.
Multi-head exploration¶
Parallel heads. Diverge the DAG and keep parallel heads alive,
reducing them back on your own schedule. Multi-head exploration whose
every reduced result is reviewed stays single-stream —
commitMerge happens, but a person owns the resulting state.
Important
In single-stream every read returns exactly what you wrote, as long as
undo / redo is walked as a stack. In multi-stream a read is an
import
as soon as a commitMerge nobody reviewed lies in the history of the
state read — a past state included, when such a fold precedes it. A
state before the first unreviewed fold is still a load. In either
context, an unreviewed Enable/Disable of a commit that later mutations
were written on top of makes a read an import too. The three
operations above do not change between the two contexts; what changes is
the contract around the state they read.
Where you do it — the context axis¶
Single-stream¶
No fold goes unreviewed — every commit extends the head it read, or a
person reviews the result of folding the heads they diverged — and no
mask reaches beneath later mutations unreviewed: undo / redo walks a
stack. No state you read has a commitMerge nobody reviewed, nor such
a mask, in its history, so the
Dual-Layer Contract is reference material, not
load-bearing.
What defines the regime is where writes land, not how many people are
behind them: several writers serialised onto the head stay here, and one
writer committing against a head that has moved, with nobody reviewing
the fold, does not. Time travel,
undo / redo and multi-head exploration all apply with only the read-side
structural guarantees — there is nothing for the engine to pick between.
Multi-stream¶
Two heads meet, and whatever folds them — reduce_heads, or a
commitMerge sequence you issue — reduces their writes mechanically.
Two flavours, split by what your invariants look like.
Local invariant — its truth depends only on a structurally disjoint subset of the data (one attachment, one path, one document, or a container used only where it is commutative — a
set/map, or anxarrayread as a set of elements). Two streams writing on disjoint paths cannot break it: the disjointness shields the invariant from reduction.Strong invariant (also called global) — its truth couples data that several streams can write in parallel: uniqueness across the whole model (no two assets share an SKU), referential integrity between attachments (an edge must point to an existing vertex), cross-document consistency, or any domain that does not tolerate silent loss (financial, safety, regulatory).
Re-validating at read time closes the gap for local invariants — there was nothing for reduction to break. It does not close the gap for strong invariants: read-side validation can detect a violation, but cannot reconstruct the intent that reduction dropped. The lost information is not recoverable downstream.
Multi-stream with local invariants¶
Divergent writes are reduced by whatever folds the heads, but the structural drops cost you nothing in practice. Two routes lead here:
Naturally local invariants — the entire mutable state lives in containers the application only ever grows:
setandmapunions, or anxarrayread only as a set of elements. Commutativity is not a property of these containers —union_in_setcommutes with another union and not with asubtract_in_set, and the API offers both. It is a property of the operations your application can emit on that path, for the whole life of the model. Anxarrayloses no insert, but the relative order of concurrently-inserted elements is fixed by linearisation, so an order-dependent invariant is not naturally local. Where the invariant is membership-only and nothing ever subtracts, there is nothing for the engine to silently break.Defensive-by-design — cross-attachment references exist and may break under reduction, but the application is built from day one to tolerate the break: load robustly, surface the integrity issue, expose corrective actions to the user. This is an uncommon architectural choice — most applications are not built this way.
The Graph Editor sample exemplifies the second route: EdgeTopology
references vertices, and the model ships ModelIntegrity as a
user-facing repair pool (recreate, delete, or respawn missing
elements), on top of an application that loads graphs with broken
integrity without crashing.
For this mode the Dual-Layer Contract is reference material, not load-bearing.
Multi-stream with strong invariants¶
Divergent writes are reduced by whatever folds the heads; your invariants are global (uniqueness, referential integrity the engine must uphold), or your domain does not tolerate silent loss (financial, safety, regulatory). This is where the Dual-Layer Contract becomes load-bearing — and where reading it is a diagnostic, not a cookbook: the four post-reduction outcomes (Ignore / Extract a subset / Correct / Reject) collapse to Reject, which is equivalent to saying mechanical reduction was the wrong primitive.
The exit is not better post-reduction handling. It is re-architecting toward multi-stream with local invariants via scope decomposition (see Cooperative Discipline), or building an application-level supervisor on top of Commit — a review UI, a semantic gate, or a coordination protocol that arbitrates before the engine reduces.