Canonical documentation for Expanse packaging, distribution channels, and automated release workflows. Architecture: ARCHITECTURE.md · CI Pipeline: CI.md · C ABI Parity: COMPAT.md
Expanse targets multiple ecosystems: native Rust crates on crates.io, multi-arch dynamic libraries and .deb/.rpm packages on Linux, drop-in DLLs and vcpkg/NuGet packaging on Windows, and universal dynamic libraries on macOS. Publication status varies by registry — see the per-ecosystem sections below. As of this writing crates.io/npm/PyPI publishing is wired in the release workflow; the .NET Orieg.Expanse NuGet package is wired but not yet landed (nuget.org returns 404), and Java/Maven Central is not yet built or published (no release-workflow job exists for it).
The entire release process is automated via .github/workflows/release.yml upon pushing a version tag.
The pipeline is anchor-first (#313): the GitHub Release is the canonical anchor, created as soon as the gate and core artifacts succeed; every registry publish depends on it and runs afterwards, independently and re-runnably. A registry can therefore never carry a version the release page does not.
graph TD
A[1. Synchronize Versions via scripts/bump_version.py] --> C[2. Commit & Create Git Tag 'vX.Y.Z']
C --> D[3. Push Tag to GitHub: git push origin vX.Y.Z]
D --> E[Phase 1: release-gate - polls CI Gate rollup + version lockstep]
E --> F[build-release-artifacts: multi-arch C ABI + .deb/.rpm]
E --> G[build-npm: platform addons]
F --> H[Phase 2: github-release - THE CANONICAL ANCHOR - assets + SHA256SUMS]
H --> I[Phase 3: publish-crates - crates.io]
H --> J[Phase 3: publish-npm - npm OIDC]
H --> K[Phase 3: package-nuget - NuGet.org OIDC]
H --> L[Phase 3: publish-pages - apt/rpm portal]
H --> M[Phase 3: publish-pypi - python.yml dispatched by github-release]
G --> J
Release policies (post-v0.4.0 incident, #313):
- Single anchor: no workflow publishes straight off a tag push; all channels
needs: github-release, and PyPI (python.yml) is dispatched bygithub-releasewith the release tag (aGITHUB_TOKEN-created release emits noreleaseevent to other workflows, so an event trigger alone can never fire from the pipeline). - Forward-only versions: once any registry publish succeeds, that version is spent — registries are immutable. Never re-push or move a tag; fix forward (
vX.Y.Z+1). - Independent recovery: a failed Phase-3 channel is re-run individually; the anchor and sibling channels are unaffected.
- Canary first: run the release workflow via
workflow_dispatch(dry_run: true) to exercise the gate, builds, packaging, and page generation with every outward publish skipped — before pushing a real tag.
- Multi-Ecosystem Version Bump:
- Run
python3 scripts/bump_version.py <NEW_VERSION>to synchronize version numbers across all 10 manifests (Cargo.toml,pyproject.toml,package.json,.csproj,pom.xml,build.gradle, etc.) and regenerateCargo.lock. - Verify lockstep sync:
python3 scripts/bump_version.py --check.
- Run
- Release Notes (automatic — no CHANGELOG file is maintained):
- The GitHub Release generates its notes from merged PR titles, grouped by the label categories in
.github/release.yml. Conventional-commit PR titles keep them readable; label PRs (enhancement,bug,performance,documentation,ci, …) for correct grouping.
- The GitHub Release generates its notes from merged PR titles, grouped by the label categories in
- Commit & Tag:
git commit -am "chore(release): prepare v0.4.0" git tag -a v0.4.0 -m "Release v0.4.0" git push origin main --tags
- Automated Pipeline Execution:
- GitHub Actions executes
.github/workflows/release.yml: the gate polls the tagged commit's CI rollup, core artifacts build, the GitHub Release is created first (the anchor), and only then do crates.io, npm, NuGet.org, and the Pages repos publish — each independently re-runnable. PyPI publishes frompython.ymlwhen the GitHub Release is published. (Java/Maven is not published by CI.)
- GitHub Actions executes
- Crates Published:
expanse-trie: Core#![no_std]trie engine (ExpanseSet,ExpanseMap,ExpanseStrMap,ExpanseBytesMap,SyncExpanseSet,SyncExpanseMap).expanse-capi: C ABI export (libexpanse.so,expanse.dll,libexpanse.dylib,libexpanse.a).
- Trusted Publishing (OIDC):
- Uses secret-less OpenID Connect authentication between GitHub Actions and crates.io (
id-token: write). No API tokens or long-lived credentials stored in repository secrets.
- Uses secret-less OpenID Connect authentication between GitHub Actions and crates.io (
Expanse maintains an automated, official Debian/Ubuntu APT repository hosted on GitHub Pages:
# 1. Add repository source
echo "deb [trusted=yes] https://orieg.github.io/expanse/apt/ stable main" | sudo tee /etc/apt/sources.list.d/expanse.list
# 2. Update and install
sudo apt-get update
sudo apt-get install -y libexpanse1 libexpanse-dev libjudy-compat-
Architectures Supported in APT Repo:
amd64(x86-64-v1,v2,v3,v4withglibc-hwcaps)arm64(AArch64 Apple Silicon Linux, Graviton, Raspberry Pi 4/5)riscv64(RV64GC embedded and server systems)
-
Packages Available:
libexpanse1: Runtime shared libraries (libexpanse.so.1.0.0withglibc-hwcaps/variants).libexpanse-dev: Development headers (expanse.h,Judy.h), static library (libexpanse.a), pkg-config, and Section 3 man pages (expanse(3),expanse_set(3), etc.).libjudy-compat: Drop-in replacement creating system-wide/usr/lib/.../libJudy.so.1symlinks to Expanse, pkg-config, and Judy compatibility man pages (Judy(3),Judy1(3),JudyL(3), etc.).
Expanse maintains an automated, official RPM repository hosted on GitHub Pages:
# 1. Add repository configuration
sudo dnf config-manager --add-repo https://orieg.github.io/expanse/rpm/expanse.repo
# Or manually download repo file for older YUM / Amazon Linux:
# sudo curl -sS -o /etc/yum.repos.d/expanse.repo https://orieg.github.io/expanse/rpm/expanse.repo
# 2. Update and install
sudo dnf install -y libexpanse libexpanse-devel libjudy-compat-
Architectures Supported in RPM Repo:
x86_64(x86-64-v1,v2,v3,v4withglibc-hwcaps)aarch64(AWS Graviton, ARM64 servers)riscv64(RV64GC embedded and server systems)
-
Packages Available:
libexpanse: Runtime shared library (/usr/lib64/libexpanse.so.1withglibc-hwcaps/variants).libexpanse-devel: Development headers (/usr/include/expanse.h,Judy.h), static library (libexpanse.a), pkg-config, and Section 3 man pages (expanse(3),expanse_map(3), etc.).libjudy-compat: Drop-in replacement creating system-wide/usr/lib64/libJudy.so.1symlinks to Expanse, pkg-config, and Judy compatibility man pages (Judy(3),Judy1(3),JudyL(3), etc.).
Expanse delivers first-class Windows MSVC binaries built with 64-bit calling conventions:
- GitHub Releases Windows ZIP Bundle:
expanse-vX.Y.Z-x86_64-pc-windows-msvc.zipcontaining:bin/expanse.dll(dynamic library)lib/expanse.lib(MSVC import library)include/expanse.h&include/Judy.h(C headers)README.txt(MSVC build and linking instructions)
- Microsoft vcpkg:
- Port files in
extra/vcpkg/(vcpkg.json,portfile.cmake) enable direct integration viavcpkg install expanseor overlay ports.
- Port files in
- NuGet Native Package:
- Specification in
extra/nuget/(expanse.nuspec,expanse.targets) packages the DLL, import lib, and auto-linking MSBuild properties for Visual Studio C++ projects.
- Specification in
Templates in extra/pkgconfig/:
expanse.pc.in(pkg-config --cflags --libs expanse)judy.pc.in(pkg-config --cflags --libs judy)
Every GitHub release bundles precompiled native archives:
expanse-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz(glibc + hwcaps)expanse-vX.Y.Z-x86_64-unknown-linux-musl.tar.gz(static Alpine Linux)expanse-vX.Y.Z-aarch64-apple-darwin.tar.gz(Apple Silicon macOS)expanse-vX.Y.Z-x86_64-apple-darwin.tar.gz(Intel macOS)expanse-vX.Y.Z-x86_64-pc-windows-msvc.zip(Windows MSVC).debpackages for Debian/UbuntuSHA256SUMScryptographic manifest
Expanse is distributed on PyPI as expanse-trie with binary abi3 wheels across Linux (x86_64, aarch64), macOS (arm64, x86_64), and Windows (x86_64).
- Package Configuration:
pyproject.tomlusingmaturinbackend (bindings/python). - Python Crate:
crates/expanse-pyexportingexpanse_trie._expanse. - Type Stubs: PEP 561 typed (
bindings/python/expanse_trie/py.typedand__init__.pyi). - CI / Distribution Workflow:
.github/workflows/python.ymlbuilds wheels, runs thepytesttest suite, and publishes to PyPI with trusted publishing (OIDC). - Full Guide: See docs/bindings/python.md.
Not yet published, and no CI publish path exists. Maven Central has zero
io.github.oriegartifacts, andrelease.ymlcontains no Maven/Gradle/Sonatype build or deploy job. The following describes the planned distribution.
Expanse is intended to be distributed on Maven Central as io.github.orieg:expanse-java with bundled multi-arch native libraries loaded via Project Panama Foreign Function & Memory (FFM) API. Until then, build from bindings/java locally.
- Package Configuration:
bindings/java/pom.xmlandbindings/java/build.gradle. - Native Loader:
io.github.orieg.expanse.internal.NativeLoaderextracts and loads precompiled native libraries across Linux, macOS, and Windows. - Full Guide: See docs/bindings/java.md.
Expanse is distributed on the npm registry as @orieg/expanse featuring high-performance native N-API binary bindings built via napi-rs (crates/expanse-node/).
- Package Configuration:
crates/expanse-node/package.jsonandcrates/expanse-node/Cargo.toml. - Native OIDC Trusted Publishing:
- Eliminates long-lived, static npm automation tokens by leveraging native OpenID Connect (OIDC) identity federation between GitHub Actions and npmjs.com.
- Configured in
.github/workflows/release.ymlwithpermissions: id-token: write, contents: read. - Configured on npmjs.com under @orieg/expanse -> Settings -> Publishing Access -> Trusted Publishing bound to repository
orieg/expanse, workflowrelease.yml, and environment/tag rules.
- Sigstore Build Provenance (
--provenance):- Published using
npm publish --access public --provenance. - Automatically generates cryptographic build attestations backed by Sigstore, linking the published tarball to the exact GitHub Actions runner, workflow run, and commit SHA.
- Users can verify package authenticity directly on npmjs.com via the verified provenance badge.
- Published using
- Multi-Runtime Installation:
# npm npm install @orieg/expanse # pnpm pnpm add @orieg/expanse # yarn yarn add @orieg/expanse # Bun bun add @orieg/expanse # Deno deno add npm:@orieg/expanse
- Quick Usage Snippet (Node.js / Bun / Deno):
import { ExpanseSet, ExpanseMap, ExpanseBlobMap, SyncExpanseMap } from '@orieg/expanse'; // 1. Dynamic sparse 64-bit integer set (Judy1) const set = new ExpanseSet([10n, 20n, 50n, 100n]); console.log(set.has(20n)); // true console.log(set.next(25n)); // 50n console.log(set.countRange(10n, 50n)); // 3n // 2. High-performance word map (JudyL) const map = new ExpanseMap(); map.set(42n, 1000n); console.log(map.get(42n)); // 1000n // 3. Off-heap polymorphic blob map (inline packing + slab arena) const blobMap = new ExpanseBlobMap(); blobMap.set(1n, Buffer.from("expanse payload"), 0x01); const entry = blobMap.getWithMeta(1n); console.log(entry.isInline); // true (0 heap allocations) // 4. Optimistic OCC concurrent map for worker threads const syncMap = new SyncExpanseMap(); syncMap.set(100n, 5000n); console.log(syncMap.get(100n)); // 5000n
- Full Guide: See crates/expanse-node/README.md.
The wasm-bindgen surface (crates/expanse-wasm) is published to npm as @orieg/expanse-wasm for browser/edge runtimes (Cloudflare Workers, Deno Deploy, and similar):
npm i @orieg/expanse-wasmThe publish-wasm release job (Phase 3, anchor-first per #313) builds with wasm-pack build --release --target web --scope orieg and publishes the generated pkg/ via npm OIDC trusted publishing, idempotent on already-published versions.
Wired but not yet landed. The
release.ymlNuGet push step exists (OIDC trusted publishing, below), butOrieg.Expansedoes not yet resolve on nuget.org (404 /totalHits:0). Build frombindings/dotnetlocally until first publish.
Expanse is intended to be distributed on NuGet.org as Orieg.Expanse, providing zero-GC off-heap collections and P/Invoke bindings wrapping libexpanse for .NET 8.0 and .NET 9.0+.
- Package Configuration:
bindings/dotnet/src/Expanse.NET/Expanse.NET.csproj. - OIDC Trusted Publishing on NuGet.org:
- NuGet.org supports secretless OpenID Connect (OIDC) authentication via Trusted Signing & Publishing Policies.
- Policy Configuration:
- Policy Name:
expanse-nuget-ci(Active) - Package Owner:
orieg - Scopes: Push new packages and package versions
- Glob Patterns & Packages:
*(orOrieg.*) - Publisher: GitHubActions (Repository Owner:
orieg, Repository:expanse, Workflow:release.yml)
- Policy Name:
- Workflow Authentication:
- Executed in
.github/workflows/release.ymlwithpermissions: id-token: write, contents: read. - Authenticates via the official
NuGet/login@v1action:- name: NuGet login uses: NuGet/login@v1 id: nuget-login with: user: orieg - name: Publish to NuGet.org env: NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY || secrets.NUGET_API_KEY }} if: env.NUGET_API_KEY != '' run: | dotnet nuget push dist/nuget/*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate || true
- Supports fallback authentication via GitHub repository secret
NUGET_API_KEYwhen OIDC token is absent.
- Executed in
- Automated Packaging Pipeline in
release.yml:dotnet pack bindings/dotnet/src/Expanse.NET/Expanse.NET.csproj -c Release -o dist/nugetgenerates both the.nupkgbinary package and.snupkgsymbol package for SourceLink step-through debugging.- Automatically pushes to
https://api.nuget.org/v3/index.json.
- Installation:
# .NET CLI dotnet add package Orieg.Expanse # PackageReference (csproj) <PackageReference Include="Orieg.Expanse" Version="0.5.0" />
- Quick Usage Snippet (C#):
using System; using Expanse; // 1. Dynamic sparse 64-bit integer set (Judy1) using var set = new ExpanseSet(); set.Add(10); set.Add(20); set.Add(50); set.Add(100); Console.WriteLine(set.Contains(20)); // True Console.WriteLine(set.Rank(50)); // O(depth) rank // 2. Off-heap key-value map (JudyL) using var map = new ExpanseMap(); map[42] = 1000; if (map.TryGet(42, out ulong val)) { Console.WriteLine($"Key 42 -> {val}"); } // 3. String trie (JudySL) and Binary key map (JudyHS) using var strMap = new ExpanseStrMap(); strMap["metrics.cpu"] = 95; // 4. Large-value off-heap blob map with zero-copy views using var blobMap = new ExpanseBlobMap(); blobMap.Insert(1, "payload data"u8, 0x01); // 5. Optimistic OCC concurrent map using var syncMap = new ExpanseSyncMap(); syncMap.Insert(100, 500);
- Full Guide: See bindings/dotnet/README.md.
Expanse provides a unified dual-driver distribution for PHP 8.1–8.5+:
- Composer / Packagist (library):
orieg/expanse— pure-PHP userland package, subsplit frombindings/phptogithub.com/orieg/expanse-php-library. - PIE (extension):
pie install orieg/expanse-extension— native Zend extension compiled locally from Rust (ext-php-rs), subsplit fromcrates/expanse-phptogithub.com/orieg/php-expanse. Follows the MongoDB two-package convention (library = bare name, extension =-extension). - Release path: both packages are published by the
subsplit.ymlmirror workflow on av*tag — not by the release DAG inrelease.yml, which contains no PHP step. Packagist ingests from the mirror repositories by webhook, so a green subsplit run means the mirrors were updated, not that Packagist serves the new version. Verify both package pages after a release (#498). - Zero-Install FFI Fallback: Automatically activates
\FFIdowncalls intolibexpansewhen native extension compilation is unavailable. - Quickstart:
composer require orieg/expanse
use Expanse\Set; use Expanse\Map; $set = new Set(); $set->add(42); $map = new Map(); $map->set(42, 1000);
- Full Guide: See docs/bindings/php.md.
Expanse is distributed for Ruby 3.0+ as the expanse gem under bindings/ruby:
- Package Configuration:
bindings/ruby/expanse.gemspecandbindings/ruby/Rakefile. - FFI Integration: Uses Ruby's standard library
Fiddleto loadlibexpansedynamically across Linux, macOS, and Windows with zero compilation dependencies. - Quickstart:
gem install expanse
require "expanse" set = Expanse::Set.new set.add(42) map = Expanse::Map.new map[42] = 1000
- Publishing: the
publish-gemrelease job (Phase 3, anchor-first per #313) pushes to rubygems.org via OIDC trusted publishing (rubygems/configure-rubygems-credentials), idempotent on already-published versions. The gem is pure Ruby — at runtime it needslibexpansefrom the apt/rpm repositories, a GitHub Release archive, or a system install. - Full Guide: See docs/bindings/ruby.md.
The Go binding is consumed directly from the monorepo as a nested Go module:
go get github.com/orieg/expanse/bindings/go@v0.5.0Pinned versions resolve via bindings/go/vX.Y.Z tags (Go's subdirectory-module convention), pushed automatically by the github-release job on every release tag.
The module supports two interchangeable build configurations:
- CGO Mode (
CGO_ENABLED=1default): Linkslibexpanse.astatically orlibexpanse.sodynamically via standard CGO. - PureGo Mode (
CGO_ENABLED=0or-tags expanse_purego): 100% CGO-free build powered bypuregoover the sharedlibexpanse.{so,dylib,dll}. Discovers the native library viaEXPANSE_LIBRARY, standard system loader paths, or embedded caching. Enables static cross-compiled binaries and distroless/scratch container images.
See bindings/go/README.md for details.
Expanse maintains packaging manifests across several ecosystems (Cargo/Rust, C/C++ headers/CMake, Python/PyPI, Node.js/npm, .NET/NuGet, Java/Maven/Gradle, PHP/Composer/PIE, and Ruby/Gems) spanning 16 canonical manifests. Publication status differs per registry (see the per-ecosystem sections: crates.io / npm (+wasm) / PyPI / NuGet / RubyGems wired into the anchor-first release DAG; PHP/Packagist published out-of-band by the subsplit.yml mirror workflow, whose success confirms the mirrors were pushed but not that Packagist ingested the version (#498); Go pinned via nested-module tags; Java/Maven not yet built or published). To guarantee version lockstep without manual error, the repository includes scripts/bump_version.py.
| Manifest File | Section / Key | Description |
|---|---|---|
Cargo.toml |
[workspace.package] version |
Root workspace package metadata |
crates/expanse/Cargo.toml |
[package] version |
Core expanse-trie Rust crate |
crates/expanse-capi/Cargo.toml |
[package] version, expanse-trie dep |
C ABI expanse-capi crate |
crates/expanse-py/Cargo.toml |
[package] version, expanse-trie dep |
PyO3 Python native binding crate |
crates/expanse-node/Cargo.toml |
[package] version, expanse-trie dep |
napi-rs Node.js native binding crate |
crates/expanse-php/Cargo.toml |
[package] version, expanse-trie dep |
ext-php-rs PHP Zend extension crate |
crates/expanse-node/package.json |
"version" |
npm package manifest (@orieg/expanse) |
bindings/php/composer.json |
"version" |
PHP Composer package manifest (orieg/expanse) |
pyproject.toml |
[project] version |
Python PyPI wheel manifest (expanse-trie) |
bindings/dotnet/src/Expanse.NET/Expanse.NET.csproj |
<Version>, <PackageVersion>, <AssemblyVersion> |
.NET NuGet package manifest (Orieg.Expanse) |
bindings/java/pom.xml |
<project><version> |
Maven Central POM manifest (io.github.orieg:expanse-java) |
bindings/java/build.gradle |
version = '...' |
Gradle build manifest |
bindings/ruby/expanse.gemspec |
spec.version |
Ruby gem specification (expanse) |
bindings/ruby/lib/expanse.rb |
VERSION = '...' |
Ruby module version constant |
components/expanse/idf_component.yml |
version: "..." |
Espressif ESP-IDF Component Manager manifest |
extra/vcpkg/vcpkg.json (extra) |
"version" |
Microsoft vcpkg C/C++ port manifest |
extra/nuget/expanse.nuspec (extra) |
<version> |
C++ native NuGet package specification |
-
Bump Version Across All Manifests & Re-generate
Cargo.lock:python3 scripts/bump_version.py 0.4.0
This automatically updates all manifests and executes
cargo check --workspaceto updateCargo.lockwith zero manual intervention. -
Dry Run (Preview Changes Without Modifying Files):
python3 scripts/bump_version.py 0.4.0 --dry-run
-
Verify Lockstep Synchronization (CI Gate):
python3 scripts/bump_version.py --check
Or verify against a specific expected version:
python3 scripts/bump_version.py 0.4.0 --check
Exits with code
0on success, or code1with descriptive mismatch reports if any manifest drifts out of sync.
Expanse is packaged as a component for the Espressif ESP-IDF framework (v5.0+). CMakeLists.txt builds libexpanse.a with cargo for the bare-metal RISC-V target matching IDF_TARGET and links it against the host-allocator glue in src/expanse_esp_idf.c.
RISC-V parts only — ESP32-C2/C3 (RV32IMC, built as riscv32imc-unknown-none-elf), ESP32-C6/H2 (RV32IMAC, built as riscv32imac-unknown-none-elf), and ESP32-P4 (RV32IMAFC, built as riscv32imafc-unknown-none-elf — the ilp32f hard-float ABI ESP-IDF compiles the P4 with; see components/expanse/README.md). Per-part ISA and core counts are sourced to the Espressif datasheets/TRMs in docs/HARDWARE.md §4.3. The Xtensa ESP32/S2/S3 have no mainline rustc target; the configure step fails with a named error rather than registering a component with no engine behind it.
The component exposes the modern expanse_* ordered set/map core and no Judy* symbols — the legacy drop-in ABI is a 64-bit-only guarantee. See the surface matrix.
components/expanse/
├── idf_component.yml # Component Manager manifest for Espressif Registry
├── CMakeLists.txt # ESP-IDF CMake integration
├── Kconfig # Menuconfig configuration options
├── README.md # Component documentation and quickstart
├── include/
│ └── expanse_esp_idf.h # SRAM capability helpers + the host allocator pair
├── src/
│ └── expanse_esp_idf.c # heap_caps_* routines; defines expanse_host_malloc/-_free
└── test/
└── test_expanse.c # Unity test suite for ESP-IDF
expanse.h is not copied here: CMakeLists.txt puts the canonical
include on the include path, so the component cannot
drift from the shipped header. Judy.h is deliberately absent — a 32-bit
build exports no Judy* symbols to back it.
Add the dependency to your project's main/idf_component.yml:
dependencies:
expanse:
version: "^0.5.0"Or clone the component directly into your project's components/ directory.
ESP-IDF developers can configure memory placement via idf.py menuconfig under Component config -> Expanse Embedded Digital Trie:
EXPANSE_SRAM_INTERNAL_ONLY: Forces allocations into internal high-speed DRAM (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) to eliminate SPI bus arbitration delays and maximize cache line throughput.