Skip to content

changelog(back): Platform.init() idempotency fix #792

changelog(back): Platform.init() idempotency fix

changelog(back): Platform.init() idempotency fix #792

Workflow file for this run

name: Open Pryv.io v2 CI
on:
pull_request:
push:
branches: [master]
# Tag pushes trigger the same test+lint+typecheck jobs as master
# pushes so the release moment is gated by the same checks. The
# docker job below additionally requires `refs/tags/*` so it only
# publishes on tag pushes, never on master pushes.
tags: ['*']
jobs:
# PostgreSQL is the default baseStorage engine; SQLite is the
# alternative. CI runs the PG matrix sequentially.
test-postgres:
runs-on: ubuntu-22.04
timeout-minutes: 40
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: pryv
POSTGRES_PASSWORD: pryv
POSTGRES_DB: pryv-node-test
ports:
- 5432:5432
# pg_isready WITHOUT -d defaults to db=$USER (pryv), which is not
# created — every healthcheck triggered a backend fork that hit
# `FATAL: database "pryv" does not exist` (visible 5s-interval
# spam in PG service logs). Hypothesis: that churn (~615 PG-touching
# processes / 5s window during test startup) contributed to the
# first pool.connect() ECONNRESET that broke test-postgres since
# 2026-05-21. Probing the real test DB stops the spam and avoids
# backend churn during the bootstrap window.
options: >-
--health-cmd "pg_isready -U pryv -d pryv-node-test"
--health-interval 5s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: extractions/setup-just@v4
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Setup dev environment
run: just setup-dev-env
- name: Install InfluxDB
run: |
wget -q https://dl.influxdata.com/influxdb/releases/influxdb-1.8.10_linux_amd64.tar.gz
tar xzf influxdb-1.8.10_linux_amd64.tar.gz
sudo cp influxdb-1.8.10-1/usr/bin/influx* /usr/local/bin/
- name: Install dependencies
run: npm install --ignore-scripts && npm rebuild
- name: Start services
run: |
./storages/engines/rqlite/scripts/start &
influxd &
sleep 10
- name: Warm up PostgreSQL connection
# The first pg-pool connect from the test runner sporadically returns
# ECONNRESET (broken on master since 2026-05-21). Doing one real client
# connection here lets PG fork and warm its connection-handling path
# so the test runner's pool sees a stable backend.
env:
PGPASSWORD: pryv
run: |
for i in 1 2 3 4 5; do
if psql -h 127.0.0.1 -p 5432 -U pryv -d pryv-node-test -c "SELECT 1;" > /dev/null 2>&1; then
echo "PG warmup OK on attempt $i"
break
fi
echo "PG warmup attempt $i failed; retrying..."
sleep 2
done
- name: Run PostgreSQL test suite
# CI stays SEQUENTIAL on purpose — parallel mode disables the
# integrity-check beforeEach/afterEach + bypasses the caching
# layer + uses a fallback for cluster_kv (no real IPC). Those
# are three real coverage gaps; sequential exercises them.
# Parallel mode is a local-dev productivity tool
# (`just test-parallel all`), not the matrix-of-record.
run: just clean-test-data && just test all
env:
# PGPASSWORD lets the `dropdb` calls inside `just clean-test-data`
# authenticate cleanly. Without it, dropdb's stdin-password fallback
# has been observed to silently block the whole step on CI runners
# — even when an earlier `psql` connection succeeded.
PGPASSWORD: pryv
storages__engines__postgresql__host: localhost
storages__engines__postgresql__port: 5432
storages__engines__postgresql__user: pryv
storages__engines__postgresql__password: pryv
storages__engines__postgresql__database: pryv-node-test
lint:
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
# Cannot use --omit=optional: ESLint 9's import-x plugin pulls in
# `unrs-resolver`, which loads its platform-specific native binding
# (e.g. @unrs/resolver-binding-linux-x64-gnu) via npm's optional
# dependencies mechanism. Skipping optionals leaves the binding
# missing and ESLint exits with "Cannot find native binding".
run: npm install --ignore-scripts
- name: Lint
run: npx eslint .
- name: Production-dependency integrity
# No runtime TOP-LEVEL require of a dev-only package (pruned by
# `--omit=dev`, which crash-loops the production image). CI installs
# devDependencies, so eslint/tests never see this class — rc.7 shipped
# a top-level require('cuid') (a devDependency) and was dead on arrival
# (#106). This static gate catches it without a prod build.
run: ./scripts/prod-dep-integrity
typecheck:
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: extractions/setup-just@v4
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm install --ignore-scripts
- name: Typecheck
run: just typecheck
- name: Type coverage floor
run: just type-coverage
# Ground-truth guard against the #106 class: a REAL production install
# (`--omit=dev`, which prunes devDependencies) + verify every external
# package that runtime code top-level-requires still resolves. rc.7 shipped
# a top-level require('cuid') (a devDependency) and crash-looped on prod
# because CI only ever installed WITH devDependencies. The `docker` release
# job below `needs` this, so a prod-broken build can never publish an image.
prod-boot-smoke:
runs-on: ubuntu-22.04
timeout-minutes: 8
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Production install (--omit=dev — devDependencies pruned)
run: npm install --omit=dev --ignore-scripts
- name: Production boot smoke
run: ./scripts/prod-boot-smoke
# Runtime-dependency vulnerability gate. Fails on any high/critical advisory
# affecting the SHIPPED (--omit=dev) dependency tree, so a freshly-disclosed
# CVE breaks the build instead of only showing up in the Dependabot tab.
# devDependencies are out of scope (pruned from the image). Accepted
# advisories are documented in the script's allowlist, not silenced.
security-audit:
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
run: npm install --ignore-scripts
- name: Audit runtime dependencies
run: ./scripts/audit-prod-deps
# Software Bill of Materials + composition scan. Emits a CycloneDX SBOM for
# the source tree (published as a build artifact for downstream consumers /
# procurement) and runs Grype against it. The scan fails only on CRITICAL so
# it does not duplicate the high-severity gate above; high/medium are
# reported as annotations. Container-image SBOM is emitted in the docker job.
sbom:
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Generate CycloneDX SBOM (source)
uses: anchore/sbom-action@v0
with:
path: .
format: cyclonedx-json
output-file: sbom.cyclonedx.json
artifact-name: sbom-source.cyclonedx.json
- name: Scan SBOM with Grype (fail on critical)
uses: anchore/scan-action@v6
with:
sbom: sbom.cyclonedx.json
fail-build: true
severity-cutoff: critical
# OFFICIAL release path: tag-push → CI runs tests → builds + pushes
# `pryvio/open-pryv.io:<tag>` + rolling `:2.0.0-pre` to Docker Hub.
# This is the channel implementers + downstream consumers pull from.
#
# SIDE CHANNEL for in-house iteration (NOT a release): build on mbp2
# + tar+scp to test-temp.datasafe.dev for quick smoke without going
# through Docker Hub:
# ./_local/scripts/rc-build-and-push.sh [--tag <tag>] [--force]
# ./_local/scripts/rc-smoketest.sh [--host <fqdn>]
# The side channel doesn't replace the tag-push release; it's just
# the dev-loop tool while iterating on RC fixes.
docker:
needs: [test-postgres, lint, typecheck, prod-boot-smoke, security-audit, sbom]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-22.04
# id-token: write enables keyless (OIDC) cosign signing + SLSA provenance —
# no long-lived signing key is stored; the identity is this workflow run.
# attestations: write lets actions/attest-build-provenance record the
# provenance attestation against the image.
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v6
- uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v7
id: build_stock
with:
context: .
push: true
# Bake the git tag into the image as PRYV_IMAGE_TAG so the install
# wizard's generated run-pryv.sh + check-config.sh launchers default
# their PRYV_IMAGE to the same tag the container was pulled from.
build-args: |
IMAGE_TAG=${{ github.ref_name }}
tags: |
pryvio/open-pryv.io:${{ github.ref_name }}
pryvio/open-pryv.io:2.0.0-pre
# Container-image SBOM (CycloneDX) for the released image, published as a
# build artifact so downstream consumers / procurement can ingest the
# exact shipped composition (base image layers + npm tree).
- name: Generate image SBOM (CycloneDX)
uses: anchore/sbom-action@v0
with:
image: pryvio/open-pryv.io@${{ steps.build_stock.outputs.digest }}
format: cyclonedx-json
output-file: sbom-image.cyclonedx.json
artifact-name: sbom-image.cyclonedx.json
# Keyless (OIDC) signature over the pushed image digest. Pullers verify
# with: cosign verify pryvio/open-pryv.io@<digest>
# --certificate-identity-regexp 'https://github.com/pryv/open-pryv.io/.*'
# --certificate-oidc-issuer https://token.actions.githubusercontent.com
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Sign image (keyless)
run: cosign sign --yes pryvio/open-pryv.io@${{ steps.build_stock.outputs.digest }}
# SLSA build provenance attestation for the released image, signed keyless
# and recorded so consumers can verify how + where the image was built.
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: docker.io/pryvio/open-pryv.io
subject-digest: ${{ steps.build_stock.outputs.digest }}
push-to-registry: true
# Encryption-at-rest variant: layers the container-encrypted-volume payload
# onto the stock image just pushed above (base image unchanged; encryption
# opt-in via CEV_ENABLED at runtime). The payload is pulled from Docker Hub
# (pryvio/container-encrypted-volume), public + same registry as this image.
- name: Build + push encryption-at-rest variant
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.encrypted
push: true
build-args: |
IMAGE_TAG=${{ github.ref_name }}
tags: |
pryvio/open-pryv.io-encrypted:${{ github.ref_name }}
pryvio/open-pryv.io-encrypted:2.0.0-pre