openvibers/openvibe.contracts Commits

Contracts 0.79.0: service-token capabilities for every internal call that still used X-Internal-Key (plan T2, register C-50–C-58): network.avatar.write, network.registry.read, network.coins.read, live.avatar.write, live.url_registry.refresh, live.analytics.read, media.avatar.ingest and tools.analytics.read, each with request/result contracts modelled on the real handlers, catalog entries and fixtures; avatar URLs must be on https://openvibe.media/. The owning services' manifests list them. npm test: 14295 checks passed; compat vs v0.78.0 without breaking changes.

OP OpenVibers committed 29 Sept 2026 · 7ddcf0699c · 77 files, +2190 −73


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 7e6368ac..199021f9 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,17 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.79.0 — 2026-09-29
++
++**Service-token capabilities for every internal call that still used X-Internal-Key** (plan T2, register C-50–C-58).
++- New internal capabilities, each with request/result contracts and fixtures: `network.avatar.write` (a site reports an
++ avatar change to Network), `network.registry.read` (the resolved URL registry), `network.coins.read` (OpenCoins totals,
++ never balances of named people), `live.avatar.write` (Network pushes an avatar to Live), `live.url_registry.refresh`,
++ `live.analytics.read` (aggregate totals for Network's navigation), `media.avatar.ingest` (Network asks Media to import
++ an avatar), `tools.analytics.read` (the Tools gateway's and tool sites' aggregate analytics).
++- Avatar URLs in the avatar requests must be on `https://openvibe.media/`.
++- The owning services' manifests list them.
++
## 0.78.0 — 2026-09-29
**OpenVibe.Realtime is gone from the estate** (plan T0).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 48c96ae7..b92b1095 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -2893,6 +2893,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "tools.analytics-read-result",
++ "version": "1.0.0",
++ "owner": "tools",
++ "schema": "tools/analytics-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "tools.job-read-result",
"version": "1.0.0",
@@@ -4339,6 +4348,24 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "media.avatar-ingest-request",
++ "version": "1.0.0",
++ "owner": "media",
++ "schema": "media/avatar-ingest-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "media.avatar-ingest-result",
++ "version": "1.0.0",
++ "owner": "media",
++ "schema": "media/avatar-ingest-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "media.file-read-result",
"version": "1.0.0",
@@@ -4485,6 +4512,42 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "live.analytics-read-result",
++ "version": "1.0.0",
++ "owner": "live",
++ "schema": "live/analytics-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "live.avatar-write-request",
++ "version": "1.0.0",
++ "owner": "live",
++ "schema": "live/avatar-write-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "live.avatar-write-result",
++ "version": "1.0.0",
++ "owner": "live",
++ "schema": "live/avatar-write-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "live.url-registry-refresh-result",
++ "version": "1.0.0",
++ "owner": "live",
++ "schema": "live/url-registry-refresh-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "live.follower-page",
"version": "1.0.0",
@@@ -5004,6 +5067,42 @@
"status": "active",
"adr": "ADR-034"
},
++ {
++ "id": "network.avatar-write-request",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/avatar-write-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "network.avatar-write-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/avatar-write-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "network.registry-read-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/registry-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "network.coins-read-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/coins-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "platform.resource-offer",
"version": "1.0.0",
ddiff --git a/contracts/live/analytics-read-result.v1.json b/contracts/live/analytics-read-result.v1.json
nnew file mode 100644
iindex 00000000..d140fbd6
---- /dev/null
++++ b/contracts/live/analytics-read-result.v1.json
@@@ -0,0 +1,39 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",
++ "title": "LiveAnalyticsReadResult",
++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "summary"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "summary": {
++ "type": "object",
++ "description": "Aggregate totals; open."
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/live/avatar-write-request.v1.json b/contracts/live/avatar-write-request.v1.json
nnew file mode 100644
iindex 00000000..ca2d35ca
---- /dev/null
++++ b/contracts/live/avatar-write-request.v1.json
@@@ -0,0 +1,36 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",
++ "title": "LiveAvatarWriteRequest",
++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed \u2014 the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/\u2026 address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
++ "type": "object",
++ "required": [
++ "openvibenetwork_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "openvibenetwork_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Fallback when no linked account matches."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/"
++ }
++ }
++}
ddiff --git a/contracts/live/avatar-write-result.v1.json b/contracts/live/avatar-write-result.v1.json
nnew file mode 100644
iindex 00000000..259ffdd6
---- /dev/null
++++ b/contracts/live/avatar-write-result.v1.json
@@@ -0,0 +1,25 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",
++ "title": "LiveAvatarWriteResult",
++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
++ "type": "object",
++ "required": [
++ "ok",
++ "id",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++}
ddiff --git a/contracts/live/url-registry-refresh-result.v1.json b/contracts/live/url-registry-refresh-result.v1.json
nnew file mode 100644
iindex 00000000..a5d50301
---- /dev/null
++++ b/contracts/live/url-registry-refresh-result.v1.json
@@@ -0,0 +1,38 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",
++ "title": "LiveUrlRegistryRefreshResult",
++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "message"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/media/avatar-ingest-request.v1.json b/contracts/media/avatar-ingest-request.v1.json
nnew file mode 100644
iindex 00000000..d002982f
---- /dev/null
++++ b/contracts/media/avatar-ingest-request.v1.json
@@@ -0,0 +1,31 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",
++ "title": "MediaAvatarIngestRequest",
++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
++ "type": "object",
++ "required": [
++ "url",
++ "user_id"
++ ],
++ "properties": {
++ "url": {
++ "type": "string",
++ "description": "The https address of the picture to import."
++ },
++ "user_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The account that owns the stored paste."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Used only for the paste title."
++ }
++ }
++}
ddiff --git a/contracts/media/avatar-ingest-result.v1.json b/contracts/media/avatar-ingest-result.v1.json
nnew file mode 100644
iindex 00000000..d071177f
---- /dev/null
++++ b/contracts/media/avatar-ingest-result.v1.json
@@@ -0,0 +1,39 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",
++ "title": "MediaAvatarIngestResult",
++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
++ "type": "object",
++ "required": [
++ "ok",
++ "slug",
++ "url",
++ "width",
++ "height",
++ "bytes"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "slug": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "width": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "height": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "bytes": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++}
ddiff --git a/contracts/network/avatar-write-request.v1.json b/contracts/network/avatar-write-request.v1.json
nnew file mode 100644
iindex 00000000..3dc1eb9a
---- /dev/null
++++ b/contracts/network/avatar-write-request.v1.json
@@@ -0,0 +1,32 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",
++ "title": "NetworkAvatarWriteRequest",
++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
++ "type": "object",
++ "required": [
++ "user_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "user_id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The Network (SSO) user id."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "An https://openvibe.media/\u2026 picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/",
++ "maxLength": 500
++ },
++ "origin": {
++ "type": "string",
++ "maxLength": 40,
++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
++ }
++ }
++}
ddiff --git a/contracts/network/avatar-write-result.v1.json b/contracts/network/avatar-write-result.v1.json
nnew file mode 100644
iindex 00000000..1bd8e8b0
---- /dev/null
++++ b/contracts/network/avatar-write-result.v1.json
@@@ -0,0 +1,20 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",
++ "title": "NetworkAvatarWriteResult",
++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
++ "type": "object",
++ "required": [
++ "ok",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++}
ddiff --git a/contracts/network/coins-read-result.v1.json b/contracts/network/coins-read-result.v1.json
nnew file mode 100644
iindex 00000000..c0d68f3f
---- /dev/null
++++ b/contracts/network/coins-read-result.v1.json
@@@ -0,0 +1,78 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",
++ "title": "NetworkCoinsReadResult",
++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "circulating",
++ "holders",
++ "transactions",
++ "recent"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "spent": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "circulating": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "holders": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "transactions": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "recent": {
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "holders"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "$ref": "#/$defs/window"
++ },
++ "spent": {
++ "$ref": "#/$defs/window"
++ },
++ "holders": {
++ "$ref": "#/$defs/window"
++ }
++ }
++ }
++ },
++ "$defs": {
++ "window": {
++ "type": "object",
++ "required": [
++ "w",
++ "pw"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "w": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pw": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/network/registry-read-result.v1.json b/contracts/network/registry-read-result.v1.json
nnew file mode 100644
iindex 00000000..16bbdd39
---- /dev/null
++++ b/contracts/network/registry-read-result.v1.json
@@@ -0,0 +1,20 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",
++ "title": "NetworkRegistryReadResult",
++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
++ "type": "object",
++ "required": [
++ "ok",
++ "registry"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "registry": {
++ "type": "object",
++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
++ }
++ }
++}
ddiff --git a/contracts/tools/analytics-read-result.v1.json b/contracts/tools/analytics-read-result.v1.json
nnew file mode 100644
iindex 00000000..1f183db6
---- /dev/null
++++ b/contracts/tools/analytics-read-result.v1.json
@@@ -0,0 +1,20 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",
++ "title": "ToolsAnalyticsReadResult",
++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
++ "type": "object",
++ "required": [
++ "ok",
++ "analytics"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "analytics": {
++ "type": "object",
++ "description": "Aggregate analytics; open."
++ }
++ }
++}
ddiff --git a/fixtures/live.analytics-read-result/invalid/no-summary.json b/fixtures/live.analytics-read-result/invalid/no-summary.json
nnew file mode 100644
iindex 00000000..ba5677b8
---- /dev/null
++++ b/fixtures/live.analytics-read-result/invalid/no-summary.json
@@@ -0,0 +1 @@
++{ "ok": true }
ddiff --git a/fixtures/live.analytics-read-result/valid/error.json b/fixtures/live.analytics-read-result/valid/error.json
nnew file mode 100644
iindex 00000000..663649c1
---- /dev/null
++++ b/fixtures/live.analytics-read-result/valid/error.json
@@@ -0,0 +1 @@
++{ "ok": false, "error": "analytics store unavailable" }
ddiff --git a/fixtures/live.analytics-read-result/valid/summary.json b/fixtures/live.analytics-read-result/valid/summary.json
nnew file mode 100644
iindex 00000000..90190668
---- /dev/null
++++ b/fixtures/live.analytics-read-result/valid/summary.json
@@@ -0,0 +1 @@
++{ "ok": true, "summary": { "pageviews": 125000, "sessions": 40211, "bots": 8300 } }
ddiff --git a/fixtures/live.avatar-write-request/invalid/no-avatar.json b/fixtures/live.avatar-write-request/invalid/no-avatar.json
nnew file mode 100644
iindex 00000000..29c7e9fc
---- /dev/null
++++ b/fixtures/live.avatar-write-request/invalid/no-avatar.json
@@@ -0,0 +1 @@
++{ "openvibenetwork_id": 4821, "username": "aurora" }
ddiff --git a/fixtures/live.avatar-write-request/valid/push.json b/fixtures/live.avatar-write-request/valid/push.json
nnew file mode 100644
iindex 00000000..7d98706c
---- /dev/null
++++ b/fixtures/live.avatar-write-request/valid/push.json
@@@ -0,0 +1 @@
++{ "openvibenetwork_id": 4821, "username": "aurora", "avatar_url": "https://openvibe.media/p/hero-shot/screenshot" }
ddiff --git a/fixtures/live.avatar-write-result/invalid/no-id.json b/fixtures/live.avatar-write-result/invalid/no-id.json
nnew file mode 100644
iindex 00000000..7a24d830
---- /dev/null
++++ b/fixtures/live.avatar-write-result/invalid/no-id.json
@@@ -0,0 +1 @@
++{ "ok": true, "changed": false }
ddiff --git a/fixtures/live.avatar-write-result/valid/stored.json b/fixtures/live.avatar-write-result/valid/stored.json
nnew file mode 100644
iindex 00000000..74068aea
---- /dev/null
++++ b/fixtures/live.avatar-write-result/valid/stored.json
@@@ -0,0 +1 @@
++{ "ok": true, "id": 77, "changed": true }
ddiff --git a/fixtures/live.url-registry-refresh-result/invalid/no-message.json b/fixtures/live.url-registry-refresh-result/invalid/no-message.json
nnew file mode 100644
iindex 00000000..ba5677b8
---- /dev/null
++++ b/fixtures/live.url-registry-refresh-result/invalid/no-message.json
@@@ -0,0 +1 @@
++{ "ok": true }
ddiff --git a/fixtures/live.url-registry-refresh-result/valid/failed.json b/fixtures/live.url-registry-refresh-result/valid/failed.json
nnew file mode 100644
iindex 00000000..fb910725
---- /dev/null
++++ b/fixtures/live.url-registry-refresh-result/valid/failed.json
@@@ -0,0 +1 @@
++{ "ok": false, "error": "registry unreachable" }
ddiff --git a/fixtures/live.url-registry-refresh-result/valid/refreshed.json b/fixtures/live.url-registry-refresh-result/valid/refreshed.json
nnew file mode 100644
iindex 00000000..7c91f84b
---- /dev/null
++++ b/fixtures/live.url-registry-refresh-result/valid/refreshed.json
@@@ -0,0 +1 @@
++{ "ok": true, "message": "URL registry refreshed" }
ddiff --git a/fixtures/media.avatar-ingest-request/invalid/no-url.json b/fixtures/media.avatar-ingest-request/invalid/no-url.json
nnew file mode 100644
iindex 00000000..d5451a13
---- /dev/null
++++ b/fixtures/media.avatar-ingest-request/invalid/no-url.json
@@@ -0,0 +1 @@
++{ "user_id": 4821, "username": "aurora" }
ddiff --git a/fixtures/media.avatar-ingest-request/valid/import.json b/fixtures/media.avatar-ingest-request/valid/import.json
nnew file mode 100644
iindex 00000000..ff419547
---- /dev/null
++++ b/fixtures/media.avatar-ingest-request/valid/import.json
@@@ -0,0 +1 @@
++{ "url": "https://cdn.example.com/me.png", "user_id": 4821, "username": "aurora" }
ddiff --git a/fixtures/media.avatar-ingest-result/invalid/not-ok.json b/fixtures/media.avatar-ingest-result/invalid/not-ok.json
nnew file mode 100644
iindex 00000000..113c4433
---- /dev/null
++++ b/fixtures/media.avatar-ingest-result/invalid/not-ok.json
@@@ -0,0 +1 @@
++{ "ok": false, "error": "That link is not a picture" }
ddiff --git a/fixtures/media.avatar-ingest-result/valid/stored.json b/fixtures/media.avatar-ingest-result/valid/stored.json
nnew file mode 100644
iindex 00000000..acf3bd78
---- /dev/null
++++ b/fixtures/media.avatar-ingest-result/valid/stored.json
@@@ -0,0 +1 @@
++{ "ok": true, "slug": "avatar-9f3k2", "url": "https://openvibe.media/p/avatar-9f3k2/screenshot", "width": 512, "height": 512, "bytes": 40231 }
ddiff --git a/fixtures/network.avatar-write-request/invalid/no-user.json b/fixtures/network.avatar-write-request/invalid/no-user.json
nnew file mode 100644
iindex 00000000..6eab1087
---- /dev/null
++++ b/fixtures/network.avatar-write-request/invalid/no-user.json
@@@ -0,0 +1 @@
++{ "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }
ddiff --git a/fixtures/network.avatar-write-request/valid/live-pick.json b/fixtures/network.avatar-write-request/valid/live-pick.json
nnew file mode 100644
iindex 00000000..1e13b158
---- /dev/null
++++ b/fixtures/network.avatar-write-request/valid/live-pick.json
@@@ -0,0 +1 @@
++{ "user_id": 4821, "avatar_url": "https://openvibe.media/p/hero-shot/screenshot", "origin": "live" }
ddiff --git a/fixtures/network.avatar-write-result/invalid/not-ok.json b/fixtures/network.avatar-write-result/invalid/not-ok.json
nnew file mode 100644
iindex 00000000..1da69e6e
---- /dev/null
++++ b/fixtures/network.avatar-write-result/invalid/not-ok.json
@@@ -0,0 +1 @@
++{ "ok": false, "changed": true }
ddiff --git a/fixtures/network.avatar-write-result/valid/changed.json b/fixtures/network.avatar-write-result/valid/changed.json
nnew file mode 100644
iindex 00000000..8203a4d9
---- /dev/null
++++ b/fixtures/network.avatar-write-result/valid/changed.json
@@@ -0,0 +1 @@
++{ "ok": true, "changed": true }
ddiff --git a/fixtures/network.coins-read-result/invalid/no-recent.json b/fixtures/network.coins-read-result/invalid/no-recent.json
nnew file mode 100644
iindex 00000000..e1520aa8
---- /dev/null
++++ b/fixtures/network.coins-read-result/invalid/no-recent.json
@@@ -0,0 +1,7 @@
++{
++ "earned": 128400,
++ "spent": 91200,
++ "circulating": 37200,
++ "holders": 1842,
++ "transactions": 44190
++}
ddiff --git a/fixtures/network.coins-read-result/valid/totals.json b/fixtures/network.coins-read-result/valid/totals.json
nnew file mode 100644
iindex 00000000..6fe343d1
---- /dev/null
++++ b/fixtures/network.coins-read-result/valid/totals.json
@@@ -0,0 +1,12 @@
++{
++ "earned": 128400,
++ "spent": 91200,
++ "circulating": 37200,
++ "holders": 1842,
++ "transactions": 44190,
++ "recent": {
++ "earned": { "w": 6400, "pw": 5900 },
++ "spent": { "w": 4100, "pw": 4300 },
++ "holders": { "w": 210, "pw": 198 }
++ }
++}
ddiff --git a/fixtures/network.registry-read-result/invalid/no-registry.json b/fixtures/network.registry-read-result/invalid/no-registry.json
nnew file mode 100644
iindex 00000000..ba5677b8
---- /dev/null
++++ b/fixtures/network.registry-read-result/invalid/no-registry.json
@@@ -0,0 +1 @@
++{ "ok": true }
ddiff --git a/fixtures/network.registry-read-result/valid/resolved.json b/fixtures/network.registry-read-result/valid/resolved.json
nnew file mode 100644
iindex 00000000..b9606e30
---- /dev/null
++++ b/fixtures/network.registry-read-result/valid/resolved.json
@@@ -0,0 +1,8 @@
++{
++ "ok": true,
++ "registry": {
++ "NETWORK_PUBLIC_URL": "https://openvibe.network",
++ "LIVE_PUBLIC_URL": "https://openvibe.live",
++ "MEDIA_PUBLIC_URL": "https://openvibe.media"
++ }
++}
ddiff --git a/fixtures/tools.analytics-read-result/invalid/no-analytics.json b/fixtures/tools.analytics-read-result/invalid/no-analytics.json
nnew file mode 100644
iindex 00000000..ba5677b8
---- /dev/null
++++ b/fixtures/tools.analytics-read-result/invalid/no-analytics.json
@@@ -0,0 +1 @@
++{ "ok": true }
ddiff --git a/fixtures/tools.analytics-read-result/valid/rollup.json b/fixtures/tools.analytics-read-result/valid/rollup.json
nnew file mode 100644
iindex 00000000..3e95764a
---- /dev/null
++++ b/fixtures/tools.analytics-read-result/valid/rollup.json
@@@ -0,0 +1,11 @@
++{
++ "ok": true,
++ "analytics": {
++ "summary": { "total_pageviews": 40231, "avg_response_ms": 84 },
++ "realtime": { "online": 12 },
++ "apps": [
++ { "name": "img", "total_pageviews": 20000 },
++ { "name": "docs", "total_pageviews": 20231 }
++ ]
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex f54b56ea..be85a1dd 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -30143,6 +30143,31 @@
]
}
},
++ "tools.analytics-read-result": {
++ "version": "1.0.0",
++ "owner": "tools",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/tools/analytics-read-result.v1.json",
++ "title": "ToolsAnalyticsReadResult",
++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
++ "type": "object",
++ "required": [
++ "ok",
++ "analytics"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "analytics": {
++ "type": "object",
++ "description": "Aggregate analytics; open."
++ }
++ }
++ }
++ },
"tools.job-read-result": {
"version": "1.0.0",
"owner": "tools",
@@@ -46861,6 +46886,86 @@
}
}
},
++ "media.avatar-ingest-request": {
++ "version": "1.0.0",
++ "owner": "media",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-request.v1.json",
++ "title": "MediaAvatarIngestRequest",
++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
++ "type": "object",
++ "required": [
++ "url",
++ "user_id"
++ ],
++ "properties": {
++ "url": {
++ "type": "string",
++ "description": "The https address of the picture to import."
++ },
++ "user_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The account that owns the stored paste."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Used only for the paste title."
++ }
++ }
++ }
++ },
++ "media.avatar-ingest-result": {
++ "version": "1.0.0",
++ "owner": "media",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/media/avatar-ingest-result.v1.json",
++ "title": "MediaAvatarIngestResult",
++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
++ "type": "object",
++ "required": [
++ "ok",
++ "slug",
++ "url",
++ "width",
++ "height",
++ "bytes"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "slug": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "width": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "height": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "bytes": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
"media.file-read-result": {
"version": "1.0.0",
"owner": "media",
@@@ -48662,6 +48767,164 @@
]
}
},
++ "live.analytics-read-result": {
++ "version": "1.0.0",
++ "owner": "live",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/analytics-read-result.v1.json",
++ "title": "LiveAnalyticsReadResult",
++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "summary"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "summary": {
++ "type": "object",
++ "description": "Aggregate totals; open."
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "live.avatar-write-request": {
++ "version": "1.0.0",
++ "owner": "live",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/avatar-write-request.v1.json",
++ "title": "LiveAvatarWriteRequest",
++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
++ "type": "object",
++ "required": [
++ "openvibenetwork_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "openvibenetwork_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Fallback when no linked account matches."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "An https://openvibe.media/… picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/"
++ }
++ }
++ }
++ },
++ "live.avatar-write-result": {
++ "version": "1.0.0",
++ "owner": "live",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/avatar-write-result.v1.json",
++ "title": "LiveAvatarWriteResult",
++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
++ "type": "object",
++ "required": [
++ "ok",
++ "id",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++ }
++ },
++ "live.url-registry-refresh-result": {
++ "version": "1.0.0",
++ "owner": "live",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/live/url-registry-refresh-result.v1.json",
++ "title": "LiveUrlRegistryRefreshResult",
++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "message"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++ }
++ },
"live.follower-page": {
"version": "1.0.0",
"owner": "live",
@@@ -55230,6 +55493,176 @@
}
}
},
++ "network.avatar-write-request": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/avatar-write-request.v1.json",
++ "title": "NetworkAvatarWriteRequest",
++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
++ "type": "object",
++ "required": [
++ "user_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "user_id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The Network (SSO) user id."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "An https://openvibe.media/… picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/",
++ "maxLength": 500
++ },
++ "origin": {
++ "type": "string",
++ "maxLength": 40,
++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
++ }
++ }
++ }
++ },
++ "network.avatar-write-result": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/avatar-write-result.v1.json",
++ "title": "NetworkAvatarWriteResult",
++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
++ "type": "object",
++ "required": [
++ "ok",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++ }
++ },
++ "network.registry-read-result": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/registry-read-result.v1.json",
++ "title": "NetworkRegistryReadResult",
++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
++ "type": "object",
++ "required": [
++ "ok",
++ "registry"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "registry": {
++ "type": "object",
++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
++ }
++ }
++ }
++ },
++ "network.coins-read-result": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/coins-read-result.v1.json",
++ "title": "NetworkCoinsReadResult",
++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "circulating",
++ "holders",
++ "transactions",
++ "recent"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "spent": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "circulating": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "holders": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "transactions": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "recent": {
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "holders"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "$ref": "#/$defs/window"
++ },
++ "spent": {
++ "$ref": "#/$defs/window"
++ },
++ "holders": {
++ "$ref": "#/$defs/window"
++ }
++ }
++ }
++ },
++ "$defs": {
++ "window": {
++ "type": "object",
++ "required": [
++ "w",
++ "pw"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "w": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pw": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
"platform.resource-offer": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 5c839e5c..3204ff9b 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 348789d1..a744118b 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex eb5a8d5e..17f327a1 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 74e44592..7f49a700 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex 5b9e5737..9c2d6e27 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 673ce294..83ecd06b 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 828f51c1..a6b00783 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex f9784f7a..19675577 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 05270051..8018970e 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 861bd5f1..36468db0 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 8cb7d57c..c1b596d9 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex aca3a403..a81fe8f2 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -82,22 +82,22 @@
"service": "live",
"name": "OpenVibe.Live",
"origin": "https://openvibe.live",
-- "operations": 27,
-- "capabilities": 6
++ "operations": 30,
++ "capabilities": 9
},
{
"service": "media",
"name": "OpenVibe.Media",
"origin": "https://openvibe.media",
-- "operations": 6,
-- "capabilities": 4
++ "operations": 7,
++ "capabilities": 5
},
{
"service": "network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
-- "operations": 26,
-- "capabilities": 19
++ "operations": 29,
++ "capabilities": 22
},
{
"service": "news",
@@@ -145,8 +145,8 @@
"service": "tools",
"name": "OpenVibe.Tools",
"origin": "https://openvibe.tools",
-- "operations": 12,
-- "capabilities": 6
++ "operations": 14,
++ "capabilities": 7
},
{
"service": "trade",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex af5f680b..fd97741c 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -1172,6 +1172,52 @@
}
}
},
++ "/internal/analytics-summary": {
++ "get": {
++ "operationId": "get_internal_analytics_summary",
++ "summary": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
++ "description": "**live.analytics.read** (internal): Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
++ "tags": [
++ "live.analytics"
++ ],
++ "x-openvibe-capabilities": [
++ "live.analytics.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "live.analytics.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/live.analytics-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/internal/followers": {
"get": {
"operationId": "get_internal_followers",
@@@ -1512,6 +1558,108 @@
}
}
}
++ },
++ "/internal/url-registry/refresh": {
++ "post": {
++ "operationId": "post_internal_url_registry_refresh",
++ "summary": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
++ "description": "**live.url_registry.refresh** (internal): Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
++ "tags": [
++ "live.url_registry"
++ ],
++ "x-openvibe-capabilities": [
++ "live.url_registry.refresh"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "live.url_registry.refresh"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/live.url-registry-refresh-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/user-avatar": {
++ "post": {
++ "operationId": "post_internal_user_avatar",
++ "summary": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat).",
++ "description": "**live.avatar.write** (internal): Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",
++ "tags": [
++ "live.avatar"
++ ],
++ "x-openvibe-capabilities": [
++ "live.avatar.write"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "live.avatar.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/live.avatar-write-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/live.avatar-write-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -1977,6 +2125,100 @@
}
}
},
++ "live.analytics-read-result.v1": {
++ "title": "LiveAnalyticsReadResult",
++ "description": "live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "summary"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "summary": {
++ "type": "object",
++ "description": "Aggregate totals; open."
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++ },
++ "live.avatar-write-request.v1": {
++ "title": "LiveAvatarWriteRequest",
++ "description": "live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.",
++ "type": "object",
++ "required": [
++ "openvibenetwork_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "openvibenetwork_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The Network (SSO) user id; matched against linked_accounts. A Live id never goes here."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Fallback when no linked account matches."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "An https://openvibe.media/… picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/"
++ }
++ }
++ },
++ "live.avatar-write-result.v1": {
++ "title": "LiveAvatarWriteResult",
++ "description": "live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).",
++ "type": "object",
++ "required": [
++ "ok",
++ "id",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++ },
"live.channel-read-result.v1": {
"title": "LiveChannelReadResult",
"description": "live.channel-read-result@1: answers of live.channel.read on OpenVibe.Live (public; the channel is addressed by username). GET /api/streams/channel/:username → the channel page: { channel, language, stream, streams, managed_streams (public projection), vods, clips, aiClips, … with totals and paging }; GET …/:username/live → { channel: { username, display_name, user_id }, streams }; GET …/:username/popular → { vod, clip, vods, clips, ranges }; GET …/:username/clips-taken → { clips, total, facets, limit, offset, hasMore, sort, of, includeSelf }; GET …/:username/bio-en → { from, to: en, text } (from and text null when there is nothing to translate); GET /api/auth/user/:username → { user } (the public profile). Unknown channels are 404 { error }.",
@@@ -3209,6 +3451,42 @@
}
}
}
++ },
++ "live.url-registry-refresh-result.v1": {
++ "title": "LiveUrlRegistryRefreshResult",
++ "description": "live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "message"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
}
},
"securitySchemes": {
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex b9c5ad79..e29437a8 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -385,6 +385,62 @@
}
}
}
++ },
++ "/internal/avatar-ingest": {
++ "post": {
++ "operationId": "post_internal_avatar_ingest",
++ "summary": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
++ "description": "**media.avatar.ingest** (internal): Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
++ "tags": [
++ "media.avatar"
++ ],
++ "x-openvibe-capabilities": [
++ "media.avatar.ingest"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "media.avatar.ingest"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/media.avatar-ingest-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/media.avatar-ingest-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -455,6 +511,72 @@
}
}
},
++ "media.avatar-ingest-request.v1": {
++ "title": "MediaAvatarIngestRequest",
++ "description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
++ "type": "object",
++ "required": [
++ "url",
++ "user_id"
++ ],
++ "properties": {
++ "url": {
++ "type": "string",
++ "description": "The https address of the picture to import."
++ },
++ "user_id": {
++ "type": [
++ "integer",
++ "string"
++ ],
++ "description": "The account that owns the stored paste."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Used only for the paste title."
++ }
++ }
++ },
++ "media.avatar-ingest-result.v1": {
++ "title": "MediaAvatarIngestResult",
++ "description": "media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).",
++ "type": "object",
++ "required": [
++ "ok",
++ "slug",
++ "url",
++ "width",
++ "height",
++ "bytes"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "slug": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "width": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "height": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "bytes": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ },
"media.file-read-result.v1": {
"title": "MediaFileReadResult",
"description": "media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).",
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex c780aae3..9691e7f6 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -594,6 +594,52 @@
}
}
},
++ "/internal/coins/stats": {
++ "get": {
++ "operationId": "get_internal_coins_stats",
++ "summary": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
++ "description": "**network.coins.read** (internal): Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
++ "tags": [
++ "network.coins"
++ ],
++ "x-openvibe-capabilities": [
++ "network.coins.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.coins.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.coins-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/internal/coins/transfer": {
"post": {
"operationId": "post_internal_coins_transfer",
@@@ -1638,6 +1684,108 @@
}
}
}
++ },
++ "/internal/url-registry/resolved": {
++ "get": {
++ "operationId": "get_internal_url_registry_resolved",
++ "summary": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
++ "description": "**network.registry.read** (internal): Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
++ "tags": [
++ "network.registry"
++ ],
++ "x-openvibe-capabilities": [
++ "network.registry.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.registry.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.registry-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/user-avatar": {
++ "post": {
++ "operationId": "post_internal_user_avatar",
++ "summary": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites.",
++ "description": "**network.avatar.write** (internal): A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",
++ "tags": [
++ "network.avatar"
++ ],
++ "x-openvibe-capabilities": [
++ "network.avatar.write"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.avatar.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.avatar-write-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.avatar-write-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -2137,6 +2285,54 @@
}
}
},
++ "network.avatar-write-request.v1": {
++ "title": "NetworkAvatarWriteRequest",
++ "description": "network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.",
++ "type": "object",
++ "required": [
++ "user_id",
++ "avatar_url"
++ ],
++ "properties": {
++ "user_id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The Network (SSO) user id."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "An https://openvibe.media/… picture; null clears the avatar.",
++ "pattern": "^https://openvibe\\.media/",
++ "maxLength": 500
++ },
++ "origin": {
++ "type": "string",
++ "maxLength": 40,
++ "description": "Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back."
++ }
++ }
++ },
++ "network.avatar-write-result.v1": {
++ "title": "NetworkAvatarWriteResult",
++ "description": "network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).",
++ "type": "object",
++ "required": [
++ "ok",
++ "changed"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "changed": {
++ "type": "boolean"
++ }
++ }
++ },
"network.blocks-result.v1": {
"title": "NetworkBlocksResult",
"description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",
@@@ -2233,6 +2429,82 @@
}
}
},
++ "network.coins-read-result.v1": {
++ "title": "NetworkCoinsReadResult",
++ "description": "network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.",
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "circulating",
++ "holders",
++ "transactions",
++ "recent"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "spent": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "circulating": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "holders": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "transactions": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "recent": {
++ "type": "object",
++ "required": [
++ "earned",
++ "spent",
++ "holders"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "earned": {
++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
++ },
++ "spent": {
++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
++ },
++ "holders": {
++ "$ref": "#/components/schemas/network.coins-read-result.v1/$defs/window"
++ }
++ }
++ }
++ },
++ "$defs": {
++ "window": {
++ "type": "object",
++ "required": [
++ "w",
++ "pw"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "w": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pw": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
"network.coins-transfer-request.v1": {
"title": "NetworkCoinsTransferRequest",
"description": "network.coins-transfer-request@1: the body of network.coins.transfer on OpenVibe.Network. POST /internal/coins/transfer: { from_user_id, to_user_id, app_id?, amount, reason?, ref?, idempotency_key } moves OpenCoins between two Network users atomically (the two ids are Network user ids and must differ). Refusals are { error } bodies: 400 invalid_from_user_id, invalid_to_user_id, invalid_transfer (the same person), invalid_amount or missing_idempotency_key; 404 user_not_found; 409 { error: insufficient_funds, balance } when the sender cannot cover it. Unknown fields are ignored.",
@@@ -3340,6 +3612,24 @@
}
}
},
++ "network.registry-read-result.v1": {
++ "title": "NetworkRegistryReadResult",
++ "description": "network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.",
++ "type": "object",
++ "required": [
++ "ok",
++ "registry"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "registry": {
++ "type": "object",
++ "description": "Setting name → resolved value; secrets and sensitive keys are omitted."
++ }
++ }
++ },
"network.staff-list-result.v1": {
"title": "NetworkStaffListResult",
"description": "network.staff-list-result@1: the answer of network.staff.read on OpenVibe.Network. GET /api/v1/staff/moderators[?service=chat|live|community|pastes|calls] (a service token holding network.staff.read, or a signed-in person holding staff.moderation.logs) → { service, staff }: the network's global moderators, admins and the owner who are not banned, with the staff capabilities their role holds in the contracts staff map; ?service keeps those holding the capability that moderates that service (another service name keeps those holding staff.content.moderate; an invalid value is ignored and answered as null). Answered with Cache-Control: private, no-store; a person without staff.moderation.logs gets 403 { error: forbidden }.",
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex ef0096bd..bec75583 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex a7c33137..b0631d5e 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.78.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex b61b45d8..56fe5cf4 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 5c9bc23e..8213e26d 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 5a1b4444..f1baf44a 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex f0c08177..8448643a 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 002afc06..534abca1 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -16,6 +16,98 @@
}
],
"paths": {
++ "/api/internal/analytics": {
++ "get": {
++ "operationId": "get_api_internal_analytics",
++ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
++ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
++ "tags": [
++ "tools.analytics"
++ ],
++ "x-openvibe-capabilities": [
++ "tools.analytics.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "tools.analytics.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/tools.analytics-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/internal/analytics/bots": {
++ "get": {
++ "operationId": "get_api_internal_analytics_bots",
++ "summary": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
++ "description": "**tools.analytics.read** (internal): Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
++ "tags": [
++ "tools.analytics"
++ ],
++ "x-openvibe-capabilities": [
++ "tools.analytics.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "tools.analytics.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/tools.analytics-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/jobs": {
"post": {
"operationId": "post_api_v1_jobs",
@@@ -814,6 +906,24 @@
}
}
},
++ "tools.analytics-read-result.v1": {
++ "title": "ToolsAnalyticsReadResult",
++ "description": "tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).",
++ "type": "object",
++ "required": [
++ "ok",
++ "analytics"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "analytics": {
++ "type": "object",
++ "description": "Aggregate analytics; open."
++ }
++ }
++ },
"tools.job-create-request.v1": {
"title": "ToolsJobCreateRequest",
"description": "tools.job-create-request@1: the request of tools.job.create on the Tools satellites that run jobs. POST /api/v1/jobs: tools.job-request@1 (JSON or multipart). POST /api/v1/jobs/:id/retry and PUT|DELETE /api/v1/jobs/:id/references/:ref take no body.",
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 2a06dc26..7b16b234 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 7355c099..8093ae08 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 12505866..1e0e33a0 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.78.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.78.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.79.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 40a0c6f9..96ba5355 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -30500,6 +30500,18 @@ export interface ToolsJobRequest {
*/
export type ToolsJobCreateRequest = ToolsJobRequest | NoBody;
++/** [email protected] (owner: tools) */
++/**
++ * tools.analytics-read-result@1: the answer of GET /api/internal/analytics on the Tools gateway and on each satellite (capability tools.analytics.read; internal key plus loopback only): { ok: true, analytics } — the satellites' analytics added up (summary totals, realtime, per-app numbers, with the busiest satellite's detail tables beside them) for Network's admin dashboard. Aggregate counts only, never rows; analytics is open (its shape is each satellite's).
++ */
++export interface ToolsAnalyticsReadResult {
++ ok: true;
++ /**
++ * Aggregate analytics; open.
++ */
++ analytics: {};
++}
++
/** [email protected] (owner: tools) */
/**
* tools.job-read-result@1: answers of tools.job.read on the Tools satellites. GET /api/v1/jobs/:id → tools.job@1, and so is the data of every event on GET /api/v1/jobs/:id/events (Server-Sent Events, Last-Event-ID resume); GET /api/v1/jobs/:id/files/:n → the result file's bytes (Content-Type of the file; 409 tools.job.not_ready before the job succeeded).
@@@ -43291,6 +43303,38 @@ export interface MediaObjectVisibilityChangedPayload {
changed_at: string;
}
++/** [email protected] (owner: media) */
++/**
++ * media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).
++ */
++export interface MediaAvatarIngestRequest {
++ /**
++ * The https address of the picture to import.
++ */
++ url: string;
++ /**
++ * The account that owns the stored paste.
++ */
++ user_id: number | string;
++ /**
++ * Used only for the paste title.
++ */
++ username?: string | null;
++}
++
++/** [email protected] (owner: media) */
++/**
++ * media.avatar-ingest-result@1: the answer of POST /internal/avatar-ingest on OpenVibe.Media: { ok: true, slug, url, width, height, bytes } — the unlisted paste holding the re-encoded avatar and its public https://openvibe.media/p/<slug>/screenshot address (always a 512×512 WebP). Refusals are { ok: false, error } (400, 422).
++ */
++export interface MediaAvatarIngestResult {
++ ok: true;
++ slug: string;
++ url: string;
++ width: number;
++ height: number;
++ bytes: number;
++}
++
/** [email protected] (owner: media) */
/**
* media.file-read-result@1: answers of media.object.read on OpenVibe.Media. GET /api/v1/:app/files/:key → media.file@1 (the file's metadata; 404 when it is not the tenant's); GET /f/:key → the bytes, with Content-Type and Range (a sandbox file only through its signed URL).
@@@ -43886,6 +43930,66 @@ export type LiveDiscoveryResult =
partial: boolean;
};
++/** [email protected] (owner: live) */
++/**
++ * live.analytics-read-result@1: the answer of GET /internal/analytics-summary?days=1-90 on OpenVibe.Live (capability live.analytics.read): { ok: true, summary } — Live's aggregate traffic totals only, for Network's navigation ordering (which sites are really used), never rows or people. summary is open (it grows with what Live measures). A failure is { ok: false, error }; a caller without the internal key gets 401 { ok: false }.
++ */
++export type LiveAnalyticsReadResult =
++ | {
++ ok: true;
++ /**
++ * Aggregate totals; open.
++ */
++ summary: {};
++ }
++ | {
++ ok: false;
++ error: string;
++ };
++
++/** [email protected] (owner: live) */
++/**
++ * live.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Live (capability live.avatar.write). Network tells Live that a person's avatar changed — the avatar is network-wide, and Live keeps a copy on its own user row for stream cards, chat and profiles. The account is found by openvibenetwork_id (through linked_accounts) or, failing that, by username; avatar_url must be an https://openvibe.media/… address of at most 500 characters, and null clears the picture. Refusals are { ok: false, error }: 400 for a malformed URL, 404 when no Live user matches, 422 when the URL is not on openvibe.media.
++ */
++export interface LiveAvatarWriteRequest {
++ /**
++ * The Network (SSO) user id; matched against linked_accounts. A Live id never goes here.
++ */
++ openvibenetwork_id: number | string;
++ /**
++ * Fallback when no linked account matches.
++ */
++ username?: string | null;
++ /**
++ * An https://openvibe.media/… picture; null clears the avatar.
++ */
++ avatar_url: string | null;
++}
++
++/** [email protected] (owner: live) */
++/**
++ * live.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Live: { ok: true, id, changed } — id is the Live user the picture was stored on and changed is false when it already matched. Refusals are { ok: false, error } (400, 404, 422, 500).
++ */
++export interface LiveAvatarWriteResult {
++ ok: true;
++ id: number;
++ changed: boolean;
++}
++
++/** [email protected] (owner: live) */
++/**
++ * live.url-registry-refresh-result@1: the answer of POST /internal/url-registry/refresh on OpenVibe.Live (capability live.url_registry.refresh): { ok: true, message } after Live reloaded Network's URL registry now (an operator changed a site's URL), or { ok: false, error } when the reload failed.
++ */
++export type LiveUrlRegistryRefreshResult =
++ | {
++ ok: true;
++ message: string;
++ }
++ | {
++ ok: false;
++ error: string;
++ };
++
/** [email protected] (owner: live) */
/**
* live.follower-page@1: the answer of GET /internal/followers?stream_id=&limit=&after= on OpenVibe.Live (live.follower.read, service token, loopback): the followers of the channel that streams stream_id, for Network's go-live notifications. A follower is named by their Network subject when Live knows it, else their Network user id. next is the cursor for the following page (null on the last). 400 without stream_id, 404 for an unknown stream.
@@@ -46288,6 +46392,63 @@ export interface NetworkNodeListResult {
generated_at: string;
}
++/** [email protected] (owner: network) */
++/**
++ * network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.
++ */
++export interface NetworkAvatarWriteRequest {
++ /**
++ * The Network (SSO) user id.
++ */
++ user_id: number;
++ /**
++ * An https://openvibe.media/… picture; null clears the avatar.
++ */
++ avatar_url: string | null;
++ /**
++ * Who asked ('live', 'network'); kept in the audit row and used to avoid echoing a push back.
++ */
++ origin?: string;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.avatar-write-result@1: the answer of POST /internal/user-avatar on OpenVibe.Network: { ok: true, changed } — changed is false when the account already had this picture (nothing was written, and nothing is fanned out to the sites). Refusals are { error } (400, 404, 422, 503).
++ */
++export interface NetworkAvatarWriteResult {
++ ok: true;
++ changed: boolean;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.registry-read-result@1: the answer of GET /internal/url-registry/resolved on OpenVibe.Network (capability network.registry.read): { ok: true, registry } — every site's resolved base URLs, keyed by setting name, that the shared navbar and service clients use. Secret-typed entries (deploy tokens) and sensitive keys are filtered out, so a caller never gets a credential. The keys are open (the registry grows with the sites); a failure is { ok: false, error }.
++ */
++export interface NetworkRegistryReadResult {
++ ok: true;
++ /**
++ * Setting name → resolved value; secrets and sensitive keys are omitted.
++ */
++ registry: {};
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.coins-read-result@1: the answer of GET /internal/coins/stats on OpenVibe.Network (capability network.coins.read): site-wide OpenCoins totals for public stat displays — earned, spent, circulating and holders, the transaction count, and two rolling seven-day windows (recent.<metric>.w is this week, .pw the seven days before it) so a display can say whether the economy is speeding up. Counts only, never a named person's balance. The answer is held for a minute; a failure is { error }.
++ */
++export interface NetworkCoinsReadResult {
++ earned: number;
++ spent: number;
++ circulating: number;
++ holders: number;
++ transactions: number;
++ recent: {
++ earned: Window;
++ spent: Window;
++ holders: Window;
++ };
++}
++
/** [email protected] (owner: network) */
/**
* What one node or external provider can run right now (roadmap WS-Z9, decision 43): capabilities, multidimensional capacity, measured latencies, health and pricing. First-party only: it carries capacity the public node registry (network.node@1) deliberately leaves out.
ddiff --git a/manifests/capabilities/live.analytics.read.json b/manifests/capabilities/live.analytics.read.json
nnew file mode 100644
iindex 00000000..0569919e
---- /dev/null
++++ b/manifests/capabilities/live.analytics.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "live.analytics.read",
++ "version": "1.0",
++ "owner": "live",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read Live's aggregate traffic totals (never rows or people) for Network's navigation ordering.",
++ "permissions": [
++ "analytics:summary"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/analytics-summary"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "live.analytics-read-result@1"
++}
ddiff --git a/manifests/capabilities/live.avatar.write.json b/manifests/capabilities/live.avatar.write.json
nnew file mode 100644
iindex 00000000..01b431ba
---- /dev/null
++++ b/manifests/capabilities/live.avatar.write.json
@@@ -0,0 +1,21 @@
++{
++ "id": "live.avatar.write",
++ "version": "1.0",
++ "owner": "live",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Network tells Live that a person's avatar changed (the avatar is network-wide; Live keeps a copy on its user row for stream cards and chat). The URL must be on openvibe.media.",
++ "permissions": [
++ "users:avatar"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/user-avatar"
++ ],
++ "inputSchema": "live.avatar-write-request@1",
++ "outputSchema": "live.avatar-write-result@1"
++}
ddiff --git a/manifests/capabilities/live.url_registry.refresh.json b/manifests/capabilities/live.url_registry.refresh.json
nnew file mode 100644
iindex 00000000..2e173dbc
---- /dev/null
++++ b/manifests/capabilities/live.url_registry.refresh.json
@@@ -0,0 +1,21 @@
++{
++ "id": "live.url_registry.refresh",
++ "version": "1.0",
++ "owner": "live",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Ask Live to reload Network's URL registry now (after an operator changed a site's URL).",
++ "permissions": [
++ "registry:refresh"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/url-registry/refresh"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "live.url-registry-refresh-result@1"
++}
ddiff --git a/manifests/capabilities/media.avatar.ingest.json b/manifests/capabilities/media.avatar.ingest.json
nnew file mode 100644
iindex 00000000..a671720d
---- /dev/null
++++ b/manifests/capabilities/media.avatar.ingest.json
@@@ -0,0 +1,21 @@
++{
++ "id": "media.avatar.ingest",
++ "version": "1.0",
++ "owner": "media",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Network asks Media to fetch a person's new avatar from a URL they chose (through Media's egress guard) and store it as their avatar object on openvibe.media.",
++ "permissions": [
++ "avatars:ingest"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/avatar-ingest"
++ ],
++ "inputSchema": "media.avatar-ingest-request@1",
++ "outputSchema": "media.avatar-ingest-result@1"
++}
ddiff --git a/manifests/capabilities/network.avatar.write.json b/manifests/capabilities/network.avatar.write.json
nnew file mode 100644
iindex 00000000..ff5b3ed0
---- /dev/null
++++ b/manifests/capabilities/network.avatar.write.json
@@@ -0,0 +1,21 @@
++{
++ "id": "network.avatar.write",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "internal",
++ "description": "A site reports that a person changed their avatar on it (Live's avatar picker); Network adopts it as the account's avatar and fans it out to the other sites. The URL must be on openvibe.media.",
++ "permissions": [
++ "profile:avatar"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/user-avatar"
++ ],
++ "inputSchema": "network.avatar-write-request@1",
++ "outputSchema": "network.avatar-write-result@1"
++}
ddiff --git a/manifests/capabilities/network.coins.read.json b/manifests/capabilities/network.coins.read.json
nnew file mode 100644
iindex 00000000..e1cdf091
---- /dev/null
++++ b/manifests/capabilities/network.coins.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "network.coins.read",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read OpenCoins totals (supply, holders, daily flow) for a site's own dashboards; never balances of named people.",
++ "permissions": [
++ "coins:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/coins/stats"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "network.coins-read-result@1"
++}
ddiff --git a/manifests/capabilities/network.registry.read.json b/manifests/capabilities/network.registry.read.json
nnew file mode 100644
iindex 00000000..9664534e
---- /dev/null
++++ b/manifests/capabilities/network.registry.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "network.registry.read",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read the resolved URL registry (every site's public and internal base URLs) that the shared navbar and service clients use.",
++ "permissions": [
++ "registry:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/url-registry/resolved"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "network.registry-read-result@1"
++}
ddiff --git a/manifests/capabilities/tools.analytics.read.json b/manifests/capabilities/tools.analytics.read.json
nnew file mode 100644
iindex 00000000..64d1867e
---- /dev/null
++++ b/manifests/capabilities/tools.analytics.read.json
@@@ -0,0 +1,22 @@
++{
++ "id": "tools.analytics.read",
++ "version": "1.0",
++ "owner": "tools",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read the Tools gateway's and each tool site's aggregate analytics (totals and bot counts, never rows) for Network's admin dashboard.",
++ "permissions": [
++ "analytics:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /api/internal/analytics",
++ "GET /api/internal/analytics/bots"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "tools.analytics-read-result@1"
++}
ddiff --git a/manifests/services/live.json b/manifests/services/live.json
iindex 8ab2b801..8bccffa4 100644
---- a/manifests/services/live.json
++++ b/manifests/services/live.json
@@@ -28,16 +28,19 @@
"position": 0
},
"capabilities": [
++ "live.analytics.read",
++ "live.avatar.write",
++ "live.channel.read",
"live.chat_context.read",
"live.chat_effects.write",
"live.chat_mirror.write",
++ "live.discovery.read",
"live.follower.read",
"live.lineage.resolve",
-- "live.tips_delivery.write",
-- "live.channel.read",
++ "live.owner.resolve",
"live.stream.read",
-- "live.discovery.read",
-- "live.owner.resolve"
++ "live.tips_delivery.write",
++ "live.url_registry.refresh"
],
"eventsProduced": [
"live.index_document.deleted",
ddiff --git a/manifests/services/media.json b/manifests/services/media.json
iindex 66dfc443..8cc13307 100644
---- a/manifests/services/media.json
++++ b/manifests/services/media.json
@@@ -27,15 +27,16 @@
"position": 4
},
"capabilities": [
-- "media.object.upload",
-- "media.object.read",
-- "media.object.list",
-- "media.object.delete",
-- "media.upload.create",
++ "media.avatar.ingest",
"media.derivative.create",
"media.derivative.read",
"media.lifecycle.read",
-- "media.lifecycle.transition"
++ "media.lifecycle.transition",
++ "media.object.delete",
++ "media.object.list",
++ "media.object.read",
++ "media.object.upload",
++ "media.upload.create"
],
"eventsProduced": [
"media.clip.failed",
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex e540f5aa..28d0bae1 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -37,20 +37,23 @@
"network.account.deletion.confirm",
"network.account.export.contribute",
"network.analytics.creator.read",
++ "network.avatar.write",
"network.blocks.read",
"network.coins.credit",
"network.coins.debit",
++ "network.coins.read",
"network.coins.transfer",
"network.follows.read",
"network.follows.write",
"network.integration.github.read",
"network.modules.read",
"network.modules.write",
++ "network.node.report",
"network.notifications.push",
"network.operator.alert",
-- "network.node.report",
"network.project.manage",
"network.project.read",
++ "network.registry.read",
"network.staff.read",
"network.status.incident"
],
ddiff --git a/manifests/services/tools.json b/manifests/services/tools.json
iindex 015fe997..5cb127c5 100644
---- a/manifests/services/tools.json
++++ b/manifests/services/tools.json
@@@ -27,12 +27,13 @@
"position": 1
},
"capabilities": [
++ "tools.analytics.read",
++ "tools.job.cancel",
"tools.job.create",
"tools.job.read",
-- "tools.job.cancel",
++ "tools.net.probe",
"tools.tool.read",
-- "tools.tool.run",
-- "tools.net.probe"
++ "tools.tool.run"
],
"eventsProduced": [
"tools.job.created",
@@@ -88,14 +89,14 @@
],
"window": "automatic only while ovhost waits for readiness after the restart (60 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
-- "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
},
"contracts": {
"range": ">=0.7.0 <1.0.0"
},
"leases": {
-- "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued → running)"
++ "none": "no claims: one process owns each app's jobs.db (better-sqlite3, single writer); a job is taken with a compare-and-set (queued \u2192 running)"
}
},
"notes": "Public at openvibe.tools: 169 tools in 9 families on the gateway (port 4001) and its satellites, catalogue at GET /api/catalog.json (re-served by Network). Asynchronous jobs (Wave 11) run on the img, audio and docs satellites only (POST /api/v1/jobs, GET/DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT|DELETE /api/v1/jobs/:id/references/:ref, SSE events, result files); the gateway has no /api/v1/jobs yet. Job results are stored as private Media objects (TOOLS_JOB_RESULTS=media: Tools holds media.object.upload and media.object.read, namespace tools) and job lifecycle events tools.job.created|started|succeeded|failed are published to OpenVibe.Events through an outbox in each satellite's jobs.db (EVENTS_URL set; events.event.publish). The tool registry (ADR-027, tools.tool.read) is served: GET /api/v1/tools[/:id[/schema]] on the gateway, for all 169 tools, and on the img, audio, docs, text, yt, maps and food satellites for their own. Planned (ADR-027): the uniform run API POST /api/v1/tools/:id/run (tools.tool.run; network probes need tools.net.probe), a jobs facade on the gateway, tiered quotas and a pseudonymous abuse log; the legacy /api/process, /api/net/* and /api/dev/* routes keep working with Deprecation and Sunset (2026-12-31) headers. GET /api/ready is truthful on the gateway (it checks every satellite) and on each satellite."
ddiff --git a/package.json b/package.json
iindex 23e00854..ffb380ea 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.78.0",
++ "version": "0.79.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",