Summary

  • In RFC 9610, a ContactCard must belong to at least one AddressBook but may belong to several; removing one membership is therefore different from destroying the card.
  • A group stores member UIDs. If access to the matching card is temporarily lost, the member disappears from the group view while the unresolved UID is preserved and can resolve again later.
  • A defensible deletion claim needs the account, server object ID, UID, pre-change membership set, exact /set arguments, response and a later authoritative lookup—not an empty screen.

The empty address book made a stronger claim than the protocol

Imagine an operator deleting a project address book at the end of a contract. The client returns to the list, shows no cards, and records “contacts deleted” in an audit note. Months later, one of the same contacts appears in a different address book. Was the deletion reversed, did a backup restore the record, or did the earlier evidence never establish destruction?

RFC 9610 supplies a less dramatic explanation. An AddressBook is a named collection. A ContactCard may belong to more than one such collection. If the project book was destroyed with onDestroyRemoveContents set to true, each card was removed from that book, but only a card with no other AddressBook membership was itself destroyed. The interface could be empty and the operation successful while the card remained exactly where another collection still required it.

That distinction is not UI trivia. It determines whether an audit describes a view, a membership edge or an object lifecycle event. Conflating them turns a clean screen into a false erasure certificate.

The protocol defines a collection, not a container that owns its contents

RFC 9610 models an Account with zero or more AddressBooks. Each AddressBook contains zero or more ContactCards, and every live ContactCard belongs to one or more AddressBooks. Its addressBookIds property is a nonempty set until the card is destroyed.

The model is deliberately many-to-many on the card side. A single representation of a supplier can appear in “Operations”, “Renewals” and “Incident contacts” without being copied three times. Removing the “Renewals” membership changes one relation. It does not silently settle the object’s existence in the other two collections.

This is a useful example of a narrow shared rule. The protocol standardizes the minimum state needed for interoperable synchronization. It does not allow an application to convert its chosen visual metaphor—folder, book, tag or workspace—into authority over every occurrence of a contact.

onDestroyRemoveContents is a conditional cascade, not a universal purge

The default value of onDestroyRemoveContents is false. A request to destroy a nonempty AddressBook is then rejected with addressBookHasContents. The rejection is evidence that the server refused the collection destruction under those arguments; it is not evidence that any card was retained everywhere forever.

When the argument is true, the server removes the AddressBook from the cards that belonged to it. A card is destroyed only if it belongs to no other AddressBook. This creates three possible results inside one request: the book can be destroyed, some cards can survive through other memberships, and orphaned cards can be destroyed.

An audit field that records only “cascade=true” loses the decisive predicate. The question is not whether cascading was requested, but what each card’s membership set was at the transition and what the server reported. A destructive-sounding option remains conditional on object state.

The server ID and the UID answer different identity questions

A ContactCard has an immutable, server-set id. It also carries the JSContact uid, and RFC 9610 expressly permits those values to differ. Within one Account, no more than one ContactCard may have the same UID.

The server ID identifies the JMAP object on which /get, /set and /changes operate. The UID supports contact identity across the JSContact representation and is the value used by group membership. If a log retains only the display name, neither identity survives. If it retains only the UID, it may miss which server object and Account were changed. If it retains only the server ID, it may miss continuity that a group or another account expresses through the UID.

Good evidence stores both, while refusing to claim that either is the human being. A card can be destroyed without deleting messages, exports, devices, CRM records or the person it describes.

A group preserves what the current principal cannot see

The group mechanism makes the visibility boundary explicit. A group ContactCard stores a set of member UIDs. A client looks for matching cards across accounts to which it currently has access. If a UID cannot be found, the client should ignore it for the displayed group but preserve it.

RFC 9610 gives a concrete example: a user adds contacts from a shared address book to a private group, then temporarily loses access to the shared book. The contacts disappear from the group because their UIDs cannot be resolved. When access returns, the preserved UIDs resolve and the contacts reappear.

Nothing had to be restored. The unresolved reference persisted throughout. A screenshot taken during the inaccessible interval proves only what that principal could resolve at that moment. It cannot prove that the referenced card was destroyed, that another principal could not see it, or that the UID had been removed from the group.

Query absence is a scoped observation

RFC 9610 defines an inAddressBook filter. A card that does not match that filter is not in the selected AddressBook. It may still exist in the Account, belong to another book, or be outside the caller’s current accessible surface.

The same discipline applies to state synchronization. JMAP state strings and /changes responses make server transitions tractable, but they remain scoped to an Account and data type. A client must retain the relevant old state, follow changes to the new state, inspect created, updated and destroyed IDs, and reconcile uncertain results with /get. “The row vanished” is not a substitute for that chain.

Even a confirmed destroyed ContactCard has a bounded meaning: the object no longer exists in that JMAP Account. It does not establish deletion from backups, other accounts, copied cards, address exports, mail history or independent systems.

The default-book request contains another warning about inferred success

onSuccessSetIsDefault asks the server to set an AddressBook as default after all other changes succeed. Yet if the ID is not found, or server policy does not permit the change, the server ignores the request and returns no error for that failure. The current default remains.

This is not the article’s deletion mechanism, but it reinforces the evidentiary rule. A submitted intention is not an observed server-set result. Clients must read the returned objects or fetch current state before displaying success. The command name, button label and request payload are evidence of an attempt; authoritative state is evidence of the outcome.

A deletion receipt needs a chain, not a color

A defensible record begins with the Account ID, ContactCard server ID and UID. It captures the complete pre-operation addressBookIds set and any relevant group UID references. It preserves whether the request destroyed an AddressBook or the ContactCard itself, the exact onDestroyRemoveContents value, the /set response including destroyed, notDestroyed, updated and error fields, and the resulting state token.

The verifier then performs a current lookup in the correct Account. The conclusion should be one of several bounded statements: removed from this collection; not currently resolvable to this principal; ContactCard destroyed in this Account; or not established. “The person was erased” is not a conclusion RFC 9610 can supply.

This is the reality-layer discipline applied to contacts. Interface absence, relationship removal, object destruction and real-world erasure occupy different layers. Reliable systems keep the receipts separate because operators, regulators and users make different decisions from each one.

Sources