openvibers/openvibe.contracts Commits

v0.67.0: network.follows.write and network.follow-write-request@1 (ADR-030 step 4): a first-party product records a follow or unfollow on a person's behalf (PUT/DELETE /internal/follows/:type/:target), and Network keeps the graph and emits network.follow.*.

OP OpenVibers committed 26 Sept 2026 · 73fbe39323 · 35 files, +363 −51


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 50554c5e..5fa46ea3 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,12 @@ the notes were in the tag and commit messages (`git tag -n1`).
## Unreleased
++## 0.67.0 — 2026-09-26
++
++**Products write follows on a person's behalf** (ADR-030 step 4).
++- **New capability** `network.follows.write` (first-party, never for apps): PUT and DELETE `/internal/follows/:type/:target` on Network, with the follower named in the request.
++- **New schema** `network.follow-write-request@1` (`follower`, `notify_email?`, `notify_push?`). Live's follow buttons use it, and Live keeps its table as a projection of `network.follow.*`.
++
## 0.66.0 — 2026-09-26
**Incidents and maintenance on the status page** (roadmap WS-N task 12).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 666dd0cd..b274bc1e 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -2545,6 +2545,16 @@
"status": "active",
"adr": "ADR-030"
},
++ {
++ "id": "network.follow-write-request",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/follow-write-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-030"
++ },
{
"id": "network.follow-status-result",
"version": "1.0.0",
ddiff --git a/contracts/network/follow-write-request.v1.json b/contracts/network/follow-write-request.v1.json
nnew file mode 100644
iindex 00000000..a744b6ca
---- /dev/null
++++ b/contracts/network/follow-write-request.v1.json
@@@ -0,0 +1,14 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/follow-write-request.v1.json",
++ "title": "NetworkFollowWriteRequest",
++ "description": "network.follow-write-request@1 (ADR-030 step 4; capability network.follows.write): a first-party product records a follow or unfollow on a person's behalf, where they pressed the button. PUT /internal/follows/:type/:target { follower, notify_email?, notify_push? } follows (idempotent; flags change a follow). DELETE /internal/follows/:type/:target?follower=usr_… unfollows (idempotent). Answers network.follow-status-result@1 for the follower. Network emits network.follow.* as for a person's own change.",
++ "type": "object",
++ "required": ["follower"],
++ "additionalProperties": false,
++ "properties": {
++ "follower": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "notify_email": { "type": "boolean" },
++ "notify_push": { "type": "boolean" }
++ }
++}
ddiff --git a/fixtures/network.follow-write-request/invalid/live-id.json b/fixtures/network.follow-write-request/invalid/live-id.json
nnew file mode 100644
iindex 00000000..f4721cb2
---- /dev/null
++++ b/fixtures/network.follow-write-request/invalid/live-id.json
@@@ -0,0 +1 @@
++{ "follower": "42" }
ddiff --git a/fixtures/network.follow-write-request/valid/follow.json b/fixtures/network.follow-write-request/valid/follow.json
nnew file mode 100644
iindex 00000000..3567b6f6
---- /dev/null
++++ b/fixtures/network.follow-write-request/valid/follow.json
@@@ -0,0 +1 @@
++{ "follower": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "notify_push": true }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex e597859b..bba85487 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -26358,6 +26358,34 @@
}
}
},
++ "network.follow-write-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/follow-write-request.v1.json",
++ "title": "NetworkFollowWriteRequest",
++ "description": "network.follow-write-request@1 (ADR-030 step 4; capability network.follows.write): a first-party product records a follow or unfollow on a person's behalf, where they pressed the button. PUT /internal/follows/:type/:target { follower, notify_email?, notify_push? } follows (idempotent; flags change a follow). DELETE /internal/follows/:type/:target?follower=usr_… unfollows (idempotent). Answers network.follow-status-result@1 for the follower. Network emits network.follow.* as for a person's own change.",
++ "type": "object",
++ "required": [
++ "follower"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ }
++ }
++ }
++ },
"network.follow-status-result": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex f0dd3775..3702b336 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.66.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 037e3506..a149b652 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.66.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 a48d313d..b59ab1c3 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.66.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 4e06d297..0d43d94c 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.66.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 5d65815b..c9a39802 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.66.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 f38a832e..59cad908 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.66.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 5f362b8b..4c83e8c8 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.66.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 cab0ff8e..b737624b 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.66.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 f6fe8f00..87f77ccc 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.66.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 ff50fc78..b32dfeaa 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.66.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 0d77422d..6a6f16fa 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.66.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 54a2a312..f0a35475 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": 15,
-- "capabilities": 13
++ "operations": 17,
++ "capabilities": 14
},
{
"service": "news",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 92604a75..6a212e7a 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.66.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 c454f3e8..600f0d76 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.66.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 7f420a19..19ed8b1a 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.66.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.67.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"
@@@ -462,6 +462,170 @@
}
}
},
++ "/internal/follows/{type}/{target}": {
++ "delete": {
++ "operationId": "delete_internal_follows_type_target",
++ "summary": "Follow or unfollow on a person's behalf, as the first-party product where they pressed the button (Live's follow buttons, ADR-030 step 4).",
++ "description": "**network.follows.write** (first-party): Follow or unfollow on a person's behalf, as the first-party product where they pressed the button (Live's follow buttons, ADR-030 step 4). The follower is named in the request; Network keeps the graph and emits the events. Never grantable to apps.",
++ "tags": [
++ "network.follows"
++ ],
++ "x-openvibe-capabilities": [
++ "network.follows.write"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "follows-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.follows.write"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/network.follow-write-request.v1"
++ },
++ "parameters": [
++ {
++ "name": "type",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "target",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "follower",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ {
++ "name": "notify_email",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean"
++ }
++ },
++ {
++ "name": "notify_push",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.follow-status-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_follows_type_target",
++ "summary": "Follow or unfollow on a person's behalf, as the first-party product where they pressed the button (Live's follow buttons, ADR-030 step 4).",
++ "description": "**network.follows.write** (first-party): Follow or unfollow on a person's behalf, as the first-party product where they pressed the button (Live's follow buttons, ADR-030 step 4). The follower is named in the request; Network keeps the graph and emits the events. Never grantable to apps.",
++ "tags": [
++ "network.follows"
++ ],
++ "x-openvibe-capabilities": [
++ "network.follows.write"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "follows-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.follows.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.follow-write-request.v1"
++ }
++ }
++ }
++ },
++ "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-status-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/internal/identity/resolve": {
"get": {
"operationId": "get_internal_identity_resolve",
@@@ -1450,6 +1614,68 @@
}
}
},
++ "network.follow-status-result.v1": {
++ "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.follow-write-request.v1": {
++ "title": "NetworkFollowWriteRequest",
++ "description": "network.follow-write-request@1 (ADR-030 step 4; capability network.follows.write): a first-party product records a follow or unfollow on a person's behalf, where they pressed the button. PUT /internal/follows/:type/:target { follower, notify_email?, notify_push? } follows (idempotent; flags change a follow). DELETE /internal/follows/:type/:target?follower=usr_… unfollows (idempotent). Answers network.follow-status-result@1 for the follower. Network emits network.follow.* as for a person's own change.",
++ "type": "object",
++ "required": [
++ "follower"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "follower": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "notify_email": {
++ "type": "boolean"
++ },
++ "notify_push": {
++ "type": "boolean"
++ }
++ }
++ },
"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 603eb1fe..9f1a34d5 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.66.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 f3323223..9dba31fb 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.66.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 bb65daa0..6282248a 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.66.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 440da5d6..1b84aa2b 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.66.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 d5558323..0ed1091e 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.66.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 6457e7cc..e5807872 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.66.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 6ce9aae0..fda7fba2 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.66.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 1f4065a9..16b8f8af 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.66.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 b8a93068..bb1d49b2 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.66.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 0c4eaf25..f8744c8d 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.66.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.66.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.67.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.67.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 752aa599..af70bfe4 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -29064,6 +29064,16 @@ export interface NetworkFollowListResult {
next_cursor: string | null;
}
++/** [email protected] (owner: network) */
++/**
++ * network.follow-write-request@1 (ADR-030 step 4; capability network.follows.write): a first-party product records a follow or unfollow on a person's behalf, where they pressed the button. PUT /internal/follows/:type/:target { follower, notify_email?, notify_push? } follows (idempotent; flags change a follow). DELETE /internal/follows/:type/:target?follower=usr_… unfollows (idempotent). Answers network.follow-status-result@1 for the follower. Network emits network.follow.* as for a person's own change.
++ */
++export interface NetworkFollowWriteRequest {
++ follower: string;
++ notify_email?: boolean;
++ notify_push?: boolean;
++}
++
/** [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).
ddiff --git a/manifests/capabilities/network.follows.write.json b/manifests/capabilities/network.follows.write.json
nnew file mode 100644
iindex 00000000..b293ffae
---- /dev/null
++++ b/manifests/capabilities/network.follows.write.json
@@@ -0,0 +1,15 @@
++{
++ "id": "network.follows.write",
++ "version": "1.0",
++ "owner": "network",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Follow or unfollow on a person's behalf, as the first-party product where they pressed the button (Live's follow buttons, ADR-030 step 4). The follower is named in the request; Network keeps the graph and emits the events. Never grantable to apps.",
++ "permissions": ["follows:write"],
++ "resourceConstraints": [],
++ "quotaClass": "follows-write",
++ "events": ["network.follow.created", "network.follow.deleted"],
++ "implementedBy": ["PUT /internal/follows/:type/:target", "DELETE /internal/follows/:type/:target"],
++ "inputSchema": "network.follow-write-request@1",
++ "outputSchema": "network.follow-status-result@1"
++}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex 3835b322..e832fdc7 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -39,6 +39,7 @@
"network.coins.debit",
"network.coins.transfer",
"network.follows.read",
++ "network.follows.write",
"network.integration.github.read",
"network.modules.read",
"network.modules.write",
ddiff --git a/package.json b/package.json
iindex bd4df687..17ea8225 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.66.0",
++ "version": "0.67.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",