openvibers/openvibe.contracts Commits
v0.58.0: host.deploy.activated@1, the release notification (WS-P task 9, ADR-016 amendment 1)
Changes
@@@ -4,6 +4,14 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `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.58.0 — 2026-09-26++++**Release notifications** (roadmap WS-P task 9, ADR-016 amendment 1). `host.deploy.activated` gets its payload contract, `host.deploy.activated@1` (catalog visibility public). The type has two shapes, and exactly one matches:++- **A network service's release went live.** OpenVibe.Host's operator plane publishes it (`ovhost deploy|rollback`, and `ovhost announce` for services deployed by their own scripts). The envelope has subject `{ type: release, id: <service>:<release> }` and visibility public, so signed-out browsers get it over Events realtime and openvibe-shared release-watch checks `/release.json` at once. The payload is `service`, `release` (the `/release.json` release id, the release manifest's pattern), `commit` (or null), `origin` (or null), `deployed_at`, and optionally `components` (kind and version only) and `rollback: true`. Identifiers only.++- **A Stage B tenant site's active deploy changed.** This is what Host's API already emitted: `project_id`, `site_id`, `site`, `deploy_id`, `previous_deploy_id`, `rollback`. It has subject `deploy` and visibility internal.++++It has fixtures for both shapes (mixing them fails), and the host manifest's notes name the operator-plane producer. The event type was already in the host manifest's `eventsProduced`, so nothing is renamed. Additive.++## 0.57.2 — 2026-09-26**Three decisions** (documentation only): ADR-029 account merge (WS-B task 5), ADR-030 follow graph (WS-E task 4), ADR-031 S3-compatible surface for Media (WS-N task 8). Each is accepted with its acceptance tests; the implementations follow in Network, Live and Media.@@@ -4577,6 +4577,16 @@"compatibility": "backward","status": "active"},++ {++ "id": "host.deploy.activated",++ "version": "1.0.0",++ "owner": "host",++ "schema": "events/payloads/host.deploy.activated.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-016"++ },{"id": "host.site-manage-result","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",++ "title": "HostDeployActivatedPayload",++ "description": "host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.",++ "type": "object",++ "oneOf": [++ {++ "description": "A network service's release went live (subject release, visibility public).",++ "type": "object",++ "required": ["service", "release", "commit", "origin", "deployed_at"],++ "additionalProperties": false,++ "properties": {++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news." },++ "release": { "type": "string", "pattern": "^[0-9a-f]{7,40}$", "description": "The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release." },++ "commit": { "type": ["string", "null"], "pattern": "^[0-9a-f]{7,40}$", "description": "The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash)." },++ "origin": { "type": ["string", "null"], "format": "uri", "pattern": "^https?://[^/?#]+$", "description": "The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known." },++ "deployed_at": { "type": "string", "format": "date-time", "description": "When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready." },++ "components": {++ "type": "object",++ "description": "Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).",++ "maxProperties": 32,++ "propertyNames": { "pattern": "^[a-z][a-z0-9-]{0,39}$" },++ "additionalProperties": {++ "type": "object",++ "required": ["kind", "version"],++ "additionalProperties": false,++ "properties": {++ "kind": { "enum": ["style", "content", "script", "server"] },++ "version": { "type": "string", "pattern": "^[0-9A-Za-z._+-]{1,64}$" }++ }++ }++ },++ "rollback": { "const": true, "description": "Present after `ovhost rollback`: the release is an older one." }++ }++ },++ {++ "description": "A Stage B tenant site's active deploy changed (subject deploy, visibility internal).",++ "type": "object",++ "required": ["project_id", "site_id", "site", "deploy_id", "previous_deploy_id", "rollback"],++ "additionalProperties": false,++ "properties": {++ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$" },++ "site_id": { "type": "string", "pattern": "^site_[0-9A-HJKMNP-TV-Z]{26}$" },++ "site": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,39}$", "description": "The site's name (<site>.openvibe.host)." },++ "deploy_id": { "type": "string", "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$" },++ "previous_deploy_id": { "type": ["string", "null"], "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The deploy that served before; null for the site's first activation." },++ "rollback": { "type": "boolean", "description": "true when the switch was a rollback." }++ }++ }++ ]++}@@@ -37,3 +37,31 @@ The manifest is advisory; removing it returns clients to today's behaviour.- A client one release behind keeps working for 24 hours after a deploy.- A deploy during an upload, call or stream does not interrupt it.- An outdated tab prompts and does not reload while the user is typing.++++## Amendment 1 (2026-09-26): release notifications over the Events realtime plane++++Roadmap WS-P task 9.++++- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.deploy.activated`++ with subject `{ type: release, id: <service>:<release> }` and visibility public. The payload contract++ is `host.deploy.activated@1`: `service`, `release`, `commit`, `origin`, `deployed_at`, and optionally++ `components` and `rollback`. It carries identifiers only; what changed stays in `/release.json`.++ - **Sent by:** `ovhost deploy|rollback` after a release went live, and `ovhost announce <service>` for++ services deployed by their own scripts.++ - **Frequency:** one event per service and release.++ - **Delivery:** best effort, and never fails a deploy. Polling is the fallback.++ - **Stage B:** tenant activations share the type, with subject `deploy` and visibility internal.++- **Client.** openvibe-shared `release-watch.js` (1.17.0) opens one anonymous EventSource per tab on++ `topics=host.deploy.activated`.++ - It acts only on events whose `payload.service` is the page's service.++ - It ignores the release it already runs or already knows (a hex prefix counts as the same release),++ and ignores repeats of an event id.++ - It collapses bursts: at most one check per 30 s, after a random 0–20 s delay so tabs do not all++ fetch at once. The check is the usual `/release.json` path: the release window, the update plan and++ the safety rules are unchanged.++ - The stream is public, so a change of account changes nothing and never opens a second stream.++ - A tab hidden for 5 minutes closes its stream; hidden tabs are covered by the poll and the check on++ becoming visible. Errors back off, from 30 s to 15 minutes, and after repeated failures the tab keeps++ only the poll.++- **Rollback.** Stop announcing (`--no-announce`, or remove the credentials): tabs fall back to polling,++ as before.nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": null,++ "origin": null,++ "deployed_at": "2026-09-26T08:00:05.000Z",++ "components": { "shell": { "kind": "binary", "version": "1" } }++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": null,++ "origin": "https://openvibe.live",++ "deployed_at": "2026-09-26T08:00:05.000Z",++ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": null,++ "origin": "https://openvibe.live/updates",++ "deployed_at": "2026-09-26T08:00:05.000Z"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "service": "live",++ "release": "v1.2.3",++ "commit": null,++ "origin": "https://openvibe.live",++ "deployed_at": "2026-09-26T08:00:05.000Z"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": null,++ "origin": null,++ "deployed_at": "2026-09-26T08:00:05.000Z",++ "rollback": false++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "site": "my-site",++ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "rollback": false++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": "d4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9",++ "origin": "https://openvibe.live",++ "deployed_at": "2026-09-26T08:00:05.000Z",++ "components": {++ "shell": { "kind": "script", "version": "3f2a9c1b7d4e" },++ "server": { "kind": "server", "version": "a1b2c3d4e5f6" }++ }++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "service": "tools",++ "release": "858845c0e1d2",++ "commit": "858845c0e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6",++ "origin": null,++ "deployed_at": "2026-09-26T08:02:00.000Z",++ "rollback": true++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "service": "news",++ "release": "56eb30198d36",++ "commit": null,++ "origin": "https://openvibe.news",++ "deployed_at": "2026-09-26T08:01:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "site": "my-site",++ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "previous_deploy_id": null,++ "rollback": false++}@@@ -51545,6 +51545,144 @@]}},++ "host.deploy.activated": {++ "version": "1.0.0",++ "owner": "host",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",++ "title": "HostDeployActivatedPayload",++ "description": "host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.",++ "type": "object",++ "oneOf": [++ {++ "description": "A network service's release went live (subject release, visibility public).",++ "type": "object",++ "required": [++ "service",++ "release",++ "commit",++ "origin",++ "deployed_at"++ ],++ "additionalProperties": false,++ "properties": {++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$",++ "description": "The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news."++ },++ "release": {++ "type": "string",++ "pattern": "^[0-9a-f]{7,40}$",++ "description": "The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release."++ },++ "commit": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^[0-9a-f]{7,40}$",++ "description": "The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash)."++ },++ "origin": {++ "type": [++ "string",++ "null"++ ],++ "format": "uri",++ "pattern": "^https?://[^/?#]+$",++ "description": "The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known."++ },++ "deployed_at": {++ "type": "string",++ "format": "date-time",++ "description": "When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready."++ },++ "components": {++ "type": "object",++ "description": "Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).",++ "maxProperties": 32,++ "propertyNames": {++ "pattern": "^[a-z][a-z0-9-]{0,39}$"++ },++ "additionalProperties": {++ "type": "object",++ "required": [++ "kind",++ "version"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "style",++ "content",++ "script",++ "server"++ ]++ },++ "version": {++ "type": "string",++ "pattern": "^[0-9A-Za-z._+-]{1,64}$"++ }++ }++ }++ },++ "rollback": {++ "const": true,++ "description": "Present after `ovhost rollback`: the release is an older one."++ }++ }++ },++ {++ "description": "A Stage B tenant site's active deploy changed (subject deploy, visibility internal).",++ "type": "object",++ "required": [++ "project_id",++ "site_id",++ "site",++ "deploy_id",++ "previous_deploy_id",++ "rollback"++ ],++ "additionalProperties": false,++ "properties": {++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "site_id": {++ "type": "string",++ "pattern": "^site_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "site": {++ "type": "string",++ "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,39}$",++ "description": "The site's name (<site>.openvibe.host)."++ },++ "deploy_id": {++ "type": "string",++ "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "previous_deploy_id": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The deploy that served before; null for the site's first activation."++ },++ "rollback": {++ "type": "boolean",++ "description": "true when the switch was a rollback."++ }++ }++ }++ ]++ }++ },"host.site-manage-result": {"version": "1.0.0","owner": "host",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRe.Stream API",-- "version": "0.57.2",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.57.2",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.58.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"@@@ -41887,6 +41887,66 @@ export type HostSiteManageRequest =}| NoBody;++/** [email protected] (owner: host) */++/**++ * host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.++ */++export type HostDeployActivatedPayload =++ | {++ /**++ * The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news.++ */++ service: string;++ /**++ * The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release.++ */++ release: string;++ /**++ * The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash).++ */++ commit: string | null;++ /**++ * The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known.++ */++ origin: string | null;++ /**++ * When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready.++ */++ deployed_at: string;++ /**++ * Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).++ */++ components?: {++ [k: string]:++ | {++ kind: "style" | "content" | "script" | "server";++ version: string;++ }++ | undefined;++ };++ /**++ * Present after `ovhost rollback`: the release is an older one.++ */++ rollback?: true;++ }++ | {++ project_id: string;++ site_id: string;++ /**++ * The site's name (<site>.openvibe.host).++ */++ site: string;++ deploy_id: string;++ /**++ * The deploy that served before; null for the site's first activation.++ */++ previous_deploy_id: string | null;++ /**++ * true when the switch was a rollback.++ */++ rollback: boolean;++ };++/** [email protected] (owner: host) *//*** host.site-manage-result@1: answers of host.site.manage on OpenVibe.Host. GET /api/v1/projects → { projects } (the caller's projects with their role). POST /api/v1/projects → 201 { project } (with quota and usage). GET /api/v1/projects/:id → { project, sites, members }. GET and PUT /api/v1/projects/:id/quota → { quota, usage }. PUT and DELETE /api/v1/projects/:id/members/:principal → { members }. GET /api/v1/projects/:id/sites → { sites }. POST /api/v1/projects/:id/sites → 201 { site }. GET /api/v1/sites/:id → { site, domains }. DELETE /api/v1/projects/:id and /sites/:id → { deleted: true } (serving stops at once; the objects are removed). DELETE /api/v1/deploys/:id → { deleted: true, objects_removed }. POST …/takedown → 201 { takedown }; DELETE …/takedown → { lifted: true }.@@@ -94,5 +94,5 @@]}},-- "notes": "Stage A: the ovhost operator CLI (no server). Stage B: the Host API service on port 4910 (unit openvibe-host.service, state /var/lib/openvibe-host-api): tenant projects keyed by project id (prj_, network_project_id once Network has projects, ADR-014), static sites served by Host header at <site>.openvibe.host (a registrable domain separate from every other OpenVibe domain) and on TXT-verified custom domains, immutable content-addressed deploys with rollback, quotas enforced here, upload logs. Consumes OpenVibe.Network (SSO, JWKS, client-credentials) and publishes through OpenVibe.Events (events.event.publish). Stage C (sandboxed user code) is not started."++ "notes": "Stage A: the ovhost operator CLI (no server). Stage B: the Host API service on port 4910 (unit openvibe-host.service, state /var/lib/openvibe-host-api): tenant projects keyed by project id (prj_, network_project_id once Network has projects, ADR-014), static sites served by Host header at <site>.openvibe.host (a registrable domain separate from every other OpenVibe domain) and on TXT-verified custom domains, immutable content-addressed deploys with rollback, quotas enforced here, upload logs. Consumes OpenVibe.Network (SSO, JWKS, client-credentials) and publishes through OpenVibe.Events (events.event.publish). The ovhost CLI publishes host.deploy.activated too, with subject release and visibility public, when a network service's release goes live (ovhost deploy/rollback/announce; release notifications, WS-P task 9); Stage B's tenant activations are subject deploy, visibility internal. Stage C (sandboxed user code) is not started."}@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.57.2",++ "version": "0.58.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",@@@ -722,6 +722,24 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);ok(!M({ ...base, text: 'the post' }) && !M({ ...base, target: { ...base.target, title: 'Hi' } }), 'it never carries the content');}++// ── Release notifications (WS-P task 9, ADR-016): host.deploy.activated, two shapes, one public ──++// A network service's release (subject release, visibility public: browsers get it over Events++// realtime) and a tenant site's activation (subject deploy, visibility internal). Exactly one shape++// matches; the release shape carries identifiers only.++{++ const H = (p) => contracts.validate('host.deploy.activated@1', p).valid;++ const cat = contracts.catalog.find(c => c.id === 'host.deploy.activated');++ ok(cat && cat.owner === 'host' && cat.visibility === 'public' && services.get('host').eventsProduced.includes('host.deploy.activated'), 'host.deploy.activated is a public Host event');++ const rel = { service: 'live', release: 'd4e849f2', commit: null, origin: 'https://openvibe.live', deployed_at: '2026-09-26T08:00:05.000Z' };++ const ten = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/host.deploy.activated/valid/tenant-site.json'), 'utf8'));++ ok(H(rel) && H(ten), 'a service release and a tenant activation are both host.deploy.activated@1');++ ok(!H({ ...rel, ...ten }), 'the two shapes never mix');++ for (const k of ['service', 'release', 'commit', 'origin', 'deployed_at']) ok(!H({ ...rel, [k]: undefined }), `a service release needs ${k} (commit and origin may be null)`);++ for (const extra of [{ commits: [] }, { notes: 'x' }, { url: 'https://openvibe.live/updates' }]) ok(!H({ ...rel, ...extra }), `a service release carries identifiers only (${Object.keys(extra)[0]})`);++ ok(H({ ...rel, release: 'd4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9' }) && !H({ ...rel, release: 'd4e84' }), 'a release is 7 to 40 hex, as registry.release-manifest@1 says');++ ok(contracts.schema('registry.release-manifest').properties.release.pattern === contracts.schema('host.deploy.activated').oneOf[0].properties.release.pattern, 'the release pattern is the release manifest\'s');++}++// ── Capability schemas (WS-C task 4): a ratchet over compatibility/capability-schema-gaps.json ──execFileSync(process.execPath, [path.join(__dirname, 'capability-schemas.test.js')], { stdio: 'inherit' });
@@ -4,6 +4,14 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,14 @@ 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,
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`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.58.0 — 2026-09-26
+
+**Release notifications** (roadmap WS-P task 9, ADR-016 amendment 1). `host.deploy.activated` gets its payload contract, `host.deploy.activated@1` (catalog visibility public). The type has two shapes, and exactly one matches:
+- **A network service's release went live.** OpenVibe.Host's operator plane publishes it (`ovhost deploy|rollback`, and `ovhost announce` for services deployed by their own scripts). The envelope has subject `{ type: release, id: <service>:<release> }` and visibility public, so signed-out browsers get it over Events realtime and openvibe-shared release-watch checks `/release.json` at once. The payload is `service`, `release` (the `/release.json` release id, the release manifest's pattern), `commit` (or null), `origin` (or null), `deployed_at`, and optionally `components` (kind and version only) and `rollback: true`. Identifiers only.
+- **A Stage B tenant site's active deploy changed.** This is what Host's API already emitted: `project_id`, `site_id`, `site`, `deploy_id`, `previous_deploy_id`, `rollback`. It has subject `deploy` and visibility internal.
+
+It has fixtures for both shapes (mixing them fails), and the host manifest's notes name the operator-plane producer. The event type was already in the host manifest's `eventsProduced`, so nothing is renamed. Additive.
+
## 0.57.2 — 2026-09-26
## 0.57.2 — 2026-09-26
**Three decisions** (documentation only): ADR-029 account merge (WS-B task 5), ADR-030 follow graph (WS-E task 4), ADR-031 S3-compatible surface for Media (WS-N task 8). Each is accepted with its acceptance tests; the implementations follow in Network, Live and Media.
**Three decisions** (documentation only): ADR-029 account merge (WS-B task 5), ADR-030 follow graph (WS-E task 4), ADR-031 S3-compatible surface for Media (WS-N task 8). Each is accepted with its acceptance tests; the implementations follow in Network, Live and Media.
@@ -4577,6 +4577,16 @@
@@ -4577,6 +4577,16 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "host.deploy.activated",
+ "version": "1.0.0",
+ "owner": "host",
+ "schema": "events/payloads/host.deploy.activated.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-016"
+ },
{
{
"id": "host.site-manage-result",
"id": "host.site-manage-result",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
+ "title": "HostDeployActivatedPayload",
+ "description": "host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.",
+ "type": "object",
+ "oneOf": [
+ {
+ "description": "A network service's release went live (subject release, visibility public).",
+ "type": "object",
+ "required": ["service", "release", "commit", "origin", "deployed_at"],
+ "additionalProperties": false,
+ "properties": {
+ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$", "description": "The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news." },
+ "release": { "type": "string", "pattern": "^[0-9a-f]{7,40}$", "description": "The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release." },
+ "commit": { "type": ["string", "null"], "pattern": "^[0-9a-f]{7,40}$", "description": "The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash)." },
+ "origin": { "type": ["string", "null"], "format": "uri", "pattern": "^https?://[^/?#]+$", "description": "The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known." },
+ "deployed_at": { "type": "string", "format": "date-time", "description": "When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready." },
+ "components": {
+ "type": "object",
+ "description": "Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).",
+ "maxProperties": 32,
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]{0,39}$" },
+ "additionalProperties": {
+ "type": "object",
+ "required": ["kind", "version"],
+ "additionalProperties": false,
+ "properties": {
+ "kind": { "enum": ["style", "content", "script", "server"] },
+ "version": { "type": "string", "pattern": "^[0-9A-Za-z._+-]{1,64}$" }
+ }
+ }
+ },
+ "rollback": { "const": true, "description": "Present after `ovhost rollback`: the release is an older one." }
+ }
+ },
+ {
+ "description": "A Stage B tenant site's active deploy changed (subject deploy, visibility internal).",
+ "type": "object",
+ "required": ["project_id", "site_id", "site", "deploy_id", "previous_deploy_id", "rollback"],
+ "additionalProperties": false,
+ "properties": {
+ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "site_id": { "type": "string", "pattern": "^site_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "site": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,39}$", "description": "The site's name (<site>.openvibe.host)." },
+ "deploy_id": { "type": "string", "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "previous_deploy_id": { "type": ["string", "null"], "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The deploy that served before; null for the site's first activation." },
+ "rollback": { "type": "boolean", "description": "true when the switch was a rollback." }
+ }
+ }
+ ]
+}
@@ -37,3 +37,31 @@ The manifest is advisory; removing it returns clients to today's behaviour.
@@ -37,3 +37,31 @@ The manifest is advisory; removing it returns clients to today's behaviour.
- A client one release behind keeps working for 24 hours after a deploy.
- A client one release behind keeps working for 24 hours after a deploy.
- A deploy during an upload, call or stream does not interrupt it.
- A deploy during an upload, call or stream does not interrupt it.
- An outdated tab prompts and does not reload while the user is typing.
- An outdated tab prompts and does not reload while the user is typing.
+
+## Amendment 1 (2026-09-26): release notifications over the Events realtime plane
+
+Roadmap WS-P task 9.
+
+- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.deploy.activated`
+ with subject `{ type: release, id: <service>:<release> }` and visibility public. The payload contract
+ is `host.deploy.activated@1`: `service`, `release`, `commit`, `origin`, `deployed_at`, and optionally
+ `components` and `rollback`. It carries identifiers only; what changed stays in `/release.json`.
+ - **Sent by:** `ovhost deploy|rollback` after a release went live, and `ovhost announce <service>` for
+ services deployed by their own scripts.
+ - **Frequency:** one event per service and release.
+ - **Delivery:** best effort, and never fails a deploy. Polling is the fallback.
+ - **Stage B:** tenant activations share the type, with subject `deploy` and visibility internal.
+- **Client.** openvibe-shared `release-watch.js` (1.17.0) opens one anonymous EventSource per tab on
+ `topics=host.deploy.activated`.
+ - It acts only on events whose `payload.service` is the page's service.
+ - It ignores the release it already runs or already knows (a hex prefix counts as the same release),
+ and ignores repeats of an event id.
+ - It collapses bursts: at most one check per 30 s, after a random 0–20 s delay so tabs do not all
+ fetch at once. The check is the usual `/release.json` path: the release window, the update plan and
+ the safety rules are unchanged.
+ - The stream is public, so a change of account changes nothing and never opens a second stream.
+ - A tab hidden for 5 minutes closes its stream; hidden tabs are covered by the poll and the check on
+ becoming visible. Errors back off, from 30 s to 15 minutes, and after repeated failures the tab keeps
+ only the poll.
+- **Rollback.** Stop announcing (`--no-announce`, or remove the credentials): tabs fall back to polling,
+ as before.
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "service": "live",
+ "release": "d4e849f2",
+ "commit": null,
+ "origin": null,
+ "deployed_at": "2026-09-26T08:00:05.000Z",
+ "components": { "shell": { "kind": "binary", "version": "1" } }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "service": "live",
+ "release": "d4e849f2",
+ "commit": null,
+ "origin": "https://openvibe.live",
+ "deployed_at": "2026-09-26T08:00:05.000Z",
+ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "service": "live",
+ "release": "d4e849f2",
+ "commit": null,
+ "origin": "https://openvibe.live/updates",
+ "deployed_at": "2026-09-26T08:00:05.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "service": "live",
+ "release": "v1.2.3",
+ "commit": null,
+ "origin": "https://openvibe.live",
+ "deployed_at": "2026-09-26T08:00:05.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "service": "live",
+ "release": "d4e849f2",
+ "commit": null,
+ "origin": null,
+ "deployed_at": "2026-09-26T08:00:05.000Z",
+ "rollback": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "site": "my-site",
+ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "rollback": false
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "service": "live",
+ "release": "d4e849f2",
+ "commit": "d4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9",
+ "origin": "https://openvibe.live",
+ "deployed_at": "2026-09-26T08:00:05.000Z",
+ "components": {
+ "shell": { "kind": "script", "version": "3f2a9c1b7d4e" },
+ "server": { "kind": "server", "version": "a1b2c3d4e5f6" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "service": "tools",
+ "release": "858845c0e1d2",
+ "commit": "858845c0e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6",
+ "origin": null,
+ "deployed_at": "2026-09-26T08:02:00.000Z",
+ "rollback": true
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "service": "news",
+ "release": "56eb30198d36",
+ "commit": null,
+ "origin": "https://openvibe.news",
+ "deployed_at": "2026-09-26T08:01:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "site": "my-site",
+ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "previous_deploy_id": null,
+ "rollback": false
+}
@@ -51545,6 +51545,144 @@
@@ -51545,6 +51545,144 @@
]
]
}
}
},
},
+ "host.deploy.activated": {
+ "version": "1.0.0",
+ "owner": "host",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
+ "title": "HostDeployActivatedPayload",
+ "description": "host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.",
+ "type": "object",
+ "oneOf": [
+ {
+ "description": "A network service's release went live (subject release, visibility public).",
+ "type": "object",
+ "required": [
+ "service",
+ "release",
+ "commit",
+ "origin",
+ "deployed_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$",
+ "description": "The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news."
+ },
+ "release": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{7,40}$",
+ "description": "The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release."
+ },
+ "commit": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^[0-9a-f]{7,40}$",
+ "description": "The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash)."
+ },
+ "origin": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri",
+ "pattern": "^https?://[^/?#]+$",
+ "description": "The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known."
+ },
+ "deployed_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready."
+ },
+ "components": {
+ "type": "object",
+ "description": "Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).",
+ "maxProperties": 32,
+ "propertyNames": {
+ "pattern": "^[a-z][a-z0-9-]{0,39}$"
+ },
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "kind",
+ "version"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "style",
+ "content",
+ "script",
+ "server"
+ ]
+ },
+ "version": {
+ "type": "string",
+ "pattern": "^[0-9A-Za-z._+-]{1,64}$"
+ }
+ }
+ }
+ },
+ "rollback": {
+ "const": true,
+ "description": "Present after `ovhost rollback`: the release is an older one."
+ }
+ }
+ },
+ {
+ "description": "A Stage B tenant site's active deploy changed (subject deploy, visibility internal).",
+ "type": "object",
+ "required": [
+ "project_id",
+ "site_id",
+ "site",
+ "deploy_id",
+ "previous_deploy_id",
+ "rollback"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "site_id": {
+ "type": "string",
+ "pattern": "^site_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "site": {
+ "type": "string",
+ "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){2,39}$",
+ "description": "The site's name (<site>.openvibe.host)."
+ },
+ "deploy_id": {
+ "type": "string",
+ "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "previous_deploy_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^dpl_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The deploy that served before; null for the site's first activation."
+ },
+ "rollback": {
+ "type": "boolean",
+ "description": "true when the switch was a rollback."
+ }
+ }
+ }
+ ]
+ }
+ },
"host.site-manage-result": {
"host.site-manage-result": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "host",
"owner": "host",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.57.2",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.57.2",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.57.2): 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.58.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.58.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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -41887,6 +41887,66 @@ export type HostSiteManageRequest =
@@ -41887,6 +41887,66 @@ export type HostSiteManageRequest =
}
}
| NoBody;
| NoBody;
+/** [email protected] (owner: host) */
+/**
+ * host.deploy.activated v1. A deploy became the one that serves. Two producers inside OpenVibe.Host, told apart by the envelope subject and by the payload's fields (exactly one shape matches). (1) A network service's release went live (roadmap WS-P task 9, ADR-016): the operator plane, lib/announce.js, after `ovhost deploy|rollback <service>` went live and for `ovhost announce <service>` (services deployed by their own scripts: Live, Tools, Sites, OpenRe, Games). Envelope: subject { type: release, id: <service>:<release> }, visibility public (signed-out browsers get it over Events realtime), priority low, actor service:host. Open tabs whose /release.json names `service` check it when `release` is not the one they run; the event carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. (2) A Stage B tenant site's active deploy changed: server/domain/deploys.js, in the transaction that moves the pointer. Envelope: subject { type: deploy, id: <deploy id> }, visibility internal (never streamed to browsers), actor the person or app that switched it.
+ */
+export type HostDeployActivatedPayload =
+ | {
+ /**
+ * The service id the pages' /release.json names (registry.release-manifest@1 `service`): live, tools, or a Sites placeholder's id such as news.
+ */
+ service: string;
+ /**
+ * The release id that service's /release.json reports now (registry.release-manifest@1 `release`). A client compares it with the release it runs, treating a hex prefix of the other as the same release.
+ */
+ release: string;
+ /**
+ * The deployed commit when it is known and `release` is its prefix, or what the operator passed; null otherwise (a Sites placeholder's release is a page hash).
+ */
+ commit: string | null;
+ /**
+ * The public origin the release serves (https://openvibe.live): the inventory's origin or the service manifest's publicOrigin; null when neither is known.
+ */
+ origin: string | null;
+ /**
+ * When Host announced it (UTC); after `ovhost deploy`, just after the service answered ready.
+ */
+ deployed_at: string;
+ /**
+ * Optional. The component versions /release.json reported (registry.release-manifest 1.1.0 `components`, kind and version only).
+ */
+ components?: {
+ [k: string]:
+ | {
+ kind: "style" | "content" | "script" | "server";
+ version: string;
+ }
+ | undefined;
+ };
+ /**
+ * Present after `ovhost rollback`: the release is an older one.
+ */
+ rollback?: true;
+ }
+ | {
+ project_id: string;
+ site_id: string;
+ /**
+ * The site's name (<site>.openvibe.host).
+ */
+ site: string;
+ deploy_id: string;
+ /**
+ * The deploy that served before; null for the site's first activation.
+ */
+ previous_deploy_id: string | null;
+ /**
+ * true when the switch was a rollback.
+ */
+ rollback: boolean;
+ };
+
/** [email protected] (owner: host) */
/** [email protected] (owner: host) */
/**
/**
* host.site-manage-result@1: answers of host.site.manage on OpenVibe.Host. GET /api/v1/projects → { projects } (the caller's projects with their role). POST /api/v1/projects → 201 { project } (with quota and usage). GET /api/v1/projects/:id → { project, sites, members }. GET and PUT /api/v1/projects/:id/quota → { quota, usage }. PUT and DELETE /api/v1/projects/:id/members/:principal → { members }. GET /api/v1/projects/:id/sites → { sites }. POST /api/v1/projects/:id/sites → 201 { site }. GET /api/v1/sites/:id → { site, domains }. DELETE /api/v1/projects/:id and /sites/:id → { deleted: true } (serving stops at once; the objects are removed). DELETE /api/v1/deploys/:id → { deleted: true, objects_removed }. POST …/takedown → 201 { takedown }; DELETE …/takedown → { lifted: true }.
* host.site-manage-result@1: answers of host.site.manage on OpenVibe.Host. GET /api/v1/projects → { projects } (the caller's projects with their role). POST /api/v1/projects → 201 { project } (with quota and usage). GET /api/v1/projects/:id → { project, sites, members }. GET and PUT /api/v1/projects/:id/quota → { quota, usage }. PUT and DELETE /api/v1/projects/:id/members/:principal → { members }. GET /api/v1/projects/:id/sites → { sites }. POST /api/v1/projects/:id/sites → 201 { site }. GET /api/v1/sites/:id → { site, domains }. DELETE /api/v1/projects/:id and /sites/:id → { deleted: true } (serving stops at once; the objects are removed). DELETE /api/v1/deploys/:id → { deleted: true, objects_removed }. POST …/takedown → 201 { takedown }; DELETE …/takedown → { lifted: true }.
@@ -94,5 +94,5 @@
@@ -94,5 +94,5 @@
]
]
}
}
},
},
- "notes": "Stage A: the ovhost operator CLI (no server). Stage B: the Host API service on port 4910 (unit openvibe-host.service, state /var/lib/openvibe-host-api): tenant projects keyed by project id (prj_, network_project_id once Network has projects, ADR-014), static sites served by Host header at <site>.openvibe.host (a registrable domain separate from every other OpenVibe domain) and on TXT-verified custom domains, immutable content-addressed deploys with rollback, quotas enforced here, upload logs. Consumes OpenVibe.Network (SSO, JWKS, client-credentials) and publishes through OpenVibe.Events (events.event.publish). Stage C (sandboxed user code) is not started."
+ "notes": "Stage A: the ovhost operator CLI (no server). Stage B: the Host API service on port 4910 (unit openvibe-host.service, state /var/lib/openvibe-host-api): tenant projects keyed by project id (prj_, network_project_id once Network has projects, ADR-014), static sites served by Host header at <site>.openvibe.host (a registrable domain separate from every other OpenVibe domain) and on TXT-verified custom domains, immutable content-addressed deploys with rollback, quotas enforced here, upload logs. Consumes OpenVibe.Network (SSO, JWKS, client-credentials) and publishes through OpenVibe.Events (events.event.publish). The ovhost CLI publishes host.deploy.activated too, with subject release and visibility public, when a network service's release goes live (ovhost deploy/rollback/announce; release notifications, WS-P task 9); Stage B's tenant activations are subject deploy, visibility internal. Stage C (sandboxed user code) is not started."
}
}
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.57.2",
+ "version": "0.58.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -722,6 +722,24 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
@@ -722,6 +722,24 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
ok(!M({ ...base, text: 'the post' }) && !M({ ...base, target: { ...base.target, title: 'Hi' } }), 'it never carries the content');
ok(!M({ ...base, text: 'the post' }) && !M({ ...base, target: { ...base.target, title: 'Hi' } }), 'it never carries the content');
}
}
+// ── Release notifications (WS-P task 9, ADR-016): host.deploy.activated, two shapes, one public ──
+// A network service's release (subject release, visibility public: browsers get it over Events
+// realtime) and a tenant site's activation (subject deploy, visibility internal). Exactly one shape
+// matches; the release shape carries identifiers only.
+{
+ const H = (p) => contracts.validate('host.deploy.activated@1', p).valid;
+ const cat = contracts.catalog.find(c => c.id === 'host.deploy.activated');
+ ok(cat && cat.owner === 'host' && cat.visibility === 'public' && services.get('host').eventsProduced.includes('host.deploy.activated'), 'host.deploy.activated is a public Host event');
+ const rel = { service: 'live', release: 'd4e849f2', commit: null, origin: 'https://openvibe.live', deployed_at: '2026-09-26T08:00:05.000Z' };
+ const ten = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/host.deploy.activated/valid/tenant-site.json'), 'utf8'));
+ ok(H(rel) && H(ten), 'a service release and a tenant activation are both host.deploy.activated@1');
+ ok(!H({ ...rel, ...ten }), 'the two shapes never mix');
+ for (const k of ['service', 'release', 'commit', 'origin', 'deployed_at']) ok(!H({ ...rel, [k]: undefined }), `a service release needs ${k} (commit and origin may be null)`);
+ for (const extra of [{ commits: [] }, { notes: 'x' }, { url: 'https://openvibe.live/updates' }]) ok(!H({ ...rel, ...extra }), `a service release carries identifiers only (${Object.keys(extra)[0]})`);
+ ok(H({ ...rel, release: 'd4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9' }) && !H({ ...rel, release: 'd4e84' }), 'a release is 7 to 40 hex, as registry.release-manifest@1 says');
+ ok(contracts.schema('registry.release-manifest').properties.release.pattern === contracts.schema('host.deploy.activated').oneOf[0].properties.release.pattern, 'the release pattern is the release manifest\'s');
+}
+
// ── Capability schemas (WS-C task 4): a ratchet over compatibility/capability-schema-gaps.json ──
// ── Capability schemas (WS-C task 4): a ratchet over compatibility/capability-schema-gaps.json ──
execFileSync(process.execPath, [path.join(__dirname, 'capability-schemas.test.js')], { stdio: 'inherit' });
execFileSync(process.execPath, [path.join(__dirname, 'capability-schemas.test.js')], { stdio: 'inherit' });