This is an automated email from the ASF dual-hosted git repository.

xiazcy pushed a commit to branch steps-taking-traversal
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit c661ea65226e9c57abbe5177e7deb5c55727cef9
Author: Yang Xia <[email protected]>
AuthorDate: Fri Jun 12 00:16:08 2026 -0700

    Add traversal-accepting has/hasLabel/is/V/E/property/where steps and 
mutation guards
    
    Wire the runtime-resolved predicates into the filter, lookup, and mutation
    steps and their DSL entry points:
    - has(key|T|label, traversal), hasLabel(traversal): normalized to a 
HasContainer
      holding P.eq(traversal) so there is a single resolution path; the direct
      traversal form on HasContainer is removed.
    - is(traversal), where(P) with a traversal-bearing predicate.
    - V(traversal)/E(traversal) start steps seed a synthetic traverser 
(consistent
      with mergeV/mergeE) so the id traversal can run without an incoming 
traverser.
    - property(traversal) producing a Map of key/value pairs, dispatched by an
      internal mapForm flag rather than a sentinel key.
    - All/Any/None step support for traversal-bearing predicates.
    
    Mutation safety is enforced in two layers: ChildTraversalValidator at DSL
    construction time and ChildTraversalVerificationStrategy at strategy time, 
the
    latter validating any step marked with the new 
AcceptsChildPredicateTraversal
    interface. GraphStep/TinkerGraphStepStrategy skip folding traversal-bearing
    HasContainers into index lookups since their value is dynamic.
---
 .../process/traversal/TraversalStrategies.java     |   2 +
 .../traversal/dsl/graph/GraphTraversal.java        | 150 +++++++++++++-
 .../traversal/dsl/graph/GraphTraversalSource.java  |  37 ++++
 .../gremlin/process/traversal/dsl/graph/__.java    |  49 +++++
 .../step/AcceptsChildPredicateTraversal.java       |  34 +++
 .../process/traversal/step/HasContainerHolder.java |   2 +-
 .../process/traversal/step/filter/AllStep.java     |  30 ++-
 .../process/traversal/step/filter/AnyStep.java     |  30 ++-
 .../process/traversal/step/filter/HasStep.java     | 109 +++++++++-
 .../process/traversal/step/filter/IsStep.java      |  28 ++-
 .../process/traversal/step/filter/NoneStep.java    |  32 ++-
 .../traversal/step/filter/WherePredicateStep.java  |  40 +++-
 .../process/traversal/step/map/GraphStep.java      | 124 ++++++++++-
 .../traversal/step/sideEffect/AddPropertyStep.java | 129 +++++++++++-
 .../step/sideEffect/AddPropertyStepContract.java   |   4 +-
 .../sideEffect/AddPropertyStepPlaceholder.java     |  16 +-
 .../process/traversal/step/util/HasContainer.java  |  10 +-
 .../optimization/InlineFilterStrategy.java         |   9 +
 .../ChildTraversalVerificationStrategy.java        |  61 ++++++
 .../traversal/util/ChildTraversalValidator.java    |  62 ++++++
 .../step/CloneIndependenceTraversalTest.java       | 120 +++++++++++
 .../traversal/step/util/HasContainerTest.java      |  90 ++++++++
 .../ChildTraversalVerificationStrategyTest.java    |  69 +++++++
 .../util/ChildTraversalValidatorTest.java          | 229 +++++++++++++++++++++
 .../traversal/step/sideEffect/TinkerGraphStep.java |   8 +
 .../optimization/TinkerGraphStepStrategy.java      |  14 +-
 .../TinkerGraphStepStrategyTraversalTest.java      | 205 ++++++++++++++++++
 27 files changed, 1649 insertions(+), 44 deletions(-)

diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
index bccb983665..e3f0d90ff6 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java
@@ -52,6 +52,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Path
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathRetractionStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ChildTraversalVerificationStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ComputerVerificationStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy;
@@ -287,6 +288,7 @@ public interface TraversalStrategies extends Serializable, 
Cloneable, Iterable<T
                     LazyBarrierStrategy.instance(),
                     ProfileStrategy.instance(),
                     StandardVerificationStrategy.instance(),
+                    ChildTraversalVerificationStrategy.instance(),
                     GValueReductionStrategy.instance());
             registerStrategies(Graph.class, graphStrategies);
             registerStrategies(EmptyGraph.class, new 
DefaultTraversalStrategies());
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
index c42859f7a5..2b8ec3eca7 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java
@@ -209,6 +209,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics;
+import 
org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator;
 import org.apache.tinkerpop.gremlin.structure.Column;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
@@ -414,6 +415,23 @@ public interface GraphTraversal<S, E> extends Traversal<S, 
E> {
         return this.asAdmin().addStep(step);
     }
 
+    /**
+     * A {@code V} step that accepts a child traversal whose results are used 
as vertex IDs for lookup.
+     * This form is only valid as a mid-traversal step; using it as a start 
step will throw an
+     * {@link IllegalStateException} at runtime because there is no traverser 
context to evaluate the child traversal.
+     *
+     * @param traversal the child traversal that produces vertex IDs
+     * @return the traversal with an appended {@link GraphStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#graph-step";
 target="_blank">Reference Documentation - Graph Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, Vertex> V(final Traversal<?, ?> 
traversal) {
+        if (null == traversal) return V(new Object[]{ null });
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.V, traversal);
+        return this.asAdmin().addStep(new GraphStep<>(this.asAdmin(), 
Vertex.class, false, traversal.asAdmin()));
+    }
+
     /**
      * A {@code E} step is usually used to start a traversal but it may also 
be used mid-traversal.
      *
@@ -437,6 +455,23 @@ public interface GraphTraversal<S, E> extends Traversal<S, 
E> {
         return this.asAdmin().addStep(step);
     }
 
+    /**
+     * A {@code E} step that accepts a child traversal whose results are used 
as edge IDs for lookup.
+     * This form is only valid as a mid-traversal step; using it as a start 
step will throw an
+     * {@link IllegalStateException} at runtime because there is no traverser 
context to evaluate the child traversal.
+     *
+     * @param traversal the child traversal that produces edge IDs
+     * @return the traversal with an appended {@link GraphStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#e-step"; 
target="_blank">Reference Documentation - E Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, Edge> E(final Traversal<?, ?> traversal) {
+        if (null == traversal) return E(new Object[]{ null });
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.E, traversal);
+        return this.asAdmin().addStep(new GraphStep<>(this.asAdmin(), 
Edge.class, false, traversal.asAdmin()));
+    }
+
     /**
      * Map the {@link Vertex} to its adjacent vertices given a direction.
      *
@@ -2670,6 +2705,47 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         }
     }
 
+    /**
+     * Filters vertices, edges and vertex properties based on their properties 
using a child traversal
+     * whose results are resolved at runtime against the current traverser. 
The resolved results are used
+     * with {@code P.within()} semantics for comparison against the named 
property.
+     *
+     * @param propertyKey the key of the property to filter on
+     * @param traversal   the child traversal whose results are used as the 
filter value
+     * @return the traversal with an appended {@link HasStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step"; 
target="_blank">Reference Documentation - Has Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> has(final String propertyKey, final 
Traversal<?, ?> traversal) {
+        if (null == traversal) return has(propertyKey, (Object) null);
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.has, propertyKey, 
traversal);
+        final HasContainer hasContainer = new HasContainer(propertyKey, 
P.eq(traversal.asAdmin()));
+        return this.asAdmin().addStep(new HasStep(this.asAdmin(), 
hasContainer));
+    }
+
+    /**
+     * Filters vertices, edges and vertex properties based on their properties 
using a child traversal
+     * whose results are resolved at runtime against the current traverser. 
The resolved results are used
+     * with {@code P.within()} semantics for comparison against the T-accessor 
value (id or label).
+     *
+     * @param accessor  the {@link T} accessor of the property to filter on
+     * @param traversal the child traversal whose results are used as the 
filter value
+     * @return the traversal with an appended {@link HasStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step"; 
target="_blank">Reference Documentation - Has Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> has(final T accessor, final 
Traversal<?, ?> traversal) {
+        if (null == accessor)
+            throw new IllegalArgumentException("The T accessor value of 
has(T,Traversal) cannot be null");
+        if (null == traversal) return has(accessor, (Object) null);
+        ChildTraversalValidator.validate(traversal.asAdmin());
+
+        this.asAdmin().getGremlinLang().addStep(Symbols.has, accessor, 
traversal);
+        final HasContainer hasContainer = new 
HasContainer(accessor.getAccessor(), P.eq(traversal.asAdmin()));
+        return this.asAdmin().addStep(new HasStep(this.asAdmin(), 
hasContainer));
+    }
+
     /**
      * Filters vertices, edges and vertex properties based on their properties.
      *
@@ -2702,6 +2778,27 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         return TraversalHelper.addHasContainer(this.asAdmin(), new 
HasContainer(propertyKey, value instanceof P ? (P) value : P.eq(value)));
     }
 
+    /**
+     * Filters vertices, edges and vertex properties based on their label and 
a property value resolved
+     * from a child traversal at runtime. The label is matched first, then the 
child traversal results
+     * are used with {@code P.within()} semantics for comparison against the 
named property.
+     *
+     * @param label       the label of the {@link Element}
+     * @param propertyKey the key of the property to filter on
+     * @param traversal   the child traversal whose results are used as the 
filter value
+     * @return the traversal with an appended {@link HasStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step"; 
target="_blank">Reference Documentation - Has Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> has(final String label, final String 
propertyKey, final Traversal<?, ?> traversal) {
+        if (null == traversal) return has(label, propertyKey, (Object) null);
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.has, label, 
propertyKey, traversal);
+        TraversalHelper.addHasContainer(this.asAdmin(), new 
HasContainer(T.label.getAccessor(), P.eq(label)));
+        final HasContainer hasContainer = new HasContainer(propertyKey, 
P.eq(traversal.asAdmin()));
+        return this.asAdmin().addStep(new HasStep(this.asAdmin(), 
hasContainer));
+    }
+
     /**
      * Filters vertices, edges and vertex properties based on the existence of 
properties.
      *
@@ -2797,6 +2894,23 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         }
     }
 
+    /**
+     * Filters vertices, edges and vertex properties based on their label 
using a child traversal
+     * whose results are resolved at runtime against the current traverser.
+     *
+     * @param traversal the child traversal whose results are used as the 
label filter value
+     * @return the traversal with an appended {@link HasStep}
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#has-step"; 
target="_blank">Reference Documentation - Has Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> hasLabel(final Traversal<?, ?> 
traversal) {
+        if (null == traversal) return hasLabel((String) null);
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.hasLabel, traversal);
+        final HasContainer hasContainer = new 
HasContainer(T.label.getAccessor(), P.eq(traversal.asAdmin()));
+        return this.asAdmin().addStep(new HasStep(this.asAdmin(), 
hasContainer));
+    }
+
     /**
      * Filters vertices, edges and vertex properties based on their label.
      *
@@ -3022,7 +3136,14 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
      */
     public default GraphTraversal<S, E> is(final Object value) {
         this.asAdmin().getGremlinLang().addStep(Symbols.is, value);
-        P<E> predicate = value instanceof P ? (P<E>) value : P.eq((E) value);
+        P<E> predicate;
+        if (value instanceof P) {
+            predicate = (P<E>) value;
+        } else if (value instanceof Traversal) {
+            predicate = (P<E>) P.eq(((Traversal<?, ?>) value).asAdmin());
+        } else {
+            predicate = P.eq((E) value);
+        }
         return this.asAdmin().addStep(predicate.isParameterized() ? new 
IsStepPlaceholder<>(this.asAdmin(), predicate) : new IsStep<>(this.asAdmin(), 
predicate));
     }
 
@@ -3825,6 +3946,25 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         }
         return this;
     }
+
+    /**
+     * Sets properties on the current element using a child traversal that 
produces a {@code Map}.
+     * Each entry in the resulting Map becomes a property (key → value) on the 
element. The traversal
+     * is evaluated at execution time against the current traverser.
+     *
+     * @param mapTraversal a traversal that produces a {@code Map<String, 
Object>} of property key/value pairs
+     * @return the traversal with the {@link AddPropertyStepPlaceholder} added
+     * @see <a 
href="http://tinkerpop.apache.org/docs/${project.version}/reference/#addproperty-step";
 target="_blank">AddProperty Step</a>
+     * @since 4.0.0
+     */
+    public default GraphTraversal<S, E> property(final Traversal<?, ?> 
mapTraversal) {
+        if (null == mapTraversal) return this;
+        ChildTraversalValidator.validate(mapTraversal.asAdmin());
+        this.asAdmin().getGremlinLang().addStep(Symbols.property, 
mapTraversal);
+        this.asAdmin().addStep(new AddPropertyStepPlaceholder(this.asAdmin(), 
null, null, mapTraversal.asAdmin(), true));
+        return this;
+    }
+
     ///////////////////// BRANCH STEPS /////////////////////
 
     /**
@@ -3972,6 +4112,10 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         if (choosePredicate.isParameterized()) {
             throw new IllegalArgumentException("Parameterized predicates are 
not supported by choose()");
         }
+        if (choosePredicate.hasTraversal()) {
+            throw new IllegalArgumentException("Traversal-bearing predicates 
are not supported by choose(). " +
+                    "Use choose(__.is(P.op(traversal)), trueChoice, 
falseChoice) instead.");
+        }
         this.asAdmin().getGremlinLang().addStep(Symbols.choose, 
choosePredicate, trueChoice, falseChoice);
         return this.asAdmin().addStep(new ChooseStep<E, E2, 
Boolean>(this.asAdmin(), (Traversal.Admin<E, ?>) __.is(choosePredicate), 
(Traversal.Admin<E, E2>) trueChoice, (Traversal.Admin<E, E2>) falseChoice));
     }
@@ -4009,6 +4153,10 @@ public interface GraphTraversal<S, E> extends 
Traversal<S, E> {
         if (choosePredicate.isParameterized()) {
             throw new IllegalArgumentException("Parameterized predicates are 
not supported by choose()");
         }
+        if (choosePredicate.hasTraversal()) {
+            throw new IllegalArgumentException("Traversal-bearing predicates 
are not supported by choose(). " +
+                    "Use choose(__.is(P.op(traversal)), trueChoice) instead.");
+        }
         this.asAdmin().getGremlinLang().addStep(Symbols.choose, 
choosePredicate, trueChoice);
         return this.asAdmin().addStep(new ChooseStep<E, E2, 
Boolean>(this.asAdmin(), (Traversal.Admin<E, ?>) __.is(choosePredicate), 
(Traversal.Admin<E, E2>) trueChoice, (Traversal.Admin<E, E2>) __.identity()));
     }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
index aec1d6fadd..3bce63c401 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java
@@ -42,6 +42,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectStep
 import 
org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStepContract;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.RequirementsStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import 
org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator;
 import org.apache.tinkerpop.gremlin.structure.Direction;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Graph;
@@ -540,6 +541,24 @@ public class GraphTraversalSource implements 
TraversalSource {
         return traversal.addStep(step);
     }
 
+    /**
+     * Spawns a {@link GraphTraversal} starting with vertices whose IDs are 
resolved from a child traversal.
+     * As a start step, a synthetic traverser is generated to seed the child 
traversal evaluation,
+     * consistent with how {@code mergeV(traversal)} handles start steps. The 
child traversal should
+     * be self-contained (e.g., {@code __.V(1).id()}) rather than dependent on 
an incoming traverser.
+     *
+     * @param traversal the child traversal that produces vertex IDs
+     * @since 4.0.0
+     */
+    public GraphTraversal<Vertex, Vertex> V(final Traversal<?, ?> traversal) {
+        if (null == traversal) return V(new Object[]{ null });
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        final GraphTraversalSource clone = this.clone();
+        clone.gremlinLang.addStep(GraphTraversal.Symbols.V, traversal);
+        final GraphTraversal.Admin<Vertex, Vertex> traversalAdmin = new 
DefaultGraphTraversal<>(clone);
+        return traversalAdmin.addStep(new GraphStep<>(traversalAdmin, 
Vertex.class, true, traversal.asAdmin()));
+    }
+
     /**
      * Spawns a {@link GraphTraversal} starting with all edges or some subset 
of edges as specified by their unique
      * identifier.
@@ -561,6 +580,24 @@ public class GraphTraversalSource implements 
TraversalSource {
         return traversal.addStep(step);
     }
 
+    /**
+     * Spawns a {@link GraphTraversal} starting with edges whose IDs are 
resolved from a child traversal.
+     * As a start step, a synthetic traverser is generated to seed the child 
traversal evaluation,
+     * consistent with how {@code mergeE(traversal)} handles start steps. The 
child traversal should
+     * be self-contained (e.g., {@code __.V(1).outE().id()}) rather than 
dependent on an incoming traverser.
+     *
+     * @param traversal the child traversal that produces edge IDs
+     * @since 4.0.0
+     */
+    public GraphTraversal<Edge, Edge> E(final Traversal<?, ?> traversal) {
+        if (null == traversal) return E(new Object[]{ null });
+        ChildTraversalValidator.validate(traversal.asAdmin());
+        final GraphTraversalSource clone = this.clone();
+        clone.gremlinLang.addStep(GraphTraversal.Symbols.E, traversal);
+        final GraphTraversal.Admin<Edge, Edge> traversalAdmin = new 
DefaultGraphTraversal<>(clone);
+        return traversalAdmin.addStep(new GraphStep<>(traversalAdmin, 
Edge.class, true, traversal.asAdmin()));
+    }
+
     /**
      * Spawns a {@link GraphTraversal} starting with a list of available 
services.
      *
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index 01986b4534..46eddc4e2f 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -140,6 +140,13 @@ public class __ {
         return __.<A>start().V(vertexIdsOrElements);
     }
 
+    /**
+     * @see GraphTraversal#V(Traversal)
+     */
+    public static <A> GraphTraversal<A, Vertex> V(final Traversal<?, ?> 
traversal) {
+        return __.<A>start().V(traversal);
+    }
+
     /**
      * @see GraphTraversal#E(Object...)
      */
@@ -147,6 +154,13 @@ public class __ {
         return __.<A>start().E(edgeIdsOrElements);
     }
 
+    /**
+     * @see GraphTraversal#E(Traversal)
+     */
+    public static <A> GraphTraversal<A, Edge> E(final Traversal<?, ?> 
traversal) {
+        return __.<A>start().E(traversal);
+    }
+
     /**
      * @see GraphTraversal#to(Direction)
      */
@@ -1092,6 +1106,13 @@ public class __ {
         return __.<A>start().has(propertyKey, predicate);
     }
 
+    /**
+     * @see GraphTraversal#has(String, Traversal)
+     */
+    public static <A> GraphTraversal<A, A> has(final String propertyKey, final 
Traversal<?, ?> traversal) {
+        return __.<A>start().has(propertyKey, traversal);
+    }
+
     /**
      * @see GraphTraversal#has(T, P)
      */
@@ -1113,6 +1134,13 @@ public class __ {
         return __.<A>start().has(accessor, value);
     }
 
+    /**
+     * @see GraphTraversal#has(T, Traversal)
+     */
+    public static <A> GraphTraversal<A, A> has(final T accessor, final 
Traversal<?, ?> traversal) {
+        return __.<A>start().has(accessor, traversal);
+    }
+
     /**
      * @see GraphTraversal#has(String, String, Object)
      */
@@ -1142,6 +1170,13 @@ public class __ {
         return __.<A>start().has(label, propertyKey, predicate);
     }
 
+    /**
+     * @see GraphTraversal#has(String, String, Traversal)
+     */
+    public static <A> GraphTraversal<A, A> has(final String label, final 
String propertyKey, final Traversal<?, ?> traversal) {
+        return __.<A>start().has(label, propertyKey, traversal);
+    }
+
     /**
      * @see GraphTraversal#has(String)
      */
@@ -1177,6 +1212,13 @@ public class __ {
         return __.<A>start().hasLabel(predicate);
     }
 
+    /**
+     * @see GraphTraversal#hasLabel(Traversal)
+     */
+    public static <A> GraphTraversal<A, A> hasLabel(final Traversal<?, ?> 
traversal) {
+        return __.<A>start().hasLabel(traversal);
+    }
+
     /**
      * @see GraphTraversal#hasId(Object, Object...)
      */
@@ -1558,6 +1600,13 @@ public class __ {
         return __.<A>start().property(value);
     }
 
+    /**
+     * @see GraphTraversal#property(Traversal)
+     */
+    public static <A> GraphTraversal<A, A> property(final Traversal<?, ?> 
mapTraversal) {
+        return __.<A>start().property(mapTraversal);
+    }
+
     ///////////////////// BRANCH STEPS /////////////////////
 
     /**
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/AcceptsChildPredicateTraversal.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/AcceptsChildPredicateTraversal.java
new file mode 100644
index 0000000000..fef3841441
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/AcceptsChildPredicateTraversal.java
@@ -0,0 +1,34 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.step;
+
+/**
+ * Marker interface for steps that accept user-supplied child traversals 
embedded as predicate or lookup
+ * arguments (for example {@code has(key, traversal)}, {@code 
is(P.gt(traversal))},
+ * {@code V(traversal)}, or {@code property(traversal)}).
+ * <p>
+ * The {@link 
org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ChildTraversalVerificationStrategy}
+ * validates the local children of every step implementing this interface, 
rejecting child traversals that
+ * contain mutating steps. New steps that accept such traversals should 
implement this interface so they are
+ * validated automatically rather than relying on a hardcoded step list.
+ *
+ * @since 4.0.0
+ */
+public interface AcceptsChildPredicateTraversal {
+}
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
index f016aa03f4..47343d3ea6 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/HasContainerHolder.java
@@ -53,7 +53,7 @@ public interface HasContainerHolder<S, E> extends 
GValueHolder<S, E> {
     }
 
     public default Collection<P<?>> getPredicatesGValueSafe() {
-        return getHasContainers().stream().map(p -> 
p.getPredicate()).collect(Collectors.toList());
+        return getHasContainers().stream().map(p -> p.getPredicate()).filter(p 
-> p != null).collect(Collectors.toList());
     }
 
     public default HasContainerHolder<S, E> asConcreteStep() {
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java
index 64853f6614..f17795dc0b 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java
@@ -21,15 +21,19 @@ package 
org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
-public final class AllStep<S, S2> extends FilterStep<S> {
+public final class AllStep<S, S2> extends FilterStep<S> implements 
TraversalParent, AcceptsChildPredicateTraversal {
 
     private P<S2> predicate;
 
@@ -41,10 +45,16 @@ public final class AllStep<S, S2> extends FilterStep<S> {
         }
 
         this.predicate = predicate;
+        P.integrateTraversals(this.predicate, this);
     }
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        if (this.predicate.hasTraversal()) {
+            this.predicate.resolve(traverser);
+            if (this.predicate.isResolvedEmpty()) return false;
+        }
+
         final S item = traverser.get();
 
         if (item instanceof Iterable || item instanceof Iterator || ((item != 
null) && item.getClass().isArray())) {
@@ -65,6 +75,14 @@ public final class AllStep<S, S2> extends FilterStep<S> {
         return StringFactory.stepString(this, this.predicate);
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>();
+        P.collectTraversals(this.predicate, traversals);
+        return (List) Collections.unmodifiableList(traversals);
+    }
+
     @Override
     public AllStep<S, S2> clone() {
         final AllStep<S, S2> clone = (AllStep<S, S2>) super.clone();
@@ -74,6 +92,12 @@ public final class AllStep<S, S2> extends FilterStep<S> {
 
     @Override
     public Set<TraverserRequirement> getRequirements() {
-        return EnumSet.of(TraverserRequirement.OBJECT);
+        return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        P.integrateTraversals(this.predicate, this);
     }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java
index 9881186d7c..ed0216934c 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java
@@ -21,15 +21,19 @@ package 
org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
-public final class AnyStep<S, S2> extends FilterStep<S> {
+public final class AnyStep<S, S2> extends FilterStep<S> implements 
TraversalParent, AcceptsChildPredicateTraversal {
 
     private P<S2> predicate;
 
@@ -41,10 +45,16 @@ public final class AnyStep<S, S2> extends FilterStep<S> {
         }
 
         this.predicate = predicate;
+        P.integrateTraversals(this.predicate, this);
     }
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        if (this.predicate.hasTraversal()) {
+            this.predicate.resolve(traverser);
+            if (this.predicate.isResolvedEmpty()) return false;
+        }
+
         final S item = traverser.get();
 
         if (item instanceof Iterable || item instanceof Iterator || ((item != 
null) && item.getClass().isArray())) {
@@ -65,6 +75,14 @@ public final class AnyStep<S, S2> extends FilterStep<S> {
         return StringFactory.stepString(this, this.predicate);
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>();
+        P.collectTraversals(this.predicate, traversals);
+        return (List) Collections.unmodifiableList(traversals);
+    }
+
     @Override
     public AnyStep<S, S2> clone() {
         final AnyStep<S, S2> clone = (AnyStep<S, S2>) super.clone();
@@ -74,6 +92,12 @@ public final class AnyStep<S, S2> extends FilterStep<S> {
 
     @Override
     public Set<TraverserRequirement> getRequirements() {
-        return EnumSet.of(TraverserRequirement.OBJECT);
+        return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        P.integrateTraversals(this.predicate, this);
     }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java
index e7ef009565..1b56dbdfeb 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/HasStep.java
@@ -18,10 +18,13 @@
  */
 package org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 
+import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring;
 import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
@@ -31,22 +34,25 @@ import 
org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public class HasStep<S extends Element> extends FilterStep<S> implements 
HasContainerHolder<S, S>, Configuring {
+public class HasStep<S extends Element> extends FilterStep<S> implements 
HasContainerHolder<S, S>, Configuring, TraversalParent, 
AcceptsChildPredicateTraversal {
 
     private final Parameters parameters = new Parameters();
     private List<HasContainer> hasContainers;
+    private List<Traversal.Admin<?, ?>> childTraversals = new ArrayList<>();
 
     public HasStep(final Traversal.Admin traversal, final HasContainer... 
hasContainers) {
         super(traversal);
         this.hasContainers = new ArrayList<>();
-        Collections.addAll(this.hasContainers, hasContainers);
+        for (final HasContainer hc : hasContainers) {
+            this.hasContainers.add(hc);
+            collectChildTraversals(hc);
+        }
     }
 
     @Override
@@ -63,14 +69,71 @@ public class HasStep<S extends Element> extends 
FilterStep<S> implements HasCont
     protected boolean filter(final Traverser.Admin<S> traverser) {
         // the generic S is defined as Element but Property can also be used 
with HasStep so this seems to cause
         // problems with some jdk versions.
-        if (traverser.get() instanceof Element)
-            return HasContainer.testAll(traverser.get(), this.hasContainers);
-        else if (traverser.get() instanceof Property)
-            return HasContainer.testAll((Property) traverser.get(), 
this.hasContainers);
-        else
+        // Check Property BEFORE Element because VertexProperty implements 
both interfaces.
+        // HasContainer.test(Property) correctly handles T.key and T.value 
accessors, whereas
+        // HasContainer.test(Element) would fall through to 
element.properties("~key") which fails.
+        if (traverser.get() instanceof Property) {
+            return testAllWithTraversals(traverser, (Property) 
traverser.get());
+        } else if (traverser.get() instanceof Element) {
+            return testAllWithTraversals(traverser, (Element) traverser.get());
+        } else {
             throw new IllegalStateException(String.format(
                     "Traverser to has() must be of type Property or Element, 
not %s",
                     traverser.get().getClass().getName()));
+        }
+    }
+
+    /**
+     * Tests all HasContainers against an Element. Traversal-bearing 
containers carry a {@code P} predicate
+     * (e.g. {@code P.eq(traversal)}) whose child traversal is resolved 
against the current traverser before
+     * the standard {@link HasContainer#test(Element)} is applied.
+     */
+    private boolean testAllWithTraversals(final Traverser.Admin<S> traverser, 
final Element element) {
+        for (final HasContainer hc : this.hasContainers) {
+            if (!resolvePredicate(hc, traverser) || !hc.test(element)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests all HasContainers against a Property. See {@link 
#testAllWithTraversals(Traverser.Admin, Element)}.
+     */
+    private boolean testAllWithTraversals(final Traverser.Admin<S> traverser, 
final Property property) {
+        for (final HasContainer hc : this.hasContainers) {
+            if (!resolvePredicate(hc, traverser) || !hc.test(property)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Resolves a container's predicate traversal (if any) against the current 
traverser, mutating the
+     * predicate with the resolved value for this test cycle.
+     *
+     * @return {@code false} if the predicate resolved empty (no comparison 
value), meaning the element
+     *         cannot match and the caller should short-circuit; {@code true} 
otherwise.
+     */
+    private boolean resolvePredicate(final HasContainer hc, final 
Traverser.Admin<S> traverser) {
+        if (hc.hasTraversal()) {
+            hc.getPredicate().resolve(traverser);
+            return !hc.getPredicate().isResolvedEmpty();
+        }
+        return true;
+    }
+
+    /**
+     * Collects the child traversals embedded within a HasContainer's 
predicate (including
+     * {@link 
org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP}, which may 
carry traversals on
+     * child predicates) into {@link #childTraversals}. Parenting is deferred 
to
+     * {@link #setTraversal(Traversal.Admin)} which is the single integration 
point.
+     */
+    private void collectChildTraversals(final HasContainer hc) {
+        if (hc.getPredicate() != null && hc.getPredicate().hasTraversal()) {
+            P.collectTraversals(hc.getPredicate(), this.childTraversals);
+        }
     }
 
     @Override
@@ -91,23 +154,49 @@ public class HasStep<S extends Element> extends 
FilterStep<S> implements HasCont
     @Override
     public void addHasContainer(final HasContainer hasContainer) {
         this.hasContainers.add(hasContainer);
+        // A container may be added after this step has already been parented 
(e.g. by a strategy), so collect
+        // its child traversals and integrate just those new ones immediately.
+        final int before = this.childTraversals.size();
+        collectChildTraversals(hasContainer);
+        for (int i = before; i < this.childTraversals.size(); i++) {
+            this.integrateChild(this.childTraversals.get(i));
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        return (List) Collections.unmodifiableList(this.childTraversals);
     }
 
     @Override
     public Set<TraverserRequirement> getRequirements() {
-        return EnumSet.of(TraverserRequirement.OBJECT);
+        return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
     }
 
     @Override
     public HasStep<S> clone() {
         final HasStep<S> clone = (HasStep<S>) super.clone();
         clone.hasContainers = new ArrayList<>();
+        clone.childTraversals = new ArrayList<>();
         for (final HasContainer hasContainer : this.hasContainers) {
-            clone.addHasContainer(hasContainer.clone());
+            final HasContainer clonedHc = hasContainer.clone();
+            clone.hasContainers.add(clonedHc);
+            if (clonedHc.getPredicate() != null && 
clonedHc.getPredicate().hasTraversal()) {
+                P.collectTraversals(clonedHc.getPredicate(), 
clone.childTraversals);
+            }
         }
         return clone;
     }
 
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        for (final Traversal.Admin<?, ?> childTraversal : 
this.childTraversals) {
+            this.integrateChild(childTraversal);
+        }
+    }
+
     @Override
     public int hashCode() {
         int result = super.hashCode();
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/IsStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/IsStep.java
index 70ad55ff31..e49c7e3537 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/IsStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/IsStep.java
@@ -21,27 +21,36 @@ package 
org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 
-import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
  * @author Daniel Kuppitz (http://gremlin.guru)
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class IsStep<S> extends FilterStep<S> implements 
IsStepContract<S> {
+public final class IsStep<S> extends FilterStep<S> implements 
IsStepContract<S>, TraversalParent, AcceptsChildPredicateTraversal {
 
     private P<S> predicate;
 
     public IsStep(final Traversal.Admin traversal, final P<S> predicate) {
         super(traversal);
         this.predicate = predicate;
+        P.integrateTraversals(this.predicate, this);
     }
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        if (this.predicate.hasTraversal()) {
+            this.predicate.resolve(traverser);
+            if (this.predicate.isResolvedEmpty()) return false;
+        }
         return this.predicate.test(traverser.get());
     }
 
@@ -55,6 +64,14 @@ public final class IsStep<S> extends FilterStep<S> 
implements IsStepContract<S>
         return this.predicate;
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>();
+        P.collectTraversals(this.predicate, traversals);
+        return (List) Collections.unmodifiableList(traversals);
+    }
+
     @Override
     public IsStep<S> clone() {
         final IsStep<S> clone = (IsStep<S>) super.clone();
@@ -69,7 +86,12 @@ public final class IsStep<S> extends FilterStep<S> 
implements IsStepContract<S>
 
     @Override
     public Set<TraverserRequirement> getRequirements() {
-        return EnumSet.of(TraverserRequirement.OBJECT);
+        return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
     }
 
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        P.integrateTraversals(this.predicate, this);
+    }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java
index 542d16ca5c..6344e3a2b8 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java
@@ -21,15 +21,19 @@ package 
org.apache.tinkerpop.gremlin.process.traversal.step.filter;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 
-import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
-public final class NoneStep<S, S2> extends FilterStep<S> {
+public final class NoneStep<S, S2> extends FilterStep<S> implements 
TraversalParent, AcceptsChildPredicateTraversal {
 
     private P<S2> predicate;
 
@@ -41,10 +45,18 @@ public final class NoneStep<S, S2> extends FilterStep<S> {
         }
 
         this.predicate = predicate;
+        P.integrateTraversals(this.predicate, this);
     }
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        if (this.predicate.hasTraversal()) {
+            this.predicate.resolve(traverser);
+            // Vacuously true: if the predicate can't be evaluated (no 
comparison value),
+            // then no item can match, so none() is satisfied.
+            if (this.predicate.isResolvedEmpty()) return true;
+        }
+
         final S item = traverser.get();
 
         if (item instanceof Iterable || item instanceof Iterator || ((item != 
null) && item.getClass().isArray())) {
@@ -65,6 +77,14 @@ public final class NoneStep<S, S2> extends FilterStep<S> {
         return StringFactory.stepString(this, this.predicate);
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>();
+        P.collectTraversals(this.predicate, traversals);
+        return (List) Collections.unmodifiableList(traversals);
+    }
+
     @Override
     public NoneStep<S, S2> clone() {
         final NoneStep<S, S2> clone = (NoneStep<S, S2>) super.clone();
@@ -74,6 +94,12 @@ public final class NoneStep<S, S2> extends FilterStep<S> {
 
     @Override
     public Set<TraverserRequirement> getRequirements() {
-        return EnumSet.of(TraverserRequirement.OBJECT);
+        return this.getSelfAndChildRequirements(TraverserRequirement.OBJECT);
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        P.integrateTraversals(this.predicate, this);
     }
 }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
index 77c3f4abc9..6a2a3a5770 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WherePredicateStep.java
@@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating;
 import org.apache.tinkerpop.gremlin.process.traversal.step.PathProcessor;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping;
@@ -45,7 +46,7 @@ import java.util.Set;
 /**
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  */
-public final class WherePredicateStep<S> extends FilterStep<S> implements 
Scoping, PathProcessor, ByModulating, TraversalParent {
+public final class WherePredicateStep<S> extends FilterStep<S> implements 
Scoping, PathProcessor, ByModulating, TraversalParent, 
AcceptsChildPredicateTraversal {
 
     protected String startKey;
     protected List<String> selectKeys;
@@ -54,6 +55,7 @@ public final class WherePredicateStep<S> extends 
FilterStep<S> implements Scopin
     protected Set<String> keepLabels;
 
     protected TraversalRing<S, ?> traversalRing = new TraversalRing<>();
+    private List<Traversal.Admin<?, ?>> predicateTraversals;
 
     public WherePredicateStep(final Traversal.Admin traversal, final 
Optional<String> startKey, final P<String> predicate) {
         super(traversal);
@@ -63,12 +65,19 @@ public final class WherePredicateStep<S> extends 
FilterStep<S> implements Scopin
         this.predicate = (P) predicate;
         this.selectKeys = new ArrayList<>();
         this.configurePredicates(this.predicate);
+        // Cache and integrate child traversals from the predicate
+        this.predicateTraversals = new ArrayList<>();
+        P.collectTraversals(this.predicate, this.predicateTraversals);
+        for (final Traversal.Admin<?, ?> t : this.predicateTraversals) {
+            this.integrateChild(t);
+        }
     }
 
     private void configurePredicates(final P<Object> predicate) {
         if (predicate instanceof ConnectiveP)
             ((ConnectiveP<Object>) 
predicate).getPredicates().forEach(this::configurePredicates);
-        else {
+        else if (!predicate.hasTraversal()) {
+            // Only configure scope keys for non-traversal predicates (string 
label references)
             final String selectKey = getSelectKey(predicate);
             this.selectKeys.add(selectKey);
             this.scopeKeys.add(selectKey);
@@ -112,14 +121,28 @@ public final class WherePredicateStep<S> extends 
FilterStep<S> implements Scopin
 
     @Override
     protected boolean filter(final Traverser.Admin<S> traverser) {
+        // Resolve the left-hand value (current traverser or startKey scope 
value)
         final TraversalProduct product = null == this.startKey ?
                 TraversalUtil.produce(traverser, this.traversalRing.next()) :
                 TraversalUtil.produce((S) this.getSafeScopeValue(Pop.last, 
this.startKey, traverser), this.traversalRing.next());
 
-        final boolean predicateValuesProductive = 
this.setPredicateValues(this.predicate, traverser, this.selectKeys.iterator());
-        this.traversalRing.reset();
+        if (!product.isProductive()) {
+            this.traversalRing.reset();
+            return false;
+        }
 
-        return product.isProductive() && predicateValuesProductive && 
this.predicate.test(product.get());
+        if (this.predicate.hasTraversal()) {
+            // Traversal-bearing predicate: resolve the child traversal, then 
test
+            this.traversalRing.reset();
+            this.predicate.resolve(traverser);
+            if (this.predicate.isResolvedEmpty()) return false;
+            return this.predicate.test(product.get());
+        } else {
+            // Standard scope-label path: resolve predicate values from path 
labels
+            final boolean predicateValuesProductive = 
this.setPredicateValues(this.predicate, traverser, this.selectKeys.iterator());
+            this.traversalRing.reset();
+            return predicateValuesProductive && 
this.predicate.test(product.get());
+        }
     }
 
     @Override
@@ -166,7 +189,12 @@ public final class WherePredicateStep<S> extends 
FilterStep<S> implements Scopin
 
     @Override
     public List<Traversal.Admin<S, ?>> getLocalChildren() {
-        return (List) this.traversalRing.getTraversals();
+        if (this.predicateTraversals.isEmpty()) {
+            return (List) this.traversalRing.getTraversals();
+        }
+        final List<Traversal.Admin<S, ?>> children = new ArrayList<>((List) 
this.traversalRing.getTraversals());
+        children.addAll((List) this.predicateTraversals);
+        return children;
     }
 
     @Override
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
index 7e64cd391a..c000c7c164 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java
@@ -24,11 +24,14 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.Contains;
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
 import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
 import 
org.apache.tinkerpop.gremlin.process.traversal.util.FastNoSuchElementException;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.T;
@@ -37,10 +40,12 @@ import 
org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
 import org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.util.iterator.EmptyIterator;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Supplier;
 
@@ -48,7 +53,7 @@ import java.util.function.Supplier;
  * @author Marko A. Rodriguez (http://markorodriguez.com)
  * @author Pieter Martin
  */
-public class GraphStep<S, E extends Element> extends AbstractStep<S, E> 
implements GraphStepContract<S, E> {
+public class GraphStep<S, E extends Element> extends AbstractStep<S, E> 
implements GraphStepContract<S, E>, TraversalParent, 
AcceptsChildPredicateTraversal {
 
     protected Parameters parameters = new Parameters();
     protected final Class<E> returnClass;
@@ -58,6 +63,7 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
     protected boolean done = false;
     private Traverser.Admin<S> head = null;
     private Iterator<E> iterator = EmptyIterator.instance();
+    private Traversal.Admin<?, ?> idTraversal;
 
 
     public GraphStep(final Traversal.Admin traversal, final Class<E> 
returnClass, final boolean isStart, final Object... ids) {
@@ -76,7 +82,36 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
                 this.getTraversal().getGraph().get().edges(this.ids));
     }
 
+    public GraphStep(final Traversal.Admin traversal, final Class<E> 
returnClass, final boolean isStart, final Traversal.Admin<?, ?> idTraversal) {
+        this(traversal, returnClass, isStart);
+        this.idTraversal = idTraversal;
+        if (this.idTraversal != null) {
+            this.integrateChild(this.idTraversal);
+        }
+    }
+
+    /**
+     * Returns the child traversal used to resolve element IDs, or {@code 
null} if literal IDs are used.
+     */
+    public Traversal.Admin<?, ?> getIdTraversal() {
+        return this.idTraversal;
+    }
+
+    /**
+     * Sets the child traversal used to resolve element IDs. Calls {@link 
#integrateChild(Traversal.Admin)}
+     * on the provided traversal.
+     */
+    public void setIdTraversal(final Traversal.Admin<?, ?> idTraversal) {
+        this.idTraversal = idTraversal;
+        if (this.idTraversal != null) {
+            this.integrateChild(this.idTraversal);
+        }
+    }
+
     public String toString() {
+        if (this.idTraversal != null) {
+            return StringFactory.stepString(this, 
this.returnClass.getSimpleName().toLowerCase(), this.idTraversal);
+        }
         return StringFactory.stepString(this, 
this.returnClass.getSimpleName().toLowerCase(), Arrays.toString(this.ids));
     }
 
@@ -164,16 +199,65 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
                 if (this.isStart) {
                     if (this.done)
                         throw FastNoSuchElementException.instance();
-                    else {
+                    if (this.idTraversal != null) {
+                        // Start step with idTraversal: generate a synthetic 
traverser to seed
+                        // the child traversal, consistent with how 
mergeV/addV handle start steps.
+                        this.done = true;
+                        final Traverser.Admin<S> syntheticTraverser = 
(Traverser.Admin<S>)
+                                
this.getTraversal().getTraverserGenerator().generate(false, (Step) this, 1L);
+                        final Object[] resolvedIds = 
resolveTraversalIds(syntheticTraverser);
+                        this.iterator = lookupElements(resolvedIds);
+                    } else {
                         this.done = true;
                         this.iterator = null == this.iteratorSupplier ? 
EmptyIterator.instance() : this.iteratorSupplier.get();
                     }
                 } else {
                     this.head = this.starts.next();
-                    this.iterator = null == this.iteratorSupplier ? 
EmptyIterator.instance() : this.iteratorSupplier.get();
+                    if (this.idTraversal != null) {
+                        final Object[] resolvedIds = 
resolveTraversalIds(this.head);
+                        this.iterator = lookupElements(resolvedIds);
+                    } else {
+                        this.iterator = null == this.iteratorSupplier ? 
EmptyIterator.instance() : this.iteratorSupplier.get();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Resolves element IDs by evaluating the child traversal against the 
current traverser.
+     * Handles Element (extract ID), Collection (unpack), and raw values.
+     */
+    @SuppressWarnings("unchecked")
+    private Object[] resolveTraversalIds(final Traverser.Admin<S> traverser) {
+        final List<Object> ids = new ArrayList<>();
+        TraversalUtil.prepare(traverser, (Traversal.Admin) this.idTraversal);
+        while (this.idTraversal.hasNext()) {
+            final Object result = this.idTraversal.next();
+            if (result instanceof Element) {
+                ids.add(((Element) result).id());
+            } else if (result instanceof Collection) {
+                for (final Object item : (Collection<?>) result) {
+                    ids.add(item instanceof Element ? ((Element) item).id() : 
item);
                 }
+            } else {
+                ids.add(result);
             }
         }
+        return ids.toArray();
+    }
+
+    /**
+     * Looks up elements (vertices or edges) by the given IDs using the graph.
+     */
+    @SuppressWarnings("unchecked")
+    private Iterator<E> lookupElements(final Object[] elementIds) {
+        if (elementIds.length == 0) {
+            return EmptyIterator.instance();
+        }
+        return (Iterator<E>) (Vertex.class.isAssignableFrom(this.returnClass) ?
+                this.getTraversal().getGraph().get().vertices(elementIds) :
+                this.getTraversal().getGraph().get().edges(elementIds));
     }
 
     @Override
@@ -182,6 +266,9 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
         this.head = null;
         this.done = false;
         this.iterator = EmptyIterator.instance();
+        if (this.idTraversal != null) {
+            this.idTraversal.reset();
+        }
     }
 
     @Override
@@ -195,6 +282,31 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
         return result;
     }
 
+    @SuppressWarnings("unchecked")
+    @Override
+    public <S, E> List<Traversal.Admin<S, E>> getLocalChildren() {
+        return this.idTraversal != null
+                ? Collections.singletonList((Traversal.Admin<S, E>) 
this.idTraversal)
+                : Collections.emptyList();
+    }
+
+    @Override
+    public GraphStep<S, E> clone() {
+        final GraphStep<S, E> clone = (GraphStep<S, E>) super.clone();
+        if (this.idTraversal != null) {
+            clone.idTraversal = this.idTraversal.clone();
+        }
+        return clone;
+    }
+
+    @Override
+    public void setTraversal(final Traversal.Admin<?, ?> parentTraversal) {
+        super.setTraversal(parentTraversal);
+        if (this.idTraversal != null) {
+            this.integrateChild(this.idTraversal);
+        }
+    }
+
     /**
      * Attempts to close an underlying iterator if it is of type {@link 
CloseableIterator}. Graph providers may choose
      * to return this interface containing their vertices and edges if there 
are expensive resources that might need to
@@ -203,6 +315,11 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
     @Override
     public void close() {
         CloseableIterator.closeIterator(iterator);
+        try {
+            TraversalParent.super.close();
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -213,6 +330,7 @@ public class GraphStep<S, E extends Element> extends 
AbstractStep<S, E> implemen
      * @return true if the {@link HasContainer} updated ids and thus, was 
processed.
      */
     public static boolean processHasContainerIds(final GraphStep<?, ?> 
graphStep, final HasContainer hasContainer) {
+        if (hasContainer.hasTraversal()) return false;
         final String key = hasContainer.getKey();
         if (key != null && key.equals(T.id.getAccessor()) && 
graphStep.ids.length == 0 &&
                 (hasContainer.getBiPredicate() == Compare.eq || 
hasContainer.getBiPredicate() == Contains.within)) {
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
index 4223f98bd5..7b5acbf44a 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java
@@ -25,6 +25,7 @@ import 
org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters;
 import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.*;
 import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.EventStrategy;
 import 
org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil;
 import org.apache.tinkerpop.gremlin.structure.Edge;
 import org.apache.tinkerpop.gremlin.structure.Element;
 import org.apache.tinkerpop.gremlin.structure.Property;
@@ -35,6 +36,7 @@ import 
org.apache.tinkerpop.gremlin.structure.util.StringFactory;
 import org.apache.tinkerpop.gremlin.structure.util.keyed.KeyedProperty;
 import org.apache.tinkerpop.gremlin.structure.util.keyed.KeyedVertexProperty;
 
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -47,15 +49,34 @@ import java.util.Set;
  */
 public class AddPropertyStep<S extends Element> extends SideEffectStep<S> 
implements AddPropertyStepContract<S> {
 
+    /**
+     * Internal placeholder key used for the single-argument {@code 
property(traversal)} (Map-producing) form,
+     * where the property keys are supplied by the traversal's resulting Map 
rather than by a caller-provided key.
+     * This value is never used for dispatch (see {@link #mapForm}); it exists 
only to give the parameter a
+     * stable, non-null key within {@link 
org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters}.
+     */
+    static final String MAP_VALUE_TRAVERSAL_KEY = "~traversalMap";
+
     private Parameters internalParameters = new Parameters();
     private Parameters withConfiguration = new Parameters();
     private final VertexProperty.Cardinality cardinality;
+    /**
+     * Marks the single-argument {@code property(traversal)} form whose child 
traversal must produce a Map of
+     * property key/value pairs. Dispatch is driven by this flag rather than 
by inspecting the property key, so a
+     * caller-supplied key that happens to collide with {@link 
#MAP_VALUE_TRAVERSAL_KEY} is handled normally.
+     */
+    private final boolean mapForm;
     private CallbackRegistry<Event.ElementPropertyChangedEvent> 
callbackRegistry;
 
     public AddPropertyStep(final Traversal.Admin traversal, final 
VertexProperty.Cardinality cardinality, final Object keyObject, final Object 
valueObject) {
+        this(traversal, cardinality, keyObject, valueObject, false);
+    }
+
+    public AddPropertyStep(final Traversal.Admin traversal, final 
VertexProperty.Cardinality cardinality, final Object keyObject, final Object 
valueObject, final boolean mapForm) {
         super(traversal);
         this.internalParameters.set(this, T.key, keyObject, T.value, 
valueObject);
         this.cardinality = cardinality;
+        this.mapForm = mapForm;
     }
 
     @Override
@@ -89,11 +110,109 @@ public class AddPropertyStep<S extends Element> extends 
SideEffectStep<S> implem
             throw new IllegalStateException(String.format("T.%s is immutable 
on existing elements", ((T) k).name()));
 
         final String key = (String) k;
-        final Object value = this.internalParameters.get(traverser, T.value, 
() -> {
-            throw new IllegalStateException("The AddPropertyStep does not have 
a provided value: " + this);
-        }).get(0);
+
+        // Check if the raw value is a traversal to enable multi-result 
handling.
+        // Exclude ConstantTraversal which is used internally by TinkerPop to 
wrap literal values.
+        final List<Object> rawValues = this.internalParameters.get(T.value, 
null);
+        final boolean valueIsTraversal = !rawValues.isEmpty()
+                && rawValues.get(0) instanceof Traversal.Admin
+                && !(rawValues.get(0) instanceof ConstantTraversal);
+
+        if (valueIsTraversal) {
+            handleTraversalValue(traverser, key);
+        } else {
+            final Object value = this.internalParameters.get(traverser, 
T.value, () -> {
+                throw new IllegalStateException("The AddPropertyStep does not 
have a provided value: " + this);
+            }).get(0);
+            final Object[] vertexPropertyKeyValues = 
this.internalParameters.getKeyValues(traverser, T.key, T.value);
+            applyPropertyMutation(traverser, key, value, 
vertexPropertyKeyValues);
+        }
+    }
+
+    /**
+     * Handles the case where the value argument is a child traversal, 
supporting multi-result
+     * resolution, Map-producing traversals, and cardinality-aware property 
setting.
+     */
+    private void handleTraversalValue(final Traverser.Admin<S> traverser, 
final String key) {
+        final List<Object> rawValues = this.internalParameters.get(T.value, 
null);
+        final Traversal.Admin<?, ?> valueTraversal = (Traversal.Admin<?, ?>) 
rawValues.get(0);
+
+        // Collect all results from the child traversal
+        final List<Object> results = new ArrayList<>();
+        final Traverser.Admin<S> trav = traverser;
+        final Iterator<?> itty = TraversalUtil.<S, Object>applyAll(trav, 
(Traversal.Admin<S, Object>) (Traversal.Admin) valueTraversal);
+        while (itty.hasNext()) {
+            results.add(itty.next());
+        }
+
+        // No-result case: skip mutation, pass element through unchanged
+        if (results.isEmpty()) {
+            return;
+        }
+
+        final Element element = traverser.get();
         final Object[] vertexPropertyKeyValues = 
this.internalParameters.getKeyValues(traverser, T.key, T.value);
 
+        // Map-producing form: single-argument property(traversal) where the 
traversal produces a Map.
+        // Only the dedicated map form expands a Map into per-entry 
properties; for the key+traversal form a
+        // Map result is treated as an ordinary property value.
+        if (this.mapForm) {
+            if (results.size() == 1 && results.get(0) instanceof Map) {
+                final Map<?, ?> map = (Map<?, ?>) results.get(0);
+                for (final Map.Entry<?, ?> entry : map.entrySet()) {
+                    if (!(entry.getKey() instanceof String)) {
+                        throw new IllegalArgumentException(
+                                "Property key must be a String but found " + 
entry.getKey().getClass().getSimpleName());
+                    }
+                    final String mapKey = (String) entry.getKey();
+                    applyPropertyMutation(traverser, mapKey, entry.getValue(), 
vertexPropertyKeyValues);
+                }
+                return;
+            }
+
+            // The map form requires a Map result. Reject anything else rather 
than guessing.
+            final Object result = results.get(0);
+            throw new IllegalArgumentException(
+                    "property(traversal) requires the traversal to produce a 
Map, but got: " +
+                    (result == null ? "null" : 
result.getClass().getSimpleName()));
+        }
+
+        // Multi-result handling with cardinality awareness
+        if (results.size() > 1) {
+            // Determine effective cardinality
+            final VertexProperty.Cardinality effectiveCard = this.cardinality 
!= null
+                    ? this.cardinality
+                    : (element instanceof Vertex ? 
element.graph().features().vertex().getCardinality(key) : null);
+
+            if (effectiveCard == VertexProperty.Cardinality.single) {
+                throw new IllegalArgumentException(
+                        "Single-cardinality property requires exactly one 
value, but traversal produced " +
+                                results.size() + " results");
+            }
+
+            // For list/set cardinality with multiple results, add each as a 
separate property value
+            if (effectiveCard == VertexProperty.Cardinality.list || 
effectiveCard == VertexProperty.Cardinality.set) {
+                if (!(element instanceof Vertex)) {
+                    throw new IllegalStateException(String.format(
+                            "Property cardinality can only be set for a Vertex 
but the traversal encountered %s for key: %s",
+                            element.getClass().getSimpleName(), key));
+                }
+                for (final Object v : results) {
+                    applyPropertyMutation(traverser, key, v, 
vertexPropertyKeyValues);
+                }
+                return;
+            }
+        }
+
+        // Single result or default cardinality: use the first result
+        applyPropertyMutation(traverser, key, results.get(0), 
vertexPropertyKeyValues);
+    }
+
+    /**
+     * Applies a single property mutation to the element, handling eventing 
and cardinality.
+     */
+    private void applyPropertyMutation(final Traverser.Admin<S> traverser, 
final String key,
+                                       final Object value, final Object[] 
vertexPropertyKeyValues) {
         final Element element = traverser.get();
 
         // can't set cardinality if the element is something other than a 
vertex as only vertices can have
@@ -116,7 +235,7 @@ public class AddPropertyStep<S extends Element> extends 
SideEffectStep<S> implem
 
         final VertexProperty.Cardinality card = this.cardinality != null
                 ? this.cardinality
-                : element.graph().features().vertex().getCardinality(key);
+                : (element instanceof Vertex ? 
element.graph().features().vertex().getCardinality(key) : null);
 
         // update property
         if (element instanceof Vertex) {
@@ -183,7 +302,7 @@ public class AddPropertyStep<S extends Element> extends 
SideEffectStep<S> implem
 
     @Override
     public int hashCode() {
-        final int hash = super.hashCode() ^ this.internalParameters.hashCode() 
^ this.withConfiguration.hashCode();
+        int hash = super.hashCode() ^ this.internalParameters.hashCode() ^ 
this.withConfiguration.hashCode() ^ Boolean.hashCode(this.mapForm);
         return (null != this.cardinality) ? (hash ^ cardinality.hashCode()) : 
hash;
     }
 
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepContract.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepContract.java
index c6547ad425..70f430db4a 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepContract.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepContract.java
@@ -19,6 +19,7 @@
 package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring;
 import org.apache.tinkerpop.gremlin.process.traversal.step.Deleting;
 import org.apache.tinkerpop.gremlin.process.traversal.step.GValue;
@@ -33,7 +34,8 @@ import java.util.HashSet;
 import java.util.List;
 
 public interface AddPropertyStepContract<S> extends Step<S, S>, 
TraversalParent, Scoping, PropertiesHolder,
-        Writing<Event.ElementPropertyChangedEvent>, 
Deleting<Event.ElementPropertyChangedEvent>, Configuring {
+        Writing<Event.ElementPropertyChangedEvent>, 
Deleting<Event.ElementPropertyChangedEvent>, Configuring,
+        AcceptsChildPredicateTraversal {
 
     /**
      * Concrete implementations of this contract that can be referenced as 
TinkerPop implementations.
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepPlaceholder.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepPlaceholder.java
index 3bb1c087cf..3dd2734cf3 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepPlaceholder.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStepPlaceholder.java
@@ -62,8 +62,18 @@ public class AddPropertyStepPlaceholder<S extends Element> 
extends SideEffectSte
 
     private Parameters withConfiguration = new Parameters();
 
+    /**
+     * Marks the single-argument {@code property(traversal)} (Map-producing) 
form.
+     */
+    private boolean mapForm;
+
     public AddPropertyStepPlaceholder(final Traversal.Admin traversal, final 
VertexProperty.Cardinality cardinality, final Object keyObject, final Object 
valueObject) {
+        this(traversal, cardinality, keyObject, valueObject, false);
+    }
+
+    public AddPropertyStepPlaceholder(final Traversal.Admin traversal, final 
VertexProperty.Cardinality cardinality, final Object keyObject, final Object 
valueObject, final boolean mapForm) {
         super(traversal);
+        this.mapForm = mapForm;
         if (keyObject instanceof GValue) {
             throw new IllegalArgumentException("GValue is not allowed for 
property keys");
         }
@@ -128,13 +138,14 @@ public class AddPropertyStepPlaceholder<S extends 
Element> extends SideEffectSte
         return Objects.equals(key, that.key) &&
                 Objects.equals(value, that.value) &&
                 cardinality == that.cardinality &&
+                mapForm == that.mapForm &&
                 Objects.equals(properties, that.properties) &&
                 Objects.equals(withConfiguration, that.withConfiguration);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), key, value, cardinality, 
properties, withConfiguration);
+        return Objects.hash(super.hashCode(), key, value, cardinality, 
mapForm, properties, withConfiguration);
     }
 
     @Override
@@ -176,6 +187,7 @@ public class AddPropertyStepPlaceholder<S extends Element> 
extends SideEffectSte
     public AddPropertyStepPlaceholder<S> clone() {
         final AddPropertyStepPlaceholder<S> clone = 
(AddPropertyStepPlaceholder<S>) super.clone();
         clone.cardinality = cardinality;
+        clone.mapForm = mapForm;
 
         // Attempt to deep clone key for Traversal and GValue. Shallow copy is 
fine if key is a String or enum
         if (this.key instanceof Traversal) {
@@ -218,7 +230,7 @@ public class AddPropertyStepPlaceholder<S extends Element> 
extends SideEffectSte
 
     @Override
     public AddPropertyStep<S> asConcreteStep() {
-        AddPropertyStep<S> step = new AddPropertyStep<>(traversal, 
cardinality, key, value instanceof GValue ? ((GValue<?>) value).get() : value);
+        AddPropertyStep<S> step = new AddPropertyStep<>(traversal, 
cardinality, key, value instanceof GValue ? ((GValue<?>) value).get() : value, 
mapForm);
 
         for (final Map.Entry<Object, List<Object>> entry : 
properties.entrySet()) {
             for (Object value : entry.getValue()) {
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
index 99a9c4e959..c6d0d1fe29 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainer.java
@@ -110,7 +110,7 @@ public class HasContainer implements Serializable, 
Cloneable, Predicate<Element>
     public HasContainer clone() {
         try {
             final HasContainer clone = (HasContainer) super.clone();
-            clone.predicate = this.predicate.clone();
+            clone.predicate = this.predicate != null ? this.predicate.clone() 
: null;
             return clone;
         } catch (final CloneNotSupportedException e) {
             throw new IllegalStateException(e.getMessage(), e);
@@ -142,6 +142,14 @@ public class HasContainer implements Serializable, 
Cloneable, Predicate<Element>
         return this.predicate.getValue();
     }
 
+    /**
+     * Determines if this {@code HasContainer}'s predicate holds a child 
traversal whose result is resolved
+     * at runtime (e.g. {@code P.eq(traversal)} or {@code 
P.within(traversal)}).
+     */
+    public boolean hasTraversal() {
+        return this.predicate != null && this.predicate.hasTraversal();
+    }
+
     ////////////
 
     /**
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
index 8a6edcde28..d8c260d87f 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java
@@ -114,6 +114,10 @@ public final class InlineFilterStrategy extends 
AbstractTraversalStrategy<Traver
     ///////////////////////////
 
     private static boolean processHasStep(final HasStep<?> step, final 
Traversal.Admin<?, ?> traversal) {
+        if 
(step.getHasContainers().stream().anyMatch(HasContainer::hasTraversal)) return 
false;
+        if (step.getPreviousStep() instanceof HasStep) {
+            if (((HasStep<?>) 
step.getPreviousStep()).getHasContainers().stream().anyMatch(HasContainer::hasTraversal))
 return false;
+        }
         if (step.getPreviousStep() instanceof HasStep) {
             final HasStep<?> previousStep = (HasStep<?>) 
step.getPreviousStep();
             final List<HasContainer> hasContainers = new 
ArrayList<>(step.getHasContainers());
@@ -221,6 +225,11 @@ public final class InlineFilterStrategy extends 
AbstractTraversalStrategy<Traver
             InlineFilterStrategy.instance().apply(childTraversal);
             for (final Step<?, ?> childStep : childTraversal.getSteps()) {
                 if (childStep instanceof HasStep) {
+                    // Skip optimization if any HasContainer has a 
traversal-bearing predicate
+                    if (((HasStep<?>) 
childStep).getHasContainers().stream().anyMatch(HasContainer::hasTraversal)) {
+                        process = false;
+                        break;
+                    }
                     P p = null;
                     for (final HasContainer hasContainer : ((HasStep<?>) 
childStep).getHasContainers()) {
                         if (null == key)
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java
new file mode 100644
index 0000000000..240cc08a85
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java
@@ -0,0 +1,61 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.strategy.verification;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.AcceptsChildPredicateTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+import 
org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import 
org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator;
+
+/**
+ * Validates that child traversals in filter, lookup, and mutation steps do 
not contain mutating steps.
+ * Serves as a safety net for programmatic traversal construction that 
bypasses the DSL's
+ * construction-time validation.
+ */
+public final class ChildTraversalVerificationStrategy
+        extends 
AbstractTraversalStrategy<TraversalStrategy.VerificationStrategy>
+        implements TraversalStrategy.VerificationStrategy {
+
+    private static final ChildTraversalVerificationStrategy INSTANCE = new 
ChildTraversalVerificationStrategy();
+
+    private ChildTraversalVerificationStrategy() {
+    }
+
+    @Override
+    public void apply(final Traversal.Admin<?, ?> traversal) {
+        for (final Step<?, ?> step : traversal.getSteps()) {
+            if (step instanceof AcceptsChildPredicateTraversal && step 
instanceof TraversalParent) {
+                for (final Traversal.Admin<?, ?> child : ((TraversalParent) 
step).getLocalChildren()) {
+                    try {
+                        ChildTraversalValidator.validate(child);
+                    } catch (final IllegalArgumentException e) {
+                        throw new VerificationException(e.getMessage(), 
traversal);
+                    }
+                }
+            }
+        }
+    }
+
+    public static ChildTraversalVerificationStrategy instance() {
+        return INSTANCE;
+    }
+}
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java
new file mode 100644
index 0000000000..5e54d6d599
--- /dev/null
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating;
+import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
+
+/**
+ * Validates that child traversals do not contain mutating steps. Child 
traversals used as
+ * arguments to filter predicates ({@code has()}, {@code is()}, etc.), lookup 
steps
+ * ({@code V(traversal)}, {@code E(traversal)}), and mutation steps ({@code 
property(traversal)})
+ * must be read-only - their purpose is to compute values, not produce side 
effects.
+ * <p>
+ * Recursion walks both {@link TraversalParent#getLocalChildren()} and
+ * {@link TraversalParent#getGlobalChildren()} to detect mutations nested 
inside
+ * {@code map()}, {@code union()}, {@code choose()}, {@code coalesce()}, or 
any other parent step.
+ */
+public final class ChildTraversalValidator {
+
+    private ChildTraversalValidator() {
+    }
+
+    /**
+     * Validates that a child traversal contains no {@link Mutating} steps at 
any nesting depth.
+     * Throws {@link IllegalArgumentException} if one is found.
+     */
+    public static void validate(final Traversal.Admin<?, ?> child) {
+        for (final Step<?, ?> step : child.getSteps()) {
+            if (step instanceof Mutating) {
+                throw new IllegalArgumentException(
+                        "Child traversal contains mutating step " +
+                        step.getClass().getSimpleName() + ". Mutating steps 
are not allowed in child traversals.");
+            }
+            if (step instanceof TraversalParent) {
+                for (final Traversal.Admin<?, ?> nested : ((TraversalParent) 
step).getLocalChildren()) {
+                    validate(nested);
+                }
+                for (final Traversal.Admin<?, ?> nested : ((TraversalParent) 
step).getGlobalChildren()) {
+                    validate(nested);
+                }
+            }
+        }
+    }
+}
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/CloneIndependenceTraversalTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/CloneIndependenceTraversalTest.java
new file mode 100644
index 0000000000..09af6da179
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/CloneIndependenceTraversalTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.step;
+
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
+import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsSame.sameInstance;
+
+/**
+ * Property 8: Clone independence for traversal-bearing steps.
+ * <p>
+ * For any step containing child traversal arguments, cloning the step SHALL 
produce a deep copy
+ * where modifying the clone's child traversal state does NOT affect the 
original step's child
+ * traversal state.
+ * <p>
+ * <b>Validates: Requirements 6.2</b>
+ */
+public class CloneIndependenceTraversalTest {
+
+    @Test
+    public void shouldProduceIndependentCloneForHasStepWithTraversalValue() {
+        // Create a HasStep with a traversal-bearing HasContainer 
(P.eq(traversal))
+        final Traversal.Admin<?, ?> childTraversal = 
__.constant("marko").asAdmin();
+        final HasContainer hc = new HasContainer("name", P.eq(childTraversal));
+        final HasStep<Vertex> original = new 
HasStep<>(EmptyTraversal.instance(), hc);
+
+        // Clone the step
+        final HasStep<Vertex> clone = original.clone();
+
+        // Verify the clone has child traversals
+        final List<Traversal.Admin<?, ?>> originalChildren = (List) 
original.getLocalChildren();
+        final List<Traversal.Admin<?, ?>> cloneChildren = (List) 
clone.getLocalChildren();
+
+        assertThat(originalChildren.size(), is(1));
+        assertThat(cloneChildren.size(), is(1));
+
+        // The child traversals should be different instances (deep copy)
+        assertThat(cloneChildren.get(0), 
is(not(sameInstance(originalChildren.get(0)))));
+
+        // Verify HasContainers are also independent
+        final List<HasContainer> originalContainers = 
original.getHasContainers();
+        final List<HasContainer> cloneContainers = clone.getHasContainers();
+        assertThat(cloneContainers.get(0), 
is(not(sameInstance(originalContainers.get(0)))));
+        assertThat(cloneContainers.get(0).getPredicate().getTraversalValue(),
+                
is(not(sameInstance(originalContainers.get(0).getPredicate().getTraversalValue()))));
+    }
+
+    @Test
+    public void shouldProduceIndependentCloneForGraphStepWithIdTraversal() {
+        // Create a GraphStep with an idTraversal
+        final Traversal.Admin<?, ?> idTraversal = __.select("ids").asAdmin();
+        final GraphStep<Vertex, Vertex> original =
+                new GraphStep<>(EmptyTraversal.instance(), Vertex.class, 
false, idTraversal);
+
+        // Clone the step
+        final GraphStep<Vertex, Vertex> clone = original.clone();
+
+        // Verify the clone has a child traversal
+        assertThat(original.getIdTraversal(), is(notNullValue()));
+        assertThat(clone.getIdTraversal(), is(notNullValue()));
+
+        // The idTraversal should be a different instance (deep copy)
+        assertThat(clone.getIdTraversal(), 
is(not(sameInstance(original.getIdTraversal()))));
+    }
+
+    @Test
+    public void 
shouldProduceIndependentCloneForHasStepWithMultipleContainers() {
+        // Create a HasStep with multiple HasContainers, some with traversals
+        final HasContainer hc1 = new HasContainer("name", 
P.eq(__.constant("marko").asAdmin()));
+        final HasContainer hc2 = new HasContainer("age", 
P.eq(__.constant(29).asAdmin()));
+        final HasStep<Vertex> original = new 
HasStep<>(EmptyTraversal.instance(), hc1, hc2);
+
+        // Clone the step
+        final HasStep<Vertex> clone = original.clone();
+
+        // Verify all containers are independent
+        final List<HasContainer> originalContainers = 
original.getHasContainers();
+        final List<HasContainer> cloneContainers = clone.getHasContainers();
+
+        assertThat(originalContainers.size(), is(2));
+        assertThat(cloneContainers.size(), is(2));
+
+        for (int i = 0; i < originalContainers.size(); i++) {
+            assertThat(cloneContainers.get(i), 
is(not(sameInstance(originalContainers.get(i)))));
+            
assertThat(cloneContainers.get(i).getPredicate().getTraversalValue(),
+                    
is(not(sameInstance(originalContainers.get(i).getPredicate().getTraversalValue()))));
+        }
+    }
+
+}
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainerTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainerTest.java
new file mode 100644
index 0000000000..4049b20eb6
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/HasContainerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.step.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link HasContainer}, including traversal-bearing predicates and 
lifecycle methods.
+ * A traversal-bearing container holds a {@link P} whose value is resolved at 
runtime from a child traversal
+ * (e.g. {@code P.eq(traversal)}); the container exposes this via {@link 
HasContainer#hasTraversal()}.
+ */
+public class HasContainerTest {
+
+    @Test
+    public void shouldReturnFalseForHasTraversalWithLiteralPredicate() {
+        final HasContainer hc = new HasContainer("name", P.eq("marko"));
+        assertThat(hc.hasTraversal(), is(false));
+    }
+
+    @Test
+    public void shouldReturnTrueForHasTraversalWithTraversalPredicate() {
+        final Traversal.Admin<?, ?> traversal = __.identity().asAdmin();
+        final HasContainer hc = new HasContainer("name", P.eq(traversal));
+        assertThat(hc.hasTraversal(), is(true));
+    }
+
+    @Test
+    public void shouldSetFieldsCorrectlyWithTraversalPredicate() {
+        final Traversal.Admin<?, ?> traversal = __.identity().asAdmin();
+        final HasContainer hc = new HasContainer("age", P.eq(traversal));
+
+        assertEquals("age", hc.getKey());
+        assertThat(hc.getPredicate(), is(notNullValue()));
+        assertThat(hc.getPredicate().getTraversalValue(), 
is(sameInstance(traversal)));
+    }
+
+    @Test
+    public void shouldCloneProduceIndependentDeepCopyOfTraversalPredicate() {
+        final Traversal.Admin<?, ?> traversal = __.identity().asAdmin();
+        final HasContainer original = new HasContainer("name", 
P.eq(traversal));
+        final HasContainer clone = original.clone();
+
+        // clone's predicate should carry a traversal that is not the same 
instance
+        assertThat(clone.getPredicate().getTraversalValue(), 
is(notNullValue()));
+        assertThat(clone.getPredicate().getTraversalValue(),
+                
is(not(sameInstance(original.getPredicate().getTraversalValue()))));
+
+        // key should be equal
+        assertEquals(original.getKey(), clone.getKey());
+
+        // both should still report hasTraversal
+        assertThat(clone.hasTraversal(), is(true));
+    }
+
+    @Test
+    public void shouldCloneLiteralHasContainerWithoutTraversal() {
+        final HasContainer original = new HasContainer("name", P.eq("marko"));
+        final HasContainer clone = original.clone();
+
+        assertThat(clone.hasTraversal(), is(false));
+        assertEquals("name", clone.getKey());
+        assertEquals(P.eq("marko"), clone.getPredicate());
+    }
+}
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java
new file mode 100644
index 0000000000..feb0688283
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.strategy.verification;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests for the ChildTraversalVerificationStrategy (strategy-time layer).
+ */
+public class ChildTraversalVerificationStrategyTest {
+
+    private final GraphTraversalSource g = EmptyGraph.instance().traversal();
+
+    @Test
+    public void shouldBeRegisteredByDefault() {
+        // The strategy should be present in the default Graph strategy set 
(not EmptyGraph which has none)
+        assertTrue(TraversalStrategies.GlobalCache
+                .getStrategies(Graph.class)
+                
.getStrategy(ChildTraversalVerificationStrategy.class).isPresent());
+    }
+
+    @Test
+    public void shouldBeRemovableViaWithoutStrategies() {
+        // Users should be able to remove the strategy if needed
+        final GraphTraversalSource gNoVerify = 
g.withoutStrategies(ChildTraversalVerificationStrategy.class);
+        
assertFalse(gNoVerify.getStrategies().getStrategy(ChildTraversalVerificationStrategy.class).isPresent());
+    }
+
+    @Test
+    public void shouldNotRejectValidReadOnlyTraversal() {
+        // Valid traversal should pass strategy application without error
+        final Traversal.Admin<?, ?> traversal = g.V().has("name", 
__.V().values("name")).asAdmin();
+        traversal.applyStrategies();
+        // If we get here without exception, the test passes
+    }
+
+    @Test
+    public void shouldNotRejectValidLookupTraversal() {
+        // Valid V(traversal) should pass
+        final Traversal.Admin<?, ?> traversal = 
g.V().V(__.out("knows").id()).asAdmin();
+        traversal.applyStrategies();
+    }
+}
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java
new file mode 100644
index 0000000000..68b99296ec
--- /dev/null
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.tinkerpop.gremlin.process.traversal.util;
+
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.TextP;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThrows;
+
+/**
+ * Tests for construction-time child traversal validation.
+ */
+public class ChildTraversalValidatorTest {
+
+    private final GraphTraversalSource g = EmptyGraph.instance().traversal();
+
+    // ===== FILTER CONTEXT: should reject mutating steps =====
+
+    @Test
+    public void shouldRejectAddVInHasTraversal() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().has("name", __.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectDropInHasTraversal() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().has("name", __.V().drop().constant("x")));
+    }
+
+    @Test
+    public void shouldRejectNestedMutatingInHasTraversal() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().has("name", __.V().map(__.addV("x")).values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInHasWithTAccessor() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().has(org.apache.tinkerpop.gremlin.structure.T.label, 
__.addV("x").label()));
+    }
+
+    @Test
+    public void shouldRejectMutatingInHasWithLabel() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().has("person", "name", __.addV("x").values("name")));
+    }
+
+    // ===== LOOKUP CONTEXT: should reject mutating steps =====
+
+    @Test
+    public void shouldRejectAddVInMidTraversalV() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().V(__.addV("x").id()));
+    }
+
+    @Test
+    public void shouldRejectAddVInMidTraversalE() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().E(__.addV("x").id()));
+    }
+
+    @Test
+    public void shouldRejectAddVInStartStepV() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V(__.addV("x").id()));
+    }
+
+    @Test
+    public void shouldRejectAddVInStartStepE() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.E(__.addV("x").id()));
+    }
+
+    // ===== P FACTORY METHODS: should reject mutating steps =====
+
+    @Test
+    public void shouldRejectMutatingInPEq() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.eq(__.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPNeq() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.neq(__.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPGt() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.gt(__.addV("x").values("age")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPLt() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.lt(__.addV("x").values("age")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPGte() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.gte(__.addV("x").values("age")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPLte() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.lte(__.addV("x").values("age")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPWithin() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.within(__.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInPWithout() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.without(__.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInMultiTraversalWithin() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.within(__.V().values("name"), __.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInMultiTraversalWithout() {
+        assertThrows(IllegalArgumentException.class, () ->
+                P.without(__.V().values("name"), __.addV("x").values("name")));
+    }
+
+    // ===== TextP FACTORY METHODS: should reject mutating steps =====
+
+    @Test
+    public void shouldRejectMutatingInTextPStartingWith() {
+        assertThrows(IllegalArgumentException.class, () ->
+                TextP.startingWith(__.addV("x").values("name")));
+    }
+
+    @Test
+    public void shouldRejectMutatingInTextPContaining() {
+        assertThrows(IllegalArgumentException.class, () ->
+                TextP.containing(__.addV("x").values("name")));
+    }
+
+    // ===== MUTATION CONTEXT: should reject DropStep but allow other 
mutations =====
+
+    @Test
+    public void shouldRejectDropInPropertyTraversal() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().property(__.V().map(__.drop()).project("x").by("name")));
+    }
+
+    @Test
+    public void shouldRejectNestedDropInPropertyTraversal() {
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().property(__.V().union(__.drop(), 
__.constant("x")).project("k").by()));
+    }
+
+    // ===== VALID TRAVERSALS: should pass without error =====
+
+    @Test
+    public void shouldAllowReadOnlyHasTraversal() {
+        // Should not throw
+        g.V().has("name", __.V().values("name"));
+    }
+
+    @Test
+    public void shouldAllowNavigationInHasTraversal() {
+        // Should not throw
+        g.V().has("name", __.V().out("knows").values("name"));
+    }
+
+    @Test
+    public void shouldAllowReadOnlyLookupTraversal() {
+        // Should not throw
+        g.V().V(__.out("knows").id());
+    }
+
+    @Test
+    public void shouldAllowReadOnlyStartStepV() {
+        // Should not throw
+        g.V(__.V().id());
+    }
+
+    @Test
+    public void shouldAllowReadOnlyPredicate() {
+        // Should not throw
+        P.eq(__.V().values("age"));
+    }
+
+    @Test
+    public void shouldRejectAddVInMutationContext() {
+        // addV is now blocked in property(traversal) - all mutating steps are 
blocked in all contexts
+        assertThrows(IllegalArgumentException.class, () ->
+                g.V().property(__.V().addV("temp").project("k").by("name")));
+    }
+
+    @Test
+    public void shouldAllowReadOnlyPropertyTraversal() {
+        // Should not throw
+        g.V().property(__.V().project("name").by("name"));
+    }
+}
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
index a950daeefe..fc7c64b060 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java
@@ -59,6 +59,14 @@ public final class TinkerGraphStep<S, E extends Element> 
extends GraphStep<S, E>
         super(originalGraphStep.getTraversal(), 
originalGraphStep.getReturnClass(), originalGraphStep.isStartStep(), 
originalGraphStep.getIds());
         originalGraphStep.getLabels().forEach(this::addLabel);
 
+        // preserve the idTraversal if the original GraphStep had one
+        if (originalGraphStep instanceof GraphStep) {
+            final GraphStep<S, E> gs = (GraphStep<S, E>) originalGraphStep;
+            if (gs.getIdTraversal() != null) {
+                this.setIdTraversal(gs.getIdTraversal());
+            }
+        }
+
         // we used to only setIteratorSupplier() if there were no ids OR the 
first id was instanceof Element,
         // but that allowed the filter in g.V(v).has('k','v') to be ignored.  
this created problems for
         // PartitionStrategy which wants to prevent someone from passing "v" 
from one TraversalSource to
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
index c9e19593a7..947cdcefa3 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
@@ -54,8 +54,18 @@ public final class TinkerGraphStepStrategy extends 
AbstractTraversalStrategy<Tra
             Step<?, ?> currentStep = tinkerGraphStep.getNextStep();
             while (currentStep instanceof HasStep || currentStep instanceof 
NoOpBarrierStep) {
                 if (currentStep instanceof HasStep) {
-                    List<HasContainer> hasContainers = ((HasContainerHolder) 
currentStep).getHasContainers();
-                    for (HasContainer hasContainer : hasContainers) {
+                    final List<HasContainer> hasContainers = 
((HasContainerHolder) currentStep).getHasContainers();
+
+                    // skip folding this HasStep if any HasContainer holds a 
child traversal -
+                    // its value is dynamic (resolved per-traverser) and 
cannot be pushed into
+                    // TinkerGraphStep. Continue to the next step so that 
subsequent literal
+                    // HasSteps can still be folded.
+                    if 
(hasContainers.stream().anyMatch(HasContainer::hasTraversal)) {
+                        currentStep = currentStep.getNextStep();
+                        continue;
+                    }
+
+                    for (final HasContainer hasContainer : hasContainers) {
                         if (!GraphStep.processHasContainerIds(tinkerGraphStep, 
hasContainer))
                             tinkerGraphStep.addHasContainer(hasContainer);
                     }
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
new file mode 100644
index 0000000000..a91e89ea52
--- /dev/null
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer;
+import 
org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrategies;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import 
org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.sideEffect.TinkerGraphStep;
+import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.hasSize;
+
+/**
+ * Property 9: Traversal-bearing HasContainers are not folded into GraphStep.
+ * <p>
+ * For any traversal of the form {@code g.V().has(key, traversal)} or {@code 
g.V().has(key, P.eq(traversal))},
+ * after applying {@link TinkerGraphStepStrategy}, the {@link HasStep} SHALL 
remain as a separate step in the
+ * traversal plan and SHALL NOT be folded into the {@link TinkerGraphStep}.
+ * <p>
+ * <b>Validates: Requirements 8.1, 8.2, 8.3</b>
+ */
+public class TinkerGraphStepStrategyTraversalTest {
+
+    private Graph graph;
+    private GraphTraversalSource g;
+
+    @Before
+    public void setup() {
+        graph = TinkerGraph.open();
+        g = graph.traversal();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        graph.close();
+    }
+
+    /**
+     * Applies TinkerGraphStepStrategy to the given traversal and returns the 
step list.
+     */
+    private List<Step> applyStrategy(final Traversal.Admin<?, ?> traversal) {
+        final TraversalStrategies strategies = new 
DefaultTraversalStrategies();
+        strategies.addStrategies(TinkerGraphStepStrategy.instance());
+        traversal.setStrategies(strategies);
+        traversal.applyStrategies();
+        return traversal.getSteps();
+    }
+
+    @Test
+    public void shouldNotFoldTraversalBearingHasContainerIntoGraphStep() {
+        // g.V().has("name", __.constant("marko")) - traversal-bearing 
HasContainer
+        final GraphTraversal<Vertex, Vertex> traversal = g.V().has("name", 
__.constant("marko"));
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // The traversal plan should contain at least 2 steps:
+        // TinkerGraphStep (replacing GraphStep) and HasStep (not folded)
+        assertThat(steps, hasSize(greaterThanOrEqualTo(2)));
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+
+        // The HasStep should remain as a separate step
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("HasStep with traversal-bearing HasContainer should NOT be 
folded into TinkerGraphStep",
+                hasStepPresent, is(true));
+
+        // The TinkerGraphStep should have no folded HasContainers
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+        assertThat("TinkerGraphStep should have no folded HasContainers for 
traversal-bearing has()",
+                tinkerGraphStep.getHasContainers(), hasSize(0));
+    }
+
+    @Test
+    public void shouldStillFoldLiteralHasContainerIntoGraphStep() {
+        // g.V().has("name", "marko") - literal HasContainer should still be 
folded
+        final GraphTraversal<Vertex, Vertex> traversal = g.V().has("name", 
"marko");
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // After folding, the HasStep should be absorbed into TinkerGraphStep
+        assertThat(steps, hasSize(1));
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+
+        // The TinkerGraphStep should have the folded HasContainer
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+        assertThat(tinkerGraphStep.getHasContainers(), hasSize(1));
+    }
+
+    @Test
+    public void shouldNotFoldPredicateWithTraversalIntoGraphStep() {
+        // g.V().has("name", P.eq(__.constant("marko"))) - predicate with 
traversal
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("name", 
org.apache.tinkerpop.gremlin.process.traversal.P.eq(__.constant("marko").asAdmin()));
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // The HasStep should remain as a separate step
+        assertThat(steps, hasSize(greaterThanOrEqualTo(2)));
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("HasStep with P.eq(traversal) should NOT be folded into 
TinkerGraphStep",
+                hasStepPresent, is(true));
+    }
+
+    @Test
+    public void shouldFoldLiteralHasButNotTraversalHasInMixedTraversal() {
+        // g.V().has("name", "marko").has("age", __.constant(29))
+        // Both containers end up in the same HasStep 
(TraversalHelper.addHasContainer merges).
+        // The strategy skips the entire HasStep because it contains a 
traversal-bearing container.
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("name", "marko").has("age", __.constant(29));
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // TinkerGraphStep should have NO folded HasContainers (the merged 
HasStep is skipped)
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+
+        // The HasStep with both containers should remain as a separate step
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("HasStep with mixed containers should remain as separate 
step",
+                hasStepPresent, is(true));
+    }
+
+    @Test
+    public void shouldFoldLiteralHasAfterBarrierAndTraversalHas() {
+        // g.V().has("age", __.constant(29)).barrier().has("name", "marko")
+        // The barrier separates the two HasSteps. The strategy should:
+        // - Skip the traversal-bearing HasStep (has("age", traversal))
+        // - Stop at the barrier (not a HasStep or NoOpBarrierStep... actually 
NoOpBarrierStep IS handled)
+        // Let's use a different separator. Actually NoOpBarrierStep is 
handled by the while loop.
+        // The key test is: after skipping a traversal-bearing HasStep, the 
strategy continues
+        // and can fold subsequent literal HasSteps.
+        //
+        // With separate HasSteps (not merged), the strategy should fold the 
literal one.
+        // We can force separate HasSteps by inserting a NoOpBarrierStep 
between them.
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("age", __.constant(29)).barrier().has("name", 
"marko");
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // TinkerGraphStep should have the literal "name" HasContainer folded
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+        assertThat("Literal HasContainer after barrier should be folded even 
when preceded by traversal-bearing HasStep",
+                tinkerGraphStep.getHasContainers(), hasSize(1));
+        assertThat(tinkerGraphStep.getHasContainers().get(0).getKey(), 
is("name"));
+
+        // The traversal-bearing HasStep for "age" should remain
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("Traversal-bearing HasStep should remain as separate step",
+                hasStepPresent, is(true));
+    }
+
+    @Test
+    public void shouldFoldMultipleLiteralHasStepsSeparatedByTraversalHas() {
+        // g.V().has("name", "marko").has("age", 
__.constant(29)).barrier().has("lang", "java")
+        // has("name") is a separate literal HasStep, has("age") is a separate 
traversal HasStep,
+        // has("lang") is a separate literal HasStep after the barrier.
+        // Strategy should fold both literal HasSteps and skip the traversal 
one.
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("name", "marko").has("age", 
__.constant(29)).barrier().has("lang", "java");
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // TinkerGraphStep should have both literal HasContainers folded 
("name" and "lang")
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+        assertThat("Both literal HasContainers should be folded",
+                tinkerGraphStep.getHasContainers(), hasSize(2));
+
+        // The traversal-bearing HasStep (age) should remain
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("Traversal-bearing HasStep should remain as separate step",
+                hasStepPresent, is(true));
+    }
+
+}

Reply via email to