Appearance
Tarinoi Consistency Model
Version 0.0.1 — 2026-05-21
Tarinoi is an eventually consistent distributed system supporting both real-time online collaboration and asynchronous offline work as first-class citizens. This document describes the data lifecycle, reconciliation model, integrity guarantees, failure modes, and semantic integrity model. It is intended for technical Tarinoi users.
Data lifecycle
All Tarinoi data originates on clients. When a client wants to mutate a resource, it stores a snapshot of the shared base along with the mutated resource in its client-side database. If the client is online, this triggers a near-immediate sync cycle. If it is offline, sync is deferred until it returns online.
When the server receives data, it emits notifications to clients, which also triggers sync cycles on them.
Sync cycle and reconciliation model
A sync cycle is pull → reconcile → push. Reconciliation is through a three-way rebase on a shared base snapshotted by the client prior to mutating a resource. Conflict policy is server wins.
Reasoning: A server-wins policy produces rare edge cases where an offline client loses concurrent edits to the same fields as an online user. These edge cases are avoidable through coordination and have a narrow blast radius. The alternative — merge-based conflict resolution — introduces structural problems (stale references, invalid scripting constructs) in the more common case where concurrent edits affect semantically related but non-overlapping fields. Server-wins handles that case cleanly.
Data layers
Data is layered: changes first appear in a buffer layer from which they are committed to a project layer. This commit triggers a flush to a filesystem Git repository, and if the customer has linked it to a remote, a push to the remote. Each of these phases has distinct history, persistence, and integrity guarantees.
Consistency model
The source of truth is the server-side ACID database that clients sync against. Clients are inconsistent with the server and each other between sync cycles; offline use may result in significant drift between a client and the server.
The data unit Tarinoi operates on is the document. With each sync cycle, the system converges to consistency at the document level. The full set of consistency guarantees is in the Guarantees table below.
Active documents
A document has an is_active flag derived by the database from three independent flags:
| Flag | Effect on clients |
|---|---|
is_tombstone | Hard-delete the document |
is_archived | Hide the document |
is_moved | Hide the document; coordinates move atomicity (see below) |
If any of these flags is set, is_active is false. The guarantee "only one active document per project, per layer" means no two documents with the same document_id in the same project and layer may simultaneously have is_active = true.
Contention resolution
If concurrent sync cycles conflict — two clients push changes that intersect — latest push to server wins. The blast radius is bounded to a few seconds of concurrent edits, and contention of this kind only occurs in intensive real-time sessions.
Commit resolution
Commit from the buffer layer to the project layer occurs in a single ACID transaction.
Move atomicity
The primary key for a document row is (layer_id, collection_id, document_id). A move of document d1 from collection c1 to c2 is executed in a single database transaction that writes two rows:
- Destination record:
document_id: d1, collection_id: c2, moved_from: c1, payload: <payload> - Signpost record:
document_id: d1, collection_id: c1, is_moved: true, moved_to: c2, payload: null
The signpost record has is_active = false (because is_moved = true). Its purpose is to allow clients performing optimistic sync to recognize that a document they previously held in c1 has moved to c2, rather than treating it as a deletion.
When the change is flushed from the buffer layer to the project layer, the system verifies that both the destination record and the signpost record are included in the same flush transaction.
Mutation history
Tarinoi maintains a project's commit history in its associated Git repository. No other mutation history guarantees are offered.
Reasoning: Offline-first architecture permits clients to diverge from the server for an arbitrary amount of time. Reconciling mutation logs across that divergence would be fragile and hard to reason about. Reconciling state through rebase against a shared base is simpler and more robust. Users should commit frequently so that meaningful mutations are captured in the Git log.
Guarantees
Tarinoi implements the following integrity guarantees on project data:
| Guarantee | Description | Enforcement mechanism |
|---|---|---|
| Active document identity | Only one active document with a given ID exists in a project layer | Database constraint |
| Commit/revert atomicity | Partial commits or reverts between the project layer and buffer layer will never occur | Single database transaction |
| Move atomicity | A moved document always has both a destination record in the target collection and a signpost record in the source collection | Single database transaction; database constraint on active document identity |
| Bulk operation atomicity | Bulk changes are accumulated in the staging layer and applied to the buffer layer atomically | Flush from staging layer to buffer layer in a single database transaction |
Integrity Enforcement Mechanisms
| Mechanism | Description |
|---|---|
| Cryptographic consistency validation | Before commit, client and server collection state is validated by comparing a rolling hash. If the hashes match, the commit proceeds. If they do not match, Tarinoi retries with randomized backoff (jitter). If hashes still differ after retrying, the system checks whether the server cursor has advanced: if it has, there is active contention — retries continue with progressively longer backoffs, then fail gracefully; if the cursor has not advanced, the client state is irreconcilable with the server, indicating a corrupt client. Tarinoi transitions to error correction mode. |
| Error correction mode | A recovery UI presented when the client cannot be reconciled with the server (cursor not advancing, hashes still mismatched). Offers: resync a specific collection; download a snapshot of the local client database; or, as a last resort, wipe the local client database and redownload from the server. Under normal operation, users should never encounter this. |
Failure modes and recovery mechanisms
| Failure mode | Blast radius | Recovery options |
|---|---|---|
| Network failure while push/pull/flush/batch operation is in flight | None | Retry when server available |
| Server failure while push/pull/flush/batch operation is in flight | None | Retry when server available |
| Client database corruption, server rejects data, client irreconcilable with server | Un-pushed changes on client | Delete local client DB, redownload from server. Manual recovery from downloaded copy of client DB may be possible |
| Server database corruption | Uncommitted changes, changes since last PITR backup | PITR on database, recover from filesystem or Git remote |
| Server filesystem corruption (no remote repo) | Commit history | Re-flush from DB to filesystem, recreate and reconnect remote repository |
| Server filesystem corruption (remote repo connected) | Commit history since last push to remote (usually none) | Clone from remote, flush to filesystem from last cursor |
Semantic Integrity Design
Tarinoi is designed to converge towards semantic integrity. These constraints are not mechanically enforced, but they are reflected in control points and facilities afforded in the UI.
- Tarinoi uses semantically meaningful identifiers for cross-document references. These include entity references, function references, variable references, list references, and template references. These references should be treated as foreign keys, and their internal consistency is observed but not guaranteed. See the Glossary for definitions.
- Cards and entities are templated. Tarinoi does not guarantee consistency between a template and the templated document if a user changes a template to make it incompatible with existing data.
- Functional dialogues have meaningful structural characteristics. Certain sets of nodes represent player choices; others represent alternatives dependent on game state. Certain structures cannot be unambiguously interpreted as dialogues. Tarinoi does not guarantee or enforce semantic structural consistency for dialogues.
| Aspect | Mechanism |
|---|---|
| Variable, function, entity, and list reference integrity | On-demand and automatic consistency scans with correction tools, with opt-in or opt-out UIs. Users may choose not to apply them, intentionally leaving the project in a semantically inconsistent state. |
| Template/templated document consistency; orphaned data | On-demand and automatic consistency scans with automatic cleanup tools. Users may choose not to apply them, intentionally leaving the project with orphaned data. |
| Semantic dialogue integrity | Automatically surface potential problems in playback; on-demand consistency scans surfacing problems. |
Glossary
Declarations
Tarinoi's scripting layer supports four types of named declarations that can be referenced across a project:
| Declaration | Description |
|---|---|
| Variable | A named value in the project's scripting scope |
| Function | A callable scripting unit |
| Entity | A game entity (e.g., a character or interactive object) |
| List | A named ordered collection of values |
Declarations are referenced by human-set identifiers, not stable internal keys. When a variable, function, entity, or list is created, the author assigns it an identifier (e.g., player_health, on_enter_room). All cross-references within the project use that identifier directly.
This is a deliberate design decision. Tarinoi's authored data is consumed by the customer's game implementation, which must reference these declarations by name. Using stable internal keys would require every game implementation to maintain a key-to-name mapping, adding indirection to every customer's codebase. Human-set identifiers keep the scripting layer directly readable, portable to game engine code, and inspectable without tooling.
The trade-off is that renaming a declaration requires both a change in Tarinoi and a corresponding change in the game implementation. Tarinoi's consistency scan tools detect stale references within the project, but cannot detect references in external codebases.