Commit System

The commit system provides transactional persistence with history tracking.

When to use: Use CommitDatabase for versioned persistence with history, concurrent streams, and sync. Every change creates a commit, enabling undo/redo and concurrent editing.


CommitDatabase

CommitDatabase provides versioned data storage:

  • History is preserved as a DAG of commits

  • You can read from any point in history

Important

The Dual-Layer Contract

CommitDatabase guarantees structural integrity (deterministic merge, DAG consistency) but NOT semantic integrity. Best-effort merge means the state returned after convergence is structurally sound but semantically untrusted: mutations may have been silently dropped, and disjoint updates may have combined into a state that violates a cross-field invariant.

Treat post-merge state like deserialized external input: re-validate it at read time, before acting on it. dsviper.Error covers only API misuse — it does not signal lost mutations or business-rule violations.

See The Dual-Layer Contract for details.


Opening a CommitDatabase

>>> db = CommitDatabase.open("model.cdb")

To create a new database with embedded definitions, use:

python3 tools/dsm_util.py create_commit_database model.dsm model.cdb

Reading State

A freshly created database has no commits — first_commit_id() and last_commit_id() return None:

>>> db.first_commit_id() is None
True
>>> db.last_commit_id() is None
True
>>> db.head_commit_ids()
set()

The initial_state() method always works and returns the empty state:

>>> initial = db.initial_state()
>>> len(initial.attachment_getting().keys(TUTO_A_USER_LOGIN))
0

AttachmentGetting Interface

Read attachments via attachment_getting():

>>> getting = state.attachment_getting()

>>> doc = getting.get(attachment, key)
>>> doc
Optional({...})

>>> keys = getting.keys(attachment)

Mutations

Create a mutable state and apply changes:

>>> mutable_state = CommitMutableState(db.state(db.last_commit_id()))
>>> mutating = mutable_state.attachment_mutating()

>>> mutating.set(attachment, key, document)

>>> mutating.update(attachment, key, path, new_value)

Note: CommitDatabase tracks history via mutations from CommitMutableState.

Committing

commit_mutations() returns the new commit id — capture it explicitly to chain further mutations or read the resulting state:

>>> commit_id = db.commit_mutations("Commit message", mutable_state)

Complete Example

Add an Alice document and read it back:

>>> key = TUTO_A_USER_LOGIN.create_key()
>>> login = TUTO_A_USER_LOGIN.create_document()
>>> login.nickname = "alice"
>>> login.password = "secret"

>>> mutable = CommitMutableState(db.initial_state())
>>> mutable.attachment_mutating().set(TUTO_A_USER_LOGIN, key, login)
>>> commit_id = db.commit_mutations("Add Alice", mutable)

>>> state = db.state(commit_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='alice', password='secret'})

Path-Based Mutators

Instead of replacing entire documents with set(), path-based mutators use Paths to target specific locations. This enables path-based merging when multiple users edit concurrently.

Mutator

Target

Operation

update

Field

Replace value at path

union_in_set

Set

Add elements

subtract_in_set

Set

Remove elements

union_in_map

Map

Add key-value pairs

subtract_in_map

Map

Remove keys

update_in_map

Map

Update existing key

insert_in_xarray

XArray

Insert at position

update_in_xarray

XArray

Update at position

remove_in_xarray

XArray

Remove at position

Field Update

>>> mutating.update(TUTO_A_USER_LOGIN, key, TUTO_P_LOGIN_NICKNAME, "alice_updated")

Set Operations

>>> mutating.union_in_set(GRAPH_A_TOPOLOGY, graph_key, GRAPH_P_TOPOLOGY_VERTEX_KEYS, {new_vertex_key})

>>> mutating.subtract_in_set(GRAPH_A_TOPOLOGY, graph_key, GRAPH_P_TOPOLOGY_VERTEX_KEYS, {deleted_vertex_key})

Map Operations

>>> mutating.union_in_map(attachment, key, path_to_map, {"new_key": "new_value"})

Why Paths Matter

When two users edit different fields simultaneously:

User A: update(attachment, key, path_to_name, "Alice")
User B: update(attachment, key, path_to_email, "bob@example.com")

After merge: Both updates apply (disjoint paths)

With set(), one user’s changes would overwrite the other’s.


Commit History

Inspect commit metadata:

>>> header = db.commit_header(commit_id)
>>> header.label()
'Add Alice'
>>> header.parent_commit_id() == ValueCommitId()
True

The first commit’s parent is the zero ValueCommitId (no ancestor).

Navigate history by passing the explicit ids you captured:

>>> state1 = db.state(first_commit_id)
>>> state2 = db.state(latest_commit_id)

Embedded Definitions

CommitDatabase stores its definitions:

>>> defs = db.definitions()
>>> sorted(str(t) for t in defs.types())
['Tuto::Account', 'Tuto::Identity', 'Tuto::Login', 'Tuto::Status', 'Tuto::Texture', 'Tuto::Thumbnail', 'Tuto::User']

Calling defs.inject() makes TUTO_A_USER_LOGIN, TUTO_S_LOGIN, etc. available as constants in the calling namespace.


What’s Next