openvibers/openvibe.contracts Commits

v0.49.0: platform blocks (WS-E task 5) — network.block.changed (blocker, blocked, active, per-pair revision; consumed by Chat and Community) and the network.blocks.read capability (GET /internal/blocks)

OP OpenVibers committed 25 Sept 2026 · c9540c84af · 14 files, +121 −3


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex ae085726..fbdd262c 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,10 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.49.0 — 2026-09-25
++
++Platform blocks (roadmap WS-E task 5). **`network.block.changed`**: a person blocked or unblocked someone (`blocker`, `blocked`, `active`, a per-pair `revision`); Network keeps the blocks, keyed by subjects, and Chat and Community consume the event. New capability **`network.blocks.read`** (first-party, never for apps): `GET /internal/blocks?subject=` lists who a person blocked and who blocked them. Fixtures for a block and an unblock. Additive.
++
## 0.48.0 — 2026-09-25
**Correction to 0.47.0.** `network.grant.changed` was already emitted by Network's developer event relay for a developer app's grant, and OpenVibe.Events acts on it (it stops an app's subscriptions when its `events.app.subscribe` grant leaves approved). 0.47.0 gave that event a service-principal payload that does not match. Now `network.grant.changed` describes what is actually sent: `{ project_id, capability, audience, from, to }`, subject `app`. A first-party service principal's grant change is the new **`network.principal_grant.changed`** (the 0.47.0 payload under its own name). Nothing consumed the 0.47.0 shape. The compat gate records this in `compatibility/corrections.json` (accepted against v0.47.0 only).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex e1e4e413..b163f3f0 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -945,6 +945,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "network.block.changed",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "events/payloads/network.block.changed.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "network.user.updated",
"version": "1.0.0",
ddiff --git a/contracts/events/payloads/network.block.changed.v1.json b/contracts/events/payloads/network.block.changed.v1.json
nnew file mode 100644
iindex 00000000..ab1b15cd
---- /dev/null
++++ b/contracts/events/payloads/network.block.changed.v1.json
@@@ -0,0 +1,16 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.block.changed.v1.json",
++ "title": "NetworkBlockChangedPayload",
++ "description": "network.block.changed v1 (OpenVibe.Network; roadmap WS-E task 5). A person blocked or unblocked someone. Blocks are the network's: kept by Network, keyed by subjects, and honoured by every product — Chat (no DM, no mention notification from someone who blocked you), Community (no reply to their threads or comments) and notifications (none from a person the recipient blocked). Written to Network's outbox in the transaction that changes the block. Consumers keep a projection and apply the latest `revision` per (blocker, blocked); a service may also read the current list at GET /internal/blocks (network.blocks.read). Envelope: subject { type: user, id: <blocker> }, visibility internal, actor the blocker.",
++ "type": "object",
++ "required": ["blocker", "blocked", "active", "revision"],
++ "additionalProperties": false,
++ "properties": {
++ "blocker": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "Who blocked." },
++ "blocked": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "Who is blocked." },
++ "active": { "type": "boolean", "description": "true: blocked; false: unblocked." },
++ "revision": { "type": "integer", "minimum": 1, "maximum": 9007199254740991, "description": "Grows with every change of this pair; a consumer ignores an older one." },
++ "at": { "type": "string", "format": "date-time" }
++ }
++}
ddiff --git a/fixtures/network.block.changed/invalid/not-a-subject.json b/fixtures/network.block.changed/invalid/not-a-subject.json
nnew file mode 100644
iindex 00000000..849c775b
---- /dev/null
++++ b/fixtures/network.block.changed/invalid/not-a-subject.json
@@@ -0,0 +1 @@
++{ "blocker": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "blocked": "bob", "active": true, "revision": 1 }
ddiff --git a/fixtures/network.block.changed/valid/block.json b/fixtures/network.block.changed/valid/block.json
nnew file mode 100644
iindex 00000000..5bc93bb4
---- /dev/null
++++ b/fixtures/network.block.changed/valid/block.json
@@@ -0,0 +1 @@
++{ "blocker": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "blocked": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "active": true, "revision": 1, "at": "2026-09-25T22:00:00.000Z" }
ddiff --git a/fixtures/network.block.changed/valid/unblock.json b/fixtures/network.block.changed/valid/unblock.json
nnew file mode 100644
iindex 00000000..2d483373
---- /dev/null
++++ b/fixtures/network.block.changed/valid/unblock.json
@@@ -0,0 +1 @@
++{ "blocker": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "blocked": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "active": false, "revision": 2 }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex c25343be..6d1c7e5f 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -10456,6 +10456,51 @@
}
}
},
++ "network.block.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.block.changed.v1.json",
++ "title": "NetworkBlockChangedPayload",
++ "description": "network.block.changed v1 (OpenVibe.Network; roadmap WS-E task 5). A person blocked or unblocked someone. Blocks are the network's: kept by Network, keyed by subjects, and honoured by every product — Chat (no DM, no mention notification from someone who blocked you), Community (no reply to their threads or comments) and notifications (none from a person the recipient blocked). Written to Network's outbox in the transaction that changes the block. Consumers keep a projection and apply the latest `revision` per (blocker, blocked); a service may also read the current list at GET /internal/blocks (network.blocks.read). Envelope: subject { type: user, id: <blocker> }, visibility internal, actor the blocker.",
++ "type": "object",
++ "required": [
++ "blocker",
++ "blocked",
++ "active",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "blocker": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Who blocked."
++ },
++ "blocked": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Who is blocked."
++ },
++ "active": {
++ "type": "boolean",
++ "description": "true: blocked; false: unblocked."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Grows with every change of this pair; a consumer ignores an older one."
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
"network.user.updated": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex b388f68d..95b600ec 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -9503,6 +9503,30 @@ export interface NetworkPrincipalGrantChangedPayload {
actor_subject?: string | null;
}
++/** [email protected] (owner: network) */
++/**
++ * network.block.changed v1 (OpenVibe.Network; roadmap WS-E task 5). A person blocked or unblocked someone. Blocks are the network's: kept by Network, keyed by subjects, and honoured by every product — Chat (no DM, no mention notification from someone who blocked you), Community (no reply to their threads or comments) and notifications (none from a person the recipient blocked). Written to Network's outbox in the transaction that changes the block. Consumers keep a projection and apply the latest `revision` per (blocker, blocked); a service may also read the current list at GET /internal/blocks (network.blocks.read). Envelope: subject { type: user, id: <blocker> }, visibility internal, actor the blocker.
++ */
++export interface NetworkBlockChangedPayload {
++ /**
++ * Who blocked.
++ */
++ blocker: string;
++ /**
++ * Who is blocked.
++ */
++ blocked: string;
++ /**
++ * true: blocked; false: unblocked.
++ */
++ active: boolean;
++ /**
++ * Grows with every change of this pair; a consumer ignores an older one.
++ */
++ revision: number;
++ at?: string;
++}
++
/** [email protected] (owner: network) */
/**
* network.user.updated v1 (OpenVibe.Network server/identity/profile-events.js; roadmap WS-B task 2). What other services may know about a person changed: their username, display name, picture, colour, role or ban. The payload is the person's whole current profile, not a diff, so a consumer keeps a projection by writing it over the one it has, and ignores one whose revision is not newer (events can arrive out of order). Emitted in the transaction of the change: a profile edit, a rename, a role grant or removal, a ban or unban. Consumers use it instead of their own copies: roles go both ways (a downgrade too), and a ban arrives even for someone who never comes back. Envelope: subject { type: user, id }, visibility internal, actor the person (their own edit) or the staff member.
ddiff --git a/manifests/capabilities/network.blocks.read.json b/manifests/capabilities/network.blocks.read.json
nnew file mode 100644
iindex 00000000..71515e97
---- /dev/null
++++ b/manifests/capabilities/network.blocks.read.json
@@@ -0,0 +1,13 @@
++{
++ "id": "network.blocks.read",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Read who a person has blocked and who has blocked them (subjects only), as an owning service honouring platform blocks: Chat for DMs and mentions, Community for replies, notification senders. Never grantable to apps.",
++ "permissions": ["blocks:read"],
++ "resourceConstraints": [],
++ "quotaClass": "blocks-read",
++ "events": ["network.block.changed"],
++ "implementedBy": ["GET /internal/blocks?subject=usr_…"]
++}
ddiff --git a/manifests/services/chat.json b/manifests/services/chat.json
iindex 0e416543..cd8413f0 100644
---- a/manifests/services/chat.json
++++ b/manifests/services/chat.json
@@@ -23,6 +23,7 @@
"chat.moderation.action"
],
"eventsConsumed": [
++ "network.block.changed",
"network.user.token_valid_after"
],
"namespacesOwned": [
ddiff --git a/manifests/services/community.json b/manifests/services/community.json
iindex a2ff532d..8733f618 100644
---- a/manifests/services/community.json
++++ b/manifests/services/community.json
@@@ -38,6 +38,7 @@
"community.thread.created"
],
"eventsConsumed": [
++ "network.block.changed",
"network.user.token_valid_after"
],
"namespacesOwned": [
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex 77601cf1..9a5e52b5 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -34,6 +34,7 @@
"capabilities": [
"identity.subject.resolve",
"mods.grant.manage",
++ "network.blocks.read",
"network.coins.credit",
"network.coins.debit",
"network.coins.transfer",
@@@ -48,6 +49,7 @@
"eventsProduced": [
"network.app.created",
"network.app.revoked",
++ "network.block.changed",
"network.credential.revoked",
"network.credential.rotated",
"network.grant.changed",
ddiff --git a/package-lock.json b/package-lock.json
iindex 869d8572..fa03876c 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.48.0",
++ "version": "0.49.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.48.0",
++ "version": "0.49.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex ec90470a..5c1a0758 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.48.0",
++ "version": "0.49.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",