Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
174 commits
Select commit Hold shift + click to select a range
483e50c
fix(gfql): temporal/error-leak family — #1915 B-5/B-7/B-8 + A-4, #188…
lmeyerov Aug 19, 2026
7779263
test(gfql): pin keyword-property filter pushdown classification (#191…
lmeyerov Aug 19, 2026
18509d3
chore(gfql): guard hygiene — typed helpers, one-line comments, baseli…
lmeyerov Aug 19, 2026
8e259ce
fix(gfql): route tz-suffixed temporal text off the OPTIONAL MATCH fil…
lmeyerov Aug 19, 2026
d7ff87f
test(gfql): pin connected-join tz-suffixed residual routing (#1915 B-7)
lmeyerov Aug 19, 2026
1f0e07b
test(gfql): close changed-line coverage gaps (composite property acce…
lmeyerov Aug 19, 2026
ff11853
test(gfql): retire the literal-temporal-ordering polars declines — CO…
lmeyerov Aug 19, 2026
1758318
merge: master (#1942) forward
lmeyerov Aug 19, 2026
246e0db
ci: chain.py comment caps follow master after the #1942 union
lmeyerov Aug 19, 2026
2858968
fix(gfql): reentry-carry seed restriction on every route (#1712) + al…
lmeyerov Aug 19, 2026
26cd590
test(gfql): register reentry-carry pins in the polars lane
lmeyerov Aug 19, 2026
1b33957
style(gfql): one-line the new constraint comments; drop new casts/Any…
lmeyerov Aug 19, 2026
1035674
test(gfql): helper-branch pins for the rows-route restore; keep the d…
lmeyerov Aug 19, 2026
5b13c0d
style(gfql): drop issue ref from the chain coalesce comment (guard)
lmeyerov Aug 19, 2026
cc5f4f1
style(gfql): keep the generic coalesce lines textually unchanged (cha…
lmeyerov Aug 19, 2026
aca2a6d
fix(gfql): scope the endpoint-alias decline to cypher chains (raw GFQ…
lmeyerov Aug 19, 2026
2c25be2
style(gfql): one-line the cypher-only decline comment (guard)
lmeyerov Aug 19, 2026
37499e9
fix(gfql): shadow restore rides an internal column; skip marker-carry…
lmeyerov Aug 19, 2026
9a1d338
style(gfql): drop the unreachable string-token restore branch (AST pa…
lmeyerov Aug 19, 2026
d2c69ad
test: normalize NaN->None at record level in the B-7 OPTIONAL MATCH pin
lmeyerov Aug 19, 2026
cf7685b
test: pin folding.py's defensive arms for the gfql-core coverage floor
lmeyerov Aug 19, 2026
7d47ebd
test: NaN->None at the shared _rows helper (py3.13 pandas renders NaN)
lmeyerov Aug 19, 2026
971b444
Merge pull request #1948 from graphistry/fix/gfql-1915-tail-1880
lmeyerov Aug 19, 2026
0c3f3a1
Merge pull request #1950 from graphistry/fix/gfql-1911d4-1712-carry
lmeyerov Aug 19, 2026
0dcdf6c
fix(gfql): make gfql_validate agree with execution on unqueryable gra…
lmeyerov Aug 19, 2026
0542a72
fix(gfql): retain min_hops branches that end below max_hops (pandas+p…
lmeyerov Aug 19, 2026
a801eee
fix(ci): unblock master lint lane (comment-guard finding above cap)
lmeyerov Aug 19, 2026
ab8a749
chore(gfql): move a #1911 rationale onto its code line to clear the m…
lmeyerov Aug 19, 2026
9792b07
Revert "chore(gfql): move a #1911 rationale onto its code line to cle…
lmeyerov Aug 19, 2026
26bda67
docs(changelog): record the #1889 validator/execution agreement fix
lmeyerov Aug 19, 2026
4e6b70c
Merge pull request #1953 from graphistry/fix/master-comment-guard-red
lmeyerov Aug 19, 2026
bf069cc
Merge remote-tracking branch 'ghhttps/master' into lz-1889
lmeyerov Aug 19, 2026
7b3e3fd
Merge remote-tracking branch 'ghhttps/master' into lz-1944
lmeyerov Aug 19, 2026
42b4f7f
Merge pull request #1951 from graphistry/fix/gfql-1889-validator-drift
lmeyerov Aug 19, 2026
9838197
Merge remote-tracking branch 'ghhttps/master' into lz-1944
lmeyerov Aug 19, 2026
0aaa862
Merge pull request #1952 from graphistry/fix/gfql-1944-prune-walk
lmeyerov Aug 19, 2026
c51a5f2
fix(gfql): keep None filter values in to_json (#1954)
lmeyerov Aug 19, 2026
09633aa
Merge pull request #1969 from graphistry/fix/1954-filter-dict-none-drop
lmeyerov Aug 19, 2026
6174ff1
fix(gfql): decline lossy remote csv decoding instead of silently re-i…
lmeyerov Aug 19, 2026
05a0795
fix(layout): group_in_a_box_layout emitted duplicate node ids at conf…
lmeyerov Aug 19, 2026
ba5787a
test(gfql): assert non-numeric id dtype rather than literal object
lmeyerov Aug 19, 2026
33fe1c9
test(gfql): teach hypergraph remote mock the df_import_args kwarg
lmeyerov Aug 19, 2026
5d49c55
fix(remote): accept the task function name python_remote's own contra…
lmeyerov Aug 19, 2026
c5bffba
Merge pull request #1970 from graphistry/fix/1961-gib-duplicate-nodes
lmeyerov Aug 19, 2026
9164f57
Merge remote-tracking branch 'ghhttps/master' into fix/1959-python-re…
lmeyerov Aug 19, 2026
64472a6
Merge master into fix/1958-remote-csv-dtype-fidelity
lmeyerov Aug 19, 2026
310cb81
Merge pull request #1971 from graphistry/fix/1958-remote-csv-dtype-fi…
lmeyerov Aug 19, 2026
327764b
Merge remote-tracking branch 'ghhttps/master' into fix/1959-python-re…
lmeyerov Aug 19, 2026
401f54f
Merge pull request #1972 from graphistry/fix/1959-python-remote-task-…
lmeyerov Aug 19, 2026
4a59b90
fix(gfql): typed remote error surface — #1956 raw leaks, #1960 plumbi…
lmeyerov Aug 19, 2026
cf0137e
fix(gfql): a compound zip member is never bound to either table (#1956)
lmeyerov Aug 19, 2026
61c3597
Merge pull request #1973 from graphistry/fix/1956-1960-remote-error-s…
lmeyerov Aug 19, 2026
29e2750
fix(remote): type the csv decline instead of raising a bare ValueError
lmeyerov Aug 19, 2026
aeb68bc
fix(remote): warn on untyped csv instead of refusing the call
lmeyerov Aug 19, 2026
5301009
fix(remote): decline unsupported frame libraries before the request
lmeyerov Aug 19, 2026
d9c2377
test(polars): register the remote csv fidelity module in the polars lane
lmeyerov Aug 19, 2026
af494be
Merge pull request #1974 from graphistry/fix/csv-decline-typed-error
lmeyerov Aug 19, 2026
a96f2fc
fix(gfql): explicit polars-gpu runs the Cypher OLAP fast paths on GPU…
lmeyerov Aug 19, 2026
e4ad11c
Merge pull request #1979 from graphistry/fix/1824-polars-gpu-fastpath…
lmeyerov Aug 19, 2026
e76b90f
fix(gfql): split a fractional month at the average month instead of d…
lmeyerov Aug 19, 2026
a0c1a38
Merge master into fix/gfql-1937-split-month-scaling
lmeyerov Aug 19, 2026
57a72db
docs(gfql): stop claiming GRAPHISTRY_GFQL_STRICT_SCHEMA affects queries
lmeyerov Aug 19, 2026
47e1b04
Merge pull request #1980 from graphistry/fix/gfql-1937-split-month-sc…
lmeyerov Aug 20, 2026
866012d
fix(gfql): pin the sum/avg-over-BOOLEAN extension's return TYPES acro…
lmeyerov Aug 20, 2026
ecfb083
feat(gfql): strictness levels for absent labels/properties, default warn
lmeyerov Aug 20, 2026
c2911d0
Merge remote-tracking branch 'ghhttps/master' into fix/1820-bool-agg-…
lmeyerov Aug 20, 2026
7cb897a
style(gfql): cut the lazy_cudf_import comment to one line for the com…
lmeyerov Aug 20, 2026
ca413dd
test(gfql): pin absent-property row-expression 3VL and its validator …
lmeyerov Aug 20, 2026
043e963
refactor(gfql): keep strict= last in chain_remote_shape so positional…
lmeyerov Aug 20, 2026
5571856
fix(gfql): defensive schema reads, label-carrier mirror, and the pre-…
lmeyerov Aug 20, 2026
912f173
Merge pull request #1982 from graphistry/fix/1820-bool-agg-contract
lmeyerov Aug 20, 2026
54266cc
Merge pull request #1981 from graphistry/fix/1916a-dead-strict-schema…
lmeyerov Aug 20, 2026
1c0f564
docs(gfql): note the label-carrier resolution mirror in the changelog
lmeyerov Aug 20, 2026
7f2fff1
Merge remote-tracking branch 'ghhttps/master' into feat/gfql-1916-str…
lmeyerov Aug 20, 2026
2c1961b
fix(gfql): update the #1981 env-inertness pin and the strict-mode doc…
lmeyerov Aug 20, 2026
8e31a91
docs(remote): csv warns and serves, it is not refused
lmeyerov Aug 20, 2026
102cca7
fix(gfql): leniency must not swallow errors unrelated to absence
lmeyerov Aug 20, 2026
ff30efc
fix: restore the unrelated except in _normalize_labels_cell
lmeyerov Aug 20, 2026
d1e715a
Merge pull request #1984 from graphistry/fix/csv-docstrings-stale-ref…
lmeyerov Aug 20, 2026
f32a31d
Merge pull request #1983 from graphistry/feat/gfql-1916-strictness-le…
lmeyerov Aug 20, 2026
91454bd
fix(gfql): an index must not change the answer to EXISTS pattern pred…
lmeyerov Aug 20, 2026
08c5959
fix(remote): warning predicate tracks real dtype/NA control; unbreak …
lmeyerov Aug 20, 2026
ac39230
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
c2e9c38
docs: correct docstrings and docs that assert behavior the code lacks
lmeyerov Aug 20, 2026
7184980
ci: run gpu-gate-audit ungated
lmeyerov Aug 20, 2026
6a5b70b
docs: state the -rs property as availability, not as a lane that runs it
lmeyerov Aug 20, 2026
683b253
fix(gfql): size() over a non-sequence column declines instead of answ…
lmeyerov Aug 20, 2026
1a0cbaf
docs: drop the superseded-doctrine aside from the AUTO cuDF arm
lmeyerov Aug 20, 2026
31bf209
test(remote): pin key-presence, not key-value, as the governing signal
lmeyerov Aug 20, 2026
4121336
perf(gfql): check each direction's adjacency keys instead of their union
lmeyerov Aug 20, 2026
3e20014
Merge pull request #1988 from graphistry/docs/fix-false-claims-2026-08
lmeyerov Aug 20, 2026
83c59c6
fix(gfql): decline size() on evidence, not dtype — an empty/all-null …
lmeyerov Aug 20, 2026
fbbec0b
fix(gfql): seeded typed 1-hop and leading OPTIONAL MATCH keep bag mul…
lmeyerov Aug 20, 2026
f60b5e4
fix(gfql): OPTIONAL reentry null-extension keeps the carried entity's…
lmeyerov Aug 20, 2026
8a72de4
Merge pull request #1987 from graphistry/fix/remote-csv-warning-predi…
lmeyerov Aug 20, 2026
3b362d1
fix(gfql): polars hop de-dups its output node table like pandas
lmeyerov Aug 20, 2026
d5d3347
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
7a548e8
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
031fdac
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
83c77cb
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
b78dd0d
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
fe496e5
fix(gfql): seeded fast path follows the dtype rule of the lane it sta…
lmeyerov Aug 20, 2026
b49b8b3
Merge pull request #1990 from graphistry/fix/1986-index-exists-pattern
lmeyerov Aug 20, 2026
e0b8135
Merge remote-tracking branch 'ghhttps/master' into lz92
lmeyerov Aug 20, 2026
10387e3
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
c4318f3
Merge remote-tracking branch 'ghhttps/master' into lz93
lmeyerov Aug 20, 2026
3e2f05e
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
f37df7e
Merge pull request #1992 from graphistry/fix/gfql-1897-optional-reent…
lmeyerov Aug 20, 2026
38ace4c
docs(changelog): restore the polars hop node de-dup entry
lmeyerov Aug 20, 2026
11a4583
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
0a9002a
Merge branch 'master' of https://github.com/graphistry/pygraphistry i…
lmeyerov Aug 20, 2026
ad0e596
docs(changelog): drop the duplicated polars hop de-dup entry
lmeyerov Aug 20, 2026
99b48b5
Merge remote-tracking branch 'ghhttps/master' into lz89b
lmeyerov Aug 20, 2026
85fc1ec
Merge pull request #1989 from graphistry/fix/1985-gfql-size-nonlist-d…
lmeyerov Aug 20, 2026
6d5c81e
Merge remote-tracking branch 'ghhttps/master' into lzr1991
lmeyerov Aug 20, 2026
e741fa3
Merge remote-tracking branch 'ghhttps/master' into lzn93
lmeyerov Aug 20, 2026
58f89fc
Merge pull request #1991 from graphistry/fix/gfql-1899-seeded-and-opt…
lmeyerov Aug 20, 2026
0516c8c
Merge remote-tracking branch 'ghhttps/master' into lzn93
lmeyerov Aug 20, 2026
9f93d45
Merge pull request #1993 from graphistry/fix/gfql-polars-hop-node-dedup
lmeyerov Aug 20, 2026
66b2280
fix(gfql): cuDF answered 0 rows where pandas answered 15 for a string…
lmeyerov Aug 20, 2026
d0e23d8
fix(gfql): a NULL id is not an identity — one NULL-endpoint contract …
lmeyerov Aug 20, 2026
280bd9f
test(gfql): pin the get_degrees boundary the NULL contract does not r…
lmeyerov Aug 20, 2026
f6edc14
Merge pull request #1998 from graphistry/fix/gfql-cudf-categorical-se…
lmeyerov Aug 20, 2026
a743bf8
Merge branch 'master' into fix/1995-null-endpoint-contract
lmeyerov Aug 20, 2026
585062c
test(gfql): pin the NULL contract on the #1658 index-backed route too
lmeyerov Aug 20, 2026
360c3d7
refactor(gfql): share endpoint identity filtering
lmeyerov Aug 21, 2026
d31d4cc
docs(gfql): separate NULL input validity from resolution
lmeyerov Aug 27, 2026
190b802
Merge pull request #1999 from graphistry/fix/1995-null-endpoint-contract
lmeyerov Aug 27, 2026
e80a233
fix(gfql): keep openCypher bag multiplicity in whole-entity endpoint …
lmeyerov Aug 20, 2026
66be93a
fix(gfql): scope the whole-entity bag lane and update the pins it moved
lmeyerov Aug 20, 2026
8678afe
docs(gfql): align polars entity projection diagnostics
lmeyerov Aug 20, 2026
4400609
ci(gfql): update cypher surface baseline
lmeyerov Aug 20, 2026
3325926
refactor(gfql): share whole-entity projection mapping
lmeyerov Aug 21, 2026
4bb0847
refactor(gfql): name node entity projection shape
lmeyerov Aug 21, 2026
bf5f754
test(gfql): baseline projection column helper coverage
lmeyerov Aug 21, 2026
6963275
docs(compute): explain endpoint frame type tracking
lmeyerov Aug 27, 2026
d509e42
Merge pull request #2000 from graphistry/fix/gfql-1994-whole-entity-bag
lmeyerov Aug 27, 2026
12adcf7
fix(gfql): treat binding seed ids as identities
lmeyerov Aug 20, 2026
05705e4
ci(gfql): add binding seed tests to polars lane
lmeyerov Aug 20, 2026
b1aac89
refactor(gfql): harden binding seed identity
lmeyerov Aug 21, 2026
a888c33
test(gfql): exercise seed identity without patching
lmeyerov Aug 27, 2026
d627fac
Merge pull request #2001 from graphistry/fix/gfql-1996-binding-seed-i…
lmeyerov Aug 28, 2026
66df470
fix(gfql): preserve boolean sum zero on polars gpu
lmeyerov Aug 20, 2026
8c55f7f
ci(gfql): satisfy comment density guard
lmeyerov Aug 20, 2026
62e8c4a
refactor(gfql): remove boolean sum narration
lmeyerov Aug 21, 2026
9f91c18
test(gfql): pin polars boolean sum null-fill boundary
lmeyerov Aug 27, 2026
d357373
docs(changelog): note polars gpu boolean sum fix
lmeyerov Aug 28, 2026
fcf369f
Merge pull request #2002 from graphistry/fix/gfql-1997-polars-gpu-boo…
lmeyerov Aug 28, 2026
f899b41
fix(gfql): validate absent relationship types
lmeyerov Aug 20, 2026
180d31d
refactor(gfql): type strict relationship tests
lmeyerov Aug 21, 2026
c855c5c
test(gfql): close relationship type judgment boundary
lmeyerov Aug 27, 2026
76d87ab
docs(changelog): note strict relationship type validation
lmeyerov Aug 28, 2026
222799c
Merge pull request #2003 from graphistry/fix/gfql-1916-relationship-t…
lmeyerov Aug 28, 2026
9b5ba26
fix(gfql): reject cross-kind WITH rebinds early
lmeyerov Aug 20, 2026
36090a8
refactor(gfql): tighten scalar rebind error contracts
lmeyerov Aug 21, 2026
914e351
test(gfql): type cross-kind rebind boundaries
lmeyerov Aug 27, 2026
dd044ea
docs(changelog): note cross-kind WITH rebind guard
lmeyerov Aug 28, 2026
a2b1b44
Merge pull request #2004 from graphistry/fix/gfql-1937-cross-kind-wit…
lmeyerov Aug 28, 2026
cca8884
fix(gfql): preflight remote engine requests
lmeyerov Aug 20, 2026
8d362f2
ci(gfql): justify remote resolver input type
lmeyerov Aug 20, 2026
268c93a
ci(gfql): keep remote contract tests py38-compatible
lmeyerov Aug 20, 2026
8289bc2
ci(gfql): classify remote engine contract lane
lmeyerov Aug 20, 2026
5defe0d
test(engine): recognize remote resolver wrapper
lmeyerov Aug 20, 2026
8825e2b
refactor(remote): type engine preflight contracts
lmeyerov Aug 21, 2026
55161a1
docs(changelog): complete remote engine preflight
lmeyerov Aug 29, 2026
749d590
Merge pull request #2005 from graphistry/fix/gfql-1957-remote-engine-…
lmeyerov Aug 29, 2026
e564301
test(gfql): clarify polars-gpu contracts
lmeyerov Aug 29, 2026
9a8934d
test(gfql): cover polars-gpu absent predicates
lmeyerov Aug 29, 2026
76dc3f3
Merge pull request #2010 from graphistry/test/gfql-1997-gpu-contracts
lmeyerov Aug 29, 2026
5825927
chore(release): sync master into 0.59.0 line
lmeyerov Aug 31, 2026
9c23ad3
docs(changelog): cut 0.59.0
lmeyerov Aug 31, 2026
dd8ef64
chore(release): sync landed GFQL stack
lmeyerov Sep 1, 2026
5975215
docs(changelog): preserve reviewed benchmark wording
lmeyerov Sep 1, 2026
749736c
ci: give Polars coverage lane deterministic margin
lmeyerov Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,22 @@ jobs:
printf 'Differential/parity tests:\n- %s\n' "${parity_tests[@]}"
python -B -m pytest -vv -n auto "${parity_tests[@]}"

gpu-gate-audit:
# Ungated: a guard against a silent coverage gap must not itself be conditionally
# silent, and the path filters do not cover every file it cross-checks.
name: gpu-gate-audit
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Audit cuDF test gates and the CI coverage they lack
run: |
python bin/ci_gpu_gate_audit.py

cypher-frontend-surface-guard:
name: cypher-frontend-surface-guard
needs: [changes]
Expand Down Expand Up @@ -1577,7 +1593,9 @@ jobs:
needs: [changes, test-minimal-python, test-gfql-core, generate-lockfiles]
if: ${{ ((needs.changes.outputs.python == 'true' && needs.changes.outputs.narrow_python_only != 'true') || needs.changes.outputs.gfql == 'true' || needs.changes.outputs.pandas_compat == 'true' || needs.changes.outputs.core == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule') && !(needs.changes.outputs.docs_only_latest == 'true' && (github.event_name == 'push' || github.event_name == 'pull_request')) }}
runs-on: ubuntu-latest
timeout-minutes: 10
# The py3.12 coverage cell takes ~9m40s on a 2-vCPU hosted runner before
# setup, the coverage audit, and artifact upload; keep deterministic margin.
timeout-minutes: 15

strategy:
matrix:
Expand Down
40 changes: 38 additions & 2 deletions CHANGELOG.md

Large diffs are not rendered by default.

23 changes: 21 additions & 2 deletions DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,31 @@ Ruff additionally rejects `getattr(x, "const")` / `setattr(x, "const", v)`

### GPU CI

GPU CI can be manually triggered by core dev team members:
**Today, no CI lane executes cuDF.** `ci.yml` never sets `TEST_CUDF` and no lane
installs `cudf`, and `ci-gpu.yml` is disabled: its jobs are gated on the
`GRAPHISTRY_ENABLE_GPU_PUBLIC` repository variable (unset), it needs the
`gpu_public` self-hosted runner, and a `gpu-disabled-guard` job hard-fails any
manual trigger. So a `TEST_CUDF=1` receipt is **developer-local evidence only** --
a cuDF-gated test can contradict the CPU contract, or rot outright, and stay green
on master indefinitely. Treat a GPU claim in a PR as unprotected until a GPU lane
exists: re-run it yourself rather than trusting the last receipt.

`bin/ci_gpu_gate_audit.py` (lane `gpu-gate-audit`) keeps the size of that gap
visible: it counts the cuDF gates, requires each to be attributable (a `reason=`
naming `TEST_CUDF`, so `pytest -rs` names what was not run rather than reporting a
bare `s`) and to actually read the flag from the environment, and cross-checks this note against
whether any workflow sets `TEST_CUDF`. Wiring a real GPU lane retires the note;
deleting the note without wiring a lane fails the audit. The audit is static -- it
proves the gates are well formed, never that the gated assertions hold.

GPU CI can be manually triggered by core dev team members, once the lane is
re-enabled:

1. Push intended changes to protected branches `gpu-public` or `master`
2. Manually trigger action [ci-gpu](https://github.com/graphistry/pygraphistry/actions/workflows/ci-gpu.yml) on one of the above branches

GPU tests can also be run locally via `./docker/test-gpu-local.sh` .
GPU tests can also be run locally via `./docker/test-gpu-local.sh` , or directly
with `TEST_CUDF=1 pytest ...` on a RAPIDS-equipped box.

## Debugging Tips

Expand Down
29 changes: 11 additions & 18 deletions bin/ci_comment_density_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"graphistry/compute/gfql/cypher/_boolean_expr_text.py": 1,
"graphistry/compute/gfql/cypher/ast.py": 5,
"graphistry/compute/gfql/cypher/ast_normalizer.py": 1,
"graphistry/compute/gfql/cypher/lowering.py": 49,
"graphistry/compute/gfql/cypher/lowering.py": 48,
"graphistry/compute/gfql/cypher/parser.py": 24,
"graphistry/compute/gfql/cypher/reentry/compiletime.py": 2,
"graphistry/compute/gfql/cypher/reentry/execution.py": 5,
Expand All @@ -49,7 +49,7 @@
"graphistry/compute/gfql/index/explain.py": 1,
"graphistry/compute/gfql/index/lookup.py": 1,
"graphistry/compute/gfql/index/registry.py": 4,
"graphistry/compute/gfql/index/traverse.py": 15,
"graphistry/compute/gfql/index/traverse.py": 13,
"graphistry/compute/gfql/index/types.py": 3,
"graphistry/compute/gfql/index/wire.py": 2,
"graphistry/compute/gfql/ir/pushdown_safety.py": 5,
Expand All @@ -59,14 +59,13 @@
"graphistry/compute/gfql/lazy/engine/polars/chain.py": 47,
"graphistry/compute/gfql/lazy/engine/polars/degrees.py": 3,
"graphistry/compute/gfql/lazy/engine/polars/dtypes.py": 3,
"graphistry/compute/gfql/lazy/engine/polars/hop.py": 2,
"graphistry/compute/gfql/lazy/engine/polars/hop_eager.py": 19,
"graphistry/compute/gfql/lazy/engine/polars/hop_eager.py": 18,
"graphistry/compute/gfql/lazy/engine/polars/lowering_context.py": 2,
"graphistry/compute/gfql/lazy/engine/polars/nan_clean.py": 2,
"graphistry/compute/gfql/lazy/engine/polars/nan_clean.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py": 9,
"graphistry/compute/gfql/lazy/engine/polars/predicates.py": 18,
"graphistry/compute/gfql/lazy/engine/polars/projection.py": 5,
"graphistry/compute/gfql/lazy/engine/polars/row_pipeline.py": 84,
"graphistry/compute/gfql/lazy/engine/polars/row_pipeline.py": 82,
"graphistry/compute/gfql/lazy/engine/polars/search.py": 3,
"graphistry/compute/gfql/lazy/engine/polars/varlen_rows.py": 1,
"graphistry/compute/gfql/logical_planner.py": 1,
Expand All @@ -77,7 +76,7 @@
"graphistry/compute/gfql/row/entity_props.py": 1,
"graphistry/compute/gfql/row/frame_ops.py": 6,
"graphistry/compute/gfql/row/ordering.py": 1,
"graphistry/compute/gfql/row/pipeline.py": 40,
"graphistry/compute/gfql/row/pipeline.py": 38,
"graphistry/compute/gfql/same_path/multihop.py": 1,
"graphistry/compute/gfql/same_path/native_shortest_path.py": 3,
"graphistry/compute/gfql/search_any.py": 4,
Expand All @@ -89,7 +88,7 @@
"graphistry/compute/gfql_unified.py": 21,
"graphistry/compute/gfql_validate.py": 3,
"graphistry/compute/graph_operation.py": 1,
"graphistry/compute/hop.py": 6,
"graphistry/compute/hop.py": 4,
"graphistry/compute/predicates/comparison.py": 9,
"graphistry/compute/predicates/from_json.py": 1,
"graphistry/compute/predicates/is_in.py": 4,
Expand Down Expand Up @@ -145,7 +144,7 @@
"graphistry/ArrowFileUploader.py": 3,
"graphistry/compute/ComputeMixin.py": 3,
"graphistry/compute/ast.py": 1,
"graphistry/compute/chain.py": 7,
"graphistry/compute/chain.py": 6,
"graphistry/compute/chain_fast_paths.py": 4,
"graphistry/compute/chain_lean_combine.py": 2,
"graphistry/compute/gfql/agg_types.py": 1,
Expand All @@ -162,12 +161,8 @@
"graphistry/compute/gfql/index/explain.py": 1,
"graphistry/compute/gfql/index/lookup.py": 3,
"graphistry/compute/gfql/index/registry.py": 5,
"graphistry/compute/gfql/index/traverse.py": 9,
"graphistry/compute/gfql/lazy/__init__.py": 7,
"graphistry/compute/gfql/lazy/engine/polars/chain.py": 7,
"graphistry/compute/gfql/lazy/engine/polars/degrees.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/hop_eager.py": 2,
"graphistry/compute/gfql/lazy/engine/polars/nan_clean.py": 2,
"graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/predicates.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/projection.py": 1,
Expand All @@ -177,9 +172,8 @@
"graphistry/compute/gfql/row/pipeline.py": 8,
"graphistry/compute/gfql/same_path/native_shortest_path.py": 1,
"graphistry/compute/gfql/temporal/constructors.py": 1,
"graphistry/compute/gfql_fast_paths.py": 17,
"graphistry/compute/gfql_fast_paths.py": 1,
"graphistry/compute/gfql_unified.py": 1,
"graphistry/compute/hop.py": 4,
"graphistry/feature_utils.py": 1,
"graphistry/layout/gib/gib.py": 1,
"graphistry/layout/gib/partitioned_layout.py": 1,
Expand Down Expand Up @@ -242,7 +236,7 @@
"graphistry/compute/gfql/cypher/_boolean_expr_text.py": 1,
"graphistry/compute/gfql/cypher/ast.py": 3,
"graphistry/compute/gfql/cypher/ast_normalizer.py": 1,
"graphistry/compute/gfql/cypher/lowering.py": 10,
"graphistry/compute/gfql/cypher/lowering.py": 8,
"graphistry/compute/gfql/cypher/parser.py": 7,
"graphistry/compute/gfql/cypher/reentry/execution.py": 2,
"graphistry/compute/gfql/cypher/reentry/flatten.py": 1,
Expand All @@ -257,11 +251,10 @@
"graphistry/compute/gfql/lazy/engine/polars/chain.py": 10,
"graphistry/compute/gfql/lazy/engine/polars/degrees.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/hop.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/hop_eager.py": 4,
"graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/projection.py": 6,
"graphistry/compute/gfql/lazy/engine/polars/reserved_columns.py": 1,
"graphistry/compute/gfql/lazy/engine/polars/row_pipeline.py": 10,
"graphistry/compute/gfql/lazy/engine/polars/row_pipeline.py": 6,
"graphistry/compute/gfql/lazy/engine/polars/varlen_rows.py": 1,
"graphistry/compute/gfql/passes/predicate_pushdown.py": 1,
"graphistry/compute/gfql/rollout.py": 1,
Expand Down
2 changes: 1 addition & 1 deletion bin/ci_cypher_surface_guard_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"max_properties": 0
}
},
"lowering_py_max_lines": 9861
"lowering_py_max_lines": 9895
}
179 changes: 179 additions & 0 deletions bin/ci_gpu_gate_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""Audit the cuDF test gates and keep the size of the unprotected surface visible.

No CI lane installs cudf or sets ``TEST_CUDF``, so every cuDF-gated test is
developer-local evidence only. This guard makes that gap loud rather than silent:

1. every cuDF gate must be attributable -- a ``reason=`` naming ``TEST_CUDF``, so
``pytest -rs`` names what was not run rather than reporting a bare ``s``;
2. every cuDF gate must read the flag from the environment, so a gate cannot
quietly become a constant;
3. ``DEVELOP.md`` must carry the unprotected-receipts note exactly while no
workflow sets ``TEST_CUDF`` -- wiring a real GPU lane retires the note, and
deleting the note without wiring a lane fails.

The audit is static: it proves the gates are well formed and counts them. It does
not and cannot prove the gated assertions are true; only a GPU lane does that.
"""
import ast
import os
import sys
from pathlib import Path
from typing import List, Optional, Tuple

REPO = Path(__file__).resolve().parent.parent
TESTS = REPO / "graphistry" / "tests"
WORKFLOWS = REPO / ".github" / "workflows"
DEVELOP = REPO / "DEVELOP.md"

FLAG = "TEST_CUDF"
UNPROTECTED_NOTE = "no CI lane executes cuDF"


class Gate:
def __init__(self, path: Path, lineno: int, source: str, reason: Optional[str]) -> None:
self.path = path
self.lineno = lineno
self.source = source
self.reason = reason

def where(self) -> str:
return f"{self.path.relative_to(REPO)}:{self.lineno}"


def _reason_of(call: ast.Call) -> Optional[str]:
for kw in call.keywords:
if kw.arg == "reason" and isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str):
return kw.value.value
return None


def _skip_message_of(call: ast.Call) -> Optional[str]:
for arg in call.args:
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
return arg.value
return _reason_of(call)


def _callee_name(call: ast.Call) -> str:
node = call.func
parts: List[str] = []
while isinstance(node, ast.Attribute):
parts.append(node.attr)
node = node.value
if isinstance(node, ast.Name):
parts.append(node.id)
return ".".join(reversed(parts))


def _gate_context(text: str, call: ast.Call, parents) -> str:
"""Source that decides the gate: the call, widened to the ``if`` that guards a bare skip."""
segment = ast.get_source_segment(text, call) or ""
node = call
while node in parents:
node = parents[node]
if isinstance(node, ast.If):
return (ast.get_source_segment(text, node.test) or "") + "\n" + segment
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module)):
break
return segment


def collect_gates(root: Path) -> Tuple[List[Gate], List[str]]:
gates: List[Gate] = []
parse_errors: List[str] = []
for path in sorted(root.rglob("*.py")):
text = path.read_text(encoding="utf-8")
if FLAG not in text:
continue
try:
tree = ast.parse(text, filename=str(path))
except SyntaxError as e:
parse_errors.append(f"{path.relative_to(REPO)}: {e}")
continue
parents = {}
for parent in ast.walk(tree):
for child in ast.iter_child_nodes(parent):
parents[child] = parent
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
name = _callee_name(node)
if not (name.endswith("skipif") or name.endswith("skip")):
continue
segment = ast.get_source_segment(text, node) or ""
if FLAG not in segment:
continue
reason = _reason_of(node) if name.endswith("skipif") else _skip_message_of(node)
gates.append(Gate(path, node.lineno, _gate_context(text, node, parents), reason))
return gates, parse_errors


def workflows_setting_flag(root: Path) -> List[str]:
if not root.is_dir():
return []
assignments = (f"{FLAG}:", f"{FLAG}=")
return sorted(
p.name for p in root.glob("*.yml")
if any(a in p.read_text(encoding="utf-8") for a in assignments)
)


def emit(line: str) -> None:
print(line)
summary = os.environ.get("GITHUB_STEP_SUMMARY")
if summary:
with open(summary, "a", encoding="utf-8") as fh:
fh.write(line + "\n")


def main() -> int:
gates, parse_errors = collect_gates(TESTS)
wired = workflows_setting_flag(WORKFLOWS)
develop = DEVELOP.read_text(encoding="utf-8")
failures: List[str] = []

failures.extend(f"test file does not parse: {e}" for e in parse_errors)

if not gates:
failures.append(
f"found zero {FLAG} gates under {TESTS.relative_to(REPO)}; the gating convention moved "
"and this audit is now blind -- update it"
)

for gate in gates:
if not gate.reason or FLAG not in gate.reason:
failures.append(
f"{gate.where()}: cuDF gate has no reason naming {FLAG}, so `pytest -rs` cannot "
f"attribute the skip: {gate.source.splitlines()[0]}"
)
if "environ" not in gate.source and "getenv" not in gate.source:
failures.append(
f"{gate.where()}: cuDF gate does not read {FLAG} from the environment, so it "
f"cannot be turned on: {gate.source.splitlines()[0]}"
)

note_present = UNPROTECTED_NOTE in develop
if wired and note_present:
failures.append(
f"workflow(s) {wired} now set {FLAG}; remove the '{UNPROTECTED_NOTE}' note from DEVELOP.md"
)
if not wired and not note_present:
failures.append(
f"no workflow sets {FLAG}, so cuDF receipts are unprotected; DEVELOP.md must say "
f"'{UNPROTECTED_NOTE}'"
)

files = sorted({str(g.path.relative_to(REPO)) for g in gates})
emit(f"cuDF gates: {len(gates)} across {len(files)} test files")
emit(f"workflows setting {FLAG}: {wired or 'NONE -- these gates are never executed by CI'}")

if failures:
for f in failures:
print(f"ERROR: {f}", file=sys.stderr)
return 1
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading