@@ -712,6 +712,134 @@ public Collection<CommonAttributeDef> getCommonAttributes() {
712712 return List .copyOf (commonAttributes .values ());
713713 }
714714
715+ /**
716+ * FR-033 (sub-step B1) — source every type / attr / common-attr <em>description</em>
717+ * (+ optional {@code rules}/{@code example}/{@code whenToUse}) from the shared
718+ * {@code spec/metamodel/*.json} (the cross-port single source of truth, read by
719+ * {@link com.metaobjects.registry.spec.SpecMetamodelReader}) onto this registry.
720+ *
721+ * <p>Applied DURING composition, BEFORE {@link #seal()} (the type definitions are
722+ * immutable, so each {@link TypeDefinition} carrying a JSON description is rebuilt
723+ * and re-put; each attr {@link ChildRequirement} that the JSON describes is copied
724+ * with its {@code docDescription}; each {@link CommonAttributeDef} is replaced by a
725+ * copy carrying the universal {@code *.*} description). Descriptions come from the
726+ * JSON ONLY — never hand-copied — so they are byte-identical to the TS reference.</p>
727+ *
728+ * <p>Where Java registers an attr the JSON does NOT describe for that subtype (or
729+ * vice-versa) this is left untouched — that scoping mismatch is reconciled in
730+ * sub-step B2; B1 applies descriptions only where they match.</p>
731+ *
732+ * @param reader the parsed embedded spec/metamodel reader
733+ */
734+ public synchronized void applySpecDescriptions (com .metaobjects .registry .spec .SpecMetamodelReader reader ) {
735+ Objects .requireNonNull (reader , "reader must not be null" );
736+ checkNotSealed ("applySpecDescriptions" );
737+
738+ // Pass 1 — rebuild every TypeDefinition with the JSON type docs + per-attr
739+ // doc descriptions on its DIRECT (+ wildcard) child requirements.
740+ for (Map .Entry <MetaDataTypeId , TypeDefinition > entry : new ArrayList <>(typeDefinitions .entrySet ())) {
741+ MetaDataTypeId id = entry .getKey ();
742+ TypeDefinition def = entry .getValue ();
743+
744+ com .metaobjects .registry .spec .SpecMetamodelReader .DocFacet typeDoc =
745+ reader .typeDoc (id .type (), id .subType ());
746+ String description = (typeDoc != null && typeDoc .description () != null )
747+ ? typeDoc .description () : def .getDescription ();
748+ String rules = typeDoc != null ? typeDoc .rules () : def .getRules ();
749+ String example = typeDoc != null ? typeDoc .example () : def .getExample ();
750+ String whenToUse = typeDoc != null ? typeDoc .whenToUse () : def .getWhenToUse ();
751+
752+ // Rebuild the DIRECT child requirements, threading attr doc descriptions.
753+ Map <String , ChildRequirement > directReqs = new LinkedHashMap <>();
754+ for (ChildRequirement req : def .getDirectChildRequirements ()) {
755+ ChildRequirement rebuilt = req ;
756+ if (MetaAttribute .TYPE_ATTR .equals (req .getExpectedType ())
757+ && req .getName () != null && !"*" .equals (req .getName ())) {
758+ com .metaobjects .registry .spec .SpecMetamodelReader .AttrEntry attrDoc =
759+ reader .attrDoc (id .type (), id .subType (), req .getName ());
760+ if (attrDoc != null && attrDoc .description () != null ) {
761+ rebuilt = req .withDocDescription (attrDoc .description ());
762+ }
763+ }
764+ String key = rebuilt .getName ();
765+ if (key == null || "*" .equals (key )) {
766+ key = "*:" + rebuilt .getExpectedType () + ":" + rebuilt .getExpectedSubType ();
767+ }
768+ directReqs .put (key , rebuilt );
769+ }
770+
771+ TypeDefinition rebuiltDef = new TypeDefinition (
772+ def .getImplementationClass (), id .type (), id .subType (), description ,
773+ directReqs , def .getParentType (), def .getParentSubType (),
774+ rules , example , whenToUse , def .getParents ());
775+ typeDefinitions .put (id , rebuiltDef );
776+ }
777+
778+ // Pass 2 — re-resolve inheritance so each child re-inherits the REBUILT parent
779+ // requirements (carrying descriptions). Crucially, an inherited attr req is
780+ // re-described against the CHILD's own (type, subType): the JSON sometimes
781+ // scopes an attr to a concrete subtype (e.g. @discriminator on object.entity)
782+ // while Java declares it on the base (object.base) and inherits it. Describing
783+ // the inherited copy by the child subtype lands the JSON description on the
784+ // child where the cross-port golden carries it, and correctly leaves it empty
785+ // on a sibling the JSON does NOT scope it to. Where Java's inheritance direction
786+ // disagrees with the JSON scope, that is the B2 scoping reconciliation — B1 only
787+ // applies the description where the JSON declares it for that subtype.
788+ for (TypeDefinition def : new ArrayList <>(typeDefinitions .values ())) {
789+ if (def .hasParent ()) {
790+ TypeDefinition parent = typeDefinitions .get (
791+ new MetaDataTypeId (def .getParentType (), def .getParentSubType ()));
792+ if (parent != null ) {
793+ resolveInheritanceForDefinition (def , parent );
794+ describeInheritedAttrs (def , reader );
795+ }
796+ }
797+ }
798+
799+ // Pass 3 — common-attr descriptions from the universal *.* documentation entry.
800+ for (Map .Entry <String , CommonAttributeDef > e : new ArrayList <>(commonAttributes .entrySet ())) {
801+ com .metaobjects .registry .spec .SpecMetamodelReader .AttrEntry doc =
802+ reader .commonAttrDoc (e .getKey ());
803+ if (doc != null && doc .description () != null ) {
804+ commonAttributes .put (e .getKey (), e .getValue ().withDescription (doc .description ()));
805+ }
806+ }
807+ }
808+
809+ /**
810+ * FR-033 (sub-step B1) — re-describe a definition's INHERITED attr requirements
811+ * against the definition's OWN {@code (type, subType)}, so a JSON description that
812+ * the spec scopes to a concrete subtype lands on the inherited copy where the
813+ * cross-port golden carries it. Replaces each inherited attr requirement with a
814+ * doc-described copy when the spec describes that attr for this subtype.
815+ */
816+ private void describeInheritedAttrs (TypeDefinition def ,
817+ com .metaobjects .registry .spec .SpecMetamodelReader reader ) {
818+ Map <String , ChildRequirement > inherited = def .getInheritedChildRequirements ();
819+ if (inherited .isEmpty ()) {
820+ return ;
821+ }
822+ Map <String , ChildRequirement > redescribed = new LinkedHashMap <>(inherited );
823+ boolean changed = false ;
824+ for (Map .Entry <String , ChildRequirement > e : inherited .entrySet ()) {
825+ ChildRequirement req = e .getValue ();
826+ if (!MetaAttribute .TYPE_ATTR .equals (req .getExpectedType ())
827+ || req .getName () == null || "*" .equals (req .getName ())) {
828+ continue ;
829+ }
830+ com .metaobjects .registry .spec .SpecMetamodelReader .AttrEntry doc =
831+ reader .attrDoc (def .getType (), def .getSubType (), req .getName ());
832+ if (doc != null && doc .description () != null
833+ && !doc .description ().equals (req .getDocDescription ())) {
834+ redescribed .put (e .getKey (), req .withDocDescription (doc .description ()));
835+ changed = true ;
836+ }
837+ }
838+ if (changed ) {
839+ def .populateInheritedRequirements (redescribed );
840+ }
841+ }
842+
715843 /**
716844 * Lightweight array-shape predicate for common-array-attr validation. Mirrors
717845 * the per-type {@code AttributeConstraintBuilder.isArrayValue} contract:
0 commit comments