3434import com .metaobjects .origin .PassthroughOrigin ;
3535import com .metaobjects .relationship .MetaRelationship ;
3636import com .metaobjects .source .MetaSource ;
37+ import com .metaobjects .source .ResolvedSource ;
3738import com .metaobjects .template .MetaTemplate ;
3839import com .metaobjects .template .OutputTemplate ;
3940import com .metaobjects .template .PromptTemplate ;
@@ -572,11 +573,11 @@ private static void validateOriginNode(MetaRoot root, MetaObject obj,
572573 + ": missing @from." ,
573574 ErrorCode .ERR_INVALID_ORIGIN , origin .getSource ());
574575 }
575- validateFromOrOfPath (from , root , obj . getName () , field .getName (),
576+ validateFromOrOfPath (from , root , obj , field .getName (),
576577 "origin.passthrough.@from" , origin .getSource ());
577578 String via = origin .getVia ();
578579 if (via != null && !via .isEmpty ()) {
579- validateViaPath (via , root , obj . getName () , field .getName (), origin .getSource ());
580+ validateViaPath (via , root , obj , field .getName (), origin .getSource ());
580581 }
581582 return ;
582583 }
@@ -603,7 +604,7 @@ private static void validateOriginNode(MetaRoot root, MetaObject obj,
603604 + ": missing @of." ,
604605 ErrorCode .ERR_INVALID_ORIGIN , origin .getSource ());
605606 }
606- validateFromOrOfPath (of , root , obj . getName () , field .getName (),
607+ validateFromOrOfPath (of , root , obj , field .getName (),
607608 "origin.aggregate.@of" , origin .getSource ());
608609
609610 String via = origin .getVia ();
@@ -614,7 +615,7 @@ private static void validateOriginNode(MetaRoot root, MetaObject obj,
614615 + ": missing @via (aggregates require a relationship path)." ,
615616 ErrorCode .ERR_INVALID_ORIGIN , origin .getSource ());
616617 }
617- validateViaPath (via , root , obj . getName () , field .getName (), origin .getSource ());
618+ validateViaPath (via , root , obj , field .getName (), origin .getSource ());
618619 return ;
619620 }
620621
@@ -1068,11 +1069,14 @@ private static void validateTemplateNode(MetaRoot root, MetaTemplate template) {
10681069
10691070 MetaObject payloadVo = findRootObject (root , payloadRef );
10701071 if (payloadVo == null || !MetaObject .SUBTYPE_VALUE .equals (payloadVo .getSubType ())) {
1072+ // FR5d — @payloadRef is a reference; emit format=resolved with
1073+ // referrer=template FQN, target=the unresolved payloadRef string.
10711074 throw new MetaDataException (
10721075 ErrorMessageConstants .ERR_INVALID_TEMPLATE
10731076 + ": template '" + template .getName () + "' @payloadRef '" + payloadRef
10741077 + "' does not resolve to an object.value at root" ,
1075- ErrorCode .ERR_INVALID_TEMPLATE , template .getSource ());
1078+ ErrorCode .ERR_INVALID_TEMPLATE ,
1079+ ResolvedSource .from (template .getSource (), template .getName (), payloadRef ));
10761080 }
10771081
10781082 // R3 — every @requiredSlots member must be a field on the payload VO
@@ -1084,12 +1088,17 @@ private static void validateTemplateNode(MetaRoot root, MetaTemplate template) {
10841088 for (String slot : required ) {
10851089 if (slot == null || slot .isEmpty ()) continue ;
10861090 if (!available .contains (slot )) {
1091+ // FR5d — @requiredSlots is a field-on-payload reference; emit
1092+ // format=resolved with referrer=template FQN, target=`payloadRef.slot`
1093+ // (the dotted ref that did not resolve to a payload field).
10871094 throw new MetaDataException (
10881095 ErrorMessageConstants .ERR_INVALID_TEMPLATE
10891096 + ": template.prompt '" + template .getName ()
10901097 + "' @requiredSlots includes '" + slot
10911098 + "' which is not a field on payload '" + payloadRef + "'" ,
1092- ErrorCode .ERR_INVALID_TEMPLATE , template .getSource ());
1099+ ErrorCode .ERR_INVALID_TEMPLATE ,
1100+ ResolvedSource .from (template .getSource (), template .getName (),
1101+ payloadRef + "." + slot ));
10931102 }
10941103 }
10951104 }
@@ -1112,31 +1121,48 @@ private static Set<String> collectPayloadFieldNames(MetaObject payloadVo) {
11121121 * and aggregate's @of). The entity must exist at the root, and the field
11131122 * must exist on that entity (inherited fields are included via the standard
11141123 * children() traversal).
1124+ *
1125+ * <p>FR5d: emits {@code format=resolved} envelopes for every throw. The
1126+ * referrer FQN format is {@code "<projection-FQN>::<fieldName>"} (mirrors
1127+ * the TS {@code _validateFromPath} contract); the target is the bad ref
1128+ * string itself.</p>
1129+ *
1130+ * @param projection the projection node that owns the field carrying the origin
11151131 */
11161132 private static void validateFromOrOfPath (String pathAttr , MetaRoot root ,
1117- String projectionName , String fieldName ,
1133+ MetaObject projection , String fieldName ,
11181134 String label ,
11191135 com .metaobjects .source .ErrorSource envelope ) {
1136+ // FR5d — referrer is `<projection-FQN>::<fieldName>` (the canonical
1137+ // "where the broken reference lives" identifier).
1138+ String projectionName = projection .getName ();
1139+ String referrer = projection .getName () + "::" + fieldName ;
11201140 int dotIdx = pathAttr .indexOf ('.' );
11211141 if (dotIdx < 1 || dotIdx == pathAttr .length () - 1 ) {
1142+ // Malformed shape (not "Entity.field") — not a reference resolution
1143+ // failure per se, but emit format=resolved with target=the bad string
1144+ // so consumers see the same envelope shape across all FR5d sites.
11221145 throw new MetaDataException (
11231146 ErrorMessageConstants .ERR_INVALID_ORIGIN
11241147 + ": " + label + " \" " + pathAttr + "\" on "
11251148 + projectionName + "." + fieldName
11261149 + ": must be of form \" Entity.field\" ." ,
1127- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1150+ ErrorCode .ERR_INVALID_ORIGIN ,
1151+ ResolvedSource .from (envelope , referrer , pathAttr ));
11281152 }
11291153 String entityName = pathAttr .substring (0 , dotIdx );
11301154 String targetFieldName = pathAttr .substring (dotIdx + 1 );
11311155
11321156 MetaObject sourceObj = findRootObject (root , entityName );
11331157 if (sourceObj == null ) {
1158+ // FR5d — entity half of the ref didn't resolve. target = full ref.
11341159 throw new MetaDataException (
11351160 ErrorMessageConstants .ERR_INVALID_ORIGIN
11361161 + ": " + label + " \" " + pathAttr + "\" on "
11371162 + projectionName + "." + fieldName
11381163 + ": no such entity \" " + entityName + "\" ." ,
1139- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1164+ ErrorCode .ERR_INVALID_ORIGIN ,
1165+ ResolvedSource .from (envelope , referrer , pathAttr ));
11401166 }
11411167
11421168 // Inherited fields included — getChildren(..., true) walks super data.
@@ -1148,13 +1174,15 @@ private static void validateFromOrOfPath(String pathAttr, MetaRoot root,
11481174 }
11491175 }
11501176 if (!fieldExists ) {
1177+ // FR5d — entity resolved, field on it did not. target = full ref.
11511178 throw new MetaDataException (
11521179 ErrorMessageConstants .ERR_INVALID_ORIGIN
11531180 + ": " + label + " \" " + pathAttr + "\" on "
11541181 + projectionName + "." + fieldName
11551182 + ": no such field \" " + targetFieldName
11561183 + "\" on " + entityName + "." ,
1157- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1184+ ErrorCode .ERR_INVALID_ORIGIN ,
1185+ ResolvedSource .from (envelope , referrer , pathAttr ));
11581186 }
11591187 }
11601188
@@ -1166,16 +1194,20 @@ private static void validateFromOrOfPath(String pathAttr, MetaRoot root,
11661194 * which becomes the next hop's current entity.
11671195 */
11681196 private static void validateViaPath (String viaAttr , MetaRoot root ,
1169- String projectionName , String fieldName ,
1197+ MetaObject projection , String fieldName ,
11701198 com .metaobjects .source .ErrorSource envelope ) {
1199+ // FR5d — referrer is `<projection-FQN>::<fieldName>`.
1200+ String projectionName = projection .getName ();
1201+ String referrer = projection .getName () + "::" + fieldName ;
11711202 String [] segments = viaAttr .split ("\\ ." );
11721203 if (segments .length < 2 ) {
11731204 throw new MetaDataException (
11741205 ErrorMessageConstants .ERR_INVALID_ORIGIN
11751206 + ": origin.@via \" " + viaAttr + "\" on "
11761207 + projectionName + "." + fieldName
11771208 + ": must be of form \" Entity.relationship[.relationship...]\" ." ,
1178- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1209+ ErrorCode .ERR_INVALID_ORIGIN ,
1210+ ResolvedSource .from (envelope , referrer , viaAttr ));
11791211 }
11801212 String entityName = segments [0 ];
11811213 MetaObject currentObj = findRootObject (root , entityName );
@@ -1185,19 +1217,30 @@ private static void validateViaPath(String viaAttr, MetaRoot root,
11851217 + ": origin.@via \" " + viaAttr + "\" on "
11861218 + projectionName + "." + fieldName
11871219 + ": no such entity \" " + entityName + "\" ." ,
1188- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1189- }
1220+ ErrorCode .ERR_INVALID_ORIGIN ,
1221+ ResolvedSource .from (envelope , referrer , viaAttr ));
1222+ }
1223+ // FR5d — track the deepest-valid-prefix as we walk. The prefix grows
1224+ // segment-by-segment; on a hop failure the error message names the prefix
1225+ // that DID resolve, so authors can fix multi-hop typos quickly. After the
1226+ // entity lookup above, the deepest valid prefix is just the entity name;
1227+ // each successful relationship hop appends a segment.
1228+ java .util .List <String > validSegments = new java .util .ArrayList <>();
1229+ validSegments .add (entityName );
11901230 for (int i = 1 ; i < segments .length ; i ++) {
11911231 String relName = segments [i ];
11921232 MetaRelationship rel = findRelationship (currentObj , relName );
11931233 if (rel == null ) {
1234+ String prefix = String .join ("." , validSegments );
11941235 throw new MetaDataException (
11951236 ErrorMessageConstants .ERR_INVALID_ORIGIN
11961237 + ": origin.@via \" " + viaAttr + "\" on "
11971238 + projectionName + "." + fieldName
11981239 + ": no such relationship \" " + relName
1199- + "\" on " + currentObj .getName () + "." ,
1200- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1240+ + "\" on " + currentObj .getName () + ". "
1241+ + "Deepest valid prefix was \" " + prefix + "\" ." ,
1242+ ErrorCode .ERR_INVALID_ORIGIN ,
1243+ ResolvedSource .from (envelope , referrer , viaAttr ));
12011244 }
12021245 String refTarget = rel .hasMetaAttr (MetaRelationship .ATTR_OBJECT_REF )
12031246 ? rel .getMetaAttr (MetaRelationship .ATTR_OBJECT_REF ).getValueAsString ()
@@ -1209,18 +1252,25 @@ private static void validateViaPath(String viaAttr, MetaRoot root,
12091252 + projectionName + "." + fieldName
12101253 + ": relationship \" " + relName + "\" on "
12111254 + currentObj .getName () + " is missing @objectRef." ,
1212- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1255+ ErrorCode .ERR_INVALID_ORIGIN ,
1256+ ResolvedSource .from (envelope , referrer , viaAttr ));
12131257 }
12141258 MetaObject nextObj = findRootObject (root , refTarget );
12151259 if (nextObj == null ) {
1260+ // FR5d — relationship's @objectRef points at a missing entity. This
1261+ // is the @objectRef-resolution edge of the via-path walk (the "5th
1262+ // site" in FR5d's scope list for @objectRef references encountered
1263+ // transitively). Target = the @objectRef value (the missing entity name).
12161264 throw new MetaDataException (
12171265 ErrorMessageConstants .ERR_INVALID_ORIGIN
12181266 + ": origin.@via \" " + viaAttr + "\" on "
12191267 + projectionName + "." + fieldName
12201268 + ": relationship \" " + relName
12211269 + "\" points to non-existent entity \" " + refTarget + "\" ." ,
1222- ErrorCode .ERR_INVALID_ORIGIN , envelope );
1270+ ErrorCode .ERR_INVALID_ORIGIN ,
1271+ ResolvedSource .from (envelope , referrer , refTarget ));
12231272 }
1273+ validSegments .add (relName );
12241274 currentObj = nextObj ;
12251275 }
12261276 }
0 commit comments