Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
155 changes: 155 additions & 0 deletions .github/workflows/publish-fixed-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Publish (ruvector + rvf-wasm)

# Publishes the npm packages that the per-feature `build-*.yml` workflows do
# NOT cover: the top-level `ruvector` package, `@ruvector/rvf-wasm`, and
# `@ruvector/rvf`. Runs the regression guard first so a structurally-broken
# tarball (the #354 / #372 / #376 / #415 / #417 class of bug) can never reach
# npm again.
#
# Triggers:
# - manual `workflow_dispatch` (DRY-RUN by default — set dry_run=false to
# actually publish)
# - push of a tag matching `ruvector-v*` (real publish)
#
# Required secrets for a real publish: NPM_TOKEN.

on:
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (build + pack + guard only, no npm publish)'
type: boolean
default: true
packages:
description: 'Which packages: all | ruvector | rvf-wasm | rvf'
type: string
default: 'all'
push:
tags:
- 'ruvector-v*'

permissions:
contents: read
id-token: write # npm provenance

concurrency:
group: publish-fixed-${{ github.ref }}
cancel-in-progress: false

env:
IS_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
WANT: ${{ github.event_name == 'workflow_dispatch' && inputs.packages || 'all' }}

jobs:
guard:
name: Regression guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Build ruvector
working-directory: npm/packages/ruvector
run: |
npm install --no-audit --no-fund --ignore-scripts
npm run build
- name: Tarball integrity check
run: node scripts/ci/check-npm-package-integrity.mjs

publish:
name: Publish to npm
needs: guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'

- name: Resolve dry-run flag
id: mode
run: |
echo "dry_run=${IS_DRY_RUN}" >> "$GITHUB_OUTPUT"
echo "Mode: dry_run=${IS_DRY_RUN}, packages=${WANT}"

# --- helper: build a package if it has a build script ----------------
- name: Build packages
run: |
set -euxo pipefail
for d in npm/packages/ruvector npm/packages/rvf npm/packages/rvf-wasm; do
( cd "$d"
npm install --no-audit --no-fund --ignore-scripts || true
if node -e "process.exit(require('./package.json').scripts?.build?0:1)"; then npm run build; fi )
done

# --- publish one package: skip if version already on npm -------------
- name: Publish @ruvector/rvf-wasm
if: ${{ env.WANT == 'all' || env.WANT == 'rvf-wasm' }}
working-directory: npm/packages/rvf-wasm
run: |
NAME=$(node -p "require('./package.json').name")
VER=$(node -p "require('./package.json').version")
npm pack >/dev/null
if npm view "$NAME@$VER" version >/dev/null 2>&1; then
echo "::notice::$NAME@$VER already published — skipping"; exit 0
fi
if [ "${{ steps.mode.outputs.dry_run }}" = "true" ]; then
echo "DRY RUN — would: npm publish --access public --provenance ($NAME@$VER)"
else
npm publish --access public --provenance
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish @ruvector/rvf
if: ${{ env.WANT == 'all' || env.WANT == 'rvf' }}
working-directory: npm/packages/rvf
run: |
NAME=$(node -p "require('./package.json').name")
VER=$(node -p "require('./package.json').version")
npm pack >/dev/null
if npm view "$NAME@$VER" version >/dev/null 2>&1; then
echo "::notice::$NAME@$VER already published — skipping"; exit 0
fi
if [ "${{ steps.mode.outputs.dry_run }}" = "true" ]; then
echo "DRY RUN — would: npm publish --access public --provenance ($NAME@$VER)"
else
npm publish --access public --provenance
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish ruvector
if: ${{ env.WANT == 'all' || env.WANT == 'ruvector' }}
working-directory: npm/packages/ruvector
run: |
NAME=$(node -p "require('./package.json').name")
VER=$(node -p "require('./package.json').version")
# `prepublishOnly` (build + verify-dist) runs automatically on publish;
# run it now for the dry-run path too so the guard is identical.
npm run prepublishOnly
npm pack >/dev/null
if npm view "$NAME@$VER" version >/dev/null 2>&1; then
echo "::notice::$NAME@$VER already published — skipping"; exit 0
fi
if [ "${{ steps.mode.outputs.dry_run }}" = "true" ]; then
echo "DRY RUN — would: npm publish --access public ($NAME@$VER)"
else
npm publish --access public
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Summary
if: always()
run: |
{
echo "## Publish run"
echo "- dry_run: \`${{ steps.mode.outputs.dry_run }}\`"
echo "- packages: \`${WANT}\`"
echo "- ruvector: $(node -p "require('./npm/packages/ruvector/package.json').version")"
echo "- @ruvector/rvf-wasm: $(node -p "require('./npm/packages/rvf-wasm/package.json').version")"
echo "- @ruvector/rvf: $(node -p "require('./npm/packages/rvf/package.json').version")"
} >> "$GITHUB_STEP_SUMMARY"
131 changes: 131 additions & 0 deletions .github/workflows/regression-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Regression Guard

# Re-tests the failure modes behind previously-fixed bugs so they cannot
# silently come back. Each job maps to one or more closed issues — see the
# comments. Keep this fast (no full workspace build) so it can gate every PR.

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: regression-guard-${{ github.ref }}
cancel-in-progress: true

jobs:
# ---------------------------------------------------------------------------
# npm publish hygiene — guards #354 (ONNX wasm not bundled), #323 (.wasm ext),
# #376 (dist/index.js missing from tarball), #415 (rvf-wasm ESM-in-CJS),
# #372 (pi-brain require() of ESM-only package).
# ---------------------------------------------------------------------------
npm-package-integrity:
name: npm package integrity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
# Build the packages that ship compiled output, so `npm pack` sees the
# real publishable tree. `prepublishOnly` runs build + verify-dist for
# the main package; rvf-wasm / pi-brain ship checked-in `pkg/` & `dist/`.
- name: Build ruvector package
working-directory: npm/packages/ruvector
run: |
npm install --no-audit --no-fund --ignore-scripts
npm run build
- name: Check published-tarball integrity
run: node scripts/ci/check-npm-package-integrity.mjs

# ---------------------------------------------------------------------------
# The MCP server must at minimum parse and import cleanly on a stock Node —
# guards #372 (require of ESM-only @ruvector/pi-brain) and #422 (spawnSync of
# `npx ruvector ...` timing out: the handler should not shell out to npx).
# ---------------------------------------------------------------------------
mcp-server-loads:
name: MCP server loads
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- working-directory: npm/packages/ruvector
run: npm install --no-audit --no-fund --ignore-scripts
- name: node --check bin/*.js
working-directory: npm/packages/ruvector
run: |
node --check bin/mcp-server.js
node --check bin/cli.js
- name: hooks_route_enhanced does not shell out to npx (#422)
working-directory: npm/packages/ruvector
run: |
# Extract the body of the `hooks_route_enhanced` case and ensure it
# does not invoke `npx` (cold-start blows the timeout — #422).
BODY=$(awk "/case 'hooks_route_enhanced'/{f=1} f{print} f&&/^ }/{exit}" bin/mcp-server.js)
echo "$BODY"
if echo "$BODY" | grep -qE "\bnpx\b"; then
echo "::error::the hooks_route_enhanced handler in bin/mcp-server.js runs \`npx ...\` — npx cold-start exceeds the timeout (#422). Call the local cli.js directly."
exit 1
fi
echo "OK: hooks_route_enhanced calls the local CLI, not npx"
- name: pi-brain imports are not require()'d (#372)
working-directory: npm/packages/ruvector
run: |
if grep -nE "require\(\s*['\"]@ruvector/pi-brain['\"]\s*\)" bin/mcp-server.js; then
echo "::error::bin/mcp-server.js require()s @ruvector/pi-brain, which is ESM-only — use await import() (#372)."
exit 1
fi
echo "OK: pi-brain is loaded via dynamic import"

# ---------------------------------------------------------------------------
# A default `cargo build` on STABLE must work — guards #438 (avx512f
# target_feature/intrinsics are nightly-only and were forcing every consumer
# of ruvector-core onto nightly).
# ---------------------------------------------------------------------------
stable-toolchain-build:
name: builds on stable (no nightly-only features)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo +stable check ruvector-core (default features)
run: cargo +stable check -p ruvector-core
- name: cargo +stable check ruvector-router-core (default features)
run: cargo +stable check -p ruvector-router-core
- name: no unconditional avx512f target_feature in ruvector-core (#438)
run: |
if grep -RInE '#\[target_feature\(enable\s*=\s*"avx512' crates/ruvector-core/src \
| grep -vE 'cfg\(|feature\s*=\s*"simd-avx512"' ; then
echo "::error::ruvector-core uses avx512 target_feature without a #[cfg(feature=\"simd-avx512\")] gate — this forces nightly on all dependents (#438)."
exit 1
fi
echo "OK: avx512 paths are feature-gated"

# ---------------------------------------------------------------------------
# postgres extension toolchain mismatch — guards #331 (pgrx pinned to 0.12 but
# `cargo install cargo-pgrx` grabs the latest). We don't build pgrx here
# (heavy); we just assert the pin is documented so users don't hit the wall.
# ---------------------------------------------------------------------------
pgrx-pin-documented:
name: pgrx version pin is documented (#331)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: cargo-pgrx version must be pinned in docs
run: |
PIN=$(grep -oE 'pgrx[^=]*=\s*"[^"]+"' crates/ruvector-postgres/Cargo.toml | head -1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?')
echo "Cargo.toml pins pgrx ~= $PIN"
if ! grep -RIn "cargo-pgrx --version" crates/ruvector-postgres docs 2>/dev/null | grep -q "$PIN"; then
echo "::error::crates/ruvector-postgres pins pgrx $PIN but no doc tells users to 'cargo install cargo-pgrx --version \"$PIN.x\" --locked' (#331)."
exit 1
fi
echo "OK: pgrx pin is documented"
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ruvector-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ harness = false
[features]
default = ["simd", "storage", "hnsw", "api-embeddings", "parallel"]
simd = ["simsimd"] # SIMD acceleration (not available in WASM)
simd-avx512 = [] # Opt-in AVX-512 intrinsics (nightly-only); OFF by default so stable builds work
parallel = ["rayon", "crossbeam"] # Parallel processing (not available in WASM)
storage = ["redb", "memmap2"] # File-based storage (not available in WASM)
hnsw = ["hnsw_rs"] # HNSW indexing (not available in WASM due to mmap dependency)
Expand Down
Loading
Loading