The problem has been traced to Broker side. The SubFlushRunner or QueueRunner are seen to continue running after the processing of SessionDetach command by the Broker. On the slower CI box, we see these threads occasionally go on to emit a SessionFlush, which is rightfully rejected by the client with the ProtocolViolationException shown above.
My proposed solution is to change the Broker so that any remaining subscriptions are unregistered on receipt of SessionDetach (ServerSession#unregister()). Since the unregister() method requires the sendlock (the lock held by SubFlushRunner/QueueRunner whilst delivering), this will cause SessionDetach to await the completion of the in-progress message delivery before detaching the session.
I believe this approach is consistent with "3.6.3. Detaching cleanly" of the AMQP 0-10 spec.