Skip to content

v0.12.0-rc.2 — queries open read-only (concurrent use no longer corrupts the graph) #26

v0.12.0-rc.2 — queries open read-only (concurrent use no longer corrupts the graph)

v0.12.0-rc.2 — queries open read-only (concurrent use no longer corrupts the graph) #26

Workflow file for this run

name: Publish to npm
# Triggered by publishing a GitHub Release. Builds, verifies, then publishes
# engramgraph to npm via OIDC Trusted Publishing — NO token needed.
#
# Setup (one-time, on npmjs.com → engramgraph → Settings → Trusted
# Publisher): add a GitHub Actions trusted publisher with
# organization/user: AsiaOstrich repository: EngramGraph workflow: publish.yml
# npm then mints short-lived credentials from this job's OIDC id-token; the repo
# must be public. Provenance is generated automatically.
on:
release:
types: [published]
jobs:
# DOES THE SUITE STILL PASS AGAINST WHAT USERS WILL ACTUALLY INSTALL?
# // implements XSPEC-366 R3
#
# A published npm package does not carry its lockfile. The `publish` job
# below installs with `package-lock.json` in place, so it tests the versions
# this repo pins. A consumer runs `npm install engramgraph` and gets whatever
# the declared ranges resolve to at that moment. When the two differ, the
# entire suite is green about a combination nobody installs.
#
# That is not hypothetical here. `"tree-sitter-c-sharp": "^0.23.1"` spanned
# three published versions with incompatible APIs; npm took the newest, and
# **no .cs file ever parsed for anyone who installed from npm** while every
# test passed, for the whole life of C# support. XSPEC-365 has the full
# account; XSPEC-366 R2 pinned the grammars afterwards.
#
# Pinning fixed the dependencies that were known to be dangerous. This job
# exists for the ones that are not known yet: it deletes the lockfile,
# resolves everything the way a consumer would, and runs the real suite
# against the result. It does not need to be told which dependency will
# break next.
#
# `publish` needs it, so a failure here stops the release rather than
# producing a version that is green in CI and broken on installation.
consumer-resolution:
name: Test against consumer resolution (no lockfile)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm install -g npm@latest
- name: Record what the lockfile pins, then remove it
run: |
set -e
node -e "
const lock = require('./package-lock.json');
const pkg = require('./package.json');
const declared = { ...pkg.dependencies, ...pkg.optionalDependencies };
const rows = Object.keys(declared).map((name) => ({
name,
range: declared[name],
locked: (lock.packages['node_modules/' + name] || {}).version || null,
}));
require('fs').writeFileSync('locked-versions.json', JSON.stringify(rows, null, 2));
console.log('Recorded ' + rows.length + ' runtime dependencies from the lockfile.');
"
rm package-lock.json
# Resolves every range fresh, exactly as a consumer's install does.
- run: npm install --legacy-peer-deps
- name: Report which dependencies resolved differently from what is pinned
run: |
set -e
# Printed whether or not anything differs. A silent job leaves "the
# suite passed against identical versions" and "the suite passed
# against sixteen untested ones" looking the same in the log, and
# only one of those is worth knowing about later.
# Versions are read straight off disk, NOT through require(). A
# package may block '/package.json' in its `exports` map — hono,
# ryugraph and @modelcontextprotocol/sdk all do — and require() then
# throws for a package that is installed and fine. The first version
# of this step did exactly that and reported three drifts where there
# were two, one of them fabricated. A drift report that invents
# drifts is worse than none: it trains the reader to ignore it.
node -e "
const { readFileSync, existsSync } = require('node:fs');
const rows = require('./locked-versions.json');
let drifted = 0;
let unreadable = 0;
for (const row of rows) {
const manifest = 'node_modules/' + row.name + '/package.json';
if (!existsSync(manifest)) {
unreadable++;
console.log(' ' + row.name + ' ' + row.range + ' pinned=' + row.locked + ' NOT INSTALLED');
continue;
}
const actual = JSON.parse(readFileSync(manifest, 'utf8')).version;
if (actual !== row.locked) {
drifted++;
console.log(' ' + row.name + ' ' + row.range + ' pinned=' + row.locked + ' resolved=' + actual);
}
}
if (unreadable > 0) {
console.log('');
console.error(unreadable + ' declared dependencies are not installed — this check cannot speak for them.');
process.exitCode = 1;
}
console.log('');
console.log(drifted + ' of ' + rows.length + ' runtime dependencies resolved to a version this repo does not pin.');
console.log(drifted === 0
? 'Consumers install exactly what the lockfile tests.'
: 'The suite below runs against THOSE versions — the ones users actually get.');
"
# ryugraph's ALGO extension is fetched from an unreliable host; the same
# from-source build the publish job uses, so this job fails on the code
# rather than on that download.
- name: Build ryugraph ALGO extension from source
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq cmake
cd node_modules/ryugraph/ryu-source
make extension-release EXTENSION_LIST=algo NUM_THREADS="$(nproc)"
mkdir -p "$HOME/.ryu/extension/25.9.0/linux_amd64/algo"
cp extension/algo/build/libalgo.ryu_extension "$HOME/.ryu/extension/25.9.0/linux_amd64/algo/"
- run: npm run build
- run: npm run test:coverage
publish:
name: Publish to npm
needs: consumer-resolution
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDC trusted publishing + provenance
steps:
- uses: actions/checkout@v4
# NOTE: no `registry-url` — it makes setup-node write an empty
# //registry.npmjs.org/:_authToken to .npmrc, which suppresses OIDC
# trusted publishing (npm takes the empty-token path → anonymous → E404).
# Without it, npm uses the default registry and engages OIDC.
- uses: actions/setup-node@v4
with:
node-version: 24
# OIDC trusted publishing requires npm >= 11.5.1 (newer than the bundled npm).
- run: npm install -g npm@latest
- run: npm --version
# npm >= 11 gates native install scripts behind an approval list by
# default (this broke this exact workflow on 2026-07-10 once `npm
# install -g npm@latest` above started pulling npm 11.x: DTS build
# failed with "Cannot find module 'ryugraph'", because ryugraph's own
# install step — which copies its native binary into place — never
# ran). The fix lives in package.json's `allowScripts` field
# (declarative, version-pinned pre-approval for this repo's known
# native deps: ryugraph/tree-sitter*/esbuild) rather than an
# imperative `--all` here, so a plain install picks it up with no
# extra step and no silent trust extended to future dependencies.
- run: npm install --legacy-peer-deps
# ryugraph's `god-nodes`/`communities`/`related` commands need its ALGO
# extension (PageRank/Louvain), which isn't bundled — ryugraph normally
# downloads it on first use from extension.ryugraph.io. That host is
# unreliable (confirmed 2026-07-11: unreachable from this exact runner,
# TCP connect just hangs — not a DNS issue). Without a fallback, tests
# that trigger the download hang for 10+ minutes before finally timing
# out (see structural-memory.test.ts's L3 tests on 2026-07-10 run
# 29105565151). Building the extension from the source ryugraph already
# ships (node_modules/ryugraph/ryu-source) and dropping it straight into
# the cache path ryugraph checks first means `INSTALL ALGO` never has to
# touch the network at all — verified locally with the download host
# blackholed via /etc/hosts (full 81/81 tests, ~7s, vs. 617s+ hanging
# before this step existed).
#
# `EXTENSION_LIST=algo` + the Makefile's `extension-release` target
# (-DBUILD_RYU=FALSE) builds only this extension, not the whole engine.
# Cache path/version/platform-string format taken from ryujs.node's own
# embedded path template and cross-checked against predictable-labs/
# ryugraph#48's real-world report (25.9.0 is the extension catalog
# version — distinct from, and older than, the ryugraph@25.9.1 npm
# package version).
- name: Build ryugraph ALGO extension from source (bypass unreliable download host)
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq cmake
cd node_modules/ryugraph/ryu-source
make extension-release EXTENSION_LIST=algo NUM_THREADS="$(nproc)"
mkdir -p "$HOME/.ryu/extension/25.9.0/linux_amd64/algo"
cp extension/algo/build/libalgo.ryu_extension "$HOME/.ryu/extension/25.9.0/linux_amd64/algo/"
# Build + verify before publishing (native: kuzu + tree-sitter compile here).
- run: npm run build
- run: npm run typecheck
# XSPEC-073 G2 — test:coverage rather than test.
# The thresholds added to vitest.config.ts only apply when coverage is
# collected, so plain `vitest run` would leave them as decoration — the
# state UDS cli's thresholds sat in for months. This is also the only
# workflow in the repo that runs tests at all (both workflows here trigger
# on `release` only, so there is no push/PR CI to attach a gate to).
- run: npm run test:coverage
# WHAT DOES A CONSUMER'S TREE LOOK LIKE, NOT WHAT DOES THIS REPO'S?
#
# The script packs the tarball, installs it into an empty directory and
# audits that. Auditing in place would be cheaper and would answer the
# wrong question: package.json overrides cmake-js to ^8.0.0 (fddd07e,
# "clearing remaining tar CVEs"), and npm overrides govern the project
# that declares them rather than travelling with a published package. So
# `npm audit --omit=dev` here reports nothing while `npm install
# engramgraph@0.9.1` into an empty directory pulls cmake-js@7.4.0 and
# tar@6.2.1 — twelve advisories, one critical.
#
# That gap is not news: README's "Dependency vulnerability warnings"
# section explains it, links the upstream issue (ryugraph#49) and gives
# consumers the override to apply themselves. What was missing is anything
# that checks. Prose does not notice when the count grows — the README
# says "4 high severity", and a consumer install today reports one
# critical and three high — and prose says nothing at all when some
# *other* dependency starts doing the same thing.
#
# Findings are cleared through security/accepted-advisories.json, where
# every entry carries an expiry date.
- name: Audit what consumers install (not what this repo pins)
run: npm run audit:ship
- name: Determine npm dist-tag
id: tag
run: |
VERSION=$(node -p "require('./package.json').version")
if echo "$VERSION" | grep -qE '\-(beta|alpha|rc)\.'; then
echo "tag=next" >> "$GITHUB_OUTPUT"
else
echo "tag=latest" >> "$GITHUB_OUTPUT"
fi
# No NODE_AUTH_TOKEN: npm authenticates via the OIDC id-token against the
# trusted publisher configured on npmjs. Provenance is automatic.
- name: Publish engramgraph (OIDC trusted publishing)
run: npm publish --access public --tag ${{ steps.tag.outputs.tag }}