CelinQ Insights · No. 47

Deleting an Enterprise Architect Element While Someone Else Is Offline: The Resurrection Problem

One architect deletes it while another, unaware, has just spent a week improving it — and neither outcome that ignores the other person is acceptable.

A NILUS perspective on collaborative modelling for Sparx Enterprise Architect

An architecture governance board decides, on a Tuesday, that an old integration component called SOAP Gateway Legacy should finally be removed from the model. It has been marked deprecated for eighteen months, nothing references it in the current architecture, and the decision to delete it is not controversial. An architect who was in the meeting goes back to their desk that afternoon and deletes it. Nobody thinks twice about this. It is exactly the kind of tidy, deliberate cleanup that keeps a model honest.

What nobody in that meeting knew is that a colleague on secondment at a client site had, that same week, been quietly finishing off documentation on the very same component — filling in its tagged values, correcting its description, adding the retirement note that was supposed to accompany the deletion in the first place — and had been doing all of it offline, on a laptop with no reliable connection back to the office. That colleague's local Enterprise Architect repository still has SOAP Gateway Legacy sitting right there, very much alive, with an afternoon of careful work sitting on top of it. When the laptop finally reconnects, two entirely legitimate, entirely well-intentioned streams of work collide over the same element, and one of them says it should not exist.

The delete/update race

This is the sharpest kind of conflict a local-first collaboration tool has to face, sharper even than two people disagreeing about a value, because the two sides are not disagreeing about a fact — they are disagreeing about whether the fact still has a right to exist at all. Distributed-systems literature calls this the delete/update race, and it earns its reputation as one of the hardest problems in the field for a simple reason: whichever answer you pick by default is wrong for somebody, and it is wrong in a way that is very easy to get silently, catastrophically backwards.

Think through the two lazy defaults a system could adopt. The first is "the delete always wins" — if an element is deleted anywhere, it stays deleted, and any concurrent edit to it is simply discarded. That sounds safe from the perspective of the person who made the deletion, but it means the offline colleague's afternoon of careful documentation work vanishes without a trace, with no warning and no chance to object, the moment their laptop reconnects. The second lazy default is the mirror image — "the update always wins," on the theory that an edit represents active, recent intent, so it should override a deletion that happened to collide with it. That sounds safe from the perspective of the person who was still working, but it means a component that a governance board deliberately agreed to remove quietly reappears in the canonical model, undoing a considered decision because someone elsewhere happened to touch a tagged value on it before they knew it was gone. Neither default is defensible. Both destroy someone's legitimate intention, silently, and the person whose work vanished usually only finds out much later, if they notice at all.

Why stale replicas make this worse, not just more common

The reason this scenario is not a rare edge case in a local-first system, the way it might be in a tightly centralised one, is precisely the thing that makes local-first valuable in the first place: every architect's local Enterprise Architect repository is a full, genuinely usable replica, not a thin cache that has to phone home before it lets you do anything. That is what makes the offline colleague's week of productive work possible at all, as covered in the earlier piece on working offline and syncing later. But it means every replica can, for a while, hold a view of the model that has quietly gone stale relative to the canonical server, and a stale replica does not know it is stale. From the offline architect's point of view, nothing is wrong. SOAP Gateway Legacy is right there, exactly as it always was, and editing it feels exactly as legitimate as editing anything else, because as far as their local repository is concerned, it is.

A system that copied whole files instead of tracking semantic facts would have no way to even notice this collision cleanly — it would see two different versions of a database and would have to guess, at the level of raw bytes, which one was "right." CelinQ tracks state at the level of individual entities and their versions, which means the moment the offline architect's changes reach the server, the server can see precisely what happened: an entity that the canonical workspace has already recorded as deleted has just received an update that was written against an older, pre-deletion base. That precision is what turns an impossible guessing game into a well-defined situation with a correct, narrow answer.

Absence is not evidence of deletion

The deeper design question is how a deletion should even be represented internally, and the wrong answer is more tempting than it sounds: represent deletion by simply removing the row. If a deleted entity is just an entity that is no longer there, then a system trying to reconcile an incoming update to that same entity ID has no positive signal that a deletion ever happened — it only has an absence, and absence is genuinely ambiguous. Absence could mean "this was deleted." It could equally mean "this entity was never synchronised to the server yet," which is exactly what a brand-new, still-offline creation looks like from the server's point of view. A system that cannot tell those two situations apart is one bad synchronisation away from treating a deliberate deletion as if it had never happened, simply because a create-shaped update arrived and the row it referred to was not there to say otherwise.

Tombstones: making deletion a fact you can compare against

CelinQ's answer is to never let deletion be represented by mere absence. Every deletion is recorded as a versioned tombstone — an explicit, permanent fact that says this specific entity, identified by its stable identity, was deleted at this specific revision. The tombstone is not a temporary marker that gets cleaned up later; it is part of the entity's own causal history, carried forward exactly the way a rename or a tagged-value change would be. This single design decision is what turns the delete/update race from an unsolvable guessing problem into an ordinary case of concurrency detection: when the offline colleague's update arrives, the server does not have to infer anything about what might have happened to that entity. It can simply look up the entity's current state, see the tombstone sitting there, and recognise instantly that this incoming change is colliding with a deletion rather than with an ordinary edit.

This is also precisely why the classification described in the earlier piece on model-aware merge rules works reliably even in the hardest cases. Stable identity means the server always knows which specific entity an incoming change refers to, deletion or not; a tombstone means the server always knows, without ambiguity, whether that entity's current canonical state is "exists" or "deleted, as of revision N." Put those two things together and the delete/update race stops being a special mystery case and becomes just another instance of comparing a base, a local change and a remote change — except that on one side, the "remote change" is a deletion rather than an edit.

Shared base revision SOAP Gateway Legacy Office: deletes it tombstone recorded Offline: keeps editing unaware of the deletion Reconnects — meets the tombstone TombstoneConflict — a person decides
A deletion and a concurrent edit from the same base don't cancel each other out silently. The tombstone makes the collision visible, and a person decides what happens next — not whoever reconnects first.

Not the same thing as a soft-delete flag

It is tempting to hear "tombstone" and assume this is just a familiar database pattern under a new name — a boolean IsDeleted column that a query filters out. It is not, and the difference matters. A soft-delete flag lives inside the same row it marks, which means it only helps if the row it is attached to is the thing being compared. In a replicated, offline-capable system, the whole difficulty is that the two sides being compared may not agree on what "the row" currently looks like at all — one side has a full pre-deletion entity with a freshly edited tagged value, the other has a server record that has moved on to a deleted state, and they arrived at the comparison from a shared base that neither of them has seen since. A tombstone in CelinQ is not a flag flipped on an existing row; it is itself a causal event in the entity's version history, carrying its own revision number, sitting in the same sequence as every rename, move and tagged-value change that entity ever had. That is what lets the three-way comparison work at all: the merge does not ask "is this row currently marked deleted," it asks "what changed between the base both sides started from and each side's current state," and a tombstone answers that question the same principled way a rename or a move would.

This also explains why a tombstone has to survive indefinitely rather than being cleaned up once the delete has "taken effect." A soft-delete flag can often be forgotten about once nothing references the row any more. A tombstone has to remain reachable for as long as any replica in the system might still be holding pre-deletion state and might still reconnect with an update written against it — which, in an offline-tolerant tool used by architects who genuinely disappear for days or weeks at a time, is an unusually long window by ordinary database standards. The permanence is not an oversight; it is the entire reason the mechanism works for the scenario this article opened with.

A tombstone is not a bookkeeping detail. It is the difference between a system that can tell you exactly what happened to an element and a system that can only tell you the element isn't there any more — and those two things sound similar until the moment someone's genuine work depends on knowing which one is true.

Delete wins, update wins, or neither: the case for a capsule

Given a clean, unambiguous signal that a deletion and a concurrent update genuinely collided, CelinQ still refuses to pick a winner automatically, and that refusal is deliberate rather than a gap. This is the one situation in the entire Fusion rule matrix where neither lazy default is acceptable, because both directions destroy a legitimate intention without the person who held it ever being consulted. So the collision is raised as a TombstoneConflict capsule: the canonical workspace holds at "deleted," the losing update is preserved intact inside the capsule rather than discarded, and a human is asked to decide. Nothing about the entity's fate is resolved by whoever happened to push first, whoever has the faster connection, or whoever reconnects sooner — the capsule sits open, evidence attached, until someone with the context to actually judge the situation looks at both sides.

That evidence is the part that makes the decision fast rather than stressful. The capsule does not simply say "conflict." It shows the base state the offline architect started from, the deletion that happened on the canonical side, and the specific update the offline architect made — in this case, the completed tagged values and the retirement note. An architect reviewing the capsule does not have to reconstruct the story from memory or chase down two colleagues to ask what happened. The full shape of the disagreement is right there.

Restore original, restore with changes, restore as new

Once a person looks at a tombstone capsule, CelinQ offers a small, deliberately limited set of resolutions, because a limited set of well-understood outcomes is safer than a general-purpose "do whatever you think is right" text box. The reviewer can accept the deletion outright, in which case the element stays gone and the offline colleague's update is retained only in the capsule's history, never silently lost, simply not applied to a model that the workspace has decided should no longer contain it. The reviewer can restore the original entity and apply the incoming changes on top of it, which is the right call in exactly the scenario above: the deletion turns out to have been premature, or the retirement note the offline colleague was writing should really have been the reason nobody deleted it that week in the first place. Or the reviewer can restore the underlying work as a new entity entirely — allocating a fresh identity for what the offline architect built, while the original, deleted identity stays deleted for good. That last option matters more than it might first appear: it lets you say "the old SOAP Gateway Legacy really is retired, but the documentation work my colleague did is worth keeping as the seed of its replacement," without pretending the two are secretly the same object with a shared history they never actually had.

None of these resolutions happens without an explicit decision, and none of them happens by guessing which side "deserves" to win based on timing. The reviewer is not choosing between two competing pieces of software logic; they are choosing between two competing pieces of human intent, with full visibility into what each one actually was.

What the audit trail keeps, even when nobody looks

One of the quieter benefits of resolving this through an explicit capsule rather than a silent rule is what it does for the historical record. Every capsule, resolved or not, is part of the ordered revision history the same way any other change is — who deleted the element, when, on what base; who kept editing it, unaware; what the resolution eventually was, and who made that call. Nothing about the collision is erased by resolving it, which matters enormously in regulated or audited environments where "why does this element exist again, three months after the meeting where we agreed to remove it" needs a real, inspectable answer rather than a shrug. A governance process that can point to the exact capsule, the exact evidence, and the exact person who decided to restore the entity is a governance process that survives scrutiny. It also means the resolution itself is not a black box: like every automatic and manual Fusion decision, it becomes part of the same proof-carrying record described in the companion piece on how CelinQ's automatic merges explain themselves, so a reviewer months later can see not just that a capsule existed, but exactly what evidence justified the choice that was made.

The point of a tombstone is not to prevent deletions from colliding with edits. Collisions like this are going to happen in any team doing real work at the same time in different places. The point is to make sure that when they do, nobody's intention disappears without a trace.

How you would actually test this

Because this is the sharpest case in the whole reconciliation model, it deserves to be tested deliberately rather than assumed to work. A reasonable test scenario simulates exactly the situation above: two clients start from an identical base state; one is disconnected and continues editing a specific entity locally, accumulating real changes; the other, still connected, deletes that same entity on the canonical workspace; the disconnected client then reconnects and pushes its accumulated offline changes. The assertions that matter are not about which outcome "feels right" in isolation, but about the invariants the system as a whole must never violate: the deletion must not silently vanish because an update happened to arrive after it, the update must not silently disappear because a deletion happened to arrive first, and the collision must be visible and attributable rather than resolved by an arbitrary tie-break. CelinQ's own Fusion benchmark exercises this exact shape of collision as part of a much larger run — a hundred thousand operations pushed through the real store and merge pipeline by five concurrent simulated clients, seeded and fully reproducible — and the invariant it checks for this case specifically is stark: zero silent loss and zero resurrection of a deleted element, across the entire run, with every client converging afterwards to an identical final state. That is the standard worth holding any tool to on this particular question, including CelinQ, and it is a standard that should be demonstrated with a reproducible run rather than simply claimed.

The honest limits

None of this makes the resurrection problem pleasant, only survivable, and it is worth being direct about where the rough edges remain. A tombstone capsule is still an interruption — the offline architect who reconnects expecting their week's documentation work to have landed cleanly instead finds a decision waiting for them, and that is friction, even if it is friction with a good reason behind it. In workspaces where deletions and offline editing genuinely overlap often — a team mid-way through a large deprecation sweep, say, while several members are travelling — that friction can show up as a small but real stream of capsules that someone has to work through, one at a time, rather than a single tidy summary. There is also a narrower technical limit worth naming honestly: for data migrated from before CelinQ tracked causal bases at all, a concurrent update to an entity with no recoverable base is treated conservatively, raising a capsule rather than guessing at a three-way merge it cannot actually perform correctly — the safer failure mode, but a manual one nonetheless.

What the tombstone mechanism does guarantee, and what makes the friction worth accepting, is that the two outcomes nobody should ever accept quietly — a colleague's real work disappearing because a deletion happened to arrive first, or a deliberate governance decision being silently overturned because an update happened to arrive first — simply cannot happen by accident. Compare that to the alternative most teams are used to, where the only defence against this scenario is informal: someone remembers to ask around before deleting anything, or a Slack message goes out warning people not to touch a particular package this week. That works until the team is busy, or new, or spread across time zones, at which point it stops working exactly when it is needed most. A rule that cannot be forgotten under deadline pressure is worth more than a courtesy that depends on everyone remembering to extend it.

The next article in this pair looks at a closely related but structurally different situation: not editing something that has been deleted, but creating something new underneath a package that has been deleted out from under it, and why CelinQ handles that case with a mechanism of its own rather than folding it into the same tombstone logic. It turns out that "someone deleted the parent while I was building something inside it" is a genuinely different shape of collision from "someone deleted the exact thing I was editing," and treating the two identically would get the harder of the two cases wrong.