Found by the round-006 paths re-probe (brute-force trail enumerator as oracle, 140 Cypher texts × 2 engines × 2 repeats @ d7cf23d; scripts in session scratchpad probe-r6-paths/, repro at repro_f1.py).
Shape: undirected + unbounded (to_fixed_point) var-length, plain row projection, pandas. polars declines typed (safe).
4-cycle a→b→c→d→a:
| query |
oracle |
actual |
MATCH (x {id:'a'})-[*]-(y) RETURN y.id |
8 rows |
4 |
... RETURN count(*) |
8 |
8 ✓ |
... RETURN y.id, count(*) |
4 groups × 2 |
✓ |
MATCH (x {id:'a'})-[*1..4]-(y) RETURN y.id |
8 |
✓ |
So the plain row lane is the only wrong lane — the count and grouped twins are trail-exact, and the bounded twin is exact. Breaks the "one MATCH = one cardinality" invariant #1903 established.
Worse than multiplicity — impossible rows. With a second element on a triangle: MATCH (x {id:'a'})-[*]-(y)-[]->(z) RETURN z.id → [a,b,c]; oracle [a,c] (reaching b requires reusing an edge). The count(*) twin is 2 (correct).
Reproduces on every cyclic fixture, seeded and unseeded (TRI 3 vs 6, C4 4 vs 8, C4CH 4 vs 25, C5CH 5 vs 30, SLPAR 2 vs 43, DIASL 5 vs 17; unseeded TRI 3 vs 18, C4 4 vs 32).
Provenance (A/B vs master 25215ce): the row lane returns the same wrong 4 on master — the row values are NOT a #1903 regression. What changed is the neighbourhood: on master the adjacent count(*) twin raised a typed error on C4/C4CH (SLPAR returned the old walk value 11); #1903 moved the count lane onto trail semantics and left the row lane on the source-table path, so the shape now presents a correct number beside a silently wrong row set.
Origin, deliberate and unpinned: graphistry/compute/gfql/cypher/lowering.py:2583-2591 — the to_fixed_point and direction == "undirected" early return False that keeps this shape off binding rows "rather than trade one wrongness for a decline (#1903 residual)". There is no test pin on it. Fix: route it through the trail-filtered binding lane, or decline typed like polars does — a silently wrong row set with impossible values is the worst of the three options.
Related, separately scoped (do not conflate): the same probe re-measured the pre-existing #1787 pandas min_hops>=2 hop-window starvation (root cause row/pipeline.py:3955-3985: the trail expander consumes edge_op.execute output already pruned by hop.py's dedup-by-node eccentricity gate, so when that empties the frame the trail machinery never sees the edges). Master and fix head are byte-identical there, and it is documented + pinned — but two sub-cases sit outside any pin or gate and are silent on pandas: seeded undirected *4..4 (C4 0 vs 2, C4CH 0 vs 6, SLPAR 0 vs 12, while *3..3 is exact) and unseeded undirected *3..3 (TRI 0 vs 6, C4 0 vs 8, C4CH 0 vs 24). Worth pins at minimum.
Found by the round-006 paths re-probe (brute-force trail enumerator as oracle, 140 Cypher texts × 2 engines × 2 repeats @ d7cf23d; scripts in session scratchpad probe-r6-paths/, repro at repro_f1.py).
Shape: undirected + unbounded (
to_fixed_point) var-length, plain row projection, pandas. polars declines typed (safe).4-cycle a→b→c→d→a:
MATCH (x {id:'a'})-[*]-(y) RETURN y.id... RETURN count(*)... RETURN y.id, count(*)MATCH (x {id:'a'})-[*1..4]-(y) RETURN y.idSo the plain row lane is the only wrong lane — the count and grouped twins are trail-exact, and the bounded twin is exact. Breaks the "one MATCH = one cardinality" invariant #1903 established.
Worse than multiplicity — impossible rows. With a second element on a triangle:
MATCH (x {id:'a'})-[*]-(y)-[]->(z) RETURN z.id→[a,b,c]; oracle[a,c](reachingbrequires reusing an edge). Thecount(*)twin is 2 (correct).Reproduces on every cyclic fixture, seeded and unseeded (TRI 3 vs 6, C4 4 vs 8, C4CH 4 vs 25, C5CH 5 vs 30, SLPAR 2 vs 43, DIASL 5 vs 17; unseeded TRI 3 vs 18, C4 4 vs 32).
Provenance (A/B vs master 25215ce): the row lane returns the same wrong 4 on master — the row values are NOT a #1903 regression. What changed is the neighbourhood: on master the adjacent
count(*)twin raised a typed error on C4/C4CH (SLPAR returned the old walk value 11); #1903 moved the count lane onto trail semantics and left the row lane on the source-table path, so the shape now presents a correct number beside a silently wrong row set.Origin, deliberate and unpinned:
graphistry/compute/gfql/cypher/lowering.py:2583-2591— theto_fixed_point and direction == "undirected"earlyreturn Falsethat keeps this shape off binding rows "rather than trade one wrongness for a decline (#1903 residual)". There is no test pin on it. Fix: route it through the trail-filtered binding lane, or decline typed like polars does — a silently wrong row set with impossible values is the worst of the three options.Related, separately scoped (do not conflate): the same probe re-measured the pre-existing #1787 pandas
min_hops>=2hop-window starvation (root causerow/pipeline.py:3955-3985: the trail expander consumesedge_op.executeoutput already pruned by hop.py's dedup-by-node eccentricity gate, so when that empties the frame the trail machinery never sees the edges). Master and fix head are byte-identical there, and it is documented + pinned — but two sub-cases sit outside any pin or gate and are silent on pandas: seeded undirected*4..4(C4 0 vs 2, C4CH 0 vs 6, SLPAR 0 vs 12, while*3..3is exact) and unseeded undirected*3..3(TRI 0 vs 6, C4 0 vs 8, C4CH 0 vs 24). Worth pins at minimum.