You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
9
9
<!-- Do Not Erase This Section - Used for tracking unreleased changes -->
10
10
11
11
### Added
12
+
-**Structured GFQL index-planner diagnostics**: `gfql_explain()` and the planner trace now expose a stable `decision_code` alongside the human-readable `decision_reason`. Codes distinguish policy-off scans, missing resident indexes, unsupported query shapes, missing graph bindings, cost-gate declines, selected indexes, engine mismatches, and unavailable index paths. `index_policy=force` bypasses only the cost gate for an index-coverable shape. Unsupported or unavailable shapes still scan and return the same result. Tests cover no resident index, missing graph columns, automatic cost decline, forced selection, policy-off diagnostics, and scan/index parity.
12
13
- **Static enforcement for the type-hygiene defect classes that keep failing code review**: five recurring review rejections -- unannotated function contracts, dynamic attribute access, `Any` + call-site `cast()`, unparameterized `list`/`dict`, and a bare `str` where a `Literal` belongs -- are now checked by tooling instead of by a human. `bin/ci_type_hygiene_guard.py` is a stdlib-only AST pass over `graphistry/` (tests excluded, matching `mypy.ini`) wired into `bin/lint.sh`, so it runs in the existing `python-lint-types` matrix on py3.8-3.14 with no new workflow and no new job; it takes ~1.4s and returns byte-identical counts on all seven interpreters. Enforcement is a **per-file count ratchet** against `bin/ci_type_hygiene_baseline.json` -- a file may not gain findings and a file absent from the baseline must have zero -- because the honest measurement is that the codebase carries 1704 missing annotations, 1605 `Any`-in-annotation sites and 959 `cast()` calls, and a rule that flags those as errors today is a rule nobody can turn on. Two checks are effectively hard rules rather than ratchets because their real counts are tiny: `plottable-setattr` has **3** sites repo-wide (one of them is the open bug #1825, where caching by `setattr` onto the caller's `Plottable` keyed on `id()` returned a stale answer after an in-place frame mutation), and `plottable-attr-write` -- the `param.attr = ...` form the same hazard takes when nobody writes `setattr` -- has 39, all in five files. A blanket `getattr`/`setattr` ban was measured and rejected: 300 non-test `getattr` sites are overwhelmingly legitimate optional-attribute reads, and the leak requires a *write*, so only writes onto a parameter annotated as a `Plottable` are flagged. Class 5 gets the narrowest defensible rule rather than a plausible one: `vocab-str-param` knows exactly six parameter names (`table`, `kind`, `direction`, `how`, `mode`, `engine`) whose vocabularies this repo has already committed to as `Literal` aliases, and covers 42 sites; the general "this `str` should be a `Literal`" question was prototyped as a call-site/comparison heuristic, measured at roughly 80% precision over 44 hits, and deliberately **not** shipped -- a column name is legitimately `str`, and a rule that needs manual triage every PR is worse than no rule. Ruff gains `B009`/`B010` (constant-name `getattr`/`setattr`) as genuine errors, with today's 31 offenders across 14 files seeded into `per-file-ignores` and retired one file at a time by `ruff check --fix`. Findings that are genuinely correct are annotated in place with `# hygiene-ok: <check> -- <reason>` rather than by raising a baseline cap. Conventions and escape hatches documented in DEVELOP.md "Type Hygiene Guard". No production code changed.
13
14
- **Native polars `rows(binding_ops=...)` for UNBOUNDED directed variable-length patterns (`-[*]->` / `-[*0..]->`) (#1709)**: the Cypher multi-alias bindings table already lowered natively for fixed-length and *bounded* variable-length segments, but an unbounded fixed-point segment declined with `NotImplementedError: polars engine does not yet natively support cypher row op 'rows'`. This was the last shape blocking `engine='polars'` on LDBC SNB interactive-short-6 (`MATCH (m:Message)-[:REPLY_OF*0..]->(p:Post)<-[:CONTAINER_OF]-(f:Forum)-[:HAS_MODERATOR]->(mod)`), the one interactive-short query polars could not answer. It now runs natively: a dedup-by-node frontier walk finds the exhaustion depth, then the SAME lazy bounded pair-join loop the `-[*1..k]->` arm uses materializes one row per distinct edge SEQUENCE (Cypher path multiplicity, parallel edges included) — no pandas bridge, no `to_pandas()` round trip. A cycle reachable from the seed means infinitely many paths; that raises the same E108 "require terminating variable-length segments" error pandas raises — same exception class and same `.code` on both engines — and is detected before the path expansion blows up rather than after, bounded by the REACHABLE node count so an unreachable remainder of the graph costs nothing. Still declining honestly (NIE, never a silent answer): UNDIRECTED unbounded (`-[*]-`, needs the min_hops == 1 multiplicity reconstruction plus backtrack-aware termination — pandas rejects it outright), aliased variable-length relationships (pandas rejects those too), unbounded segments WITHOUT `to_fixed_point` (pandas silently truncates at a bound this lowering cannot reconstruct), unbounded segments with `min_hops >= 2` (`-[*2..]->`: pandas' step pairs are pruned by min_hops against a dedup-by-node eccentricity, which this raw-edge reconstruction cannot reproduce — serving it would return a different count with no error), and `to_fixed_point` combined with an explicit bound (declined on master too; it is not Cypher-reachable, since the parser only sets the flag for `*` / `*k..` where there is no maximum, but through the AST surface it hits the same reconstruction gap for `min_hops >= 3`). Cross-engine parity is the gate: differential fuzz vs the pandas oracle over random DAGs and cyclic graphs with self-loops and parallel edges, plus pinned IS6/zero-hop/multiplicity/cycle tests.
0 commit comments