openvibers/openvibe.host Commits

Release notifications publish host.release.published, not host.deploy.activated (WS-P task 9)

OP OpenVibers committed 26 Sept 2026 · 100c27949a · 6 files, +21 −20

The release notification is now its own event type (openvibe-contracts 0.58.0: host.release.published@1). ovhost announce and the announcement after a deploy or rollback that went live publish event_type host.release.published: subject { type: release, id: <service>:<release> }, visibility public, the same payload (service, release, commit, origin, deployed_at, components?, rollback?). host.deploy.activated stays Stage B's tenant activation only (subject deploy, visibility internal), unchanged. test/announce.test.js expects the new type and validates the payload as host.release.published@1 once the installed contracts have it. Checked again by hand against a real OpenVibe.Events: stored, and delivered to an anonymous subscriber of /realtime/stream?topics=host.release.published; the payload validates as host.release.published@1 of the Contracts working copy and not as host.deploy.activated@1. docs/release-notifications.md (the curl topic, the example event, the two types), README, the CLI help and STATUS.json's event list use the new name.

Changes

ddiff --git a/README.md b/README.md
iindex 241feae..fc4b81f 100644
---- a/README.md
++++ b/README.md
@@@ -114,7 +114,7 @@ Every command accepts `--json` and `--inventory <file>`. `drill` exits `0` passe
`deploy --browser-check` checks the service's public site in headless Chrome after a deploy that went through. It runs [`scripts/browser-check.js`](docs/browser-check.md) for that one site. The check is report only: it never changes the exit code or the release record. It needs Chrome where ovhost runs.
--**Release notifications** (WS-P task 9). A deploy or rollback that went live is announced to OpenVibe.Events as `host.deploy.activated`. The event is public, has subject `release` and carries the service, its `/release.json` release id, commit and origin. Open tabs (openvibe-shared release-watch 1.17.0 and later) then check `/release.json` within seconds instead of at their next poll. `ovhost announce <service>` does the same for services deployed by their own scripts (Live, Tools and Sites call it at the end of theirs). It is best effort: a few seconds at most, never a failed deploy, one event per release. The credentials are Host's service principal from `/etc/openvibe/host.env`. [docs/release-notifications.md](docs/release-notifications.md) covers the payload, the credentials, provisioning and an end-to-end check.
++**Release notifications** (WS-P task 9). A deploy or rollback that went live is announced to OpenVibe.Events as `host.release.published`. The event is public, has subject `release` and carries the service, its `/release.json` release id, commit and origin. Open tabs (openvibe-shared release-watch 1.17.0 and later) then check `/release.json` within seconds instead of at their next poll. `ovhost announce <service>` does the same for services deployed by their own scripts (Live, Tools and Sites call it at the end of theirs). It is best effort: a few seconds at most, never a failed deploy, one event per release. The credentials are Host's service principal from `/etc/openvibe/host.env`. [docs/release-notifications.md](docs/release-notifications.md) covers the payload, the credentials, provisioning and an end-to-end check.
### Browser check (all public sites)
@@@ -275,7 +275,7 @@ Through the `openvibe-sdk` v0.5.0 transactional outbox (`event_outbox`), inside
The relay publishes with Host's service token (`events.event.publish`, audience `openvibe.events`) only when `EVENTS_URL` and `OV_OAUTH_CLIENT_SECRET` are set. Otherwise rows wait and `/api/ready` says the relay is off.
--These tenant events have subject `deploy` and visibility `internal`. The operator plane (`ovhost`) publishes `host.deploy.activated` with subject `release` and visibility `public` for network service releases ([docs/release-notifications.md](docs/release-notifications.md)). Both shapes are the one contract `host.deploy.activated@1` (openvibe-contracts 0.58.0).
++These tenant events have subject `deploy` and visibility `internal`; `host.deploy.activated@1` in openvibe-contracts 0.58.0 is the tenant shape only. Releases of network services are a separate event type: the operator plane (`ovhost`) publishes `host.release.published` with subject `release` and visibility `public` ([docs/release-notifications.md](docs/release-notifications.md)).
### Dashboard
ddiff --git a/STATUS.json b/STATUS.json
iindex 5cbcf37..df6a4b0 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -24,7 +24,8 @@
"host.deploy.created",
"host.deploy.activated",
"host.deploy.failed",
-- "host.domain.verified"
++ "host.domain.verified",
++ "host.release.published"
],
"stages": {
"A": "alpha: ovhost CLI installed on the production host 2026-09-23 (/usr/local/bin/ovhost, inventory /etc/openvibe/host.json); status/validate/backup/drill used there (19 restore drills passed 2026-09-23, docs/restore-drills.md); no service deploy has moved to it yet; the D41 deploy-and-rollback proof (scripts/d41-proof.sh) is written and tested, not run",
ddiff --git a/docs/release-notifications.md b/docs/release-notifications.md
iindex 321aea4..ea4d0a1 100644
---- a/docs/release-notifications.md
++++ b/docs/release-notifications.md
@@@ -1,7 +1,7 @@
--# Release notifications (`host.deploy.activated`)
++# Release notifications (`host.release.published`)
Roadmap WS-P task 9, ADR-016 (active client updates). When a network service's release goes live,
--Host publishes one `host.deploy.activated` event to OpenVibe.Events. The event has visibility
++Host publishes one `host.release.published` event to OpenVibe.Events. The event has visibility
**public** and subject `{ type: "release", id: "<service>:<release>" }`. Open tabs run
`openvibe-shared/release-watch.js` (1.17.0 and later) and listen for it on the Events realtime stream.
When the event names their service, they check `/release.json` within about 20 seconds instead of
@@@ -17,9 +17,9 @@ at their next 10-minute poll. The event carries identifiers only.
| `components` | Optional: the manifest's `{ name: { kind, version } }` |
| `rollback` | Optional: `true` after `ovhost rollback` |
--The payload contract is `host.deploy.activated@1` in openvibe-contracts 0.58.0. The same event type also
--covers Stage B tenant activations, which use subject `deploy`, a different payload and visibility
--`internal`, so browsers never receive them.
++The payload contract is `host.release.published@1` in openvibe-contracts 0.58.0. It is a separate event
++type from `host.deploy.activated`, which Stage B emits for a tenant site's activation (subject `deploy`,
++visibility `internal`, its own payload). Browsers never receive that one.
## Who announces
@@@ -136,7 +136,7 @@ moves the pin, or set `data-events="off"` on the `ov-release` meta tag.
1. Subscribe the way a signed-out browser does, from any machine:
```
-- curl -N 'https://events.openvibe.network/realtime/stream?topics=host.deploy.activated'
++ curl -N 'https://events.openvibe.network/realtime/stream?topics=host.release.published'
```
The stream starts with `retry: 3000` and `: connected anonymous`, then sends a `: hb` comment every
25 s.
@@@ -148,7 +148,7 @@ moves the pin, or set `data-events="off"` on the `ov-release` meta tag.
[ovhost] release notification sent: live 1a2b3c4d (from /release.json) as evt_01… (seq 12345)
```
3. The subscriber prints
-- `id: 12345` / `data: {"seq":12345,"event":{"event_type":"host.deploy.activated",…,"visibility":"public","subject":{"type":"release","id":"live:1a2b3c4d"},"payload":{"service":"live",…}}}`.
++ `id: 12345` / `data: {"seq":12345,"event":{"event_type":"host.release.published",…,"visibility":"public","subject":{"type":"release","id":"live:1a2b3c4d"},"payload":{"service":"live",…}}}`.
4. In a browser on a site that pins openvibe-shared 1.17.0 or later, run `OVRelease.state().realtime` in
the console. It shows `{ state: 'open', service: 'live', events: 1, … }` after the announcement.
An `--force` of the release the tab already runs is ignored (`ignored: 1`).
ddiff --git a/lib/announce.js b/lib/announce.js
iindex 900b3ea..8055ac1 100644
---- a/lib/announce.js
++++ b/lib/announce.js
@@@ -1,7 +1,7 @@
'use strict';
/**
* Release notifications (roadmap WS-P task 9; ADR-016). When a network service's release goes live,
-- * Host publishes `host.deploy.activated` to OpenVibe.Events with visibility public and subject
++ * Host publishes `host.release.published` to OpenVibe.Events with visibility public and subject
* { type: release, id: <service>:<release> }. Open tabs (openvibe-shared release-watch, subscribed to
* that topic over Events realtime) then check /release.json within seconds instead of at their next
* poll.
@@@ -33,7 +33,7 @@
const path = require('path');
const { git } = require('./git');
--const EVENT_TYPE = 'host.deploy.activated';
++const EVENT_TYPE = 'host.release.published';
const DEFAULT_ENV_FILE = '/etc/openvibe/host.env';
const SERVICE_RE = /^[a-z][a-z0-9-]{1,39}$/;
const HEX_RE = /^[0-9a-f]{7,40}$/;
@@@ -184,7 +184,7 @@ function newEventId(now) {
return require('openvibe-contracts').ids.newId('event', now);
}
--/** The events.event-envelope@1 for a description (payload: contract host.deploy.activated@1). */
++/** The events.event-envelope@1 for a description (payload: contract host.release.published@1). */
function envelope(d, { now, rollback = false, eventId = newEventId(now) }) {
const at = new Date(now).toISOString();
const payload = { service: d.service, release: d.release, commit: d.commit || null, origin: d.origin || null, deployed_at: at };
ddiff --git a/lib/cli.js b/lib/cli.js
iindex ebee390..fed662a 100644
---- a/lib/cli.js
++++ b/lib/cli.js
@@@ -29,7 +29,7 @@ Usage: ovhost <command> [args] [--inventory <file>] [--json]
[--no-announce] a deploy that went live is announced (see announce) unless this is set
rollback <service> [--to <sha>] [--wait-idle] [--force] [--no-announce]
announce <service> [--release <id>] [--commit <sha>] [--origin <url>] [--force] [--dry-run]
-- publish host.deploy.activated to OpenVibe.Events (public), so open
++ publish host.release.published to OpenVibe.Events (public), so open
tabs check /release.json now: for services deployed by their own
scripts. Release: --release, else the service's loopback
/release.json, else the checkout's HEAD; the same release is sent
ddiff --git a/test/announce.test.js b/test/announce.test.js
iindex b0bec9e..eb592c7 100644
---- a/test/announce.test.js
++++ b/test/announce.test.js
@@@ -1,7 +1,7 @@
'use strict';
/**
* Release notifications (lib/announce.js, WS-P task 9): `ovhost announce` and the announcement after a
-- * deploy publish host.deploy.activated to OpenVibe.Events with Host's service token, never fail or
++ * deploy publish host.release.published to OpenVibe.Events with Host's service token, never fail or
* change a deploy, send one release once, and never show the client secret or the token.
*/
const assert = require('assert');
@@@ -13,12 +13,12 @@ const TOKEN = 'tok_SERVICE_TOKEN_MUST_NEVER_APPEAR_91c2';
const ENV_FILE = '/etc/openvibe/host.env';
const HOST_SECRET = `${SECRET}-host`;
--/** The installed openvibe-contracts has host.deploy.activated@1 from 0.58.0; older pins skip this check. */
++/** The installed openvibe-contracts has host.release.published@1 from 0.58.0; older pins skip this check. */
function payloadSchemaCheck(payload) {
let known = true;
-- try { contracts.schema('host.deploy.activated'); } catch { known = false; }
++ try { contracts.schema('host.release.published'); } catch { known = false; }
if (!known) return;
-- const v = contracts.validate('host.deploy.activated@1', payload);
++ const v = contracts.validate('host.release.published@1', payload);
assert.ok(v.valid, JSON.stringify(v.errors));
}
@@@ -77,7 +77,7 @@ runTests([
[['POST', 'application/x-www-form-urlencoded', 'client_credentials', 'host', 'openvibe.events', 'events.event.publish']]);
assert.strictEqual(host.published.length, 1);
const e = host.published[0];
-- assert.strictEqual(e.event_type, 'host.deploy.activated');
++ assert.strictEqual(e.event_type, 'host.release.published');
assert.strictEqual(e.version, 1);
assert.strictEqual(e.source, 'host');
assert.deepStrictEqual(e.actor, { type: 'service', id: 'host' });
@@@ -227,7 +227,7 @@ runTests([
assert.strictEqual(r.code, 0, r.out);
assert.match(r.out, /dry run: tools [0-9a-f]{12} \(from git HEAD\); nothing sent/);
const env = JSON.parse(r.out.slice(r.out.indexOf('{')));
-- assert.strictEqual(env.event_type, 'host.deploy.activated');
++ assert.strictEqual(env.event_type, 'host.release.published');
assert.ok(contracts.validate('events.event-envelope@1', env).valid);
assert.ok(!host.reads.includes(ENV_FILE), 'the credentials were not read');
const j = await host.cli('announce', 'tools', '--dry-run', '--json');