CAUSEWAY-3676: improves handling of abstract input types

... can now specify concrete type when needed using 'logicalTypeName'
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
index 612a61c..3fbe85e 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
@@ -118,7 +118,7 @@
             final Optional<DomainObject> domainObjectIfAny,
             final ProcessObjectTypeContext processClassContext) {
 
-        if(!domainObjectIfAny.isPresent()) {
+        if(domainObjectIfAny.isEmpty()) {
             return;
         }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java
index f036115..73d84df 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvAction.java
@@ -130,6 +130,8 @@
                 .map(oap -> {
                     final ObjectSpecification elementType = oap.getElementType();
                     Object argumentValue = argumentPojos.get(oap.getId());
+                    Object pojoOrPojoList;
+
                     switch (elementType.getBeanSort()) {
 
                         case VALUE:
@@ -140,20 +142,23 @@
                             if (argumentValue == null) {
                                 return ManagedObject.empty(elementType);
                             }
-                            Object pojoOrPojoList;
+                            // fall through
+
+                        case ABSTRACT:
+                            // if the parameter is abstract, we still attempt to figure out the arguments.
+                            // the arguments will need to either use 'ref' or else both 'id' AND 'logicalTypeName'
                             if (argumentValue instanceof List) {
                                 val argumentValueList = (List<Object>) argumentValue;
                                 pojoOrPojoList = argumentValueList.stream()
-                                        .map(value -> asPojo(oap.getElementType(), value, context.bookmarkService, environment))
+                                        .map(value -> asPojo(oap.getElementType(), value, environment, context))
                                         .filter(Optional::isPresent)
                                         .map(Optional::get)
                                         .collect(Collectors.toList());
                             } else {
-                                pojoOrPojoList = asPojo(oap.getElementType(), argumentValue, context.bookmarkService, environment).orElse(null);
+                                pojoOrPojoList = asPojo(oap.getElementType(), argumentValue, environment, context).orElse(null);
                             }
                             return ManagedObject.adaptParameter(oap, pojoOrPojoList);
 
-                        case ABSTRACT:
                         case COLLECTION:
                         case MANAGED_BEAN_CONTRIBUTING:
                         case VETOED:
@@ -185,27 +190,64 @@
     public static Optional<Object> asPojo(
             final ObjectSpecification elementType,
             final Object argumentValueObj,
-            final BookmarkService bookmarkService,
-            final Environment environment) {
+            final Environment environment,
+            final Context context
+    ) {
         val argumentValue = (Map<String, String>) argumentValueObj;
-        String idValue = argumentValue.get("id");
+
+        val refValue = argumentValue.get("ref");
+        if (refValue != null) {
+            String key = GqlvMetaSaveAs.keyFor(refValue);
+            BookmarkedPojo bookmarkedPojo = environment.getGraphQlContext().get(key);
+            if (bookmarkedPojo == null) {
+                throw new IllegalArgumentException(String.format(
+                    "Could not find object referenced '%s' in the execution context; was it saved previously using \"saveAs\" ?", refValue));
+            }
+            val targetPojoClass = bookmarkedPojo.getTargetPojo().getClass();
+            val targetPojoSpec = context.specificationLoader.loadSpecification(targetPojoClass);
+            if (targetPojoSpec == null) {
+                throw new IllegalArgumentException(String.format(
+                    "The object referenced '%s' is not part of the metamodel (has class '%s')",
+                    refValue, targetPojoClass.getCanonicalName()));
+            }
+            if (!elementType.isPojoCompatible(bookmarkedPojo.getTargetPojo())) {
+                throw new IllegalArgumentException(String.format(
+                    "The object referenced '%s' has a type '%s' that is not assignable to the required type '%s'",
+                    refValue, targetPojoSpec.getLogicalTypeName(), elementType.getLogicalTypeName()));
+            }
+            return Optional.of(bookmarkedPojo).map(BookmarkedPojo::getTargetPojo);
+        }
+
+        val idValue = argumentValue.get("id");
         if (idValue != null) {
             Class<?> paramClass = elementType.getCorrespondingClass();
-            Optional<Bookmark> bookmarkIfAny = bookmarkService.bookmarkFor(paramClass, idValue);
+            Optional<Bookmark> bookmarkIfAny;
+            if(elementType.isAbstract()) {
+                val logicalTypeName = argumentValue.get("logicalTypeName");
+                if (logicalTypeName == null) {
+                    throw new IllegalArgumentException(String.format(
+                            "The 'logicalTypeName' is required along with the 'id', because the input type '%s' is abstract",
+                            elementType.getLogicalTypeName()));
+                }
+                if(context.specificationLoader.specForLogicalTypeName(logicalTypeName).isEmpty()) {
+                    throw new IllegalArgumentException(String.format(
+                            "The 'logicalTypeName' of '%s' is unknown in the metamodel",
+                            logicalTypeName));
+                }
+
+                 bookmarkIfAny = Optional.of(Bookmark.forLogicalTypeNameAndIdentifier(logicalTypeName, idValue));
+            } else {
+                bookmarkIfAny = context.bookmarkService.bookmarkFor(paramClass, idValue);
+            }
             return bookmarkIfAny
-                    .map(bookmarkService::lookup)
+                    .map(context.bookmarkService::lookup)
                     .filter(Optional::isPresent)
                     .map(Optional::get);
         }
-        String refValue = argumentValue.get("ref");
-        if (refValue != null) {
-            String key = GqlvMetaSaveAs.keyFor(refValue);
-            BookmarkedPojo value = environment.getGraphQlContext().get(key);
-            return Optional.of(value).map(BookmarkedPojo::getTargetPojo);
-        }
         throw new IllegalArgumentException("Either 'id' or 'ref' must be specified for a DomainObject input type");
     }
 
+
     public void addGqlArguments(
             final ObjectAction objectAction,
             final GraphQLFieldDefinition.Builder builder,
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionInvokeArgsArg.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionInvokeArgsArg.java
index f46436a..f5627da 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionInvokeArgsArg.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvActionInvokeArgsArg.java
@@ -26,8 +26,6 @@
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.causeway.viewer.graphql.model.context.Context;
-import org.apache.causeway.viewer.graphql.model.domain.GqlvAbstract;
-import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
 import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectActionProvider;
 import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectSpecificationProvider;
 
@@ -37,12 +35,8 @@
 import lombok.extern.log4j.Log4j2;
 import lombok.val;
 
-import java.util.Map;
-
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 
-import static org.apache.causeway.viewer.graphql.model.domain.GqlvAction.asPojo;
-
 @Log4j2
 public class GqlvActionInvokeArgsArg
         extends GqlvAbstract {
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
index 64f200e..cc4849c 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvDomainObject.java
@@ -60,7 +60,35 @@
     public static GqlvDomainObject of(
             final ObjectSpecification objectSpecification,
             final Context context) {
-        return context.domainObjectBySpec.computeIfAbsent(objectSpecification, spec -> new GqlvDomainObject(spec, context));
+
+        mapSuperclassesIfNecessary(objectSpecification, context);
+
+        return computeIfAbsentGqlvDomainObject(context, objectSpecification);
+    }
+
+    private static void mapSuperclassesIfNecessary(
+            final ObjectSpecification objectSpecification,
+            final Context context) {
+        // no need to map if the target subclass has already been built
+        if(context.domainObjectBySpec.containsKey(objectSpecification)) {
+            return;
+        }
+        val superclasses = superclassesOf(objectSpecification);
+        superclasses.forEach(objectSpec -> computeIfAbsentGqlvDomainObject(context, objectSpec));
+    }
+
+    private static GqlvDomainObject computeIfAbsentGqlvDomainObject(Context context, ObjectSpecification objectSpec) {
+        return context.domainObjectBySpec.computeIfAbsent(objectSpec, spec -> new GqlvDomainObject(spec, context));
+    }
+
+    private static List<ObjectSpecification> superclassesOf(final ObjectSpecification objectSpecification) {
+        val superclasses = new ArrayList<ObjectSpecification>();
+        ObjectSpecification superclass = objectSpecification.superclass();
+        while (superclass != null && superclass.getCorrespondingClass() != Object.class) {
+            superclasses.add(0, superclass);
+            superclass = superclass.superclass();
+        }
+        return superclasses;
     }
 
     private GqlvDomainObject(
@@ -83,11 +111,19 @@
         inputObjectTypeBuilder
                 .field(newInputObjectField()
                         .name("id")
+                        .description("Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state")
                         .type(Scalars.GraphQLID)
                         .build()
                 )
                 .field(newInputObjectField()
+                        .name("logicalTypeName")
+                        .description("If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class")
+                        .type(Scalars.GraphQLString)
+                        .build()
+                )
+                .field(newInputObjectField()
                         .name("ref")
+                        .description("Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'")
                         .type(Scalars.GraphQLString)
                         .build()
                 )
@@ -156,7 +192,7 @@
     @Override
     protected Object fetchData(DataFetchingEnvironment dataFetchingEnvironment) {
         Object target = dataFetchingEnvironment.getArgument("object");
-        return GqlvAction.asPojo(getObjectSpecification(), target, this.context.bookmarkService, new Environment.For(dataFetchingEnvironment))
+        return GqlvAction.asPojo(getObjectSpecification(), target, new Environment.For(dataFetchingEnvironment), context)
                 .orElse(null);
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForAction.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForAction.java
index 3a92c86..3f89482 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForAction.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForAction.java
@@ -19,6 +19,8 @@
 package org.apache.causeway.viewer.graphql.model.domain;
 
 import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
 
 import graphql.schema.DataFetchingEnvironment;
 import graphql.schema.GraphQLArgument;
@@ -29,6 +31,9 @@
 
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
+
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.annotation.Where;
@@ -126,7 +131,33 @@
             sourcePojo = context.serviceRegistry.lookupServiceElseFail(objectSpec.getCorrespondingClass());
         } else {
             Object target = dataFetchingEnvironment.getArgument(argumentName);
-            sourcePojo = GqlvAction.asPojo(objectSpec, target, context.bookmarkService, environment)
+            Optional<Object> result;
+            val argumentValue = (Map<String, String>) target;
+            String idValue = argumentValue.get("id");
+            if (idValue != null) {
+                String logicalTypeName = argumentValue.get("logicalTypeName");
+                Optional<Bookmark> bookmarkIfAny;
+                if (logicalTypeName != null) {
+                    bookmarkIfAny = Optional.of(Bookmark.forLogicalTypeNameAndIdentifier(logicalTypeName, idValue));
+                } else {
+                    Class<?> paramClass = objectSpec.getCorrespondingClass();
+                    bookmarkIfAny = context.bookmarkService.bookmarkFor(paramClass, idValue);
+                }
+                result = bookmarkIfAny
+                        .map(context.bookmarkService::lookup)
+                        .filter(Optional::isPresent)
+                        .map(Optional::get);
+            } else {
+                String refValue = argumentValue.get("ref");
+                if (refValue != null) {
+                    String key = GqlvMetaSaveAs.keyFor(refValue);
+                    BookmarkedPojo value = ((Environment) environment).getGraphQlContext().get(key);
+                    result = Optional.of(value).map(BookmarkedPojo::getTargetPojo);
+                } else {
+                    throw new IllegalArgumentException("Either 'id' or 'ref' must be specified for a DomainObject input type");
+                }
+            }
+            sourcePojo = result
                     .orElseThrow(); // TODO: better error handling if no such object found.
         }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForProperty.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForProperty.java
index 5320b48..e4c6502 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForProperty.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMutationForProperty.java
@@ -19,6 +19,7 @@
 package org.apache.causeway.viewer.graphql.model.domain;
 
 import java.util.Map;
+import java.util.Optional;
 
 import graphql.schema.DataFetchingEnvironment;
 import graphql.schema.GraphQLArgument;
@@ -28,6 +29,7 @@
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
 
 import org.apache.causeway.applib.annotation.Where;
+import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
@@ -36,6 +38,7 @@
 import org.apache.causeway.viewer.graphql.model.exceptions.DisabledException;
 import org.apache.causeway.viewer.graphql.model.exceptions.HiddenException;
 import org.apache.causeway.viewer.graphql.model.exceptions.InvalidException;
+import org.apache.causeway.viewer.graphql.model.fetcher.BookmarkedPojo;
 import org.apache.causeway.viewer.graphql.model.types.TypeMapper;
 
 import lombok.val;
@@ -80,7 +83,34 @@
 
 
         Object target = dataFetchingEnvironment.getArgument(argumentName);
-        Object sourcePojo = GqlvAction.asPojo(objectSpec, target, context.bookmarkService, new Environment.For(dataFetchingEnvironment))
+        Optional<Object> result;
+        final Environment environment = new Environment.For(dataFetchingEnvironment);
+        val argumentValue1 = (Map<String, String>) target;
+        String idValue = argumentValue1.get("id");
+        if (idValue != null) {
+            String logicalTypeName = argumentValue1.get("logicalTypeName");
+            Optional<Bookmark> bookmarkIfAny;
+            if (logicalTypeName != null) {
+                bookmarkIfAny = Optional.of(Bookmark.forLogicalTypeNameAndIdentifier(logicalTypeName, idValue));
+            } else {
+                Class<?> paramClass = objectSpec.getCorrespondingClass();
+                bookmarkIfAny = context.bookmarkService.bookmarkFor(paramClass, idValue);
+            }
+            result = bookmarkIfAny
+                    .map(context.bookmarkService::lookup)
+                    .filter(Optional::isPresent)
+                    .map(Optional::get);
+        } else {
+            String refValue = argumentValue1.get("ref");
+            if (refValue != null) {
+                String key = GqlvMetaSaveAs.keyFor(refValue);
+                BookmarkedPojo value = environment.getGraphQlContext().get(key);
+                result = Optional.of(value).map(BookmarkedPojo::getTargetPojo);
+            } else {
+                throw new IllegalArgumentException("Either 'id' or 'ref' must be specified for a DomainObject input type");
+            }
+        }
+        Object sourcePojo = result
                     .orElseThrow(); // TODO: better error handling if no such object found.
 
         val managedObject = ManagedObject.adaptSingular(objectSpec, sourcePojo);
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java
index 7b9718e..d082623 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvScenarioStep.java
@@ -25,6 +25,7 @@
             return;
         }
 
+        // add domain object lookup to top-level query
         context.objectSpecifications().forEach(objectSpec -> {
             switch (objectSpec.getBeanSort()) {
 
@@ -32,7 +33,7 @@
                 case VIEW_MODEL: // @DomainObject(nature=VIEW_MODEL)
                 case ENTITY:     // @DomainObject(nature=ENTITY)
 
-                    domainObjects.add(GqlvDomainObject.of(objectSpec, context));
+                    domainObjects.add(addChildFieldFor(GqlvDomainObject.of(objectSpec, context)));
 
                     break;
             }
@@ -41,19 +42,10 @@
         context.objectSpecifications().forEach(objectSpec -> {
             if (Objects.requireNonNull(objectSpec.getBeanSort()) == BeanSort.MANAGED_BEAN_CONTRIBUTING) { // @DomainService
                 context.serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName())
-                        .ifPresent(servicePojo -> {
-                            val gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context);
-                            addChildFieldFor(gqlvDomainService);
-                            domainServices.add(gqlvDomainService);
-                        });
+                        .ifPresent(servicePojo -> domainServices.add(addChildFieldFor(GqlvDomainService.of(objectSpec, servicePojo, context))));
             }
         });
 
-        // add domain object lookup to top-level query
-        for (val gqlvDomainObject : this.domainObjects) {
-            addChildFieldFor(gqlvDomainObject);
-        }
-
         buildObjectType();
     }
 
diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java
index 0dee8a5..ad13080 100644
--- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java
+++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/toplevel/GqlvTopLevelQuery.java
@@ -27,6 +27,7 @@
     public GqlvTopLevelQuery(final Context context) {
         super("Query", context);
 
+        // add domain object lookup to top-level query
         context.objectSpecifications().forEach(objectSpec -> {
             switch (objectSpec.getBeanSort()) {
 
@@ -34,7 +35,7 @@
                 case VIEW_MODEL: // @DomainObject(nature=VIEW_MODEL)
                 case ENTITY:     // @DomainObject(nature=ENTITY)
 
-                    domainObjects.add(GqlvDomainObject.of(objectSpec, context));
+                    domainObjects.add(addChildFieldFor(GqlvDomainObject.of(objectSpec, context)));
 
                     break;
             }
@@ -45,20 +46,13 @@
             switch (objectSpec.getBeanSort()) {
                 case MANAGED_BEAN_CONTRIBUTING: // @DomainService
                     context.serviceRegistry.lookupBeanById(objectSpec.getLogicalTypeName())
-                            .ifPresent(servicePojo -> {
-                                val gqlvDomainService = GqlvDomainService.of(objectSpec, servicePojo, context);
-                                addChildFieldFor(gqlvDomainService);
-                                domainServices.add(gqlvDomainService);
-                            });
+                            .ifPresent(servicePojo ->
+                                    domainServices.add(
+                                            addChildFieldFor(GqlvDomainService.of(objectSpec, servicePojo, context))));
                     break;
             }
         });
 
-        // add domain object lookup to top-level query
-        for (val gqlvDomainObject : this.domainObjects) {
-            addChildFieldFor(gqlvDomainObject);
-        }
-
         addChildFieldFor(scenario = new GqlvScenario(context));
 
         buildObjectType();
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/DeptHead.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/DeptHead.java
index 12ed536..00694e3 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/DeptHead.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/DeptHead.java
@@ -58,7 +58,7 @@
 )
 @DomainObjectLayout(describedAs = "Departmental head, responsible for curriculum, research, funding and staff")
 @NoArgsConstructor
-public class DeptHead implements Comparable<DeptHead> {
+public class DeptHead extends Person implements Comparable<DeptHead>  {
 
     public DeptHead(String name) {
         this.name = name;
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/People.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/People.java
new file mode 100644
index 0000000..197815a
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/People.java
@@ -0,0 +1,36 @@
+package org.apache.causeway.viewer.graphql.viewer.test.domain.dept;
+
+import lombok.RequiredArgsConstructor;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.DomainService;
+import org.apache.causeway.applib.annotation.NatureOfService;
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.SemanticsOf;
+
+@Named("university.dept.People")
+@DomainService(
+        nature= NatureOfService.VIEW)
+@javax.annotation.Priority(PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class People {
+
+    private final StaffMemberRepository staffMemberRepository;
+    private final DeptHeadRepository deptHeadRepository;
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public Person findNamed(String name) {
+        return Optional.ofNullable((Person)staffMemberRepository.findByName(name))
+                .orElse(deptHeadRepository.findByName(name));
+    }
+
+    @Action(semantics = SemanticsOf.SAFE)
+    public String nameOf(Person person) {
+        return person.getName();
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/Person.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/Person.java
new file mode 100644
index 0000000..612d71a
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/Person.java
@@ -0,0 +1,17 @@
+package org.apache.causeway.viewer.graphql.viewer.test.domain.dept;
+
+import javax.inject.Named;
+import javax.persistence.MappedSuperclass;
+
+import org.apache.causeway.applib.annotation.DomainObject;
+import org.apache.causeway.applib.annotation.Nature;
+import org.apache.causeway.applib.annotation.Property;
+
+@MappedSuperclass
+@Named("university.dept.Person")
+@DomainObject(nature = Nature.NOT_SPECIFIED)
+public abstract class Person {
+
+    @Property
+    public abstract String getName();
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
index a8e4716..1813f9f 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/domain/dept/StaffMember.java
@@ -54,7 +54,7 @@
 @DomainObject(nature = Nature.ENTITY, autoCompleteRepository = StaffMemberRepository.class, autoCompleteMethod = "findByNameMatching")
 @DomainObjectLayout(describedAs = "Staff member of a university department, responsible for delivering lectures, tutorials, exam invigilation and candidate interviews")
 @NoArgsConstructor
-public class StaffMember implements Comparable<StaffMember> {
+public class StaffMember extends Person implements Comparable<StaffMember> {
 
     public StaffMember(
             final String name,
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Calculator_IntegTest.each.add_big_decimals._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Calculator_IntegTest.each.add_big_decimals._.gql
index 5bb92ef..3b02246 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Calculator_IntegTest.each.add_big_decimals._.gql
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Calculator_IntegTest.each.add_big_decimals._.gql
@@ -9,5 +9,14 @@
         results
       }
     }
+    addBigDecimals {
+      invoke(x: "1.1", y: "2.2") {
+        args {
+          x
+          y
+        }
+        results
+      }
+    }
   }
 }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person._.gql
new file mode 100644
index 0000000..4effad8
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person._.gql
@@ -0,0 +1,31 @@
+{
+  Scenario(name: "DeptHead is also a Person") {
+    Name
+    Given {
+      university_dept_People {
+        findNamed {
+          invoke(name: "Dr. Helen Johansen") {
+            args {
+              name
+            }
+            results {
+              name {
+                get
+              }
+              _meta {
+                saveAs(ref: "dept-head")
+              }
+            }
+          }
+        }
+      }
+    }
+    When {
+      university_dept_Person(object: {ref: "dept-head"}) {
+        name {
+          get
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person.approved.json
new file mode 100644
index 0000000..1079e5c
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.each.find_person.approved.json
@@ -0,0 +1,33 @@
+{
+  "data" : {
+    "Scenario" : {
+      "Name" : "DeptHead is also a Person",
+      "Given" : {
+        "university_dept_People" : {
+          "findNamed" : {
+            "invoke" : {
+              "args" : {
+                "name" : "Dr. Helen Johansen"
+              },
+              "results" : {
+                "name" : {
+                  "get" : "Dr. Helen Johansen"
+                },
+                "_meta" : {
+                  "saveAs" : "dept-head"
+                }
+              }
+            }
+          }
+        }
+      },
+      "When" : {
+        "university_dept_Person" : {
+          "name" : {
+            "get" : "Dr. Helen Johansen"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.java
new file mode 100644
index 0000000..f5b031b
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/People_IntegTest.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Collectors;
+
+import org.approvaltests.Approvals;
+import org.approvaltests.integrations.junit5.JupiterApprovals;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestFactory;
+
+import org.springframework.test.context.ActiveProfiles;
+
+import lombok.val;
+
+
+//NOT USING @Transactional since we are running server within same transaction otherwise
+@Order(50)
+@ActiveProfiles("test")
+public class People_IntegTest extends Abstract_IntegTest {
+
+    @TestFactory
+    Iterable<DynamicTest> each() throws IOException, URISyntaxException {
+
+        val integClassName = getClass().getSimpleName();
+        val classUrl = getClass().getResource(integClassName + ".class");
+        Path classPath = Paths.get(classUrl.toURI());
+        Path directoryPath = classPath.getParent();
+
+        return Files.walk(directoryPath)
+                .filter(Files::isRegularFile)
+                .filter(file -> {
+                    String fileName = file.getFileName().toString();
+                    return fileName.startsWith(integClassName) && fileName.endsWith("._.gql");
+                })
+                .map(file -> {
+                    String fileName = file.getFileName().toString();
+                    String testName = fileName.substring(integClassName.length() + ".each.".length()).replace("._.gql", "");
+                    return JupiterApprovals.dynamicTest(
+                            testName,
+                            options -> {
+                                Approvals.verify(submitFileNamed(fileName), jsonOptions(options));
+                                afterEach();
+                                beforeEach();
+                            });
+                })
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id._.gql
new file mode 100644
index 0000000..507b900
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id._.gql
@@ -0,0 +1,14 @@
+{
+  Scenario(name: "Obtain name of person but forget to specify the logicalTypeName") {
+    Name
+    When {
+      university_dept_People {
+        nameOf {
+          invoke(person: {id: "123"}) {
+            results
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id.approved.json
new file mode 100644
index 0000000..7bb8f1b
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_id.approved.json
@@ -0,0 +1,27 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/Scenario/When/university_dept_People/nameOf/invoke/results) : The 'logicalTypeName' is required along with the 'id', because the input type 'university.dept.Person' is abstract",
+    "locations" : [ {
+      "line" : 8,
+      "column" : 13
+    } ],
+    "path" : [ "Scenario", "When", "university_dept_People", "nameOf", "invoke", "results" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "Scenario" : {
+      "Name" : "Obtain name of person but forget to specify the logicalTypeName",
+      "When" : {
+        "university_dept_People" : {
+          "nameOf" : {
+            "invoke" : {
+              "results" : null
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref._.gql
new file mode 100644
index 0000000..2fb8d4b
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref._.gql
@@ -0,0 +1,33 @@
+{
+  Scenario(name: "Obtain name of person that's a DeptHead") {
+    Name
+    Given {
+      university_dept_DeptHeads {
+        findHeadByName {
+          invoke(name: "Dr. Helen Johansen") {
+            args {
+              name
+            }
+            results {
+              name {
+                get
+              }
+              _meta {
+                saveAs(ref: "dept-head")
+              }
+            }
+          }
+        }
+      }
+    }
+    When {
+      university_dept_People {
+        nameOf {
+          invoke(person: {ref: "dept-head"}) {
+            results
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref.approved.json
new file mode 100644
index 0000000..2ac1a93
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_dept_head_using_ref.approved.json
@@ -0,0 +1,35 @@
+{
+  "data" : {
+    "Scenario" : {
+      "Name" : "Obtain name of person that's a DeptHead",
+      "Given" : {
+        "university_dept_DeptHeads" : {
+          "findHeadByName" : {
+            "invoke" : {
+              "args" : {
+                "name" : "Dr. Helen Johansen"
+              },
+              "results" : {
+                "name" : {
+                  "get" : "Dr. Helen Johansen"
+                },
+                "_meta" : {
+                  "saveAs" : "dept-head"
+                }
+              }
+            }
+          }
+        }
+      },
+      "When" : {
+        "university_dept_People" : {
+          "nameOf" : {
+            "invoke" : {
+              "results" : "Dr. Helen Johansen"
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id._.gql
new file mode 100644
index 0000000..eae4185
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id._.gql
@@ -0,0 +1,14 @@
+{
+  Scenario(name: "Obtain name of person that's a non-existent StaffMember") {
+    Name
+    When {
+      university_dept_People {
+        nameOf {
+          invoke(person: {id: "123456", logicalTypeName: "university.dept.StaffMember"}) {
+            results
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id.approved.json
new file mode 100644
index 0000000..b8b5fe2
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_invalid_id.approved.json
@@ -0,0 +1,27 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/Scenario/When/university_dept_People/nameOf/invoke/results) : 'Person' is mandatory",
+    "locations" : [ {
+      "line" : 8,
+      "column" : 13
+    } ],
+    "path" : [ "Scenario", "When", "university_dept_People", "nameOf", "invoke", "results" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "Scenario" : {
+      "Name" : "Obtain name of person that's a non-existent StaffMember",
+      "When" : {
+        "university_dept_People" : {
+          "nameOf" : {
+            "invoke" : {
+              "results" : null
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref._.gql
new file mode 100644
index 0000000..a993291
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref._.gql
@@ -0,0 +1,33 @@
+{
+  Scenario(name: "Obtain name of person that's a StaffMember") {
+    Name
+    Given {
+      university_dept_Staff {
+        findStaffMemberByName {
+          invoke(name: "Letitia Leadbetter") {
+            args {
+              name
+            }
+            results {
+              name {
+                get
+              }
+              _meta {
+                saveAs(ref: "staff-member")
+              }
+            }
+          }
+        }
+      }
+    }
+    When {
+      university_dept_People {
+        nameOf {
+          invoke(person: {ref: "staff-member"}) {
+            results
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref.approved.json
new file mode 100644
index 0000000..d189aa8
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.each.name_of_when_staff_member_using_ref.approved.json
@@ -0,0 +1,35 @@
+{
+  "data" : {
+    "Scenario" : {
+      "Name" : "Obtain name of person that's a StaffMember",
+      "Given" : {
+        "university_dept_Staff" : {
+          "findStaffMemberByName" : {
+            "invoke" : {
+              "args" : {
+                "name" : "Letitia Leadbetter"
+              },
+              "results" : {
+                "name" : {
+                  "get" : "Letitia Leadbetter"
+                },
+                "_meta" : {
+                  "saveAs" : "staff-member"
+                }
+              }
+            }
+          }
+        }
+      },
+      "When" : {
+        "university_dept_People" : {
+          "nameOf" : {
+            "invoke" : {
+              "results" : "Letitia Leadbetter"
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.java
new file mode 100644
index 0000000..403a9e0
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/Person_IntegTest.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Collectors;
+
+import org.approvaltests.Approvals;
+import org.approvaltests.integrations.junit5.JupiterApprovals;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.TestFactory;
+
+import org.springframework.test.context.ActiveProfiles;
+
+import lombok.val;
+
+
+//NOT USING @Transactional since we are running server within same transaction otherwise
+@Order(50)
+@ActiveProfiles("test")
+public class Person_IntegTest extends Abstract_IntegTest {
+
+    @TestFactory
+    Iterable<DynamicTest> each() throws IOException, URISyntaxException {
+
+        val integClassName = getClass().getSimpleName();
+        val classUrl = getClass().getResource(integClassName + ".class");
+        Path classPath = Paths.get(classUrl.toURI());
+        Path directoryPath = classPath.getParent();
+
+        return Files.walk(directoryPath)
+                .filter(Files::isRegularFile)
+                .filter(file -> {
+                    String fileName = file.getFileName().toString();
+                    return fileName.startsWith(integClassName) && fileName.endsWith("._.gql");
+                })
+                .map(file -> {
+                    String fileName = file.getFileName().toString();
+                    String testName = fileName.substring(integClassName.length() + ".each.".length()).replace("._.gql", "");
+                    return JupiterApprovals.dynamicTest(
+                            testName,
+                            options -> {
+                                Approvals.verify(submitFileNamed(fileName), jsonOptions(options));
+                                afterEach();
+                                beforeEach();
+                            });
+                })
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/DeptHeadMutating_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/DeptHeadMutating_IntegTest.java
index abd2185..f60514e 100644
--- a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/DeptHeadMutating_IntegTest.java
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/DeptHeadMutating_IntegTest.java
@@ -60,6 +60,5 @@
 
         // then payload
         Approvals.verify(response, jsonOptions());
-
     }
 }
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.java b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.java
new file mode 100644
index 0000000..5752a5d
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.java
@@ -0,0 +1,86 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.viewer.graphql.viewer.test.e2e.special;
+
+import java.util.Optional;
+
+import org.apache.causeway.viewer.graphql.viewer.test.domain.dept.StaffMember;
+
+import org.approvaltests.Approvals;
+import org.approvaltests.reporters.DiffReporter;
+import org.approvaltests.reporters.UseReporter;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Propagation;
+
+import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.commons.internal.collections._Maps;
+import org.apache.causeway.viewer.graphql.viewer.test.domain.dept.Department;
+import org.apache.causeway.viewer.graphql.viewer.test.e2e.Abstract_IntegTest;
+
+import lombok.val;
+
+
+//NOT USING @Transactional since we are running server within same transaction otherwise
+@Order(120)
+@ActiveProfiles("test")
+public class Person_2_IntegTest extends Abstract_IntegTest {
+
+    @Test
+    @UseReporter(DiffReporter.class)
+    void name_of_person_using_id_and_logicalTypeName() throws Exception {
+
+        final Bookmark bookmark =
+                transactionService.callTransactional(
+                        Propagation.REQUIRED,
+                        () -> {
+                            StaffMember staffMember = staffMemberRepository.findByName("Letitia Leadbetter");
+                            Optional<Bookmark> bookmark1 = bookmarkService.bookmarkFor(staffMember);
+                            return bookmark1.orElseThrow();
+                        }
+                ).valueAsNonNullElseFail();
+
+        val response = submit(_Maps.unmodifiable("$staffMemberId", bookmark.getIdentifier()));
+
+        // then payload
+        Approvals.verify(response, jsonOptions());
+    }
+
+    @Test
+    @UseReporter(DiffReporter.class)
+    void name_of_person_using_id_but_invalid_logicalTypeName() throws Exception {
+
+        final Bookmark bookmark =
+                transactionService.callTransactional(
+                        Propagation.REQUIRED,
+                        () -> {
+                            StaffMember staffMember = staffMemberRepository.findByName("Letitia Leadbetter");
+                            Optional<Bookmark> bookmark1 = bookmarkService.bookmarkFor(staffMember);
+                            return bookmark1.orElseThrow();
+                        }
+                ).valueAsNonNullElseFail();
+
+        val response = submit(_Maps.unmodifiable("$staffMemberId", bookmark.getIdentifier()));
+
+        // then payload
+        Approvals.verify(response, jsonOptions());
+    }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName._.gql
new file mode 100644
index 0000000..d390911
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName._.gql
@@ -0,0 +1,7 @@
+{
+  university_dept_Person(object: {id: "$staffMemberId", logicalTypeName: "university.dept.StaffMember"}) {
+    name {
+      get
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName.approved.json
new file mode 100644
index 0000000..74cfd1d
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_and_logicalTypeName.approved.json
@@ -0,0 +1,9 @@
+{
+  "data" : {
+    "university_dept_Person" : {
+      "name" : {
+        "get" : "Letitia Leadbetter"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName._.gql b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName._.gql
new file mode 100644
index 0000000..7d26fdc
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName._.gql
@@ -0,0 +1,7 @@
+{
+  university_dept_Person(object: {id: "$staffMemberId", logicalTypeName: "university.dept.XXX"}) {
+    name {
+      get
+    }
+  }
+}
diff --git a/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName.approved.json b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName.approved.json
new file mode 100644
index 0000000..66c31d8
--- /dev/null
+++ b/viewers/graphql/test/src/test/java/org/apache/causeway/viewer/graphql/viewer/test/e2e/special/Person_2_IntegTest.name_of_person_using_id_but_invalid_logicalTypeName.approved.json
@@ -0,0 +1,16 @@
+{
+  "errors" : [ {
+    "message" : "Exception while fetching data (/university_dept_Person) : The 'logicalTypeName' of 'university.dept.XXX' is unknown in the metamodel",
+    "locations" : [ {
+      "line" : 2,
+      "column" : 3
+    } ],
+    "path" : [ "university_dept_Person" ],
+    "extensions" : {
+      "classification" : "DataFetchingException"
+    }
+  } ],
+  "data" : {
+    "university_dept_Person" : null
+  }
+}
\ No newline at end of file
diff --git a/viewers/graphql/test/src/test/resources/schema.gql b/viewers/graphql/test/src/test/resources/schema.gql
index ece5374..d8c8312 100644
--- a/viewers/graphql/test/src/test/resources/schema.gql
+++ b/viewers/graphql/test/src/test/resources/schema.gql
@@ -111,6 +111,8 @@
   university_dept_Departments: university_dept_Departments
   university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): university_dept_DeptHead
   university_dept_DeptHeads: university_dept_DeptHeads
+  university_dept_People: university_dept_People
+  university_dept_Person(object: university_dept_Person__gqlv_input): university_dept_Person
   university_dept_Staff: university_dept_Staff
   university_dept_StaffMember(object: university_dept_StaffMember__gqlv_input): university_dept_StaffMember
 }
@@ -169,6 +171,8 @@
   university_dept_Departments: university_dept_Departments
   university_dept_DeptHead(object: university_dept_DeptHead__gqlv_input): university_dept_DeptHead
   university_dept_DeptHeads: university_dept_DeptHeads
+  university_dept_People: university_dept_People
+  university_dept_Person(object: university_dept_Person__gqlv_input): university_dept_Person
   university_dept_Staff: university_dept_Staff
   university_dept_StaffMember(object: university_dept_StaffMember__gqlv_input): university_dept_StaffMember
 }
@@ -1601,6 +1605,22 @@
   validate(service: Boolean): String
 }
 
+type causeway_schema_metamodel_v2_FacetHolder {
+  "Object metadata"
+  _meta: causeway_schema_metamodel_v2_FacetHolder__gqlv_meta
+}
+
+type causeway_schema_metamodel_v2_FacetHolder__gqlv_meta {
+  cssClass: String
+  grid: String
+  icon: String
+  id: String!
+  layout: String
+  logicalTypeName: String!
+  saveAs(ref: String): String
+  title: String!
+}
+
 type causeway_security_LoginRedirect {
   "Object metadata"
   _meta: causeway_security_LoginRedirect__gqlv_meta
@@ -3738,6 +3758,102 @@
   validity: String
 }
 
+type university_dept_People {
+  "Find Named"
+  findNamed: university_dept_People__findNamed__gqlv_action
+  "Name Of"
+  nameOf: university_dept_People__nameOf__gqlv_action
+}
+
+type university_dept_People__findNamed__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(name: String!): university_dept_People__findNamed__gqlv_action_invoke
+  "Parameters of this action"
+  params: university_dept_People__findNamed__gqlv_action_params
+  validate(name: String): String
+}
+
+type university_dept_People__findNamed__gqlv_action_args {
+  name: String
+}
+
+type university_dept_People__findNamed__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: university_dept_People__findNamed__gqlv_action_args
+  results: university_dept_Person
+}
+
+type university_dept_People__findNamed__gqlv_action_params {
+  "Name"
+  name: university_dept_People__findNamed__name__gqlv_action_parameter
+}
+
+type university_dept_People__findNamed__name__gqlv_action_parameter {
+  datatype: String
+  disabled(name: String): String
+  hidden: Boolean
+  validity: String
+}
+
+type university_dept_People__nameOf__gqlv_action {
+  disabled: String
+  hidden: Boolean
+  invoke(person: university_dept_Person__gqlv_input!): university_dept_People__nameOf__gqlv_action_invoke
+  "Parameters of this action"
+  params: university_dept_People__nameOf__gqlv_action_params
+  validate(person: university_dept_Person__gqlv_input): String
+}
+
+type university_dept_People__nameOf__gqlv_action_args {
+  person: university_dept_Person
+}
+
+type university_dept_People__nameOf__gqlv_action_invoke {
+  "Arguments used to invoke this action"
+  args: university_dept_People__nameOf__gqlv_action_args
+  results: String
+}
+
+type university_dept_People__nameOf__gqlv_action_params {
+  "Person"
+  person: university_dept_People__nameOf__person__gqlv_action_parameter
+}
+
+type university_dept_People__nameOf__person__gqlv_action_parameter {
+  datatype: String
+  disabled(person: university_dept_Person__gqlv_input): String
+  hidden: Boolean
+  validity: String
+}
+
+type university_dept_Person {
+  "Object metadata"
+  _meta: university_dept_Person__gqlv_meta
+  "Name"
+  name: university_dept_Person__name__gqlv_property
+}
+
+type university_dept_Person__gqlv_meta {
+  cssClass: String
+  grid: String
+  icon: String
+  id: String!
+  layout: String
+  logicalTypeName: String!
+  saveAs(ref: String): String
+  title: String!
+}
+
+type university_dept_Person__name__gqlv_property {
+  datatype: String
+  disabled: String
+  get: String!
+  hidden: Boolean
+  set(name: String!): university_dept_Person
+  validate(name: String): String
+}
+
 type university_dept_Staff {
   "Staff member of a university department, responsible for delivering lectures, tutorials, exam invigilation and candidate interviews"
   createStaffMember: university_dept_Staff__createStaffMember__gqlv_action
@@ -3959,186 +4075,352 @@
 scalar UUID
 
 input causeway_applib_DomainObjectList__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_FacetGroupNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_ParameterNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_PropertyNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_RoleMemento__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_TypeNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_UserMemento__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_node_ActionNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_node_CollectionNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_node_FacetAttrNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_applib_node_FacetNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_conf_ConfigurationProperty__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_conf_ConfigurationViewmodel__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationFeatureViewModel__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationNamespace__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationTypeAction__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationTypeCollection__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationTypeMember__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationTypeProperty__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_feat_ApplicationType__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_schema_metamodel_v2_DomainClassDto__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
+  ref: String
+}
+
+input causeway_schema_metamodel_v2_FacetHolder__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
+  id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_security_LoginRedirect__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input causeway_testing_fixtures_FixtureResult__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_lang_Runnable__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_Map__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_SortedMap__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_concurrent_Callable__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_function_BiFunction__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_function_Consumer__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_function_Function__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input java_util_stream_Stream__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input university_dept_Department__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input university_dept_DeptHead__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
+  ref: String
+}
+
+input university_dept_Person__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
+  id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }
 
 input university_dept_StaffMember__gqlv_input {
+  "Use either 'id' or 'ref'; looks up an entity from the persistent data store, or if a view model, then recreates using the id as a memento of the object's state"
   id: ID
+  "If object identified by 'id', then optionally specifies concrete type.  This is only required if the parameter type defines a super class"
+  logicalTypeName: String
+  "Use either 'ref' or 'id'; looks up an object previously saved to the execution context using 'saveAs(ref: ...)'"
   ref: String
 }