openvibers/openvibe.sdk Commits

openvibe-sdk 0.166.0: test-services.sh without Docker (#252)

OP OpenVibers committed 21 min ago · 0557d2bbb5 · 9 files, +169 −12

* openvibe-sdk 0.166.0: test-services.sh without Docker (the same services from local binaries)

A check job on a shared node runs a repository's Actions job with act in host mode: no Docker daemon, no root.
Nearly every repository's test line starts with test-services.sh up, which could not start its containers there.
With no working Docker, up now starts PostgreSQL 18 (SCRAM, fsync off), PgBouncer (transaction mode, each test
role looked up with auth_query, SCRAM passed through) and Valkey from local binaries as the current user, under
one throwaway directory, on the same ports, and prints the same env. A second up reuses them; down stops them
(waiting until each exits) and removes the directory. OV_TEST_SERVICES=docker|local chooses and
OV_TEST_SERVICES_PATH limits the search. Verified on ov-node-01 (no Docker, unprivileged): testing-db, db,
pg-outbox, db-ambient, valkey and govern ran against them with nothing skipped.

* README: v0.166.0 install and status lines

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 62f74ec..526a196 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,23 @@
# Changelog
++## 0.166.0 — 2026-11-13
++
++**`scripts/test-services.sh` without Docker: the same services from local binaries** (CI checks on shared nodes). A
++check job on a shared node runs a repository's GitHub Actions job with act in host mode, with no Docker daemon and no
++root, so `eval "$(node_modules/openvibe-sdk/scripts/test-services.sh up)" && npm test && npm run test:pg`, the test line
++of nearly every OpenVibers repository, could not start its containers there.
++
++- With no working Docker (`docker info` does not answer), `up` starts PostgreSQL 18 (initdb, SCRAM, fsync off),
++ PgBouncer (transaction mode; each test role is looked up with `auth_query` and SCRAM is passed through) and Valkey from
++ local binaries, as the current user, under one throwaway directory (`OV_TEST_SERVICES_DIR`, default
++ `$TMPDIR/ovsdk-services-<uid>`), on the same ports with the same users, and prints the same three variables. A second
++ `up` reuses what runs; `down` stops them and removes the directory (and the containers, when Docker is there).
++- `OV_TEST_SERVICES=docker|local` chooses; `OV_TEST_SERVICES_PATH` limits where the binaries are looked for. Ubuntu:
++ `apt install postgresql-18 pgbouncer valkey-server` (their own services can stay off).
++- Verified on ov-node-01 as an unprivileged user with no Docker: `testing-db`, `db`, `pg-outbox`, `db-ambient`,
++ `valkey` and `govern` all ran against the local services (no case skipped) and `down` left nothing behind. Tests:
++ `test-services` (no Docker and no binaries says what is missing and starts nothing; `OV_TEST_SERVICES` overrides).
++
## 0.165.0 — 2026-11-13
**`openvibe-sdk/agents/soul`: who an agent is** (agents.soul@1, Contracts 0.424.0; Design rules 175
ddiff --git a/README.md b/README.md
iindex 3f80dd3..e02d749 100644
---- a/README.md
++++ b/README.md
@@@ -8,12 +8,12 @@
**Install and test:** `npm ci && npm test` (Node 20 or newer)
```bash
--npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.165.0
++npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.166.0
```
> Supported browser and server clients for the OpenVibe platform.
--**Status:** alpha, v0.165.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
++**Status:** alpha, v0.166.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30.
**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.
@@@ -22,7 +22,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
After v0.97.1 is tagged:
```bash
--npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.165.0
++npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.166.0
```
It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).
ddiff --git a/STATUS.json b/STATUS.json
iindex 6777821..9d4d6eb 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -3,7 +3,7 @@
"stage": "alpha",
"code": true,
"wave": "W2",
-- "version": "0.165.0",
++ "version": "0.166.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
ddiff --git a/browser/openvibe-sdk.mjs b/browser/openvibe-sdk.mjs
iindex 9d23575..413a35c 100644
---- a/browser/openvibe-sdk.mjs
++++ b/browser/openvibe-sdk.mjs
@@@ -1,4 +1,4 @@
--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.165.0, MIT). Do not edit.
++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.166.0, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// (auth, locker, registry, trust, levels, modules, realtime, media, composer, cam, mediaUpload, ui, uiLayout, community, homeFeed, feed, views, bot, search, bench, meme, vision, themes, elements, design, redact, chat, ai, credentials, jobs, agents, frame, tools, projects, geo, history, userCard, player, agentSheet). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
@@@ -68,7 +68,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
--const SDK_VERSION = '0.165.0';
++const SDK_VERSION = '0.166.0';
module.exports = {
createClient,
ddiff --git a/package-lock.json b/package-lock.json
iindex 8d7b9f1..67bd79a 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-sdk",
-- "version": "0.165.0",
++ "version": "0.166.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-sdk",
-- "version": "0.165.0",
++ "version": "0.166.0",
"license": "MIT",
"bin": {
"openvibe-download": "bin/openvibe-download.js"
ddiff --git a/package.json b/package.json
iindex c1c7bbf..0ca8cd9 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-sdk",
-- "version": "0.165.0",
++ "version": "0.166.0",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"main": "index.js",
"types": "types/index.d.ts",
ddiff --git a/scripts/test-services.sh b/scripts/test-services.sh
iindex 0df25af..a7502e0 100755
---- a/scripts/test-services.sh
++++ b/scripts/test-services.sh
@@@ -13,10 +13,116 @@
# Every test process gets a role of its own, so PgBouncer holds a pool per role: idle server connections close after
# 5 s and one database takes at most 120, or a service with many test files runs PostgreSQL out of connections.
# ovsdk-pg gets a 1 GB /dev/shm: Docker's 64 MB default overflows when parallel test databases resize shared memory (53100).
++#
++# Without Docker (a check job on a shared node: GitHub Actions' workflow run by act in host mode, no daemon and no root)
++# the same three services start from local binaries as the current user, under one throwaway directory
++# (OV_TEST_SERVICES_DIR, default $TMPDIR/ovsdk-services-<uid>), on the same ports with the same users, and `up` prints
++# the same env: PostgreSQL 18 (initdb, SCRAM, fsync off), PgBouncer (transaction mode; each test role is looked up with
++# auth_query, SCRAM passed through) and Valkey. OV_TEST_SERVICES=docker|local chooses; by default Docker is used when
++# `docker info` answers. Ubuntu: apt install postgresql-18 pgbouncer valkey-server (their own services can stay off).
set -euo pipefail
NET=ovsdk-test; PW=ovtestpw
++
++# The first executable called $1 in the places PostgreSQL, PgBouncer and Valkey install to (pgbouncer is in /usr/sbin),
++# or only in OV_TEST_SERVICES_PATH's directories (colon-separated) when that is set.
++local_bin() {
++ local d dirs
++ if [ -n "${OV_TEST_SERVICES_PATH:-}" ]; then
++ IFS=: read -r -a dirs <<< "$OV_TEST_SERVICES_PATH"
++ else
++ dirs=(/usr/lib/postgresql/18/bin /usr/lib/postgresql/*/bin /usr/local/pgsql/bin /usr/sbin /usr/local/sbin /usr/local/bin /usr/bin /opt/homebrew/bin)
++ fi
++ for d in "${dirs[@]}"; do
++ if [ -x "$d/$1" ]; then echo "$d/$1"; return 0; fi
++ done
++ if [ -z "${OV_TEST_SERVICES_PATH:-}" ]; then command -v "$1" 2>/dev/null || true; fi
++}
++mode() {
++ case "${OV_TEST_SERVICES:-}" in docker|local) echo "$OV_TEST_SERVICES"; return ;; esac
++ if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then echo docker; else echo local; fi
++}
++LOCAL_DIR="${OV_TEST_SERVICES_DIR:-${TMPDIR:-/tmp}/ovsdk-services-$(id -u)}"
++port_open() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; }
++pid_alive() { [ -s "$1" ] && kill -0 "$(cat "$1")" 2>/dev/null; }
++
++local_up() {
++ local D="$LOCAL_DIR" initdb pgctl psql pgb vk missing="" i
++ initdb=$(local_bin initdb); pgctl=$(local_bin pg_ctl); psql=$(local_bin psql); pgb=$(local_bin pgbouncer); vk=$(local_bin valkey-server)
++ [ -n "$initdb" ] && [ -n "$pgctl" ] && [ -n "$psql" ] || missing="$missing postgresql-18"
++ [ -n "$pgb" ] || missing="$missing pgbouncer"
++ [ -n "$vk" ] || missing="$missing valkey-server"
++ if [ -n "$missing" ]; then
++ echo "test-services: no Docker, and no local$missing to start instead" >&2
++ exit 1
++ fi
++ mkdir -p "$D"; chmod 700 "$D"
++ if ! port_open 55432; then
++ if [ ! -s "$D/pg/PG_VERSION" ]; then
++ printf '%s\n' "$PW" > "$D/pw"
++ "$initdb" -D "$D/pg" -U ov --pwfile="$D/pw" --auth-host=scram-sha-256 --auth-local=scram-sha-256 -E UTF8 --no-instructions >/dev/null
++ rm -f "$D/pw"
++ fi
++ "$pgctl" -D "$D/pg" -l "$D/pg.log" -w -t 60 -o "-c listen_addresses=127.0.0.1 -c port=55432 -c unix_socket_directories=$D -c max_connections=200 -c dynamic_shared_memory_type=mmap -c fsync=off -c synchronous_commit=off -c full_page_writes=off" start >/dev/null
++ if ! PGPASSWORD=$PW "$psql" -h 127.0.0.1 -p 55432 -U ov -d postgres -Atqc "SELECT 1 FROM pg_database WHERE datname = 'ovtest'" | grep -q 1; then
++ PGPASSWORD=$PW "$psql" -h 127.0.0.1 -p 55432 -U ov -d postgres -qc "CREATE DATABASE ovtest" >/dev/null
++ fi
++ fi
++ if ! pid_alive "$D/pgbouncer.pid"; then
++ printf '"ov" "%s"\n' "$PW" > "$D/userlist.txt"
++ cat > "$D/pgbouncer.ini" <<INI
++[databases]
++* = host=127.0.0.1 port=55432
++[pgbouncer]
++listen_addr = 127.0.0.1
++listen_port = 56432
++unix_socket_dir =
++auth_type = scram-sha-256
++auth_file = $D/userlist.txt
++auth_user = ov
++auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename = \$1
++pool_mode = transaction
++max_client_conn = 500
++default_pool_size = 10
++max_db_connections = 120
++server_idle_timeout = 5
++ignore_startup_parameters = extra_float_digits
++pidfile = $D/pgbouncer.pid
++logfile = $D/pgbouncer.log
++INI
++ "$pgb" -d -q "$D/pgbouncer.ini"
++ fi
++ if ! pid_alive "$D/valkey.pid"; then
++ "$vk" --bind 127.0.0.1 --port 56379 --save "" --appendonly no --daemonize yes --dir "$D" --pidfile "$D/valkey.pid" --logfile "$D/valkey.log" >/dev/null
++ fi
++ for i in $(seq 1 40); do port_open 56432 && port_open 56379 && break; sleep 0.25; done
++ port_open 56432 || { echo "test-services: PgBouncer did not start (see $D/pgbouncer.log)" >&2; exit 1; }
++ port_open 56379 || { echo "test-services: Valkey did not start (see $D/valkey.log)" >&2; exit 1; }
++}
++
++local_down() {
++ local D="$LOCAL_DIR" pgctl
++ [ -d "$D" ] || return 0
++ pgctl=$(local_bin pg_ctl)
++ if [ -n "$pgctl" ] && [ -s "$D/pg/postmaster.pid" ]; then "$pgctl" -D "$D/pg" -m immediate stop >/dev/null 2>&1 || true; fi
++ local f pid i
++ for f in "$D/pgbouncer.pid" "$D/valkey.pid"; do
++ pid_alive "$f" || continue
++ pid=$(cat "$f"); kill "$pid" 2>/dev/null || true
++ # Wait for it to exit: it may still be writing its log while the directory is removed.
++ for i in $(seq 1 40); do kill -0 "$pid" 2>/dev/null || break; sleep 0.125; done
++ done
++ rm -rf "$D" 2>/dev/null || { sleep 0.5; rm -rf "$D"; }
++}
++
case "${1:-up}" in
up)
++ if [ "$(mode)" = local ]; then
++ local_up
++ echo "export OV_TEST_PG_URL=postgres://ov:[email protected]:56432/ovtest"
++ echo "export OV_TEST_PG_DIRECT_URL=postgres://ov:[email protected]:55432/ovtest"
++ echo "export OV_TEST_VALKEY_URL=redis://127.0.0.1:56379/0"
++ exit 0
++ fi
docker network inspect $NET >/dev/null 2>&1 || docker network create $NET >/dev/null
if docker inspect ovsdk-pg >/dev/null 2>&1 && [ "$(docker inspect -f '{{.HostConfig.ShmSize}}' ovsdk-pg)" -lt 1073741824 ]; then
# A reused ovsdk-pg from before --shm-size has the 64 MB default; remove it and PgBouncer (which points at it) so both start again.
@@@ -45,6 +151,11 @@ up)
echo "export OV_TEST_PG_DIRECT_URL=postgres://ov:[email protected]:55432/ovtest"
echo "export OV_TEST_VALKEY_URL=redis://127.0.0.1:56379/0"
;;
--down) docker rm -f -v ovsdk-pg ovsdk-pgbouncer ovsdk-valkey >/dev/null 2>&1 || true; docker network rm $NET >/dev/null 2>&1 || true; echo removed ;;
++down)
++ local_down
++ if command -v docker >/dev/null 2>&1; then
++ docker rm -f -v ovsdk-pg ovsdk-pgbouncer ovsdk-valkey >/dev/null 2>&1 || true; docker network rm $NET >/dev/null 2>&1 || true
++ fi
++ echo removed ;;
*) echo "usage: $0 up|down" >&2; exit 2 ;;
esac
ddiff --git a/src/core/index.js b/src/core/index.js
iindex 6110a2e..fc23d2b 100644
---- a/src/core/index.js
++++ b/src/core/index.js
@@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
--const SDK_VERSION = '0.165.0';
++const SDK_VERSION = '0.166.0';
module.exports = {
createClient,
ddiff --git a/test/test-services.test.js b/test/test-services.test.js
iindex 28433c0..34f1feb 100644
---- a/test/test-services.test.js
++++ b/test/test-services.test.js
@@@ -1,5 +1,6 @@
'use strict';
--/** Exercise the test service lifecycle with a Docker stub; no containers are started. */
++/** Exercise the test service lifecycle with a Docker stub; no containers are started. Without a working Docker the
++ * script starts the services from local binaries (a shared node's check job); here it finds none and says so. */
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
@@@ -13,6 +14,7 @@ fs.writeFileSync(path.join(dir, 'docker'), `#!/usr/bin/env node
const fs = require('node:fs');
const args = process.argv.slice(2);
fs.appendFileSync(process.env.DOCKER_TRACE, JSON.stringify(args) + '\\n');
++if (args[0] === 'info' && process.env.DOCKER_INFO_FAIL) process.exit(1);
if (args[0] === 'inspect' && args[1] === '-f') console.log(process.env.TEST_SHM || 1073741824);
if (args[0] === 'ps') console.log('ovsdk-pg\\novsdk-pgbouncer\\novsdk-valkey');
` , { mode: 0o755 });
@@@ -27,7 +29,33 @@ function up(extra = {}) {
return fs.readFileSync(trace, 'utf8').trim().split('\n').map(JSON.parse);
}
++function upLocal(extra = {}) {
++ fs.writeFileSync(trace, '');
++ const empty = fs.mkdtempSync(path.join(dir, 'no-bins-'));
++ const result = spawnSync('bash', [path.join(__dirname, '../scripts/test-services.sh'), 'up'], {
++ encoding: 'utf8',
++ env: { ...process.env, PATH: `${dir}:${process.env.PATH}`, DOCKER_TRACE: trace, OV_TEST_SERVICES_PATH: empty,
++ OV_TEST_SERVICES_DIR: path.join(dir, 'services'), ...extra },
++ });
++ return { result, calls: fs.readFileSync(trace, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse) };
++}
++
run([
++ ['without a working Docker, up starts nothing in Docker and names the local services it needs', () => {
++ const { result, calls } = upLocal({ DOCKER_INFO_FAIL: '1' });
++ assert.equal(result.status, 1);
++ assert.match(result.stderr, /no Docker, and no local postgresql-18 pgbouncer valkey-server/);
++ assert.equal(result.stdout, '');
++ assert.equal(calls.some((a) => a[0] === 'run'), false);
++ }],
++ ['OV_TEST_SERVICES=local skips Docker even when it answers; =docker keeps it', () => {
++ const local = upLocal({ OV_TEST_SERVICES: 'local' });
++ assert.equal(local.result.status, 1);
++ assert.deepEqual(local.calls, []);
++ const docker = upLocal({ OV_TEST_SERVICES: 'docker', DOCKER_INFO_FAIL: '1' });
++ assert.equal(docker.result.status, 0, docker.result.stderr);
++ assert.match(docker.result.stdout, /export OV_TEST_PG_URL=postgres:\/\/ov:ovtestpw@127\.0\.0\.1:56432\/ovtest/);
++ }],
['up preserves a reused database even when no test has an open query connection', () => {
const calls = up();
assert.equal(calls.some((a) => a.includes('psql') || a.includes('rm')), false);