openvibers/openvibe.contracts Commits

v0.47.0: network.grant.changed payload (a service grant given, changed, revoked or expired, with actor and reason; WS-D task 3) and the network.staff.read capability (list the network's staff and their staff-map capabilities; WS-D task 4)

OP OpenVibers committed 25 Sept 2026 · 8453df651e · 12 files, +152 −4


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 3d54e316..a6c9d1bd 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.47.0 — 2026-09-25
++
++**`network.grant.changed`** gets its payload contract (roadmap WS-D task 3): a service principal's capability grant was given, changed (namespaces or expiry), revoked by the owner, or expired, with actor and reason. Network writes it beside an audit row in the transaction that changes the grant; the seeded defaults are not announced. New capability **`network.staff.read`** (active, first-party, never grantable to apps): list the network's staff with their roles and staff-map capabilities, `GET /api/v1/staff/moderators` (WS-D task 4). Fixtures for a grant and an expiry. Additive.
++
## 0.46.0 — 2026-09-25
**`live.moderation.action`** (ADR-022, roadmap WS-D task 1): a staff or channel-moderator action taken on OpenVibe.Live outside chat (site, global and IP bans, message deletes and purges from the admin panel, a stream force-ended, relay users hidden, channel moderators added or removed), with the same payload as `chat.moderation.action`. Events requires an event's prefix to match its source, so Live cannot send Chat's event for its own admin actions. OpenVibe.Network consumes it into the moderation audit log. The live manifest produces it and the network manifest consumes it. Fixtures cover a site ban and a force-ended stream. Additive.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex d001a369..59c88e20 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -927,6 +927,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "network.grant.changed",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "events/payloads/network.grant.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.grant.changed.v1.json b/contracts/events/payloads/network.grant.changed.v1.json
nnew file mode 100644
iindex 00000000..0bbd5751
---- /dev/null
++++ b/contracts/events/payloads/network.grant.changed.v1.json
@@@ -0,0 +1,19 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.grant.changed.v1.json",
++ "title": "NetworkGrantChangedPayload",
++ "description": "network.grant.changed v1 (OpenVibe.Network server/identity/grants-admin.js; roadmap WS-D task 3). A service principal's capability grant was given, changed (namespaces or expiry), revoked by the owner, or expired. The seeded default grants are not announced. Written to Network's outbox in the transaction that changes principal_grants, beside an audit row. A service's next token (at most 5 minutes later) carries the change. Envelope: subject { type: grant, id: <client_id>:<capability>@<audience> }, visibility internal, actor the owner who changed it (or the system for an expiry).",
++ "type": "object",
++ "required": ["client_id", "capability", "audience", "change"],
++ "additionalProperties": false,
++ "properties": {
++ "client_id": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,63}$", "description": "The service principal (oauth client id)." },
++ "capability": { "type": "string", "pattern": "^[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*$" },
++ "audience": { "type": "string", "pattern": "^openvibe\\.[a-z][a-z0-9_-]{0,63}$" },
++ "change": { "enum": ["granted", "updated", "revoked", "expired"] },
++ "namespaces": { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 200 } },
++ "expires_at": { "type": ["string", "null"], "format": "date-time" },
++ "reason": { "type": ["string", "null"], "maxLength": 500 },
++ "actor_subject": { "type": ["string", "null"], "description": "The owner who made the change; null for an expiry." }
++ }
++}
ddiff --git a/fixtures/network.grant.changed/invalid/bad-change.json b/fixtures/network.grant.changed/invalid/bad-change.json
nnew file mode 100644
iindex 00000000..0ee2af59
---- /dev/null
++++ b/fixtures/network.grant.changed/invalid/bad-change.json
@@@ -0,0 +1 @@
++{ "client_id": "live", "capability": "network.modules.write", "audience": "openvibe.network", "change": "deleted" }
ddiff --git a/fixtures/network.grant.changed/valid/expired.json b/fixtures/network.grant.changed/valid/expired.json
nnew file mode 100644
iindex 00000000..4916881a
---- /dev/null
++++ b/fixtures/network.grant.changed/valid/expired.json
@@@ -0,0 +1 @@
++{ "client_id": "tools", "capability": "media.object.write", "audience": "openvibe.media", "change": "expired", "namespaces": [], "expires_at": "2026-09-25T00:00:00.000Z", "reason": null, "actor_subject": null }
ddiff --git a/fixtures/network.grant.changed/valid/granted.json b/fixtures/network.grant.changed/valid/granted.json
nnew file mode 100644
iindex 00000000..0a487c14
---- /dev/null
++++ b/fixtures/network.grant.changed/valid/granted.json
@@@ -0,0 +1 @@
++{ "client_id": "live", "capability": "network.modules.write", "audience": "openvibe.network", "change": "granted", "namespaces": ["live.profile"], "expires_at": "2026-12-31T00:00:00.000Z", "reason": "profile summaries", "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 00b31f8a..b4d9fa92 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -10334,6 +10334,77 @@
}
}
},
++ "network.grant.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.grant.changed.v1.json",
++ "title": "NetworkGrantChangedPayload",
++ "description": "network.grant.changed v1 (OpenVibe.Network server/identity/grants-admin.js; roadmap WS-D task 3). A service principal's capability grant was given, changed (namespaces or expiry), revoked by the owner, or expired. The seeded default grants are not announced. Written to Network's outbox in the transaction that changes principal_grants, beside an audit row. A service's next token (at most 5 minutes later) carries the change. Envelope: subject { type: grant, id: <client_id>:<capability>@<audience> }, visibility internal, actor the owner who changed it (or the system for an expiry).",
++ "type": "object",
++ "required": [
++ "client_id",
++ "capability",
++ "audience",
++ "change"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "client_id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]{0,63}$",
++ "description": "The service principal (oauth client id)."
++ },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*$"
++ },
++ "audience": {
++ "type": "string",
++ "pattern": "^openvibe\\.[a-z][a-z0-9_-]{0,63}$"
++ },
++ "change": {
++ "enum": [
++ "granted",
++ "updated",
++ "revoked",
++ "expired"
++ ]
++ },
++ "namespaces": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "type": "string",
++ "maxLength": 200
++ }
++ },
++ "expires_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "actor_subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The owner who made the change; null for an expiry."
++ }
++ }
++ }
++ },
"network.user.updated": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 1ba257fd..ebb0df30 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -9467,6 +9467,30 @@ export interface NetworkModuleUpdatedPayload {
};
}
++/** [email protected] (owner: network) */
++/**
++ * network.grant.changed v1 (OpenVibe.Network server/identity/grants-admin.js; roadmap WS-D task 3). A service principal's capability grant was given, changed (namespaces or expiry), revoked by the owner, or expired. The seeded default grants are not announced. Written to Network's outbox in the transaction that changes principal_grants, beside an audit row. A service's next token (at most 5 minutes later) carries the change. Envelope: subject { type: grant, id: <client_id>:<capability>@<audience> }, visibility internal, actor the owner who changed it (or the system for an expiry).
++ */
++export interface NetworkGrantChangedPayload {
++ /**
++ * The service principal (oauth client id).
++ */
++ client_id: string;
++ capability: string;
++ audience: string;
++ change: "granted" | "updated" | "revoked" | "expired";
++ /**
++ * @maxItems 100
++ */
++ namespaces?: string[];
++ expires_at?: string | null;
++ reason?: string | null;
++ /**
++ * The owner who made the change; null for an expiry.
++ */
++ actor_subject?: string | null;
++}
++
/** [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.staff.read.json b/manifests/capabilities/network.staff.read.json
nnew file mode 100644
iindex 00000000..d911e56d
---- /dev/null
++++ b/manifests/capabilities/network.staff.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "network.staff.read",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "List the network's staff (global moderators, admins and the owner) with their roles and staff capabilities from the staff map, as an owning service: to show who moderates, or to route a report. Never grantable to apps.",
++ "permissions": [
++ "staff:read"
++ ],
++ "resourceConstraints": [],
++ "quotaClass": "staff-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/staff/moderators"
++ ]
++}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex f43e143f..3b664f3c 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -42,7 +42,8 @@
"network.modules.write",
"network.notifications.push",
"network.project.manage",
-- "network.project.read"
++ "network.project.read",
++ "network.staff.read"
],
"eventsProduced": [
"network.app.created",
ddiff --git a/package-lock.json b/package-lock.json
iindex e33b1981..c83d3d0e 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.46.0",
++ "version": "0.47.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.46.0",
++ "version": "0.47.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 77bcac88..bbca34b8 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.46.0",
++ "version": "0.47.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",