openvibers/openvibe.shared Commits
Shared 1.24.0: no fake green in readiness (roadmap WS-Q task 7). ready.js knew only ok and fail, so a check whose feature was not configured had to claim ok (Network's Discord bot, Media's events outbox and News's source pull did, as production's /api/ready shows) or fail and turn the service yellow for a setting. A check now returns skip(reason, detail?) (exported) or { skipped: reason }: it reports status skipped with the reason, never ok, and is listed in the body's new skipped array. A skipped optional check leaves a service whose other checks passed ready; a skipped required check is also degraded (yellow, still HTTP 200: nothing failed); and a service is ready only when at least one check really passed, so one with no checks or every check skipped is degraded (before: ready). test/ready.test.js covers each case; README and CHANGELOG describe it. npm test (Node 22) 38/38.
Changes
@@@ -4,6 +4,13 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking change to any exported module, browser global or served file name is a new major.A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).++## 1.24.0 — 2026-09-28++++**No fake green in readiness** (roadmap WS-Q task 7: "skipped counts as yellow; nothing may be marked green by being synthesised from skipped parts"). `ready.js` had only `ok` and `fail`, so a check whose feature was not configured had to claim `ok` (Network's Discord bot, Media's events outbox and News's source pull did) or fail and turn the service yellow for a setting.++- **`skip(reason, detail?)`**, exported by `openvibe-shared/ready`, or a check returning `{ skipped: reason }`: the check reports status `skipped` with `reason` (and `detail`), never `ok`, and is listed in the new `skipped` array of the body.++- A skipped **optional** check leaves a service whose other checks passed `ready`. A skipped **required** check is listed in `degraded` too, and the service is `degraded` (still HTTP 200: nothing failed).++- A service is `ready` only when at least one check really passed: one with no checks, or with every check skipped, is `degraded`. This changes what a service with no checks reports (before: `ready`).++## 1.23.1 — 2026-09-27**Two release-watch fixes found by the release-lifecycle acceptance scenarios** (D46, roadmap WS-P task 16; OpenVibe.Host `scripts/release-acceptance.js`). release-watch.js stays within its 5 KB brotli budget (4.99 KB; comments shortened).@@@ -72,6 +72,12 @@ A check fails when it throws, times out, returns `false`, a string (the reason)`{ ok: false, error }`. Name a dependency required only when the service really cannot servewithout it.++A check that verified nothing (its feature is not configured, its dependency is switched off)++returns `skip('not configured')` (`require('openvibe-shared/ready').skip`, or `{ skipped: reason }`),++never `true`: it reports status `skipped` with the reason and is listed in `skipped`. No fake green++(roadmap WS-Q task 7): a skipped required check makes the service `degraded`, and a service is++`ready` only when at least one check really passed.++### Configuration (server, WS-C task 7)`openvibe-shared/config` keeps one namespace of a service's configuration as immutable revisions@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "1.23.1",++ "version": "1.24.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "1.23.1",++ "version": "1.24.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "1.23.1",++ "version": "1.24.0","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "1.23.1",++ "version": "1.24.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","exports": {@@@ -20,6 +20,13 @@// throw or time out. Overall `ready` is false only when a REQUIRED check fails (HTTP 503); a failed// optional check keeps the service ready (HTTP 200) and is listed in `degraded`, so a missing optional// dependency never reads as "healthy" or as "down".++//++// No fake green (roadmap WS-Q task 7): a check that did not verify anything (its feature is not++// configured, its dependency is switched off) returns `skip('not configured')` ({ skipped: reason })++// and reports status `skipped` with the reason, never `ok`. Skipped checks are listed in `skipped`.++// A skipped REQUIRED check is also `degraded` (yellow: the service cannot say it serves that), and a++// service is `ready` (green) only when at least one check really passed: none, or all skipped, is++// `degraded`.// ═══════════════════════════════════════════════════════════════const NAME_RE = /^[a-z][a-z0-9_.-]{0,63}$/;@@@ -63,6 +70,11 @@ function createReadiness({ service, release = null, checks = [], details = null,const v = await withTimeout(Promise.resolve().then(() => c.check()), c.timeoutMs);if (v === false) { out.status = 'fail'; out.error = 'check returned false'; }else if (typeof v === 'string') { out.status = 'fail'; out.error = safeReason(v); }++ else if (v && typeof v === 'object' && !Array.isArray(v) && v.skipped) {++ out.status = 'skipped';++ out.reason = safeReason(v.skipped === true ? 'not checked' : v.skipped);++ if (v.detail !== undefined) out.detail = v.detail;++ }else if (v && typeof v === 'object' && !Array.isArray(v)) {if (v.ok === false) { out.status = 'fail'; out.error = safeReason(v.error || v.detail || 'failed'); }if (v.detail !== undefined && (v.ok !== false || typeof v.detail === 'object')) out.detail = v.detail;@@@ -84,20 +96,24 @@ function createReadiness({ service, release = null, checks = [], details = null,async function run() {const results = await Promise.all(list.map(runOne));const out = {};-- const failed = []; const degraded = [];++ const failed = []; const degraded = []; const skipped = [];list.forEach((c, i) => {out[c.name] = results[i];-- if (results[i].status !== 'ok') (c.required ? failed : degraded).push(c.name);++ if (results[i].status === 'skipped') { skipped.push(c.name); if (c.required) degraded.push(c.name); }++ else if (results[i].status !== 'ok') (c.required ? failed : degraded).push(c.name);});const ready = failed.length === 0;++ // Green is built from checks that passed, never from skipped ones or from none.++ const verified = results.some((r) => r.status === 'ok');const body = {ready,-- status: !ready ? 'not_ready' : degraded.length ? 'degraded' : 'ready',++ status: !ready ? 'not_ready' : degraded.length || !verified ? 'degraded' : 'ready',service,release: typeof release === 'function' ? release() : release,checked_at: now().toISOString(),failed,degraded,++ skipped,checks: out,};if (details) {@@@ -112,7 +128,7 @@ function createReadiness({ service, release = null, checks = [], details = null,async function handler(_req, res) {let body;try { body = await run(); } catch (err) {-- body = { ready: false, status: 'not_ready', service, checked_at: now().toISOString(), failed: ['readiness'], degraded: [], checks: {}, error: safeReason(err) };++ body = { ready: false, status: 'not_ready', service, checked_at: now().toISOString(), failed: ['readiness'], degraded: [], skipped: [], checks: {}, error: safeReason(err) };}res.statusCode = body.ready ? 200 : 503;res.setHeader('Content-Type', 'application/json; charset=utf-8');@@@ -123,4 +139,9 @@ function createReadiness({ service, release = null, checks = [], details = null,return { run, handler, checks: list.map((c) => ({ name: c.name, required: c.required })) };}--module.exports = { createReadiness, safeReason };++/** What a check returns when it verified nothing: status `skipped` with this reason, never `ok`. */++function skip(reason = 'not checked', detail) {++ return detail === undefined ? { skipped: String(reason || 'not checked') } : { skipped: String(reason || 'not checked'), detail };++}++++module.exports = { createReadiness, safeReason, skip };@@@ -1,8 +1,9 @@'use strict';// ready.js: required vs optional checks, status/latency/checked_at on every check, timeouts,--// cached checks keep the time they really ran, and failure reasons never leak secrets.++// cached checks keep the time they really ran, failure reasons never leak secrets, and skipped checks++// never make a service green (roadmap WS-Q task 7).const assert = require('assert');--const { createReadiness, safeReason } = require('../ready');++const { createReadiness, safeReason, skip } = require('../ready');(async () => {assert.throws(() => createReadiness({}), /service/);@@@ -75,9 +76,38 @@ const { createReadiness, safeReason } = require('../ready');assert.strictEqual(res2.statusCode, 200);assert.strictEqual(JSON.parse(res2.body).checks.falsey.error, 'check returned false');++ // No fake green: nothing checked is not green.const none = await createReadiness({ service: 'x' }).run();assert.strictEqual(none.ready, true);-- assert.strictEqual(none.status, 'ready');++ assert.strictEqual(none.status, 'degraded', 'no check ran: not green');++ assert.deepStrictEqual(none.skipped, []);++++ // Skipped checks: status skipped with the reason, never ok; an optional one leaves a verified service green,++ // a required one is yellow, and a service whose every check was skipped is yellow.++ const withSkips = createReadiness({ service: 'x', checks: [++ { name: 'db', check: () => true },++ { name: 'discord_bot', required: false, check: () => skip('not configured') },++ { name: 'legacy', required: false, check: () => ({ skipped: true }) },++ ] });++ body = await withSkips.run();++ assert.strictEqual(body.status, 'ready', 'optional skips do not turn a verified service yellow');++ assert.deepStrictEqual(body.skipped, ['discord_bot', 'legacy']);++ assert.deepStrictEqual(body.degraded, []);++ assert.deepStrictEqual([body.checks.discord_bot.status, body.checks.discord_bot.reason], ['skipped', 'not configured']);++ assert.strictEqual(body.checks.legacy.reason, 'not checked');++ assert.ok(!('error' in body.checks.discord_bot));++ const reqSkip = createReadiness({ service: 'x', checks: [++ { name: 'db', check: () => true },++ { name: 'storage', check: () => skip('bucket not configured', { bucket: null }) },++ ] });++ body = await reqSkip.run();++ assert.strictEqual(body.ready, true, 'a skip is not a failure');++ assert.strictEqual(body.status, 'degraded', 'a skipped required check is yellow');++ assert.deepStrictEqual(body.degraded, ['storage']);++ assert.deepStrictEqual(body.checks.storage.detail, { bucket: null });++ body = await createReadiness({ service: 'x', checks: [{ name: 'a', required: false, check: () => skip('off') }] }).run();++ assert.strictEqual(body.status, 'degraded', 'every check skipped: not green');++ assert.deepStrictEqual(skip(), { skipped: 'not checked' });assert.strictEqual(safeReason(Object.assign(new Error('x'), { name: 'AbortError' })), 'timeout');assert.strictEqual(safeReason('bad token=abc123&x=1'), 'bad token=…&x=1');
@@ -4,6 +4,13 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,13 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 1.24.0 — 2026-09-28
+
+**No fake green in readiness** (roadmap WS-Q task 7: "skipped counts as yellow; nothing may be marked green by being synthesised from skipped parts"). `ready.js` had only `ok` and `fail`, so a check whose feature was not configured had to claim `ok` (Network's Discord bot, Media's events outbox and News's source pull did) or fail and turn the service yellow for a setting.
+- **`skip(reason, detail?)`**, exported by `openvibe-shared/ready`, or a check returning `{ skipped: reason }`: the check reports status `skipped` with `reason` (and `detail`), never `ok`, and is listed in the new `skipped` array of the body.
+- A skipped **optional** check leaves a service whose other checks passed `ready`. A skipped **required** check is listed in `degraded` too, and the service is `degraded` (still HTTP 200: nothing failed).
+- A service is `ready` only when at least one check really passed: one with no checks, or with every check skipped, is `degraded`. This changes what a service with no checks reports (before: `ready`).
+
## 1.23.1 — 2026-09-27
## 1.23.1 — 2026-09-27
**Two release-watch fixes found by the release-lifecycle acceptance scenarios** (D46, roadmap WS-P task 16; OpenVibe.Host `scripts/release-acceptance.js`). release-watch.js stays within its 5 KB brotli budget (4.99 KB; comments shortened).
**Two release-watch fixes found by the release-lifecycle acceptance scenarios** (D46, roadmap WS-P task 16; OpenVibe.Host `scripts/release-acceptance.js`). release-watch.js stays within its 5 KB brotli budget (4.99 KB; comments shortened).
@@ -72,6 +72,12 @@ A check fails when it throws, times out, returns `false`, a string (the reason)
@@ -72,6 +72,12 @@ A check fails when it throws, times out, returns `false`, a string (the reason)
`{ ok: false, error }`. Name a dependency required only when the service really cannot serve
`{ ok: false, error }`. Name a dependency required only when the service really cannot serve
without it.
without it.
+A check that verified nothing (its feature is not configured, its dependency is switched off)
+returns `skip('not configured')` (`require('openvibe-shared/ready').skip`, or `{ skipped: reason }`),
+never `true`: it reports status `skipped` with the reason and is listed in `skipped`. No fake green
+(roadmap WS-Q task 7): a skipped required check makes the service `degraded`, and a service is
+`ready` only when at least one check really passed.
+
### Configuration (server, WS-C task 7)
### Configuration (server, WS-C task 7)
`openvibe-shared/config` keeps one namespace of a service's configuration as immutable revisions
`openvibe-shared/config` keeps one namespace of a service's configuration as immutable revisions
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "1.23.1",
+ "version": "1.24.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "1.23.1",
+ "version": "1.24.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "1.23.1",
+ "version": "1.24.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "1.23.1",
+ "version": "1.24.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"exports": {
"exports": {
@@ -20,6 +20,13 @@
@@ -20,6 +20,13 @@
// throw or time out. Overall `ready` is false only when a REQUIRED check fails (HTTP 503); a failed
// throw or time out. Overall `ready` is false only when a REQUIRED check fails (HTTP 503); a failed
// optional check keeps the service ready (HTTP 200) and is listed in `degraded`, so a missing optional
// optional check keeps the service ready (HTTP 200) and is listed in `degraded`, so a missing optional
// dependency never reads as "healthy" or as "down".
// dependency never reads as "healthy" or as "down".
+//
+// No fake green (roadmap WS-Q task 7): a check that did not verify anything (its feature is not
+// configured, its dependency is switched off) returns `skip('not configured')` ({ skipped: reason })
+// and reports status `skipped` with the reason, never `ok`. Skipped checks are listed in `skipped`.
+// A skipped REQUIRED check is also `degraded` (yellow: the service cannot say it serves that), and a
+// service is `ready` (green) only when at least one check really passed: none, or all skipped, is
+// `degraded`.
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
const NAME_RE = /^[a-z][a-z0-9_.-]{0,63}$/;
const NAME_RE = /^[a-z][a-z0-9_.-]{0,63}$/;
@@ -63,6 +70,11 @@ function createReadiness({ service, release = null, checks = [], details = null,
@@ -63,6 +70,11 @@ function createReadiness({ service, release = null, checks = [], details = null,
const v = await withTimeout(Promise.resolve().then(() => c.check()), c.timeoutMs);
const v = await withTimeout(Promise.resolve().then(() => c.check()), c.timeoutMs);
if (v === false) { out.status = 'fail'; out.error = 'check returned false'; }
if (v === false) { out.status = 'fail'; out.error = 'check returned false'; }
else if (typeof v === 'string') { out.status = 'fail'; out.error = safeReason(v); }
else if (typeof v === 'string') { out.status = 'fail'; out.error = safeReason(v); }
+ else if (v && typeof v === 'object' && !Array.isArray(v) && v.skipped) {
+ out.status = 'skipped';
+ out.reason = safeReason(v.skipped === true ? 'not checked' : v.skipped);
+ if (v.detail !== undefined) out.detail = v.detail;
+ }
else if (v && typeof v === 'object' && !Array.isArray(v)) {
else if (v && typeof v === 'object' && !Array.isArray(v)) {
if (v.ok === false) { out.status = 'fail'; out.error = safeReason(v.error || v.detail || 'failed'); }
if (v.ok === false) { out.status = 'fail'; out.error = safeReason(v.error || v.detail || 'failed'); }
if (v.detail !== undefined && (v.ok !== false || typeof v.detail === 'object')) out.detail = v.detail;
if (v.detail !== undefined && (v.ok !== false || typeof v.detail === 'object')) out.detail = v.detail;
@@ -84,20 +96,24 @@ function createReadiness({ service, release = null, checks = [], details = null,
@@ -84,20 +96,24 @@ function createReadiness({ service, release = null, checks = [], details = null,
async function run() {
async function run() {
const results = await Promise.all(list.map(runOne));
const results = await Promise.all(list.map(runOne));
const out = {};
const out = {};
- const failed = []; const degraded = [];
+ const failed = []; const degraded = []; const skipped = [];
list.forEach((c, i) => {
list.forEach((c, i) => {
out[c.name] = results[i];
out[c.name] = results[i];
- if (results[i].status !== 'ok') (c.required ? failed : degraded).push(c.name);
+ if (results[i].status === 'skipped') { skipped.push(c.name); if (c.required) degraded.push(c.name); }
+ else if (results[i].status !== 'ok') (c.required ? failed : degraded).push(c.name);
});
});
const ready = failed.length === 0;
const ready = failed.length === 0;
+ // Green is built from checks that passed, never from skipped ones or from none.
+ const verified = results.some((r) => r.status === 'ok');
const body = {
const body = {
ready,
ready,
- status: !ready ? 'not_ready' : degraded.length ? 'degraded' : 'ready',
+ status: !ready ? 'not_ready' : degraded.length || !verified ? 'degraded' : 'ready',
service,
service,
release: typeof release === 'function' ? release() : release,
release: typeof release === 'function' ? release() : release,
checked_at: now().toISOString(),
checked_at: now().toISOString(),
failed,
failed,
degraded,
degraded,
+ skipped,
checks: out,
checks: out,
};
};
if (details) {
if (details) {
@@ -112,7 +128,7 @@ function createReadiness({ service, release = null, checks = [], details = null,
@@ -112,7 +128,7 @@ function createReadiness({ service, release = null, checks = [], details = null,
async function handler(_req, res) {
async function handler(_req, res) {
let body;
let body;
try { body = await run(); } catch (err) {
try { body = await run(); } catch (err) {
- body = { ready: false, status: 'not_ready', service, checked_at: now().toISOString(), failed: ['readiness'], degraded: [], checks: {}, error: safeReason(err) };
+ body = { ready: false, status: 'not_ready', service, checked_at: now().toISOString(), failed: ['readiness'], degraded: [], skipped: [], checks: {}, error: safeReason(err) };
}
}
res.statusCode = body.ready ? 200 : 503;
res.statusCode = body.ready ? 200 : 503;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
@@ -123,4 +139,9 @@ function createReadiness({ service, release = null, checks = [], details = null,
@@ -123,4 +139,9 @@ function createReadiness({ service, release = null, checks = [], details = null,
return { run, handler, checks: list.map((c) => ({ name: c.name, required: c.required })) };
return { run, handler, checks: list.map((c) => ({ name: c.name, required: c.required })) };
}
}
-module.exports = { createReadiness, safeReason };
+/** What a check returns when it verified nothing: status `skipped` with this reason, never `ok`. */
+function skip(reason = 'not checked', detail) {
+ return detail === undefined ? { skipped: String(reason || 'not checked') } : { skipped: String(reason || 'not checked'), detail };
+}
+
+module.exports = { createReadiness, safeReason, skip };
@@ -1,8 +1,9 @@
@@ -1,8 +1,9 @@
'use strict';
'use strict';
// ready.js: required vs optional checks, status/latency/checked_at on every check, timeouts,
// ready.js: required vs optional checks, status/latency/checked_at on every check, timeouts,
-// cached checks keep the time they really ran, and failure reasons never leak secrets.
+// cached checks keep the time they really ran, failure reasons never leak secrets, and skipped checks
+// never make a service green (roadmap WS-Q task 7).
const assert = require('assert');
const assert = require('assert');
-const { createReadiness, safeReason } = require('../ready');
+const { createReadiness, safeReason, skip } = require('../ready');
(async () => {
(async () => {
assert.throws(() => createReadiness({}), /service/);
assert.throws(() => createReadiness({}), /service/);
@@ -75,9 +76,38 @@ const { createReadiness, safeReason } = require('../ready');
@@ -75,9 +76,38 @@ const { createReadiness, safeReason } = require('../ready');
assert.strictEqual(res2.statusCode, 200);
assert.strictEqual(res2.statusCode, 200);
assert.strictEqual(JSON.parse(res2.body).checks.falsey.error, 'check returned false');
assert.strictEqual(JSON.parse(res2.body).checks.falsey.error, 'check returned false');
+ // No fake green: nothing checked is not green.
const none = await createReadiness({ service: 'x' }).run();
const none = await createReadiness({ service: 'x' }).run();
assert.strictEqual(none.ready, true);
assert.strictEqual(none.ready, true);
- assert.strictEqual(none.status, 'ready');
+ assert.strictEqual(none.status, 'degraded', 'no check ran: not green');
+ assert.deepStrictEqual(none.skipped, []);
+
+ // Skipped checks: status skipped with the reason, never ok; an optional one leaves a verified service green,
+ // a required one is yellow, and a service whose every check was skipped is yellow.
+ const withSkips = createReadiness({ service: 'x', checks: [
+ { name: 'db', check: () => true },
+ { name: 'discord_bot', required: false, check: () => skip('not configured') },
+ { name: 'legacy', required: false, check: () => ({ skipped: true }) },
+ ] });
+ body = await withSkips.run();
+ assert.strictEqual(body.status, 'ready', 'optional skips do not turn a verified service yellow');
+ assert.deepStrictEqual(body.skipped, ['discord_bot', 'legacy']);
+ assert.deepStrictEqual(body.degraded, []);
+ assert.deepStrictEqual([body.checks.discord_bot.status, body.checks.discord_bot.reason], ['skipped', 'not configured']);
+ assert.strictEqual(body.checks.legacy.reason, 'not checked');
+ assert.ok(!('error' in body.checks.discord_bot));
+ const reqSkip = createReadiness({ service: 'x', checks: [
+ { name: 'db', check: () => true },
+ { name: 'storage', check: () => skip('bucket not configured', { bucket: null }) },
+ ] });
+ body = await reqSkip.run();
+ assert.strictEqual(body.ready, true, 'a skip is not a failure');
+ assert.strictEqual(body.status, 'degraded', 'a skipped required check is yellow');
+ assert.deepStrictEqual(body.degraded, ['storage']);
+ assert.deepStrictEqual(body.checks.storage.detail, { bucket: null });
+ body = await createReadiness({ service: 'x', checks: [{ name: 'a', required: false, check: () => skip('off') }] }).run();
+ assert.strictEqual(body.status, 'degraded', 'every check skipped: not green');
+ assert.deepStrictEqual(skip(), { skipped: 'not checked' });
assert.strictEqual(safeReason(Object.assign(new Error('x'), { name: 'AbortError' })), 'timeout');
assert.strictEqual(safeReason(Object.assign(new Error('x'), { name: 'AbortError' })), 'timeout');
assert.strictEqual(safeReason('bad token=abc123&x=1'), 'bad token=…&x=1');
assert.strictEqual(safeReason('bad token=abc123&x=1'), 'bad token=…&x=1');