openvibers/openvibe.contracts Commits

v0.65.0: the follow graph on Network (roadmap WS-E task 4, ADR-030). Adds the events network.follow.created and network.follow.deleted (follower, target_type channel, target_id the channel owner's subject, notify flags, per-pair revision; subject visibility), network.follow-status-result@1 (public count plus the caller's own follow), network.follow-list-result@1 (a page of follows, never public) and the capability network.follows.read (first-party, never for apps).

OP OpenVibers committed 26 Sept 2026 · 34266487ef · 47 files, +629 −51


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 3e0e77a9..246b547f 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,13 @@ the notes were in the tag and commit messages (`git tag -n1`).
## Unreleased
++## 0.65.0 — 2026-09-26
++
++**The follow graph on Network** (roadmap WS-E task 4, ADR-030).
++- **New event types** `network.follow.created` and `network.follow.deleted` (subject visibility for the follower). Their payloads carry `follower`, `target_type` (`channel` today: a Live channel named by its owner's subject), `target_id`, the notify flags, a per-pair `revision`, and for a delete a `reason`. Products keep projections they can rebuild.
++- **New schemas** `network.follow-status-result@1` (the public follower count, plus the caller's own follow when signed in) and `network.follow-list-result@1` (a page of follows, never public).
++- **New capability** `network.follows.read` (first-party, never for apps): who follows a target, for notifications and projections.
++
## 0.64.0 — 2026-09-26
**Wiki VIP spaces and pages** (roadmap WS-K task 8).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 329ac799..6cde3738 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -2406,6 +2406,26 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "network.follow.created",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "events/payloads/network.follow.created.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-030"
++ },
++ {
++ "id": "network.follow.deleted",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "events/payloads/network.follow.deleted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-030"
++ },
{
"id": "network.block.changed",
"version": "1.0.0",
@@@ -2515,6 +2535,26 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "network.follow-list-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/follow-list-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-030"
++ },
++ {
++ "id": "network.follow-status-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/follow-status-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-030"
++ },
{
"id": "network.operator-alerts-request",
"version": "1.0.0",
ddiff --git a/contracts/events/payloads/network.follow.created.v1.json b/contracts/events/payloads/network.follow.created.v1.json
nnew file mode 100644
iindex 00000000..76c9f42b
---- /dev/null
++++ b/contracts/events/payloads/network.follow.created.v1.json
@@@ -0,0 +1,18 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.follow.created.v1.json",
++ "title": "NetworkFollowCreatedPayload",
++ "description": "network.follow.created v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person followed something: today a Live channel, whose target id is the channel owner's subject. Follows are the network's, kept by Network and keyed by subjects; products such as Live keep a projection they can rebuild. Written to Network's outbox in the transaction that stores the follow. A consumer applies the latest revision per (follower, target) and ignores an older one. Following again what is already followed changes nothing and sends nothing. Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower.",
++ "type": "object",
++ "required": ["follower", "target_type", "target_id", "revision"],
++ "additionalProperties": false,
++ "properties": {
++ "follower": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "Who follows." },
++ "target_type": { "enum": ["channel"], "description": "What kind of thing is followed. channel: a Live channel, named by its owner's subject." },
++ "target_id": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The followed target: for a channel, the channel owner's subject." },
++ "notify_email": { "type": "boolean" },
++ "notify_push": { "type": "boolean" },
++ "revision": { "type": "integer", "minimum": 1, "maximum": 9007199254740991, "description": "Grows with every change of this pair." },
++ "at": { "type": "string", "format": "date-time" }
++ }
++}
ddiff --git a/contracts/events/payloads/network.follow.deleted.v1.json b/contracts/events/payloads/network.follow.deleted.v1.json
nnew file mode 100644
iindex 00000000..a98289fb
---- /dev/null
++++ b/contracts/events/payloads/network.follow.deleted.v1.json
@@@ -0,0 +1,17 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.follow.deleted.v1.json",
++ "title": "NetworkFollowDeletedPayload",
++ "description": "network.follow.deleted v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person stopped following something (or the follow went with an account). Written to Network's outbox in the transaction that removes the follow; unfollowing what is not followed sends nothing. A consumer applies the latest revision per (follower, target). Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower (system:network for an account removal).",
++ "type": "object",
++ "required": ["follower", "target_type", "target_id", "revision"],
++ "additionalProperties": false,
++ "properties": {
++ "follower": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "target_type": { "enum": ["channel"] },
++ "target_id": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "reason": { "enum": ["unfollowed", "account_removed", "target_removed"], "description": "Why the follow went (default unfollowed)." },
++ "revision": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
++ "at": { "type": "string", "format": "date-time" }
++ }
++}
ddiff --git a/contracts/network/follow-list-result.v1.json b/contracts/network/follow-list-result.v1.json
nnew file mode 100644
iindex 00000000..ae78516b
---- /dev/null
++++ b/contracts/network/follow-list-result.v1.json
@@@ -0,0 +1,29 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/follow-list-result.v1.json",
++ "title": "NetworkFollowListResult",
++ "description": "network.follow-list-result@1 (ADR-030): a page of follows on OpenVibe.Network, newest first. GET /api/v1/me/follows (what the caller follows), GET /api/v1/follows/:type/:target/followers (who follows a target: the target's owner, or a service with network.follows.read). Lists are never public; counts are.",
++ "type": "object",
++ "required": ["items", "next_cursor"],
++ "additionalProperties": false,
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": ["follower", "target_type", "target_id", "created_at"],
++ "additionalProperties": false,
++ "properties": {
++ "follower": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "target_type": { "enum": ["channel"] },
++ "target_id": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "notify_email": { "type": "boolean" },
++ "notify_push": { "type": "boolean" },
++ "created_at": { "type": "string", "format": "date-time" }
++ }
++ }
++ },
++ "next_cursor": { "type": ["string", "null"], "maxLength": 200 }
++ }
++}
ddiff --git a/contracts/network/follow-status-result.v1.json b/contracts/network/follow-status-result.v1.json
nnew file mode 100644
iindex 00000000..e166e559
---- /dev/null
++++ b/contracts/network/follow-status-result.v1.json
@@@ -0,0 +1,18 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/follow-status-result.v1.json",
++ "title": "NetworkFollowStatusResult",
++ "description": "network.follow-status-result@1 (ADR-030): GET /api/v1/follows/:type/:target on OpenVibe.Network: the public follower count of a target, and, for a signed-in caller, whether they follow it and how they are notified. PUT and DELETE /api/v1/me/follows/:type/:target answer the same document after the change (idempotent: repeating either changes nothing).",
++ "type": "object",
++ "required": ["target_type", "target_id", "followers"],
++ "additionalProperties": false,
++ "properties": {
++ "target_type": { "enum": ["channel"] },
++ "target_id": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "followers": { "type": "integer", "minimum": 0 },
++ "following": { "type": "boolean", "description": "Present for a signed-in caller." },
++ "notify_email": { "type": "boolean" },
++ "notify_push": { "type": "boolean" },
++ "since": { "type": "string", "format": "date-time", "description": "When the caller started following." }
++ }
++}
ddiff --git a/fixtures/network.follow-list-result/invalid/leaks-username.json b/fixtures/network.follow-list-result/invalid/leaks-username.json
nnew file mode 100644
iindex 00000000..86a883fb
---- /dev/null
++++ b/fixtures/network.follow-list-result/invalid/leaks-username.json
@@@ -0,0 +1 @@
++{ "items": [ { "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "created_at": "2026-09-20T10:00:00Z", "username": "alice" } ], "next_cursor": null }
ddiff --git a/fixtures/network.follow-list-result/valid/page.json b/fixtures/network.follow-list-result/valid/page.json
nnew file mode 100644
iindex 00000000..3e671a5c
---- /dev/null
++++ b/fixtures/network.follow-list-result/valid/page.json
@@@ -0,0 +1 @@
++{ "items": [ { "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "notify_email": true, "notify_push": true, "created_at": "2026-09-20T10:00:00Z" } ], "next_cursor": null }
ddiff --git a/fixtures/network.follow-status-result/invalid/negative.json b/fixtures/network.follow-status-result/invalid/negative.json
nnew file mode 100644
iindex 00000000..d13ee554
---- /dev/null
++++ b/fixtures/network.follow-status-result/invalid/negative.json
@@@ -0,0 +1 @@
++{ "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "followers": -1 }
ddiff --git a/fixtures/network.follow-status-result/valid/anonymous.json b/fixtures/network.follow-status-result/valid/anonymous.json
nnew file mode 100644
iindex 00000000..76cf7c41
---- /dev/null
++++ b/fixtures/network.follow-status-result/valid/anonymous.json
@@@ -0,0 +1 @@
++{ "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "followers": 12 }
ddiff --git a/fixtures/network.follow-status-result/valid/signed-in.json b/fixtures/network.follow-status-result/valid/signed-in.json
nnew file mode 100644
iindex 00000000..9a9d6e91
---- /dev/null
++++ b/fixtures/network.follow-status-result/valid/signed-in.json
@@@ -0,0 +1 @@
++{ "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "followers": 12, "following": true, "notify_email": false, "notify_push": true, "since": "2026-09-20T10:00:00Z" }
ddiff --git a/fixtures/network.follow.created/invalid/live-id.json b/fixtures/network.follow.created/invalid/live-id.json
nnew file mode 100644
iindex 00000000..8c76b7f0
---- /dev/null
++++ b/fixtures/network.follow.created/invalid/live-id.json
@@@ -0,0 +1 @@
++{ "follower": "42", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 1 }
ddiff --git a/fixtures/network.follow.created/invalid/unknown-type.json b/fixtures/network.follow.created/invalid/unknown-type.json
nnew file mode 100644
iindex 00000000..5b7879af
---- /dev/null
++++ b/fixtures/network.follow.created/invalid/unknown-type.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "streamer", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 1 }
ddiff --git a/fixtures/network.follow.created/valid/follow.json b/fixtures/network.follow.created/valid/follow.json
nnew file mode 100644
iindex 00000000..6e5412d1
---- /dev/null
++++ b/fixtures/network.follow.created/valid/follow.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "notify_email": true, "notify_push": true, "revision": 1, "at": "2026-09-26T16:00:00Z" }
ddiff --git a/fixtures/network.follow.created/valid/minimal.json b/fixtures/network.follow.created/valid/minimal.json
nnew file mode 100644
iindex 00000000..f450efb2
---- /dev/null
++++ b/fixtures/network.follow.created/valid/minimal.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 1 }
ddiff --git a/fixtures/network.follow.deleted/invalid/revision-zero.json b/fixtures/network.follow.deleted/invalid/revision-zero.json
nnew file mode 100644
iindex 00000000..3518d60b
---- /dev/null
++++ b/fixtures/network.follow.deleted/invalid/revision-zero.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "revision": 0 }
ddiff --git a/fixtures/network.follow.deleted/valid/unfollow.json b/fixtures/network.follow.deleted/valid/unfollow.json
nnew file mode 100644
iindex 00000000..ade3e06d
---- /dev/null
++++ b/fixtures/network.follow.deleted/valid/unfollow.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "target_type": "channel", "target_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR", "reason": "unfollowed", "revision": 2, "at": "2026-09-26T17:00:00Z" }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 9aaf89fb..051f138f 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -25347,6 +25347,110 @@
}
}
},
++ "network.follow.created": {
++ "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.follow.created.v1.json",
++ "title": "NetworkFollowCreatedPayload",
++ "description": "network.follow.created v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person followed something: today a Live channel, whose target id is the channel owner's subject. Follows are the network's, kept by Network and keyed by subjects; products such as Live keep a projection they can rebuild. Written to Network's outbox in the transaction that stores the follow. A consumer applies the latest revision per (follower, target) and ignores an older one. Following again what is already followed changes nothing and sends nothing. Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower.",
++ "type": "object",
++ "required": [
++ "follower",
++ "target_type",
++ "target_id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Who follows."
++ },
++ "target_type": {
++ "enum": [
++ "channel"
++ ],
++ "description": "What kind of thing is followed. channel: a Live channel, named by its owner's subject."
++ },
++ "target_id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The followed target: for a channel, the channel owner's subject."
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991,
++ "description": "Grows with every change of this pair."
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "network.follow.deleted": {
++ "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.follow.deleted.v1.json",
++ "title": "NetworkFollowDeletedPayload",
++ "description": "network.follow.deleted v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person stopped following something (or the follow went with an account). Written to Network's outbox in the transaction that removes the follow; unfollowing what is not followed sends nothing. A consumer applies the latest revision per (follower, target). Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower (system:network for an account removal).",
++ "type": "object",
++ "required": [
++ "follower",
++ "target_type",
++ "target_id",
++ "revision"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "target_type": {
++ "enum": [
++ "channel"
++ ]
++ },
++ "target_id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "reason": {
++ "enum": [
++ "unfollowed",
++ "account_removed",
++ "target_removed"
++ ],
++ "description": "Why the follow went (default unfollowed)."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 9007199254740991
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
"network.block.changed": {
"version": "1.0.0",
"owner": "network",
@@@ -26189,6 +26293,119 @@
]
}
},
++ "network.follow-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/follow-list-result.v1.json",
++ "title": "NetworkFollowListResult",
++ "description": "network.follow-list-result@1 (ADR-030): a page of follows on OpenVibe.Network, newest first. GET /api/v1/me/follows (what the caller follows), GET /api/v1/follows/:type/:target/followers (who follows a target: the target's owner, or a service with network.follows.read). Lists are never public; counts are.",
++ "type": "object",
++ "required": [
++ "items",
++ "next_cursor"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": [
++ "follower",
++ "target_type",
++ "target_id",
++ "created_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "target_type": {
++ "enum": [
++ "channel"
++ ]
++ },
++ "target_id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ }
++ }
++ }
++ },
++ "network.follow-status-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/follow-status-result.v1.json",
++ "title": "NetworkFollowStatusResult",
++ "description": "network.follow-status-result@1 (ADR-030): GET /api/v1/follows/:type/:target on OpenVibe.Network: the public follower count of a target, and, for a signed-in caller, whether they follow it and how they are notified. PUT and DELETE /api/v1/me/follows/:type/:target answer the same document after the change (idempotent: repeating either changes nothing).",
++ "type": "object",
++ "required": [
++ "target_type",
++ "target_id",
++ "followers"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "target_type": {
++ "enum": [
++ "channel"
++ ]
++ },
++ "target_id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "followers": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "following": {
++ "type": "boolean",
++ "description": "Present for a signed-in caller."
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ },
++ "since": {
++ "type": "string",
++ "format": "date-time",
++ "description": "When the caller started following."
++ }
++ }
++ }
++ },
"network.operator-alerts-request": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 552db225..875d60e5 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex a919d94d..0999c2a6 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 2085df32..d0be4f1f 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 2dfa645b..c0279087 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex 326866b4..43b0552c 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 1340f85f..5340f3a4 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 5e683ba6..67dc01f5 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex 4487e088..dfb99bbf 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 7ff2c837..459b2571 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex cfe46271..25e9536d 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex a294d2e5..77a31a6c 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex e2d8ae06..0883db88 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -96,8 +96,8 @@
"service": "network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
-- "operations": 12,
-- "capabilities": 11
++ "operations": 13,
++ "capabilities": 12
},
{
"service": "news",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex de35c414..316a756a 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 8e65951a..acaaa716 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex 5c34209d..07326ffd 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
@@@ -16,6 +16,70 @@
}
],
"paths": {
++ "/api/v1/follows/{type}/{target}/followers": {
++ "get": {
++ "operationId": "get_api_v1_follows_type_target_followers",
++ "summary": "Read who follows a target (subjects only), as an owning service: go-live and other notifications, and product projections that rebuild from Network (ADR-030).",
++ "description": "**network.follows.read** (first-party): Read who follows a target (subjects only), as an owning service: go-live and other notifications, and product projections that rebuild from Network (ADR-030). Counts are public without it. Never grantable to apps.",
++ "tags": [
++ "network.follows"
++ ],
++ "x-openvibe-capabilities": [
++ "network.follows.read"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "follows-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.follows.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "type",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "target",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.follow-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/staff/moderators": {
"get": {
"operationId": "get_api_v1_staff_moderators",
@@@ -1206,6 +1270,64 @@
}
}
},
++ "network.follow-list-result.v1": {
++ "title": "NetworkFollowListResult",
++ "description": "network.follow-list-result@1 (ADR-030): a page of follows on OpenVibe.Network, newest first. GET /api/v1/me/follows (what the caller follows), GET /api/v1/follows/:type/:target/followers (who follows a target: the target's owner, or a service with network.follows.read). Lists are never public; counts are.",
++ "type": "object",
++ "required": [
++ "items",
++ "next_cursor"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "type": "object",
++ "required": [
++ "follower",
++ "target_type",
++ "target_id",
++ "created_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "target_type": {
++ "enum": [
++ "channel"
++ ]
++ },
++ "target_id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ }
++ }
++ },
"network.github-token-result.v1": {
"title": "NetworkGithubTokenResult",
"description": "network.github-token-result@1: the answer of network.integration.github.read on OpenVibe.Network. GET /internal/integrations/github-token (a service token holding the capability; the shared internal key is refused) → { token, source }: the network's read-only GitHub API token and where it is set (GITHUB_TOKEN in the environment, or the value the owner saved in admin). Answered with Cache-Control: no-store; 404 { error: not_configured, detail } when no token is set.",
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex fae2ada5..a88ecbc2 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex 6471bd7f..2ad64b05 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.64.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 74bb8379..b6cb893d 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 70004c38..ad944560 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 40425bf0..140f8c6e 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 006db6f1..556ec9e3 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 29ce81fc..9513b22d 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 9f49af9b..2319bc4d 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 449dd403..364962bf 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex ce931674..4bcf8880 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.64.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.64.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.65.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.65.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"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 2bd0d3d2..ee925bb8 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -28611,6 +28611,48 @@ export interface NetworkPrincipalGrantChangedPayload {
actor_subject?: string | null;
}
++/** [email protected] (owner: network) */
++/**
++ * network.follow.created v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person followed something: today a Live channel, whose target id is the channel owner's subject. Follows are the network's, kept by Network and keyed by subjects; products such as Live keep a projection they can rebuild. Written to Network's outbox in the transaction that stores the follow. A consumer applies the latest revision per (follower, target) and ignores an older one. Following again what is already followed changes nothing and sends nothing. Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower.
++ */
++export interface NetworkFollowCreatedPayload {
++ /**
++ * Who follows.
++ */
++ follower: string;
++ /**
++ * What kind of thing is followed. channel: a Live channel, named by its owner's subject.
++ */
++ target_type: "channel";
++ /**
++ * The followed target: for a channel, the channel owner's subject.
++ */
++ target_id: string;
++ notify_email?: boolean;
++ notify_push?: boolean;
++ /**
++ * Grows with every change of this pair.
++ */
++ revision: number;
++ at?: string;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.follow.deleted v1 (OpenVibe.Network; roadmap WS-E task 4, ADR-030). A person stopped following something (or the follow went with an account). Written to Network's outbox in the transaction that removes the follow; unfollowing what is not followed sends nothing. A consumer applies the latest revision per (follower, target). Envelope: subject { type: user, id: <follower> }, visibility subject, actor the follower (system:network for an account removal).
++ */
++export interface NetworkFollowDeletedPayload {
++ follower: string;
++ target_type: "channel";
++ target_id: string;
++ /**
++ * Why the follow went (default unfollowed).
++ */
++ reason?: "unfollowed" | "account_removed" | "target_removed";
++ revision: number;
++ at?: string;
++}
++
/** [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.
@@@ -29003,6 +29045,45 @@ export type NetworkNotificationPushResult =
total: number;
};
++/** [email protected] (owner: network) */
++/**
++ * network.follow-list-result@1 (ADR-030): a page of follows on OpenVibe.Network, newest first. GET /api/v1/me/follows (what the caller follows), GET /api/v1/follows/:type/:target/followers (who follows a target: the target's owner, or a service with network.follows.read). Lists are never public; counts are.
++ */
++export interface NetworkFollowListResult {
++ /**
++ * @maxItems 500
++ */
++ items: {
++ follower: string;
++ target_type: "channel";
++ target_id: string;
++ notify_email?: boolean;
++ notify_push?: boolean;
++ created_at: string;
++ }[];
++ next_cursor: string | null;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.follow-status-result@1 (ADR-030): GET /api/v1/follows/:type/:target on OpenVibe.Network: the public follower count of a target, and, for a signed-in caller, whether they follow it and how they are notified. PUT and DELETE /api/v1/me/follows/:type/:target answer the same document after the change (idempotent: repeating either changes nothing).
++ */
++export interface NetworkFollowStatusResult {
++ target_type: "channel";
++ target_id: string;
++ followers: number;
++ /**
++ * Present for a signed-in caller.
++ */
++ following?: boolean;
++ notify_email?: boolean;
++ notify_push?: boolean;
++ /**
++ * When the caller started following.
++ */
++ since?: string;
++}
++
/** [email protected] (owner: network) */
/**
* network.operator-alerts-request@1: POST /internal/operator/alerts on OpenVibe.Network (capability network.operator.alert, held by the Host principal). The complete set of alerts firing now at one source: an alert Network has not seen firing opens and pages the operator; one still firing is reminded once a day; one that was firing and is absent from the set is resolved and says so. An empty list resolves everything from that source. Fingerprints are stable per alert (name and labels), never secrets; summaries and descriptions are the alert rule's annotations.
ddiff --git a/manifests/capabilities/network.follows.read.json b/manifests/capabilities/network.follows.read.json
nnew file mode 100644
iindex 00000000..0cbf3e89
---- /dev/null
++++ b/manifests/capabilities/network.follows.read.json
@@@ -0,0 +1,15 @@
++{
++ "id": "network.follows.read",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Read who follows a target (subjects only), as an owning service: go-live and other notifications, and product projections that rebuild from Network (ADR-030). Counts are public without it. Never grantable to apps.",
++ "permissions": ["follows:read"],
++ "resourceConstraints": [],
++ "quotaClass": "follows-read",
++ "events": ["network.follow.created", "network.follow.deleted"],
++ "implementedBy": ["GET /api/v1/follows/:type/:target/followers"],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "network.follow-list-result@1"
++}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex 1be2704e..9cb3bd5d 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -38,6 +38,7 @@
"network.coins.credit",
"network.coins.debit",
"network.coins.transfer",
++ "network.follows.read",
"network.integration.github.read",
"network.modules.read",
"network.modules.write",
@@@ -53,6 +54,8 @@
"network.block.changed",
"network.credential.revoked",
"network.credential.rotated",
++ "network.follow.created",
++ "network.follow.deleted",
"network.grant.changed",
"network.module.updated",
"network.notification.created",
ddiff --git a/package.json b/package.json
iindex c99038c0..1ae9ccaf 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.64.0",
++ "version": "0.65.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",