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 an xarray read 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: set and map unions, or an xarray read only as a set of elements. Commutativity is not a property of these containers — union_in_set commutes with another union and not with a subtract_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. An xarray loses 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.