openvibers/openvibe.events Commits
Events: OpenVibe.Space publishes (source space); contracts 0.110.0, shared 2.12.0 (#19)
Changes
@@@ -38,7 +38,7 @@ Services call with an OpenVibe.Network client-credentials token (`POST /oauth/to| `events.delivery.admin` | `GET /api/v1/deliveries`, `POST /api/v1/deliveries/replay` || `events.resource.read` | `GET /api/v1/resources`, `GET /api/v1/resources/:ovrn` (the [resource index](#resource-index)) |--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. `events.resource.read` is `first-party` (ADR-048) and still `planned` in `openvibe-contracts` until the deploy lands — the guard is the same service-token check, and the same 401/403, every other route here gets. `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.107.0 knows all eight).++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. `events.resource.read` is `first-party` (ADR-048), `active` in `openvibe-contracts` since v0.110.0 — the guard is the same service-token check, and the same 401/403, every other route here gets. `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.110.0 knows all eight).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`.@@@ -66,7 +66,7 @@ Implemented here (the service manifest's `capabilities`): the four internal ones(`events.event.publish`, `events.event.read`, `events.subscription.manage`, `events.delivery.admin`),the three public developer-app ones (`events.app.publish`, `events.app.read`, `events.app.subscribe`,[Developer apps](#developer-apps)) and the first-party `events.resource.read` (ADR-048,--[Resource index](#resource-index); `planned` in `openvibe-contracts` until the deploy lands). Events++[Resource index](#resource-index); `active` in `openvibe-contracts` since v0.110.0). Eventscalls no other service with a grant: it only loads the Network signing key (JWKS) and makes the signeddeliveries its subscriptions ask for. It produces one event of its own, `events.usage.recorded` (aproject's hourly publishing and delivery rollup, for Network; never visible to apps).@@@ -75,7 +75,7 @@ project's hourly publishing and delivery rollup, for Network; never visible to a`POST /api/v1/events` takes one `events.event-envelope@1` envelope, or `{ "events": [ … ] }` (up to 100, stored atomically).--- `source` must be the calling service (`svc:live` publishes `source: "live"`), and `event_type` must start with a prefix that source owns (`live.*`, `media.*`, `network.*`, `community.*`, `chat.*`, `openre.*`, `billing.*`, `tips.*`, `vip.*`, `ai.*`, `games.*`, `tools.*`, `codes.*`, `host.*`, `bot.*`, `watch.*`, `run.*`, and the publication products (`wiki.*`, `blog.*`, `news.*`, `reviews.*`, `deals.*`, `coupons.*`, `trade.*`, `sources.*`, `search.*`); `EVENTS_SOURCE_PREFIXES` overrides). A network-wide prefix may be published by any first-party service: `provider.*` carries storage provider health/capacity telemetry (`provider.health.degraded`, `provider.capacity.warning`), so whichever service observes a provider can report it; `EVENTS_SHARED_PREFIXES` overrides.++- `source` must be the calling service (`svc:live` publishes `source: "live"`), and `event_type` must start with a prefix that source owns (`live.*`, `media.*`, `network.*`, `community.*`, `chat.*`, `openre.*`, `billing.*`, `tips.*`, `vip.*`, `ai.*`, `games.*`, `tools.*`, `codes.*`, `host.*`, `bot.*`, `watch.*`, `run.*`, `space.*`, and the publication products (`wiki.*`, `blog.*`, `news.*`, `reviews.*`, `deals.*`, `coupons.*`, `trade.*`, `sources.*`, `search.*`); `EVENTS_SOURCE_PREFIXES` overrides). A network-wide prefix may be published by any first-party service: `provider.*` carries storage provider health/capacity telemetry (`provider.health.degraded`, `provider.capacity.warning`), so whichever service observes a provider can report it; `EVENTS_SHARED_PREFIXES` overrides.- Idempotent on `event_id`: a repeat answers `200 { event_id, seq, duplicate: true }` and is never stored twice (even after retention, for `EVENTS_RECEIPT_RETENTION_DAYS`).- Missing `trace_id` is taken from the request's `traceparent`; `priority` defaults to `important`, `visibility` to `internal`.- Loop guard: an event whose trace already carries 8 hops (the cross-service chain depth, or the same source/type/subject repeating in the trace) is refused with `409 events.loop_detected`. The chain depth of a first-party publish counts first-party events only (developer-app events in the same trace never count toward it), so an app cannot poison a trace it has seen.@@@ -7,7 +7,7 @@"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9","port": 4300,"domain": "events.openvibe.network",-- "contracts": "openvibe-contracts v0.107.0",++ "contracts": "openvibe-contracts v0.110.0","capabilities": ["events.event.publish","events.subscription.manage",@@@ -12,9 +12,9 @@"dotenv": "^16.4.7","express": "^4.21.2","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.12.0","pg": "^8.23.0"},"devDependencies": {@@@ -828,9 +828,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.107.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",-- "integrity": "sha512-bttjTDLzCfHFtq5h0EkBaPspVV6U934jj5Hz7RViIh2UTiC2Xzy3d6AnHLsEomVyTwaiwg9veNiO+eZTZ0lxsg==",++ "version": "0.110.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",++ "integrity": "sha512-+65KnRBVOzPN+MKSdaKzbvop6CT/wr8ZrO9mrQrRDdprRDmL4+CCOnykKYFOiMWPtoYkXlYg+GXmwmOw7eBL/g==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -873,9 +873,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.10.1",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",-- "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",++ "version": "2.12.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.12.0",++ "integrity": "sha512-GQpNFs2h61OKKNLexQwCfSJTLzNQ7fSbwqvrerB4oNdoxUgDvmdD43SELWycrb9DX8lkje4cR3ARznYIINnR8Q==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -24,9 +24,9 @@"dotenv": "^16.4.7","express": "^4.21.2","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.12.0","pg": "^8.23.0"},"engines": {@@@ -23,8 +23,8 @@* That is also what GET /api/v1/resources/:ovrn can read: only a resource whose computed ovrn equals the one* asked for answers, so an OVRN naming another project, or a project-less subscription's id, is a 404.*-- * The capability stays `planned` in openvibe-contracts until the deploy lands and Opus flips it (this step-- * does not change Contracts); the guard is the same service-token check every other first-party route uses.++ * The capability is `active` in openvibe-contracts since v0.110.0; the guard is the same service-token check++ * every other first-party route uses.*/const express = require('express');const contracts = require('openvibe-contracts');@@@ -33,9 +33,9 @@ const CAPS = Object.freeze({appPublish: 'events.app.publish',appRead: 'events.app.read',appSubscribe: 'events.app.subscribe',-- // The authority resource index (ADR-048, plan T13 step 8). `planned` in openvibe-contracts until the-- // deploy lands, so capabilities.check() answers capability.unknown and the local rule (exact id or a-- // `family.*` grant) decides — the same rule, and the same 401/403, every other service route gets.++ // The authority resource index (ADR-048, plan T13 step 8). `active` in openvibe-contracts since v0.110.0,++ // so capabilities.check() applies the contracts rule (exact id or a `family.*` grant); the local fallback++ // below only covers an id the installed contracts do not know yet.resourceRead: 'events.resource.read',});@@@ -25,7 +25,9 @@ const DEFAULT_SOURCES = ['live', 'media', 'network', 'community', 'chat', 'openr// Watch (OpenVibe.Watch): watch.observation.recorded, watch.watch.triggered, watch.check.failed'watch',// Run (OpenVibe.Run, plan T14): run.job.queued|started|succeeded|failed|cancelled|expired-- 'run'];++ 'run',++ // Space (OpenVibe.Space, plan T10 step 3): space.thread.created, space.post.created, space.moderation.action++ 'space'];/*** Namespaces that belong to the network rather than to one service. `provider.*` carries storage@@@ -117,6 +117,22 @@ t('a watch service principal publishes watch.* only', async () => {assert.strictEqual(r.body.code, 'events.unknown_source');});++t('a space service principal publishes space.* only', async () => {++ const space = serviceToken('space', ['events.event.publish']);++ // Real ids from openvibe-contracts (Space, v0.110.0+): space.thread.created, space.post.created, space.moderation.action.++ for (const event_type of ['space.thread.created', 'space.post.created', 'space.moderation.action']) {++ const r = await request(h.base, 'POST', '/api/v1/events', { token: space, body: envelope('space', { event_type }) });++ assert.strictEqual(r.status, 201, r.text);++ }++ let r = await request(h.base, 'POST', '/api/v1/events', { token: space, body: envelope('space', { event_type: 'live.stream.started' }) });++ assert.strictEqual(r.status, 403);++ assert.strictEqual(r.body.code, 'events.type_not_allowed');++ // A source not in the list cannot claim the space namespace.++ r = await request(h.base, 'POST', '/api/v1/events', { token: serviceToken('stranger', ['events.event.publish']), body: envelope('stranger', { event_type: 'space.thread.created' }) });++ assert.strictEqual(r.status, 403);++ assert.strictEqual(r.body.code, 'events.unknown_source');++});++t('unknown source (not in the manifest map) -> 403', async () => {const r = await request(h.base, 'POST', '/api/v1/events', { token: serviceToken('stranger', ['events.event.publish']), body: envelope('stranger') });assert.strictEqual(r.status, 403);
@@ -38,7 +38,7 @@ Services call with an OpenVibe.Network client-credentials token (`POST /oauth/to
@@ -38,7 +38,7 @@ Services call with an OpenVibe.Network client-credentials token (`POST /oauth/to
| `events.delivery.admin` | `GET /api/v1/deliveries`, `POST /api/v1/deliveries/replay` |
| `events.delivery.admin` | `GET /api/v1/deliveries`, `POST /api/v1/deliveries/replay` |
| `events.resource.read` | `GET /api/v1/resources`, `GET /api/v1/resources/:ovrn` (the [resource index](#resource-index)) |
| `events.resource.read` | `GET /api/v1/resources`, `GET /api/v1/resources/:ovrn` (the [resource index](#resource-index)) |
-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. `events.resource.read` is `first-party` (ADR-048) and still `planned` in `openvibe-contracts` until the deploy lands — the guard is the same service-token check, and the same 401/403, every other route here gets. `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.107.0 knows all eight).
+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. `events.resource.read` is `first-party` (ADR-048), `active` in `openvibe-contracts` since v0.110.0 — the guard is the same service-token check, and the same 401/403, every other route here gets. `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.110.0 knows all eight).
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`.
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`.
@@ -66,7 +66,7 @@ Implemented here (the service manifest's `capabilities`): the four internal ones
@@ -66,7 +66,7 @@ Implemented here (the service manifest's `capabilities`): the four internal ones
(`events.event.publish`, `events.event.read`, `events.subscription.manage`, `events.delivery.admin`),
(`events.event.publish`, `events.event.read`, `events.subscription.manage`, `events.delivery.admin`),
the three public developer-app ones (`events.app.publish`, `events.app.read`, `events.app.subscribe`,
the three public developer-app ones (`events.app.publish`, `events.app.read`, `events.app.subscribe`,
[Developer apps](#developer-apps)) and the first-party `events.resource.read` (ADR-048,
[Developer apps](#developer-apps)) and the first-party `events.resource.read` (ADR-048,
-[Resource index](#resource-index); `planned` in `openvibe-contracts` until the deploy lands). Events
+[Resource index](#resource-index); `active` in `openvibe-contracts` since v0.110.0). Events
calls no other service with a grant: it only loads the Network signing key (JWKS) and makes the signed
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
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).
project's hourly publishing and delivery rollup, for Network; never visible to apps).
@@ -75,7 +75,7 @@ project's hourly publishing and delivery rollup, for Network; never visible to a
@@ -75,7 +75,7 @@ project's hourly publishing and delivery rollup, for Network; never visible to a
`POST /api/v1/events` takes one `events.event-envelope@1` envelope, or `{ "events": [ … ] }` (up to 100, stored atomically).
`POST /api/v1/events` takes one `events.event-envelope@1` envelope, or `{ "events": [ … ] }` (up to 100, stored atomically).
-- `source` must be the calling service (`svc:live` publishes `source: "live"`), and `event_type` must start with a prefix that source owns (`live.*`, `media.*`, `network.*`, `community.*`, `chat.*`, `openre.*`, `billing.*`, `tips.*`, `vip.*`, `ai.*`, `games.*`, `tools.*`, `codes.*`, `host.*`, `bot.*`, `watch.*`, `run.*`, and the publication products (`wiki.*`, `blog.*`, `news.*`, `reviews.*`, `deals.*`, `coupons.*`, `trade.*`, `sources.*`, `search.*`); `EVENTS_SOURCE_PREFIXES` overrides). A network-wide prefix may be published by any first-party service: `provider.*` carries storage provider health/capacity telemetry (`provider.health.degraded`, `provider.capacity.warning`), so whichever service observes a provider can report it; `EVENTS_SHARED_PREFIXES` overrides.
+- `source` must be the calling service (`svc:live` publishes `source: "live"`), and `event_type` must start with a prefix that source owns (`live.*`, `media.*`, `network.*`, `community.*`, `chat.*`, `openre.*`, `billing.*`, `tips.*`, `vip.*`, `ai.*`, `games.*`, `tools.*`, `codes.*`, `host.*`, `bot.*`, `watch.*`, `run.*`, `space.*`, and the publication products (`wiki.*`, `blog.*`, `news.*`, `reviews.*`, `deals.*`, `coupons.*`, `trade.*`, `sources.*`, `search.*`); `EVENTS_SOURCE_PREFIXES` overrides). A network-wide prefix may be published by any first-party service: `provider.*` carries storage provider health/capacity telemetry (`provider.health.degraded`, `provider.capacity.warning`), so whichever service observes a provider can report it; `EVENTS_SHARED_PREFIXES` overrides.
- Idempotent on `event_id`: a repeat answers `200 { event_id, seq, duplicate: true }` and is never stored twice (even after retention, for `EVENTS_RECEIPT_RETENTION_DAYS`).
- Idempotent on `event_id`: a repeat answers `200 { event_id, seq, duplicate: true }` and is never stored twice (even after retention, for `EVENTS_RECEIPT_RETENTION_DAYS`).
- Missing `trace_id` is taken from the request's `traceparent`; `priority` defaults to `important`, `visibility` to `internal`.
- Missing `trace_id` is taken from the request's `traceparent`; `priority` defaults to `important`, `visibility` to `internal`.
- Loop guard: an event whose trace already carries 8 hops (the cross-service chain depth, or the same source/type/subject repeating in the trace) is refused with `409 events.loop_detected`. The chain depth of a first-party publish counts first-party events only (developer-app events in the same trace never count toward it), so an app cannot poison a trace it has seen.
- Loop guard: an event whose trace already carries 8 hops (the cross-service chain depth, or the same source/type/subject repeating in the trace) is refused with `409 events.loop_detected`. The chain depth of a first-party publish counts first-party events only (developer-app events in the same trace never count toward it), so an app cannot poison a trace it has seen.
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4300,
"port": 4300,
"domain": "events.openvibe.network",
"domain": "events.openvibe.network",
- "contracts": "openvibe-contracts v0.107.0",
+ "contracts": "openvibe-contracts v0.110.0",
"capabilities": [
"capabilities": [
"events.event.publish",
"events.event.publish",
"events.subscription.manage",
"events.subscription.manage",
@@ -12,9 +12,9 @@
@@ -12,9 +12,9 @@
"dotenv": "^16.4.7",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.12.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"devDependencies": {
"devDependencies": {
@@ -828,9 +828,9 @@
@@ -828,9 +828,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.107.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",
- "integrity": "sha512-bttjTDLzCfHFtq5h0EkBaPspVV6U934jj5Hz7RViIh2UTiC2Xzy3d6AnHLsEomVyTwaiwg9veNiO+eZTZ0lxsg==",
+ "version": "0.110.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
+ "integrity": "sha512-+65KnRBVOzPN+MKSdaKzbvop6CT/wr8ZrO9mrQrRDdprRDmL4+CCOnykKYFOiMWPtoYkXlYg+GXmwmOw7eBL/g==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -873,9 +873,9 @@
@@ -873,9 +873,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.10.1",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.10.1",
- "integrity": "sha512-6fUOw22bSvxg0K33BefAyAkNRkETe9euLNT0CHs2mLIvzYR1q38KiHRUQIWj8FESbVANF0pXMojhBz5TeqpMtg==",
+ "version": "2.12.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.12.0",
+ "integrity": "sha512-GQpNFs2h61OKKNLexQwCfSJTLzNQ7fSbwqvrerB4oNdoxUgDvmdD43SELWycrb9DX8lkje4cR3ARznYIINnR8Q==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -24,9 +24,9 @@
@@ -24,9 +24,9 @@
"dotenv": "^16.4.7",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.107.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.110.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.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.12.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -23,8 +23,8 @@
@@ -23,8 +23,8 @@
* That is also what GET /api/v1/resources/:ovrn can read: only a resource whose computed ovrn equals the one
* That is also what GET /api/v1/resources/:ovrn can read: only a resource whose computed ovrn equals the one
* asked for answers, so an OVRN naming another project, or a project-less subscription's id, is a 404.
* asked for answers, so an OVRN naming another project, or a project-less subscription's id, is a 404.
*
*
- * The capability stays `planned` in openvibe-contracts until the deploy lands and Opus flips it (this step
- * does not change Contracts); the guard is the same service-token check every other first-party route uses.
+ * The capability is `active` in openvibe-contracts since v0.110.0; the guard is the same service-token check
+ * every other first-party route uses.
*/
*/
const express = require('express');
const express = require('express');
const contracts = require('openvibe-contracts');
const contracts = require('openvibe-contracts');
@@ -33,9 +33,9 @@ const CAPS = Object.freeze({
@@ -33,9 +33,9 @@ const CAPS = Object.freeze({
appPublish: 'events.app.publish',
appPublish: 'events.app.publish',
appRead: 'events.app.read',
appRead: 'events.app.read',
appSubscribe: 'events.app.subscribe',
appSubscribe: 'events.app.subscribe',
- // The authority resource index (ADR-048, plan T13 step 8). `planned` in openvibe-contracts until the
- // deploy lands, so capabilities.check() answers capability.unknown and the local rule (exact id or a
- // `family.*` grant) decides — the same rule, and the same 401/403, every other service route gets.
+ // The authority resource index (ADR-048, plan T13 step 8). `active` in openvibe-contracts since v0.110.0,
+ // so capabilities.check() applies the contracts rule (exact id or a `family.*` grant); the local fallback
+ // below only covers an id the installed contracts do not know yet.
resourceRead: 'events.resource.read',
resourceRead: 'events.resource.read',
});
});
@@ -25,7 +25,9 @@ const DEFAULT_SOURCES = ['live', 'media', 'network', 'community', 'chat', 'openr
@@ -25,7 +25,9 @@ const DEFAULT_SOURCES = ['live', 'media', 'network', 'community', 'chat', 'openr
// Watch (OpenVibe.Watch): watch.observation.recorded, watch.watch.triggered, watch.check.failed
// Watch (OpenVibe.Watch): watch.observation.recorded, watch.watch.triggered, watch.check.failed
'watch',
'watch',
// Run (OpenVibe.Run, plan T14): run.job.queued|started|succeeded|failed|cancelled|expired
// Run (OpenVibe.Run, plan T14): run.job.queued|started|succeeded|failed|cancelled|expired
- 'run'];
+ 'run',
+ // Space (OpenVibe.Space, plan T10 step 3): space.thread.created, space.post.created, space.moderation.action
+ 'space'];
/**
/**
* Namespaces that belong to the network rather than to one service. `provider.*` carries storage
* Namespaces that belong to the network rather than to one service. `provider.*` carries storage
@@ -117,6 +117,22 @@ t('a watch service principal publishes watch.* only', async () => {
@@ -117,6 +117,22 @@ t('a watch service principal publishes watch.* only', async () => {
assert.strictEqual(r.body.code, 'events.unknown_source');
assert.strictEqual(r.body.code, 'events.unknown_source');
});
});
+t('a space service principal publishes space.* only', async () => {
+ const space = serviceToken('space', ['events.event.publish']);
+ // Real ids from openvibe-contracts (Space, v0.110.0+): space.thread.created, space.post.created, space.moderation.action.
+ for (const event_type of ['space.thread.created', 'space.post.created', 'space.moderation.action']) {
+ const r = await request(h.base, 'POST', '/api/v1/events', { token: space, body: envelope('space', { event_type }) });
+ assert.strictEqual(r.status, 201, r.text);
+ }
+ let r = await request(h.base, 'POST', '/api/v1/events', { token: space, body: envelope('space', { event_type: 'live.stream.started' }) });
+ assert.strictEqual(r.status, 403);
+ assert.strictEqual(r.body.code, 'events.type_not_allowed');
+ // A source not in the list cannot claim the space namespace.
+ r = await request(h.base, 'POST', '/api/v1/events', { token: serviceToken('stranger', ['events.event.publish']), body: envelope('stranger', { event_type: 'space.thread.created' }) });
+ assert.strictEqual(r.status, 403);
+ assert.strictEqual(r.body.code, 'events.unknown_source');
+});
+
t('unknown source (not in the manifest map) -> 403', async () => {
t('unknown source (not in the manifest map) -> 403', async () => {
const r = await request(h.base, 'POST', '/api/v1/events', { token: serviceToken('stranger', ['events.event.publish']), body: envelope('stranger') });
const r = await request(h.base, 'POST', '/api/v1/events', { token: serviceToken('stranger', ['events.event.publish']), body: envelope('stranger') });
assert.strictEqual(r.status, 403);
assert.strictEqual(r.status, 403);