openvibers/openvibe.contracts Commits
v0.58.0 (continued): the release notification is its own event type, host.release.published@1; host.deploy.activated@1 is the tenant shape only
Changes
@@@ -6,11 +6,12 @@ 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.++**Release notifications** (roadmap WS-P task 9, ADR-016 amendment 1). A new event type, **`host.release.published`**, with the payload contract `host.release.published@1` (catalog visibility public). It means a network service's release went live.++- **Who publishes it.** OpenVibe.Host's operator plane: `ovhost deploy|rollback`, and `ovhost announce` for services deployed by their own scripts.++- **Envelope.** Subject `{ type: release, id: <service>:<release> }` and visibility public. Signed-out browsers get it over Events realtime, and openvibe-shared release-watch (1.17.0, `topics=host.release.published`) checks `/release.json` at once.++- **Payload.** `service`, `release` (the `/release.json` release id, with the release manifest's pattern), `commit` or null, `origin` (an http(s) origin) or null, `deployed_at`, and optionally `components` (kind and version only, at most 32) and `rollback: true`. Identifiers only.--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.++`host.deploy.activated` also gets its payload contract, `host.deploy.activated@1` (first-party), which is what Host's Stage B already emits for a tenant site's activation: `project_id`, `site_id`, `site`, `deploy_id`, `previous_deploy_id`, `rollback`. It has subject `deploy` and visibility internal. The two types never share a payload, and fixtures check that neither passes for the other. The host manifest lists `host.release.published` in `eventsProduced`. Additive.## 0.57.2 — 2026-09-26@@@ -4582,6 +4582,16 @@"version": "1.0.0","owner": "host","schema": "events/payloads/host.deploy.activated.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-004"++ },++ {++ "id": "host.release.published",++ "version": "1.0.0",++ "owner": "host",++ "schema": "events/payloads/host.release.published.v1.json","visibility": "public","compatibility": "backward","status": "active",@@@ -2,51 +2,46 @@"$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.",++ "description": "host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.","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." }-- }++ "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}$"},-- {-- "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." }-- }++ "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."}-- ]++ }}nnew file mode 100644@@@ -0,0 +1,83 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/host.release.published.v1.json",++ "title": "HostReleasePublishedPayload",++ "description": "host.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.",++ "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."++ }++ }++}@@@ -42,17 +42,18 @@ The manifest is advisory; removing it returns clients to today's behaviour.Roadmap WS-P task 9.--- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.deploy.activated`++- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.release.published`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++ is `host.release.published@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>` forservices 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.++ - **Stage B:** tenant activations stay `host.deploy.activated` (subject `deploy`, visibility internal),++ a separate event type with its own payload. The two are never mixed.- **Client.** openvibe-shared `release-watch.js` (1.17.0) opens one anonymous EventSource per tab on-- `topics=host.deploy.activated`.++ `topics=host.release.published`.- 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.nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "service": "live",++ "release": "d4e849f2",++ "commit": null,++ "origin": "https://openvibe.live",++ "deployed_at": "2026-09-26T08:00:05.000Z"++}ddeleted file mode 100644@@@ -1,8 +0,0 @@--{-- "service": "live",-- "release": "d4e849f2",-- "commit": null,-- "origin": null,-- "deployed_at": "2026-09-26T08:00:05.000Z",-- "components": { "shell": { "kind": "binary", "version": "1" } }--}ddeleted file mode 100644@@@ -1,8 +0,0 @@--{-- "service": "live",-- "release": "d4e849f2",-- "commit": null,-- "origin": "https://openvibe.live",-- "deployed_at": "2026-09-26T08:00:05.000Z",-- "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR"--}ddeleted file mode 100644@@@ -1,7 +0,0 @@--{-- "service": "live",-- "release": "d4e849f2",-- "commit": null,-- "origin": "https://openvibe.live/updates",-- "deployed_at": "2026-09-26T08:00:05.000Z"--}ddeleted file mode 100644@@@ -1,7 +0,0 @@--{-- "service": "live",-- "release": "v1.2.3",-- "commit": null,-- "origin": "https://openvibe.live",-- "deployed_at": "2026-09-26T08:00:05.000Z"--}ddeleted file mode 100644@@@ -1,8 +0,0 @@--{-- "service": "live",-- "release": "d4e849f2",-- "commit": null,-- "origin": null,-- "deployed_at": "2026-09-26T08:00:05.000Z",-- "rollback": false--}ddeleted file mode 100644@@@ -1,11 +0,0 @@--{-- "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" }-- }--}ddeleted file mode 100644@@@ -1,8 +0,0 @@--{-- "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,8 @@++{++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "site": "my-site",++ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "previous_deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "rollback": true++}ddeleted file mode 100644@@@ -1,7 +0,0 @@--{-- "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++}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,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,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,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"++}@@@ -51553,134 +51553,136 @@"$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.",++ "description": "host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.","type": "object",-- "oneOf": [-- {-- "description": "A network service's release went live (subject release, visibility public).",-- "type": "object",-- "required": [-- "service",-- "release",-- "commit",-- "origin",-- "deployed_at"++ "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"],-- "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}$"++ "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.release.published": {++ "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.release.published.v1.json",++ "title": "HostReleasePublishedPayload",++ "description": "host.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.",++ "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"++ ]},-- "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}$"-- }-- }++ "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."-- }-- }++ "rollback": {++ "const": true,++ "description": "Present after `ovhost rollback`: the release is an older one."}-- ]++ }}},"host.site-manage-result": {@@@ -41889,63 +41889,67 @@ export type HostSiteManageRequest =/** [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.++ * host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.*/--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;-- };++export interface HostDeployActivatedPayload {++ 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.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.++ */++export interface HostReleasePublishedPayload {++ /**++ * 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;++}/** [email protected] (owner: host) *//**@@@ -36,6 +36,7 @@"host.deploy.created","host.deploy.activated","host.deploy.failed",++ "host.release.published","host.domain.verified"],"eventsConsumed": [],@@@ -94,5 +95,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). 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."++ "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.release.published (subject release, visibility public) when a network service's release goes live (ovhost deploy/rollback/announce; release notifications, WS-P task 9); host.deploy.activated is Stage B's tenant activations only. Stage C (sandboxed user code) is not started."}@@@ -722,22 +722,25 @@ 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.++// ── Release notifications (WS-P task 9, ADR-016): host.release.published, public, identifiers only ──++// A network service's release went live (subject release, visibility public: browsers get it over++// Events realtime). Tenant hosting's activations stay host.deploy.activated (first-party, its own shape);++// neither payload passes for the other.{-- 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 R = (p) => contracts.validate('host.release.published@1', p).valid;++ const D = (p) => contracts.validate('host.deploy.activated@1', p).valid;++ const cat = (id) => contracts.catalog.find(c => c.id === id);++ ok(cat('host.release.published') && cat('host.release.published').owner === 'host' && cat('host.release.published').visibility === 'public', 'host.release.published is a public Host event');++ ok(cat('host.deploy.activated') && cat('host.deploy.activated').visibility === 'first-party', 'host.deploy.activated stays first-party (tenant hosting)');++ for (const id of ['host.release.published', 'host.deploy.activated']) ok(services.get('host').eventsProduced.includes(id), `the host manifest produces ${id}`);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');++ ok(R(rel) && D(ten) && !R(ten) && !D(rel) && !R({ ...rel, ...ten }), 'a release and a tenant activation are two contracts that never mix');++ ok(!contracts.schema('host.deploy.activated').oneOf && !contracts.schema('host.release.published').oneOf, 'one shape each');++ for (const k of ['service', 'release', 'commit', 'origin', 'deployed_at']) ok(!R({ ...rel, [k]: undefined }), `a release needs ${k} (commit and origin may be null)`);++ for (const extra of [{ commits: [] }, { notes: 'x' }, { url: 'https://openvibe.live/updates' }]) ok(!R({ ...rel, ...extra }), `a release carries identifiers only (${Object.keys(extra)[0]})`);++ ok(R({ ...rel, release: 'd4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9' }) && !R({ ...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.release.published').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 ──
@@ -6,11 +6,12 @@ the notes were in the tag and commit messages (`git tag -n1`).
@@ -6,11 +6,12 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.58.0 — 2026-09-26
## 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.
+**Release notifications** (roadmap WS-P task 9, ADR-016 amendment 1). A new event type, **`host.release.published`**, with the payload contract `host.release.published@1` (catalog visibility public). It means a network service's release went live.
+- **Who publishes it.** OpenVibe.Host's operator plane: `ovhost deploy|rollback`, and `ovhost announce` for services deployed by their own scripts.
+- **Envelope.** Subject `{ type: release, id: <service>:<release> }` and visibility public. Signed-out browsers get it over Events realtime, and openvibe-shared release-watch (1.17.0, `topics=host.release.published`) checks `/release.json` at once.
+- **Payload.** `service`, `release` (the `/release.json` release id, with the release manifest's pattern), `commit` or null, `origin` (an http(s) origin) or null, `deployed_at`, and optionally `components` (kind and version only, at most 32) and `rollback: true`. Identifiers only.
-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.
+`host.deploy.activated` also gets its payload contract, `host.deploy.activated@1` (first-party), which is what Host's Stage B already emits for a tenant site's activation: `project_id`, `site_id`, `site`, `deploy_id`, `previous_deploy_id`, `rollback`. It has subject `deploy` and visibility internal. The two types never share a payload, and fixtures check that neither passes for the other. The host manifest lists `host.release.published` in `eventsProduced`. Additive.
## 0.57.2 — 2026-09-26
## 0.57.2 — 2026-09-26
@@ -4582,6 +4582,16 @@
@@ -4582,6 +4582,16 @@
"version": "1.0.0",
"version": "1.0.0",
"owner": "host",
"owner": "host",
"schema": "events/payloads/host.deploy.activated.v1.json",
"schema": "events/payloads/host.deploy.activated.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-004"
+ },
+ {
+ "id": "host.release.published",
+ "version": "1.0.0",
+ "owner": "host",
+ "schema": "events/payloads/host.release.published.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
"status": "active",
"status": "active",
@@ -2,51 +2,46 @@
@@ -2,51 +2,46 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
"$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
"title": "HostDeployActivatedPayload",
"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.",
+ "description": "host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.",
"type": "object",
"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." }
- }
+ "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}$"
},
},
- {
- "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." }
- }
+ "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."
}
}
- ]
+ }
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/host.release.published.v1.json",
+ "title": "HostReleasePublishedPayload",
+ "description": "host.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.",
+ "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."
+ }
+ }
+}
@@ -42,17 +42,18 @@ The manifest is advisory; removing it returns clients to today's behaviour.
@@ -42,17 +42,18 @@ The manifest is advisory; removing it returns clients to today's behaviour.
Roadmap WS-P task 9.
Roadmap WS-P task 9.
-- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.deploy.activated`
+- **Event.** When a network service's release goes live, OpenVibe.Host publishes `host.release.published`
with subject `{ type: release, id: <service>:<release> }` and visibility public. The payload contract
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
+ is `host.release.published@1`: `service`, `release`, `commit`, `origin`, `deployed_at`, and optionally
`components` and `rollback`. It carries identifiers only; what changed stays in `/release.json`.
`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
- **Sent by:** `ovhost deploy|rollback` after a release went live, and `ovhost announce <service>` for
services deployed by their own scripts.
services deployed by their own scripts.
- **Frequency:** one event per service and release.
- **Frequency:** one event per service and release.
- **Delivery:** best effort, and never fails a deploy. Polling is the fallback.
- **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.
+ - **Stage B:** tenant activations stay `host.deploy.activated` (subject `deploy`, visibility internal),
+ a separate event type with its own payload. The two are never mixed.
- **Client.** openvibe-shared `release-watch.js` (1.17.0) opens one anonymous EventSource per tab on
- **Client.** openvibe-shared `release-watch.js` (1.17.0) opens one anonymous EventSource per tab on
- `topics=host.deploy.activated`.
+ `topics=host.release.published`.
- It acts only on events whose `payload.service` is the page's service.
- 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),
- 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.
and ignores repeats of an event id.
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",
+ "deployed_at": "2026-09-26T08:00:05.000Z"
+}
deleted file mode 100644
deleted file mode 100644
@@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
-{
- "service": "live",
- "release": "d4e849f2",
- "commit": null,
- "origin": null,
- "deployed_at": "2026-09-26T08:00:05.000Z",
- "components": { "shell": { "kind": "binary", "version": "1" } }
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
-{
- "service": "live",
- "release": "d4e849f2",
- "commit": null,
- "origin": "https://openvibe.live",
- "deployed_at": "2026-09-26T08:00:05.000Z",
- "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR"
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
-{
- "service": "live",
- "release": "d4e849f2",
- "commit": null,
- "origin": "https://openvibe.live/updates",
- "deployed_at": "2026-09-26T08:00:05.000Z"
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
-{
- "service": "live",
- "release": "v1.2.3",
- "commit": null,
- "origin": "https://openvibe.live",
- "deployed_at": "2026-09-26T08:00:05.000Z"
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
-{
- "service": "live",
- "release": "d4e849f2",
- "commit": null,
- "origin": null,
- "deployed_at": "2026-09-26T08:00:05.000Z",
- "rollback": false
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
-{
- "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" }
- }
-}
deleted file mode 100644
deleted file mode 100644
@@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
-{
- "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,8 @@
@@ -0,0 +1,8 @@
+{
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "site_id": "site_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "site": "my-site",
+ "deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "previous_deploy_id": "dpl_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "rollback": true
+}
deleted file mode 100644
deleted file mode 100644
@@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
-{
- "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
+}
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,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,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,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"
+}
@@ -51553,134 +51553,136 @@
@@ -51553,134 +51553,136 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
"$id": "https://openvibe.network/contracts/events/payloads/host.deploy.activated.v1.json",
"title": "HostDeployActivatedPayload",
"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.",
+ "description": "host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.",
"type": "object",
"type": "object",
- "oneOf": [
- {
- "description": "A network service's release went live (subject release, visibility public).",
- "type": "object",
- "required": [
- "service",
- "release",
- "commit",
- "origin",
- "deployed_at"
+ "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"
],
],
- "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}$"
+ "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.release.published": {
+ "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.release.published.v1.json",
+ "title": "HostReleasePublishedPayload",
+ "description": "host.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.",
+ "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"
+ ]
},
},
- "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}$"
- }
- }
+ "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."
- }
- }
+ "rollback": {
+ "const": true,
+ "description": "Present after `ovhost rollback`: the release is an older one."
}
}
- ]
+ }
}
}
},
},
"host.site-manage-result": {
"host.site-manage-result": {
@@ -41889,63 +41889,67 @@ export type HostSiteManageRequest =
@@ -41889,63 +41889,67 @@ export type HostSiteManageRequest =
/** [email protected] (owner: host) */
/** [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.
+ * host.deploy.activated v1 (OpenVibe.Host server/domain/deploys.js switchPointer, Stage B tenant hosting). A tenant site's active deploy changed, written into Host's event outbox in the transaction that moves the pointer (activate or rollback). Envelope: subject { type: deploy, id: <deploy id> }, visibility internal, priority low, actor the person or app that switched it. A network service's release going live is host.release.published, not this.
*/
*/
-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;
- };
+export interface HostDeployActivatedPayload {
+ 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.release.published v1 (roadmap WS-P task 9, ADR-016 amendment 1). A network service's release went live. OpenVibe.Host's operator plane (lib/announce.js) publishes it 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` (openvibe-shared release-watch 1.17.0, topics=host.release.published) check it when `release` is not the one they run. It carries identifiers only, never what changed. At most one per service and release unless an operator forces it; a retry reuses the event_id. Tenant sites' activations are host.deploy.activated, not this.
+ */
+export interface HostReleasePublishedPayload {
+ /**
+ * 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;
+}
/** [email protected] (owner: host) */
/** [email protected] (owner: host) */
/**
/**
@@ -36,6 +36,7 @@
@@ -36,6 +36,7 @@
"host.deploy.created",
"host.deploy.created",
"host.deploy.activated",
"host.deploy.activated",
"host.deploy.failed",
"host.deploy.failed",
+ "host.release.published",
"host.domain.verified"
"host.domain.verified"
],
],
"eventsConsumed": [],
"eventsConsumed": [],
@@ -94,5 +95,5 @@
@@ -94,5 +95,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). 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."
+ "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.release.published (subject release, visibility public) when a network service's release goes live (ovhost deploy/rollback/announce; release notifications, WS-P task 9); host.deploy.activated is Stage B's tenant activations only. Stage C (sandboxed user code) is not started."
}
}
@@ -722,22 +722,25 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
@@ -722,22 +722,25 @@ 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.
+// ── Release notifications (WS-P task 9, ADR-016): host.release.published, public, identifiers only ──
+// A network service's release went live (subject release, visibility public: browsers get it over
+// Events realtime). Tenant hosting's activations stay host.deploy.activated (first-party, its own shape);
+// neither payload passes for the other.
{
{
- 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 R = (p) => contracts.validate('host.release.published@1', p).valid;
+ const D = (p) => contracts.validate('host.deploy.activated@1', p).valid;
+ const cat = (id) => contracts.catalog.find(c => c.id === id);
+ ok(cat('host.release.published') && cat('host.release.published').owner === 'host' && cat('host.release.published').visibility === 'public', 'host.release.published is a public Host event');
+ ok(cat('host.deploy.activated') && cat('host.deploy.activated').visibility === 'first-party', 'host.deploy.activated stays first-party (tenant hosting)');
+ for (const id of ['host.release.published', 'host.deploy.activated']) ok(services.get('host').eventsProduced.includes(id), `the host manifest produces ${id}`);
const rel = { service: 'live', release: 'd4e849f2', commit: null, origin: 'https://openvibe.live', deployed_at: '2026-09-26T08:00:05.000Z' };
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'));
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');
+ ok(R(rel) && D(ten) && !R(ten) && !D(rel) && !R({ ...rel, ...ten }), 'a release and a tenant activation are two contracts that never mix');
+ ok(!contracts.schema('host.deploy.activated').oneOf && !contracts.schema('host.release.published').oneOf, 'one shape each');
+ for (const k of ['service', 'release', 'commit', 'origin', 'deployed_at']) ok(!R({ ...rel, [k]: undefined }), `a release needs ${k} (commit and origin may be null)`);
+ for (const extra of [{ commits: [] }, { notes: 'x' }, { url: 'https://openvibe.live/updates' }]) ok(!R({ ...rel, ...extra }), `a release carries identifiers only (${Object.keys(extra)[0]})`);
+ ok(R({ ...rel, release: 'd4e849f2a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9' }) && !R({ ...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.release.published').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 ──