openvibers/openvibe.network Commits
Network: contracts 0.109.0 (Run and Services internal), AI joins the sandbox defaults (ai.app.run) (#74)
Changes
@@@ -48,7 +48,7 @@ Identity and account service for the OpenVibe network. Manages user accounts, OA## Depends on--- `openvibe-contracts` v0.108.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQL++- `openvibe-contracts` v0.109.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQLand Valkey adapters, events, per-actor limits, universal telemetry), `openvibe-shared` v2.10.1, pinned by release tarball- OpenVibe.Events (Network's outbox relay; the events it consumes), OpenVibe.AI (the `network.site_copy`workflow), OpenVibe.Blog (the changelog feed it proxies)@@@ -21,7 +21,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1","pg": "^8.23.0",@@@ -1556,9 +1556,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.108.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",-- "integrity": "sha512-T07n7B482/XIdSD3ikehPGHFUTWsyGaeLCF2SerWsKD1V09s1RYs7TD9+Y2z7vdxYjSYhQPMHr1r2xP3F9xIbA==",++ "version": "0.109.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0",++ "integrity": "sha512-Ks2zLtR9XcN6ggr1APv93Ot8o/U1oAn8fOHNruYTDh1wjL/mKiwq5Lee8mx4rl6eb2A9zY8A45P/6edb3NNxgQ==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -30,7 +30,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1","pg": "^8.23.0",@@@ -48,10 +48,11 @@ function grantableCatalog() {/*** Audiences that accept sandbox app tokens when DEV_SANDBOX_AUDIENCES is unset. Each of these keeps* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,-- * Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5). openvibe.network-- * is deliberately not one of them.++ * Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5), AI with++ * sandbox runs on free and local capacity only (ai.app.run, plan T6). openvibe.network is deliberately++ * not one of them.*/--const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.codes']);++const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.codes', 'openvibe.ai']);/*** Capabilities every project's SANDBOX apps may hold without a staff decision when@@@ -66,6 +67,7 @@ const DEFAULT_SANDBOX_ALLOWANCE = Object.freeze(['events.app.publish', 'events.app.read', 'events.app.subscribe','tools.job.create', 'tools.job.read', 'tools.job.cancel', 'tools.tool.read', 'tools.tool.run','codes.release.manage',++ 'ai.app.run',]);/** Public + grantable only (the rule for every allowance that is not set by staff by hand). */@@@ -67,11 +67,11 @@ async function withoutCatalog(hidden, fn) {(async () => {// ── Settings: defaults, env overrides, and the public-only rule ──let s = policy.settings({});-- assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.codes', 'openvibe.events', 'openvibe.media', 'openvibe.tools']);++ assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.codes', 'openvibe.events', 'openvibe.media', 'openvibe.tools']);assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');-- assert.deepStrictEqual(s.sandboxAllowance, ['codes.release.manage', 'events.app.publish', 'events.app.read', 'events.app.subscribe',++ assert.deepStrictEqual(s.sandboxAllowance, ['ai.app.run', 'codes.release.manage', 'events.app.publish', 'events.app.read', 'events.app.subscribe','media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],-- 'the default sandbox allowance (openvibe-contracts 0.59.0 defines media.object.list and .delete)');++ 'the default sandbox allowance (openvibe-contracts 0.59.0 defines media.object.list and .delete, 0.109.0 ai.app.run)');assert.deepStrictEqual(policy.DEFAULT_SANDBOX_ALLOWANCE.filter(id => id.startsWith('events.')), ['events.app.publish', 'events.app.read', 'events.app.subscribe']);s = policy.settings({ developer: { sandboxAudiences: '', sandboxAllowance: '' } });assert.strictEqual(s.sandboxAudiences.size, 0, 'set-but-empty means none');@@@ -77,8 +77,9 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');assert.strictEqual(inCat('events'), 'platform'); assert.strictEqual(inCat('ai'), 'platform'); assert.strictEqual(inCat('sources'), 'platform');assert.strictEqual(inCat('sdk'), 'library'); assert.strictEqual(inCat('contracts'), 'library');assert.strictEqual(inCat('examples'), 'repository');-- // 'planned' = exposure placeholder: Contracts 0.83.0's media-hub and space, v0.87-v0.90's zone and run, v0.95.0's services (charter only, nothing runs). Watch left it in v0.108.0 (it runs, internal).-- assert.deepStrictEqual(cats.planned.services.map(x => x.id).sort(), ['media-hub', 'run', 'services', 'space', 'zone']);++ // 'planned' = exposure placeholder: Contracts 0.83.0's media-hub and space, v0.87-v0.90's zone (charter only, nothing runs).++ // Watch left it in v0.108.0, Run and Services in v0.109.0 (they run, internal).++ assert.deepStrictEqual(cats.planned.services.map(x => x.id).sort(), ['media-hub', 'space', 'zone']);const liveRow = cats.site.services.find(x => x.id === 'live');assert.strictEqual(liveRow.runtime, 'down'); assert.ok(liveRow.checked_at, 'rows carry the last check');r = await get('/api/v1/registry/categories/library');@@@ -20,10 +20,10 @@ const PINNED = {news: 'internal', reviews: 'internal', deals: 'internal', coupons: 'internal', trade: 'internal', host: 'live',tips: 'internal', vip: 'internal', openre: 'internal', search: 'live', sources: 'internal', chat: 'live', 'media-hub': 'placeholder', space: 'placeholder',ai: 'internal',-- // openvibe-contracts v0.86-v0.90: Bot runs (its own exposure row); Zone and Run are charters only.-- bot: 'live', zone: 'placeholder', run: 'placeholder',-- // openvibe-contracts v0.95.0: OpenVibe.Services is a charter only (the T13 control plane).-- services: 'placeholder',++ // openvibe-contracts v0.86-v0.90: Bot runs (its own exposure row); Zone is a charter only.++ bot: 'live', zone: 'placeholder',++ run: 'internal', // OpenVibe.Run runs on loopback since 2026-10-07 (contracts 0.109.0)++ services: 'internal', // OpenVibe.Services, the T13 control plane, runs on loopback since 2026-10-07 (contracts 0.109.0)// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).watch: 'internal', // OpenVibe.Watch runs on loopback since 2026-10-07 (contracts 0.108.0)sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',@@@ -110,7 +110,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], chassert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),// v0.95.0 Services; none is retired-- assert.deepStrictEqual(st.exposure_summary, { live: 16, internal: 11, library: 4, repository: 1, placeholder: 5, retired: 0 });++ assert.deepStrictEqual(st.exposure_summary, { live: 16, internal: 13, library: 4, repository: 1, placeholder: 3, retired: 0 });// The page: no row for a service whose domain serves a placeholder shows a bare "Up".const html = await fetch(base + '/status').then(x => x.text());
@@ -48,7 +48,7 @@ Identity and account service for the OpenVibe network. Manages user accounts, OA
@@ -48,7 +48,7 @@ Identity and account service for the OpenVibe network. Manages user accounts, OA
## Depends on
## Depends on
-- `openvibe-contracts` v0.108.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQL
+- `openvibe-contracts` v0.109.0 (registry manifests, capability checks), `openvibe-sdk` v0.28.0 (PostgreSQL
and Valkey adapters, events, per-actor limits, universal telemetry), `openvibe-shared` v2.10.1, pinned by release tarball
and Valkey adapters, events, per-actor limits, universal telemetry), `openvibe-shared` v2.10.1, pinned by release tarball
- OpenVibe.Events (Network's outbox relay; the events it consumes), OpenVibe.AI (the `network.site_copy`
- OpenVibe.Events (Network's outbox relay; the events it consumes), OpenVibe.AI (the `network.site_copy`
workflow), OpenVibe.Blog (the changelog feed it proxies)
workflow), OpenVibe.Blog (the changelog feed it proxies)
@@ -21,7 +21,7 @@
@@ -21,7 +21,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -1556,9 +1556,9 @@
@@ -1556,9 +1556,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.108.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",
- "integrity": "sha512-T07n7B482/XIdSD3ikehPGHFUTWsyGaeLCF2SerWsKD1V09s1RYs7TD9+Y2z7vdxYjSYhQPMHr1r2xP3F9xIbA==",
+ "version": "0.109.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0",
+ "integrity": "sha512-Ks2zLtR9XcN6ggr1APv93Ot8o/U1oAn8fOHNruYTDh1wjL/mKiwq5Lee8mx4rl6eb2A9zY8A45P/6edb3NNxgQ==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.108.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.109.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.28.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -48,10 +48,11 @@ function grantableCatalog() {
@@ -48,10 +48,11 @@ function grantableCatalog() {
/**
/**
* Audiences that accept sandbox app tokens when DEV_SANDBOX_AUDIENCES is unset. Each of these keeps
* Audiences that accept sandbox app tokens when DEV_SANDBOX_AUDIENCES is unset. Each of these keeps
* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,
* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,
- * Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5). openvibe.network
- * is deliberately not one of them.
+ * Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5), AI with
+ * sandbox runs on free and local capacity only (ai.app.run, plan T6). openvibe.network is deliberately
+ * not one of them.
*/
*/
-const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.codes']);
+const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.codes', 'openvibe.ai']);
/**
/**
* Capabilities every project's SANDBOX apps may hold without a staff decision when
* Capabilities every project's SANDBOX apps may hold without a staff decision when
@@ -66,6 +67,7 @@ const DEFAULT_SANDBOX_ALLOWANCE = Object.freeze([
@@ -66,6 +67,7 @@ const DEFAULT_SANDBOX_ALLOWANCE = Object.freeze([
'events.app.publish', 'events.app.read', 'events.app.subscribe',
'events.app.publish', 'events.app.read', 'events.app.subscribe',
'tools.job.create', 'tools.job.read', 'tools.job.cancel', 'tools.tool.read', 'tools.tool.run',
'tools.job.create', 'tools.job.read', 'tools.job.cancel', 'tools.tool.read', 'tools.tool.run',
'codes.release.manage',
'codes.release.manage',
+ 'ai.app.run',
]);
]);
/** Public + grantable only (the rule for every allowance that is not set by staff by hand). */
/** Public + grantable only (the rule for every allowance that is not set by staff by hand). */
@@ -67,11 +67,11 @@ async function withoutCatalog(hidden, fn) {
@@ -67,11 +67,11 @@ async function withoutCatalog(hidden, fn) {
(async () => {
(async () => {
// ── Settings: defaults, env overrides, and the public-only rule ──
// ── Settings: defaults, env overrides, and the public-only rule ──
let s = policy.settings({});
let s = policy.settings({});
- assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.codes', 'openvibe.events', 'openvibe.media', 'openvibe.tools']);
+ assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.codes', 'openvibe.events', 'openvibe.media', 'openvibe.tools']);
assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');
assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');
- assert.deepStrictEqual(s.sandboxAllowance, ['codes.release.manage', 'events.app.publish', 'events.app.read', 'events.app.subscribe',
+ assert.deepStrictEqual(s.sandboxAllowance, ['ai.app.run', 'codes.release.manage', 'events.app.publish', 'events.app.read', 'events.app.subscribe',
'media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],
'media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],
- 'the default sandbox allowance (openvibe-contracts 0.59.0 defines media.object.list and .delete)');
+ 'the default sandbox allowance (openvibe-contracts 0.59.0 defines media.object.list and .delete, 0.109.0 ai.app.run)');
assert.deepStrictEqual(policy.DEFAULT_SANDBOX_ALLOWANCE.filter(id => id.startsWith('events.')), ['events.app.publish', 'events.app.read', 'events.app.subscribe']);
assert.deepStrictEqual(policy.DEFAULT_SANDBOX_ALLOWANCE.filter(id => id.startsWith('events.')), ['events.app.publish', 'events.app.read', 'events.app.subscribe']);
s = policy.settings({ developer: { sandboxAudiences: '', sandboxAllowance: '' } });
s = policy.settings({ developer: { sandboxAudiences: '', sandboxAllowance: '' } });
assert.strictEqual(s.sandboxAudiences.size, 0, 'set-but-empty means none');
assert.strictEqual(s.sandboxAudiences.size, 0, 'set-but-empty means none');
@@ -77,8 +77,9 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');
@@ -77,8 +77,9 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');
assert.strictEqual(inCat('events'), 'platform'); assert.strictEqual(inCat('ai'), 'platform'); assert.strictEqual(inCat('sources'), 'platform');
assert.strictEqual(inCat('events'), 'platform'); assert.strictEqual(inCat('ai'), 'platform'); assert.strictEqual(inCat('sources'), 'platform');
assert.strictEqual(inCat('sdk'), 'library'); assert.strictEqual(inCat('contracts'), 'library');
assert.strictEqual(inCat('sdk'), 'library'); assert.strictEqual(inCat('contracts'), 'library');
assert.strictEqual(inCat('examples'), 'repository');
assert.strictEqual(inCat('examples'), 'repository');
- // 'planned' = exposure placeholder: Contracts 0.83.0's media-hub and space, v0.87-v0.90's zone and run, v0.95.0's services (charter only, nothing runs). Watch left it in v0.108.0 (it runs, internal).
- assert.deepStrictEqual(cats.planned.services.map(x => x.id).sort(), ['media-hub', 'run', 'services', 'space', 'zone']);
+ // 'planned' = exposure placeholder: Contracts 0.83.0's media-hub and space, v0.87-v0.90's zone (charter only, nothing runs).
+ // Watch left it in v0.108.0, Run and Services in v0.109.0 (they run, internal).
+ assert.deepStrictEqual(cats.planned.services.map(x => x.id).sort(), ['media-hub', 'space', 'zone']);
const liveRow = cats.site.services.find(x => x.id === 'live');
const liveRow = cats.site.services.find(x => x.id === 'live');
assert.strictEqual(liveRow.runtime, 'down'); assert.ok(liveRow.checked_at, 'rows carry the last check');
assert.strictEqual(liveRow.runtime, 'down'); assert.ok(liveRow.checked_at, 'rows carry the last check');
r = await get('/api/v1/registry/categories/library');
r = await get('/api/v1/registry/categories/library');
@@ -20,10 +20,10 @@ const PINNED = {
@@ -20,10 +20,10 @@ const PINNED = {
news: 'internal', reviews: 'internal', deals: 'internal', coupons: 'internal', trade: 'internal', host: 'live',
news: 'internal', reviews: 'internal', deals: 'internal', coupons: 'internal', trade: 'internal', host: 'live',
tips: 'internal', vip: 'internal', openre: 'internal', search: 'live', sources: 'internal', chat: 'live', 'media-hub': 'placeholder', space: 'placeholder',
tips: 'internal', vip: 'internal', openre: 'internal', search: 'live', sources: 'internal', chat: 'live', 'media-hub': 'placeholder', space: 'placeholder',
ai: 'internal',
ai: 'internal',
- // openvibe-contracts v0.86-v0.90: Bot runs (its own exposure row); Zone and Run are charters only.
- bot: 'live', zone: 'placeholder', run: 'placeholder',
- // openvibe-contracts v0.95.0: OpenVibe.Services is a charter only (the T13 control plane).
- services: 'placeholder',
+ // openvibe-contracts v0.86-v0.90: Bot runs (its own exposure row); Zone is a charter only.
+ bot: 'live', zone: 'placeholder',
+ run: 'internal', // OpenVibe.Run runs on loopback since 2026-10-07 (contracts 0.109.0)
+ services: 'internal', // OpenVibe.Services, the T13 control plane, runs on loopback since 2026-10-07 (contracts 0.109.0)
// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).
// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).
watch: 'internal', // OpenVibe.Watch runs on loopback since 2026-10-07 (contracts 0.108.0)
watch: 'internal', // OpenVibe.Watch runs on loopback since 2026-10-07 (contracts 0.108.0)
sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',
sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',
@@ -110,7 +110,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
@@ -110,7 +110,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);
assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);
// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),
// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),
// v0.95.0 Services; none is retired
// v0.95.0 Services; none is retired
- assert.deepStrictEqual(st.exposure_summary, { live: 16, internal: 11, library: 4, repository: 1, placeholder: 5, retired: 0 });
+ assert.deepStrictEqual(st.exposure_summary, { live: 16, internal: 13, library: 4, repository: 1, placeholder: 3, retired: 0 });
// The page: no row for a service whose domain serves a placeholder shows a bare "Up".
// The page: no row for a service whose domain serves a placeholder shows a bare "Up".
const html = await fetch(base + '/status').then(x => x.text());
const html = await fetch(base + '/status').then(x => x.text());