openvibers/openvibe.events Commits

README and STATUS.json are current (roadmap WS-U task 3): the README gains Capabilities (the seven implemented capabilities; Events calls no service with a grant; events.usage.recorded), Security (SECURITY.md, auth, private data, egress, delivery signatures, exposure and ticket-free logs) and Deploy (sudo ovhost deploy events, unit, port, env file, store, vhost, drain, rollback) sections. The status line no longer says Media reports only over Live's webhook or that no browser uses the realtime stream, and the contracts note names the v0.63.0 pin. STATUS.json names openvibe-contracts v0.63.0, gains features and is updated 2026-09-28.

OP OpenVibers committed 28 Sept 2026 · 19919ece79 · 2 files, +62 −14


Changes

ddiff --git a/README.md b/README.md
iindex 02af6c0..553fd4e 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Durable events, subscriptions, delivery, retry, dead letters and replay for the network.
--**Status:** alpha (roadmap Wave 3), deployed on `openvibe-ovh` (unit `openvibe-events`, 127.0.0.1:4300, public at `https://events.openvibe.network`). Live (`live.stream.*`), Media, Chat, Games, Network and OpenRe publish to it in production, and every consumer webhook requires signature v2. Few consumers act on the events yet: Media still reports finalisation to Live over `/internal/media-webhook`, and no browser surface uses the realtime stream.
++**Status:** alpha (roadmap Wave 3), deployed on `openvibe-ovh` (unit `openvibe-events`, 127.0.0.1:4300, public at `https://events.openvibe.network`). Live (`live.stream.*`), Media, Chat, Games, Network and OpenRe publish to it in production, and every consumer webhook requires signature v2. Media's completion events reach Live through Events (`/internal/media-events`; Live keeps the older `/internal/media-webhook` during the transition), and the shared notification bell can follow `network.notification.created` over the realtime stream with a Network ticket.
**Domain:** `events.openvibe.network` (the realtime gateway is served here too: ADR-005 put Realtime inside Events, and `realtime.openvibe.network` has no runtime of its own)
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §6 and §6.3.
**License:** AGPL-3.0 (same as every OpenVibe service).
@@@ -37,7 +37,7 @@ Services call with an OpenVibe.Network client-credentials token (`POST /oauth/to
| `events.event.read` | `GET /api/v1/events`, `GET /api/v1/events/:id`, `/api/v1/checkpoints`, realtime as a service |
| `events.delivery.admin` | `GET /api/v1/deliveries`, `POST /api/v1/deliveries/replay` |
--These four are `internal` in `openvibe-contracts` (never granted to developer apps). Developer apps use the three `public` capabilities in [Developer apps](#developer-apps) instead, on the same routes. `server/auth.js` grants with the contracts rule (exact id or a `family.*` grant) and hands the decision to `capabilities.check()` for every id the installed contracts know (v0.28.0 knows all seven).
++These four are `internal` in `openvibe-contracts` (never granted to developer apps). Developer apps use the three `public` capabilities in [Developer apps](#developer-apps) instead, on the same routes. `server/auth.js` grants with the contracts rule (exact id or a `family.*` grant) and hands the decision to `capabilities.check()` for every id the installed contracts know (the pinned v0.63.0 knows all seven).
Sandbox tokens (`env: sandbox`, developer apps only) are accepted only on the developer-app routes; every other route answers `401 token.sandbox_refused`. App tokens are never judged on a first-party capability: an app token on an operator route is a `403`.
@@@ -59,6 +59,16 @@ Every capability route also limits each principal (`svc:live`, `app:app_…`) by
Never limited: `/api/health`, `/api/ready`, `/release.json`, `/limits.json`, `/metrics` and the realtime stream (capped by `REALTIME_MAX_CONNECTIONS` and `REALTIME_MAX_TOPICS`). `test/actor-limits.test.js`.
++## Capabilities
++
++Implemented here (the service manifest's `capabilities`): the four internal ones in the table above
++(`events.event.publish`, `events.event.read`, `events.subscription.manage`, `events.delivery.admin`)
++and the three public developer-app ones (`events.app.publish`, `events.app.read`,
++`events.app.subscribe`, [Developer apps](#developer-apps)). Events calls no other service with a
++grant: it only loads the Network signing key (JWKS) and makes the signed deliveries its subscriptions
++ask for. It produces one event of its own, `events.usage.recorded` (a project's hourly publishing and
++delivery rollup, for Network; never visible to apps).
++
## Publishing
`POST /api/v1/events` takes one `events.event-envelope@1` envelope, or `{ "events": [ … ] }` (up to 100, stored atomically).
@@@ -234,6 +244,41 @@ es.addEventListener('gap', (m) => { /* events were missed: refetch state */ });
Replaces the best-effort internal POSTs/webhooks between Live, Media, Network and Community. First migrations: notifications, then Media ready/failed, then stream lifecycle.
++## Security
++
++Reporting a vulnerability: [SECURITY.md](SECURITY.md). The rules the code keeps:
++
++- **Auth.** Every API route needs a Network client-credentials token for audience `openvibe.events`,
++ verified offline against the Network JWKS, holding that route's capability. App tokens are never
++ judged on a first-party capability; sandbox tokens are accepted only on developer-app routes.
++ Realtime accepts a single-use Network ticket, the `ov_token` cookie, a Bearer user JWT or a service
++ token with `events.event.read`; a ticket is never a session and is never logged.
++- **Private data.** `internal` events never reach a browser; `subject` events reach only that person;
++ a guessed topic yields nothing; redacted events replay as tombstones. Developer apps see only their
++ own project and environment.
++- **Egress.** First-party subscription endpoints must be `http(s)` on `127.0.0.1` or an OpenVibe host
++ (`EVENTS_ENDPOINT_HOSTS`); app webhooks must be `https` and resolve only to public addresses,
++ checked at creation and inside every delivery's DNS lookup ([server/egress.js](server/egress.js));
++ redirects are never followed.
++- **Integrity.** Deliveries are signed per subscription (v1 and v2; consumers require v2); a
++ subscription secret is shown once.
++- **Exposure.** `/api/v1/deliveries` stays host-local and `/metrics` answers direct loopback callers
++ only; nginx logs `/realtime/stream` without its query string
++ (`deploy/nginx/log_format_events_noquery.conf`), so tickets never reach the access log.
++
++## Deploy
++
++Production deploys with `sudo ovhost deploy events` on the host (strategy `git-checkout`: fetch,
++fast-forward `/opt/openvibe.events`, install on a lockfile change, restart, wait for `/api/ready`).
++The unit is `openvibe-events.service` on `127.0.0.1:4300`, the env file `/etc/openvibe/events.env`. The store is
++`/var/lib/openvibe-events/events.db`; nginx serves `events.openvibe.network` from
++[deploy/nginx/events.openvibe.network.conf](deploy/nginx/events.openvibe.network.conf). ovhost treats open
++realtime connections as a report-only drain, so `--wait-idle` waits for them.
++Rollback: ovhost puts the previous sha back by itself when `/api/ready` does not answer 2xx after the
++restart; afterwards `sudo ovhost rollback events --to <sha>`. Nothing blocks a rollback: the schema
++code only adds tables and columns.
++`EVENTS_LIMITS=off` turns the per-actor limits off without a deploy.
++
## Launch rule
This repository does not make the product real, and the domain keeps its placeholder page on
ddiff --git a/STATUS.json b/STATUS.json
iindex a12affa..459c2a5 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -7,18 +7,21 @@
"runtime": "node 22, express, better-sqlite3",
"port": 4300,
"domain": "events.openvibe.network",
-- "contracts": "openvibe-contracts v0.28.0",
-- "capabilities": [
-- "events.event.publish",
-- "events.subscription.manage",
-- "events.event.read",
-- "events.delivery.admin",
-- "events.app.publish",
-- "events.app.read",
-- "events.app.subscribe"
-- ],
++ "contracts": "openvibe-contracts v0.63.0",
++ "capabilities": ["events.event.publish", "events.subscription.manage", "events.event.read", "events.delivery.admin", "events.app.publish", "events.app.read", "events.app.subscribe"],
"capabilitiesRegistered": true,
"deployed": true,
-- "deployment": "openvibe-ovh /opt/openvibe.events, unit openvibe-events, 127.0.0.1:4300; public at https://events.openvibe.network (realtime SSE, health, token-guarded /api/v1/events, /api/v1/subscriptions, /api/v1/checkpoints; /api/v1/deliveries host-local); release 5def126; real traffic since 2026-09-23 (Live, Media, Chat, Games, Network and OpenRe publish; 8 subscriptions, held by Search, Tips, VIP, News and Reviews); restore drill passed 2026-09-23",
-- "updated": "2026-09-23"
++ "deployment": "Deployed with `sudo ovhost deploy events`: /opt/openvibe.events, unit openvibe-events, 127.0.0.1:4300; public at https://events.openvibe.network (realtime SSE, health, token-guarded /api/v1/events, /api/v1/subscriptions, /api/v1/checkpoints; /api/v1/deliveries host-local); real traffic since 2026-09-23 (Live, Media, Chat, Games, Network and OpenRe publish; on 2026-09-23 8 subscriptions, held by Search, Tips, VIP, News and Reviews); restore drill passed 2026-09-23",
++ "updated": "2026-09-28",
++ "features": [
++ "durable event store with the canonical envelope",
++ "subscriptions with signed webhook delivery (v1 and v2), retry, dead letters and replay",
++ "priority classes and backpressure",
++ "pull consumers with cursors and checkpoints",
++ "realtime SSE with cursor resume, gap reports and Network tickets",
++ "developer-app publish, read and subscribe with sandbox separation, SSRF-guarded webhooks and project quotas",
++ "hourly project usage rollups (events.usage.recorded)",
++ "redaction tombstones",
++ "per-actor limits (openvibe-sdk/limits)"
++ ]
}