Skip to content

chore(deps): bump actions/setup-node from 4.4.0 to 7.0.0 #120

chore(deps): bump actions/setup-node from 4.4.0 to 7.0.0

chore(deps): bump actions/setup-node from 4.4.0 to 7.0.0 #120

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: |
frontend/package-lock.json
app/package-lock.json
mastervault-mcp-server/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install app dependencies
working-directory: app
run: npm ci
- name: Install mastervault-mcp-server dependencies
working-directory: mastervault-mcp-server
run: npm ci
- name: Lint frontend
working-directory: frontend
run: npm run lint
- name: Typecheck frontend
working-directory: frontend
run: npx tsc -b
- name: Typecheck app
working-directory: app
run: npx tsc -p tsconfig.json --noEmit
- name: Test frontend
working-directory: frontend
run: npm test
- name: Test app
working-directory: app
run: npm test
- name: Build frontend
working-directory: frontend
run: npm run build
- name: Build app
working-directory: app
run: npm run build
- name: Test mastervault-mcp-server
working-directory: mastervault-mcp-server
run: npm test
- name: Build bundled mastervault-mcp-server (the copy ModelForge packages)
working-directory: mastervault-mcp-server
run: npm run build:bundled
- name: Smoke-test the bundled mastervault-mcp-server over stdio
working-directory: mastervault-mcp-server
run: |
set -euo pipefail
mkdir -p /tmp/mastervault-ci-vault
echo '# CI test vault' > /tmp/mastervault-ci-vault/_orientation.md
printf '%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| node dist-bundled/index.js /tmp/mastervault-ci-vault > worker-output.json
cat worker-output.json
test "$(grep -o 'mastervault_orient' worker-output.json | wc -l)" -ge 1
server:
# server/ (the Fastify IAM/patient-case service) has its own package
# boundary and DATABASE_URL/REDIS_URL-gated integration suites (see e.g.
# store/postgres-case-store.test.ts's own disclosure comment) that
# simply skip — not fail — when no real Postgres/Redis is reachable.
# Real services are wired below so those suites actually run in CI
# instead of only ever being exercised by a developer running them
# locally; docs/ENTERPRISE_ARCHITECTURE_ROADMAP.md's P0 backlog item 18
# ("Run PostgreSQL and Redis suites in mandatory CI... No gated/skipped
# enterprise suites") is exactly this gap.
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: modelforge
POSTGRES_PASSWORD: modelforge
POSTGRES_DB: modelforge_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U modelforge"
--health-interval 5s
--health-timeout 3s
--health-retries 10
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
env:
# DATABASE_URL stays the migration-owner ("modelforge") role — every
# postgres-*.test.ts file runs its own runMigrations() against
# DATABASE_URL directly (see e.g. store/postgres-iam-store.test.ts),
# which needs CREATE SCHEMA/CREATE FUNCTION/ALTER DEFAULT PRIVILEGES
# rights a restricted runtime role deliberately doesn't have.
# RUNTIME_DATABASE_URL is the separate, restricted role created below
# — used by store/postgres-rls.test.ts to prove tenant isolation
# (RLS on shared control-plane tables, pooled-connection tenant-
# context cleanup) actually holds under a real NO BYPASSRLS,
# non-owning role, not just under whatever role happened to run the
# migrations. See migrations/010_runtime_role_grants.sql.
DATABASE_URL: postgres://modelforge:modelforge@localhost:5432/modelforge_test
RUNTIME_DATABASE_URL: postgres://modelforge_runtime:modelforge_runtime@localhost:5432/modelforge_test
REDIS_URL: redis://localhost:6379
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Install postgresql-client (for role setup below)
run: sudo apt-get update -qq && sudo apt-get install -y -qq postgresql-client
- name: Create restricted runtime database role
# Created here (as the modelforge owner role, over the same
# connection the app's migrations use) rather than via the
# postgres:16-alpine service container's own init hooks, so the
# exact privileges this role gets are visible in this workflow file
# next to DATABASE_URL/RUNTIME_DATABASE_URL, not buried in image
# config. NOSUPERUSER/NOBYPASSRLS is what makes
# migrations/008_control_plane_rls.sql's tenant_isolation policies
# actually apply to this role at all (a superuser or BYPASSRLS role
# ignores row-level security entirely, which would make the RLS
# penetration tests pass for the wrong reason). NOCREATEDB/
# NOCREATEROLE bounds what a compromised runtime process could do
# beyond the schemas/tables migrations/010 explicitly grants it.
run: |
PGPASSWORD=modelforge psql -h localhost -U modelforge -d modelforge_test -v ON_ERROR_STOP=1 <<'SQL'
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'modelforge_runtime') THEN
CREATE ROLE modelforge_runtime WITH LOGIN PASSWORD 'modelforge_runtime'
NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS NOREPLICATION;
END IF;
END
$$;
GRANT CONNECT ON DATABASE modelforge_test TO modelforge_runtime;
SQL
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: server/package-lock.json
- name: Install server dependencies
working-directory: server
run: npm ci
- name: Typecheck server
working-directory: server
run: npm run typecheck
- name: Test server (Postgres- and Redis-backed suites included, not skipped)
working-directory: server
run: npm test
- name: Build server
working-directory: server
run: npm run build
admin-console:
# admin-console/ is a separate, standalone web app (its own trust
# boundary per docs/ENTERPRISE_MULTIUSER_ARCHITECTURE.md §5, not the
# Electron clinician app) for managing a ModelForge organization's IAM.
# Its own job for the same reason server/e2e are separate: independent
# dependency graph, no reason a flaky/slow run here should block them
# or vice versa.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: admin-console/package-lock.json
- name: Install admin-console dependencies
working-directory: admin-console
run: npm ci
- name: Typecheck admin-console
working-directory: admin-console
run: npx tsc -b
- name: Lint admin-console
working-directory: admin-console
run: npm run lint
- name: Test admin-console
working-directory: admin-console
run: npm test
- name: Build admin-console
working-directory: admin-console
run: npm run build
e2e:
# Electron E2E tests spin up a real Electron process and a Vite preview
# server per test, which is slower and flakier than the unit-test job
# above — kept as its own job so a flaky e2e run never blocks/hides a
# unit-test failure, and so it can be retried or skipped independently.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: |
frontend/package-lock.json
app/package-lock.json
e2e/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install app dependencies
working-directory: app
run: npm ci
- name: Install e2e dependencies
working-directory: e2e
run: npm ci
- name: Install Playwright browsers
working-directory: e2e
run: npx playwright install --with-deps chromium
- name: Build frontend
working-directory: frontend
run: npm run build
- name: Build app
working-directory: app
run: npm run build
- name: Run e2e suite
working-directory: e2e
# Electron needs a real (or virtual) display even headless-ish, hence
# xvfb — there's no way to launch a BrowserWindow without one on Linux.
run: xvfb-run --auto-servernum npx playwright test
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: playwright-report
path: e2e/playwright-report
retention-days: 7
rust:
# lib/ (modelforge-native) is the napi-rs addon backing GGUF downloads
# and, more recently, the JSON datastore/audit-hashing primitives (see
# docs/ARCHITECTURE.md). fmt/clippy/cargo test only prove the Rust side
# is correct on whichever OS runs them — they say nothing about whether
# the resulting `.node` binary actually loads under Node on that OS,
# which is a materially different failure mode (wrong ABI, missing
# platform target, packaging mismatch). Previously this job only ran on
# ubuntu-latest, so nothing in CI ever built or loaded the addon on
# Windows or macOS despite installers shipping for both — see
# docs/RUST_MIGRATION_ASSESSMENT.md's platform-matrix section. The
# matrix below and the "napi build + require() smoke test" step close
# that gap.
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: lib
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Install rustfmt and clippy
run: rustup component add rustfmt clippy
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry
~/.cargo/git
lib/target
key: ${{ runner.os }}-cargo-${{ hashFiles('lib/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
# fmt/clippy only need to run once — Rust source and formatting are
# identical across OSes, so repeating this on all three runners would
# just burn CI minutes for the same answer three times.
- name: Format check
if: matrix.os == 'ubuntu-latest'
run: cargo fmt --check
- name: Clippy
if: matrix.os == 'ubuntu-latest'
run: cargo clippy --all-targets -- -D warnings
- name: Build
run: cargo build --release
- name: Test
run: cargo test
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: lib/package-lock.json
- name: Install napi CLI
run: npm ci
- name: napi build (produces the real platform-specific .node binary)
run: npm run build:debug
# The actual gap this job exists to close: proves Node on *this* OS
# can load the binary napi build just produced, not just that Cargo
# compiled it. A wrong target triple, an ABI mismatch, or a packaging
# error would fail cargo build's cross-compile silently-succeed case
# but fail right here.
- name: Load the built addon under Node and verify its exports
shell: bash
run: |
node -e "
const addon = require('../app/native');
const expected = ['downloadGgufFile', 'DownloadManager', 'readJsonFileNative', 'writeJsonFileAtomicNative', 'sha256HexNative', 'appendJsonArrayElementNative'];
const missing = expected.filter((name) => !(name in addon));
if (missing.length > 0) {
console.error('Native addon loaded but is missing expected exports:', missing);
process.exit(1);
}
console.log('Native addon loaded successfully on ${{ matrix.os }} with all expected exports.');
"
sbom:
# Generates a CycloneDX SBOM per npm workspace so every release has an
# auditable, machine-readable dependency manifest — required groundwork
# for the supply-chain/provenance controls flagged in
# docs/ENTERPRISE_READINESS_ASSESSMENT.md, and useful on its own for
# answering "are we affected by CVE-X" without re-deriving the dependency
# tree by hand. Runs independently of the test job so a flaky SBOM
# generation never blocks the build/test signal.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
cache-dependency-path: |
frontend/package-lock.json
app/package-lock.json
mastervault-mcp-server/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Install app dependencies
working-directory: app
run: npm ci
- name: Install mastervault-mcp-server dependencies
working-directory: mastervault-mcp-server
run: npm ci
- name: Generate SBOM (frontend)
working-directory: frontend
run: npx --yes @cyclonedx/cyclonedx-npm --output-file ../sbom-frontend.cdx.json
- name: Generate SBOM (app)
working-directory: app
run: npx --yes @cyclonedx/cyclonedx-npm --output-file ../sbom-app.cdx.json
- name: Generate SBOM (mastervault-mcp-server)
working-directory: mastervault-mcp-server
run: npx --yes @cyclonedx/cyclonedx-npm --output-file ../sbom-mastervault-mcp-server.cdx.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sbom
path: sbom-*.cdx.json
retention-days: 90
security:
# SAST + secret scanning — docs/ENTERPRISE_ARCHITECTURE_ROADMAP.md P1
# item 9 ("Add artifact signing, provenance, SAST, and secret-scanning
# gates"), minus the signing/provenance half: that needs real
# certificates/key-custody/HSM infrastructure this repository does not
# build (a standing instruction — see that item's own note in the
# roadmap's §14 P1 list). SAST and secret-scanning need neither, so
# they're not blocked on it. Its own job, independent of test/build, so
# a finding here is a distinct, unambiguous signal rather than mixed
# into an unrelated build failure.
#
# Both tools were run locally against this exact repository (working
# tree + full commit history) before this job was added, specifically
# to avoid shipping an untested gate that immediately red-lines CI on
# day one: gitleaks found zero leaks across 121 commits; Semgrep (see
# config below) found zero real findings after fixing what it caught
# (an unpinned-Actions supply-chain gap across every workflow file, two
# `github`-context shell-injection spots in release.yml, a missing
# explicit AES-GCM auth-tag length in app/src/case-encryption.ts, and a
# missing Dependabot cooldown period) — see docs/OBSERVABILITY.md's
# sibling P1 item 8 work for the same "verify locally before gating"
# approach applied to the metrics endpoint. The one excluded rule below
# is a genuine false-positive class for this codebase, not a suppressed
# real finding — see its own comment.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 0 # gitleaks scans commit history, not just the working tree
- name: Install gitleaks
run: |
set -euo pipefail
GITLEAKS_VERSION=8.21.2
curl -sSL -o /tmp/gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
gitleaks version
- name: Secret scan (gitleaks — full commit history)
run: gitleaks detect --source . --redact -v
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
- name: Install Semgrep
run: python -m pip install --upgrade semgrep
- name: SAST scan (Semgrep — OWASP/JS/TS community rulesets, no account required)
run: |
semgrep scan \
--config p/ci \
--config p/owasp-top-ten \
--config p/javascript \
--config p/typescript \
--exclude-rule javascript.express.security.audit.xss.direct-response-write.direct-response-write \
--error \
--metrics=off
# javascript.express.security.audit.xss.direct-response-write assumes
# an Express-style app rendering HTML from `res.send()`. Every route
# in server/ is a Fastify JSON API — `reply.send(jsonValue)` goes
# through Fastify's own JSON serializer, never raw HTML, so there is
# no XSS surface for this rule to be finding here. Confirmed by
# manual review of every one of the 39 matches this rule produced
# locally before adding this exclusion (see this job's own top
# comment) — not a blanket "trust the tool less" suppression.
python-recommender:
# ml/hardware-recommender/ is a standalone project (its own venv, own
# train/download-dataset scripts) that isn't part of the app's build —
# only its exported ONNX model ships with the app
# (app/python/artifacts/hardware_recommender.onnx), loaded at runtime by
# app/python/recommender_worker.py. This job is deliberately scoped to
# what release publishing actually depends on: the recommender package's
# own unit tests (data/feature encoding logic, no dataset or GPU needed)
# and a smoke test that the packaged worker script can actually load the
# shipped artifact and answer a request — never train.py or
# download_dataset.py, which need a real (large) dataset pull.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: ml/hardware-recommender/requirements.txt
- name: Install dependencies (CPU-only torch for ML tests)
working-directory: ml/hardware-recommender
# torch's CPU wheels live on a separate index from the rest of
# PyPI. Install it separately to keep the training/test stack CPU-only
# and avoid a multi-gigabyte CUDA wheel download on a GPU-less runner;
# the packaged app uses ONNX Runtime for inference and does not ship
# torch in its managed runtime.
run: |
python -m pip install --upgrade pip
python -m pip install --only-binary=:all: --index-url https://download.pytorch.org/whl/cpu torch==2.13.0
# pip parses comments and blank lines correctly. Passing the file
# through shell command substitution turned the first comment into
# a literal `#` requirement and failed the job.
python -m pip install --only-binary=:all: -r requirements.txt
- name: Run hardware-recommender unit tests
working-directory: ml/hardware-recommender
run: python -m pytest tests/ -q
- name: Verify the packaged model artifacts exist
run: |
test -f app/python/artifacts/hardware_recommender.onnx
test -f app/python/artifacts/hardware_recommender.onnx.sha256
test -f app/python/artifacts/hardware_recommender.meta.json
- name: Smoke-test the packaged recommender worker against the shipped artifact
working-directory: app/python
run: |
printf '%s\n%s\n' \
'{"protocol":1,"id":"1","method":"health","params":{}}' \
'{"protocol":1,"id":"2","method":"recommend","params":{"model_params_b":7,"ram_gb":16,"vram_gb":8,"cpu_cores":8,"platform":"linux","gpu_backend":"cuda"}}' \
| python recommender_worker.py > worker-output.json
cat worker-output.json
test "$(grep -o '"ok": *true' worker-output.json | wc -l)" -eq 2