openvibers/openvibe.sdk Commits
openvibe-sdk 0.166.0: test-services.sh without Docker (#252)
Changes
@@@ -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@@@ -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/tagsAfter 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).@@@ -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",@@@ -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,@@@ -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"@@@ -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",@@@ -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 pipefailNET=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}" inup)++ 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++ fidocker network inspect $NET >/dev/null 2>&1 || docker network create $NET >/dev/nullif 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@@@ -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,@@@ -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 nodeconst 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);
@@ -1,5 +1,23 @@
@@ -1,5 +1,23 @@
# Changelog
# 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
## 0.165.0 — 2026-11-13
**`openvibe-sdk/agents/soul`: who an agent is** (agents.soul@1, Contracts 0.424.0; Design rules 175
**`openvibe-sdk/agents/soul`: who an agent is** (agents.soul@1, Contracts 0.424.0; Design rules 175
@@ -8,12 +8,12 @@
@@ -8,12 +8,12 @@
**Install and test:** `npm ci && npm test` (Node 20 or newer)
**Install and test:** `npm ci && npm test` (Node 20 or newer)
```bash
```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.
> 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.
**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.
**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
@@ -22,7 +22,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
After v0.97.1 is tagged:
After v0.97.1 is tagged:
```bash
```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).
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).
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
"stage": "alpha",
"stage": "alpha",
"code": true,
"code": true,
"wave": "W2",
"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",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
"deployment": "none: a package released as git tags; services and apps pin a tag",
@@ -1,4 +1,4 @@
@@ -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
// 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.
// (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 */
/* eslint-disable */
@@ -68,7 +68,7 @@ const { paginate, offsetPager } = __load(7);
@@ -68,7 +68,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
/** 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 = {
module.exports = {
createClient,
createClient,
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.165.0",
+ "version": "0.166.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.165.0",
+ "version": "0.166.0",
"license": "MIT",
"license": "MIT",
"bin": {
"bin": {
"openvibe-download": "bin/openvibe-download.js"
"openvibe-download": "bin/openvibe-download.js"
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"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.",
"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",
"main": "index.js",
"types": "types/index.d.ts",
"types": "types/index.d.ts",
@@ -13,10 +13,116 @@
@@ -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
# 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.
# 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).
# 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
set -euo pipefail
NET=ovsdk-test; PW=ovtestpw
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
case "${1:-up}" in
up)
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
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
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.
# 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)
@@ -45,6 +151,11 @@ up)
echo "export OV_TEST_PG_DIRECT_URL=postgres://ov:[email protected]:55432/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"
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 ;;
*) echo "usage: $0 up|down" >&2; exit 2 ;;
esac
esac
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
/** 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 = {
module.exports = {
createClient,
createClient,
@@ -1,5 +1,6 @@
@@ -1,5 +1,6 @@
'use strict';
'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 assert = require('node:assert/strict');
const fs = require('node:fs');
const fs = require('node:fs');
const os = require('node:os');
const os = require('node:os');
@@ -13,6 +14,7 @@ fs.writeFileSync(path.join(dir, 'docker'), `#!/usr/bin/env node
@@ -13,6 +14,7 @@ fs.writeFileSync(path.join(dir, 'docker'), `#!/usr/bin/env node
const fs = require('node:fs');
const fs = require('node:fs');
const args = process.argv.slice(2);
const args = process.argv.slice(2);
fs.appendFileSync(process.env.DOCKER_TRACE, JSON.stringify(args) + '\\n');
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] === 'inspect' && args[1] === '-f') console.log(process.env.TEST_SHM || 1073741824);
if (args[0] === 'ps') console.log('ovsdk-pg\\novsdk-pgbouncer\\novsdk-valkey');
if (args[0] === 'ps') console.log('ovsdk-pg\\novsdk-pgbouncer\\novsdk-valkey');
` , { mode: 0o755 });
` , { mode: 0o755 });
@@ -27,7 +29,33 @@ function up(extra = {}) {
@@ -27,7 +29,33 @@ function up(extra = {}) {
return fs.readFileSync(trace, 'utf8').trim().split('\n').map(JSON.parse);
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([
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', () => {
['up preserves a reused database even when no test has an open query connection', () => {
const calls = up();
const calls = up();
assert.equal(calls.some((a) => a.includes('psql') || a.includes('rm')), false);
assert.equal(calls.some((a) => a.includes('psql') || a.includes('rm')), false);