TCK: GroupConversionValidationTest
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
index ae6f629..8f4cdda 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
@@ -27,11 +27,13 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 import javax.validation.GroupDefinitionException;
 import javax.validation.GroupSequence;
 import javax.validation.ValidationException;
 import javax.validation.groups.Default;
+import javax.validation.metadata.GroupConversionDescriptor;
 
 import org.apache.bval.util.Exceptions;
 import org.apache.bval.util.Validate;
@@ -72,6 +74,55 @@
     }
 
     /**
+     * Compute groups for a single cascading validation taking into account the specified set of
+     * {@link GroupConversionDescriptor}s.
+     * 
+     * @param groupConversions
+     * @param group
+     * @return {@link Groups}
+     */
+    public final Groups computeCascadingGroups(Set<GroupConversionDescriptor> groupConversions, Class<?> group) {
+        final Groups preliminaryResult = computeGroups(group);
+
+        final Map<Class<?>, Class<?>> gcMap = groupConversions.stream()
+            .collect(Collectors.toMap(GroupConversionDescriptor::getFrom, GroupConversionDescriptor::getTo));
+
+        final boolean simpleGroup = preliminaryResult.getSequences().isEmpty();
+
+        // conversion of a simple (non-sequence) group:
+        if (simpleGroup && gcMap.containsKey(group)) {
+            return computeGroups(gcMap.get(group));
+        }
+
+        final Groups result = new Groups();
+
+        if (simpleGroup) {
+            // ignore group inheritance from initial argument as that is handled elsewhere:
+            result.insertGroup(preliminaryResult.getGroups().get(0));
+        } else {
+            // expand group sequence conversions in place:
+
+            for (List<Group> seq : preliminaryResult.getSequences()) {
+                final List<Group> converted = new ArrayList<>();
+                for (Group gg : seq) {
+                    final Class<?> c = gg.getGroup();
+                    if (gcMap.containsKey(c)) {
+                        final Groups convertedGroupExpansion = computeGroups(gcMap.get(c));
+                        if (convertedGroupExpansion.getSequences().isEmpty()) {
+                            converted.add(gg);
+                        } else {
+                            convertedGroupExpansion.getSequences().stream().flatMap(Collection::stream)
+                                .forEach(converted::add);
+                        }
+                    }
+                }
+                result.insertSequence(converted);
+            }
+        }
+        return result;
+    }
+
+    /**
      * Main compute implementation.
      * 
      * @param groups
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
index 70905d0..f2eaa26 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
@@ -271,6 +271,20 @@
 
         @Override
         void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+            final Groups convertedGroups =
+                validatorContext.getGroupsComputer().computeCascadingGroups(descriptor.getGroupConversions(), group);
+
+            convertedGroups.getGroups().stream().map(Group::getGroup).forEach(g -> recurseSingleExpandedGroup(g, sink));
+
+            sequences: for (List<Group> seq : convertedGroups.getSequences()) {
+                final boolean proceed = each(seq.stream().map(Group::getGroup), this::recurseSingleExpandedGroup, sink);
+                if (!proceed) {
+                    break sequences;
+                }
+            }
+        }
+
+        protected void recurseSingleExpandedGroup(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
             @SuppressWarnings({ "unchecked", "rawtypes" })
             final Stream<ContainerElementTypeD> containerElements = descriptor.getConstrainedContainerElementTypes()
                 .stream().flatMap(d -> ComposedD.unwrap(d, (Class) ContainerElementTypeD.class));
@@ -363,7 +377,7 @@
         }
 
         @Override
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        protected void recurseSingleExpandedGroup(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
             final PathImpl path = context.getPath();
             final NodeImpl leafNode = path.getLeafNode();
 
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
index a3701d4..2440948 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Liskov.java
@@ -18,6 +18,7 @@
 
 import java.lang.annotation.ElementType;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -115,8 +116,17 @@
         }
         switch (elementKind) {
         case RETURN_VALUE:
-            noStrengtheningOfPreconditions(delegates, detectGroupConversion());
             noRedeclarationOfReturnValueCascading(delegates);
+
+            final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements =
+                detectValidationElements(delegates, detectGroupConversion());
+
+            // pre-check return value overridden hierarchy:
+            Stream.of(StrengtheningIssue.values())
+                .filter((Predicate<? super StrengtheningIssue>) si -> !(si == StrengtheningIssue.overriddenHierarchy
+                    && detectedValidationElements.values().stream().filter(s -> !s.isEmpty()).count() < 2))
+                .forEach(si -> si.check(detectedValidationElements));
+
             break;
         case PARAMETER:
             noStrengtheningOfPreconditions(delegates, detectConstraints(), detectCascading(), detectGroupConversion());
@@ -160,6 +170,20 @@
     private static <D extends ElementDelegate<?, ?>> void noStrengtheningOfPreconditions(List<? extends D> delegates,
         Function<? super D, ValidationElement>... detectors) {
 
+        final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements = 
+                detectValidationElements(delegates, detectors);
+
+        if (detectedValidationElements.isEmpty()) {
+            return;
+        }
+        for (StrengtheningIssue s : StrengtheningIssue.values()) {
+            s.check(detectedValidationElements);
+        }
+    }
+
+    @SafeVarargs
+    private static <D extends ElementDelegate<?, ?>> Map<Meta<?>, Set<ValidationElement>> detectValidationElements(
+        List<? extends D> delegates, Function<? super D, ValidationElement>... detectors) {
         final Map<Meta<?>, Set<ValidationElement>> detectedValidationElements = new LinkedHashMap<>();
         delegates.forEach(d -> {
             detectedValidationElements.put(d.getHierarchyElement(),
@@ -168,11 +192,9 @@
         });
         if (detectedValidationElements.values().stream().allMatch(Collection::isEmpty)) {
             // nothing declared
-            return;
+            return Collections.emptyMap();
         }
-        for (StrengtheningIssue s : StrengtheningIssue.values()) {
-            s.check(detectedValidationElements);
-        }
+        return detectedValidationElements;
     }
 
     private static boolean related(Class<?> c1, Class<?> c2) {