openvibers/openvibe.events Commits

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

OP OpenVibers committed 23 Sept 2026 · 039e2036f9 · 5 files, +16 −12

- /release.json is mounted with release.mount(app, { registry }) on the instrument() registry, so it
  also answers POST /release-metrics (release_client_updates_total in /metrics). On contracts 0.32.0
  the manifest carries the registry.release-manifest 1.1.0 fields; no components are declared, so
  nothing changes for clients. The observability test validates the served manifest against the
  contracts.
- Contracts 0.28.0 -> 0.32.0 is additive for Events (it validates envelopes only, not payloads);
  the lockfile now also records openvibe-shared's integrity.

Changes

ddiff --git a/README.md b/README.md
iindex 362b8d9..53dbb1e 100644
---- a/README.md
++++ b/README.md
@@@ -24,7 +24,7 @@ Node 22 in production (`fnm exec --using=22.22.1 npm test`). Production: `/opt/o
`GET /api/health` is liveness. `GET /api/ready` (openvibe-shared/ready) is 200 only when every required check passes — `db` (a real query), `network_jwks` (the Network signing key has loaded; it retries every 30 s while Network boots) and `delivery_worker` (when `EVENTS_WORKER` is on) — and 503 otherwise, with `failed: [...]`. The optional `dlq` check fails once more than `EVENTS_DLQ_DEGRADED_AT` (default 100) deliveries are dead: the service stays ready (200) and reports `status: "degraded"`, `degraded: ["dlq"]`. Each check carries `status`, `required`, `latency_ms`, `checked_at` and, for `dlq`, `detail: { depth, threshold }`; `latest_seq`, `deliveries`, `worker` and `realtime_connections` are still in the body. **Shape change (Track O):** `checks` used to be booleans (`{ db, worker, key }`); they are now objects keyed `db`, `network_jwks`, `delivery_worker`, `dlq`.
--`GET /metrics` (Prometheus text, direct loopback callers only — a request carrying `X-Forwarded-For` gets 404, and nginx refuses the path): HTTP golden signals by route template (`http_requests_total`, `http_request_duration_seconds`, `http_requests_in_flight`; SSE sessions excluded), process metrics, `release_info`, and `events_deliveries{status}`, `events_dlq_depth`, `events_latest_seq`, `events_realtime_connections`, `events_delivery_latency_seconds{priority}` (acceptance → successful delivery, retries included), `events_delivery_attempts_total{outcome}`. `GET /release.json` is the deployed release (ADR-016).
++`GET /metrics` (Prometheus text, direct loopback callers only — a request carrying `X-Forwarded-For` gets 404, and nginx refuses the path): HTTP golden signals by route template (`http_requests_total`, `http_request_duration_seconds`, `http_requests_in_flight`; SSE sessions excluded), process metrics, `release_info`, and `events_deliveries{status}`, `events_dlq_depth`, `events_latest_seq`, `events_realtime_connections`, `events_delivery_latency_seconds{priority}` (acceptance → successful delivery, retries included), `events_delivery_attempts_total{outcome}`. `GET /release.json` is the deployed release (ADR-016, `release.mount` from openvibe-shared 1.5.0, with the manifest 1.1.0 fields under openvibe-contracts 0.32.0); its `POST /release-metrics` would count open tabs' update reports as `release_client_updates_total`, but Events has no pages and nginx does not proxy that path.
## Auth
ddiff --git a/package-lock.json b/package-lock.json
iindex 1884c92..ee7fa78 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -12,8 +12,8 @@
"better-sqlite3": "^11.10.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.28.0",
-- "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-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
"engines": {
"node": ">=20.0.0"
@@@ -967,9 +967,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.28.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.28.0",
-- "integrity": "sha512-cOOEmIhcjDmnWbo4jsz6kYOKT+Kc+aitYoc4w/EJOKg5uvJDt10vctjKB3IIl53UXeuDOhbckP53yBdBeewC1A==",
++ "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",
@@@ -983,8 +983,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.3.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
++ "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 c780019..3093b71 100644
---- a/package.json
++++ b/package.json
@@@ -17,8 +17,8 @@
"better-sqlite3": "^11.10.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.28.0",
-- "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-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
"engines": {
"node": ">=20.0.0"
ddiff --git a/server/app.js b/server/app.js
iindex f10bcb9..36e7532 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -18,9 +18,10 @@ function createApp({ config, store, auth, keys, worker, realtime, metrics, dnsLo
const release = createRelease({ service: 'events', root: path.join(__dirname, '..') });
// HTTP golden signals by route template + GET /metrics (direct loopback callers only). An SSE
// connection is a session, not a request, so it is counted by events_realtime_connections instead.
-- instrument(app, { service: 'events', release: release.release, registry: metrics && metrics.registry, skip: (req) => req.path === '/realtime/stream' });
++ const instrumented = instrument(app, { service: 'events', release: release.release, registry: metrics && metrics.registry, skip: (req) => req.path === '/realtime/stream' });
app.use(http.middleware());
-- 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: instrumented.registry });
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
ddiff --git a/test/observability.test.js b/test/observability.test.js
iindex dbdc14c..0619add 100644
---- a/test/observability.test.js
++++ b/test/observability.test.js
@@@ -84,6 +84,8 @@ t('release.json', async () => {
const r = await request(h.base, 'GET', '/release.json');
assert.strictEqual(r.status, 200);
assert.strictEqual(r.body.service, 'events');
++ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', r.body).errors, []);
++ assert.strictEqual(r.body.metrics_url, '/release-metrics');
});
t('a stopped worker makes the service unready', async () => {