Field API does not yet support revisions. It can easily enough, but we have a few decisions to make.
In D6 CCK, all per-field and per-content type storage tables contain columns for nid and vid but the primary key is vid. Multiple versions all go into the same table.
Since D5, the node and node revisions table have operated differently. The node table stores information about the most current revision, including title, body, and vid, and a variety of housekeeping info (uid, created timestamp, etc.). The node_revisions table stores information about all revisions, but only for the parts of a node row that change between revisions.
David Strauss proposed that we change Field storage to be more like core does it. Namely, the main field tables contain only data about the most current version. A separate paired revision table stores information about all revisions. The advantages of this arrangement are that the primary field tables are smaller and faster, and the revision tables can potentially be treated in some way optimized for other purposes (e.g. compressed to save space, outside the relational database entirely, etc.). The default implementation would store the data normally, just in a different table.
We decided that this seemed like a good idea with minimal downsides. However, I realized it leads to a problem. When (e.g.) the node module calls field_attach_load('node', $node), and $node contains nid and vid, the Field API needs to know whether to look in the primary table or the revision table for the data (or else it has to query both, which is baaaaad).
node_load decides whether to query the node_revisions table based on whether a vid query parameter is passed to the function. However, field_attach_load() is designed to accept a fully populated object, and if an object class (like node) has a revision attribute, presumably it will be present in the object even when loading the most current revision.
The solution that occurs to me is for field_attach_load() to always read from the primary table, and for a new API function, field_attach_load_revision(), to read from the revision table. Object classes that are not versioned, or that know (as node_load() does) that they want the current version, will call field_attach_load(). Requests that are for a past version will call feild_attach_load_revision().
Comments?

Comments
I guess it could be
I guess it could be either
1) a separate field_attach_load_revision() function,
2) an optional flag argument on the existing field_attach_load() function,
3) a special flag property on the $object : something like $object->not_current_revision = TRUE (er, with a better name for the flag, of course). Variant : always add a $object->current_vid property, and reason on whether $object->current_vid == $object->vid.
3) has the advantage of not being a field-only fix : it brings the ability for non-field-based node addtions (meaning 'nodeapi modules as we currently know them') to implement the same db optimisation (move revisioned data to a different table) for their own storage if they want.
node_load_revision() too then?
There's a patch to separate as much of revisions from node module as possible here: http://drupal.org/node/120967 - one option there would be a separate node_load_revision() function just for loading revisions (which could use field_attach_load_revision() instead of field_attach_load() for example). The revision handling in both 6 and 7 is somewhat tacked on to node_load() - i.e. revisions aren't included in the static cache and it's pretty much a lot of special cases inside node_load which could easily be factored out to a different function entirely. This would have the advantage of bringing node/user/term/file loading a further step closer to each other - almost to the point where they could share 70-80% of their code once multiple load is fully applied to each.
Questions and thoughts
What happens when you call *_load_revision() and the revision ID is the current revision? Or do only old revisions get "revision IDs"? It's an entirely reasonable design for the current value to not have a revision ID.
Do we store the current revision in the revision archive (treating the current field value table as a write-through cache) or only store non-current revisions in the archive tables? I'm guessing the latter, but I just want to confirm.
On a related note, one of the first things I'd try for optimizing the revision archive tables is to have the field values for old revisions stored as revision ranges. This would have the nice property of only inserting new rows for changed values. Most rows on the entity with fields would only get updated to reflect their continued value. It would also have the nice property of allowing efficient per-field diffs.
My plan is for writes to
My plan is for writes to write to both the current-value table and the revision-archive table, every time. Very simple and easy to understand.
The alternative is that when writing the current value to the current-value table we first have to copy what is presently in the current-value table into the revision-archive table. With the alternative, we do not save any queries, and the logic is more complex. I do not see any advantages.
If the current version is always written to the revision-archive table, then field_attach_load_revision() on the current revision works fine. No special-case logic needed.
Revision ranges for the archive tables is an interesting idea but not something we should target for the initial commit. Implementation thoughts: Every write would have to become "read the previous revision from the current value table; read the previous table from the revision archive table; update the current value table; compare to the new value and the previous value, and if different insert a new row into the revision archive table or update the from/to range on the existing row." So, it requires an additional two SELECTs or one SELECT/JOIN per write.