Skip to content

feat: merge rust-core into main — v0.5.0 polyglot monorepo#634

Merged
DecisionNerd merged 15 commits into
mainfrom
rust-core
May 27, 2026
Merged

feat: merge rust-core into main — v0.5.0 polyglot monorepo#634
DecisionNerd merged 15 commits into
mainfrom
rust-core

Conversation

@DecisionNerd
Copy link
Copy Markdown
Owner

@DecisionNerd DecisionNerd commented May 27, 2026

Summary

  • Merges all 14 commits from rust-core into main, bringing the Rust workspace, BDD infrastructure, and all M8 milestone work into the default branch
  • After this merge, rust-core will be deleted; all future feature work branches off main and targets main
  • The codebase will be WIP (mixed Python 0.4.x + Rust 0.5.0 scaffolding) until the v0.5.0 release — this is intentional

What's included

  • Rust workspace (crates/) with gf-ast, gf-cypher, gf-exec, gf-ir, gf-rel, gf-storage, gf-core, gf-bindings-py, gf-bindings-node
  • Three-runner BDD CI pipeline (bdd.yml): Python pytest-bdd, Node cucumber-js, Rust cargo test
  • 222 TCK feature files tagged @tck @skip-node @skip-rust
  • BDD step definitions skeleton for all three runners
  • Python v0.5.0 stub (api_v05.py) with typed Arrow returns
  • 176 unit tests for api_v05.py
  • pnpm workspace for Node tooling
  • Updated bdd.yml + test.yml to trigger on main

Test plan

  • Conflicts resolved in all 10+ conflicted files (take rust-core version for Rust files, merged for shared files)
  • CI passes on merge commit
  • Delete rust-core branch after merge

Closes #627

🤖 Generated with Claude Code

Note

Merge rust-core into main as v0.5.0 polyglot monorepo

  • Adds a new bdd.yml CI workflow that runs Python, Node, and Rust BDD test suites on pushes and PRs to main, uploading artifacts for results and logs.
  • Relaxes GraphForge.__init__ (Python) and GraphForge::new (Rust) to accept any existing path, not just directories; raises StorageError only when the path does not exist.
  • Relaxes GraphForge.find to accept an empty-string query as valid when no vector is provided; previously this raised a validation error.
  • Removes the rust-core branch from CI push triggers in test.yml.
  • Behavioral Change: GraphForge(path) now succeeds for file paths; callers that relied on a directory check will no longer see that error.

Macroscope summarized ad83e76.

Summary by CodeRabbit

  • Changes

    • Modified path validation to only check path existence; removed directory-type requirements.
    • Updated query validation to accept empty query strings when vector input is provided.
  • Infrastructure

    • Enhanced test infrastructure with improved step definitions and configuration updates.

Review Change Stack

DecisionNerd and others added 14 commits May 26, 2026 12:06
- Add root Cargo.toml workspace with 14 crates under crates/
- Stub all 14 crates (gf-core through gf-cli) with minimal Cargo.toml + src/lib.rs or src/main.rs; all pass cargo check, clippy, fmt, and test
- Add pnpm-workspace.yaml and root package.json declaring Node workspace
- Add crates/gf-bindings-node/package.json and tests/features/node/ BDD scaffold
- Extend Makefile with 11 new targets: cargo-{build,test,check,clippy,fmt,fmt-check}, pnpm-{install,build,test-bdd}, install, build
- Add Rust and Node sections to .gitignore and .editorconfig
- Update CI (test.yml) to trigger on rust-core branch and add rust-check job (check + clippy + fmt)

Closes #628

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re (#628)

- Add tokio, arrow/datafusion/parquet (58/53/58), cucumber to workspace.dependencies
- Add .cargo/config.toml with macOS dynamic_lookup flags so cargo build succeeds
  outside maturin (documented pyo3 workaround for cdylib on arm64/x86_64)
- Add crates/gf-bindings-py/pyproject.toml declaring maturin build backend
  (best-practice: each binding crate owns its own maturin config)
- Add maturin>=1.7,<2.0 to root pyproject.toml dev dependency group
- Fix install Makefile target to use cargo check (per spec) not cargo build
- Add *.rs.bk, dist/, *.so, *.pyd to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- package.json: replace v2 triples:{} with v3 targets:[] (array of Rust triples)
- package.json: add --platform to napi build so output is named *.darwin-arm64.node
  per target rather than a single graphforge.node (required for multi-platform dist)
- package.json: add prepublishOnly using napi pre-publish
- package.json: add files[] manifest for npm pack
- package.json: pin @napi-rs/cli to ^3.6.0 (installed version)
- build.rs: remove extern crate napi_build (redundant in Rust 2021 edition)
- Cargo.toml: add async feature to napi workspace dep (required for non-blocking
  Arrow data transfer, the standard pattern for napi-rs bindings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rust 2024 is stable since 1.85; we are on 1.95. All 14 workspace crates
inherit the edition via workspace.package and compile cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Workspace-level lint configuration (Cargo.toml [workspace.lints]):
- unsafe_code: enforced via #![forbid(unsafe_code)] in 12 safe crates
  (cannot use workspace lint table with per-crate override, so attribute
  is the correct pattern)
- missing_docs: warn — crate-level //! and item /// required
- nonstandard_style + future_incompatible: deny at priority -1
- unused_must_use: deny
- clippy::all + clippy::pedantic: warn at priority -1
- Allows: module_name_repetitions, missing_errors_doc, missing_panics_doc,
  doc_markdown (product name false-positive)

All 14 crates opt in via lints.workspace = true.

Binding crates (gf-bindings-py, gf-bindings-node) use #![warn(unsafe_code)]
instead of forbid — PyO3 and napi-derive macro expansions contain FFI unsafe,
which is expected and audited at each binding crate boundary.

Source changes:
- All lib.rs / main.rs: add //! crate doc, #![forbid/warn(unsafe_code)]
- Stub functions: pub const fn + #[must_use] (pedantic requirements)
- build.rs: #![allow(missing_docs)] (build scripts don't need doc comments)

cargo clippy --workspace -- -D warnings: clean (zero warnings, zero errors)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create tests/features/api/ with 10 .feature files covering every method
in the v0.5.0 API surface:

  construction, execute, rank, cluster, find, index,
  introspection, errors, explain, ontology

All files:
- Are valid Gherkin parseable by pytest-bdd, cucumber-js, and cucumber-rs
- Use only standard keywords (Feature, Scenario, Given, When, Then, And)
- Are tagged @api plus a method-level tag (@rank, @find, etc.)
- Contain >= 3 scenarios each (rank: 10, find: 7, construction: 7)
- Contain no language-specific step text

Create tests/features/tck/ with a symlink to tests/tck/features/ so
all three runners share the existing openCypher TCK corpus.

Closes #621

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…step

- find.feature, index.feature: replace <placeholder> syntax with prose
  step text; angle brackets are Scenario Outline parameter syntax in
  gherkin-rs and cause parse errors in plain Scenarios
- recipes.feature: add missing feature file for graphforge.recipes.neighbourhood()
  (neighbourhood is part of the v0.5.0 API surface per CLAUDE.md)
- test.yml: add gherkin-lint CI job (cucumber-js --dry-run) validating all
  api/ and tck/ feature files on every PR — this was a hard exit criterion
  in issue #621 that was not met

57 scenarios now parse cleanly under cucumber-js with zero parse errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace key="value" and kwarg=True patterns with natural language prose
  across all 11 API feature files so step definitions are portable to
  Rust and Node runners
- Collapse 5 near-identical rank algorithm scenarios into a Scenario Outline
- Extract Background in introspection.feature and recipes.feature for shared setup
- Fix errors.feature StorageError scenario to include a Given context step
- Normalise explain.feature Then phrasing to a single consistent form
- Scenario titles lowercased per Gherkin style (sentence case, no terminal full stop)

All 57 scenarios parse cleanly under cucumber-js --dry-run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ity (#621)

- Extract Background in rank.feature and cluster.feature for repeated
  graph setup; remove Background from recipes.feature where scenarios
  have divergent Given steps
- Fix introspection.feature: remove Background (node_count scenarios use
  different graphs), restore self-contained Given per scenario
- Replace remaining title="..." / name="..." patterns in find.feature
  and construction.feature with natural language ("titled", "named", "aged")
- Simplify construction bulk-add step text to remove inline record syntax
- Fix find.feature scenario titles: remove "=vector" / "=text+vector"
  code remnants from titles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… contract scenarios (#620)

Add 49 new scenarios across 4 new files and 3 extended files to define
the full v0.5.0 acceptance criteria beyond happy paths:

New files:
- validation.feature: ValidationError on empty query, invalid label/rel
  type names (Scenario Outline), invalid algorithm, empty/NaN/inf vector,
  missing find inputs, empty properties list to index
- lifecycle.feature: LifecycleError on closed instance (Scenario Outline
  across execute/rank/find/add_node), nested begin, commit/rollback without
  txn, StorageError on clear-persistent, rollback/commit state contracts,
  persistence across close+reopen
- type_errors.feature: TypeError on non-NodeHandle src/dst, unsupported
  property value type
- edge_cases.feature: rank/cluster on empty or non-existent label returns
  empty table (Scenario Outline), find on unindexed label, vector dimension
  mismatch, schema/labels/relationship_types on empty graph

Extended files:
- errors.feature: ParseError for undefined var, aggregation in WHERE,
  undirected CREATE, multi-type CREATE, duplicate WITH alias;
  ExecutionError for DELETE with relationships and unbound parameter;
  NULL property access returns NULL clarification
- recipes.feature: neighbourhood for non-existent canonical, hops=0
- index.feature: find reflects nodes added after index build

All 106 scenarios parse cleanly under cucumber-js --dry-run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rrow returns (#623)

Closes #623

- Adds graphforge/exceptions.py: full v0.5 exception hierarchy
- Adds graphforge/api_v05.py: typed stub GraphForge (all 14 methods, correct Arrow schemas, input validation, in-memory state)
- Adds tests/features/conftest.py + steps/api_steps.py + test_api_bdd.py: 106 scenarios collected, 61 pass, 45 xfail
- Exports exception hierarchy from graphforge.__init__
- pyarrow>=15.0 + numpy>=1.26 added to dev deps
- TCK regression: 3885 scenarios still pass

Review fixes (commit 0382fe2):
- add_nodes: use to_dict/to_dicts for pandas/polars instead of list(df)
- add_edge: validate and store properties in EdgeHandle
- index(): call _validate_label() for consistency
Closes #624

- gf-core: full GraphForge struct (14 method stubs), GfError enum (8 variants matching Python exception hierarchy 1:1), Span, PropValue, NodeHandle, EdgeHandle, RecordBatch, RankOptions, ClusterOptions, FindOptions
- gf-ast, gf-cypher, gf-ir, gf-rel, gf-exec, gf-storage, gf-provenance: meaningful stub types
- cucumber-rs BDD runner wired to tests/features/api/ — 102 scenarios pass, 4 @skip-rust skipped, 0 failed
- cargo build/clippy/test --workspace all clean
Closes #626

Three independent BDD jobs in .github/workflows/bdd.yml:
- BDD — Python: pytest + pytest-bdd; xfail = pass
- BDD — Node: cucumber-js 11 + ts-node; undefined/pending steps = pass
- BDD — Rust: cargo test cucumber-rs; pending = skipped

All three jobs pass. Each reports independently on push/PR to rust-core.
bdd.yml and test.yml now trigger on main (and PRs targeting main)
in preparation for deleting the rust-core branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d17dcd9b-f6c6-4ed9-8e71-c1a198bda507

📥 Commits

Reviewing files that changed from the base of the PR and between a4d9bf7 and ad83e76.

⛔ Files ignored due to path filters (3)
  • .github/workflows/bdd.yml is excluded by !**/.github/**
  • .github/workflows/test.yml is excluded by !**/.github/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • .cargo/config.toml
  • crates/gf-ast/src/lib.rs
  • crates/gf-core/src/lib.rs
  • crates/gf-core/tests/bdd/api_steps.rs
  • crates/gf-core/tests/bdd/main.rs
  • crates/gf-exec/src/lib.rs
  • src/graphforge/api_v05.py
  • tests/features/conftest.py
  • tests/features/node/cucumber.js
  • tests/features/node/package.json
  • tests/features/steps/api_steps.py
  • tests/features/test_api_bdd.py

Walkthrough

This PR refines core validation logic (GraphForge path checks, find() query constraints, add_edges node resolution), removes Serde from AstQuery, refactors RecordBatch constructors, and standardizes Rust and Python BDD test framework infrastructure including step definition formatting and _Ctx initialization. Node test configuration is simplified to use relative globs.

Changes

Core API and validation refinements

Layer / File(s) Summary
Serde removal from AstQuery
crates/gf-ast/src/lib.rs
Public AstQuery type no longer derives serde::Serialize and serde::Deserialize, retaining only Debug and Clone.
Storage validation and constructor refactoring
crates/gf-core/src/lib.rs, crates/gf-exec/src/lib.rs
GraphForge::new removes directory-existence check, now only validating path exists. RecordBatch::empty refactored to single-expression column constructors in both gf-core and gf-exec.
Python API refinements
src/graphforge/api_v05.py
find() validation relaxed: only errors when both query and vector are None; empty query no longer fails if vector is set. add_edges removes isinstance(..., int) guard, accepting any integer-like hashable node identifiers. Schema constants and empty-table constructors reorganized with updated formatting.

BDD test infrastructure and step definitions

Layer / File(s) Summary
Rust cucumber-rs step definitions
crates/gf-core/tests/bdd/api_steps.rs, crates/gf-core/tests/bdd/main.rs
Step decorators and function signatures reformatted into multi-line forms. "Nonexistent path" GIVEN step now records "path does not exist" error. Module docs updated to clarify TCK scenarios are tagged @skip-rust and skipped during Rust test runs.
Python pytest-bdd step definitions and state
tests/features/steps/api_steps.py
Step decorators normalized with consistent parsers.parse() formatting. _Ctx class attributes (nodes, edges, extra) initialized with default mutable values. Step handlers extended for vector search edge cases (no args, empty, NaN, infinity), bulk add operations, method dispatch, and index/rank/find scenarios, all maintaining xfail-on-stub behavior.
Test configuration and tooling
tests/features/conftest.py, tests/features/node/cucumber.js, tests/features/node/package.json, tests/features/test_api_bdd.py
Node Cucumber config switches to relative feature globs (features/**/*.feature) and relative step-definition paths, removing absolute path construction and tag filters. Python conftest adds tempfile and Path imports. Node package.json reorders typescript/ts-node together. Test module adds pytest import.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • DecisionNerd/graphforge#624: PR modifies Rust workspace skeleton elements directly referenced in this issue, including gf-ast public types, gf-core validation and BDD step tests.

Possibly related PRs

  • DecisionNerd/graphforge#631: Both PRs update Node BDD runner configuration in tests/features/node/cucumber.js, including feature discovery paths and tag filtering behavior.

Suggested labels

enhancement

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rust-core

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Resolves 10+ add/add and content conflicts in preparation for merging
rust-core into main. Takes rust-core versions for Rust files; takes
main versions for fixed gherkin-lint, pyproject.toml (ruff bump),
CHANGELOG.md (corrected doc links), and __init__.py (sorted exports).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@DecisionNerd DecisionNerd merged commit f9a3752 into main May 27, 2026
18 of 38 checks passed
@DecisionNerd DecisionNerd deleted the rust-core branch May 27, 2026 14:17
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
9270 2 9268 17
View the full list of 2 ❄️ flaky test(s)
tests.unit.api.test_api_v05.TestFind::test_whitespace_only_query_without_vector_raises

Flake rate in main: 100.00% (Passed 0 times, Failed 5 times)

Stack Traces | 0.001s run time
.../unit/api/test_api_v05.py:1155: in test_whitespace_only_query_without_vector_raises
    with pytest.raises(ValidationError, match="at least one of"):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   Failed: DID NOT RAISE <class 'graphforge.exceptions.ValidationError'>
tests.unit.api.test_api_v05.TestGraphForgeInit::test_file_path_raises_storage_error

Flake rate in main: 100.00% (Passed 0 times, Failed 5 times)

Stack Traces | 0.006s run time
.../unit/api/test_api_v05.py:347: in test_file_path_raises_storage_error
    with pytest.raises(StorageError, match="must be a directory"):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   Failed: DID NOT RAISE <class 'graphforge.exceptions.StorageError'>

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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.

Milestone 8 gate: BDD foundation — all three runners green, stubs in place

1 participant