Skip to content

refactor(gfql): route flow productized: chain_specializations layout, routes-off replay, shape registry + route harness, CI ledger - #2054

Merged
lmeyerov merged 12 commits into
masterfrom
refactor/gfql-route-admission-predicates
Sep 7, 2026
Merged

refactor(gfql): route flow productized: chain_specializations layout, routes-off replay, shape registry + route harness, CI ledger#2054
lmeyerov merged 12 commits into
masterfrom
refactor/gfql-route-admission-predicates

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

The route-centric test flow from the #2054 review, folded into one PR at the owner's request (formerly #2054 + #2061 + #2057). Stacked on #2056.

Receipts on this head below; earlier receipts on the closed #2061 and #2057.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA

@lmeyerov

lmeyerov commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Slice 2 added on the same PR (462202f): polars_seeded_lane_admits is the structural gate _try_seeded_chain_polars calls first (frame conditions stay in the body). Pins over the shared corpus: the admitted set (8 shapes), the lane never serves a shape it does not admit under the real dispatch, and called directly on an indexed fixture it serves every admitted non-colliding shape and declines the three alias-collision ones. Local: 115 passed / 1 xfailed on the two admission files; guards OK; mypy identical to master; ruff clean.

raise NotImplementedError(f"polars chain engine does not support op {type(op).__name__}")


PolarsPlainSingleHopShape = Literal["seeded-index", "skip-combine"]

@lmeyerov lmeyerov Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we take this as a chance to cleanup the code a bit, like

  • polars/chain_specializations/{admission,hotpaths}.py: keep in same files to aid DRY, understanding, etc
  • unit tests can then label (& verify) inputs that can tried against multiple hotpaths instead of just 1, increasing tested shapes

note: this is a disruptive refactor, so should rebase & redo once things settle (or prioritize for before more?) to avoid issues like incomplete/drifted refactors

@lmeyerov

lmeyerov commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Agreed on both points, and on the sequencing.

Structure. Redo as graphistry/compute/gfql/lazy/engine/polars/chain_specializations/{admission,hotpaths}.py for polars and the eager twin graphistry/compute/chain_specializations/{admission,hotpaths}.py (the seeded typed-hop lane, single-node lane, RETURN-destination lane and their predicates move out of chain_fast_paths.py / chain.py together), so each specialization's gate and body sit in the same module and the dispatchers only import. Tests mirror those paths.

Corpus. The shape corpus becomes a registry, not a hand-written list: each specialization's own test module registers its shape table (the fast/bypass tables in test_chain.py, the seeded-resolution and lookup shapes, the collision matrix, the residual-polars shapes, …) tagged with the defect classes it exercises; the route harness then runs every registered shape against every route whose predicate admits it, asserting served-by and full-path parity. That is what lets one input be tried against several hot paths instead of one.

Sequencing. This PR stays as-is (behavior-neutral, green) but I am converting it to draft: the restructure is disruptive to chain.py and the polars chain, which #2055 and #2056 also touch, so those land first and the restructure is redone on top of the settled tree in one pass rather than carried through rebases.

@lmeyerov
lmeyerov marked this pull request as draft September 5, 2026 21:48
@lmeyerov

lmeyerov commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

CI on 462202f: 77/77 check-runs success (kept as draft per the restructure plan above).

@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from 462202f to 083078d Compare September 5, 2026 22:14
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from 083078d to c836187 Compare September 6, 2026 00:58
@lmeyerov lmeyerov changed the title refactor(gfql): chain hot paths expose their shape admission predicates refactor(gfql): chain specializations live next to their admission predicates; route-off replay ledger Sep 6, 2026
@lmeyerov
lmeyerov changed the base branch from master to fix/gfql-2050-binding-alias-validation September 6, 2026 00:58
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch 2 times, most recently from 083078d to 511a36f Compare September 6, 2026 01:04
@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

CI receipt at ae307f6: 84 check-runs, all success (python-lint-types incl. the comment/type guards and the relative-import rule; test-polars 3.12 coverage audit with floors for the three new polars modules; tck-gfql; gfql-benchmarks; test-gfql-core; test-pandas-compat-gfql). Local at this head: cuDF lane 1390 pass / 2 xfail; broad CPU 12592 pass (the 2 failures were the polars-lane completeness pin for the harness file, which #2061 registers); routes-off replay per mode posted in the stacked PR. The dgx cuDF + polars-gpu lane runs on the stack head (#2062, which contains this PR) once the current dgx batch finishes.

@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Routes-off replay ledger at the harness head (bin/test-routes-off.sh, local, cuDF on; suites test_chain/test_hop/test_gfql + tests/compute/gfql, ~13.5k tests per mode):

mode failing ids after skipping marked pins what they were
native-fast 25 7 engagement functions (hits==1 / served spies, now marked in #2061), the #2058 dtype class (fixed in #2062), the #2039 alias-collision shape's general-path dtypes (same class)
polars-seeded 12 5 engagement functions (index served / explain), marked
polars-plain 1 the harness serve-ratio pin, marked
index-hop 14 #2058 dtype parity ×3 functions (#2062), 2 GPU edge-match engagement tests (marked)
indexed-kernel 27 served-flag pins (marked), #2058 two-alias dtype parity (#2062)
cypher-fast 46 14 engagement functions (fused/col-stats/degree-fact consult, served lanes; marked), #2059's typed NotImplementedError on the polars rows op
all-off 57 the union above plus 4 combined-route engagement pins (marked) and #2034 (duplicate node ids multiply on the full path; the lanes answer once)

Zero result divergences outside the filed classes (#2058#2062, #2059, #2034). Ledger files: reviews/2054/ on #2061's branch. tck-gfql per-route ledger (graphistry/tck-gfql#196): every mode 4143 passed / 689 xfailed, 0 masked, 0 route-only.

@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

GPU receipt (dgx-spark, graphistry/test-rapids-official:26.02-gfql-polars, cudf 26.02.01 / cupy 13.6.0 / polars 1.35.2, safe_run, TEST_CUDF=1; suites test_chain.py, test_hop.py, test_chain_alias_column_collision.py, tests/compute/gfql/**) on the stack head 676232a (#2062, which contains #2055, #2056, #2054 and #2061): 13383 passed / 93 failed / 55 skipped / 44 xfailed / 4 xpassed. The 93 failures are exactly master 86de0f5's set in the same image (13227 passed / 93 failed: the polars-gpu fused grouped-aggregate lane and strictness tests, #2064): set difference empty, zero branch-specific failures.

@lmeyerov
lmeyerov marked this pull request as ready for review September 6, 2026 02:47
@lmeyerov
lmeyerov force-pushed the fix/gfql-2050-binding-alias-validation branch from 157a55c to f04fe11 Compare September 6, 2026 11:57
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from ae307f6 to 70ddc36 Compare September 6, 2026 11:57
@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

GPU receipt (dgx-spark, graphistry/test-rapids-official:26.02-gfql-polars, cudf 26.02.01 / polars 1.35.2, safe_run, TEST_CUDF=1; suites test_chain.py, test_hop.py, test_chain_alias_column_collision.py, tests/compute/gfql/**) on the restacked stack head a54ac4b (#2065, containing #2055 rework, #2056, #2054, #2061, #2062): 13386 passed / 95 failed / 38 xfailed / 4 xpassed. 93 of the failures are master 86de0f5's polars-gpu fused-lane set (#2064, fixed separately in #2066). The other 2 are strict XPASS of the #2043 prune_to_endpoints markers on cuDF 26.02: the prune shapes now agree fast-vs-full on this stack (the #2062 closure fix removed the divergence), so #2062 drops that marker in a follow-up commit (tree otherwise identical; #2065 restacked).

@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

CI receipt at 70ddc36 (restacked on the #2055 rework): 84 check-runs, all success. GPU receipt on the stack head above.

@lmeyerov
lmeyerov force-pushed the fix/gfql-2050-binding-alias-validation branch from 11ad4f4 to 6d2c654 Compare September 6, 2026 15:33
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from 00002b8 to 68c4ac2 Compare September 6, 2026 15:33
@lmeyerov lmeyerov changed the title refactor(gfql): chain specializations live next to their admission predicates; route-off replay ledger refactor(gfql): route flow productized: chain_specializations layout, routes-off replay, shape registry + route harness, CI ledger Sep 6, 2026
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from c40b996 to b19ac3c Compare September 6, 2026 15:38
@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

GPU receipt (dgx-spark, graphistry/test-rapids-official:26.02-gfql-polars, cudf 26.02.01 / polars 1.35.2, safe_run, TEST_CUDF=1; suites test_chain.py, test_hop.py, test_chain_alias_column_collision.py, tests/compute/gfql/**) on the folded stack top 500265e (#2062, containing #2056 and #2054): 13532 passed / 7 failed / 37 xfailed / 4 xpassed, versus master 86de0f5's 13227 / 93 in the same image. Every one of the 7 is inside master's set (5 polars-gpu strictness cases and the routing-contract / indexed-bypass pins tracked on #2064 and #1803); zero branch-specific failures, and the fused-lane fix folded from #2066 clears the other 86.

@lmeyerov

lmeyerov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

CI receipt at b19ac3c (folded: layout + routes-off replay + registry/harness + CI filter; includes the fold repair of the polars plain lane): 84 check-runs, all success. Local on the stack top: broad CPU + cuDF 12939 / 0; tck-gfql 4143 / 689 xfail; GPU receipt above.

@lmeyerov
lmeyerov force-pushed the fix/gfql-2050-binding-alias-validation branch from 2a645a3 to 5775f76 Compare September 7, 2026 01:51
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from b19ac3c to b5ba132 Compare September 7, 2026 02:03
@lmeyerov

lmeyerov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the #2056 head 5775f76b5ba132. The one conflict was the chain.py block this PR moves out plus #2056's new helper in the same region; resolved by keeping only the helper. The PR's own product delta is line-for-line identical before and after the rebase (sorted diff: 0 lines). Gates on the new head: routes harness, both specialization packages, collision matrix, chain, scoping (609 pass / 14 xfail incl. cuDF), lane completeness 12/12, lint/mypy unchanged vs master. CI 84/84.

Base automatically changed from fix/gfql-2050-binding-alias-validation to master September 7, 2026 06:05
lmeyerov and others added 11 commits September 6, 2026 23:06
The pandas/cuDF chain fast path and the polars chain's plain single-hop branches decided
admission with inline checks and closures, so a test could only learn which route served a
shape by spying. Each gate is now a function the dispatcher calls — `native_fast_path_admits`
and `polars_plain_single_hop_admits` — and a shared route corpus
(graphistry/tests/compute/gfql/routes/corpus.py) is filtered per route by that same
function: the decision table is pinned per shape, served-by is asserted against the
predicate on pandas and cuDF, and every admitted shape is checked against the pandas
full path. No route admits or declines anything it did not before.

Found while pinning: the polars plain branch admits `prune_to_endpoints` where the pandas
gate declines it, and the results differ (#2053, strict expected failure).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
(cherry picked from commit 82b2f0e)
…edicate

`polars_seeded_lane_admits` is the structural gate `_try_seeded_chain_polars` now calls
first; frame conditions (polars frames, id dtypes, valid resident indexes, scalar filters,
alias collisions) stay in the body. Pins over the shared corpus: the admitted set, the
lane never serves a shape it does not admit under the real dispatch, and called directly
on an indexed fixture it serves every admitted non-colliding shape and declines the
colliding ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
(cherry picked from commit 083078d)
…edicates

The pandas/cuDF lanes (single-node, seeded typed single hop, seeded typed
RETURN-destination) move from chain.py/chain_fast_paths.py into
graphistry/compute/chain_specializations/{admission,hotpaths}.py; the polars
lanes (plain single-hop branches, seeded lane, RETURN-destination) move into
graphistry/compute/gfql/lazy/engine/polars/chain_specializations/. chain.py
and the polars chain only dispatch; chain_fast_paths.py keeps the shared
seed/index helpers. No route admits or declines anything it did not before.
Tests mirror the new module paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…blocking CI ledger

GFQL_ROUTES_OFF=<route,...> (tests/conftest.py) makes named hot paths decline
so every existing test replays through the other routes. Tests that assert a
route serves carry @pytest.mark.route_engaged(<route>, ...) and are skipped in
that mode, so bin/test-routes-off.sh reports result divergences only; the
gfql-routes-off CI matrix uploads the per-route ledger without blocking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…arness

Each specialization's test module registers the shape table it already owns
(routes corpus, the six test_chain tables, the alias-collision matrix) with
its frames and defect-class tags. The harness tries every registered shape
against every chain route whose admission predicate admits it and pins that
the lane serves, that the answer matches the same engine's general path on
values, and that node/edge sets match the pandas general path. A lane that
declines an admitted shape is recorded as an expected failure (the
attenuation ledger); filed divergences are strict expected failures keyed by
tag. The pandas bypass table's prune shapes surface #2053 on the polars route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…gement pins marked

The route harness mentions polars, so it runs in bin/test-polars.sh (lane
completeness pin). Six more tests that assert a native-fast serve (hits == 1,
served spies) carry the route_engaged marker, so the routes-off replay for
native-fast reports result divergences only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…he route_engaged marker

Replay at the harness head (scratchpad ledger kept under reviews/2054/): every
remaining single-route id was an engagement pin (a served spy, a trace or a
lane-specific explain step) or the #2058 dtype class; the all-off residue adds
four combined-route engagement pins and the #2034 duplicate-id case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
The gfql change filter listed compute/gfql/** and the unified entrypoint but not the
chain engine itself (chain.py, chain_fast_paths.py, hop.py, gfql_fast_paths.py,
filter_by_dict.py, ast.py, predicates/), so a PR touching only those skipped tck-gfql,
the Cypher-frontend gates and the gfql benchmark lane.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
(cherry picked from commit c402b7f)
…rule (fold repair: the inline branch the layout extracted had come back beside the dispatch)
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from b5ba132 to 9616f11 Compare September 7, 2026 06:06
@lmeyerov

lmeyerov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Post-#2056 audit of the moved code (owner ask: did the files this PR moves change in #2056?)

Checked mechanically at head 9616f11 (rebased onto master f45469c):

CI on 9616f11 in progress; posted when it lands.

@lmeyerov

lmeyerov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Read-through of the moved code vs master f45469c (owner: "don't trust mechanical; read them")

Read side by side, function by function, at head 9616f11:

  • Eager gate native_fast_path_admits: master's inline conditions one for one (engine pandas/cuDF, no start_nodes; 1 op → unqueried node; 3 ops → unqueried nodes, simple single hop with no node matches/queries, no edge query, no zero-hop seed, no prune; distinct aliases; undirected declines with names or filters).
  • _try_chain_fast_path: single-node branch statement-identical (index-or-filter rows, alias column placement, empty edges). Three-op body statement-identical from the alias tuple to the final drop_duplicates and _tag_fast_path_aliases, including both binding-collision declines (node alias == node id; edge alias == from-side binding), the unconstrained endpoint validation, the seeded typed-hop call, and the filtered branch.
  • _seeded_typed_hop_pandas_cudf, _seeded_typed_return_dst_pandas_cudf, _single_node_rows_via_index_or_filter: bodies identical; only comment trims and the absolute import.
  • Polars gate polars_plain_single_hop_admits / _plain_node / _plain_edge: master's _fp_node/_plain_edge and the two inline conditions (seed filter + no destination filter + directed → index consult; unconstrained or directed → skip-combine; filtered undirected → full chain).
  • _plain_single_hop_polars: master's inline branch statement for statement (endpoint alignment, null-endpoint drop, per-side semi joins, the closure check for sides not closed by a filter, and fix(gfql): chain result contract: binding-column aliases decline, duplicate ids answer once, no internal columns (#2050, #2051, #2067) #2056's unique(subset=[ncol], maintain_order=True)).
  • _plain_seeded_index_hop_polars: master's inline block, same maybe_index_hop call and policy/registry gate (negated for the early return).
  • _try_seeded_chain_polars, _seeded_typed_return_dst_polars: identical from the frame checks through the alias flags and _record_indexed_traversal.
  • Dispatch order in the polars chain matches master: index consult → seeded lane → plain lane. _combine_edges keeps fix(gfql): chain result contract: binding-column aliases decline, duplicate ids answer once, no internal columns (#2050, #2051, #2067) #2056's shadow-restore scoping verbatim. chain.py keeps reject_alias_named_like_binding and calls the relocated fast path at the same point (policy-free only).

Nothing from #2056 is missing and nothing moved changed meaning. CI on 9616f11: 70 green, 15 running.

@lmeyerov

lmeyerov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

CI on 9616f11: 86/86 green. Head is final; ready to merge.

…e the tree

The shape corpus now runs over six frame variants (string ids, nullable ids with nulls, duplicate ids, self-loops and a cycle, an empty edge table, no edge-id binding) as well as the base frames; the harness oracle normalizes null ids, the serve-ratio pin reads the base corpus only, and known-divergence xfails are non-strict on variants where a shape can coincide. The only divergence the variants surfaced is #2034 (duplicate node ids on the single-node lookup), pinned by row. Route replay ledgers are PR comments, not tracked files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
@lmeyerov
lmeyerov force-pushed the refactor/gfql-route-admission-predicates branch from 2efeb04 to f5cee3b Compare September 7, 2026 06:53
@lmeyerov

lmeyerov commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Corpus breadth (owner: "corpus.py seems myopic") and review ledgers (head f5cee3b)

The registry the harness runs is wider than corpus.py (80 shapes from four test modules, 161 route cases), but every frame was a 4–5 node graph with int64 ids and nothing structural to trip on. This head adds a frame dimension: the same 22 shapes now also run over string ids, nullable ids with nulls, duplicate ids, self-loops plus a cycle, an empty edge table, and no edge-id binding, on pandas, cuDF and polars (359 harness cases). Findings:

  • One real divergence, already known: gfql: rows(table=nodes, source=alias) multiplies rows for duplicate node ids and joins null ids to each other #2034 (single-node predicate lookup keeps each duplicate-id row; the general path collapses them), pinned by row on both eager engines.
  • The variants attenuate the lanes as expected (null/string ids decline the polars seeded lane; recorded in the xfail ledger, not counted in the serve-ratio pin).
  • Everything else the variants tripped was harness or fixture: the oracle now normalizes null ids; known-divergence xfails are non-strict on variants where a shape can coincide.

Still narrow, and queued in the plan rather than grown by hand here: shapes should be harvested from the main GFQL shape suites (record every op list + small frames the suites execute and replay them through the harness) instead of cherry-picked; the harness wires three routes (native-fast, polars-plain, polars-seeded), not index-hop, indexed-kernel, cypher-fast or rows; no polars-gpu engine.

reviews/2054/ is no longer tracked; the replay ledger table stays in the comment above and the files live locally under plans/.

@lmeyerov
lmeyerov merged commit c2d3a07 into master Sep 7, 2026
62 checks passed
@lmeyerov
lmeyerov deleted the refactor/gfql-route-admission-predicates branch September 7, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant