Description
Proposal, not a request. Came out of #1147 and two CodeRabbit findings on it. The wallet already works around both, so none of this is urgent. I'm raising it because the workarounds are in the wallet while the cause is in evault-core, and whoever owns the binding-document model is better placed than me to say whether either is worth changing.
A social_connection invite is two documents: the real one in the recipient's vault, and a single-signature mirror in the sender's. Nothing links them, and the sender can only learn the invite's fate by reading the recipient's vault. Two problems follow.
1. An invite has no stable id. The wallet matches a mirror to its remote document on data.relation_description, because there's nothing else to match on. Two pending invites to the same person with the same description (usually the empty one) are indistinguishable, so cancelling one can withdraw the other, and reconciliation can put the "confirmed" label on the wrong row. Neither is visible to the user, since the rows only differ by timestamp, so the cost today is about nil.
Putting an id in data doesn't work from a client. I tried both ways against a local eVault:
- Sign over data including the extra field →
Invalid owner signature. validateBindingDocumentData rebuilds the object from kind, name, parties, relation_description only (BindingDocumentService.ts:115-119) and hashes that.
- Sign over the stripped data and send the extra field anyway → accepted, and the field is gone on read.
So it needs a schema change plus a plan for existing documents.
2. Withdrawing an invite is a read-then-delete. cancelSentSocialBinding checks the signature count, then calls deleteMetaEnvelope, which matches on id and eName and deletes unconditionally (typedefs.ts:475, db.service.ts:627). If the recipient counter-signs in between, the withdrawal deletes a completed binding. The window is one round trip and the recipient has to tap Accept inside it, so it's rare, but a second client-side read can't close it.
One more thing from the same review: BindingDocument and MetaEnvelope carry no server-assigned timestamp, so the wallet compares signature timestamps written by two different phones when it decides whether an envelope is a stale leftover or a new invite. Clock skew makes those two cases look the same. A server-side creation time would settle it.
Reference
Acceptance Criteria
Only if either is picked up:
- An invite can be matched to its counterpart across two vaults without relying on
relation_description.
- A withdrawal can't delete a document that's been counter-signed since it was read.
Desired Output (may vary)
Sketches, not designs:
- For (1), an optional id on
social_connection that survives validation, with existing documents still parsing without it.
- For (2), a delete that takes an expected state — a signature count or a document hash — and refuses instead of deleting when it no longer matches. The wallet would keep the local mirror on a refusal instead of orphaning it.
Description
Proposal, not a request. Came out of #1147 and two CodeRabbit findings on it. The wallet already works around both, so none of this is urgent. I'm raising it because the workarounds are in the wallet while the cause is in evault-core, and whoever owns the binding-document model is better placed than me to say whether either is worth changing.
A
social_connectioninvite is two documents: the real one in the recipient's vault, and a single-signature mirror in the sender's. Nothing links them, and the sender can only learn the invite's fate by reading the recipient's vault. Two problems follow.1. An invite has no stable id. The wallet matches a mirror to its remote document on
data.relation_description, because there's nothing else to match on. Two pending invites to the same person with the same description (usually the empty one) are indistinguishable, so cancelling one can withdraw the other, and reconciliation can put the "confirmed" label on the wrong row. Neither is visible to the user, since the rows only differ by timestamp, so the cost today is about nil.Putting an id in
datadoesn't work from a client. I tried both ways against a local eVault:Invalid owner signature.validateBindingDocumentDatarebuilds the object fromkind,name,parties,relation_descriptiononly (BindingDocumentService.ts:115-119) and hashes that.So it needs a schema change plus a plan for existing documents.
2. Withdrawing an invite is a read-then-delete.
cancelSentSocialBindingchecks the signature count, then callsdeleteMetaEnvelope, which matches on id and eName and deletes unconditionally (typedefs.ts:475,db.service.ts:627). If the recipient counter-signs in between, the withdrawal deletes a completed binding. The window is one round trip and the recipient has to tap Accept inside it, so it's rare, but a second client-side read can't close it.One more thing from the same review:
BindingDocumentandMetaEnvelopecarry no server-assigned timestamp, so the wallet compares signature timestamps written by two different phones when it decides whether an envelope is a stale leftover or a new invite. Clock skew makes those two cases look the same. A server-side creation time would settle it.Reference
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts(resolveSentStatuses,cancelSentSocialBinding)Acceptance Criteria
Only if either is picked up:
relation_description.Desired Output (may vary)
Sketches, not designs:
social_connectionthat survives validation, with existing documents still parsing without it.