Wrap all block-level elements in an invisible container

What is the best way to go about implementing a special node called container which satisfies the following properties:

  1. All block-level elements must exist inside a container.
  2. Splitting a paragraph A inside a container C (i.e. C(A)) yields two new nested containers (i.e. C(C(A1), C(A2))).
  3. containers may contain either one block element OR two or more nested containers.
  4. containers are functionally invisible to the user, e.g. in the previous example, putting the cursor at the start of A2 and pressing backspace should merge the two paragraphs, yielding C(A) (since C(C(A)) is considered invalid), even though the paragraphs are structurally isolated in their own containers. In this case, the outermost container should be retained.

I began by trying to represent these properties in-schema where a container is specified to have content (block | (container{2,}), but this resulted in me being unable to split paragraphs.

The second idea I considered was registering a appendTransaction listener which would restructure the document on the fly to satisfy the above constraints, but this is proving difficult to implement elegantly. Any suggestions on how I might implement a node with such specific behavior are appreciated.