ContentDescriptor.fields is a flat Record<Field["id"], Field> keyed by PropertyField.computeId, which derives identity from the declaring class + property name + relationship path only. There is no content-source / target component in the key. When several content sources (one per selected class) surface a property declared on a shared ancestor, those occurrences collapse into a single fields entry. Any per-source decision — most importantly a propertyOverrides entry scoped to one class — then leaks onto every other source that shares the field.
Worked example (the requirement)
Two selected classes, Stuff.Door and Stuff.Window, both derive from Stuff.Thing, which declares Height. Today the descriptor contains exactly one field:
id: "Stuff.Thing.Height"
sourceClassName: "Stuff.Thing" // declaring class
propertyName: "Height"
pathFromTarget: []
Desired behavior when a content modifier overrides Height specifically for Window (e.g. relabel/hide):
Door keeps an unmodified Height field.
Window gets a separate Height field carrying the override.
i.e. the descriptor must be able to represent two fields for the same declared property when — and only when — a per-source decision makes them diverge. While they remain identical (no scoped override), they stay merged as one field.
Worked example 2 (the harder requirement: related, polymorphic endpoint)
A fields provider loads related properties over bis.Element → bis.ExternalSourceAspect, surfacing Identifier (declared on the base ExternalSourceAspect). In the data, elements actually carry aspects of two concrete subclasses, ExternalSourceAspectX and ExternalSourceAspectY. Today that is a single field:
id: "BisCore.ExternalSourceAspect.Identifier(<path>)"
sourceClassName: "BisCore.ExternalSourceAspect" // declaring class
propertyName: "Identifier"
pathFromTarget: [ Element→ExternalSourceAspect ]
Desired behavior when a modifier overrides Identifier specifically for ExternalSourceAspectX:
- The
ExternalSourceAspectY occurrence keeps an unmodified Identifier.
- The
ExternalSourceAspectX occurrence gets a separate Identifier field carrying the override.
The divergence axis here is not the selected primary class (Door/Window) — it is the concrete class of the related instance that supplies the value (ExternalSourceAspectX vs ExternalSourceAspectY). The provenance model must therefore key on the value-supplier class, which unifies both worked examples: for a direct property the value-supplier is the primary instance; for a related property it is the instance at the path's terminal end.
This is a prerequisite for implementing a descriptor transformer that can handle legacy content modifiers - propertyOverrides transformer cannot be made correct until the descriptor model can carry per-value-supplier provenance and split a shared field.
ContentDescriptor.fieldsis a flatRecord<Field["id"], Field>keyed byPropertyField.computeId, which derives identity from the declaring class + property name + relationship path only. There is no content-source / target component in the key. When several content sources (one per selected class) surface a property declared on a shared ancestor, those occurrences collapse into a singlefieldsentry. Any per-source decision — most importantly apropertyOverridesentry scoped to one class — then leaks onto every other source that shares the field.Worked example (the requirement)
Two selected classes,
Stuff.DoorandStuff.Window, both derive fromStuff.Thing, which declaresHeight. Today the descriptor contains exactly one field:Desired behavior when a content modifier overrides
Heightspecifically forWindow(e.g. relabel/hide):Doorkeeps an unmodifiedHeightfield.Windowgets a separateHeightfield carrying the override.i.e. the descriptor must be able to represent two fields for the same declared property when — and only when — a per-source decision makes them diverge. While they remain identical (no scoped override), they stay merged as one field.
Worked example 2 (the harder requirement: related, polymorphic endpoint)
A fields provider loads related properties over
bis.Element → bis.ExternalSourceAspect, surfacingIdentifier(declared on the baseExternalSourceAspect). In the data, elements actually carry aspects of two concrete subclasses,ExternalSourceAspectXandExternalSourceAspectY. Today that is a single field:Desired behavior when a modifier overrides
Identifierspecifically forExternalSourceAspectX:ExternalSourceAspectYoccurrence keeps an unmodifiedIdentifier.ExternalSourceAspectXoccurrence gets a separateIdentifierfield carrying the override.The divergence axis here is not the selected primary class (Door/Window) — it is the concrete class of the related instance that supplies the value (
ExternalSourceAspectXvsExternalSourceAspectY). The provenance model must therefore key on the value-supplier class, which unifies both worked examples: for a direct property the value-supplier is the primary instance; for a related property it is the instance at the path's terminal end.This is a prerequisite for implementing a descriptor transformer that can handle legacy content modifiers -
propertyOverridestransformer cannot be made correct until the descriptor model can carry per-value-supplier provenance and split a shared field.