Content types Will be unghosted if you have defined them declaratively and then change them from UI!
There are no supported way of detecting if a content type is unghosted, you only can see it indirectly by your changes not being provisioned.
There is unsupported way of doing this, but that includes doing selects directly on your content database, but you should only do this on copies of databases, and not running farms, as this could render your farm in an unsupported state!
My friend Søren Nielsen has created an "Content Type Hierarchy" app here.
The checks that the internal methods do to determine a Content Types state boils down to this:
Is it a feature based content type, by checking “FeatureID != null”, if not mark it as “DB only”. Note that this is actually a private field, so I have to go the long way around and fetch it through reflection.
If it is feature based on the version > 0 then it has been unghosted. The version number seem to work well for
this. It will always be 0 for ghosted
content types and be incremented by
one every time somebody modifies (and
thereby unghost it). According to the
xml schema you can specify this field
in your content type definition, but
it will (fortunately) not make it to
the database so the detection seems
sound. If you make new versions of the
xml file, the server won’t really
notice
Also be sure to read the other two articles on Content Types by Søren here.
While most of the story are the same in SP2010 we do have a couple of new features to help us:
We have some help though, through the OVERWRITE="TRUE" attribute in ContentType element. This will enforce an owerwrite of the site content type.
Feature upgrade also has options to add fields to an existing content type.
In general it is a recommended approach to create your "root level" or base content types as sealed, which basically sets this content type as read only. Then you create your child content types which can be "depricated" when we need to add or delete fields: hide the old feature and create a new content type on the same level in the hierarchy to replace it. By hiding it you make sure you dont loose data (content created by that content type is still there, you just cant add new content with that content type).
Read my related post on Stack Overflow here.