Release Process
This document describes GraphForge’s professional versioning and release process.
Table of Contents
Section titled “Table of Contents”- Versioning Strategy
- Release Types
- Release Checklist
- Creating a Release
- Post-Release Tasks
- Hotfix Process
- Pre-release Versions
Versioning Strategy
Section titled “Versioning Strategy”GraphForge follows Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH
Example: 1.2.3 │ │ └─── PATCH: Backwards-compatible bug fixes │ └───── MINOR: New features, backwards-compatible └─────── MAJOR: Breaking changesVersion Increments
Section titled “Version Increments”MAJOR version (1.0.0 → 2.0.0) when:
- Breaking API changes
- Incompatible changes to data formats
- Removal of deprecated features
- Changes requiring user migration
Examples:
- Changing column naming from
col_0tocolumn_0 - Removing deprecated methods
- Changing SQLite schema in incompatible way
MINOR version (1.2.0 → 1.3.0) when:
- New features (backwards-compatible)
- New Cypher clauses (OPTIONAL MATCH, UNWIND)
- Performance improvements
- Deprecations (with backward compatibility)
Examples:
- Adding OPTIONAL MATCH support
- Adding variable-length paths
- New aggregation functions
- Enhanced error messages
PATCH version (1.2.3 → 1.2.4) when:
- Bug fixes (backwards-compatible)
- Documentation updates
- Internal refactoring with no API changes
- Security patches
Examples:
- Fixing column naming regression
- Fixing SKIP/LIMIT empty results
- Documentation typos
- Dependency security updates
Pre-1.0.0 Versions
Section titled “Pre-1.0.0 Versions”GraphForge is currently in 0.x.x (pre-1.0.0):
- 0.x.0: Minor features, may include breaking changes
- 0.x.y: Bug fixes and patches
- Breaking changes are allowed before 1.0.0
Version 1.0.0 will be released when:
- API is stable and battle-tested
- Core openCypher features complete
- TCK compliance > 50%
- Production-ready with documented migration path
Release Types
Section titled “Release Types”Regular Release (Minor/Patch)
Section titled “Regular Release (Minor/Patch)”Timeline: Every 2-4 weeks
Criteria:
- All tests passing
- No critical bugs
- Documentation updated
- CHANGELOG.md updated
Major Release
Section titled “Major Release”Timeline: When breaking changes are necessary
Additional criteria:
- Migration guide written
- Deprecation warnings in previous release
- Community notification (if applicable)
- Extended testing period
Hotfix Release
Section titled “Hotfix Release”Timeline: As soon as possible
Criteria:
- Critical bug or security issue
- Fixes only, no new features
- Fast-tracked review process
Release Checklist
Section titled “Release Checklist”Pre-Release (1-2 days before)
Section titled “Pre-Release (1-2 days before)”- All planned features merged to
main - Verify Apache-2.0 metadata and distributed copies:
python3 scripts/license_check.py --report license-compliance-report.json - All CI checks passing
- All tests passing (351 unit + integration tests)
- Code coverage ≥ 81% (current baseline)
- No open critical bugs
- Dependencies up to date
- Security scan clean (bandit)
Documentation
Section titled “Documentation”- Update
CHANGELOG.mdwith all changes since last release - Update version in
pyproject.toml - Update version references in docs if changed
- Update README.md if major changes
- Update
docs/project-status-and-roadmap.md - Review all documentation for accuracy
Testing
Section titled “Testing”- Run full test suite locally:
pytest tests/ - Test on Python 3.10, 3.11, 3.12, 3.13
- After merging, when assembling publication evidence for the v0.5.0 publication close-out issue,
dispatch
Binding Release Candidatewith the exact currentmaincommit SHA; branch-head and stale SHA dispatches are rejected before the platform matrix starts - Confirm its Linux, macOS, and Windows Python/Node targets and cross-built Node package checks produce a passing aggregate artifact
- Link that same-SHA workflow run and aggregate artifact from the release close-out record (the v0.5.0 publication close-out issue)—not as a close ritual for child issues
- Run the standardized XS-XL matrix with
make release-load-matrixon the release-candidate SHA and retain the accepted evidence bundle in the publication record - After the exact-SHA Rust surface and Binding Release Candidate runs pass,
dispatch
M22 Non-Cypher Surface Gatewith the currentmainSHA and those two immutable run IDs. Confirm its 144-case load job and finalM22-Non-Cypher-Surface-Gate-<sha>aggregate artifact for the v0.5.0 publication close-out issue publication readiness. This cascade is release certification; it is not a close criterion for child implementation or construction issues (seeAGENTS.md§ Issue close) - Test installation from built package:
uv build && uv tool install dist/graphforge-*.whl - Manual smoke tests with examples
- Test upgrade path from previous version (if applicable)
Release
Section titled “Release”- Confirm the release commit contains
Apache-2.0metadata,LICENSE,NOTICE,legal/THIRD_PARTY_NOTICES.md(and packaging copies), a greencargo deny check licenses, and a passing SHA-bound license compliance report - Create release branch:
release/vX.Y.Z - Update version in
pyproject.toml - Update
CHANGELOG.md: Move [Unreleased] to [X.Y.Z] - Commit:
chore(release): bump version to X.Y.Z - Push release branch
- Create PR to
main - Get PR approval
- Merge to
main - Create Git tag:
git tag -a vX.Y.Z -m "Release version X.Y.Z" - Push tag:
git push origin vX.Y.Z - Create GitHub Release with release notes
- Verify PyPI publish workflow succeeds
- Verify package on PyPI: https://pypi.org/project/graphforge/
Post-Release
Section titled “Post-Release”- Announce release (if applicable)
- Close milestone (if using milestones)
- Create next milestone
- Update project boards
- Tweet/blog post (if major release)
Creating a Release
Section titled “Creating a Release”For v0.5.0 registry sequencing, stop conditions, and rollback, follow
publication-order.md (authoritative for §5/§6).
Step-by-Step Guide
Section titled “Step-by-Step Guide”1. Prepare Release Branch
Section titled “1. Prepare Release Branch”# Ensure main is up to dategit checkout maingit pull origin main
# Create release branchgit checkout -b release/v0.2.02. Update Version
Section titled “2. Update Version”Edit pyproject.toml:
[project]name = "graphforge"version = "0.2.0" # ← Update this3. Update CHANGELOG.md
Section titled “3. Update CHANGELOG.md”Move unreleased changes to new version section:
## [Unreleased]
<!-- Empty or new features in development -->
## [0.2.0] - 2026-02-15
### Added- Variable-length path support (`[*1..3]`)- OPTIONAL MATCH for left outer joins
### Changed- Improved error messages with error codes
### Fixed- Edge case in aggregation handling
[Unreleased]: https://github.com/CurateLabs/graphforge-legecy/compare/v0.2.0...HEAD[0.2.0]: https://github.com/CurateLabs/graphforge-legecy/compare/v0.1.1...v0.2.04. Commit and Push
Section titled “4. Commit and Push”# Commit changesgit add pyproject.toml CHANGELOG.mdgit commit -m "chore(release): bump version to 0.2.0"
# Push release branchgit push -u origin release/v0.2.05. Create and Merge PR
Section titled “5. Create and Merge PR”# Create PRgh pr create --title "Release v0.2.0" --body "Release version 0.2.0
See CHANGELOG.md for details."
# Wait for CI to pass and get approval
# Merge (squash not recommended for release PRs)gh pr merge --merge6. Create Git Tag
Section titled “6. Create Git Tag”# Switch to main and pullgit checkout maingit pull origin main
# Create annotated taggit tag -a v0.2.0 -m "Release version 0.2.0
## What's Changed- Variable-length path support- OPTIONAL MATCH clause- Improved error messages
See CHANGELOG.md for full details."
# Push taggit push origin v0.2.07. Create GitHub Release
Section titled “7. Create GitHub Release”# Create release using GitHub CLIgh release create v0.2.0 \ --title "GraphForge v0.2.0" \ --notes-file <(cat <<'EOF'## What's New in v0.2.0
### 🚀 New Features
- **Variable-Length Paths**: Query transitive relationships with `[*1..3]` syntax- **OPTIONAL MATCH**: Left outer join semantics for optional patterns
### 🔧 Improvements
- Enhanced error messages with error codes- Better TCK compliance (now at 25%)
### 🐛 Bug Fixes
- Fixed edge case in aggregation handling
### 📦 Installation
```bashpip install graphforge==0.2.0# oruv add graphforge==0.2.0📖 Documentation
Section titled “📖 Documentation”See CHANGELOG.md for complete details.
🙏 Contributors
Section titled “🙏 Contributors”Thanks to all contributors who made this release possible! EOF )
Or use the GitHub web interface:1. Go to https://github.com/CurateLabs/graphforge-legecy/releases2. Click "Draft a new release"3. Choose tag: `v0.2.0`4. Release title: `GraphForge v0.2.0`5. Copy release notes from CHANGELOG6. Check "Set as the latest release"7. Click "Publish release"
#### 8. Verify Publication
```bash# Wait for publish workflow to completegh run list --workflow=publish.yaml --limit 1
# Check PyPIopen https://pypi.org/project/graphforge/
# Test installationpip install graphforge==0.2.0Native binding parity evidence
Section titled “Native binding parity evidence”Every pull request that changes a binding or the Rust facade runs the required Linux
Python Binding and Node Binding jobs in test.yml. Those jobs build the wheel and addon
from the checked-out SHA, install or load those exact artifacts, run the native non-Cypher
matrix, and upload a JSON report containing the source SHA, package version, artifact hash,
classification hash, exact test identities, outcomes, and sanitized parity diff. The policy
job independently rejects an unclassified Rust, PyO3, or napi surface change.
The release-only publish.yaml expands the same native sources across the supported package
targets:
| Package | Release build coverage |
|---|---|
| Python abi3 wheel | Linux manylinux, macOS, Windows (CPython 3.10+) |
| Node addon | macOS x64/arm64, Linux x64/arm64, Windows x64 |
The release matrix is the omission list: adding a supported target requires adding it to the
workflow matrix and the package manifest together. Cross-platform publication does not
replace the required Linux behavior tests; it consumes the contract already proven on the
pull-request SHA. Final release readiness additionally requires clean-install evidence for
the published artifacts under the v0.5.0 release gate (#742 section 7 / #2795). Run the harness in
clean-environment-verification.md (make clean-env-verify
or workflow_dispatch Clean Environment Verify) only after public registry publication;
preflight fails closed when packages are missing.
Post-Release Tasks
Section titled “Post-Release Tasks”1. Verify Package
Section titled “1. Verify Package”# Install in fresh environmentuv venv test-envsource test-env/bin/activateuv pip install graphforge==0.2.0
# Run quick testpython -c "from graphforge import GraphForge; print(GraphForge.__name__)"2. Update Documentation Sites
Section titled “2. Update Documentation Sites”If you have external docs:
- Update version in docs deployment
- Regenerate API documentation
- Update “latest” links
3. Announce Release
Section titled “3. Announce Release”GitHub Discussions:
# GraphForge v0.2.0 Released! 🎉
We're excited to announce the release of GraphForge v0.2.0!
## Highlights- Variable-length path queries- OPTIONAL MATCH support- Improved error handling
Full details: https://github.com/CurateLabs/graphforge-legecy/releases/tag/v0.2.0Twitter/Social Media (if applicable):
🎉 GraphForge v0.2.0 is out!
✨ Variable-length paths: [*1..3]🔍 OPTIONAL MATCH support🐛 Bug fixes and improvements
📦 pip install graphforge==0.2.0
#Python #GraphDatabase #OpenCypherHotfix Process
Section titled “Hotfix Process”For critical bugs in production release:
1. Create Hotfix Branch
Section titled “1. Create Hotfix Branch”# Branch from the release taggit checkout -b hotfix/v0.2.1 v0.2.0
# Or from main if it's already aheadgit checkout -b hotfix/v0.2.1 main2. Fix the Bug
Section titled “2. Fix the Bug”# Make minimal changes to fix the issuegit commit -m "fix: critical bug in query execution"3. Update Version and CHANGELOG
Section titled “3. Update Version and CHANGELOG”# Update pyproject.toml to 0.2.1# Update CHANGELOG.md with hotfix details
git commit -m "chore(release): bump version to 0.2.1"4. Fast-Track Review
Section titled “4. Fast-Track Review”# Create PR with "hotfix" labelgh pr create --title "Hotfix v0.2.1: Fix critical bug" \ --label hotfix
# Get expedited review and mergegh pr merge --merge5. Release Immediately
Section titled “5. Release Immediately”# Create tag and releasegit tag -a v0.2.1 -m "Hotfix release v0.2.1"git push origin v0.2.1
gh release create v0.2.1 \ --title "GraphForge v0.2.1 (Hotfix)" \ --notes "## Critical Bug Fix
Fixed critical bug in query execution that caused...
This is a hotfix release. All users on v0.2.0 should upgrade immediately."Pre-release Versions
Section titled “Pre-release Versions”For testing before official release:
Alpha Releases
Section titled “Alpha Releases”Purpose: Early testing, unstable
Version format: X.Y.Z-alpha.N (e.g., 0.3.0-alpha.1)
# Update version in pyproject.tomlversion = "0.3.0-alpha.1"
# Create taggit tag -a v0.3.0-alpha.1 -m "Alpha release for testing"git push origin v0.3.0-alpha.1
# Create pre-release on GitHub (check "This is a pre-release")gh release create v0.3.0-alpha.1 --prerelease \ --title "GraphForge v0.3.0-alpha.1" \ --notes "⚠️ Alpha release for testing only. Not for production use."Beta Releases
Section titled “Beta Releases”Purpose: Feature complete, needs testing
Version format: X.Y.Z-beta.N (e.g., 0.3.0-beta.1)
version = "0.3.0-beta.1"Release Candidates
Section titled “Release Candidates”Purpose: Final testing before release
Version format: X.Y.Z-rc.N (e.g., 0.3.0-rc.1)
version = "0.3.0-rc.1"Automation Tools
Section titled “Automation Tools”Version Bumping Script
Section titled “Version Bumping Script”Create scripts/bump-version.py:
#!/usr/bin/env python3"""Bump version in pyproject.toml"""import sysimport refrom pathlib import Path
def bump_version(bump_type): pyproject = Path("pyproject.toml") content = pyproject.read_text()
# Find current version match = re.search(r'version = "(\d+)\.(\d+)\.(\d+)"', content) if not match: print("Error: Could not find version in pyproject.toml") sys.exit(1)
major, minor, patch = map(int, match.groups())
# Bump version if bump_type == "major": major += 1 minor = 0 patch = 0 elif bump_type == "minor": minor += 1 patch = 0 elif bump_type == "patch": patch += 1 else: print(f"Error: Invalid bump type '{bump_type}'") sys.exit(1)
new_version = f"{major}.{minor}.{patch}"
# Replace version new_content = re.sub( r'version = "\d+\.\d+\.\d+"', f'version = "{new_version}"', content )
pyproject.write_text(new_content) print(f"Version bumped to {new_version}") return new_version
if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: bump-version.py [major|minor|patch]") sys.exit(1)
bump_version(sys.argv[1])Usage:
python scripts/bump-version.py patch # 0.1.1 → 0.1.2python scripts/bump-version.py minor # 0.1.1 → 0.2.0python scripts/bump-version.py major # 0.1.1 → 1.0.0Best Practices
Section titled “Best Practices”- Follow semantic versioning strictly
- Update CHANGELOG.md for every release
- Test thoroughly before releasing
- Create annotated git tags with release notes
- Use GitHub Releases for visibility
- Announce breaking changes in advance
- Keep release notes user-focused
- Test package installation after publish
DON’T ❌
Section titled “DON’T ❌”- Skip version numbers (0.1.1 → 0.1.3)
- Make breaking changes in patch releases
- Release without updating CHANGELOG
- Push to PyPI without testing
- Create release tags from non-main branches (except hotfixes)
- Reuse or delete version tags
- Release on Fridays (avoid weekend emergencies)
- Rush releases without proper testing
Troubleshooting
Section titled “Troubleshooting”Release Failed to Publish to PyPI
Section titled “Release Failed to Publish to PyPI”# Check workflow logsgh run view --log
# Manually publish if needed (requires PyPI token)uv builduv publishWrong Version Published
Section titled “Wrong Version Published”Cannot unpublish from PyPI! You can only:
- Yank the release (marks it as unsuitable)
- Publish a new patch version with fixes
# On PyPI web interface, click "Yank" for the bad version# Then release a new versionTag Already Exists
Section titled “Tag Already Exists”# Delete local taggit tag -d v0.2.0
# Delete remote tag (careful!)git push origin :refs/tags/v0.2.0
# Create correct taggit tag -a v0.2.0 -m "Release version 0.2.0"git push origin v0.2.0Quick Reference
Section titled “Quick Reference”Check Current Version
Section titled “Check Current Version”grep 'version =' pyproject.tomlList All Releases
Section titled “List All Releases”gh release listgit tag -lView Release Details
Section titled “View Release Details”gh release view v0.1.1git show v0.1.1Compare Versions
Section titled “Compare Versions”git diff v0.1.0..v0.1.1gh compare v0.1.0...v0.1.1Related Documentation
Section titled “Related Documentation”Last Updated: 2026-02-01