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

spmallette pushed a commit to branch jsprocess
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit c7392acd0ff0c87de250bb40f97895f71c2cac81
Author: Stephen Mallette <[email protected]>
AuthorDate: Fri Jun 26 15:38:43 2026 -0400

    Add by() modulation to path() in gremlin-javascript Tiny Gremlin
    
    path() now projects each path element via by(). Supported forms: by(String),
    by(T), and by(Traversal) restricted to a single value-extraction step (id,
    label, key, value, values, valueMap, elementMap; values takes the first 
item).
    Multiple by() modulators apply round-robin across path elements. A 
non-productive
    by() filters the path traverser, matching default Gremlin semantics
    (ProductiveByStrategy is not supported).
    
    - LocalExecutor: fold consecutive by() into path(); applyPath runs the 
single
      value-extraction sub-step per element; reject by(Traversal) on order()
    - steps.ts: round-robin projection in stepPath with NON_PRODUCTIVE filtering
    - ExecuteVisitor: parse the by(Traversal) sub-pipeline (was a discarded 
placeholder)
    - local-graph-connection: drop the erroneous override on 
commit()/rollback(), which
      are not part of the RemoteConnection abstract contract, so the test suite 
compiles
    
    Tests:
    - Path.feature: tag three existing path().by() scenarios @TinyGremlin and 
add
      by(T.label) and by(__.values("name")) scenarios
    - new unit test for by(T), by(Traversal) and the single-step restriction 
error
    - new integration test exercising the primary use case: subgraph() from a 
remote
      graph then local Tiny Gremlin traversal, plus JavaScript closure 
post-processing
      (map/filter/flat/reduce/forEach) for steps outside the subset
    
    Regenerates the cross-language feature test code from the corpus, which 
also catches
    up generated code left stale by the prior Tiny Gremlin commit (AddVertex 
scenarios).
    
    Assisted-by: Claude Code:claude-opus-4-8
---
 docs/src/dev/provider/tiny-gremlin.asciidoc        |  16 +-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   4 +
 gremlin-go/driver/cucumber/gremlin.go              |   4 +
 .../lib/driver/local-graph-connection.ts           |   8 +-
 .../lib/language/executor/ExecuteVisitor.ts        |  12 +-
 .../lib/process/local/LocalExecutor.ts             |  58 +++++-
 .../gremlin-javascript/lib/process/local/steps.ts  |  44 ++++-
 .../gremlin-javascript/test/cucumber/gremlin.js    |   6 +-
 .../integration/tiny-gremlin-subgraph-tests.js     | 218 +++++++++++++++++++++
 .../test/unit/tiny-gremlin-path-by-test.js         |  75 +++++++
 .../src/main/python/tests/feature/gremlin.py       |   4 +
 .../gremlin/language/translator/translations.json  | 128 ++++++++++++
 .../gremlin/test/features/map/Path.feature         |  36 +++-
 13 files changed, 598 insertions(+), 15 deletions(-)

diff --git a/docs/src/dev/provider/tiny-gremlin.asciidoc 
b/docs/src/dev/provider/tiny-gremlin.asciidoc
index 2dd151598a..bffd26056e 100644
--- a/docs/src/dev/provider/tiny-gremlin.asciidoc
+++ b/docs/src/dev/provider/tiny-gremlin.asciidoc
@@ -304,9 +304,23 @@ Maps an edge to its outgoing vertex (the tail of the 
arrow).
 
 Maps the current traverser to the `Path` of elements it has visited.
 
+`by()` modulation is supported to project each element in the path. Multiple 
`by()` modulators are applied
+round-robin across the path elements (the first element uses the first `by()`, 
the second the second, and so on,
+cycling back to the first). The following forms are supported:
+
+* `by(String)` — project the named property value (the first value for 
multi-properties).
+* `by(T)` — project `T.id` or `T.label`.
+* `by()` — natural identity (the raw element), equivalent to omitting `by()` 
for that position.
+* `by(Traversal)` — restricted to a *single value-extraction step*: `id()`, 
`label()`, `key()`, `value()`,
+  `values()`, `valueMap()`, or `elementMap()`. `values()` projects the first 
value. Any other traversal shape
+  raises an error.
+
+A non-productive `by()` (for example, `by("age")` on an element without an 
`age` property) filters the whole
+path traverser, matching default Gremlin semantics. `ProductiveByStrategy` is 
not supported, so there is no
+null-producing variant.
+
 *Tiny Gremlin limitations:*
 
-* `by()` modulation is not supported. Path objects always contain the raw 
elements visited.
 * `from()` and `to()` selection within a path is not supported because `as()` 
step labels are excluded from Tiny
   Gremlin.
 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs 
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 0d610f47d9..8424c6aba5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -1033,6 +1033,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_addV_propertyXset_emptyX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "foo").Property(Cardinality.Set, new 
Dictionary<object, object> {}), (g,p) 
=>g.V().HasLabel("person").Values<object>()}}, 
                {"g_addVXpersonX_propertyXname_joshX_propertyXage_nullX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", "josh").Property("age", 
null), (g,p) =>g.V().Has("person", "age", (object) null)}}, 
                
{"g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", 
"marko").Property("friendWeight", null, "acl", null), (g,p) 
=>g.V().Has("person", "name", "marko").Has("friendWeight", (object) null), 
(g,p) =>g.V().Has("person", "name", 
"marko").Properties<object>("friendWeight").Has("acl", (object) null), (g,p) 
=>g.V().Has("person",  [...]
+               {"g_V_hasXperson_name_aliceX_propertyXage_51X", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", "alice").Property("age", 
50), (g,p) =>g.V().Has("person", "name", "alice").Property("age", 51), (g,p) 
=>g.V().Has("person", "age", 50), (g,p) =>g.V().Has("person", "age", 51)}}, 
+               {"g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", "alice").Property("age", 
30), (g,p) =>g.V().Has("person", "name", "alice"), (g,p) =>g.V().Has("person", 
"age", 30)}}, 
                
{"g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", 
"alice").Property(Cardinality.Single, "age", 50), (g,p) =>g.V().Has("person", 
"name", "alice").Property("age", __.Union<object>(__.Values<object>("age"), 
__.Constant<object>(1)).Sum<object>()), (g,p) =>g.V().Has("person", "age", 50), 
(g,p) =>g.V().Has("person", "age", [...]
                
{"g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX",
 new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 
29).As("marko").AddV((string) "person").Property("name", 
"vadas").Property("age", 27).As("vadas").AddV((string) 
"software").Property("name", "lop").Property("lang", 
"java").As("lop").AddV((string) "pers [...]
                
{"g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.AddV().Property("name", "marko").With("key", 
"value").Values<object>("name", "key")}}, 
@@ -1666,6 +1668,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_VX1X_outEXcreatedX_inV_inE_outV_path", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.V(p["vid1"]).OutE("created").InV().InE().OutV().Path()}}, 
                {"g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX", 
new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) 
=>g.V().As("a").Out().As("b").Out().As("c").Path().From("b").To("c").By("name")}},
 
                {"g_VX1X_out_path_byXageX", new List<Func<GraphTraversalSource, 
IDictionary<string, object>, ITraversal>> {(g,p) 
=>g.V(p["vid1"]).Out().Path().By("age")}}, 
+               {"g_VX1X_out_path_byXlabelX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.V(p["vid1"]).Out().Path().By(T.Label)}}, 
+               {"g_VX1X_out_path_byXvaluesXnameXX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.V(p["vid1"]).Out().Path().By(__.Values<object>("name"))}}, 
                
{"g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.WithStrategies(new 
ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}}, 
                {"g_injectX1_null_nullX_path", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.Inject<object>(1, null, null).Path()}}, 
                {"g_injectX1_null_nullX_path_dedup", new 
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> 
{(g,p) =>g.Inject<object>(1, null, null).Path().Dedup()}}, 
diff --git a/gremlin-go/driver/cucumber/gremlin.go 
b/gremlin-go/driver/cucumber/gremlin.go
index 70822b8eba..253337d7c5 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -1003,6 +1003,8 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     "g_addV_propertyXset_emptyX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.AddV("foo").Property(gremlingo.Cardinality.Set, map[interface{}]interface{}{ 
})}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().HasLabel("person").Values()}}, 
     "g_addVXpersonX_propertyXname_joshX_propertyXage_nullX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"josh").Property("age", nil)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", 
"age", nil)}}, 
     
"g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("friendWeight", nil, "acl", nil)}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("person", "name", 
"marko").Has("friendWeight", nil)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interfa [...]
+    "g_V_hasXperson_name_aliceX_propertyXage_51X": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"alice").Property("age", 50)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", 
"name", "alice").Property("age", 51)}, func(g *gremlingo.GraphTraversalSource, 
p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("perso 
[...]
+    "g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"alice").Property("age", 30)}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", 
"name", "alice")}, func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("person", " 
[...]
     
"g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX": 
{func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"alice").Property(gremlingo.Cardinality.Single, "age", 50)}, func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.V().Has("person", "name", 
"alice").Property("age", gremlingo.T__.Union(gremlingo.T__.Values("age"), 
gremlingo.T_ [...]
     
"g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX":
 {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV("person").Property("name", 
"marko").Property("age", 29).As("marko").AddV("person").Property("name", 
"vadas").Property("age", 27).As("vadas").AddV("software").Property("name", 
"lop").Property("lang", "java").As("lop").AddV("person").Property("name", 
"josh").Prop [...]
     "g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.AddV().Property("name", 
"marko").With("key", "value").Values("name", "key")}}, 
@@ -1636,6 +1638,8 @@ var translationMap = map[string][]func(g 
*gremlingo.GraphTraversalSource, p map[
     "g_VX1X_outEXcreatedX_inV_inE_outV_path": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V(p["vid1"]).OutE("created").InV().InE().OutV().Path()}}, 
     "g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V().As("a").Out().As("b").Out().As("c").Path().From("b").To("c").By("name")}},
 
     "g_VX1X_out_path_byXageX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V(p["vid1"]).Out().Path().By("age")}}, 
+    "g_VX1X_out_path_byXlabelX": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return 
g.V(p["vid1"]).Out().Path().By(gremlingo.T.Label)}}, 
+    "g_VX1X_out_path_byXvaluesXnameXX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.V(p["vid1"]).Out().Path().By(gremlingo.T__.Values("name"))}}, 
     "g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return 
g.WithStrategies(gremlingo.ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}},
 
     "g_injectX1_null_nullX_path": {func(g *gremlingo.GraphTraversalSource, p 
map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1, nil, 
nil).Path()}}, 
     "g_injectX1_null_nullX_path_dedup": {func(g 
*gremlingo.GraphTraversalSource, p map[string]interface{}) 
*gremlingo.GraphTraversal {return g.Inject(1, nil, nil).Path().Dedup()}}, 
diff --git a/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts 
b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
index 0828caf8f9..2601e4eff6 100644
--- a/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
+++ b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
@@ -78,11 +78,13 @@ export class LocalGraphConnection extends RemoteConnection {
     return new RemoteTraversal(generator);
   }
 
-  override async commit(): Promise<void> {
-    // no-op: local graph mutations are in-memory only
+  // commit()/rollback() are not part of the RemoteConnection abstract 
contract, so they
+  // are plain methods (no 'override'); local graph mutations are in-memory 
only.
+  async commit(): Promise<void> {
+    // no-op
   }
 
-  override async rollback(): Promise<void> {
+  async rollback(): Promise<void> {
     // no-op
   }
 
diff --git 
a/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts 
b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
index 99957284f2..37c5808196 100644
--- a/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
+++ b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
@@ -449,8 +449,16 @@ export class ExecuteVisitor {
     this.push('by', [null, dir]);
   }
 
-  visitTraversalMethod_by_Traversal(_ctx: any): void { 
this.push('by(Traversal)', []); }
-  visitTraversalMethod_by_Traversal_Comparator(_ctx: any): void { 
this.push('by(Traversal)', []); }
+  // by(Traversal) — emit the parsed sub-pipeline; the local executor 
restricts it to a
+  // single value-extraction step (e.g. elementMap()) when used as a path() 
projection.
+  visitTraversalMethod_by_Traversal(ctx: any): void {
+    this.push('by', 
[this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+  }
+
+  // by(Traversal, Comparator) — Order is irrelevant for path() projection and 
is dropped.
+  visitTraversalMethod_by_Traversal_Comparator(ctx: any): void {
+    this.push('by', 
[this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+  }
 
   /** by(T, Order) — T.id, T.label etc. with a direction, routed through 
traversalFunction */
   visitTraversalMethod_by_Function(ctx: any): void {
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts 
b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
index ee4c04dc63..059b1ddc17 100644
--- a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
@@ -22,7 +22,7 @@ import { defaultPasses, type OptimizationPass } from 
'./passes.js';
 import { LocalExecutionError } from './LocalExecutionError.js';
 import { Graph, Vertex, Edge } from '../../structure/graph.js';
 import {
-  wrap, getValue, type StreamItem,
+  wrap, getValue, NON_PRODUCTIVE, type StreamItem,
   stepOut, stepIn, stepBoth, stepOutE, stepInE, stepBothE, stepOutV, stepInV, 
stepOtherV,
   stepHas, stepHasId, stepHasLabel, stepHasNot,
   stepId, stepLabel, stepValue, stepKey, stepValues, stepValueMap, 
stepElementMap,
@@ -33,6 +33,11 @@ import {
 
 type StepFn = (source: Iterable<StreamItem>, args: Arg[], graph: Graph, 
trackPaths: boolean) => Generator<StreamItem>;
 
+/** Steps allowed inside a by(Traversal) projection on path() — single value 
extraction only. */
+const VALUE_EXTRACTION_STEPS = new Set<string>([
+  'id', 'label', 'key', 'value', 'values', 'valueMap', 'elementMap',
+]);
+
 /**
  * Executes a Tiny Gremlin Pipeline against a local in-memory Graph, returning
  * an AsyncGenerator that yields one result element at a time.
@@ -76,12 +81,37 @@ export class LocalExecutor {
         // Fold a single following by() modulator into the order step args
         const next = pipeline[i + 1];
         if (next?.name === 'by') {
+          if (Array.isArray(next.args[0])) {
+            throw new LocalExecutionError('by(Traversal) is not supported for 
order() in Tiny Gremlin');
+          }
           result.push({ name: 'order', args: next.args }); // args = [key, 
direction]
           i += 2;
         } else {
           result.push(step);
           i++;
         }
+      } else if (step.name === 'path') {
+        // Fold all following by() modulators into the path step as a list of 
projections.
+        // Each projection is a key (String/T/null) or a single-step 
value-extraction
+        // sub-pipeline (by(Traversal)); they are applied round-robin across 
path elements.
+        const projections: Arg[] = [];
+        let j = i + 1;
+        while (j < pipeline.length && pipeline[j].name === 'by') {
+          const proj = pipeline[j].args[0];
+          if (Array.isArray(proj)) {
+            const sub = proj as unknown as Pipeline;
+            if (sub.length !== 1 || !VALUE_EXTRACTION_STEPS.has(sub[0].name)) {
+              throw new LocalExecutionError(
+                'Tiny Gremlin only supports by(Traversal) on path() with a 
single value-extraction ' +
+                'step (id, label, key, value, values, valueMap, elementMap)',
+              );
+            }
+          }
+          projections.push(proj);
+          j++;
+        }
+        result.push({ name: 'path', args: projections });
+        i = j;
       } else {
         result.push(step);
         i++;
@@ -115,11 +145,35 @@ export class LocalExecutor {
 
   private applyStep(step: StepDescriptor, source: Iterable<StreamItem>, graph: 
Graph, trackPaths: boolean): Iterable<StreamItem> {
     if (step.name === 'addE') return this.applyAddE(source, step, graph, 
trackPaths);
+    if (step.name === 'path') return this.applyPath(source, step, graph, 
trackPaths);
     const fn = STEP_REGISTRY.get(step.name);
     if (!fn) throw new LocalExecutionError(`No implementation for step 
'${step.name}'`);
     return fn(source, step.args, graph, trackPaths);
   }
 
+  /**
+   * Executes path() with its folded by() projections. Provides stepPath a 
callback that
+   * runs a single value-extraction sub-step against one path element, taking 
its first
+   * result (e.g. values('name') yields the first value).
+   */
+  private applyPath(
+    source: Iterable<StreamItem>,
+    step: StepDescriptor,
+    graph: Graph,
+    trackPaths: boolean,
+  ): Iterable<StreamItem> {
+    const runTraversal = (sub: Pipeline, object: any): any => {
+      const sub0 = sub[0];
+      const fn = STEP_REGISTRY.get(sub0.name);
+      if (!fn) throw new LocalExecutionError(`No implementation for step 
'${sub0.name}'`);
+      for (const out of fn([wrap(object, [], false)], sub0.args, graph, 
false)) {
+        return getValue(out, false);
+      }
+      return NON_PRODUCTIVE; // non-productive by(Traversal) filters the path 
traverser
+    };
+    return stepPath(source, step.args, trackPaths, runTraversal);
+  }
+
   private applyAddE(
     source: Iterable<StreamItem>,
     step: StepDescriptor,
@@ -237,7 +291,7 @@ const STEP_REGISTRY = new Map<string, StepFn>([
   ['values',      stepValues],
   ['valueMap',    stepValueMap],
   ['elementMap',  stepElementMap],
-  ['path',        stepPath],
+  // 'path' is dispatched via applyPath (needs the registry for by(Traversal) 
projections)
   ['limit',       stepLimit],
   ['range',       stepRange],
   ['skip',        stepSkip],
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/steps.ts 
b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
index 7ae0700683..ba2b04a5a0 100644
--- a/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
@@ -19,7 +19,7 @@
 
 import { P, TextP, t, direction } from '../traversal.js';
 import { Graph, Vertex, Edge, VertexProperty, Property, Path } from 
'../../structure/graph.js';
-import type { Arg } from './types.js';
+import type { Arg, Pipeline } from './types.js';
 
 // ── Traverser helpers 
─────────────────────────────────────────────────────────
 
@@ -487,13 +487,51 @@ export function* stepElementMap(
 
 // ── Path step 
─────────────────────────────────────────────────────────────────
 
+/**
+ * Sentinel signalling a non-productive by() projection (e.g. by('age') on an 
element
+ * lacking the 'age' property). Without ProductiveByStrategy — which Tiny 
Gremlin does not
+ * support — a non-productive by() filters the whole path traverser.
+ */
+export const NON_PRODUCTIVE = Symbol('nonProductive');
+
+/** by(String)/by(T)/natural projection. Returns NON_PRODUCTIVE for an absent 
property. */
+function projectByKey(el: any, key: string | null | undefined): any {
+  if (key == null) return el;                              // natural / by()
+  if (key === 'T.id' || key === 'id') return el?.id;
+  if (key === 'T.label' || key === 'label') return el?.label;
+  if (el instanceof Vertex || el instanceof Edge) {
+    const vals = getValues(el, key);
+    return vals.length === 0 ? NON_PRODUCTIVE : vals[0];   // first value, 
multi-valued
+  }
+  return el;
+}
+
+// projections carry one entry per by() modulator, applied round-robin across 
path
+// elements. An entry is either a key (String/T/null for 
by(String)/by(T)/natural) or a
+// single-step sub-Pipeline (by(Traversal)), run via runTraversal. Empty when 
no by()
+// modulators are present, in which case the raw path objects are used.
 export function* stepPath(
-  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+  source: Iterable<StreamItem>,
+  projections: Arg[],
+  trackPaths: boolean,
+  runTraversal: (sub: Pipeline, object: any) => any,
 ): Generator<StreamItem> {
   for (const item of source) {
     const pathEntries: PathEntry[] = getPath(item, trackPaths);
     const labels: string[][] = pathEntries.map(e => e.labels);
-    const objects: any[] = pathEntries.map(e => e.object);
+    const objects: any[] = [];
+    let productive = true;
+    for (let idx = 0; idx < pathEntries.length; idx++) {
+      const object = pathEntries[idx].object;
+      if (projections.length === 0) { objects.push(object); continue; }
+      const proj = projections[idx % projections.length];
+      const value = Array.isArray(proj)
+        ? runTraversal(proj as unknown as Pipeline, object)
+        : projectByKey(object, proj as string | null | undefined);
+      if (value === NON_PRODUCTIVE) { productive = false; break; }
+      objects.push(value);
+    }
+    if (!productive) continue;   // default (non-ProductiveBy) semantics: 
filter the path
     const pathObj = new Path(labels, objects);
     // Yield as a traverser so toAsync's trackPaths unwrap produces the Path 
value correctly.
     yield trackPaths ? { value: pathObj, path: [] } : pathObj;
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js 
b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
index 73ef8f48f3..4b1e157bbb 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
@@ -1034,7 +1034,9 @@ const gremlins = {
     g_addV_propertyXset_emptyX: [function({g}) { return 
g.addV("foo").property(Cardinality.set, new Map([])) }, function({g}) { return 
g.V().hasLabel("person").values() }], 
     g_addVXpersonX_propertyXname_joshX_propertyXage_nullX: [function({g}) { 
return g.addV("person").property("name", "josh").property("age", null) }, 
function({g}) { return g.V().has("person", "age", null) }], 
     g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX: 
[function({g}) { return g.addV("person").property("name", 
"marko").property("friendWeight", null, "acl", null) }, function({g}) { return 
g.V().has("person", "name", "marko").has("friendWeight", null) }, function({g}) 
{ return g.V().has("person", "name", 
"marko").properties("friendWeight").has("acl", null) }, function({g}) { return 
g.V().has("person", "name", "marko").properties("friendWeight").count() }], 
-    
g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: 
[function({g}) { return g.addV("person").property("name", 
"alice").property("age", 50) }, function({g}) { return g.V().has("person", 
"name", "alice").property("age", 51) }, function({g}) { return 
g.V().has("person", "age", 50) }, function({g}) { return g.V().has("person", 
"age", 51) }],
+    g_V_hasXperson_name_aliceX_propertyXage_51X: [function({g}) { return 
g.addV("person").property("name", "alice").property("age", 50) }, function({g}) 
{ return g.V().has("person", "name", "alice").property("age", 51) }, 
function({g}) { return g.V().has("person", "age", 50) }, function({g}) { return 
g.V().has("person", "age", 51) }], 
+    g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query: [function({g}) 
{ return g.addV("person").property("name", "alice").property("age", 30) }, 
function({g}) { return g.V().has("person", "name", "alice") }, function({g}) { 
return g.V().has("person", "age", 30) }], 
+    
g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: 
[function({g}) { return g.addV("person").property("name", 
"alice").property(Cardinality.single, "age", 50) }, function({g}) { return 
g.V().has("person", "name", "alice").property("age", __.union(__.values("age"), 
__.constant(1)).sum()) }, function({g}) { return g.V().has("person", "age", 50) 
}, function({g}) { return g.V().has("person", "age", 51) }], 
     
g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX:
 [function({g}) { return g.addV("person").property("name", 
"marko").property("age", 29).as("marko").addV("person").property("name", 
"vadas").property("age", 27).as("vadas").addV("software").property("name", 
"lop").property("lang", "java").as("lop").addV("person").property("name", 
"josh").property("age", 32).as("josh").addV("software").property("name", 
"ripple").property [...]
     g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX: 
[function({g}) { return g.addV().property("name", "marko").with_("key", 
"value").values("name", "key") }], 
     
g_addV_propertyXname_marko_since_2010X_withXkey_valueX_propertiesXnameX_valuesXsince_keyX:
 [function({g}) { return g.addV().property("name", "marko", "since", 
2010).with_("key", "value").properties("name").values("since", "key") }], 
@@ -1667,6 +1669,8 @@ const gremlins = {
     g_VX1X_outEXcreatedX_inV_inE_outV_path: [function({g, vid1}) { return 
g.V(vid1).outE("created").inV().inE().outV().path() }], 
     g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX: [function({g}) 
{ return 
g.V().as("a").out().as("b").out().as("c").path().from_("b").to("c").by("name") 
}], 
     g_VX1X_out_path_byXageX: [function({g, vid1}) { return 
g.V(vid1).out().path().by("age") }], 
+    g_VX1X_out_path_byXlabelX: [function({g, vid1}) { return 
g.V(vid1).out().path().by(T.label) }], 
+    g_VX1X_out_path_byXvaluesXnameXX: [function({g, vid1}) { return 
g.V(vid1).out().path().by(__.values("name")) }], 
     g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX: 
[function({g, vid1}) { return g.withStrategies(new 
ProductiveByStrategy()).V(vid1).out().path().by("age") }], 
     g_injectX1_null_nullX_path: [function({g}) { return g.inject(1, null, 
null).path() }], 
     g_injectX1_null_nullX_path_dedup: [function({g}) { return g.inject(1, 
null, null).path().dedup() }], 
diff --git 
a/gremlin-js/gremlin-javascript/test/integration/tiny-gremlin-subgraph-tests.js 
b/gremlin-js/gremlin-javascript/test/integration/tiny-gremlin-subgraph-tests.js
new file mode 100644
index 0000000000..7ff34e194c
--- /dev/null
+++ 
b/gremlin-js/gremlin-javascript/test/integration/tiny-gremlin-subgraph-tests.js
@@ -0,0 +1,218 @@
+/*
+ *  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.
+ */
+
+import assert from 'assert';
+import anon from '../../lib/process/anonymous-traversal.js';
+import { getConnection } from '../helper.js';
+import { Graph, Vertex, Edge } from '../../lib/structure/graph.js';
+import { t } from '../../lib/process/traversal.js';
+
+/**
+ * Exercises a core Tiny Gremlin use case end-to-end: pull a subgraph() from a 
remote
+ * graph, then run local in-process traversals over the detached Graph 
returned to the client.
+ *
+ * The "created" subgraph of the modern graph contains 5 vertices
+ * (marko, josh, peter — person; lop, ripple — software; vadas is excluded) 
and 4 created
+ * edges (marko->lop, josh->ripple, josh->lop, peter->lop).
+ */
+describe('Tiny Gremlin over subgraph()', function () {
+  let connection;
+  let sg;        // detached Graph returned by subgraph()
+  let lg;        // local Tiny Gremlin traversal source over sg
+  let markoId;   // marko's id from the remote graph, for id-preservation 
checks
+
+  const sorted = (arr) => [...arr].sort();
+  const pathObjects = (paths) => paths.map((p) => p.objects);
+
+  before(async function () {
+    connection = getConnection('gmodern');
+    await connection.open();
+    const g = anon.traversal().with_(connection);
+    sg = (await 
g.E().hasLabel('created').subgraph('sg').cap('sg').next()).value;
+    lg = anon.traversal().with_(sg);
+    markoId = (await g.V().has('name', 'marko').id().next()).value;
+  });
+
+  after(function () {
+    return connection.close();
+  });
+
+  it('returns a detached Graph from subgraph()', function () {
+    assert.ok(sg instanceof Graph);
+  });
+
+  it('captures the created-subgraph vertex set', async function () {
+    const vertices = await lg.V().toList();
+    assert.strictEqual(vertices.length, 5);
+    vertices.forEach((v) => assert.ok(v instanceof Vertex));
+    const names = sorted(await lg.V().values('name').toList());
+    assert.deepStrictEqual(names, ['josh', 'lop', 'marko', 'peter', 'ripple']);
+    assert.ok(!names.includes('vadas'), 'vadas has no created edges and must 
be excluded');
+  });
+
+  it('captures the created edges', async function () {
+    const edges = await lg.E().toList();
+    assert.strictEqual(edges.length, 4);
+    edges.forEach((e) => assert.ok(e instanceof Edge));
+    const labels = await lg.E().label().toList();
+    assert.deepStrictEqual(sorted(labels), ['created', 'created', 'created', 
'created']);
+  });
+
+  it('filters by label locally', async function () {
+    const software = sorted(await 
lg.V().hasLabel('software').values('name').toList());
+    assert.deepStrictEqual(software, ['lop', 'ripple']);
+    const people = sorted(await 
lg.V().hasLabel('person').values('name').toList());
+    assert.deepStrictEqual(people, ['josh', 'marko', 'peter']);
+  });
+
+  it('preserves vertex properties through the round-trip', async function () {
+    assert.deepStrictEqual(await lg.V().has('name', 
'marko').values('age').toList(), [29]);
+    assert.deepStrictEqual(await lg.V().has('name', 
'josh').values('age').toList(), [32]);
+    const langs = await lg.V().hasLabel('software').values('lang').toList();
+    assert.deepStrictEqual(sorted(langs), ['java', 'java']);
+  });
+
+  it('navigates out-edges locally', async function () {
+    const joshOut = sorted(await lg.V().has('name', 
'josh').out().values('name').toList());
+    assert.deepStrictEqual(joshOut, ['lop', 'ripple']);
+    const markoOut = await lg.V().has('name', 
'marko').out().values('name').toList();
+    assert.deepStrictEqual(markoOut, ['lop']);
+  });
+
+  it('navigates in-edges locally', async function () {
+    const lopIn = sorted(await lg.V().has('name', 
'lop').in_().values('name').toList());
+    assert.deepStrictEqual(lopIn, ['josh', 'marko', 'peter']);
+    const rippleIn = await lg.V().has('name', 
'ripple').in_().values('name').toList();
+    assert.deepStrictEqual(rippleIn, ['josh']);
+  });
+
+  it('preserves edge properties', async function () {
+    // weight may serialize as float; compare within tolerance rather than 
exact equality
+    const near = (actual, expected) => assert.ok(Math.abs(actual - expected) < 
1e-6, `${actual} ~ ${expected}`);
+    const markoWeight = await lg.V().has('name', 
'marko').outE().values('weight').toList();
+    assert.strictEqual(markoWeight.length, 1);
+    near(markoWeight[0], 0.4);
+    const weights = (await 
lg.E().values('weight').toList()).map(Number).sort((a, b) => a - b);
+    assert.strictEqual(weights.length, 4);
+    [0.2, 0.4, 0.4, 1.0].forEach((expected, i) => near(weights[i], expected));
+  });
+
+  it('orders results locally', async function () {
+    const ordered = await 
lg.V().hasLabel('person').values('name').order().toList();
+    assert.deepStrictEqual(ordered, ['josh', 'marko', 'peter']);
+  });
+
+  it('projects paths with by() over the subgraph', async function () {
+    const byName = pathObjects(await lg.V().has('name', 
'josh').out().path().by('name').toList());
+    assert.strictEqual(byName.length, 2);
+    byName.forEach((p) => assert.strictEqual(p[0], 'josh'));
+    assert.deepStrictEqual(sorted(byName.map((p) => p[1])), ['lop', 'ripple']);
+    const byLabel = pathObjects(await lg.V().has('name', 
'marko').out().path().by(t.label).toList());
+    assert.deepStrictEqual(byLabel, [['person', 'software']]);
+  });
+
+  it('preserves element ids for direct lookup', async function () {
+    const localMarkoId = (await lg.V().has('name', 'marko').id().next()).value;
+    assert.deepStrictEqual(localMarkoId, markoId);
+    const byId = await lg.V(markoId).values('name').toList();
+    assert.deepStrictEqual(byId, ['marko']);
+  });
+
+  it('returns elementMap with tokens and properties', async function () {
+    const m = (await lg.V().has('name', 'marko').elementMap().next()).value;
+    assert.strictEqual(m.get('name'), 'marko');
+    assert.strictEqual(m.get('age'), 29);
+    assert.strictEqual(m.get(t.label), 'person');
+    assert.deepStrictEqual(m.get(t.id), markoId);
+  });
+
+  // Tiny Gremlin is a subset — steps like project(), group(), groupCount(), 
sum() and fold()
+  // are unavailable. The pattern is to do everything the subset DOES support 
inside the
+  // traversal (here, sort with order().by(...)), terminate with toList(), 
then finish in
+  // ordinary JavaScript with the array methods the subset lacks. 
forEach()/reduce() are
+  // native Tiny Gremlin terminals; map()/filter()/flat() run on the 
materialized array.
+  describe('post-processing with JavaScript closures', function () {
+    it('projects with Array.map() in place of project()', async function () {
+      // order() does the sorting; only the projection has no Tiny Gremlin 
equivalent
+      const projected = (await 
lg.V().hasLabel('person').order().by('name').elementMap().toList())
+        .map((m) => ({ name: m.get('name'), age: m.get('age') }));
+      assert.deepStrictEqual(projected, [
+        { name: 'josh', age: 32 },
+        { name: 'marko', age: 29 },
+        { name: 'peter', age: 35 },
+      ]);
+    });
+
+    it('filters with Array.filter() on a JS-only predicate', async function () 
{
+      // name length has no Tiny Gremlin predicate, so the filter must live in 
JS
+      const longNames = (await 
lg.V().hasLabel('person').order().by('name').elementMap().toList())
+        .filter((m) => m.get('name').length > 4)
+        .map((m) => m.get('name'));
+      assert.deepStrictEqual(longNames, ['marko', 'peter']);
+    });
+
+    it('fans out and flattens with Array.flat() in place of flatMap()', async 
function () {
+      const people = await 
lg.V().hasLabel('person').order().by('name').values('name').toList();
+      const created = (
+        await Promise.all(
+          people.map(async (name) => {
+            const items = await lg.V().has('name', 
name).out().order().by('name').values('name').toList();
+            return items.map((item) => `${name}->${item}`);
+          }),
+        )
+      ).flat();
+      assert.deepStrictEqual(created, ['josh->lop', 'josh->ripple', 
'marko->lop', 'peter->lop']);
+    });
+
+    it('aggregates with the reduce() terminal in place of sum()', async 
function () {
+      const totalAge = await 
lg.V().hasLabel('person').values('age').reduce((acc, age) => acc + age, 0);
+      assert.strictEqual(totalAge, 29 + 32 + 35);
+    });
+
+    it('group-counts with Array.reduce() in place of groupCount()', async 
function () {
+      const labels = await lg.V().label().toList();
+      const counts = labels.reduce((acc, label) => {
+        acc[label] = (acc[label] || 0) + 1;
+        return acc;
+      }, {});
+      assert.deepStrictEqual(counts, { person: 3, software: 2 });
+    });
+
+    it('groups into buckets with Array.reduce() in place of group()', async 
function () {
+      // order().by('name') means each bucket comes out name-sorted with no JS 
sort
+      const maps = await lg.V().order().by('name').elementMap().toList();
+      const byLabel = maps.reduce((acc, m) => {
+        const label = m.get(t.label);
+        (acc[label] = acc[label] || []).push(m.get('name'));
+        return acc;
+      }, {});
+      assert.deepStrictEqual(byLabel, {
+        person: ['josh', 'marko', 'peter'],
+        software: ['lop', 'ripple'],
+      });
+    });
+
+    it('collects via the forEach() terminal for side-effecting consumption', 
async function () {
+      const collected = [];
+      await lg.V().hasLabel('software').order().by('name').values('name')
+        .forEach((name) => collected.push(name.toUpperCase()));
+      assert.deepStrictEqual(collected, ['LOP', 'RIPPLE']);
+    });
+  });
+});
diff --git 
a/gremlin-js/gremlin-javascript/test/unit/tiny-gremlin-path-by-test.js 
b/gremlin-js/gremlin-javascript/test/unit/tiny-gremlin-path-by-test.js
new file mode 100644
index 0000000000..b8e9b750ed
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/unit/tiny-gremlin-path-by-test.js
@@ -0,0 +1,75 @@
+/*
+ *  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.
+ */
+
+import { assert } from 'chai';
+import anon from '../../lib/process/anonymous-traversal.js';
+import { statics as __ } from '../../lib/process/graph-traversal.js';
+import { t } from '../../lib/process/traversal.js';
+import { buildModernGraph } from '../cucumber/local-fixtures.js';
+
+/**
+ * Unit coverage for path().by() projection forms that the @TinyGremlin Gherkin
+ * scenarios do not exercise: by(T), the limited by(Traversal), and the 
rejection
+ * of unsupported by(Traversal) shapes. by(String) round-robin and 
non-productive
+ * filtering are covered by Path.feature.
+ */
+describe('Tiny Gremlin path().by()', () => {
+  let g;
+  const markoId = 1; // modern graph: makeVertex(1, 'person', ...)
+
+  beforeEach(() => {
+    g = anon.traversal().with_(buildModernGraph().graph);
+  });
+
+  const objectsOf = (paths) => paths.map((p) => p.objects);
+
+  it('by(String) filters non-productive elements (lop has no age)', async () 
=> {
+    const result = await g.V(markoId).out().path().by('age').toList();
+    // marko(29) -> {vadas(27), josh(32), lop(no age -> path filtered)}
+    assert.sameDeepMembers(objectsOf(result), [[29, 27], [29, 32]]);
+  });
+
+  it('by(T) projects element id/label', async () => {
+    const result = await g.V(markoId).out().path().by(t.label).toList();
+    // marko=person, out-neighbours: vadas/josh=person, lop=software
+    assert.sameDeepMembers(objectsOf(result), [
+      ['person', 'person'],
+      ['person', 'person'],
+      ['person', 'software'],
+    ]);
+  });
+
+  it('by(Traversal) with a single value-extraction step (values) takes the 
first value', async () => {
+    const result = await 
g.V(markoId).out().path().by(__.values('name')).toList();
+    assert.sameDeepMembers(objectsOf(result), [
+      ['marko', 'vadas'],
+      ['marko', 'josh'],
+      ['marko', 'lop'],
+    ]);
+  });
+
+  it('rejects by(Traversal) that is not a single value-extraction step', async 
() => {
+    try {
+      await g.V(markoId).out().path().by(__.out().values('name')).toList();
+      assert.fail('expected a LocalExecutionError for a multi-step 
by(Traversal)');
+    } catch (err) {
+      assert.match(err.message, /single value-extraction step/);
+    }
+  });
+});
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py 
b/gremlin-python/src/main/python/tests/feature/gremlin.py
index 6ce019b0c0..52c2651eda 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -1008,6 +1008,8 @@ world.gremlins = {
     'g_addV_propertyXset_emptyX': [(lambda 
g:g.add_v('foo').property(Cardinality.set_, {  })), (lambda 
g:g.V().has_label('person').values())], 
     'g_addVXpersonX_propertyXname_joshX_propertyXage_nullX': [(lambda 
g:g.add_v('person').property('name', 'josh').property('age', None)), (lambda 
g:g.V().has('person', 'age', None))], 
     
'g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX': 
[(lambda g:g.add_v('person').property('name', 'marko').property('friendWeight', 
None, 'acl', None)), (lambda g:g.V().has('person', 'name', 
'marko').has('friendWeight', None)), (lambda g:g.V().has('person', 'name', 
'marko').properties('friendWeight').has('acl', None)), (lambda 
g:g.V().has('person', 'name', 'marko').properties('friendWeight').count())], 
+    'g_V_hasXperson_name_aliceX_propertyXage_51X': [(lambda 
g:g.add_v('person').property('name', 'alice').property('age', 50)), (lambda 
g:g.V().has('person', 'name', 'alice').property('age', 51)), (lambda 
g:g.V().has('person', 'age', 50)), (lambda g:g.V().has('person', 'age', 51))], 
+    'g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query': [(lambda 
g:g.add_v('person').property('name', 'alice').property('age', 30)), (lambda 
g:g.V().has('person', 'name', 'alice')), (lambda g:g.V().has('person', 'age', 
30))], 
     
'g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX': 
[(lambda g:g.add_v('person').property('name', 
'alice').property(Cardinality.single, 'age', 50)), (lambda 
g:g.V().has('person', 'name', 'alice').property('age', 
__.union(__.values('age'), __.constant(1)).sum_())), (lambda 
g:g.V().has('person', 'age', 50)), (lambda g:g.V().has('person', 'age', 51))], 
     
'g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX':
 [(lambda g:g.add_v('person').property('name', 'marko').property('age', 
29).as_('marko').add_v('person').property('name', 'vadas').property('age', 
27).as_('vadas').add_v('software').property('name', 'lop').property('lang', 
'java').as_('lop').add_v('person').property('name', 'josh').property('age', 
32).as_('josh').add_v('software').property('name', 'ripple').property(' [...]
     'g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX': [(lambda 
g:g.add_v().property('name', 'marko').with_('key', 'value').values('name', 
'key'))], 
@@ -1641,6 +1643,8 @@ world.gremlins = {
     'g_VX1X_outEXcreatedX_inV_inE_outV_path': [(lambda g, 
vid1=None:g.V(vid1).out_e('created').in_v().in_e().out_v().path())], 
     'g_V_asXaX_out_asXbX_out_asXcX_path_fromXbX_toXcX_byXnameX': [(lambda 
g:g.V().as_('a').out().as_('b').out().as_('c').path().from_('b').to('c').by('name'))],
 
     'g_VX1X_out_path_byXageX': [(lambda g, 
vid1=None:g.V(vid1).out().path().by('age'))], 
+    'g_VX1X_out_path_byXlabelX': [(lambda g, 
vid1=None:g.V(vid1).out().path().by(T.label))], 
+    'g_VX1X_out_path_byXvaluesXnameXX': [(lambda g, 
vid1=None:g.V(vid1).out().path().by(__.values('name')))], 
     'g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX': [(lambda 
g, 
vid1=None:g.with_strategies(ProductiveByStrategy()).V(vid1).out().path().by('age'))],
 
     'g_injectX1_null_nullX_path': [(lambda g:g.inject(1, None, None).path())], 
     'g_injectX1_null_nullX_path_dedup': [(lambda g:g.inject(1, None, 
None).path().dedup())], 
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
index d7e90db6b3..e701f0f03d 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
@@ -20551,6 +20551,100 @@
             }
         ]
     },
+    {
+        "scenario": "g_V_hasXperson_name_aliceX_propertyXage_51X",
+        "traversals": [
+            {
+                "original": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "language": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "canonical": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "anonymized": "g.addV(string0).property(string1, 
string2).property(string3, number0)",
+                "dotnet": "g.AddV((string) \"person\").Property(\"name\", 
\"alice\").Property(\"age\", 50)",
+                "go": "g.AddV(\"person\").Property(\"name\", 
\"alice\").Property(\"age\", 50)",
+                "groovy": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "java": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "javascript": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 50)",
+                "python": "g.add_v('person').property('name', 
'alice').property('age', 50)"
+            },
+            {
+                "original": 
"g.V().has(\"person\",\"name\",\"alice\").property(\"age\", 51)",
+                "language": "g.V().has(\"person\", \"name\", 
\"alice\").property(\"age\", 51)",
+                "canonical": "g.V().has(\"person\", \"name\", 
\"alice\").property(\"age\", 51)",
+                "anonymized": "g.V().has(string0, string1, 
string2).property(string3, number0)",
+                "dotnet": "g.V().Has(\"person\", \"name\", 
\"alice\").Property(\"age\", 51)",
+                "go": "g.V().Has(\"person\", \"name\", 
\"alice\").Property(\"age\", 51)",
+                "groovy": "g.V().has(\"person\", \"name\", 
\"alice\").property(\"age\", 51)",
+                "java": "g.V().has(\"person\", \"name\", 
\"alice\").property(\"age\", 51)",
+                "javascript": "g.V().has(\"person\", \"name\", 
\"alice\").property(\"age\", 51)",
+                "python": "g.V().has('person', 'name', 
'alice').property('age', 51)"
+            },
+            {
+                "original": "g.V().has(\"person\",\"age\",50)",
+                "language": "g.V().has(\"person\", \"age\", 50)",
+                "canonical": "g.V().has(\"person\", \"age\", 50)",
+                "anonymized": "g.V().has(string0, string1, number0)",
+                "dotnet": "g.V().Has(\"person\", \"age\", 50)",
+                "go": "g.V().Has(\"person\", \"age\", 50)",
+                "groovy": "g.V().has(\"person\", \"age\", 50)",
+                "java": "g.V().has(\"person\", \"age\", 50)",
+                "javascript": "g.V().has(\"person\", \"age\", 50)",
+                "python": "g.V().has('person', 'age', 50)"
+            },
+            {
+                "original": "g.V().has(\"person\",\"age\",51)",
+                "language": "g.V().has(\"person\", \"age\", 51)",
+                "canonical": "g.V().has(\"person\", \"age\", 51)",
+                "anonymized": "g.V().has(string0, string1, number0)",
+                "dotnet": "g.V().Has(\"person\", \"age\", 51)",
+                "go": "g.V().Has(\"person\", \"age\", 51)",
+                "groovy": "g.V().has(\"person\", \"age\", 51)",
+                "java": "g.V().has(\"person\", \"age\", 51)",
+                "javascript": "g.V().has(\"person\", \"age\", 51)",
+                "python": "g.V().has('person', 'age', 51)"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query",
+        "traversals": [
+            {
+                "original": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "language": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "canonical": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "anonymized": "g.addV(string0).property(string1, 
string2).property(string3, number0)",
+                "dotnet": "g.AddV((string) \"person\").Property(\"name\", 
\"alice\").Property(\"age\", 30)",
+                "go": "g.AddV(\"person\").Property(\"name\", 
\"alice\").Property(\"age\", 30)",
+                "groovy": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "java": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "javascript": "g.addV(\"person\").property(\"name\", 
\"alice\").property(\"age\", 30)",
+                "python": "g.add_v('person').property('name', 
'alice').property('age', 30)"
+            },
+            {
+                "original": "g.V().has(\"person\",\"name\",\"alice\")",
+                "language": "g.V().has(\"person\", \"name\", \"alice\")",
+                "canonical": "g.V().has(\"person\", \"name\", \"alice\")",
+                "anonymized": "g.V().has(string0, string1, string2)",
+                "dotnet": "g.V().Has(\"person\", \"name\", \"alice\")",
+                "go": "g.V().Has(\"person\", \"name\", \"alice\")",
+                "groovy": "g.V().has(\"person\", \"name\", \"alice\")",
+                "java": "g.V().has(\"person\", \"name\", \"alice\")",
+                "javascript": "g.V().has(\"person\", \"name\", \"alice\")",
+                "python": "g.V().has('person', 'name', 'alice')"
+            },
+            {
+                "original": "g.V().has(\"person\",\"age\",30)",
+                "language": "g.V().has(\"person\", \"age\", 30)",
+                "canonical": "g.V().has(\"person\", \"age\", 30)",
+                "anonymized": "g.V().has(string0, string1, number0)",
+                "dotnet": "g.V().Has(\"person\", \"age\", 30)",
+                "go": "g.V().Has(\"person\", \"age\", 30)",
+                "groovy": "g.V().has(\"person\", \"age\", 30)",
+                "java": "g.V().has(\"person\", \"age\", 30)",
+                "javascript": "g.V().has(\"person\", \"age\", 30)",
+                "python": "g.V().has('person', 'age', 30)"
+            }
+        ]
+    },
     {
         "scenario": 
"g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX",
         "traversals": [
@@ -35224,6 +35318,40 @@
             }
         ]
     },
+    {
+        "scenario": "g_VX1X_out_path_byXlabelX",
+        "traversals": [
+            {
+                "original": "g.V(vid1).out().path().by(T.label)",
+                "language": "g.V(vid1).out().path().by(T.label)",
+                "canonical": "g.V(vid1).out().path().by(T.label)",
+                "anonymized": "g.V(vid1).out().path().by(T.label)",
+                "dotnet": "g.V(vid1).Out().Path().By(T.Label)",
+                "go": "g.V(vid1).Out().Path().By(gremlingo.T.Label)",
+                "groovy": "g.V(vid1).out().path().by(T.label)",
+                "java": "g.V(vid1).out().path().by(T.label)",
+                "javascript": "g.V(vid1).out().path().by(T.label)",
+                "python": "g.V(vid1).out().path().by(T.label)"
+            }
+        ]
+    },
+    {
+        "scenario": "g_VX1X_out_path_byXvaluesXnameXX",
+        "traversals": [
+            {
+                "original": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "language": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "canonical": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "anonymized": "g.V(vid1).out().path().by(__.values(string0))",
+                "dotnet": 
"g.V(vid1).Out().Path().By(__.Values<object>(\"name\"))",
+                "go": 
"g.V(vid1).Out().Path().By(gremlingo.T__.Values(\"name\"))",
+                "groovy": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "java": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "javascript": "g.V(vid1).out().path().by(__.values(\"name\"))",
+                "python": "g.V(vid1).out().path().by(__.values('name'))"
+            }
+        ]
+    },
     {
         "scenario": 
"g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX",
         "traversals": [
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
index adfdd7ccb1..d7a62ad900 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
@@ -31,7 +31,7 @@ Feature: Step - path()
       | result |
       | p[v[marko],marko] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
   Scenario: g_VX1X_out_path_byXageX_byXnameX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -59,7 +59,7 @@ Feature: Step - path()
       | p[v[marko],josh,java] |
       | p[v[marko],josh,java] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
   Scenario: g_V_out_out_path_byXnameX_byXageX
     Given the modern graph
     And the traversal of
@@ -111,7 +111,7 @@ Feature: Step - path()
       | p[josh,ripple] |
       | p[josh,lop] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
   Scenario: g_VX1X_out_path_byXageX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -125,6 +125,36 @@ Feature: Step - path()
       | p[d[29].i,d[27].i] |
       | p[d[29].i,d[32].i] |
 
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
+  Scenario: g_VX1X_out_path_byXlabelX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V(vid1).out().path().by(T.label)
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | p[person,person] |
+      | p[person,person] |
+      | p[person,software] |
+
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
+  Scenario: g_VX1X_out_path_byXvaluesXnameXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V(vid1).out().path().by(__.values("name"))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | p[marko,vadas] |
+      | p[marko,josh] |
+      | p[marko,lop] |
+
   @GraphComputerVerificationReferenceOnly @WithProductiveByStrategy
   Scenario: g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX
     Given the modern graph

Reply via email to