and when played out of order, it's guaranteed to resolve to foobaz eventually or immediately, depending on when messages are received
when you encounter the scenario of a fork, there's usually a fork resolution rule, e.g. D: { "prev_hash": "<hash of B>", "content": "foobazbar" }
to resolve C vs D, sort lexicographically, choose direction of sort order and pick first
When you have non-continuous data due to messages dropping, e.g. you have B and perhaps an E that builds on C, you can either use the same lexicographic rule, or make the hash basis a combination of timestamp and hash, so you get temporality and lineage.
As for deletes, you have either the single set approach of simply making the message content empty and that _is_ the delete, or you have the 2-phase sets, where there exists an add set and a delete set.
Quite a few ways to approach it, but commutativity can be readily preserved.