Must a block contain a coinbase transaction? If so, how is this enforced?
Yes, it must.
Block without coinbase transaction will be rejected by network.
From the Protocol, rule #6:
First transaction must be coinbase (i.e. only 1 input, with hash=0, n=-1), the rest must not be
Every block must include one or more transactions. The first one of these transactions must be a coinbase transaction which should collect and spend the block reward and any transaction fees paid by transactions included in this block.
Bitcoin Foundation Developer Guide— https://bitcoin.org/en/developer-guide#transaction-data