openvibers/openvibe.contracts Commits

Contracts 0.110.0: OpenVibe.Space and AI's public home launch; Community's forum capabilities retire (#57)

OP OpenVibers committed 7 Oct 2026 · 1e30593d3d · 67 files, +1615 −221

* Contracts 0.110.0: OpenVibe.Space launches; Community's forum capabilities retire

Prepared **0.110.0** in this checkout. No commit was made.

- [manifests/services/space.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/services/space.json): marks Space live and alpha; lists its four served capabilities and current events.
- [manifests/services/community.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/services/community.json): removes forum capabilities and forum creation events while retaining Community’s other services.
- [Space capability manifests](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/capabilities/space.forum.manage.json): activate the four served grants with Space routes; thread.write and pulse.read remain planned.
- [Community capability manifests](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/capabilities/community.post.create.json): retire four forum grants; community.vote.set now names only its comment route.
- [contracts/catalog.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/contracts/catalog.json) and [compatibility/deprecations.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/compatibility/deprecations.json): register three Space events and retain the retired Community forum contracts and event payloads for historical data.
- [Space event payloads](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/contracts/events/payloads/space.thread.created.v1.json): add thread, post, and moderation schemas with valid and invalid fixtures.
- [docs/space-problem-codes.md](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/docs/space-problem-codes.md): documents Space’s returned codes, including `403 space.blocked`; a problem fixture covers it.
- [package.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/package.json), [package-lock.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/package-lock.json), and [CHANGELOG.md](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/CHANGELOG.md): set and describe 0.110.0.
- [generated/](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/generated): regenerated bundle, types, and OpenAPI files; generation check passes.
- [test/run.js](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/test/run.js) and [test/space-release.test.js](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/test/space-release.test.js): account for retired entries and check the la

* Contracts 0.110.0: OpenVibe.Space and AI's public home launch; Community's forum capabilities retire

Prepared **0.110.0** in this checkout. No commit was made.

- [manifests/services/space.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/services/space.json): marks Space live and alpha; lists its four served capabilities and current events.
- [manifests/services/community.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/services/community.json): removes forum capabilities and forum creation events while retaining Community’s other services.
- [Space capability manifests](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/capabilities/space.forum.manage.json): activate the four served grants with Space routes; thread.write and pulse.read remain planned.
- [Community capability manifests](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/manifests/capabilities/community.post.create.json): retire four forum grants; community.vote.set now names only its comment route.
- [contracts/catalog.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/contracts/catalog.json) and [compatibility/deprecations.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/compatibility/deprecations.json): register three Space events and retain the retired Community forum contracts and event payloads for historical data.
- [Space event payloads](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/contracts/events/payloads/space.thread.created.v1.json): add thread, post, and moderation schemas with valid and invalid fixtures.
- [docs/space-problem-codes.md](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/docs/space-problem-codes.md): documents Space’s returned codes, including `403 space.blocked`; a problem fixture covers it.
- [package.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/package.json), [package-lock.json](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/package-lock.json), and [CHANGELOG.md](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/CHANGELOG.md): set and describe 0.110.0.
- [generated/](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/generated): regenerated bundle, types, and OpenAPI files; generation check passes.
- [test/run.js](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/test/run.js) and [test/space-release.test.js](/tmp/claude-1000/-home-workstation-orca-workspaces-OpenVibe-Live-seadragon/070220df-455a-45d2-b679-8c15af458e9a/scratchpad/c13-space/test/space-release.test.js): account for retired entries and check the la

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 2aaaca86..9c271461 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,31 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.110.0 — 2026-10-07
++
++**OpenVibe.Space and OpenVibe.AI's public home launch** (plan T10 step 3, T6). It serves the forum at `https://openvibe.space` from
++loopback :4940. Community keeps pastes, comments, Pulse and submissions; its former `/s/*` forum paths
++redirect permanently to Space.
++- `manifests/services/space.json`: `status` alpha, `exposure` live with `publicSite: service`,
++ health and readiness paths, lifecycle, and the events Space produces and consumes.
++- `space.forum.read`, `space.forum.manage`, `space.thread.read`, and `space.post.write` are active with
++ Space's implemented routes. `space.post.write` still guards thread creation; `space.thread.write`
++ remains planned as a separate grant. `space.pulse.read` remains planned because Space serves no Pulse.
++- `space.thread.created`, `space.post.created`, and `space.moderation.action` have payload contracts and
++ valid and invalid fixtures. Space's source is `space`; `space.moderation.action` is
++ `common.moderation-action@1` (ADR-022) and OpenVibe.Network consumes it into the moderation audit log.
++- Space's problem codes, including `403 space.blocked`, are documented in
++ `docs/space-problem-codes.md`; the problem fixture demonstrates the stable URI and code.
++- The four former Community forum capabilities are retired and removed from its service list. The seven
++ Community forum schemas remain loadable but retired, with Space replacements and a compatibility
++ window through 1.0.0. Historical `community.thread.created` and `community.post.created` payloads
++ also remain loadable as retired types; Community no longer claims to produce them. Community's
++ planned vote grant now describes its surviving comment route only.
++- `manifests/services/ai.json`: `exposure` live with `publicSite: service`; `publicOrigin` is
++ `https://ai.openvibe.services` (the home, `/stats` and the developer-app API, AI #23), and
++ `ai.openvibe.network`, still listed in `domains`, answers 301 to it.
++- Generated bundles, types, and OpenAPI documents are refreshed for 0.110.0.
++
## 0.109.0 — 2026-10-07
**OpenVibe.Run runs, OpenVibe.Services registers, and Bot learns buttons and points.** Two schemas gained optional
ddiff --git a/compatibility/capability-schema-gaps.json b/compatibility/capability-schema-gaps.json
iindex c8f19d7d..a2db35f3 100644
---- a/compatibility/capability-schema-gaps.json
++++ b/compatibility/capability-schema-gaps.json
@@@ -21,6 +21,13 @@
"inputSchema",
"outputSchema"
]
++ },
++ {
++ "id": "space.forum.manage",
++ "missing": [
++ "inputSchema",
++ "outputSchema"
++ ]
}
]
}
ddiff --git a/compatibility/deprecations.json b/compatibility/deprecations.json
iindex e51d8019..c0aed773 100644
---- a/compatibility/deprecations.json
++++ b/compatibility/deprecations.json
@@@ -5,50 +5,64 @@
"id": "community.forum-space",
"replacement": "space.forum-space@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "the forum, its spaces/threads/posts and the community.space.*/thread.read/post.create capabilities move to OpenVibe.Space (plan T10, D1/D3)"
++ "removeAfter": "1.0.0",
++ "reason": "the forum, its spaces/threads/posts and the community.space.*/thread.read/post.create capabilities move to OpenVibe.Space (plan T10, D1/D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.forum-thread",
"replacement": "space.forum-thread@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "the forum data moves to OpenVibe.Space (plan T10, D1/D3)"
++ "removeAfter": "1.0.0",
++ "reason": "the forum data moves to OpenVibe.Space (plan T10, D1/D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.forum-post",
"replacement": "space.forum-post@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "the forum data moves to OpenVibe.Space (plan T10, D1/D3)"
++ "removeAfter": "1.0.0",
++ "reason": "the forum data moves to OpenVibe.Space (plan T10, D1/D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.space-read-result",
"replacement": "space.forum-read-result@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "community.space.read is renamed space.forum.read (plan T10, D3)"
++ "removeAfter": "1.0.0",
++ "reason": "community.space.read is renamed space.forum.read (plan T10, D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.thread-read-result",
"replacement": "space.thread-read-result@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "community.thread.read is renamed space.thread.read (plan T10, D3)"
++ "removeAfter": "1.0.0",
++ "reason": "community.thread.read is renamed space.thread.read (plan T10, D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.post-write-request",
"replacement": "space.post-write-request@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "community.post.create is renamed space.post.write (plan T10, D3)"
++ "removeAfter": "1.0.0",
++ "reason": "community.post.create is renamed space.post.write (plan T10, D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
},
{
"id": "community.post-write-result",
"replacement": "space.post-write-result@1",
"since": "0.82.0",
-- "removeAfter": "0.84.0",
-- "reason": "community.post.create is renamed space.post.write (plan T10, D3)"
++ "removeAfter": "1.0.0",
++ "reason": "community.post.create is renamed space.post.write (plan T10, D3); retired in 0.110.0 when Space launched, kept loadable for historical data and compatibility"
++ },
++ {
++ "id": "community.thread.created",
++ "replacement": "space.thread.created@1",
++ "since": "0.110.0",
++ "removeAfter": "1.0.0",
++ "reason": "Community forum producer retired after the move to OpenVibe.Space; historical payload remains loadable"
++ },
++ {
++ "id": "community.post.created",
++ "replacement": "space.post.created@1",
++ "since": "0.110.0",
++ "removeAfter": "1.0.0",
++ "reason": "Community forum producer retired after the move to OpenVibe.Space; historical payload remains loadable"
}
]
}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 247e6cf3..cbb93ad5 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -4590,7 +4590,7 @@
"schema": "events/payloads/community.thread.created.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "retired"
},
{
"id": "community.post.created",
@@@ -4599,7 +4599,7 @@
"schema": "events/payloads/community.post.created.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "retired"
},
{
"id": "community.comment.created",
@@@ -5007,7 +5007,7 @@
"schema": "community/post-write-request.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.pulse-write-result",
@@@ -5025,7 +5025,7 @@
"schema": "community/forum-post.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.forum-thread",
@@@ -5034,7 +5034,7 @@
"schema": "community/forum-thread.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.paste-create-result",
@@@ -5052,7 +5052,7 @@
"schema": "community/post-write-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.pulse-write-request",
@@@ -5106,7 +5106,7 @@
"schema": "community/forum-space.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.pulse-item",
@@@ -5133,7 +5133,7 @@
"schema": "community/space-read-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "community.thread-read-result",
@@@ -5142,7 +5142,7 @@
"schema": "community/thread-read-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "deprecated"
++ "status": "retired"
},
{
"id": "space.forum-post",
@@@ -6229,6 +6229,34 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-034"
++ },
++ {
++ "id": "space.thread.created",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "events/payloads/space.thread.created.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.post.created",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "events/payloads/space.post.created.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.moderation.action",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "events/payloads/space.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
}
]
}
ddiff --git a/contracts/events/payloads/space.moderation.action.v1.json b/contracts/events/payloads/space.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..2994b720
---- /dev/null
++++ b/contracts/events/payloads/space.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.moderation.action.v1.json",
++ "title": "SpaceModerationActionPayload",
++ "description": "space.moderation.action v1 (ADR-022; OpenVibe.Space server/events.js moderationAction). A staff or space moderator action on someone else's forum content, or a space moderator assignment: deleting, locking or unlocking a thread, deleting a post, adding or removing a space moderator. Not a person acting on their own content. Written to Space's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the moderator. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/space.post.created.v1.json b/contracts/events/payloads/space.post.created.v1.json
nnew file mode 100644
iindex 00000000..0869f631
---- /dev/null
++++ b/contracts/events/payloads/space.post.created.v1.json
@@@ -0,0 +1,55 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.post.created.v1.json",
++ "title": "SpacePostCreatedPayload",
++ "description": "space.post.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A reply was posted in a forum thread. Never the body. Envelope: subject { type: post, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.",
++ "type": "object",
++ "required": [
++ "post_id",
++ "thread_id",
++ "space",
++ "author",
++ "visibility",
++ "url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "post_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "thread_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
++ },
++ "author": {
++ "oneOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "hidden"
++ ]
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "The public page, or null when the item is not public."
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/space.thread.created.v1.json b/contracts/events/payloads/space.thread.created.v1.json
nnew file mode 100644
iindex 00000000..98b90d7d
---- /dev/null
++++ b/contracts/events/payloads/space.thread.created.v1.json
@@@ -0,0 +1,50 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.thread.created.v1.json",
++ "title": "SpaceThreadCreatedPayload",
++ "description": "space.thread.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A forum thread was started in a space. Envelope: subject { type: thread, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.",
++ "type": "object",
++ "required": [
++ "thread_id",
++ "space",
++ "author",
++ "visibility",
++ "url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "thread_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
++ },
++ "author": {
++ "oneOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "hidden"
++ ]
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "The public page, or null when the item is not public."
++ }
++ }
++}
ddiff --git a/contracts/space/post-write-request.v1.json b/contracts/space/post-write-request.v1.json
iindex 22136030..c834a284 100644
---- a/contracts/space/post-write-request.v1.json
++++ b/contracts/space/post-write-request.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/space/post-write-request.v1.json",
"title": "SpacePostWriteRequest",
-- "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, …).",
++ "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, 403 space.blocked, …); see docs/space-problem-codes.md.",
"anyOf": [
{
"type": "object",
ddiff --git a/docs/space-problem-codes.md b/docs/space-problem-codes.md
nnew file mode 100644
iindex 00000000..503e671d
---- /dev/null
++++ b/docs/space-problem-codes.md
@@@ -0,0 +1,25 @@
++# OpenVibe.Space problem codes
++
++Space returns `errors.problem@1` (`application/problem+json`). Its `code` is stable and its default `type` is `https://openvibe.network/problems/<code>`. Space's `server/forum/service.js`, `server/identity/blocks.js`, `server/forum/routes.js`, and `server/events-consumer.js` issue these `space.*` codes:
++
++| Status | Code | Meaning |
++| --- | --- | --- |
++| 403 | `space.blocked` | The author or moderator target has a blocking relationship with the caller. This also refuses adding a space moderator when either person blocked the other. |
++| 404 | `space.not_found` | The space or requested parent space does not exist, or a staff space is hidden from the caller. |
++| 403 | `space.staff_only` | Only discussion staff can post in this space. |
++| 403 | `space.staff_threads` | Only staff can start a Roadmap thread. |
++| 400 | `space.invalid_name` | A space needs a valid name. |
++| 400 | `space.invalid_style` | The style must be `feed` or `forum`. |
++| 400 | `space.invalid_kind` | The kind must be `discussion`, `request`, or `roadmap`. |
++| 400 | `space.invalid_parent` | The parent creates a cycle or nests boards too deeply. |
++| 400 | `space.invalid_slug` | The requested slug is invalid or reserved. |
++| 409 | `space.slug_taken` | The requested slug already belongs to a space. |
++| 403 | `space.reactions_off` | Ratings are off in this space. |
++| 403 | `space.votes_off` | Votes are off in this space. |
++| 403 | `space.internal_only` | The Events delivery route only accepts local requests. |
++| 503 | `space.events_disabled` | The Events delivery secret is not configured. |
++| 401 | `space.bad_signature` | The Events delivery signature is invalid or outside its replay window. |
++| 400 | `space.bad_delivery` | The Events delivery body is malformed. |
++| 500 | `space.event_failed` | Processing an Events delivery failed; Events may retry it. |
++
++`community.blocked` remains a Community code for its surviving comment and paste routes. Space uses its own `space.blocked` namespace for forum operations.
ddiff --git a/fixtures/errors.problem/valid/space-blocked.json b/fixtures/errors.problem/valid/space-blocked.json
nnew file mode 100644
iindex 00000000..6e732b66
---- /dev/null
++++ b/fixtures/errors.problem/valid/space-blocked.json
@@@ -0,0 +1,7 @@
++{
++ "type": "https://openvibe.network/problems/space.blocked",
++ "title": "Forbidden",
++ "status": 403,
++ "code": "space.blocked",
++ "detail": "One of you blocked the other"
++}
ddiff --git a/fixtures/space.moderation.action/invalid/carries-content.json b/fixtures/space.moderation.action/invalid/carries-content.json
nnew file mode 100644
iindex 00000000..aa003c41
---- /dev/null
++++ b/fixtures/space.moderation.action/invalid/carries-content.json
@@@ -0,0 +1,9 @@
++{
++ "action": "post.deleted",
++ "target": {
++ "type": "post",
++ "id": "31"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "content": "private body"
++}
ddiff --git a/fixtures/space.moderation.action/valid/space-moderator-added.json b/fixtures/space.moderation.action/valid/space-moderator-added.json
nnew file mode 100644
iindex 00000000..d2502c82
---- /dev/null
++++ b/fixtures/space.moderation.action/valid/space-moderator-added.json
@@@ -0,0 +1,7 @@
++{
++ "action": "space.moderator_added",
++ "target": { "type": "space", "id": "general", "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": null,
++ "details": {}
++}
ddiff --git a/fixtures/space.moderation.action/valid/space-moderator-removed.json b/fixtures/space.moderation.action/valid/space-moderator-removed.json
nnew file mode 100644
iindex 00000000..04c44a10
---- /dev/null
++++ b/fixtures/space.moderation.action/valid/space-moderator-removed.json
@@@ -0,0 +1,7 @@
++{
++ "action": "space.moderator_removed",
++ "target": { "type": "space", "id": "general", "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": null,
++ "details": {}
++}
ddiff --git a/fixtures/space.moderation.action/valid/thread-locked.json b/fixtures/space.moderation.action/valid/thread-locked.json
nnew file mode 100644
iindex 00000000..cafc0865
---- /dev/null
++++ b/fixtures/space.moderation.action/valid/thread-locked.json
@@@ -0,0 +1,11 @@
++{
++ "action": "thread.locked",
++ "target": {
++ "type": "thread",
++ "id": "31",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": null,
++ "details": {}
++}
ddiff --git a/fixtures/space.post.created/invalid/carries-content.json b/fixtures/space.post.created/invalid/carries-content.json
nnew file mode 100644
iindex 00000000..ded71ee9
---- /dev/null
++++ b/fixtures/space.post.created/invalid/carries-content.json
@@@ -0,0 +1,12 @@
++{
++ "post_id": 31,
++ "thread_id": 7,
++ "space": "general",
++ "author": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "visibility": "public",
++ "url": "https://openvibe.space/s/general/7#p31",
++ "body": "the content never travels"
++}
ddiff --git a/fixtures/space.post.created/valid/example.json b/fixtures/space.post.created/valid/example.json
nnew file mode 100644
iindex 00000000..1e9ec637
---- /dev/null
++++ b/fixtures/space.post.created/valid/example.json
@@@ -0,0 +1,11 @@
++{
++ "post_id": 31,
++ "thread_id": 7,
++ "space": "general",
++ "author": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "visibility": "public",
++ "url": "https://openvibe.space/s/general/7#p31"
++}
ddiff --git a/fixtures/space.thread.created/invalid/carries-content.json b/fixtures/space.thread.created/invalid/carries-content.json
nnew file mode 100644
iindex 00000000..a4d0a0fa
---- /dev/null
++++ b/fixtures/space.thread.created/invalid/carries-content.json
@@@ -0,0 +1,11 @@
++{
++ "thread_id": 7,
++ "space": "general",
++ "author": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "visibility": "public",
++ "url": "https://openvibe.space/s/general/7",
++ "body": "the content never travels"
++}
ddiff --git a/fixtures/space.thread.created/valid/example.json b/fixtures/space.thread.created/valid/example.json
nnew file mode 100644
iindex 00000000..c8927aa0
---- /dev/null
++++ b/fixtures/space.thread.created/valid/example.json
@@@ -0,0 +1,10 @@
++{
++ "thread_id": 7,
++ "space": "general",
++ "author": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "visibility": "public",
++ "url": "https://openvibe.space/s/general/7"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 05fc56a6..aa1987c2 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -49830,7 +49830,7 @@
"community.thread.created": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/community.thread.created.v1.json",
@@@ -49885,7 +49885,7 @@
"community.post.created": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/community.post.created.v1.json",
@@@ -56611,7 +56611,7 @@
"community.post-write-request": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/post-write-request.v1.json",
@@@ -56778,7 +56778,7 @@
"community.forum-post": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-post.v1.json",
@@@ -56973,7 +56973,7 @@
"community.forum-thread": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-thread.v1.json",
@@@ -57258,7 +57258,7 @@
"community.post-write-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/post-write-result.v1.json",
@@@ -57847,7 +57847,7 @@
"community.forum-space": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-space.v1.json",
@@@ -58149,7 +58149,7 @@
"community.space-read-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/space-read-result.v1.json",
@@@ -58224,7 +58224,7 @@
"community.thread-read-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/thread-read-result.v1.json",
@@@ -59034,7 +59034,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/space/post-write-request.v1.json",
"title": "SpacePostWriteRequest",
-- "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, …).",
++ "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, 403 space.blocked, …); see docs/space-problem-codes.md.",
"anyOf": [
{
"type": "object",
@@@ -72453,6 +72453,133 @@
}
}
}
++ },
++ "space.thread.created": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.thread.created.v1.json",
++ "title": "SpaceThreadCreatedPayload",
++ "description": "space.thread.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A forum thread was started in a space. Envelope: subject { type: thread, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.",
++ "type": "object",
++ "required": [
++ "thread_id",
++ "space",
++ "author",
++ "visibility",
++ "url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "thread_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
++ },
++ "author": {
++ "oneOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "hidden"
++ ]
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "The public page, or null when the item is not public."
++ }
++ }
++ }
++ },
++ "space.post.created": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.post.created.v1.json",
++ "title": "SpacePostCreatedPayload",
++ "description": "space.post.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A reply was posted in a forum thread. Never the body. Envelope: subject { type: post, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.",
++ "type": "object",
++ "required": [
++ "post_id",
++ "thread_id",
++ "space",
++ "author",
++ "visibility",
++ "url"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "post_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "thread_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "space": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
++ },
++ "author": {
++ "oneOf": [
++ {
++ "$ref": "../../identity/subject-ref.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "hidden"
++ ]
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500,
++ "description": "The public page, or null when the item is not public."
++ }
++ }
++ }
++ },
++ "space.moderation.action": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/space.moderation.action.v1.json",
++ "title": "SpaceModerationActionPayload",
++ "description": "space.moderation.action v1 (ADR-022; OpenVibe.Space server/events.js moderationAction). A staff or space moderator action on someone else's forum content, or a space moderator assignment: deleting, locking or unlocking a thread, deleting a post, adding or removing a space moderator. Not a person acting on their own content. Written to Space's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the moderator. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
}
}
}
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex b66142e4..43753796 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.109.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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"
@@@ -12,7 +12,7 @@
},
"servers": [
{
-- "url": "https://ai.openvibe.network"
++ "url": "https://ai.openvibe.services"
}
],
"paths": {
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 7a861ae6..a9306e1d 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.109.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 286f57ff..7c10fb62 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.109.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/bot.json b/generated/openapi/bot.json
iindex f38a36c3..030f5b23 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.109.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 5c69f899..32ce7260 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.109.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 e024e66b..e0aca3d9 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.109.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 4eda6549..025c3ea4 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.109.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 98cafafe..8857300e 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.109.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 7b0a6605..5112038f 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.109.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 c3b7e7a5..dc373676 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.109.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 079ffa8d..8554e27d 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.109.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 96a8e71b..502db92a 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.109.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 04d3f040..9bce5d0b 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -4,7 +4,7 @@
{
"service": "ai",
"name": "OpenVibe.AI",
-- "origin": "https://ai.openvibe.network",
++ "origin": "https://ai.openvibe.services",
"operations": 43,
"capabilities": 8
},
@@@ -159,8 +159,8 @@
"service": "space",
"name": "OpenVibe.Space",
"origin": "https://openvibe.space",
-- "operations": 15,
-- "capabilities": 3
++ "operations": 30,
++ "capabilities": 4
},
{
"service": "tips",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex ce1f19f0..1319b1d9 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.109.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 eb4897af..07924ec8 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.109.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 90e9967f..186f1e2c 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.109.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/news.json b/generated/openapi/news.json
iindex 7aca1859..e2adc977 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.109.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 6ca0c1dc..fddb5d39 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.109.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 3ae09d44..96466273 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.109.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/run.json b/generated/openapi/run.json
iindex 5c54981c..69a37b13 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.109.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 bca56c38..f55fcafc 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.109.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/services.json b/generated/openapi/services.json
iindex eda49d10..ecb0ccd9 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.109.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 1bb8e225..30d9aa58 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.109.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/space.json b/generated/openapi/space.json
iindex 1fa4a9c8..7bbc19f1 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.109.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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"
@@@ -139,9 +139,592 @@
}
}
},
++ "/api/v1/posts/{id}/reactions": {
++ "post": {
++ "operationId": "post_api_v1_posts_id_reactions",
++ "summary": "Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Sub",
++ "description": "**space.post.write** (public): Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Subject) or as origin \"ai\" (no author, labelled as AI). Space visibility (public, members, staff), locks and limits are checked by OpenVibe.Space.",
++ "tags": [
++ "space.post"
++ ],
++ "x-openvibe-capabilities": [
++ "space.post.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.post.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/space-groups": {
++ "get": {
++ "operationId": "get_api_v1_space_groups",
++ "summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
++ "description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.forum-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/space-groups/{group}": {
++ "put": {
++ "operationId": "put_api_v1_space_groups_group",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "group",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/spaces": {
"get": {
-- "operationId": "get_api_v1_spaces",
++ "operationId": "get_api_v1_spaces",
++ "summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
++ "description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.forum-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_spaces",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}": {
++ "get": {
++ "operationId": "get_api_v1_spaces_space",
++ "summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
++ "description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.forum-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/attachments": {
++ "post": {
++ "operationId": "post_api_v1_spaces_space_attachments",
++ "summary": "Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Sub",
++ "description": "**space.post.write** (public): Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Subject) or as origin \"ai\" (no author, labelled as AI). Space visibility (public, members, staff), locks and limits are checked by OpenVibe.Space.",
++ "tags": [
++ "space.post"
++ ],
++ "x-openvibe-capabilities": [
++ "space.post.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.post.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/categories": {
++ "get": {
++ "operationId": "get_api_v1_spaces_space_categories",
++ "summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
++ "description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.forum-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/categories/{category}": {
++ "delete": {
++ "operationId": "delete_api_v1_spaces_space_categories_category",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "category",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_api_v1_spaces_space_categories_category",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "category",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/members-only": {
++ "put": {
++ "operationId": "put_api_v1_spaces_space_members_only",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/moderators": {
++ "get": {
++ "operationId": "get_api_v1_spaces_space_moderators",
"summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
"description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
"tags": [
@@@ -161,6 +744,16 @@
]
}
],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
"responses": {
"2XX": {
"description": "Success",
@@@ -185,25 +778,25 @@
}
}
},
-- "/api/v1/spaces/{space}": {
-- "get": {
-- "operationId": "get_api_v1_spaces_space",
-- "summary": "List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum).",
-- "description": "**space.forum.read** (public): List the spaces the caller can open and read one space's metadata (renamed from community.space.read, plan T10 D3: OpenVibe.Space owns the forum). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Space checks space visibility itself.",
++ "/api/v1/spaces/{space}/moderators/{subject}": {
++ "delete": {
++ "operationId": "delete_api_v1_spaces_space_moderators_subject",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
"tags": [
"space.forum"
],
"x-openvibe-capabilities": [
-- "space.forum.read"
++ "space.forum.manage"
],
"x-openvibe-visibility": [
-- "public"
++ "internal"
],
-- "x-openvibe-quota-class": "space-read",
++ "x-openvibe-quota-class": "space-moderate",
"security": [
{
"openvibeToken": [
-- "space.forum.read"
++ "space.forum.manage"
]
}
],
@@@ -215,18 +808,123 @@
"schema": {
"type": "string"
}
++ },
++ {
++ "name": "subject",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
}
],
"responses": {
"2XX": {
-- "description": "Success",
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
"content": {
-- "application/json": {
++ "application/problem+json": {
"schema": {
-- "$ref": "#/components/schemas/space.forum-read-result.v1"
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_api_v1_spaces_space_moderators_subject",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "subject",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
}
}
}
++ }
++ }
++ }
++ },
++ "/api/v1/spaces/{space}/settings": {
++ "put": {
++ "operationId": "put_api_v1_spaces_space_settings",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
},
"default": {
"description": "An error (RFC 9457 problem details)",
@@@ -245,7 +943,7 @@
"get": {
"operationId": "get_api_v1_spaces_space_threads",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -430,7 +1128,7 @@
"get": {
"operationId": "get_api_v1_spaces_space_threads_slug",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -490,6 +1188,80 @@
}
}
},
++ "/api/v1/spaces/{space}/threads/{slug}/crosspost": {
++ "post": {
++ "operationId": "post_api_v1_spaces_space_threads_slug_crosspost",
++ "summary": "Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Sub",
++ "description": "**space.post.write** (public): Forum writes as a service (renamed from community.post.create, plan T10 D3): create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Subject) or as origin \"ai\" (no author, labelled as AI). Space visibility (public, members, staff), locks and limits are checked by OpenVibe.Space.",
++ "tags": [
++ "space.post"
++ ],
++ "x-openvibe-capabilities": [
++ "space.post.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.post.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "slug",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.post-write-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/spaces/{space}/threads/{slug}/posts": {
"post": {
"operationId": "post_api_v1_spaces_space_threads_slug_posts",
@@@ -564,6 +1336,63 @@
}
}
},
++ "/api/v1/spaces/{space}/threads/{slug}/state": {
++ "put": {
++ "operationId": "put_api_v1_spaces_space_threads_slug_state",
++ "summary": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members.",
++ "description": "**space.forum.manage** (internal): Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
++ "tags": [
++ "space.forum"
++ ],
++ "x-openvibe-capabilities": [
++ "space.forum.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "space-moderate",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.forum.manage"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "slug",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/spaces/{space}/threads/{slug}/votes": {
"post": {
"operationId": "post_api_v1_spaces_space_threads_slug_votes",
@@@ -688,7 +1517,7 @@
"get": {
"operationId": "get_s_space",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -744,7 +1573,7 @@
"get": {
"operationId": "get_s_space_feed_xml",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -800,7 +1629,7 @@
"get": {
"operationId": "get_s_space_t_slug",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -864,7 +1693,7 @@
"get": {
"operationId": "get_s_feed_xml",
"summary": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3).",
-- "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "**space.thread.read** (public): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"tags": [
"space.thread"
],
@@@ -1621,7 +2450,7 @@
},
"space.post-write-request.v1": {
"title": "SpacePostWriteRequest",
-- "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, …).",
++ "description": "space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, 403 space.blocked, …); see docs/space-problem-codes.md.",
"anyOf": [
{
"type": "object",
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 61d5b8e7..0b98b99f 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.109.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 a80fb961..7c77d296 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.109.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 863f5a7e..dacd9139 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.109.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 792742c5..3bac1d66 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.109.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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/watch.json b/generated/openapi/watch.json
iindex acc689b8..ae053a71 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.109.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 c8105a43..2620f99b 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.109.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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.110.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.110.0): each operation lists the capabilities it performs; a token for 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 a54bf774..b5fc1b66 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -53417,7 +53417,7 @@ export interface SpaceForumThread {
/** [email protected] (owner: space) */
/**
-- * space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, …).
++ * space.post-write-request@1: bodies of the forum writes on OpenVibe.Space (space.post.write; a service writes as X-OV-Subject, or as AI with X-OV-Origin: ai). POST /api/v1/spaces/:space/threads: { title, body, members_only?, category?, attachments?, pastes? } (title 3 to 200 characters after whitespace is collapsed; body Markdown, non-blank, at most 40000 characters; body_markdown is accepted for body). POST …/threads/:slug/posts (a reply) and PUT /api/v1/posts/:id (an edit): { body, attachments?, pastes? }. POST …/threads/:slug/votes: { value: 1 | -1 | 0 }. DELETE /api/v1/posts/:id and DELETE /api/v1/spaces/:space/threads/:slug take no body. Unknown fields are ignored; refusals are problem+json (post.empty, post.too_long, thread.invalid_title, vote.invalid, thread.locked, vip.members_only, 403 space.blocked, …); see docs/space-problem-codes.md.
*/
export type SpacePostWriteRequest =
| {
@@@ -58669,3 +58669,65 @@ export interface NetworkNodeRevokedPayload {
*/
at: string;
}
++
++/** [email protected] (owner: space) */
++/**
++ * space.thread.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A forum thread was started in a space. Envelope: subject { type: thread, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.
++ */
++export interface SpaceThreadCreatedPayload {
++ thread_id: number;
++ space: string;
++ author: SubjectRef | null;
++ visibility: "public" | "members" | "hidden";
++ /**
++ * The public page, or null when the item is not public.
++ */
++ url: string | null;
++}
++
++/** [email protected] (owner: space) */
++/**
++ * space.post.created v1 (OpenVibe.Space server/events.js, queued in the transaction of the change). A reply was posted in a forum thread. Never the body. Envelope: subject { type: post, id }, visibility public when the item is public else internal, priority low, actor the person or service:space.
++ */
++export interface SpacePostCreatedPayload {
++ post_id: number;
++ thread_id: number;
++ space: string;
++ author: SubjectRef | null;
++ visibility: "public" | "members" | "hidden";
++ /**
++ * The public page, or null when the item is not public.
++ */
++ url: string | null;
++}
++
++/** [email protected] (owner: space) */
++/**
++ * space.moderation.action v1 (ADR-022; OpenVibe.Space server/events.js moderationAction). A staff or space moderator action on someone else's forum content, or a space moderator assignment: deleting, locking or unlocking a thread, deleting a post, adding or removing a space moderator. Not a person acting on their own content. Written to Space's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the moderator. Never carries the content itself.
++ */
++export interface SpaceModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
ddiff --git a/manifests/capabilities/community.post.create.json b/manifests/capabilities/community.post.create.json
iindex 877173bd..3feb375b 100644
---- a/manifests/capabilities/community.post.create.json
++++ b/manifests/capabilities/community.post.create.json
@@@ -2,10 +2,10 @@
"id": "community.post.create",
"version": "1.1",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"visibility": "public",
"sensitive": true,
-- "description": "DEPRECATED in favour of space.post.write (plan T10, D3): Forum writes as a service: create threads and replies in a space, vote on threads, and edit or delete one's own posts, as the acting subject (X-OV-Subject) or as origin \"ai\" (no author, labelled as AI). Space visibility (public, members, staff), locks and limits are checked by Community.",
++ "description": "RETIRED at 0.110.0: OpenVibe.Community no longer serves this forum capability. Use space.post.write on OpenVibe.Space. Community /s/* paths redirect permanently to openvibe.space.",
"outputSchema": "community.post-write-result@1",
"permissions": [
"community:write"
@@@ -14,16 +14,7 @@
"space_member"
],
"quotaClass": "community-write",
-- "events": [
-- "community.thread.created",
-- "community.post.created"
-- ],
-- "implementedBy": [
-- "POST /api/v1/spaces/:space/threads",
-- "POST /api/v1/spaces/:space/threads/:slug/posts",
-- "POST /api/v1/spaces/:space/threads/:slug/votes",
-- "PUT|DELETE /api/v1/posts/:id",
-- "DELETE /api/v1/spaces/:space/threads/:slug"
-- ],
++ "events": [],
++ "implementedBy": [],
"inputSchema": "community.post-write-request@1"
}
ddiff --git a/manifests/capabilities/community.space.manage.json b/manifests/capabilities/community.space.manage.json
iindex 4a736ada..26371ddc 100644
---- a/manifests/capabilities/community.space.manage.json
++++ b/manifests/capabilities/community.space.manage.json
@@@ -2,9 +2,9 @@
"id": "community.space.manage",
"version": "1.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"visibility": "internal",
-- "description": "DEPRECATED in favour of space.forum.manage (plan T10, D3): PLANNED: staff administration of spaces: create, rename, set visibility, and gate a space to one creator's VIP members. Only the members-only gate exists today (checked with community.comment.moderate plus a moderator); spaces are seeded by Community's database setup and nothing creates or edits them.",
++ "description": "RETIRED at 0.110.0: OpenVibe.Community no longer serves this forum capability. Use space.forum.manage on OpenVibe.Space. Community /s/* paths redirect permanently to openvibe.space.",
"permissions": [
"community:moderate"
],
@@@ -13,7 +13,5 @@
],
"quotaClass": "community-moderate",
"events": [],
-- "implementedBy": [
-- "PUT /api/v1/spaces/:space/members-only"
-- ]
++ "implementedBy": []
}
ddiff --git a/manifests/capabilities/community.space.read.json b/manifests/capabilities/community.space.read.json
iindex 177625da..1dbc43e1 100644
---- a/manifests/capabilities/community.space.read.json
++++ b/manifests/capabilities/community.space.read.json
@@@ -2,9 +2,9 @@
"id": "community.space.read",
"version": "1.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"visibility": "public",
-- "description": "DEPRECATED in favour of space.forum.read (plan T10, D3): List the spaces the caller can open and read one space's metadata (roadmap §30.2 community.space.*). Anonymous callers see public spaces, signed-in people also members spaces, moderators also staff spaces; members-only (VIP) spaces are flagged. Served to anyone; Community checks space visibility itself.",
++ "description": "RETIRED at 0.110.0: OpenVibe.Community no longer serves this forum capability. Use space.forum.read on OpenVibe.Space. Community /s/* paths redirect permanently to openvibe.space.",
"outputSchema": "community.space-read-result@1",
"permissions": [
"community:read"
@@@ -14,10 +14,6 @@
],
"quotaClass": "community-read",
"events": [],
-- "implementedBy": [
-- "GET /api/v1/spaces",
-- "GET /api/v1/spaces/:space",
-- "GET /s"
-- ],
++ "implementedBy": [],
"inputSchema": "common.no-body@1"
}
ddiff --git a/manifests/capabilities/community.thread.read.json b/manifests/capabilities/community.thread.read.json
iindex a9e11dc3..1ed9ffc3 100644
---- a/manifests/capabilities/community.thread.read.json
++++ b/manifests/capabilities/community.thread.read.json
@@@ -2,9 +2,9 @@
"id": "community.thread.read",
"version": "1.0",
"owner": "community",
-- "status": "deprecated",
++ "status": "retired",
"visibility": "public",
-- "description": "DEPRECATED in favour of space.thread.read (plan T10, D3): Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds. Community checks space visibility and the VIP gate (403 vip.members_only). Creating threads and replies stays community.post.create.",
++ "description": "RETIRED at 0.110.0: OpenVibe.Community no longer serves this forum capability. Use space.thread.read on OpenVibe.Space. Community /s/* paths redirect permanently to openvibe.space.",
"outputSchema": "community.thread-read-result@1",
"permissions": [
"community:read"
@@@ -14,13 +14,6 @@
],
"quotaClass": "community-read",
"events": [],
-- "implementedBy": [
-- "GET /api/v1/spaces/:space/threads",
-- "GET /api/v1/spaces/:space/threads/:slug",
-- "GET /s/:space",
-- "GET /s/:space/t/:slug",
-- "GET /s/feed.xml",
-- "GET /s/:space/feed.xml"
-- ],
++ "implementedBy": [],
"inputSchema": "common.no-body@1"
}
ddiff --git a/manifests/capabilities/community.vote.set.json b/manifests/capabilities/community.vote.set.json
iindex ef4a1c45..b130527d 100644
---- a/manifests/capabilities/community.vote.set.json
++++ b/manifests/capabilities/community.vote.set.json
@@@ -4,19 +4,17 @@
"owner": "community",
"status": "planned",
"visibility": "public",
-- "description": "PLANNED as its own grant: up-vote, down-vote or clear a vote (value 1, -1 or 0; 0 removes it, so there is no separate vote.remove) on a thread or a comment as the acting person (roadmap §30.2 community.vote.set|remove). Locked threads refuse; 60 votes a minute per person. The routes serve production today, guarded by space.post.write (threads) and community.comment.write (comments), which keep covering them.",
++ "description": "PLANNED as its own grant for Community comments: up-vote, down-vote or clear a vote (value 1, -1 or 0; 0 removes it). Community.comment.write guards the comment vote route today. Forum thread votes moved to OpenVibe.Space and are guarded there by space.post.write.",
"inputSchema": "common.entity-ref@1",
"permissions": [
"community:vote"
],
"resourceConstraints": [
-- "space_member"
++ "none"
],
"quotaClass": "community-vote",
"events": [],
"implementedBy": [
-- "POST /api/v1/spaces/:space/threads/:slug/votes",
-- "POST /api/v1/comments/:commentId/votes",
-- "POST /s/:space/t/:slug/vote"
++ "POST /api/v1/comments/:commentId/votes"
]
}
ddiff --git a/manifests/capabilities/space.forum.manage.json b/manifests/capabilities/space.forum.manage.json
iindex 7a1f8b2a..47c5b7fe 100644
---- a/manifests/capabilities/space.forum.manage.json
++++ b/manifests/capabilities/space.forum.manage.json
@@@ -2,9 +2,9 @@
"id": "space.forum.manage",
"version": "1.0",
"owner": "space",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "PLANNED: staff administration of forum spaces: create, rename, set visibility, and gate a space to one creator's VIP members (renamed from community.space.manage, plan T10 D3). Nothing creates or edits spaces yet; OpenVibe.Space is not built.",
++ "description": "Manage forum spaces and moderation in OpenVibe.Space: create spaces, update settings and categories, manage moderators, and gate spaces to creator VIP members. Service callers need space.forum.manage; people need discussion staff or a space moderator where supported. PUT /api/v1/spaces/:space/moderators/:subject refuses a block in either direction with 403 space.blocked (errors.problem@1). Other Space problem codes are documented in docs/space-problem-codes.md.",
"permissions": [
"space:moderate"
],
@@@ -12,8 +12,16 @@
"none"
],
"quotaClass": "space-moderate",
-- "events": [],
++ "events": [
++ "space.moderation.action"
++ ],
"implementedBy": [
-- "PUT /api/v1/spaces/:space/members-only"
++ "POST /api/v1/spaces",
++ "PUT /api/v1/spaces/:space/settings",
++ "PUT /api/v1/spaces/:space/members-only",
++ "PUT|DELETE /api/v1/spaces/:space/categories/:category",
++ "PUT|DELETE /api/v1/spaces/:space/moderators/:subject",
++ "PUT /api/v1/spaces/:space/threads/:slug/state",
++ "PUT /api/v1/space-groups/:group"
]
}
ddiff --git a/manifests/capabilities/space.forum.read.json b/manifests/capabilities/space.forum.read.json
iindex 522c8aea..e557a10c 100644
---- a/manifests/capabilities/space.forum.read.json
++++ b/manifests/capabilities/space.forum.read.json
@@@ -17,7 +17,10 @@
"implementedBy": [
"GET /api/v1/spaces",
"GET /api/v1/spaces/:space",
-- "GET /s"
++ "GET /s",
++ "GET /api/v1/spaces/:space/categories",
++ "GET /api/v1/spaces/:space/moderators",
++ "GET /api/v1/space-groups"
],
"inputSchema": "common.no-body@1"
}
ddiff --git a/manifests/capabilities/space.post.write.json b/manifests/capabilities/space.post.write.json
iindex d022c64f..37db01a0 100644
---- a/manifests/capabilities/space.post.write.json
++++ b/manifests/capabilities/space.post.write.json
@@@ -14,13 +14,19 @@
"space_member"
],
"quotaClass": "space-write",
-- "events": [],
++ "events": [
++ "space.thread.created",
++ "space.post.created"
++ ],
"implementedBy": [
"POST /api/v1/spaces/:space/threads",
"POST /api/v1/spaces/:space/threads/:slug/posts",
"POST /api/v1/spaces/:space/threads/:slug/votes",
"PUT|DELETE /api/v1/posts/:id",
-- "DELETE /api/v1/spaces/:space/threads/:slug"
++ "DELETE /api/v1/spaces/:space/threads/:slug",
++ "POST /api/v1/spaces/:space/attachments",
++ "POST /api/v1/spaces/:space/threads/:slug/crosspost",
++ "POST /api/v1/posts/:id/reactions"
],
"inputSchema": "space.post-write-request@1"
}
ddiff --git a/manifests/capabilities/space.thread.read.json b/manifests/capabilities/space.thread.read.json
iindex a88231ee..4975856c 100644
---- a/manifests/capabilities/space.thread.read.json
++++ b/manifests/capabilities/space.thread.read.json
@@@ -4,7 +4,7 @@
"owner": "space",
"status": "active",
"visibility": "public",
-- "description": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write and space.thread.write.",
++ "description": "Read a space's threads (hot, new or top, paged) and one thread with its posts, plus the RSS feeds (renamed from community.thread.read, plan T10 D3). Space checks space visibility and the VIP gate (403 vip.members_only); writing threads and replies is space.post.write.",
"outputSchema": "space.thread-read-result@1",
"permissions": [
"space:read"
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex a30dda34..2ae36ae1 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -8,16 +8,17 @@
"status": "alpha",
"repository": "OpenVibers/OpenVibe.AI",
"domains": [
++ "ai.openvibe.services",
"ai.openvibe.network"
],
-- "publicOrigin": "https://ai.openvibe.network",
++ "publicOrigin": "https://ai.openvibe.services",
"health": "/api/health",
"ready": "/api/ready",
"internalOrigin": "http://127.0.0.1:4700",
"exposure": {
-- "state": "internal",
-- "publicSite": null,
-- "note": "called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page"
++ "state": "live",
++ "publicSite": "service",
++ "note": "the public home, /stats and the developer-app API (ai.app.run) at ai.openvibe.services since 2026-10-07 (AI #23); ai.openvibe.network answers 301 to it; services on the host call 127.0.0.1:4700"
},
"capabilities": [
"ai.app.run",
@@@ -76,14 +77,14 @@
],
"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
-- "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
},
"contracts": {
"range": ">=0.6.0 <1.0.0"
},
"leases": {
-- "none": "no claims: one process owns ai.db (better-sqlite3, single writer); a run is taken with a compare-and-set on its status (queued → running)"
++ "none": "no claims: one process owns ai.db (better-sqlite3, single writer); a run is taken with a compare-and-set on its status (queued \u2192 running)"
}
},
"notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."
ddiff --git a/manifests/services/community.json b/manifests/services/community.json
iindex 5eac40c2..ee584449 100644
---- a/manifests/services/community.json
++++ b/manifests/services/community.json
@@@ -16,13 +16,9 @@
"community.paste.create",
"community.paste.write",
"community.paste.moderate",
-- "community.post.create",
"community.comment.write",
"community.comment.moderate",
"community.pulse.write",
-- "community.space.read",
-- "community.space.manage",
-- "community.thread.read",
"community.vote.set",
"community.pulse.read"
],
@@@ -33,9 +29,7 @@
"community.moderation.action",
"community.paste.created",
"community.paste.deleted",
-- "community.paste.updated",
-- "community.post.created",
-- "community.thread.created"
++ "community.paste.updated"
],
"eventsConsumed": [
"network.account.deleted",
@@@ -71,7 +65,7 @@
"drains": [
"Pulse subscription retries, the community.profile scan and the search-document scans stopped",
"stops taking connections; idle keep-alive connections closed (kept 65 s otherwise); requests in flight answered with Connection: close and allowed to finish (4 s at most, then cut)",
-- "the profile scan in progress (it stops between people), the Discord relay's drain and the events outbox's send awaited; unsent rows stay in their tables",
++ "the profile scan in progress (it stops between people) and the events outbox's send awaited; unsent rows stay in their tables",
"community.db closed, exit 0"
],
"note": "server/graceful.js; a stop that passes 5 s exits 1. Only the in-memory rate-limit sweeps end with the process. The unit sets no TimeoutStopSec (systemd's 90 s default applies)"
@@@ -84,7 +78,7 @@
},
{
"kind": "schedule",
-- "what": "the Roadmap space re-syncs from docs/roadmap/public.json; the community.profile and search-document scans start again from what changed"
++ "what": "the community.profile and search-document scans restart from what changed"
},
{
"kind": "consumer",
@@@ -108,5 +102,6 @@
"leases": {
"none": "no claims: one process owns community.db (better-sqlite3, single writer)"
}
-- }
++ },
++ "notes": "Community continues to serve pastes, comments, Pulse and submissions. Its former /s/* forum paths issue 301 redirects to openvibe.space; Space owns forum data and writes."
}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex b13b3c9e..a273594a 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -99,6 +99,7 @@
"media.moderation.action",
"news.moderation.action",
"reviews.moderation.action",
++ "space.moderation.action",
"tips.interaction.moderated",
"tools.moderation.action",
"tools.usage.recorded",
ddiff --git a/manifests/services/space.json b/manifests/services/space.json
iindex f88786f8..071271ef 100644
---- a/manifests/services/space.json
++++ b/manifests/services/space.json
@@@ -5,16 +5,15 @@
},
"id": "space",
"name": "OpenVibe.Space",
-- "status": "placeholder",
++ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Space",
"domains": [
"openvibe.space"
],
"publicOrigin": "https://openvibe.space",
"exposure": {
-- "state": "placeholder",
-- "publicSite": "placeholder",
-- "note": "planned: no OpenVibe.Space service yet; openvibe.space still serves the OpenVibe.Sites placeholder. T10 item 13 installs its own vhost"
++ "state": "live",
++ "publicSite": "service"
},
"site": {
"name": "Space",
@@@ -25,19 +24,19 @@
"position": 19,
"tld": "space",
"accent": "#818cf8",
-- "description": "OpenVibe.Space will be the forums of the OpenVibe network: create a forum for any topic, with the depth of classic boards like XenForo and phpBB, the feel of a modern community, and AI help for topics, research and moderation. The forums on OpenVibe.Community move here when it launches.",
++ "description": "OpenVibe.Space is the network forum: browse public and members-only boards, start threads, reply, vote and moderate spaces with one OpenVibe identity.",
"pillars": [
{
"title": "Any forum",
-- "text": "Anyone can start one, for any topic."
++ "text": "Browse boards for discussions, requests and the roadmap."
},
{
"title": "Classic depth",
-- "text": "Sub-forums, threads, ranks, moderation tools."
++ "text": "Child boards, threads, categories and moderation tools."
},
{
-- "title": "AI assist",
-- "text": "Topic ideas, research and summaries, always labelled."
++ "title": "Make it yours",
++ "text": "Moderators can shape boards, categories and visibility."
},
{
"title": "One account",
@@@ -45,50 +44,80 @@
}
],
"keywords": "openvibe space, forums, create a forum, community boards, xenforo alternative, phpbb alternative",
-- "launch": "the forum engine moving over from OpenVibe.Community",
-- "relationships": {
-- "noRepo": true,
-- "meanwhile": [
-- {
-- "label": "OpenVibe.Community",
-- "url": "https://openvibe.community",
-- "note": "Forums, pastes and people live there today."
-- }
-- ]
-- }
++ "launch": "Forums moved from OpenVibe.Community to OpenVibe.Space (plan T10 step 3)."
},
"capabilities": [
"space.forum.read",
"space.forum.manage",
"space.thread.read",
-- "space.thread.write",
-- "space.post.write",
-- "space.pulse.read"
++ "space.post.write"
++ ],
++ "eventsProduced": [
++ "space.thread.created",
++ "space.post.created",
++ "space.moderation.action"
++ ],
++ "eventsConsumed": [
++ "network.user.token_valid_after",
++ "network.block.changed",
++ "network.subject.merged",
++ "network.account.export_requested",
++ "network.account.deleted",
++ "vip.membership.changed"
],
-- "eventsProduced": [],
-- "eventsConsumed": [],
"namespacesOwned": [
"space.*"
],
"lifecycle": {
"liveness": {
-- "none": "planned service: no OpenVibe.Space process runs yet (plan T10 item 3)"
++ "endpoint": "/api/health",
++ "means": "the Space process answers HTTP; /api/ready checks PostgreSQL and reports degraded dependencies"
},
"shutdown": {
-- "none": "planned service: nothing to stop yet"
++ "signal": "SIGTERM",
++ "deadlineSeconds": 5,
++ "drains": [
++ "subscription retries stop; the HTTP server stops accepting requests and gives in-flight requests up to 4 seconds",
++ "the Discord relay and Events outbox finish their current sends; unsent rows remain for the next start",
++ "the PostgreSQL pool closes and the process exits"
++ ],
++ "note": "server/index.js uses openvibe-sdk/service gracefulStop with a 4-second drain and 5-second deadline"
},
"startupRecovery": {
-- "none": "planned service: nothing runs to resume"
++ "resumes": [
++ {
++ "kind": "outbox",
++ "what": "unsent space.* events in the PostgreSQL outbox relay after restart (at least once)"
++ },
++ {
++ "kind": "consumer",
++ "what": "Events subscriptions are recreated and pending network and VIP deliveries are retried idempotently"
++ },
++ {
++ "kind": "schedule",
++ "what": "the Roadmap space resyncs from docs/roadmap/public.json and Discord relay workers restart"
++ }
++ ]
},
"rollback": {
-- "none": "planned service: nothing is deployed to roll back"
++ "conditions": [
++ "automatic: ovhost deploy restores the prior sha if /api/ready fails in its readiness window",
++ "manual: ovhost rollback space --to <sha>"
++ ],
++ "window": "automatic during deploy readiness; manual afterwards while the previous code is database compatible",
++ "blockers": {
++ "none": "the initial PostgreSQL migration creates the Space schema without dropping earlier data"
++ }
},
"contracts": {
"range": ">=0.82.0 <1.0.0"
},
"leases": {
-- "none": "planned service: no process, no claims"
++ "none": "PostgreSQL outbox workers coordinate delivery; no separate service-owned lease is exposed"
}
},
-- "notes": "Planned. Owns the forum subsystem moved from OpenVibe.Community (spaces, threads, posts, votes, categories, reactions, moderation, chat-room attach, Discord relay) in its own database ov_space; the community.space.*/thread.read/post.create capabilities are renamed space.* and the community.space/thread/post contracts are deprecated with space.forum-*/thread-*/post-* replacements (plan T10, D1-D3). The routes these capabilities name are served by Community today and move at T10 item 4."
++ "notes": "Space owns forum spaces, threads, posts, votes, categories, reactions, moderation and the Discord relay in its PostgreSQL database. OpenVibe.Community retains pastes, comments, Pulse and submissions; legacy /s/* pages redirect permanently to openvibe.space.",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4940"
}
ddiff --git a/package-lock.json b/package-lock.json
iindex 902e4480..d62d1134 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.109.0",
++ "version": "0.110.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.109.0",
++ "version": "0.110.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex a99f5eda..86c0c144 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.109.0",
++ "version": "0.110.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",
ddiff --git a/test/run.js b/test/run.js
iindex 51de2a72..e72508c1 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -89,7 +89,8 @@ for (const cap of capabilities.manifests) {
const r = contracts.validate('capabilities.capability@1', cap);
ok(r.valid, `capability ${cap.id}: ${JSON.stringify(r.errors)}`);
ok(serviceIds.has(cap.owner), `${cap.id} owner ${cap.owner} is a registered service`);
-- ok(services.get(cap.owner).capabilities.includes(cap.id), `${cap.owner} manifest lists ${cap.id}`);
++ ok(['planned', 'retired'].includes(cap.status) || services.get(cap.owner).capabilities.includes(cap.id), `${cap.owner} manifest lists served ${cap.id}`);
++ if (cap.status === 'retired') ok(!services.get(cap.owner).capabilities.includes(cap.id), `${cap.owner} no longer offers retired ${cap.id}`);
for (const k of ['inputSchema', 'outputSchema']) if (cap[k]) ok(contracts.resolve(cap[k]), `${cap.id} ${k} resolves`);
ok(cap.status !== 'active' || cap.implementedBy.length > 0, `active ${cap.id} names the route that implements it`);
}
@@@ -121,8 +122,9 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
const major = Number(c.version.split('.')[0]);
ok(c.schema === `events/payloads/${c.id}.v${major}.json`, `${c.id} lives at events/payloads/<event_type>.v<major>.json`);
ok(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$/.test(c.id), `${c.id} is an event type`);
-- ok(services.get(c.owner) && services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} manifest lists ${c.id} in eventsProduced`);
-- ok(['active', 'planned'].includes(c.status), `${c.id} status ${c.status}`);
++ ok(services.get(c.owner) && (c.status === 'retired' || services.get(c.owner).eventsProduced.includes(c.id)), `${c.owner} manifest lists current ${c.id} in eventsProduced`);
++ ok(['active', 'planned', 'retired'].includes(c.status), `${c.id} status ${c.status}`);
++ if (c.status === 'retired') ok(!services.get(c.owner).eventsProduced.includes(c.id), `${c.owner} no longer produces retired ${c.id}`);
const s = contracts.schema(c.id);
ok((s.type === 'object' || s.$ref || s.allOf) && s.title.endsWith('Payload'), `${c.id} is an object payload schema`);
if (s.properties && s.properties.redacts) ok(s.properties.redacts.$ref === '../redaction-directive.v1.json', `${c.id} redacts uses events.redaction-directive@1`);
@@@ -466,7 +468,7 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
const gaps = {
'live.channel.read': 'active', 'live.stream.read': 'active', 'live.discovery.read': 'active', 'live.owner.resolve': 'planned',
'media.upload.create': 'planned', 'media.derivative.create': 'planned', 'media.derivative.read': 'planned', 'media.lifecycle.read': 'planned', 'media.lifecycle.transition': 'planned',
-- 'community.vote.set': 'planned', 'community.pulse.read': 'active', 'space.forum.read': 'active', 'space.forum.manage': 'planned', 'space.thread.read': 'active',
++ 'community.vote.set': 'planned', 'community.pulse.read': 'active', 'space.forum.read': 'active', 'space.thread.read': 'active',
'search.query.run': 'active',
};
for (const [id, status] of Object.entries(gaps)) {
@@@ -475,9 +477,13 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
if (status === 'planned') ok(c.description.startsWith('PLANNED'), `${id}: a planned capability says so first`);
if (status === 'active') ok(c.visibility === 'public' && c.implementedBy.every(r => r.startsWith('GET ')), `${id}: an active §30.2 read is a public GET`);
}
++ ok(capabilities.get('space.forum.manage').status === 'active' && capabilities.get('space.forum.manage').implementedBy.includes('POST /api/v1/spaces'), 'space.forum.manage is served by Space');
++ ok(capabilities.get('space.thread.write').status === 'planned' && !services.get('space').capabilities.includes('space.thread.write'), 'space.thread.write is not a separate grant for the existing create route');
++ ok(capabilities.get('space.pulse.read').status === 'planned' && !services.get('space').capabilities.includes('space.pulse.read'), 'Space does not serve Pulse');
++ for (const id of ['community.space.read', 'community.space.manage', 'community.thread.read', 'community.post.create']) ok(capabilities.get(id).status === 'retired' && !services.get('community').capabilities.includes(id), `${id} retired from Community`);
for (const id of ['media.upload.create', 'media.derivative.create']) ok(/media\.object\.upload/.test(capabilities.get(id).description) && capabilities.get(id).implementedBy.length > 0, `${id} names its routes and the id that guards them today`);
ok(/media\.object\.read/.test(capabilities.get('media.lifecycle.read').description), 'media.lifecycle.read says media.object.read guards its routes today');
-- ok(/space\.post\.write/.test(capabilities.get('community.vote.set').description) && /community\.comment\.write/.test(capabilities.get('community.vote.set').description), 'community.vote.set says which grants cover its routes today');
++ ok(/space\.post\.write/i.test(capabilities.get('community.vote.set').description) && /community\.comment\.write/i.test(capabilities.get('community.vote.set').description) && capabilities.get('community.vote.set').implementedBy.length === 1, 'community.vote.set retains only the comment route');
ok(capabilities.get('live.owner.resolve').implementedBy.length === 0 && capabilities.get('live.owner.resolve').outputSchema === 'lineage.resolution@1', 'live.owner.resolve has no public route yet and answers lineage.resolution@1');
ok(JSON.stringify(capabilities.get('search.query.run').implementedBy) === JSON.stringify(capabilities.get('search.query.delegate').implementedBy), 'search.query.run and search.query.delegate are the same routes, asked anonymously or for a person');
const pub = services.get('publishing');
@@@ -799,7 +805,7 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
// without an older shape are common.moderation-action@1, and Network consumes every one of them.
{
const moderation = contracts.catalog.filter(c => /\.moderation\.action$/.test(c.id));
-- for (const svc of ['media', 'tools', 'games', 'wiki', 'blog', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes']) {
++ for (const svc of ['media', 'tools', 'games', 'wiki', 'blog', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'space']) {
const id = `${svc}.moderation.action`;
const c = moderation.find(x => x.id === id);
ok(c && c.owner === svc && c.adr === 'ADR-022', `${id} is owned by ${svc}`);
ddiff --git a/test/space-release.test.js b/test/space-release.test.js
nnew file mode 100644
iindex 00000000..257aafa0
---- /dev/null
++++ b/test/space-release.test.js
@@@ -0,0 +1,55 @@
++'use strict';
++const { test } = require('node:test');
++const assert = require('node:assert/strict');
++const fs = require('node:fs');
++const path = require('node:path');
++const contracts = require('..');
++const root = path.join(__dirname, '..');
++const fixture = (id, kind, name) => JSON.parse(fs.readFileSync(path.join(root, 'fixtures', id, kind, name), 'utf8'));
++
++test('Space launch serves its grants and retires Community forum grants', () => {
++ const space = contracts.services.get('space');
++ const community = contracts.services.get('community');
++ assert.equal(space.exposure.state, 'live');
++ assert.equal(space.exposure.publicSite, 'service');
++ assert.deepEqual(space.capabilities, ['space.forum.read', 'space.forum.manage', 'space.thread.read', 'space.post.write']);
++ assert.equal(contracts.capabilities.get('space.forum.manage').status, 'active');
++ assert.ok(contracts.capabilities.get('space.post.write').implementedBy.includes('POST /api/v1/spaces/:space/threads'));
++ for (const id of ['space.thread.write', 'space.pulse.read']) {
++ assert.equal(contracts.capabilities.get(id).status, 'planned');
++ assert.ok(!space.capabilities.includes(id));
++ }
++ for (const id of ['community.space.read', 'community.space.manage', 'community.thread.read', 'community.post.create']) {
++ assert.equal(contracts.capabilities.get(id).status, 'retired');
++ assert.ok(!community.capabilities.includes(id));
++ assert.equal(contracts.capabilities.check({ cap: [id] }, id).code, 'capability.unknown');
++ }
++});
++
++test('Space event payloads validate and match the producer', () => {
++ const space = contracts.services.get('space');
++ for (const id of ['space.thread.created', 'space.post.created', 'space.moderation.action']) {
++ assert.ok(space.eventsProduced.includes(id));
++ assert.equal(contracts.catalog.find(c => c.id === id).owner, 'space');
++ const dir = path.join(root, 'fixtures', id);
++ for (const kind of ['valid', 'invalid']) for (const name of fs.readdirSync(path.join(dir, kind))) {
++ const payload = fixture(id, kind, name);
++ assert.equal(contracts.validate(`${id}@1`, payload).valid, kind === 'valid', `${id} ${kind}/${name}`);
++ if (kind === 'valid') {
++ const envelope = { event_id: 'evt_01JAB2C3D4E5F6G7H8J9K0MNPQ', event_type: id, version: 1, source: 'space', actor: { type: 'service', id: 'space' }, timestamp: '2026-10-07T00:00:00.000Z', subject: { type: 'thread', id: '1' }, payload };
++ assert.equal(contracts.validate('events.event-envelope@1', envelope).valid, true, `${id} envelope`);
++ }
++ }
++ }
++ for (const id of ['community.thread.created', 'community.post.created']) {
++ assert.equal(contracts.catalog.find(c => c.id === id).status, 'retired');
++ assert.ok(!contracts.services.get('community').eventsProduced.includes(id));
++ }
++});
++
++test('Space blocked response uses the shared problem contract', () => {
++ const blocked = fixture('errors.problem', 'valid', 'space-blocked.json');
++ assert.equal(contracts.validate('errors.problem@1', blocked).valid, true);
++ assert.equal(blocked.code, 'space.blocked');
++ assert.equal(blocked.status, 403);
++});