openvibers/openvibe.host Commits

openvibe-shared v1.5.0 (Track R release manifest), openvibe-contracts v0.32.0, openvibe-sdk v0.5.0

OP OpenVibers committed 23 Sept 2026 · 315d852bec · 9 files, +38 −32

- /release.json is mounted with release.mount(app, { registry }) on Host's metrics registry: POST
  /release-metrics takes open tabs' update reports into /metrics (release_client_updates_total).
  On contracts 0.32.0 the manifest carries the release-manifest 1.1.0 fields; no components are
  declared, so every release still prompts a reload. The dashboard test validates the served
  manifest against the contracts.
- Sandbox tokens keep working as before: since openvibe-contracts 0.26.0 verifyServiceToken refuses
  env=sandbox unless the receiver opts in, and Host is a receiver that handles them itself (a
  sandbox token may act on sandbox projects only; elsewhere 403 environment.sandbox_token). The
  verify call passes acceptSandbox: true, so nothing changes.
- identity.service-token-claims 1.2.0 requires project_id and env on app tokens: the mock
  Network's app tokens carry them, as Network's do.
- host.site.manage lists the four takedown routes in 0.32.0: README, STATUS and docs/launch.md no
  longer call that a pending Contracts change, and list the new pins.

Changes

ddiff --git a/README.md b/README.md
iindex 759ca98..0f6bb10 100644
---- a/README.md
++++ b/README.md
@@@ -46,10 +46,10 @@ Stage A adds a few safety rules of its own:
## Depends on
--- OpenVibe.Contracts (`openvibe-contracts` v0.24.0; `host.*` capabilities and the `host` manifest are released there and unchanged in v0.30.1): service manifests (vhost rendering, snapshots, the first-party domain list), ids, problem+json, service-token verification, capability checks.
++- OpenVibe.Contracts (`openvibe-contracts` v0.32.0; `host.*` capabilities and the `host` manifest were released in v0.24.0, and v0.32.0 adds the takedown routes to `host.site.manage`): service manifests (vhost rendering, snapshots, the first-party domain list), ids, problem+json, service-token verification, capability checks.
- OpenVibe.Network (Stage B): SSO for the dashboard, the JWKS that verifies user and service tokens, client-credentials tokens for the outbox relay.
--- OpenVibe.Events (Stage B): `host.*` events through the `openvibe-sdk` v0.2.2 transactional outbox.
--- `openvibe-shared` v1.3.0 (Stage B): shared chrome, legal pages, `/release.json`, `/metrics`, `/api/ready`.
++- OpenVibe.Events (Stage B): `host.*` events through the `openvibe-sdk` v0.5.0 transactional outbox.
++- `openvibe-shared` v1.5.0 (Stage B): shared chrome, legal pages, `/release.json`, `/metrics`, `/api/ready`.
- OpenVibe.Media: not yet. The roadmap stores artifacts "through Media where practical"; Stage B keeps them on local disk for now (see [Not done yet](#not-done-yet-stage-b)).
## Stages
@@@ -245,7 +245,7 @@ People present their Network user JWT as a Bearer token. Services and apps prese
Errors are RFC 9457 problems (`application/problem+json`, with the legacy `error` field). A refused upload answers 413/422 with `deploy_id` and the log lines; `503 upload.busy` (with `Retry-After`) when `HOST_MAX_CONCURRENT_UPLOADS` uploads are already being validated; `507 storage.host_full` while the disk has less than `HOST_MIN_FREE_BYTES` free. Network staff (`role: admin`) can read every project, site, deploy and log, delete sites, projects and domains, set quotas, and **take a site or project down** (451 on every host, content kept for review, members see the reason and cannot publish or delete around it); they cannot publish into a tenant's site.
--The three capability ids and the service manifest are released in `openvibe-contracts` (since v0.24.0, identical in v0.30.1), and the CI contract check is blocking. [`docs/capabilities-proposal/`](docs/capabilities-proposal/) mirrors them; `host.site.manage` there also lists the four takedown routes, which the next Contracts release should add (see [docs/launch.md](docs/launch.md#the-window-in-this-order)).
++The three capability ids and the service manifest are released in `openvibe-contracts` (since v0.24.0; v0.32.0 adds the takedown routes to `host.site.manage`), and the CI contract check is blocking. [`docs/capabilities-proposal/`](docs/capabilities-proposal/) mirrors them; `host.site.manage` there also lists the four takedown routes, which the next Contracts release should add (see [docs/launch.md](docs/launch.md#the-window-in-this-order)).
### Activation and rollback
@@@ -253,7 +253,7 @@ Activation is one SQLite transaction: a compare-and-set on the site's `active_de
### Events
--Through the `openvibe-sdk` v0.2.2 transactional outbox (`event_outbox`), inside the transaction that makes the change:
++Through the `openvibe-sdk` v0.5.0 transactional outbox (`event_outbox`), inside the transaction that makes the change:
| Event | When | Payload |
|---|---|---|
@@@ -266,7 +266,7 @@ The relay publishes with Host's service token (`events.event.publish`, audience
### Dashboard
--Server-rendered pages with the shared chrome (`openvibe-shared` v1.3.0: navbar and theme loader from the Network, `<noscript>` navigation, SSR footer, app icon, legal pages). Every action is a plain form, so it works without JavaScript: projects, quotas and usage, members, sites, folder or archive upload, deploys with activate/rollback/delete, activation history, upload logs and file lists, domains with their DNS records and a "check DNS now" button. Pages are `private, no-store`, `noindex`, `frame-ancestors 'none'`.
++Server-rendered pages with the shared chrome (`openvibe-shared` v1.5.0: navbar and theme loader from the Network, `<noscript>` navigation, SSR footer, app icon, legal pages). Every action is a plain form, so it works without JavaScript: projects, quotas and usage, members, sites, folder or archive upload, deploys with activate/rollback/delete, activation history, upload logs and file lists, domains with their DNS records and a "check DNS now" button. Pages are `private, no-store`, `noindex`, `frame-ancestors 'none'`.
### Observability
@@@ -277,7 +277,7 @@ Server-rendered pages with the shared chrome (`openvibe-shared` v1.3.0: navbar a
- OpenVibe.Network OAuth client **`host`**, redirect `https://openvibe.host/auth/callback`, scope `profile theme`. The same client is the service principal `svc:host`.
- Grant `[host, events.event.publish, openvibe.events]`.
- Callers of Host get `[<client>, host.site.manage | host.deploy.create | host.domain.manage, openvibe.host]` as needed. None exist yet (Codes, the expected first caller, is not built).
--- Released in `openvibe-contracts` v0.24.0 (CI contract check blocking). Next release: add the takedown routes to `host.site.manage.implementedBy` (docs/launch.md).
++- Released in `openvibe-contracts` v0.24.0 (CI contract check blocking); v0.32.0 adds the takedown routes to `host.site.manage.implementedBy`.
### Deploying Stage B (for the operator)
ddiff --git a/STATUS.json b/STATUS.json
iindex b2952a8..53a11db 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -9,9 +9,9 @@
"domain": "openvibe.host",
"tenantDomain": "*.openvibe.host",
"packages": [
-- "openvibe-contracts v0.24.0 (host.* capabilities and manifest released; unchanged in v0.30.1)",
-- "openvibe-sdk v0.2.2",
-- "openvibe-shared v1.3.0"
++ "openvibe-contracts v0.32.0 (host.* capabilities and manifest released in v0.24.0; v0.32.0 adds the takedown routes to host.site.manage)",
++ "openvibe-sdk v0.5.0",
++ "openvibe-shared v1.5.0"
],
"capabilities": [
"host.site.manage",
@@@ -38,6 +38,6 @@
"placeholderInSites": true,
"adoptedByServices": [],
"updated": "2026-09-23",
-- "capabilitiesNote": "released in openvibe-contracts v0.24.0 and identical in v0.30.1; the next release should add the four takedown routes to host.site.manage.implementedBy (docs/launch.md)",
++ "capabilitiesNote": "released in openvibe-contracts v0.24.0; v0.32.0 adds the four takedown routes to host.site.manage.implementedBy",
"launchReady": true
}
ddiff --git a/docs/launch.md b/docs/launch.md
iindex c504aa4..6744f76 100644
---- a/docs/launch.md
++++ b/docs/launch.md
@@@ -146,9 +146,8 @@ restart `openvibe-network`, poll `/api/ready`).
**6. Host's own records.** In a follow-up Host commit, set README **Status**, `STATUS.json`
(`"deployed"`, `"placeholderInSites": false`) and the Launch rule note to *launched on <date>*.
--**Contracts** (no runtime effect, whenever the next release is cut): in
--`manifests/capabilities/host.site.manage.json`, add the takedown routes and sentence from this
--repository's `docs/capabilities-proposal/host.site.manage.json`:
++**Contracts** (done in openvibe-contracts v0.32.0, which Host pins): `host.site.manage` lists the
++takedown routes from this repository's `docs/capabilities-proposal/host.site.manage.json`:
- `POST /api/v1/projects/:id/takedown`
- `DELETE /api/v1/projects/:id/takedown`
ddiff --git a/package-lock.json b/package-lock.json
iindex 2ec5ede..e8b8149 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,9 +16,9 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.24.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.2.2",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
"bin": {
"ovhost": "bin/ovhost"
@@@ -1412,9 +1412,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.24.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.24.0",
-- "integrity": "sha512-8cG+TyI7DAEF1x2MAKlc6Y2AiKWArICYj0QNX4DAbTsOT+ENVTFN5AI35oByz3B1AvBX3nHUNFCK7IM1Zy/+QA==",
++ "version": "0.32.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
++ "integrity": "sha512-ntN58SIG03YDxbhsiIzOM5GRH19jRMHZUYWI3xU2VwovExagF5DdwApLVfezsu84UTJ+V2Jfn+LWDc+Oz8ab+g==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1428,18 +1428,18 @@
}
},
"node_modules/openvibe-sdk": {
-- "version": "0.2.2",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.2.2",
-- "integrity": "sha512-kDifUp3EcmnTWzElAmaCZnX659WErcnm4+qVEoAiYQWX/w/1p4TWvoHbUPsrX65TSROVR3tiyVkl801n/0yyCw==",
++ "version": "0.5.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
++ "integrity": "sha512-QVJkR5iazQX0ZwIrDp2A88ZeYYBSXEmSzhpgURPUSnzezIM3jz0HBhVKowyhkvg2aBO61XNf77eb4zJGkMjZiw==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/openvibe-shared": {
-- "version": "1.3.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
-- "integrity": "sha512-w8yl/5+9eend8I2O/DzAJnEAL2N3GTUY7c7pzK+MLJuLAGD8FmJJQ9gwbgyrq70Snjt3ebrg3TlpORhRAp2kgA==",
++ "version": "1.5.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0",
++ "integrity": "sha512-cehnscuYMkJwuCYGcI6wPSswbUn0bhTyGAaD5feNCoO//2e9gyC7cgjkEcIOtM8DQF3yTuUNyXnOwckIeM4fUg==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex c51c81e..2f71c30 100644
---- a/package.json
++++ b/package.json
@@@ -31,9 +31,9 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.24.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.2.2",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
"engines": {
"node": ">=22.0.0"
ddiff --git a/server/app.js b/server/app.js
iindex d0992fd..8740b1f 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -128,7 +128,8 @@ function createApp(opts = {}) {
// ── Machine endpoints ───────────────────────────────────
app.get('/api/health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-host', version: VERSION }));
-- app.get('/release.json', release.handler);
++ // GET /release.json (ADR-016) and POST /release-metrics: open tabs' update reports into /metrics.
++ release.mount(app, { registry: registry });
const readiness = createHostReadiness({ store, blobs, auth, outbox, release: release.release, minFreeBytes: () => config.uploads.minFreeBytes });
app.get('/api/ready', readiness.handler);
app.get('/metrics', sharedMetrics.metricsHandler(registry));
ddiff --git a/server/auth/viewer.js b/server/auth/viewer.js
iindex 0b66f77..58d8684 100644
---- a/server/auth/viewer.js
++++ b/server/auth/viewer.js
@@@ -36,7 +36,10 @@ function createViewerResolver({ auth, config }) {
async function fromServiceToken(req, token) {
const publicKey = await auth.ensureKey();
if (!publicKey) throw new ViewerError(503, 'identity.unavailable', 'the Network signing key is not loaded yet');
-- const r = serviceAuth.verifyServiceToken(token, { publicKey, issuer: config.networkUrl, audience: AUDIENCE });
++ // acceptSandbox: Host takes sandbox tokens itself and confines them to sandbox projects
++ // (projects.js/access.js answer 403 environment.sandbox_token elsewhere); openvibe-contracts
++ // >= 0.26.0 refuses them before that unless the receiver opts in.
++ const r = serviceAuth.verifyServiceToken(token, { publicKey, issuer: config.networkUrl, audience: AUDIENCE, acceptSandbox: true });
if (!r.ok) throw new ViewerError(401, r.code, r.reason);
const sub = r.claims.sub;
if (sub.startsWith('mod:')) throw new ViewerError(403, 'principal.not_allowed', 'mods cannot manage hosted sites');
ddiff --git a/test/host-secrets-dashboard.test.js b/test/host-secrets-dashboard.test.js
iindex 7084816..40559ff 100644
---- a/test/host-secrets-dashboard.test.js
++++ b/test/host-secrets-dashboard.test.js
@@@ -145,6 +145,8 @@ const ORIGIN = 'https://openvibe.host';
assert.match(ready.text, /relay off/);
const rel = await t.api('GET', '/release.json');
assert.strictEqual(rel.json().service, 'host');
++ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json()).errors, []);
++ assert.strictEqual(rel.json().metrics_url, '/release-metrics');
const m = await t.api('GET', '/metrics');
assert.strictEqual(m.status, 200);
assert.match(m.text, /http_requests_total\{method="GET",route="tenant_site"/);
ddiff --git a/test/stageb/mocks.js b/test/stageb/mocks.js
iindex 2cccfed..a69625f 100644
---- a/test/stageb/mocks.js
++++ b/test/stageb/mocks.js
@@@ -63,8 +63,9 @@ async function startNetwork() {
function serviceToken(client, cap, extra = {}) {
return sign({ sub: `svc:${client}`, aud: ['openvibe.host'], cap, ...extra });
}
++ // An app token carries its developer project and env, as Network's do (identity.service-token-claims 1.2.0).
function appToken(appId, cap, extra = {}) {
-- return sign({ sub: `app:${appId}`, actor_type: 'app', aud: ['openvibe.host'], cap, ...extra });
++ return sign({ sub: `app:${appId}`, actor_type: 'app', aud: ['openvibe.host'], cap, project_id: 'prj_01J8ZQ4Y7N3M2K1H0G9F8E7D6C', env: 'production', ...extra });
}
return { ...srv, publicPem, addUser, userToken, fedcmAssertion, serviceToken, appToken, sign };
}