openvibers/openvibe.contracts Commits
Presence: network.presence@1 and network.presence.changed (0.318.0) (#325)
Changes
@@@ -1,5 +1,13 @@# Changelog++## 0.318.0 — 2026-11-07++++- Add `network.presence@1`: one person's online status as another may see it — `online`, `away`, `dnd`, `invisible` or `offline`, an optional status line (80 characters) and one emoji, whether they are streaming on OpenVibe.Live (`live`), and when it changed. OpenVibe.Network owns it; the state is derived from a heartbeat (online under 2 minutes, offline after 3) and the last input (away after 10 minutes), and invisible or `show_status` off is seen as offline.++- Add `network.presence-update-request@1`: the optional fields of `PUT /api/v1/me/presence` — the chosen state (`auto`, `dnd`, `invisible`), the status line and emoji, and the "Show my status" switch.++- Add the event `network.presence.changed`, at most once per person per 30 seconds and only for a real change. Its payload is the public view: an invisible person is sent as offline with no status text or emoji, and the state list has no `invisible`.++- Add the capabilities `network.presence.read` (OpenVibe.Network's `GET /internal/presence`, for Chat) and `chat.dm.peers.read` (OpenVibe.Chat's `POST /internal/chat/dm/peers`, for Network's "follows or has a DM" rule), and name them in the two service manifests.++- Valid and invalid fixtures cover the presence, the update request and the event, so a bad subject, an unknown state, an over-long status or a second emoji fails the run.++## 0.317.0 — 2026-11-07- Add `frame.menu-card@1`: one card a site publishes for the network's Frame menu (the menu's network cards). A card has a stable id and site, a title of at most 60 characters, a kind (`rail`, `rows` or `chips`), up to six items with a title of at most 80, an optional meta line of at most 120 and an https URL, an optional `more` link with a label of at most 24, the time it was built and its ttl. A URL must be https on the publishing site's own origin or an `openvibe.*` host; the text is plain words and Network strips markup and drops unknown fields.@@@ -9789,6 +9789,51 @@"visibility": "public","compatibility": "backward","status": "active"++ },++ {++ "id": "network.presence",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/presence.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.presence-update-request",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/presence-update-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.presence.changed",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.presence.changed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.presence-list-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/presence-list-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "chat.dm-peers-result",++ "version": "1.0.0",++ "owner": "chat",++ "schema": "chat/dm-peers-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"}]}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/dm-peers-result.v1.json",++ "title": "ChatDmPeersResult",++ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",++ "type": "object",++ "additionalProperties": false,++ "required": ["subject", "peers"],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose conversations were read."++ },++ "peers": {++ "type": "array",++ "maxItems": 100,++ "items": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },++ "description": "The candidates that share at least one conversation with them, as subjects."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,51 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.presence.changed.v1.json",++ "title": "NetworkPresenceChangedPayload",++ "description": "network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_\u2026> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "state",++ "updated_at"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "state": {++ "enum": [++ "online",++ "away",++ "dnd",++ "offline"++ ],++ "description": "The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji."++ },++ "status_text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80++ },++ "emoji": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 16++ },++ "live": {++ "type": "boolean",++ "default": false,++ "description": "Streaming on OpenVibe.Live now; false while invisible or with show_status off."++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence-list-result.v1.json",++ "title": "NetworkPresenceListResult",++ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",++ "type": "object",++ "additionalProperties": false,++ "required": ["presence"],++ "properties": {++ "presence": {++ "type": "array",++ "maxItems": 100,++ "items": { "$ref": "presence.v1.json" },++ "description": "The visible people, in no particular order."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence-update-request.v1.json",++ "title": "NetworkPresenceUpdateRequest",++ "description": "network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.",++ "type": "object",++ "additionalProperties": false,++ "minProperties": 1,++ "properties": {++ "state": {++ "enum": ["auto", "dnd", "invisible"],++ "description": "The chosen state. auto derives online or away from the newest heartbeat and the last input."++ },++ "status_text": {++ "type": ["string", "null"],++ "maxLength": 80,++ "description": "The status line; null clears it. Markup and control characters are stripped."++ },++ "emoji": {++ "type": ["string", "null"],++ "maxLength": 16,++ "description": "One emoji, or null to clear it. More than one grapheme cluster is refused."++ },++ "show_status": {++ "type": "boolean",++ "description": "The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence.v1.json",++ "title": "NetworkPresence",++ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",++ "type": "object",++ "additionalProperties": false,++ "required": ["subject", "state", "updated_at"],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person's canonical subject."++ },++ "state": {++ "enum": ["online", "away", "dnd", "invisible", "offline"],++ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."++ },++ "status_text": {++ "type": ["string", "null"],++ "maxLength": 80,++ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."++ },++ "emoji": {++ "type": ["string", "null"],++ "maxLength": 16,++ "description": "One emoji that sits beside the status line, or null."++ },++ "live": {++ "type": "boolean",++ "default": false,++ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."++ },++ "updated_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the person's presence row last changed."++ }++ }++}@@@ -78,3 +78,42 @@ Guessed private topic yields nothing; reconnect from a cursor gets the complete- A stored notification and its event commit together or not at all.These are Events `test/realtime-tickets.test.js`, Network `test/notification-events.test.js` and Shared `test/notification-live.test.js`.++++## Amendment 3 (2026-11-07): a person's own online status, owned by Network++++**Status:** Accepted (plan 2026-10-22 track X item 9). This amendment supersedes amendment 1 for a person's++chosen online status; amendment 1 still governs who is connected to a room right now.++++**Decision.** Each person has a **presence** of online, away, do not disturb or invisible, with an optional++status line and one emoji. It lives in OpenVibe.Network (`network.presence@1`), not in Chat's WebSocket map:++the person carries it across every site, and a DM list must show it when the person is not in that room.++- A heartbeat from any open tab (`POST /api/v1/me/presence/heartbeat`, at most once a minute) keeps it fresh.++ Online while the newest heartbeat is under 2 minutes old; away after 10 minutes without input (the client++ says `idle: true`); offline after 3 minutes without a heartbeat.++- `PUT /api/v1/me/presence` sets the chosen state (`auto`, `dnd`, `invisible`), the status line, the emoji and++ the "Show my status" switch. Invisible and the switch off read as offline to everyone.++- `GET /api/v1/presence?subjects=…` answers only for people the asker follows or has a DM with (and++ themselves), up to 100 subjects, and never for an invisible person. Last seen is never returned.++- `network.presence.changed` is written to Network's outbox on a real change, at most once per person per++ 30 seconds, with the public view only (invisible is offline). It is an administrative event for first-party++ consumers, not a per-join realtime topic: no join or leave is ever published, and the event carries no++ status text or emoji for someone who hid them.++- Chat keeps its per-room connection map for "who is in this room", and draws the person's dot from Network's++ presence (cached 30 seconds), never the reverse. DND mutes Chat's own notification pings.++++**Alternatives considered.**++- Keeping presence in Chat only: rejected. It cannot follow a person across sites or exist while they are++ not connected to a chat socket, and the owner asked for a status that follows them everywhere.++- A per-join/per-leave event topic: still rejected (amendment 1); this amendment adds only a debounced state++ change, never connection churn.++- Storing last seen and returning it: rejected. The owner's privacy list says last seen is never shown.++++**Consequences.** Network stores one row per person who has ever sent a heartbeat (erased with the account and++included in its export, ADR-033). Services read the API with a person's own token, or with a service token and++`network.presence.read` on behalf of one person; the DM part of the rule is read from Chat through++`chat.dm.peers.read`. A stranger's presence is indistinguishable from an offline one.++++**Acceptance.** A stranger gets no row; a follower gets online/away/dnd; an invisible person gets no row and++the event says offline; a heartbeat older than 3 minutes reads offline; an idle heartbeat does not move++`last_active_at`; the event is debounced. These are Network `test/presence.test.js` and Chat++`test/presence.test.js`.@@@ -8,7 +8,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-002](ADR-002-contract-repository.md) | Contract repository and compatibility policy | Accepted, implemented 2026-09-22 (openvibe-contracts v0.1.0+) || [ADR-003](ADR-003-service-principals.md) | Service/app principal authentication and capability grants | Accepted, implemented 2026-09-22; amended 2026-09-24 (delegated authorization and approval UX), 2026-10-12 (project delegation) || [ADR-004](ADR-004-durable-events.md) | Durable event store, outbox/inbox and delivery semantics | Accepted 2026-09-22; implementation in progress (OpenVibe.Events) |--| [ADR-005](ADR-005-realtime.md) | Realtime topics, cursor/resume and whether OpenVibe.Realtime exists | Accepted 2026-09-22: realtime runs inside OpenVibe.Events; amendment 1 (2026-09-26): presence is ephemeral, in Chat's delivery plane; amendment 2 (2026-09-26): a person's topic is `network.notification.*` with subject visibility, and browsers authenticate with two-minute realtime tickets |++| [ADR-005](ADR-005-realtime.md) | Realtime topics, cursor/resume and whether OpenVibe.Realtime exists | Accepted 2026-09-22: realtime runs inside OpenVibe.Events; amendment 1 (2026-09-26): presence is ephemeral, in Chat's delivery plane; amendment 2 (2026-09-26): a person's topic is `network.notification.*` with subject visibility, and browsers authenticate with two-minute realtime tickets; amendment 3 (2026-11-07): a person's chosen online status is `network.presence@1` in Network, with a debounced `network.presence.changed` || [ADR-006](ADR-006-media-objects.md) | Media object, location and namespace model | Accepted 2026-09-22; implementation in progress (OpenVibe.Media Wave 4); amended 2026-09-24 (placement classes, local provider, infrequent access deferred) || [ADR-007](ADR-007-data-ownership.md) | Logical data ownership and database technology | Accepted 2026-09-22; amended 2026-09-24 (PgBouncer, replicas, SERIALIZABLE money paths, Redis non-authoritative) || [ADR-008](ADR-008-shared-packages.md) | Shared package publication and release manifests | Accepted 2026-09-22; implementation in progress (OpenVibe.Shared) |nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": ["bob"] }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": [] }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": ["usr_01JAB2C3D4E5F6G7H8J9K0MNPR"] }nnew file mode 100644@@@ -0,0 +1 @@++{ "presence": [{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }] }nnew file mode 100644@@@ -0,0 +1 @@++{ "presence": [] }nnew file mode 100644@@@ -0,0 +1 @@++{ "presence": [{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }] }nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1 @@++{ "emoji": "not-an-emoji-at-all" }nnew file mode 100644@@@ -0,0 +1 @@++{ "state": "auto", "last_seen": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "state": "online" }nnew file mode 100644@@@ -0,0 +1 @@++{ "status_text": null, "emoji": null, "show_status": false }nnew file mode 100644@@@ -0,0 +1 @@++{ "state": "dnd" }nnew file mode 100644@@@ -0,0 +1 @@++{ "status_text": "Out for lunch", "emoji": "🍜" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "invisible", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "offline", "status_text": null, "emoji": null, "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "last_seen": "2026-11-07T09:00:00.000Z", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "This status line is deliberately longer than eighty characters so the schema refuses it without any doubt at all.", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "busy", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "away", "status_text": null, "emoji": null, "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "dnd", "status_text": "Heads down", "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "offline", "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }nnew file mode 100644@@@ -0,0 +1 @@++{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }@@@ -107814,6 +107814,226 @@"$ref": "menu-card.v1.json"}}++ },++ "network.presence": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence.v1.json",++ "title": "NetworkPresence",++ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "state",++ "updated_at"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person's canonical subject."++ },++ "state": {++ "enum": [++ "online",++ "away",++ "dnd",++ "invisible",++ "offline"++ ],++ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."++ },++ "status_text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."++ },++ "emoji": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 16,++ "description": "One emoji that sits beside the status line, or null."++ },++ "live": {++ "type": "boolean",++ "default": false,++ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."++ },++ "updated_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the person's presence row last changed."++ }++ }++ }++ },++ "network.presence-update-request": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence-update-request.v1.json",++ "title": "NetworkPresenceUpdateRequest",++ "description": "network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.",++ "type": "object",++ "additionalProperties": false,++ "minProperties": 1,++ "properties": {++ "state": {++ "enum": [++ "auto",++ "dnd",++ "invisible"++ ],++ "description": "The chosen state. auto derives online or away from the newest heartbeat and the last input."++ },++ "status_text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "The status line; null clears it. Markup and control characters are stripped."++ },++ "emoji": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 16,++ "description": "One emoji, or null to clear it. More than one grapheme cluster is refused."++ },++ "show_status": {++ "type": "boolean",++ "description": "The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible."++ }++ }++ }++ },++ "network.presence.changed": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.presence.changed.v1.json",++ "title": "NetworkPresenceChangedPayload",++ "description": "network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_…> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "state",++ "updated_at"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "state": {++ "enum": [++ "online",++ "away",++ "dnd",++ "offline"++ ],++ "description": "The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji."++ },++ "status_text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80++ },++ "emoji": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 16++ },++ "live": {++ "type": "boolean",++ "default": false,++ "description": "Streaming on OpenVibe.Live now; false while invisible or with show_status off."++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.presence-list-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/presence-list-result.v1.json",++ "title": "NetworkPresenceListResult",++ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "presence"++ ],++ "properties": {++ "presence": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "$ref": "presence.v1.json"++ },++ "description": "The visible people, in no particular order."++ }++ }++ }++ },++ "chat.dm-peers-result": {++ "version": "1.0.0",++ "owner": "chat",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/dm-peers-result.v1.json",++ "title": "ChatDmPeersResult",++ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "peers"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose conversations were read."++ },++ "peers": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "The candidates that share at least one conversation with them, as subjects."++ }++ }++ }}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bench API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3201,6 +3201,52 @@}}},++ "/internal/chat/dm/peers": {++ "post": {++ "operationId": "post_internal_chat_dm_peers",++ "summary": "Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat.",++ "description": "**chat.dm.peers.read** (first-party): Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat. It answers with subjects only (never a message, a conversation name or another participant), for OpenVibe.Network's presence privacy rule: presence is visible to people someone follows or has a DM with, never to strangers. First-party only; never grantable to an app or an agent.",++ "tags": [++ "chat.dm"++ ],++ "x-openvibe-capabilities": [++ "chat.dm.peers.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-read",++ "security": [++ {++ "openvibeToken": [++ "chat.dm.peers.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/chat.dm-peers-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/chat/events": {"post": {"operationId": "post_internal_chat_events",@@@ -6492,6 +6538,32 @@}}},++ "chat.dm-peers-result.v1": {++ "title": "ChatDmPeersResult",++ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "peers"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose conversations were read."++ },++ "peers": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "The candidates that share at least one conversation with them, as subjects."++ }++ }++ },"chat.event-request.v1": {"title": "ChatEventRequest","description": "chat.event-request@1: what a first-party service hands OpenVibe.Chat's POST /internal/chat/events (chat.event.publish) to push a transient frame to chat sockets: cards, alerts, sounds, media queue state, call invites. Nothing is stored; Chat broadcasts the frame to the target. Applied once per (caller, key): a retry with the same key and body answers the first result, the same key with another body gets 409. Chat also refuses a frame whose JSON is longer than 16384 characters or that has a __proto__, constructor or prototype key; a news card's timestamp must parse as a date.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe Connect API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Design API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Domains API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Food API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -47,8 +47,8 @@"service": "chat","name": "OpenVibe.Chat","origin": "https://openvibe.chat",-- "operations": 66,-- "capabilities": 25++ "operations": 67,++ "capabilities": 26},{"service": "codes",@@@ -159,8 +159,8 @@"service": "network","name": "OpenVibe.Network","origin": "https://openvibe.network",-- "operations": 62,-- "capabilities": 35++ "operations": 63,++ "capabilities": 36},{"service": "news",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.MediaHub API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3503,6 +3503,52 @@}}},++ "/internal/presence?for=usr_…&subjects=usr_…,usr_…": {++ "get": {++ "operationId": "get_internal_presence_for_usr_subjects_usr_usr",++ "summary": "Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live.",++ "description": "**network.presence.read** (first-party): Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live. It answers only for people that person follows or has a DM with (and themselves); invisible, show_status off and strangers are never disclosed, and last seen is never returned. First-party services only (Chat draws the dots in its DMs and member lists); never grantable to an app or an agent.",++ "tags": [++ "network.presence"++ ],++ "x-openvibe-capabilities": [++ "network.presence.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "presence-read",++ "security": [++ {++ "openvibeToken": [++ "network.presence.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.presence-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/projects/{project_id}": {"get": {"operationId": "get_internal_projects_project_id",@@@ -7297,6 +7343,79 @@}}},++ "network.presence-list-result.v1": {++ "title": "NetworkPresenceListResult",++ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "presence"++ ],++ "properties": {++ "presence": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "$ref": "#/components/schemas/network.presence.v1"++ },++ "description": "The visible people, in no particular order."++ }++ }++ },++ "network.presence.v1": {++ "title": "NetworkPresence",++ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "subject",++ "state",++ "updated_at"++ ],++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person's canonical subject."++ },++ "state": {++ "enum": [++ "online",++ "away",++ "dnd",++ "invisible",++ "offline"++ ],++ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."++ },++ "status_text": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."++ },++ "emoji": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 16,++ "description": "One emoji that sits beside the status line, or null."++ },++ "live": {++ "type": "boolean",++ "default": false,++ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."++ },++ "updated_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the person's presence row last changed."++ }++ }++ },"network.project-member-result.v1": {"title": "NetworkProjectMemberResult","description": "network.project-member-result@1: OpenVibe.Network's answer to GET /internal/projects/:project_id/members/:subject (network.project.member.read). It says whether one person belongs to a developer project, with which role, and whether the project is archived, so a receiver can check a project delegation (ADR-003 amendment 2026-10-12: the person must be an admin or owner of an unarchived project). Only members are answered; a non-member and an unknown project are both 404 project.member_not_found. It carries no other members, no apps and no secrets.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRestream API",-- "version": "0.317.0",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Play API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Quest API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Report API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Run API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Services API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Today API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Vision API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Watch API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Work API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Zone API",-- "version": "0.317.0",-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.318.0",++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -298693,3 +298693,106 @@ export interface FrameMenuCard {*/export type FrameMenuCards =[] | [FrameMenuCard] | [FrameMenuCard, FrameMenuCard] | [FrameMenuCard, FrameMenuCard, FrameMenuCard];++++/** [email protected] (owner: network) */++/**++ * network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)++ */++export interface NetworkPresence {++ /**++ * The person's canonical subject.++ */++ subject: string;++ /**++ * The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible.++ */++ state: "online" | "away" | "dnd" | "invisible" | "offline";++ /**++ * The person's status line in their own words, or null. Never shown while they are invisible or have show_status off.++ */++ status_text?: string | null;++ /**++ * One emoji that sits beside the status line, or null.++ */++ emoji?: string | null;++ /**++ * The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off.++ */++ live?: boolean;++ /**++ * When the person's presence row last changed.++ */++ updated_at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.++ */++export interface NetworkPresenceUpdateRequest {++ /**++ * The chosen state. auto derives online or away from the newest heartbeat and the last input.++ */++ state?: "auto" | "dnd" | "invisible";++ /**++ * The status line; null clears it. Markup and control characters are stripped.++ */++ status_text?: string | null;++ /**++ * One emoji, or null to clear it. More than one grapheme cluster is refused.++ */++ emoji?: string | null;++ /**++ * The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible.++ */++ show_status?: boolean;++}++++/** [email protected] (owner: network) */++/**++ * network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_…> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.++ */++export interface NetworkPresenceChangedPayload {++ subject: string;++ /**++ * The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji.++ */++ state: "online" | "away" | "dnd" | "offline";++ status_text?: string | null;++ emoji?: string | null;++ /**++ * Streaming on OpenVibe.Live now; false while invisible or with show_status off.++ */++ live?: boolean;++ updated_at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.++ */++export interface NetworkPresenceListResult {++ /**++ * The visible people, in no particular order.++ *++ * @maxItems 100++ */++ presence: NetworkPresence[];++}++++/** [email protected] (owner: chat) */++/**++ * chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.++ */++export interface ChatDmPeersResult {++ /**++ * The person whose conversations were read.++ */++ subject: string;++ /**++ * The candidates that share at least one conversation with them, as subjects.++ *++ * @maxItems 100++ */++ peers: string[];++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "chat.dm.peers.read",++ "version": "1.0",++ "owner": "chat",++ "status": "active",++ "visibility": "first-party",++ "description": "Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat. It answers with subjects only (never a message, a conversation name or another participant), for OpenVibe.Network's presence privacy rule: presence is visible to people someone follows or has a DM with, never to strangers. First-party only; never grantable to an app or an agent.",++ "permissions": [++ "chat:dm-peers:read"++ ],++ "resourceConstraints": [],++ "quotaClass": "internal-read",++ "events": [],++ "implementedBy": [++ "POST /internal/chat/dm/peers"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "chat.dm-peers-result@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.presence.read",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "first-party",++ "description": "Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live. It answers only for people that person follows or has a DM with (and themselves); invisible, show_status off and strangers are never disclosed, and last seen is never returned. First-party services only (Chat draws the dots in its DMs and member lists); never grantable to an app or an agent.",++ "permissions": [++ "presence:read"++ ],++ "resourceConstraints": [],++ "quotaClass": "presence-read",++ "events": [++ "network.presence.changed"++ ],++ "implementedBy": [++ "GET /internal/presence?for=usr_\u2026&subjects=usr_\u2026,usr_\u2026"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "network.presence-list-result@1"++}@@@ -38,7 +38,8 @@"chat.ticket.write","chat.tts.speak","chat.vibe.ask",-- "chat.vibe.deliver"++ "chat.vibe.deliver",++ "chat.dm.peers.read"],"eventsProduced": ["chat.message.created",@@@ -94,7 +95,7 @@},{"kind": "consumer",-- "what": "OpenVibe.Events redelivers the events of Chat's subscriptions (network.block.changed, network.user.token_valid_after, live.release.deployed, \u2026) that were pending during the stop (at least once, deduped by event_id)"++ "what": "OpenVibe.Events redelivers the events of Chat's subscriptions (network.block.changed, network.user.token_valid_after, live.release.deployed, …) that were pending during the stop (at least once, deduped by event_id)"}]},@@@ -105,7 +106,7 @@],"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies","blockers": {-- "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"++ "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"}},"contracts": {@@@ -83,7 +83,8 @@"network.consent.read","network.consent.manage","network.machines.read",-- "network.machines.manage"++ "network.machines.manage",++ "network.presence.read"],"eventsProduced": ["network.account.deleted",@@@ -115,7 +116,8 @@"share.revoked","share.transfer.requested","share.transfer.accepted",-- "share.transfer.declined"++ "share.transfer.declined",++ "network.presence.changed"],"eventsConsumed": ["billing.staff.action",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.317.0",++ "version": "0.318.0","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",@@@ -1,5 +1,6 @@'use strict';--// ADR-005 amendment 1: people's presence is ephemeral, in Chat's delivery plane.++// ADR-005 amendments 1 and 3: rooms stay ephemeral in Chat's delivery plane; a person's chosen online++// status is Network's `network.presence@1`, announced only as the debounced `network.presence.changed`.// ADR-043 records robot connectivity in Events; ADR-057 records Connect device connectivity;// neither is a person's presence.// The machines hub (plan L34) records the same for a person's machines: a machine coming online is the@@@ -12,6 +13,14 @@ const deviceConnectivity = new Set(['bot.robot.online', 'bot.robot.offline','network.machine.online', 'network.machine.offline',]);--const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t));--assert.ok(presenceEvents.length === 0, `no person presence event type: ${presenceEvents}`);++const personPresence = new Set(['network.presence.changed']);++const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t) && !personPresence.has(t));++assert.ok(presenceEvents.length === 0, `no other person presence event type: ${presenceEvents}`);++// The one allowed person-presence event is Network's, and it is in the contract catalog (amendment 3).++const network = c.services.manifests.find((m) => m.id === 'network');++assert.ok(network.eventsProduced.includes('network.presence.changed'), 'Network announces its own presence');++assert.ok(c.schema('network.presence.changed@1'), 'network.presence.changed@1 is a contract');++// No per-join or per-leave topic: the event's payload has no connection vocabulary.++const payload = c.schema('network.presence.changed@1');++assert.ok(!Object.keys(payload.properties).some((k) => /join|leave|connect/i.test(k)), 'no connection churn in the payload');console.log('presence policy: all checks passed');
@@ -1,5 +1,13 @@
@@ -1,5 +1,13 @@
# Changelog
# Changelog
+## 0.318.0 — 2026-11-07
+
+- Add `network.presence@1`: one person's online status as another may see it — `online`, `away`, `dnd`, `invisible` or `offline`, an optional status line (80 characters) and one emoji, whether they are streaming on OpenVibe.Live (`live`), and when it changed. OpenVibe.Network owns it; the state is derived from a heartbeat (online under 2 minutes, offline after 3) and the last input (away after 10 minutes), and invisible or `show_status` off is seen as offline.
+- Add `network.presence-update-request@1`: the optional fields of `PUT /api/v1/me/presence` — the chosen state (`auto`, `dnd`, `invisible`), the status line and emoji, and the "Show my status" switch.
+- Add the event `network.presence.changed`, at most once per person per 30 seconds and only for a real change. Its payload is the public view: an invisible person is sent as offline with no status text or emoji, and the state list has no `invisible`.
+- Add the capabilities `network.presence.read` (OpenVibe.Network's `GET /internal/presence`, for Chat) and `chat.dm.peers.read` (OpenVibe.Chat's `POST /internal/chat/dm/peers`, for Network's "follows or has a DM" rule), and name them in the two service manifests.
+- Valid and invalid fixtures cover the presence, the update request and the event, so a bad subject, an unknown state, an over-long status or a second emoji fails the run.
+
## 0.317.0 — 2026-11-07
## 0.317.0 — 2026-11-07
- Add `frame.menu-card@1`: one card a site publishes for the network's Frame menu (the menu's network cards). A card has a stable id and site, a title of at most 60 characters, a kind (`rail`, `rows` or `chips`), up to six items with a title of at most 80, an optional meta line of at most 120 and an https URL, an optional `more` link with a label of at most 24, the time it was built and its ttl. A URL must be https on the publishing site's own origin or an `openvibe.*` host; the text is plain words and Network strips markup and drops unknown fields.
- Add `frame.menu-card@1`: one card a site publishes for the network's Frame menu (the menu's network cards). A card has a stable id and site, a title of at most 60 characters, a kind (`rail`, `rows` or `chips`), up to six items with a title of at most 80, an optional meta line of at most 120 and an https URL, an optional `more` link with a label of at most 24, the time it was built and its ttl. A URL must be https on the publishing site's own origin or an `openvibe.*` host; the text is plain words and Network strips markup and drops unknown fields.
@@ -9789,6 +9789,51 @@
@@ -9789,6 +9789,51 @@
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
+ },
+ {
+ "id": "network.presence",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/presence.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.presence-update-request",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/presence-update-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.presence.changed",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.presence.changed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.presence-list-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/presence-list-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "chat.dm-peers-result",
+ "version": "1.0.0",
+ "owner": "chat",
+ "schema": "chat/dm-peers-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/dm-peers-result.v1.json",
+ "title": "ChatDmPeersResult",
+ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["subject", "peers"],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose conversations were read."
+ },
+ "peers": {
+ "type": "array",
+ "maxItems": 100,
+ "items": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "description": "The candidates that share at least one conversation with them, as subjects."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.presence.changed.v1.json",
+ "title": "NetworkPresenceChangedPayload",
+ "description": "network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_\u2026> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "state",
+ "updated_at"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "state": {
+ "enum": [
+ "online",
+ "away",
+ "dnd",
+ "offline"
+ ],
+ "description": "The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji."
+ },
+ "status_text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80
+ },
+ "emoji": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 16
+ },
+ "live": {
+ "type": "boolean",
+ "default": false,
+ "description": "Streaming on OpenVibe.Live now; false while invisible or with show_status off."
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence-list-result.v1.json",
+ "title": "NetworkPresenceListResult",
+ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["presence"],
+ "properties": {
+ "presence": {
+ "type": "array",
+ "maxItems": 100,
+ "items": { "$ref": "presence.v1.json" },
+ "description": "The visible people, in no particular order."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence-update-request.v1.json",
+ "title": "NetworkPresenceUpdateRequest",
+ "description": "network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.",
+ "type": "object",
+ "additionalProperties": false,
+ "minProperties": 1,
+ "properties": {
+ "state": {
+ "enum": ["auto", "dnd", "invisible"],
+ "description": "The chosen state. auto derives online or away from the newest heartbeat and the last input."
+ },
+ "status_text": {
+ "type": ["string", "null"],
+ "maxLength": 80,
+ "description": "The status line; null clears it. Markup and control characters are stripped."
+ },
+ "emoji": {
+ "type": ["string", "null"],
+ "maxLength": 16,
+ "description": "One emoji, or null to clear it. More than one grapheme cluster is refused."
+ },
+ "show_status": {
+ "type": "boolean",
+ "description": "The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence.v1.json",
+ "title": "NetworkPresence",
+ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["subject", "state", "updated_at"],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person's canonical subject."
+ },
+ "state": {
+ "enum": ["online", "away", "dnd", "invisible", "offline"],
+ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."
+ },
+ "status_text": {
+ "type": ["string", "null"],
+ "maxLength": 80,
+ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."
+ },
+ "emoji": {
+ "type": ["string", "null"],
+ "maxLength": 16,
+ "description": "One emoji that sits beside the status line, or null."
+ },
+ "live": {
+ "type": "boolean",
+ "default": false,
+ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the person's presence row last changed."
+ }
+ }
+}
@@ -78,3 +78,42 @@ Guessed private topic yields nothing; reconnect from a cursor gets the complete
@@ -78,3 +78,42 @@ Guessed private topic yields nothing; reconnect from a cursor gets the complete
- A stored notification and its event commit together or not at all.
- A stored notification and its event commit together or not at all.
These are Events `test/realtime-tickets.test.js`, Network `test/notification-events.test.js` and Shared `test/notification-live.test.js`.
These are Events `test/realtime-tickets.test.js`, Network `test/notification-events.test.js` and Shared `test/notification-live.test.js`.
+
+## Amendment 3 (2026-11-07): a person's own online status, owned by Network
+
+**Status:** Accepted (plan 2026-10-22 track X item 9). This amendment supersedes amendment 1 for a person's
+chosen online status; amendment 1 still governs who is connected to a room right now.
+
+**Decision.** Each person has a **presence** of online, away, do not disturb or invisible, with an optional
+status line and one emoji. It lives in OpenVibe.Network (`network.presence@1`), not in Chat's WebSocket map:
+the person carries it across every site, and a DM list must show it when the person is not in that room.
+- A heartbeat from any open tab (`POST /api/v1/me/presence/heartbeat`, at most once a minute) keeps it fresh.
+ Online while the newest heartbeat is under 2 minutes old; away after 10 minutes without input (the client
+ says `idle: true`); offline after 3 minutes without a heartbeat.
+- `PUT /api/v1/me/presence` sets the chosen state (`auto`, `dnd`, `invisible`), the status line, the emoji and
+ the "Show my status" switch. Invisible and the switch off read as offline to everyone.
+- `GET /api/v1/presence?subjects=…` answers only for people the asker follows or has a DM with (and
+ themselves), up to 100 subjects, and never for an invisible person. Last seen is never returned.
+- `network.presence.changed` is written to Network's outbox on a real change, at most once per person per
+ 30 seconds, with the public view only (invisible is offline). It is an administrative event for first-party
+ consumers, not a per-join realtime topic: no join or leave is ever published, and the event carries no
+ status text or emoji for someone who hid them.
+- Chat keeps its per-room connection map for "who is in this room", and draws the person's dot from Network's
+ presence (cached 30 seconds), never the reverse. DND mutes Chat's own notification pings.
+
+**Alternatives considered.**
+- Keeping presence in Chat only: rejected. It cannot follow a person across sites or exist while they are
+ not connected to a chat socket, and the owner asked for a status that follows them everywhere.
+- A per-join/per-leave event topic: still rejected (amendment 1); this amendment adds only a debounced state
+ change, never connection churn.
+- Storing last seen and returning it: rejected. The owner's privacy list says last seen is never shown.
+
+**Consequences.** Network stores one row per person who has ever sent a heartbeat (erased with the account and
+included in its export, ADR-033). Services read the API with a person's own token, or with a service token and
+`network.presence.read` on behalf of one person; the DM part of the rule is read from Chat through
+`chat.dm.peers.read`. A stranger's presence is indistinguishable from an offline one.
+
+**Acceptance.** A stranger gets no row; a follower gets online/away/dnd; an invisible person gets no row and
+the event says offline; a heartbeat older than 3 minutes reads offline; an idle heartbeat does not move
+`last_active_at`; the event is debounced. These are Network `test/presence.test.js` and Chat
+`test/presence.test.js`.
@@ -8,7 +8,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -8,7 +8,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-002](ADR-002-contract-repository.md) | Contract repository and compatibility policy | Accepted, implemented 2026-09-22 (openvibe-contracts v0.1.0+) |
| [ADR-002](ADR-002-contract-repository.md) | Contract repository and compatibility policy | Accepted, implemented 2026-09-22 (openvibe-contracts v0.1.0+) |
| [ADR-003](ADR-003-service-principals.md) | Service/app principal authentication and capability grants | Accepted, implemented 2026-09-22; amended 2026-09-24 (delegated authorization and approval UX), 2026-10-12 (project delegation) |
| [ADR-003](ADR-003-service-principals.md) | Service/app principal authentication and capability grants | Accepted, implemented 2026-09-22; amended 2026-09-24 (delegated authorization and approval UX), 2026-10-12 (project delegation) |
| [ADR-004](ADR-004-durable-events.md) | Durable event store, outbox/inbox and delivery semantics | Accepted 2026-09-22; implementation in progress (OpenVibe.Events) |
| [ADR-004](ADR-004-durable-events.md) | Durable event store, outbox/inbox and delivery semantics | Accepted 2026-09-22; implementation in progress (OpenVibe.Events) |
-| [ADR-005](ADR-005-realtime.md) | Realtime topics, cursor/resume and whether OpenVibe.Realtime exists | Accepted 2026-09-22: realtime runs inside OpenVibe.Events; amendment 1 (2026-09-26): presence is ephemeral, in Chat's delivery plane; amendment 2 (2026-09-26): a person's topic is `network.notification.*` with subject visibility, and browsers authenticate with two-minute realtime tickets |
+| [ADR-005](ADR-005-realtime.md) | Realtime topics, cursor/resume and whether OpenVibe.Realtime exists | Accepted 2026-09-22: realtime runs inside OpenVibe.Events; amendment 1 (2026-09-26): presence is ephemeral, in Chat's delivery plane; amendment 2 (2026-09-26): a person's topic is `network.notification.*` with subject visibility, and browsers authenticate with two-minute realtime tickets; amendment 3 (2026-11-07): a person's chosen online status is `network.presence@1` in Network, with a debounced `network.presence.changed` |
| [ADR-006](ADR-006-media-objects.md) | Media object, location and namespace model | Accepted 2026-09-22; implementation in progress (OpenVibe.Media Wave 4); amended 2026-09-24 (placement classes, local provider, infrequent access deferred) |
| [ADR-006](ADR-006-media-objects.md) | Media object, location and namespace model | Accepted 2026-09-22; implementation in progress (OpenVibe.Media Wave 4); amended 2026-09-24 (placement classes, local provider, infrequent access deferred) |
| [ADR-007](ADR-007-data-ownership.md) | Logical data ownership and database technology | Accepted 2026-09-22; amended 2026-09-24 (PgBouncer, replicas, SERIALIZABLE money paths, Redis non-authoritative) |
| [ADR-007](ADR-007-data-ownership.md) | Logical data ownership and database technology | Accepted 2026-09-22; amended 2026-09-24 (PgBouncer, replicas, SERIALIZABLE money paths, Redis non-authoritative) |
| [ADR-008](ADR-008-shared-packages.md) | Shared package publication and release manifests | Accepted 2026-09-22; implementation in progress (OpenVibe.Shared) |
| [ADR-008](ADR-008-shared-packages.md) | Shared package publication and release manifests | Accepted 2026-09-22; implementation in progress (OpenVibe.Shared) |
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": ["bob"] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "peers": ["usr_01JAB2C3D4E5F6G7H8J9K0MNPR"] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "presence": [{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "presence": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "presence": [{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "emoji": "not-an-emoji-at-all" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "state": "auto", "last_seen": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "state": "online" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "status_text": null, "emoji": null, "show_status": false }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "state": "dnd" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "status_text": "Out for lunch", "emoji": "🍜" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "invisible", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "offline", "status_text": null, "emoji": null, "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "ann", "state": "online", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "last_seen": "2026-11-07T09:00:00.000Z", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "This status line is deliberately longer than eighty characters so the schema refuses it without any doubt at all.", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "busy", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "away", "status_text": null, "emoji": null, "live": true, "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "dnd", "status_text": "Heads down", "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "offline", "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "state": "online", "status_text": "Building presence", "emoji": "🟢", "live": false, "updated_at": "2026-11-07T09:00:00.000Z" }
@@ -107814,6 +107814,226 @@
@@ -107814,6 +107814,226 @@
"$ref": "menu-card.v1.json"
"$ref": "menu-card.v1.json"
}
}
}
}
+ },
+ "network.presence": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence.v1.json",
+ "title": "NetworkPresence",
+ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "state",
+ "updated_at"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person's canonical subject."
+ },
+ "state": {
+ "enum": [
+ "online",
+ "away",
+ "dnd",
+ "invisible",
+ "offline"
+ ],
+ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."
+ },
+ "status_text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."
+ },
+ "emoji": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 16,
+ "description": "One emoji that sits beside the status line, or null."
+ },
+ "live": {
+ "type": "boolean",
+ "default": false,
+ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the person's presence row last changed."
+ }
+ }
+ }
+ },
+ "network.presence-update-request": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence-update-request.v1.json",
+ "title": "NetworkPresenceUpdateRequest",
+ "description": "network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.",
+ "type": "object",
+ "additionalProperties": false,
+ "minProperties": 1,
+ "properties": {
+ "state": {
+ "enum": [
+ "auto",
+ "dnd",
+ "invisible"
+ ],
+ "description": "The chosen state. auto derives online or away from the newest heartbeat and the last input."
+ },
+ "status_text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "The status line; null clears it. Markup and control characters are stripped."
+ },
+ "emoji": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 16,
+ "description": "One emoji, or null to clear it. More than one grapheme cluster is refused."
+ },
+ "show_status": {
+ "type": "boolean",
+ "description": "The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible."
+ }
+ }
+ }
+ },
+ "network.presence.changed": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.presence.changed.v1.json",
+ "title": "NetworkPresenceChangedPayload",
+ "description": "network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_…> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "state",
+ "updated_at"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "state": {
+ "enum": [
+ "online",
+ "away",
+ "dnd",
+ "offline"
+ ],
+ "description": "The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji."
+ },
+ "status_text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80
+ },
+ "emoji": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 16
+ },
+ "live": {
+ "type": "boolean",
+ "default": false,
+ "description": "Streaming on OpenVibe.Live now; false while invisible or with show_status off."
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.presence-list-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/presence-list-result.v1.json",
+ "title": "NetworkPresenceListResult",
+ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "presence"
+ ],
+ "properties": {
+ "presence": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "$ref": "presence.v1.json"
+ },
+ "description": "The visible people, in no particular order."
+ }
+ }
+ }
+ },
+ "chat.dm-peers-result": {
+ "version": "1.0.0",
+ "owner": "chat",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/dm-peers-result.v1.json",
+ "title": "ChatDmPeersResult",
+ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "peers"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose conversations were read."
+ },
+ "peers": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "The candidates that share at least one conversation with them, as subjects."
+ }
+ }
+ }
}
}
}
}
}
}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Actor API",
"title": "OpenVibe.Actor API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bench API",
"title": "OpenVibe.Bench API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3201,6 +3201,52 @@
@@ -3201,6 +3201,52 @@
}
}
}
}
},
},
+ "/internal/chat/dm/peers": {
+ "post": {
+ "operationId": "post_internal_chat_dm_peers",
+ "summary": "Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat.",
+ "description": "**chat.dm.peers.read** (first-party): Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat. It answers with subjects only (never a message, a conversation name or another participant), for OpenVibe.Network's presence privacy rule: presence is visible to people someone follows or has a DM with, never to strangers. First-party only; never grantable to an app or an agent.",
+ "tags": [
+ "chat.dm"
+ ],
+ "x-openvibe-capabilities": [
+ "chat.dm.peers.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "chat.dm.peers.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/chat.dm-peers-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/chat/events": {
"/internal/chat/events": {
"post": {
"post": {
"operationId": "post_internal_chat_events",
"operationId": "post_internal_chat_events",
@@ -6492,6 +6538,32 @@
@@ -6492,6 +6538,32 @@
}
}
}
}
},
},
+ "chat.dm-peers-result.v1": {
+ "title": "ChatDmPeersResult",
+ "description": "chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "peers"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose conversations were read."
+ },
+ "peers": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "The candidates that share at least one conversation with them, as subjects."
+ }
+ }
+ },
"chat.event-request.v1": {
"chat.event-request.v1": {
"title": "ChatEventRequest",
"title": "ChatEventRequest",
"description": "chat.event-request@1: what a first-party service hands OpenVibe.Chat's POST /internal/chat/events (chat.event.publish) to push a transient frame to chat sockets: cards, alerts, sounds, media queue state, call invites. Nothing is stored; Chat broadcasts the frame to the target. Applied once per (caller, key): a retry with the same key and body answers the first result, the same key with another body gets 409. Chat also refuses a frame whose JSON is longer than 16384 characters or that has a __proto__, constructor or prototype key; a news card's timestamp must parse as a date.",
"description": "chat.event-request@1: what a first-party service hands OpenVibe.Chat's POST /internal/chat/events (chat.event.publish) to push a transient frame to chat sockets: cards, alerts, sounds, media queue state, call invites. Nothing is stored; Chat broadcasts the frame to the target. Applied once per (caller, key): a retry with the same key and body answers the first result, the same key with another body gets 409. Chat also refuses a frame whose JSON is longer than 16384 characters or that has a __proto__, constructor or prototype key; a news card's timestamp must parse as a date.",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe Connect API",
"title": "OpenVibe Connect API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Design API",
"title": "OpenVibe.Design API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Domains API",
"title": "OpenVibe.Domains API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Food API",
"title": "OpenVibe.Food API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -47,8 +47,8 @@
@@ -47,8 +47,8 @@
"service": "chat",
"service": "chat",
"name": "OpenVibe.Chat",
"name": "OpenVibe.Chat",
"origin": "https://openvibe.chat",
"origin": "https://openvibe.chat",
- "operations": 66,
- "capabilities": 25
+ "operations": 67,
+ "capabilities": 26
},
},
{
{
"service": "codes",
"service": "codes",
@@ -159,8 +159,8 @@
@@ -159,8 +159,8 @@
"service": "network",
"service": "network",
"name": "OpenVibe.Network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
"origin": "https://openvibe.network",
- "operations": 62,
- "capabilities": 35
+ "operations": 63,
+ "capabilities": 36
},
},
{
{
"service": "news",
"service": "news",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Inventory API",
"title": "OpenVibe.Inventory API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.MediaHub API",
"title": "OpenVibe.MediaHub API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3503,6 +3503,52 @@
@@ -3503,6 +3503,52 @@
}
}
}
}
},
},
+ "/internal/presence?for=usr_…&subjects=usr_…,usr_…": {
+ "get": {
+ "operationId": "get_internal_presence_for_usr_subjects_usr_usr",
+ "summary": "Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live.",
+ "description": "**network.presence.read** (first-party): Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live. It answers only for people that person follows or has a DM with (and themselves); invisible, show_status off and strangers are never disclosed, and last seen is never returned. First-party services only (Chat draws the dots in its DMs and member lists); never grantable to an app or an agent.",
+ "tags": [
+ "network.presence"
+ ],
+ "x-openvibe-capabilities": [
+ "network.presence.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "presence-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.presence.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.presence-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/projects/{project_id}": {
"/internal/projects/{project_id}": {
"get": {
"get": {
"operationId": "get_internal_projects_project_id",
"operationId": "get_internal_projects_project_id",
@@ -7297,6 +7343,79 @@
@@ -7297,6 +7343,79 @@
}
}
}
}
},
},
+ "network.presence-list-result.v1": {
+ "title": "NetworkPresenceListResult",
+ "description": "network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "presence"
+ ],
+ "properties": {
+ "presence": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "$ref": "#/components/schemas/network.presence.v1"
+ },
+ "description": "The visible people, in no particular order."
+ }
+ }
+ },
+ "network.presence.v1": {
+ "title": "NetworkPresence",
+ "description": "network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "subject",
+ "state",
+ "updated_at"
+ ],
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person's canonical subject."
+ },
+ "state": {
+ "enum": [
+ "online",
+ "away",
+ "dnd",
+ "invisible",
+ "offline"
+ ],
+ "description": "The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible."
+ },
+ "status_text": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "The person's status line in their own words, or null. Never shown while they are invisible or have show_status off."
+ },
+ "emoji": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 16,
+ "description": "One emoji that sits beside the status line, or null."
+ },
+ "live": {
+ "type": "boolean",
+ "default": false,
+ "description": "The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off."
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the person's presence row last changed."
+ }
+ }
+ },
"network.project-member-result.v1": {
"network.project-member-result.v1": {
"title": "NetworkProjectMemberResult",
"title": "NetworkProjectMemberResult",
"description": "network.project-member-result@1: OpenVibe.Network's answer to GET /internal/projects/:project_id/members/:subject (network.project.member.read). It says whether one person belongs to a developer project, with which role, and whether the project is archived, so a receiver can check a project delegation (ADR-003 amendment 2026-10-12: the person must be an admin or owner of an unarchived project). Only members are answered; a non-member and an unknown project are both 404 project.member_not_found. It carries no other members, no apps and no secrets.",
"description": "network.project-member-result@1: OpenVibe.Network's answer to GET /internal/projects/:project_id/members/:subject (network.project.member.read). It says whether one person belongs to a developer project, with which role, and whether the project is archived, so a receiver can check a project delegation (ADR-003 amendment 2026-10-12: the person must be an admin or owner of an unarchived project). Only members are answered; a non-member and an unknown project are both 404 project.member_not_found. It carries no other members, no apps and no secrets.",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRestream API",
"title": "OpenRestream API",
- "version": "0.317.0",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Play API",
"title": "OpenVibe.Play API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Quest API",
"title": "OpenVibe.Quest API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Report API",
"title": "OpenVibe.Report API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Run API",
"title": "OpenVibe.Run API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Services API",
"title": "OpenVibe.Services API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Today API",
"title": "OpenVibe.Today API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Vision API",
"title": "OpenVibe.Vision API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Watch API",
"title": "OpenVibe.Watch API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Work API",
"title": "OpenVibe.Work API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Zone API",
"title": "OpenVibe.Zone API",
- "version": "0.317.0",
- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.317.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.318.0",
+ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.318.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -298693,3 +298693,106 @@ export interface FrameMenuCard {
@@ -298693,3 +298693,106 @@ export interface FrameMenuCard {
*/
*/
export type FrameMenuCards =
export type FrameMenuCards =
[] | [FrameMenuCard] | [FrameMenuCard, FrameMenuCard] | [FrameMenuCard, FrameMenuCard, FrameMenuCard];
[] | [FrameMenuCard] | [FrameMenuCard, FrameMenuCard] | [FrameMenuCard, FrameMenuCard, FrameMenuCard];
+
+/** [email protected] (owner: network) */
+/**
+ * network.presence@1 (plan 2026-10-22 track X item 9): one person's online status as another person may see it. OpenVibe.Network owns presence: POST /api/v1/me/presence/heartbeat keeps it fresh, PUT /api/v1/me/presence sets the chosen state, status line and emoji, and GET /api/v1/presence?subjects=… answers for the people the asker follows or has a DM with. The state is derived: online while the newest heartbeat is under 2 minutes old, away after 10 minutes without input (the client's heartbeat says idle), offline after 3 minutes without a heartbeat. Invisible and show_status off are seen as offline, and an invisible person is left out of the answer entirely; last seen is never returned. live is true while the person is streaming on OpenVibe.Live, and only for someone who would otherwise be visible. (The event is network.presence.changed@1.)
+ */
+export interface NetworkPresence {
+ /**
+ * The person's canonical subject.
+ */
+ subject: string;
+ /**
+ * The public state. A reader never receives invisible (the person is offline instead, or absent from a list); the stored chosen state can be auto, dnd or invisible.
+ */
+ state: "online" | "away" | "dnd" | "invisible" | "offline";
+ /**
+ * The person's status line in their own words, or null. Never shown while they are invisible or have show_status off.
+ */
+ status_text?: string | null;
+ /**
+ * One emoji that sits beside the status line, or null.
+ */
+ emoji?: string | null;
+ /**
+ * The person is streaming on OpenVibe.Live now. Live outranks their chosen state; it is false while they are invisible or have show_status off.
+ */
+ live?: boolean;
+ /**
+ * When the person's presence row last changed.
+ */
+ updated_at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.presence-update-request@1: the body of PUT /api/v1/me/presence on OpenVibe.Network. Every field is optional; only the fields sent change. state is the person's chosen state (auto: online or away from heartbeats, dnd, invisible); status_text and emoji are theirs; show_status is the 'Show my status' switch. An empty body is refused.
+ */
+export interface NetworkPresenceUpdateRequest {
+ /**
+ * The chosen state. auto derives online or away from the newest heartbeat and the last input.
+ */
+ state?: "auto" | "dnd" | "invisible";
+ /**
+ * The status line; null clears it. Markup and control characters are stripped.
+ */
+ status_text?: string | null;
+ /**
+ * One emoji, or null to clear it. More than one grapheme cluster is refused.
+ */
+ emoji?: string | null;
+ /**
+ * The 'Show my status' switch (default on). Off reads as offline to everyone, like invisible.
+ */
+ show_status?: boolean;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.presence.changed v1 (OpenVibe.Network server/presence; plan 2026-10-22 track X item 9). A person's public presence changed: their chosen state, status line, emoji, the show_status switch, or the derived online/away/offline state. OpenVibe.Network writes it to its outbox beside the presence row, at most once per person per 30 seconds (the debounce), and only for a real change. The payload is the public view in network.presence@1: invisible and show_status off are sent as offline with no status text or emoji, so the event never carries what the person hid. Envelope: subject { type: user, id: <usr_…> }, visibility internal, actor the person. A presence change is not a reason to notify anyone.
+ */
+export interface NetworkPresenceChangedPayload {
+ subject: string;
+ /**
+ * The public state. Never invisible: an invisible person is sent as offline, without their status text or emoji.
+ */
+ state: "online" | "away" | "dnd" | "offline";
+ status_text?: string | null;
+ emoji?: string | null;
+ /**
+ * Streaming on OpenVibe.Live now; false while invisible or with show_status off.
+ */
+ live?: boolean;
+ updated_at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.presence-list-result@1: the public presence of the people one person may see, from GET /api/v1/presence?subjects=… (the person's own token) or GET /internal/presence?for=…&subjects=… (a first-party service with network.presence.read). Only people the asker follows or has a DM with, and themselves, are answered; a stranger and an invisible person are left out (the caller reads them as offline), never as an error. At most 100 subjects per call.
+ */
+export interface NetworkPresenceListResult {
+ /**
+ * The visible people, in no particular order.
+ *
+ * @maxItems 100
+ */
+ presence: NetworkPresence[];
+}
+
+/** [email protected] (owner: chat) */
+/**
+ * chat.dm-peers-result@1: which of the candidate people share a conversation with one person on OpenVibe.Chat (POST /internal/chat/dm/peers, chat.dm.peers.read). It answers with subjects only; a candidate with no shared conversation is simply absent. Used by OpenVibe.Network's presence privacy rule.
+ */
+export interface ChatDmPeersResult {
+ /**
+ * The person whose conversations were read.
+ */
+ subject: string;
+ /**
+ * The candidates that share at least one conversation with them, as subjects.
+ *
+ * @maxItems 100
+ */
+ peers: string[];
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "chat.dm.peers.read",
+ "version": "1.0",
+ "owner": "chat",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read which of the candidate people share a direct or group conversation with one person on OpenVibe.Chat. It answers with subjects only (never a message, a conversation name or another participant), for OpenVibe.Network's presence privacy rule: presence is visible to people someone follows or has a DM with, never to strangers. First-party only; never grantable to an app or an agent.",
+ "permissions": [
+ "chat:dm-peers:read"
+ ],
+ "resourceConstraints": [],
+ "quotaClass": "internal-read",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/chat/dm/peers"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "chat.dm-peers-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.presence.read",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read the public presence of people on behalf of one person: the online/away/dnd/offline state, status line and emoji, and whether they are streaming on OpenVibe.Live. It answers only for people that person follows or has a DM with (and themselves); invisible, show_status off and strangers are never disclosed, and last seen is never returned. First-party services only (Chat draws the dots in its DMs and member lists); never grantable to an app or an agent.",
+ "permissions": [
+ "presence:read"
+ ],
+ "resourceConstraints": [],
+ "quotaClass": "presence-read",
+ "events": [
+ "network.presence.changed"
+ ],
+ "implementedBy": [
+ "GET /internal/presence?for=usr_\u2026&subjects=usr_\u2026,usr_\u2026"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "network.presence-list-result@1"
+}
@@ -38,7 +38,8 @@
@@ -38,7 +38,8 @@
"chat.ticket.write",
"chat.ticket.write",
"chat.tts.speak",
"chat.tts.speak",
"chat.vibe.ask",
"chat.vibe.ask",
- "chat.vibe.deliver"
+ "chat.vibe.deliver",
+ "chat.dm.peers.read"
],
],
"eventsProduced": [
"eventsProduced": [
"chat.message.created",
"chat.message.created",
@@ -94,7 +95,7 @@
@@ -94,7 +95,7 @@
},
},
{
{
"kind": "consumer",
"kind": "consumer",
- "what": "OpenVibe.Events redelivers the events of Chat's subscriptions (network.block.changed, network.user.token_valid_after, live.release.deployed, \u2026) that were pending during the stop (at least once, deduped by event_id)"
+ "what": "OpenVibe.Events redelivers the events of Chat's subscriptions (network.block.changed, network.user.token_valid_after, live.release.deployed, …) that were pending during the stop (at least once, deduped by event_id)"
}
}
]
]
},
},
@@ -105,7 +106,7 @@
@@ -105,7 +106,7 @@
],
],
"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
"blockers": {
- "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
+ "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
}
},
},
"contracts": {
"contracts": {
@@ -83,7 +83,8 @@
@@ -83,7 +83,8 @@
"network.consent.read",
"network.consent.read",
"network.consent.manage",
"network.consent.manage",
"network.machines.read",
"network.machines.read",
- "network.machines.manage"
+ "network.machines.manage",
+ "network.presence.read"
],
],
"eventsProduced": [
"eventsProduced": [
"network.account.deleted",
"network.account.deleted",
@@ -115,7 +116,8 @@
@@ -115,7 +116,8 @@
"share.revoked",
"share.revoked",
"share.transfer.requested",
"share.transfer.requested",
"share.transfer.accepted",
"share.transfer.accepted",
- "share.transfer.declined"
+ "share.transfer.declined",
+ "network.presence.changed"
],
],
"eventsConsumed": [
"eventsConsumed": [
"billing.staff.action",
"billing.staff.action",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.317.0",
+ "version": "0.318.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -1,5 +1,6 @@
@@ -1,5 +1,6 @@
'use strict';
'use strict';
-// ADR-005 amendment 1: people's presence is ephemeral, in Chat's delivery plane.
+// ADR-005 amendments 1 and 3: rooms stay ephemeral in Chat's delivery plane; a person's chosen online
+// status is Network's `network.presence@1`, announced only as the debounced `network.presence.changed`.
// ADR-043 records robot connectivity in Events; ADR-057 records Connect device connectivity;
// ADR-043 records robot connectivity in Events; ADR-057 records Connect device connectivity;
// neither is a person's presence.
// neither is a person's presence.
// The machines hub (plan L34) records the same for a person's machines: a machine coming online is the
// The machines hub (plan L34) records the same for a person's machines: a machine coming online is the
@@ -12,6 +13,14 @@ const deviceConnectivity = new Set([
@@ -12,6 +13,14 @@ const deviceConnectivity = new Set([
'bot.robot.online', 'bot.robot.offline',
'bot.robot.online', 'bot.robot.offline',
'network.machine.online', 'network.machine.offline',
'network.machine.online', 'network.machine.offline',
]);
]);
-const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t));
-assert.ok(presenceEvents.length === 0, `no person presence event type: ${presenceEvents}`);
+const personPresence = new Set(['network.presence.changed']);
+const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t) && !personPresence.has(t));
+assert.ok(presenceEvents.length === 0, `no other person presence event type: ${presenceEvents}`);
+// The one allowed person-presence event is Network's, and it is in the contract catalog (amendment 3).
+const network = c.services.manifests.find((m) => m.id === 'network');
+assert.ok(network.eventsProduced.includes('network.presence.changed'), 'Network announces its own presence');
+assert.ok(c.schema('network.presence.changed@1'), 'network.presence.changed@1 is a contract');
+// No per-join or per-leave topic: the event's payload has no connection vocabulary.
+const payload = c.schema('network.presence.changed@1');
+assert.ok(!Object.keys(payload.properties).some((k) => /join|leave|connect/i.test(k)), 'no connection churn in the payload');
console.log('presence policy: all checks passed');
console.log('presence policy: all checks passed');