Conflict Behavior

When the same space changes in incompatible ways on different devices (concurrent edits, renames, deletes, or changes made while offline), kutl resolves them by a consistent set of rules. This page states what those rules are. The aim isn't to absorb every imaginable situation, but to make the ones that actually come up behave predictably. Each scenario below has one defined outcome. Two goals shape that design: every device should reach the same result regardless of the order changes arrive, and conflict resolution should not silently discard content.

Two kinds of change behave differently. Edits to a file's contents merge automatically: two people typing in the same file don't collide, nothing is overwritten, and there is no merge conflict to resolve by hand. The rules on this page are about the files themselves (their names, where they live, and whether they exist), which is where a choice sometimes has to be made.

Two goals

Order independence. However changes interleave or arrive across these scenarios, every device should compute the same winners and losers and settle on the same state. This is the central goal the resolution rules are built around, and the arbitration logic is exercised by property-based testing, not just by hand-picked examples.

Content is not silently lost. When two different files can't both keep one name, the losing side is preserved as a separate copy rather than overwritten. An explicit change is still honored: if a delete wins a race, the file is removed, and every device agrees on that outcome.

The same file, renamed or deleted on two devices

Both rename it to different names. One name wins, chosen by clock order (the later rename wins). It stays one document. The losing name disappears, and the content is kept.

One renames it, the other deletes it. Every device settles on the same outcome: present at the new name, or gone.

One edits it, the other deletes it. A delete made while offline loses to an edit made after it: the file comes back with the edit. When an edit and a delete happen at the same moment with both devices online, the outcome is a genuine race. Every device still agrees on the result, but which side wins is not fixed.

One edits it, the other renames it. Both apply: the file ends at the new name with the edit. A move and an edit are independent.

Two different files claim one name

When two distinct files end up at the same path (for example, each person renames their own file onto notes.md, or one creates a file while another renames onto it, or both create the same name), both are kept. One keeps the name; the other is moved to a separate copy beside it, named notes.kutl-conflict-<id>.md. Which file keeps the original name is decided by clock order. The copy is stable: it does not move or oscillate across restarts.

Going offline and rejoining

An offline change vs. a later online one. Whichever change happened later wins, regardless of who reconnects last. Your offline change is recorded; it simply loses to the more recent one.

Offline edits. Edits made offline merge with edits made online during the same window when you reconnect.

You delete a file offline while someone edits it online. The edit brings the file back when you rejoin. A disconnected delete cannot erase work done after you left.

You rename a file offline that the team already deleted. Renaming your stale copy of a file the team already deleted does not bring it back; it stays deleted.

A long offline session. A large batch of offline creates, renames, and deletes settles on reconnect without stalling.

Deleting and bringing back

Delete a file, then bring it back later. If you recreate or rename a file after deleting it, it comes back with its original content.

A new file at a deleted file's old name. It is a new document, not the old one resurrected. Reusing a name does not reattach the previous file's history.

Two people recreate the same deleted file at once. Both are kept as separate files: one at the name, one as a .kutl-conflict-<id> copy, each with its own content.

Restarting and joining

A deletion survives a relay restart. If the relay restarts and reloads from storage, deleted files stay deleted.

A new device joins. It syncs every live file in the space, each at its current name.

Self-hosted and cloud behave the same. A given conflict reaches the same outcome whether the relay is the self-hosted build or the hosted service. Resolution does not depend on the backend.