Thank you for the explanations.
Every node using the reference client is a "full node" with a full blockchain history. But, it turns out that you only need a subset of the blockchain for most things, including transaction verification. You can discard transactions after they are spent and just keep the unspent ones*. The set of unspent outputs is relative small, like a couple hundred MB, rather than several GB for the full block history.
Right now, the protocol doesn't allow requesting or sending partial blocks, but they are working on it. That will allow even lighter clients.
* Well, you need rollback history too.
If you had such a lighter client that could request partial blocks, it seems that it would be able to verify the existence of a transaction input (e.g., by checking the Merkle branch and making sure the hashes work) but is there any way that it could verify that the input hadn't already been spent (i.e., in some block that the client doesn't have a copy of)?