edit: I've done some investigation and digging and I found my answer. I'll let this thread die since it's actually easier to search for info on the topic than I had realized. But I will also share my best links in case someone else searching for the same answer stumbles across this.
Milesluigi has a detailed video showing how to spawnproof a rail system in the nether. It's old but should work in pretty much any version of Minecraft.
This post on reddit says mobs no longer spawn on carpets, so there may be other blocks that can be used, such as snow layering. Also the new grass path blocks give a way to spawnproof and they should always work the same as half slabs even if the change is reverted and carpets stop working, because they are the same kind of block as half slabs--transparent with a shortened hitbox.
Here's a video demonstrating half-slab spawn-proofing.
Originally, mobs could not spawn on top of upper slabs, but now they can. They still cannot spawn on top of lower slabs.
I have been told and also read that mobs can't spawn on half-slabs, but it seems like they are spawning on them in my game. I've got a minecart rail system that is enclosed, and it keeps getting zombie pigmen spawning in it. Can anyone tell me how/why/where they are spawning?
A mob can only spawn in a certain position if the block below the spawn block has a solid top surface. Blocks with a slab in the top half of the block-space have a solid top surface. Blocks which only have a slab in the bottom half, do not.
Mobs spawning on upper half-slabs is nothing new; I'm not sure why the second video is claiming that mobs can now spawn on top of half-slabs as of 1.7 when they could in older versions, likely far older than 1.6.4:
Likewise, this is also true for stairs, hoppers (claimed here to be a 1.9+ mechanic, but this proves otherwise), and 8-high snow layers (1-high snow layers also allow mobs to spawn but due to the fact that prior to 1.8 the pack center had to be an air block it inhibits pack spawning, entirely in Superflat with a decreased spawn rate in normal worlds):
* Performs check to see if the block is a normal, solid block, or if the metadata of the block indicates
* that its facing puts its solid side upwards. (inverted stairs, for example)
public boolean isBlockTopFacingSurfaceSolid(Block par1Block, int par2)
return par1Block == null ? false : (par1Block.blockMaterial.isOpaque() && par1Block.renderAsNormalBlock() ? true : (par1Block instanceof BlockStairs ? (par2 & 4) == 4 : (par1Block instanceof BlockHalfSlab ? (par2 & 8) == 8 : (par1Block instanceof BlockHopper ? true : (par1Block instanceof BlockSnow ? (par2 & 7) == 7 : false)))));