simple "scientific" thoughts. It's like databases triggers. Db triggers apply to the "onupdate" event, i.e. they let you use a set of just updated fields AND the fields that are going to be updated. Web2py has to intercept the update before or after, because databases (the python dbapi in general) don't expose that functionality. I rely on database triggers most of the times (as soon as I have access to the underlying database), but that's just because for simple things I'm faster on coding database triggers than python functions.
However, with after_update, you can't "scientific-ly" know the values the rows had before the update, because the update has "already happened". The "stack" works (I have in production several apps relying on "web2py's triggers"). After all, all your db(something).update() pass to the same function that applies - conditionally - the triggers. PS: if the update fails, then the trigger fails too if you don't do a db.commit() in the "trigger" itself. All web2py's operations are handled in transactions, so it's "safe". Moreover, all the before_* triggers have the "feature" that if that function call returns "True" the update is not done.... kinda of an additional validation (I use that in business logics, e.g., "you can't delete the default mapping for a particular product category") --