The lost reply
The worst case in the field is not having no network. It is the server saving the record and the reply getting lost on the way: the app tries again and duplicates it. So each record is born with a key. The server reuses what already exists with that key, and a partial unique index in the database guarantees the rest, even if the code is wrong.
-- migration 0022: a reply lost after commit became a retry, and the retry duplicated
alter table activities add column if not exists client_key text;
create unique index if not exists uq_activities_client_key
on activities (client_key) where client_key is not null;
