Who Owns This Fact? Assign Ownership Per Datum Before You Integrate, Not After
When several integrated systems each hold a version of the same fact and each can write it, there is no answer to which one is right. Not a hard answer, no answer: there is no shared clock, no shared sequence, and last-write-wins gives you determinism rather than truth. Fact ownership has to be assigned per datum before integration. This covers the single-writer rule, read-only projections with provenance, what actually happens when two systems both update, and why sync is not an answer.
Who Owns This Fact? Assign Ownership Per Datum Before You Integrate, Not After
The short answer. When several integrated systems each hold a version of the same fact and each can write it, there is no answer to which one is right. Not a difficult answer, no answer. Deciding requires a total order over the writes and a notion of which write was authoritative, and across independent systems you have neither, because there is no shared clock and no shared sequence. Last-write-wins is not a tie-break, it is a coin toss with a timestamp attached. So fact ownership has to be assigned per datum, before integration: exactly one system accepts writes for each field, everyone else holds a read-only projection carrying provenance and an as-of time. Sync is not an answer, because sync is two writers with the conflict policy left unwritten.
Four systems. A member-facing application, a support team's customer relationship tool, a billing platform, and an operational system of record that predates all of them.
All four hold a date of birth, an email address, a consent flag and some notion of a plan. All four have a screen where somebody can edit those, and each screen was built by someone who reasonably believed their system was where the data lived.
Then the integration project starts, and the question arrives in the worst possible form: which of these four is right?
A system of record is a property of a datum, not a system
The first mistake is naming one system the system of record wholesale. It sounds decisive and it does not survive contact with the field list.
The operational system is authoritative for service history. The billing platform is authoritative for the plan and its price, because that is where the money is reconciled. The member application is authoritative for the preferences the member set themselves, because nowhere else did the member say anything. The support tool is authoritative for the notes the support team wrote, and for nothing else.
So ownership is per datum. For every field that appears in more than one system, exactly one system owns it, and the others hold copies. Produce that list as a register before you write any integration code, because the register is the design. The pipes are an implementation detail of it.
The register has one row per shared field and these columns: the field, the owning system, the single writer, the readers, the acceptable staleness, the behaviour when the owner is unreachable, the behaviour on a rejected write, and the name of the person accountable for the field being wrong. That last column is the one that makes the exercise useful, and it is the one that stalls.
It stalls because the exercise surfaces disagreements that are organisational rather than technical. Two departments both believe they own the member's address, and each is held responsible when it is wrong. No integration pattern resolves that. Somebody with authority has to decide, and the value of doing it before integration is that they decide once, on paper, rather than being found never to have decided when two records diverge in production.
What actually happens when two systems both write
This is the part that gets hand-waved, so here it is mechanically.
A support agent updates an email address in the support tool at what that machine calls 14:02:11. The member updates the same email address in the application at what that machine calls 14:02:09. Both writes succeed. Neither system sees an error. Both systems are now internally consistent and mutually contradictory, and nothing anywhere is in a failed state.
To resolve that you need to know which write happened later. You do not. The two timestamps come from different clocks with independent drift, possibly different precision, possibly different time zones recorded badly, and skew of a few seconds between two cloud services is unremarkable. The ordering you would use to pick a winner is not reliably available.
Now fix the clocks perfectly. You still have not solved it, because the later write is not the more correct one. It is very often a stale human action: an agent typing a value from a form the member filled in last month, overwriting the correction the member made this morning. Recency and authority are different properties, and last-write-wins conflates them in order to terminate.
- Member corrects the email in the appthe correction they made this morning, by what that machine calls the time
- Agent types the email in the support toola value read off a form the member filled in last month, by what that machine calls the time
- Both writes succeedneither system sees an error, so there is no failure for anybody to investigate
- Two clocks with independent driftdifferent precision, time zones recorded badly, and a few seconds of skew between two cloud services is unremarkable
- Now fix the clocks perfectlythe later write is still the stale human action overwriting the fresh correction, because recency and authority are different properties
This is where distributed-systems machinery gets misapplied. Conflict-free replicated data types and merge strategies give you convergence, which is a real property: all replicas end up agreeing. They do not give you correctness, because nothing in a merge function knows which human was entitled to set the field. For a consent flag, converging on the wrong value is worse than diverging, because divergence shows up as a discrepancy somebody can investigate and convergence looks like success.
The honest conclusion is that there is no resolution rule, only an ownership rule applied earlier.
Single writer, read-only projections everywhere else
The pattern is unglamorous and it works.
One writer per fact. For each field in the register, exactly one system accepts writes. Writes elsewhere are rejected, not merged. A rejection is a loud, debuggable event. A merge is a silent decision.
Everyone else holds a projection. A copy, explicitly labelled as a copy, with the edit control removed from the interface, the field excluded from the write path in the API, and a constraint or trigger in the database so that a future developer who adds a write path gets an error rather than a surprise. Read-only enforced in one of those three layers is a convention. Enforced in all three it is a property.
Projections carry provenance. Four pieces of metadata, not one: the source system, the source record identifier, the source's version or updated-at value, and your ingestion time. The last two differ and both are needed. Source updated-at tells you how old the fact is. Ingestion time tells you how old your knowledge of it is. With only one you cannot distinguish a stale projection from a genuine disagreement, which is the distinction you need during an incident.
Edits route to the owner. If a support agent genuinely needs to change an owned field, the support tool calls the owner's API and refreshes its projection. The agent's experience is unchanged. The write happens once and the audit trail has one entry rather than two contradictory ones.
Why sync is not an answer
Sync is the word teams use for bidirectional replication. Bidirectional replication is two writers by construction. So calling the integration a sync does not avoid the ownership question, it buries it in middleware.
Ask one question of any proposed sync and the gap appears. What happens when both sides changed since the last run? Last write wins, which is the coin toss above. One side always wins, which is single-writer ownership, so say that and enforce it properly. A human reviews a conflict queue, which is a real answer and needs somebody staffing it. Or there is no answer and whichever job runs last overwrites, which is the usual case and nobody's intention.
Two more failures are specific to sync and show up in production rather than in design.
The feedback loop. System A writes to B. B's change feed fires and writes back to A, whose change feed fires and writes to B. You break this with an origin tag on every write so a system can recognise its own echo, and the origin tag is what gets forgotten, because the loop only manifests under a race or a retry.
The middleware becomes the owner by accident. Once merge logic lives in the integration layer, that layer decides values. It is now the authority for the field, with no interface, no audit trail a business user can read, and no owner in the register.
If you already have two writers
Which is the realistic situation. The sequence that works.
Measure the divergence first. Pull both populations and count the records where the field disagrees. That number is the only thing that reliably makes the case internally, and it is usually larger than anyone expects.
Pick the owner by accountability, not by technology. The owner is the system whose users are held responsible when the value is wrong. It is frequently the older, less pleasant system, and that is not a reason to choose differently.
Freeze the other write path before backfilling, in the interface and the API and the schema, otherwise you are backfilling into a moving target.
Backfill with a report. For every record you change, write down what it was and what it became. Somebody will ask about a specific person.
Keep the comparison running forever. A scheduled job that counts disagreements between owner and projection and alerts on any non-zero result. This is the control that survives staff turnover, because the next integration project will reintroduce a second writer and the monitor is the only thing that will notice.
When one owner is the wrong model
Sometimes two systems legitimately hold different values and neither is wrong. Two clinicians record different assessments. Two sources report different addresses because the person has two. A sensor and a manual reading differ because they measured at different moments.
That is not a conflict. It is evidence that you have modelled one field where the domain has many observations. Store observations with their source, time and method, and define a derivation rule that produces the single value the rest of the system reads. The rule is then an owned, versioned, testable artefact with a name, instead of a tie-break hidden in a merge function.
A surprising share of integration conflicts dissolve under that reframing, and the ones that remain are genuine ownership questions.
The honest limit
Assigning ownership is an organisational act before it is a technical one, and sometimes you cannot. A vendor system you do not control writes a field on a schedule you cannot change, and no register stops that. There you are reduced to detection: compare, alert, and document a manual procedure. Worse than ownership, much better than nothing.
Projections are always slightly stale, and some use cases cannot tolerate the latency window. If a decision with financial or clinical consequence depends on a field, read it from the owner synchronously and accept the coupling, or state the tolerable staleness in the register.
And the hardest case is not a conflict at all. It is two systems using the same field name with different definitions, where one system's active member means subscribed and the other's means logged in this quarter. Those records disagree for a reason no ownership rule addresses, and they will look exactly like a sync bug for as long as you treat them as one.
FAQ
Which system should be the system of record? The question is malformed at the system level. Ownership belongs to each datum, so the operational system may own service history while the billing platform owns the plan and the member application owns self-set contact preferences. Build a register with one row per shared field naming the single writer and the accountable person, and do it before writing integration code.
What happens if two systems both update the same field? Both writes succeed, neither errors, and the two systems become internally consistent and mutually contradictory with nothing in a failed state. Resolving it needs a total order over the writes, which you do not have across independent clocks, and even with perfect clocks the later write is often a stale human re-entry overwriting a fresh correction.
Why is last-write-wins not good enough? Because it conflates recency with authority. It needs a reliable total order that independent systems with drifting clocks cannot provide, and when it does terminate it has picked arbitrarily rather than correctly. It delivers convergence, not truth, and for a fact such as a consent flag, converging on the wrong value hides the problem that divergence would have exposed.
Is bidirectional sync a valid integration pattern? Bidirectional sync is two writers by definition, so it does not avoid the ownership question, it moves it into middleware where nobody owns it. Ask what happens when both sides changed since the last run. Every honest answer is either single-writer ownership, a human conflict queue somebody must staff, or an arbitrary overwrite nobody chose.
How do I fix an integration that already has two writers? Count the diverging records first, because that number is what makes the case. Choose the owner by who is accountable when the value is wrong rather than by which system is newer. Freeze the other write path before backfilling, backfill with a per-record report, then run a permanent job that compares owner against projection and alerts on any disagreement.
What metadata does a read-only copy need? Four fields: source system, source record identifier, the source's own updated-at or version, and your ingestion time. The last two are distinct and both are required, because together they let you tell a stale projection from a genuine disagreement. Enforce the read-only property in the interface, the API and the schema, since one layer alone is a convention.
The five signs that a failure is in the seams, then one register per boundary covering units, ranges, timing, retry ownership and partial-success behaviour. Filled in separately by both sides, which is the whole method.
Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.
Bring us the program