openvibers/openvibe.media Commits

Media's readiness has no fake green (roadmap WS-Q task 7, Shared v1.24.0). events_outbox answered ok with { enabled: false } while the relay was off, and an unconfigured remote tier (b2, r2) had no check at all, so nothing showed it was not in use. The relay off is now skipped with the reason (detail { enabled: false } kept), and an unconfigured tier is listed as remote_<name> skipped "not configured"; neither is ever ok, and an optional skip leaves a service whose checks passed ready. Drill mode still lists no remote tier. openvibe-shared is pinned at v1.24.0 (from v1.22.0), named in the README. test/observability.test.js checks both skips (the configured-tier and failure cases are unchanged). npm test passes.

OP OpenVibers committed 28 Sept 2026 · 51283d3211 · 5 files, +20 −11


Changes

ddiff --git a/README.md b/README.md
iindex eb9875f..00726fb 100644
---- a/README.md
++++ b/README.md
@@@ -107,7 +107,7 @@ server/
vod.duration.reconcile (stored vs measured durations, local and B2/R2),
storage.orphans.scan (monthly storage orphan report, service-wide, report only)
client-ip.js trust proxy = loopback; req.ip is the only client address
--(openvibe-shared v1.22.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
++(openvibe-shared v1.24.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
ddiff --git a/package-lock.json b/package-lock.json
iindex 4fed695..6e18fe6 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -18,7 +18,7 @@
"multer": "^1.4.5-lts.1",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
"sharp": "^0.33.4"
}
},
@@@ -1944,9 +1944,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.22.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
-- "integrity": "sha512-FtqbR8h6YphOnJNqklJED3HyqvTwZJl58gyiZ3QgzmZtZcA16O2NMGq67O4p7grLojwaIG5DhXDMgmZcpScxJQ==",
++ "version": "1.24.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
++ "integrity": "sha512-VPzafrc07Pw0oqmXioOlDRtlX5xu82jNLBYkVYxXvhjkILMpWsyjExItIRt5Y6l2st0ozHm7bDQFTJpKEShMEA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex ec72417..0c1a01f 100644
---- a/package.json
++++ b/package.json
@@@ -21,7 +21,7 @@
"dotenv": "^16.4.5",
"express": "^4.19.2",
"multer": "^1.4.5-lts.1",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.22.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
"sharp": "^0.33.4"
}
}
ddiff --git a/server/observability.js b/server/observability.js
iindex 8d87399..65b3a60 100644
---- a/server/observability.js
++++ b/server/observability.js
@@@ -20,7 +20,7 @@ const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const metrics = require('openvibe-shared/metrics');
--const { createReadiness } = require('openvibe-shared/ready');
++const { createReadiness, skip } = require('openvibe-shared/ready');
const copyReport = require('./objects/copy-report');
@@@ -119,14 +119,16 @@ function createMediaReadiness({ release, db, config, auth, recorder, events, rem
}
checks.push({ name: 'network_jwks', required: false, description: 'user sign-in and service tokens (app keys work without it)', check: () => auth.jwksLoaded() || 'Network public key not loaded yet' });
for (const name of ['b2', 'r2']) {
-- if (drill || !remote.configured(name)) continue; // not configured: no check, rather than a pretend pass
++ if (drill) continue;
++ // Not configured: listed as skipped (never a pretend pass), so the status shows the tier is not in use.
++ if (!remote.configured(name)) { checks.push({ name: `remote_${name}`, required: false, check: () => skip('not configured') }); continue; }
checks.push({ name: `remote_${name}`, required: false, cacheMs: 60 * 1000, timeoutMs: 5000, check: () => remote.probe(name) });
}
checks.push({
name: 'events_outbox', required: false, description: 'durable events to OpenVibe.Events (webhooks are separate)',
check: () => {
const s = events.status();
-- if (!s.enabled) return { ok: true, detail: { enabled: false } };
++ if (!s.enabled) return skip('relay off (EVENTS_URL or the service credentials unset)', { enabled: false }); // verified nothing: never ok (WS-Q task 7)
if (s.pending > OUTBOX_BACKLOG_LIMIT) return { ok: false, error: `${s.pending} events waiting (limit ${OUTBOX_BACKLOG_LIMIT})`, detail: { pending: s.pending } };
return { ok: true, detail: { pending: s.pending, rejected: s.rejected } };
},
ddiff --git a/test/observability.test.js b/test/observability.test.js
iindex 33fb2f0..0ece7f0 100644
---- a/test/observability.test.js
++++ b/test/observability.test.js
@@@ -62,8 +62,13 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.strictEqual(body.service, 'media');
assert.strictEqual(body.recordings_in_progress, 1);
const names = Object.keys(body.checks).sort();
-- assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods'], 'r2 is not configured, so it has no check');
++ assert.deepStrictEqual(names, ['db', 'events_outbox', 'network_jwks', 'object_copies', 'remote_b2', 'remote_r2', 'storage_clips', 'storage_files', 'storage_objects', 'storage_pastes', 'storage_thumbnails', 'storage_vods']);
++ // r2 is not configured: listed as skipped with the reason, never a pretend pass (WS-Q task 7), and an optional
++ // skip leaves the verified service ready.
++ assert.deepStrictEqual([body.checks.remote_r2.status, body.checks.remote_r2.reason], ['skipped', 'not configured']);
++ assert.deepStrictEqual(body.skipped, ['remote_r2']);
for (const [n, c] of Object.entries(body.checks)) {
++ if (n === 'remote_r2') continue;
assert.strictEqual(c.status, 'ok', n);
assert.strictEqual(typeof c.latency_ms, 'number');
assert.ok(Date.parse(c.checked_at));
@@@ -89,6 +94,8 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.deepStrictEqual(body.degraded, ['remote_b2', 'remote_r2']);
assert.strictEqual(body.checks.remote_r2.error, 'r2 HeadBucket failed: AccessDenied');
assert.deepStrictEqual(body.checks.events_outbox.detail, { enabled: false });
++ assert.strictEqual(body.checks.events_outbox.status, 'skipped', 'a relay that is off verified nothing: skipped, never ok');
++ assert.match(body.checks.events_outbox.reason, /relay off/);
// A storage directory that cannot be written: not ready, 503, named.
jwks = true; outbox = { enabled: false };
@@@ -140,7 +147,7 @@ const listen = (app) => new Promise((resolve) => { const s = app.listen(0, '127.
assert.strictEqual(up.status, 200, up.body);
assert.strictEqual(body.release, 'abcdef123456');
assert.ok(body.checks.db && body.checks.storage_objects);
-- assert.ok(!body.checks.remote_b2, 'unconfigured tiers are not checked');
++ assert.strictEqual(body.checks.remote_b2.status, 'skipped', 'an unconfigured tier is not probed: skipped, never ok');
const m = await request(b2, '/metrics');
assert.strictEqual(m.status, 200);
assert.ok(m.body.includes('http_requests_total{method="GET",route="/api/ready",status_class="2xx"} 1\n'), m.body.slice(0, 400));