openvibers/openvibe.tools Commits

openvibe-contracts v0.33.1 (gateway, img, audio, docs) and openvibe-shared v1.5.1 (every app). Descriptors now publish the catalogue's keywords (every tool; what ?q= matches) and each API tool's example as examples[] (145 tools; a job tool's with sample files that fit files.min and files.accept), which v0.33.1 validates and v0.33.0 rejected; no API, no examples. Every app mounts release.mount (GET /release.json and the navbar's POST /release-metrics into release_client_updates_total) through apps/_shared/release.js, which declares each app's components: shell (public/, the server files that stamp its pages, the job helper) and server (the rest), so an API-only release no longer reloads open tabs; the gateway, img, audio and docs serve manifest 1.1.0 validated against their own contracts (the release-manifest schema passed explicitly: the release root is the repository, which has no node_modules), maps, food, text and yt the 1.0.0 fields. Tests: release.test.js (components valid per app, mount, metrics), descriptors.test.js (keywords and examples published)

OP OpenVibers committed 24 Sept 2026 · 5ac8309e5a · 31 files, +266 −81


Changes

ddiff --git a/README.md b/README.md
iindex 19e530c..b75b48f 100644
---- a/README.md
++++ b/README.md
@@@ -364,7 +364,15 @@ Every server (gateway and satellites) mounts `apps/_shared/observe.js` with `ope
| food (4011) | `maps` (every food API is proxied to it) | `analytics_db` |
| maps, text (4010, 4015) | — | `analytics_db` (maps' external data sources are not probed) |
--`TOOLS_SATELLITE_PORTS="img=5012,…"` overrides the gateway's satellite ports (tests, a moved unit).
++`TOOLS_SATELLITE_PORTS="img=5012,…"` overrides the gateway's satellite ports (tests, a moved unit); every app reads
++it (`apps/_shared/tools/satellites.js`), so set it for all units if a port moves.
++
++**Releases (ADR-016).** Every app mounts openvibe-shared's `release.mount` (`apps/_shared/release.js`): `GET
++/release.json` and `POST /release-metrics` (the shared navbar's release-watch outcomes →
++`release_client_updates_total` on `/metrics`). The gateway, img, audio and docs (which pin openvibe-contracts) serve
++manifest 1.1.0 components: `shell` (what their pages are made of: `public/`, the server files that stamp the pages,
++the job helper) and `server` (the rest), so a release that changes only API code no longer reloads open tabs. Maps,
++food, text and yt serve the 1.0.0 fields.
## Tests
ddiff --git a/apps/_shared/release.js b/apps/_shared/release.js
nnew file mode 100644
iindex 0000000..7d33246
---- /dev/null
++++ b/apps/_shared/release.js
@@@ -0,0 +1,54 @@
++'use strict';
++// ═══════════════════════════════════════════════════════════════
++// What each Tools app says about the deploy it runs: GET /release.json (ADR-016, registry.release-
++// manifest@1) through openvibe-shared/release, mounted with release.mount (it also takes the shared
++// navbar's POST /release-metrics into /metrics).
++//
++// The release is the repository's commit; every app is its own surface, so each one declares its
++// components (manifest 1.1.0, served when the app's openvibe-contracts has that schema):
++// shell what a page is made of (kind script): the app's public/ files, the server files that stamp
++// its pages, and the job helper the pages load (/js/ov-jobs.js) — a change reloads open tabs
++// at a safe moment
++// server everything else the app runs (kind server): an API-only release leaves open tabs alone
++// Before 1.5.0 every release reloaded every tab.
++// ═══════════════════════════════════════════════════════════════
++
++const path = require('path');
++
++const ROOT = path.join(__dirname, '..', '..');
++
++// Per app: the server files that shape its pages (templates, SEO stamping, host branding, catalogue).
++const PAGE_FILES = {
++ gateway: ['server/pages', 'server/seo', 'server/registry', 'server/net/config.js', 'server/dev/config.js'],
++ img: ['server/seo.js', 'server/domain-map.js'],
++ audio: ['server/seo.js', 'server/domain-map.js'],
++ docs: ['server/seo.js', 'server/domain-map.js'],
++ text: ['server/seo.js', 'server/hosts.js'],
++};
++const JOB_APPS = new Set(['img', 'audio', 'docs']);
++
++/**
++ * @param {string} app 'gateway' | 'img' | …
++ * @param {Function} appRequire the app's require (its own openvibe-shared and openvibe-contracts)
++ * @param {object} [opts] passed on to createRelease (tests: env, now, logger)
++ */
++function toolsRelease(app, appRequire, opts = {}) {
++ const { createRelease } = appRequire('openvibe-shared/release');
++ const dir = `apps/${app}`;
++ let schema = null;
++ try { schema = appRequire('openvibe-contracts/contracts/registry/release-manifest.v1.json'); } catch { schema = null; }
++ const components = {
++ shell: {
++ kind: 'script',
++ files: [`${dir}/public`, ...(PAGE_FILES[app] || []).map(f => `${dir}/${f}`), 'apps/_shared/host-role.js', ...(JOB_APPS.has(app) ? ['apps/_shared/jobs/client.js'] : [])],
++ },
++ server: { kind: 'server', files: [`${dir}/server`, 'apps/_shared', `${dir}/package-lock.json`] },
++ };
++ return createRelease({
++ service: 'tools', root: ROOT, publicDir: `${dir}/public`, components,
++ ...(schema && { schema }),
++ ...opts,
++ });
++}
++
++module.exports = { toolsRelease, PAGE_FILES, ROOT };
ddiff --git a/apps/_shared/test/release.test.js b/apps/_shared/test/release.test.js
nnew file mode 100644
iindex 0000000..7e78c60
---- /dev/null
++++ b/apps/_shared/test/release.test.js
@@@ -0,0 +1,56 @@
++'use strict';
++// GET /release.json for each Tools app (apps/_shared/release.js, openvibe-shared ≥ 1.5.1 release.mount):
++// • the four apps that pin openvibe-contracts serve manifest 1.1.0 with their components (shell: what
++// their pages are made of; server: the rest), valid against their own contracts
++// • the others serve the 1.0.0 fields their (absent) contracts allow
++// • mount answers GET /release.json and takes the navbar's POST /release-metrics into /metrics
++const assert = require('assert');
++const path = require('path');
++const { createRequire } = require('module');
++const { toolsRelease } = require('../release');
++
++const APPS = path.join(__dirname, '..', '..');
++const requireOf = (app) => createRequire(path.join(APPS, app, 'server', 'index.js'));
++const quiet = { warn() {}, log() {}, error() {} };
++
++(async () => {
++ for (const app of ['gateway', 'img', 'audio', 'docs']) {
++ const req = requireOf(app);
++ const r = toolsRelease(app, req, { logger: quiet });
++ const m = r.manifest();
++ assert.deepStrictEqual(Object.keys(m.components).sort(), ['server', 'shell'], app);
++ assert.strictEqual(m.components.shell.kind, 'script');
++ assert.strictEqual(m.components.server.kind, 'server');
++ assert.match(m.release, /^[0-9a-f]{7,12}$/);
++ const v = r.validate({ contracts: req('openvibe-contracts') });
++ assert.strictEqual(v.valid, true, `${app}: ${JSON.stringify(v.errors)}`);
++ }
++ for (const app of ['maps', 'food', 'text', 'yt']) {
++ const m = toolsRelease(app, requireOf(app), { logger: quiet }).manifest();
++ assert.ok(!('components' in m), `${app}: no contracts pinned, so the 1.0.0 fields`);
++ assert.strictEqual(m.service, 'tools');
++ }
++
++ // The mount: GET /release.json, POST /release-metrics → release_client_updates_total.
++ const req = requireOf('gateway');
++ const express = req('express');
++ const metrics = req('openvibe-shared/metrics');
++ const app = express();
++ const inst = metrics.instrument(app, { service: 'tools', release: 'test' });
++ const release = toolsRelease('gateway', req, { logger: quiet });
++ release.mount(app, { registry: inst.registry });
++ const srv = await new Promise(res => { const s = app.listen(0, '127.0.0.1', () => res(s)); });
++ const base = `http://127.0.0.1:${srv.address().port}`;
++ try {
++ let r = await fetch(`${base}/release.json`);
++ assert.strictEqual(r.status, 200);
++ const body = await r.json();
++ assert.strictEqual(body.metrics_url, '/release-metrics');
++ assert.ok(body.components.shell.version);
++ r = await fetch(`${base}/release-metrics`, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ counts: { applied: { style: 2 } } }) });
++ assert.strictEqual(r.status, 204);
++ const text = await (await fetch(`${base}/metrics`)).text();
++ assert.match(text, /release_client_updates_total\{outcome="applied",reason="style"\} 2/);
++ } finally { await new Promise(res => srv.close(res)); inst.stop && inst.stop(); }
++ console.log('release: gateway/img/audio/docs serve components (shell, server) valid against their contracts; the others 1.0.0; mount serves /release.json and /release-metrics');
++})().catch((err) => { console.error(err); process.exit(1); });
ddiff --git a/apps/_shared/tools/descriptor.js b/apps/_shared/tools/descriptor.js
iindex d9f6cdf..f263fdd 100644
---- a/apps/_shared/tools/descriptor.js
++++ b/apps/_shared/tools/descriptor.js
@@@ -13,7 +13,8 @@
// route the server route that runs a sync tool today ({ method, path, query })
// requires programs the tool needs on the host ('qpdf', 'heif-dec', …); the satellite marks it
// unavailable while one is missing
--// example { input } that runs: used by the tests, later by the docs
++// example { input } that runs: used by the tests, and published as the descriptor's `examples` (a tool
++// with an API; a job tool's sample files are named after its accepted types)
// cacheTtlMs how long the run API may reuse an inline answer (0: never — random or time-dependent
// output; unset: engines 10 minutes, routes never)
//
@@@ -38,10 +39,43 @@ function summaryOf(rec) {
return /[.!?]$/.test(t) ? t : `${t}.`;
}
++// Sample file names for a job tool's example, by media type (what people would upload).
++const SAMPLE = {
++ 'application/pdf': 'document.pdf', 'image/png': 'image.png', 'image/jpeg': 'photo.jpg', 'image/webp': 'image.webp',
++ 'image/gif': 'animation.gif', 'image/avif': 'image.avif', 'image/tiff': 'scan.tiff', 'image/bmp': 'image.bmp',
++ 'audio/mpeg': 'song.mp3', 'audio/wav': 'recording.wav', 'audio/ogg': 'clip.ogg', 'audio/flac': 'track.flac',
++};
++
++/** The catalogue's search terms, as tools.tool@1 allows them (1–100 characters, unique, at most 50). */
++function keywordsOf(list) {
++ const out = [];
++ for (const k of list || []) {
++ const v = String(k).replace(/\s+/g, ' ').trim();
++ if (v && v.length <= 100 && !out.includes(v)) out.push(v);
++ if (out.length === 50) break;
++ }
++ return out;
++}
++
++/** The spec's example as tools.tool@1 `examples` (API tools only); a job tool's gets sample files. */
++function examplesOf(spec, api) {
++ if (!api || !spec.example || !spec.example.input) return null;
++ const ex = { input: clone(spec.example.input) };
++ const f = spec.files;
++ if (f && f.min > 0) {
++ const mime = (f.accept || []).find(m => !m.endsWith('/*') && SAMPLE[m]) || (f.accept || []).find(m => !m.endsWith('/*'));
++ if (mime) {
++ const name = SAMPLE[mime] || `file.${mime.split('/')[1].replace(/[^a-z0-9]/g, '') || 'bin'}`;
++ ex.files = Array.from({ length: f.min }, (_, i) => ({ name: f.min > 1 ? name.replace(/(\.[^.]+)$/, `-${i + 1}$1`) : name, mime }));
++ }
++ }
++ return [ex];
++}
++
/**
* spec + catalogue facts → a tools.tool@1 descriptor with its schemas embedded.
* @param {object} spec the app's spec (see the header)
-- * @param {object} meta { family, name, summary, hosts: string[], docs?, status?, statusReason? } — a
++ * @param {object} meta { family, name, summary, hosts: string[], keywords?, docs?, status?, statusReason? } — a
* status here (the host is missing a program, the catalogue marks it) wins
*/
function compose(spec, meta) {
@@@ -63,6 +97,10 @@ function compose(spec, meta) {
d.egress = spec.egress === true;
d.hosts = [...(meta.hosts || [])];
d.docs = meta.docs || docsUrl(spec.id);
++ const keywords = keywordsOf(meta.keywords);
++ if (keywords.length) d.keywords = keywords;
++ const examples = examplesOf(spec, api);
++ if (examples) d.examples = examples;
return d;
}
ddiff --git a/apps/_shared/tools/local.js b/apps/_shared/tools/local.js
iindex 473606b..688826f 100644
---- a/apps/_shared/tools/local.js
++++ b/apps/_shared/tools/local.js
@@@ -47,7 +47,7 @@ function createLocalRegistry(o) {
const tools = rows.map(({ spec, c }, i) => {
const h = defaultHosts(spec.id);
return compose(spec, {
-- family: c.family, name: c.name, summary: summaryOf(c),
++ family: c.family, name: c.name, summary: summaryOf(c), keywords: c.keywords,
hosts: [h.canonical, h.short].filter(Boolean),
...(live[i] && { status: live[i].status, statusReason: live[i].statusReason }),
});
ddiff --git a/apps/audio/package-lock.json b/apps/audio/package-lock.json
iindex 477ba36..d4554b5 100644
---- a/apps/audio/package-lock.json
++++ b/apps/audio/package-lock.json
@@@ -19,9 +19,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"uuid": "^11.1.0"
}
},
@@@ -1159,9 +1159,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.33.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==",
++ "version": "0.33.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "integrity": "sha512-0S6SubDzdBmmIMbb4x1gWjWieqCl9W6nuvKrv/voUBpEvJChMGKlRmWnhLQV0KU7IZGxZcDahoYe9vI03pmf+A==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1184,9 +1184,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/audio/package.json b/apps/audio/package.json
iindex d7d2e79..a60c91e 100644
---- a/apps/audio/package.json
++++ b/apps/audio/package.json
@@@ -20,9 +20,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"uuid": "^11.1.0"
}
}
ddiff --git a/apps/audio/server/index.js b/apps/audio/server/index.js
iindex 30d71b7..3313d2b 100644
---- a/apps/audio/server/index.js
++++ b/apps/audio/server/index.js
@@@ -61,7 +61,7 @@ const toolOf = (req) => { const d = guard.toolForJob('audio.process', String((re
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('audio', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready, which } = require('../../_shared/observe');
@@@ -82,7 +82,9 @@ const obs = observe({
jobs: () => jobs,
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/docs/package-lock.json b/apps/docs/package-lock.json
iindex 2b802b8..63a192a 100644
---- a/apps/docs/package-lock.json
++++ b/apps/docs/package-lock.json
@@@ -18,9 +18,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"pdf-lib": "^1.17.1",
"sharp": "^0.33.5",
"uuid": "^11.1.0"
@@@ -1238,9 +1238,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.33.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==",
++ "version": "0.33.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "integrity": "sha512-0S6SubDzdBmmIMbb4x1gWjWieqCl9W6nuvKrv/voUBpEvJChMGKlRmWnhLQV0KU7IZGxZcDahoYe9vI03pmf+A==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1263,9 +1263,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/docs/package.json b/apps/docs/package.json
iindex 855fdfd..4540a0b 100644
---- a/apps/docs/package.json
++++ b/apps/docs/package.json
@@@ -19,9 +19,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"pdf-lib": "^1.17.1",
"sharp": "^0.33.5",
"uuid": "^11.1.0"
ddiff --git a/apps/docs/server/index.js b/apps/docs/server/index.js
iindex 8f4dbec..e53fbec 100644
---- a/apps/docs/server/index.js
++++ b/apps/docs/server/index.js
@@@ -78,7 +78,7 @@ async function readUploads(files) {
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('docs', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready } = require('../../_shared/observe');
@@@ -99,7 +99,9 @@ const obs = observe({
jobs: () => jobs,
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/food/package-lock.json b/apps/food/package-lock.json
iindex 5413ffc..0c54732 100644
---- a/apps/food/package-lock.json
++++ b/apps/food/package-lock.json
@@@ -15,7 +15,7 @@
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
},
"node_modules/accepts": {
@@@ -968,9 +968,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/food/package.json b/apps/food/package.json
iindex 819f4b9..477671d 100644
---- a/apps/food/package.json
++++ b/apps/food/package.json
@@@ -16,6 +16,6 @@
"better-sqlite3": "^11.0.0",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
}
ddiff --git a/apps/food/server/index.js b/apps/food/server/index.js
iindex b4dad7f..eaf801b 100644
---- a/apps/food/server/index.js
++++ b/apps/food/server/index.js
@@@ -42,7 +42,7 @@ const MAPS_API = process.env.MAPS_API || 'http://127.0.0.1:4010';
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('food', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready } = require('../../_shared/observe');
@@@ -56,7 +56,9 @@ const obs = observe({
],
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/gateway/package-lock.json b/apps/gateway/package-lock.json
iindex 478084c..df66684 100644
---- a/apps/gateway/package-lock.json
++++ b/apps/gateway/package-lock.json
@@@ -17,8 +17,8 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
},
"node_modules/accepts": {
@@@ -1035,9 +1035,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.33.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==",
++ "version": "0.33.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "integrity": "sha512-0S6SubDzdBmmIMbb4x1gWjWieqCl9W6nuvKrv/voUBpEvJChMGKlRmWnhLQV0KU7IZGxZcDahoYe9vI03pmf+A==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1051,9 +1051,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/gateway/package.json b/apps/gateway/package.json
iindex c17d29b..15e68cb 100644
---- a/apps/gateway/package.json
++++ b/apps/gateway/package.json
@@@ -18,7 +18,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
}
ddiff --git a/apps/gateway/server/index.js b/apps/gateway/server/index.js
iindex 17dac2e..bfef264 100644
---- a/apps/gateway/server/index.js
++++ b/apps/gateway/server/index.js
@@@ -31,7 +31,7 @@ const { createGuard, TRUST_PROXY } = require('../../_shared/guard');
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('gateway', require); // its components: apps/_shared/release.js
// The satellites on this host (ports), for readiness, the analytics roll-up, the run API and the
// jobs facade. TOOLS_SATELLITE_PORTS="img=5012,yt=5013" overrides single entries (tests, a moved unit).
@@@ -87,7 +87,9 @@ const obs = observe({
],
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
function getRequestHost(req) {
return String(req.headers.host || '').split(':')[0].toLowerCase();
ddiff --git a/apps/gateway/server/registry/descriptors.js b/apps/gateway/server/registry/descriptors.js
iindex 9f5980d..7758d3d 100644
---- a/apps/gateway/server/registry/descriptors.js
++++ b/apps/gateway/server/registry/descriptors.js
@@@ -82,7 +82,7 @@ function build() {
if (!s) { problems.push(`${t.id}: a catalogue tool without a descriptor`); continue; }
const own = live.get(t.id) || (t.status === 'unavailable' ? { status: 'unavailable', statusReason: t.unavailable } : null);
const d = compose(s.spec, {
-- family: t.family, name: t.name, summary: summaryOf(t),
++ family: t.family, name: t.name, summary: summaryOf(t), keywords: t.keywords,
hosts: t.external ? [] : [t.hosts.canonical, t.hosts.short].filter(Boolean),
...(own && { status: own.status, statusReason: own.statusReason }),
});
ddiff --git a/apps/gateway/test/descriptors.test.js b/apps/gateway/test/descriptors.test.js
iindex 6bc11e4..effdbdf 100644
---- a/apps/gateway/test/descriptors.test.js
++++ b/apps/gateway/test/descriptors.test.js
@@@ -54,6 +54,19 @@ for (const d of snap.tools) {
for (const k of ['execution', 'files', 'output', 'limits', 'auth', 'quotaClass', 'egress']) if (k in spec) assert.deepStrictEqual(d[k], spec[k] === undefined ? null : spec[k], `${d.id}.${k}`);
}
++// Published since openvibe-contracts v0.33.1: the catalogue's keywords, and each API tool's example (a job
++// tool's with sample files that fit files.min and files.accept); no API, no examples. checkDescriptor
++// (above) holds every example to the input schema, maxInputBytes and files.
++for (const d of snap.tools) {
++ const s = reg.internal(d.id).spec;
++ assert.ok(Array.isArray(d.keywords) && d.keywords.length, `${d.id}: keywords`);
++ if (!d.api) { assert.ok(!d.examples, `${d.id}: no API, no examples`); continue; }
++ assert.strictEqual(d.examples.length, 1, d.id);
++ assert.deepStrictEqual(d.examples[0].input, s.example.input, `${d.id}: the spec's example`);
++ if (d.files && d.files.min) assert.strictEqual(d.examples[0].files.length, d.files.min, `${d.id}: sample files`);
++}
++assert.strictEqual(snap.tools.filter(d => d.examples).length, snap.tools.filter(d => d.api).length);
++
// ── Schemas compile; examples match their input ──
for (const d of snap.tools) {
if (d.input) {
ddiff --git a/apps/img/package-lock.json b/apps/img/package-lock.json
iindex e9959b8..71128d2 100644
---- a/apps/img/package-lock.json
++++ b/apps/img/package-lock.json
@@@ -18,9 +18,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"sharp": "^0.33.5",
"to-ico": "^1.1.5",
"uuid": "^11.1.0"
@@@ -1729,9 +1729,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.33.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
-- "integrity": "sha512-7q8NUhcPxgb8lxrVdC3pFbiPdjzliJM1mMP/1G5a2PmOr2tJVAXwYa8+a8YgCJvYeMSeMHWijLkIEiZ9IhllOg==",
++ "version": "0.33.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
++ "integrity": "sha512-0S6SubDzdBmmIMbb4x1gWjWieqCl9W6nuvKrv/voUBpEvJChMGKlRmWnhLQV0KU7IZGxZcDahoYe9vI03pmf+A==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1776,9 +1776,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/img/package.json b/apps/img/package.json
iindex a4262e7..12630c7 100644
---- a/apps/img/package.json
++++ b/apps/img/package.json
@@@ -19,9 +19,9 @@
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.33.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"sharp": "^0.33.5",
"to-ico": "^1.1.5",
"uuid": "^11.1.0"
ddiff --git a/apps/img/server/index.js b/apps/img/server/index.js
iindex ffb5c78..7209221 100644
---- a/apps/img/server/index.js
++++ b/apps/img/server/index.js
@@@ -84,7 +84,7 @@ function syncError(req, res, err, label) {
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('img', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready } = require('../../_shared/observe');
@@@ -105,7 +105,9 @@ const obs = observe({
jobs: () => jobs,
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/maps/package-lock.json b/apps/maps/package-lock.json
iindex f1ceeb2..bf2895f 100644
---- a/apps/maps/package-lock.json
++++ b/apps/maps/package-lock.json
@@@ -19,7 +19,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"node-cache": "^5.1.2",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
},
"node_modules/accepts": {
@@@ -1383,9 +1383,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/maps/package.json b/apps/maps/package.json
iindex 013dcf0..1c60b90 100644
---- a/apps/maps/package.json
++++ b/apps/maps/package.json
@@@ -11,7 +11,7 @@
"dependencies": {
"axios": "^1.7.0",
"better-sqlite3": "^11.0.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"cheerio": "^1.0.0-rc.12",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
ddiff --git a/apps/maps/server/index.js b/apps/maps/server/index.js
iindex 05dc171..d0dd51c 100644
---- a/apps/maps/server/index.js
++++ b/apps/maps/server/index.js
@@@ -45,7 +45,7 @@ const guard = createGuard({
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('maps', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready } = require('../../_shared/observe');
@@@ -58,7 +58,9 @@ const obs = observe({
// The public data sources maps queries (OSM and others) are not probed here: each request says when one fails.
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/text/package-lock.json b/apps/text/package-lock.json
iindex 2cedd3b..c359aa6 100644
---- a/apps/text/package-lock.json
++++ b/apps/text/package-lock.json
@@@ -16,7 +16,7 @@
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
}
},
"node_modules/accepts": {
@@@ -969,9 +969,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/text/package.json b/apps/text/package.json
iindex b20036a..e002393 100644
---- a/apps/text/package.json
++++ b/apps/text/package.json
@@@ -16,7 +16,7 @@
"better-sqlite3": "^11.0.0",
"express-rate-limit": "^7.1.5",
"dotenv": "^16.3.1",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1"
},
"license": "MIT"
}
ddiff --git a/apps/text/server/index.js b/apps/text/server/index.js
iindex f81cddf..8f0af37 100644
---- a/apps/text/server/index.js
++++ b/apps/text/server/index.js
@@@ -36,7 +36,7 @@ const guard = createGuard({
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('text', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready } = require('../../_shared/observe');
@@@ -48,7 +48,9 @@ const obs = observe({
],
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));
ddiff --git a/apps/yt/package-lock.json b/apps/yt/package-lock.json
iindex 6733ee5..68fde5c 100644
---- a/apps/yt/package-lock.json
++++ b/apps/yt/package-lock.json
@@@ -17,7 +17,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"uuid": "^11.1.0"
}
},
@@@ -800,9 +800,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.4.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
-- "integrity": "sha512-H7jAsUklS1p57d9y6ozME++HmsX18iPYZ/U3P6bLoL1E2oSl6DbSPzLsVUkA7RYoYmAtcI3AwJ4hTNlAr66Vdw==",
++ "version": "1.5.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
++ "integrity": "sha512-1XWRFwN8br3iiy2Z8fmnXQMgR76VlfMpHjP1xr5uKLCJmtvtNSWO1BgA1kQmVtyBY7g+1n6YFIjahaD4qWbD/Q==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/apps/yt/package.json b/apps/yt/package.json
iindex 7046b02..f0a2764 100644
---- a/apps/yt/package.json
++++ b/apps/yt/package.json
@@@ -17,7 +17,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"better-sqlite3": "^11.0.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.4.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.1",
"jsonwebtoken": "^9.0.2",
"uuid": "^11.1.0"
}
ddiff --git a/apps/yt/server/index.js b/apps/yt/server/index.js
iindex 782c12a..7c20c3b 100644
---- a/apps/yt/server/index.js
++++ b/apps/yt/server/index.js
@@@ -42,7 +42,7 @@ const guard = createGuard({
const app = express();
// What this deploy runs (ADR-016); the shared navbar's release-watch polls it on every tool host.
--const release = require('openvibe-shared/release').createRelease({ service: 'tools', root: require('path').join(__dirname, '..', '..', '..') });
++const release = require('../../_shared/release').toolsRelease('yt', require); // its components: apps/_shared/release.js
// Metrics (GET /metrics, direct loopback callers only) and GET /api/ready from this server's real
// dependencies (roadmap Track O). First, so the HTTP metrics see every request.
const { observe, checks: ready, which } = require('../../_shared/observe');
@@@ -62,7 +62,9 @@ const obs = observe({
details: () => ({ yt_proxy_configured: !!String(process.env.YT_PROXY || '').trim() }),
});
guard.attachMetrics(obs.registry);
--app.get('/release.json', release.handler);
++// GET /release.json (ADR-016) and POST /release-metrics, which the shared navbar's release-watch reports
++// its update outcomes to (release_client_updates_total on /metrics): openvibe-shared release.mount.
++release.mount(app, { registry: obs.registry });
// Legal documents live on the apex; every tool host points there instead of answering 404.
app.get(['/terms', '/privacy', '/dmca', '/tos'], (req, res) => res.redirect(301, 'https://openvibe.tools' + (req.path === '/tos' ? '/terms' : req.path)));