openvibers/openvibe.contracts Commits

Contracts 0.82.0: Space and the media hub (plan T10, J2). manifests/services/space.json (openvibe.space) and media-hub.json (openvibe.video, openvibe.pics, openvibe.download), both placeholders until they serve; space.forum.read/manage, space.thread.read/write, space.post.write, space.pulse.read with seven space.* contracts and fixtures; planned video.*, pics.*, download.* capabilities. The community.space/thread/post capabilities and their forum contracts are deprecated with space.* replacements (removed after 0.84.0), still loadable and still Community's until Space serves. 14589 checks; compat vs v0.81.0: no breaking changes.

OP OpenVibers committed 29 Sept 2026 · 46f3038384 · 86 files, +5447 −2239


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 3e3437dc..67bd25d4 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,28 @@ 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.82.0 — 2026-09-29
++
++**Space and the media hub: the forum moves off Community and the three new products register** (plan T10, D1–D4).
++- Two service manifests, both `status: placeholder` / `exposure.state: placeholder` (the schema has no `planned`; a
++ placeholder is "charter only", which is what these are until each serves): `manifests/services/space.json`
++ (OpenVibe.Space, `openvibe.space`) and `manifests/services/media-hub.json` (OpenVibe.MediaHub). The media hub carries
++ all three brand domains in one manifest (`domains: [openvibe.video, openvibe.pics, openvibe.download]`, the same list
++ mechanism Network/Live/OpenRe/Games already use); the per-brand nav entry awaits the ADR-039 brand mechanism (T11).
++- The forum capabilities are renamed off Community: `community.space.read` → `space.forum.read` (active),
++ `community.space.manage` → `space.forum.manage` (planned), `community.thread.read` → `space.thread.read` (active),
++ `community.post.create` → `space.post.write` (active), plus the new planned `space.thread.write` and
++ `space.pulse.read`. The four old capability manifests stay loadable with `status: deprecated` and are still listed by
++ `community` until their window closes.
++- The seven forum contracts are re-owned under `space.*` (`space.forum-space/thread/post`, `space.forum-read-result`,
++ `space.thread-read-result`, `space.post-write-request/result`) with fixtures; the `community.*` originals go
++ `deprecated` in the catalog and get a `compatibility/deprecations.json` record naming the replacement and window
++ (compat vs v0.80.0: no breaking changes). Contracts only exist for the three active capabilities.
++- The smallest honest set for the media hub, all planned: `video.vod.read`, `video.playlist.read`,
++ `video.playlist.manage`; `pics.image.read`, `pics.image.upload`, `pics.album.read`, `pics.album.manage`;
++ `download.file.read`, `download.file.upload`, `download.share.create`. No media-hub capability is active, so none has
++ a request/result contract yet.
++
## 0.81.0 — 2026-09-29
**Opaque cursors beside the global seq** (plan T7, ADR-042, additive).
ddiff --git a/compatibility/deprecations.json b/compatibility/deprecations.json
iindex e259b074..e51d8019 100644
---- a/compatibility/deprecations.json
++++ b/compatibility/deprecations.json
@@@ -1 +1,54 @@
--{ "_about": "Deprecated contracts: { id, replacement, since, removeAfter, reason }. A deprecated contract stays loadable until removeAfter.", "deprecations": [] }
++{
++ "_about": "Deprecated contracts: { id, replacement, since, removeAfter, reason }. A deprecated contract stays loadable until removeAfter.",
++ "deprecations": [
++ {
++ "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)"
++ },
++ {
++ "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)"
++ },
++ {
++ "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)"
++ },
++ {
++ "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)"
++ },
++ {
++ "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)"
++ },
++ {
++ "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)"
++ },
++ {
++ "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)"
++ }
++ ]
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex c1528887..ded6e096 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -4700,7 +4700,7 @@
"schema": "community/post-write-request.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.pulse-write-result",
@@@ -4718,7 +4718,7 @@
"schema": "community/forum-post.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.forum-thread",
@@@ -4727,7 +4727,7 @@
"schema": "community/forum-thread.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.paste-create-result",
@@@ -4745,7 +4745,7 @@
"schema": "community/post-write-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.pulse-write-request",
@@@ -4799,7 +4799,7 @@
"schema": "community/forum-space.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.pulse-item",
@@@ -4826,7 +4826,7 @@
"schema": "community/space-read-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
-- "status": "active"
++ "status": "deprecated"
},
{
"id": "community.thread-read-result",
@@@ -4835,6 +4835,69 @@
"schema": "community/thread-read-result.v1.json",
"visibility": "first-party",
"compatibility": "backward",
++ "status": "deprecated"
++ },
++ {
++ "id": "space.forum-post",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/forum-post.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.forum-read-result",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/forum-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.forum-space",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/forum-space.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.forum-thread",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/forum-thread.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.post-write-request",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/post-write-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.post-write-result",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/post-write-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "space.thread-read-result",
++ "version": "1.0.0",
++ "owner": "space",
++ "schema": "space/thread-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
"status": "active"
},
{
ddiff --git a/contracts/space/forum-post.v1.json b/contracts/space/forum-post.v1.json
nnew file mode 100644
iindex 00000000..0f2e4bf7
---- /dev/null
++++ b/contracts/space/forum-post.v1.json
@@@ -0,0 +1,190 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-post.v1.json",
++ "title": "SpaceForumPost",
++ "description": "space.forum-post@1: one forum post (the opening post of a thread or a reply) as OpenVibe.Space's API shows it (server/forum/service.js shapePost). A deleted post keeps its place with no author, body, attachments or pastes.",
++ "type": "object",
++ "required": [
++ "id",
++ "thread_id",
++ "is_opening",
++ "origin",
++ "author",
++ "body_markdown",
++ "body_html",
++ "revision",
++ "deleted",
++ "can_edit",
++ "attachments",
++ "pastes"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "thread_id": {
++ "type": "integer"
++ },
++ "is_opening": {
++ "type": "boolean"
++ },
++ "origin": {
++ "type": "string"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "body_markdown": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "body_html": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "deleted": {
++ "type": "boolean"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "can_edit": {
++ "type": "boolean"
++ },
++ "attachments": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "media_id",
++ "url"
++ ],
++ "properties": {
++ "media_id": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "filename": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "mime": {
++ "type": "string"
++ },
++ "size_bytes": {
++ "type": "integer"
++ }
++ }
++ }
++ },
++ "pastes": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "url"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string"
++ },
++ "language": {
++ "type": "string"
++ },
++ "type": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "screenshot_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "lines": {
++ "type": "integer"
++ },
++ "excerpt": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/space/forum-read-result.v1.json b/contracts/space/forum-read-result.v1.json
nnew file mode 100644
iindex 00000000..502b6b68
---- /dev/null
++++ b/contracts/space/forum-read-result.v1.json
@@@ -0,0 +1,70 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-read-result.v1.json",
++ "title": "SpaceForumReadResult",
++ "description": "space.forum-read-result@1: answers of space.forum.read on OpenVibe.Space. GET /api/v1/spaces → { spaces, groups } (the spaces the caller can open, each with its last post, and the board index: groups in order with their top-level spaces and child boards); GET /api/v1/spaces/:space → { space } (a staff or members-only space the caller cannot open is 404 or 401/403 vip.members_only); GET /s is the board index as an HTML page.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "spaces",
++ "groups"
++ ],
++ "properties": {
++ "spaces": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-space.v1.json"
++ }
++ },
++ "groups": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "spaces"
++ ],
++ "properties": {
++ "slug": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "spaces": {
++ "type": "array"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space"
++ ],
++ "properties": {
++ "space": {
++ "$ref": "forum-space.v1.json"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s: the board index page."
++ }
++ ]
++}
ddiff --git a/contracts/space/forum-space.v1.json b/contracts/space/forum-space.v1.json
nnew file mode 100644
iindex 00000000..9ca7ac6c
---- /dev/null
++++ b/contracts/space/forum-space.v1.json
@@@ -0,0 +1,139 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-space.v1.json",
++ "title": "SpaceForumSpace",
++ "description": "space.forum-space@1: one forum space as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeSpace): its slug, name, visibility, thread kind and statuses, style, whether votes and reactions are on, its group and parent board, the members-only gate and, in lists, its counts and last activity.",
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "visibility",
++ "url",
++ "thread_kind",
++ "statuses",
++ "style",
++ "votes",
++ "reactions",
++ "group",
++ "parent",
++ "members_only"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "staff"
++ ]
++ },
++ "url": {
++ "type": "string"
++ },
++ "thread_kind": {
++ "type": "string"
++ },
++ "statuses": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "style": {
++ "enum": [
++ "feed",
++ "forum"
++ ]
++ },
++ "votes": {
++ "type": "boolean"
++ },
++ "reactions": {
++ "type": "boolean"
++ },
++ "group": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "parent": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "post_count": {
++ "type": "integer"
++ },
++ "thread_count": {
++ "type": "integer"
++ },
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ]
++ },
++ "children": {
++ "type": "array"
++ }
++ }
++}
ddiff --git a/contracts/space/forum-thread.v1.json b/contracts/space/forum-thread.v1.json
nnew file mode 100644
iindex 00000000..3c7af75f
---- /dev/null
++++ b/contracts/space/forum-thread.v1.json
@@@ -0,0 +1,247 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-thread.v1.json",
++ "title": "SpaceForumThread",
++ "description": "space.forum-thread@1: one forum thread as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeThread): where it lives, its kind and status, category, members-only gate, author, origin, pin and lock state, score, reply count, views, pages, the last post and the caller's vote.",
++ "type": "object",
++ "required": [
++ "id",
++ "space",
++ "slug",
++ "title",
++ "url",
++ "kind",
++ "author",
++ "origin",
++ "pinned",
++ "locked",
++ "score",
++ "reply_count",
++ "pages",
++ "created_at",
++ "my_vote"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "space": {
++ "type": "string"
++ },
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "description": "/s/<space>/t/<slug>"
++ },
++ "kind": {
++ "type": "string",
++ "description": "discussion, request, roadmap, …"
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "category": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "position": {},
++ "thread_count": {
++ "type": "integer"
++ }
++ }
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "origin": {
++ "type": "string"
++ },
++ "pinned": {
++ "type": "boolean"
++ },
++ "locked": {
++ "type": "boolean"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "reply_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "views": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ }
++ }
++ },
++ "crosspost_of": {},
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++}
ddiff --git a/contracts/space/post-write-request.v1.json b/contracts/space/post-write-request.v1.json
nnew file mode 100644
iindex 00000000..22136030
---- /dev/null
++++ b/contracts/space/post-write-request.v1.json
@@@ -0,0 +1,49 @@
++{
++ "$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, …).",
++ "anyOf": [
++ {
++ "type": "object",
++ "anyOf": [
++ { "required": ["body"] },
++ { "required": ["body_markdown"] }
++ ],
++ "properties": {
++ "title": { "type": "string", "description": "A new thread's title." },
++ "body": { "type": "string", "minLength": 1, "maxLength": 40000, "description": "Markdown." },
++ "body_markdown": { "type": "string", "minLength": 1, "maxLength": 40000, "description": "Read when body is absent." },
++ "members_only": {
++ "description": "A new thread only: true gates it to the author's own VIP members; { owner: usr_… } (or the subject itself) names the creator (moderators, or the author naming themselves); null or false: open.",
++ "anyOf": [
++ { "type": ["boolean", "null"] },
++ { "type": "string", "pattern": "^usr_" },
++ { "type": "object", "required": ["owner"], "properties": { "owner": { "type": "string", "pattern": "^usr_" } } }
++ ]
++ },
++ "category": { "type": "string", "description": "A new thread only: the slug of one of the space's categories." },
++ "attachments": {
++ "description": "At most 4 images the writer uploaded (POST /api/v1/spaces/:space/attachments) and has not used yet, by media id. People only.",
++ "anyOf": [
++ { "type": "string", "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$" },
++ { "type": "array", "maxItems": 4, "items": { "type": "string", "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$" } }
++ ]
++ },
++ "pastes": {
++ "description": "At most 4 shareable pastes (not private, not burn-after-read) by slug or /p/<slug> link: an array, or one string separated by spaces or commas.",
++ "type": ["array", "string"],
++ "items": { "type": "string" }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": ["value"],
++ "properties": {
++ "value": { "enum": [1, -1, 0, "1", "-1", "0"], "description": "A thread vote; 0 takes the vote back." }
++ }
++ },
++ { "$ref": "../common/no-body.v1.json" }
++ ]
++}
ddiff --git a/contracts/space/post-write-result.v1.json b/contracts/space/post-write-result.v1.json
nnew file mode 100644
iindex 00000000..d61a82d6
---- /dev/null
++++ b/contracts/space/post-write-result.v1.json
@@@ -0,0 +1,92 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/post-write-result.v1.json",
++ "title": "SpacePostWriteResult",
++ "description": "space.post-write-result@1: answers of the forum writes on OpenVibe.Space (space.post.write). POST /api/v1/spaces/:space/threads → 201 { thread, post } (the thread and its opening post); POST …/threads/:slug/posts → 201 { post, page, url } (the reply, the page it lands on and its address with #post-<id>); PUT /api/v1/posts/:id → { post }; POST …/threads/:slug/votes → { thread_id, score, upvotes, downvotes, my_vote }; DELETE /api/v1/posts/:id → { ok, id } (deleting the opening post deletes the thread: { ok, id, deleted: thread }); DELETE /api/v1/spaces/:space/threads/:slug → { ok, id, deleted: thread }. Refusals are problem+json.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "thread",
++ "post"
++ ],
++ "properties": {
++ "thread": {
++ "$ref": "forum-thread.v1.json"
++ },
++ "post": {
++ "$ref": "forum-post.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "post"
++ ],
++ "properties": {
++ "post": {
++ "$ref": "forum-post.v1.json"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "url": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "thread_id",
++ "score",
++ "upvotes",
++ "downvotes",
++ "my_vote"
++ ],
++ "properties": {
++ "thread_id": {
++ "type": "integer"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "upvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "downvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "id"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer"
++ },
++ "deleted": {
++ "const": "thread"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/space/thread-read-result.v1.json b/contracts/space/thread-read-result.v1.json
nnew file mode 100644
iindex 00000000..182b49ff
---- /dev/null
++++ b/contracts/space/thread-read-result.v1.json
@@@ -0,0 +1,134 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/thread-read-result.v1.json",
++ "title": "SpaceThreadReadResult",
++ "description": "space.thread-read-result@1: answers of space.thread.read on OpenVibe.Space. GET /api/v1/spaces/:space/threads?sort=&page=&limit=&category=&status= → a page of threads with the space, paging, categories, children and what the viewer may do; GET /api/v1/spaces/:space/threads/:slug?page= → the thread with a page of posts (reactions, author stats, crosspost info, attachments settings, paging, viewer). GET /s/:space and /s/:space/t/:slug are the same as HTML pages; GET /s/feed.xml and /s/:space/feed.xml are RSS feeds of public threads.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "threads",
++ "page",
++ "pages",
++ "total"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "sort": {
++ "type": "string"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "total": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "categories": {
++ "type": "array"
++ },
++ "category": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "viewer": {
++ "type": "object"
++ },
++ "children": {
++ "type": "array"
++ },
++ "groups": {
++ "type": "array"
++ },
++ "threads": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-thread.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "thread",
++ "posts",
++ "page",
++ "pages"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "thread": {
++ "$ref": "forum-thread.v1.json"
++ },
++ "posts": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-post.v1.json"
++ }
++ },
++ "reactions": {
++ "type": "array"
++ },
++ "crosspost": {
++ "type": "object"
++ },
++ "categories": {
++ "type": "array"
++ },
++ "attachments": {
++ "type": "object"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "total": {
++ "type": "integer"
++ },
++ "viewer": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s/:space and /s/:space/t/:slug: HTML pages."
++ },
++ {
++ "type": "string",
++ "contentMediaType": "application/rss+xml",
++ "description": "GET /s/feed.xml and /s/:space/feed.xml: RSS feeds."
++ }
++ ]
++}
ddiff --git a/fixtures/space.forum-post/invalid/revision-zero.json b/fixtures/space.forum-post/invalid/revision-zero.json
nnew file mode 100644
iindex 00000000..6f42d5cf
---- /dev/null
++++ b/fixtures/space.forum-post/invalid/revision-zero.json
@@@ -0,0 +1 @@
++{ "id": 1, "thread_id": 1, "is_opening": false, "origin": "user", "author": null, "body_markdown": null, "body_html": null, "revision": 0, "deleted": true, "can_edit": false, "attachments": [], "pastes": [] }
ddiff --git a/fixtures/space.forum-post/valid/opening.json b/fixtures/space.forum-post/valid/opening.json
nnew file mode 100644
iindex 00000000..eed41637
---- /dev/null
++++ b/fixtures/space.forum-post/valid/opening.json
@@@ -0,0 +1 @@
++{ "attachments": [], "pastes": [ { "slug": "k3JfQ2", "title": "ffmpeg restream flags", "language": "text", "type": "paste", "url": "/p/k3JfQ2", "screenshot_url": null, "lines": 1, "excerpt": "ffmpeg -re -i in.flv -c copy -f flv rtmp://example/live" } ], "id": 5120, "thread_id": 311, "is_opening": true, "origin": "user", "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "body_markdown": "What bitrates do you use?", "body_html": "<p>What bitrates do you use?</p>", "revision": 1, "deleted": false, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "can_edit": true }
ddiff --git a/fixtures/space.forum-read-result/invalid/partial-space.json b/fixtures/space.forum-read-result/invalid/partial-space.json
nnew file mode 100644
iindex 00000000..34fb7c07
---- /dev/null
++++ b/fixtures/space.forum-read-result/invalid/partial-space.json
@@@ -0,0 +1 @@
++{ "spaces": [ { "slug": "x" } ], "groups": [] }
ddiff --git a/fixtures/space.forum-read-result/valid/index.json b/fixtures/space.forum-read-result/valid/index.json
nnew file mode 100644
iindex 00000000..8f01f4f6
---- /dev/null
++++ b/fixtures/space.forum-read-result/valid/index.json
@@@ -0,0 +1 @@
++{ "spaces": [ { "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" } ], "groups": [ { "slug": "make", "name": "Make things", "description": null, "spaces": [ { "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" } ] } ] }
ddiff --git a/fixtures/space.forum-read-result/valid/one.json b/fixtures/space.forum-read-result/valid/one.json
nnew file mode 100644
iindex 00000000..01e00034
---- /dev/null
++++ b/fixtures/space.forum-read-result/valid/one.json
@@@ -0,0 +1 @@
++{ "space": { "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" } }
ddiff --git a/fixtures/space.forum-space/invalid/bad-style.json b/fixtures/space.forum-space/invalid/bad-style.json
nnew file mode 100644
iindex 00000000..f2eade33
---- /dev/null
++++ b/fixtures/space.forum-space/invalid/bad-style.json
@@@ -0,0 +1 @@
++{ "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "wiki", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/fixtures/space.forum-space/valid/streaming.json b/fixtures/space.forum-space/valid/streaming.json
nnew file mode 100644
iindex 00000000..3950e255
---- /dev/null
++++ b/fixtures/space.forum-space/valid/streaming.json
@@@ -0,0 +1 @@
++{ "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/fixtures/space.forum-thread/invalid/bad-vote.json b/fixtures/space.forum-thread/invalid/bad-vote.json
nnew file mode 100644
iindex 00000000..3e5f903f
---- /dev/null
++++ b/fixtures/space.forum-thread/invalid/bad-vote.json
@@@ -0,0 +1 @@
++{ "id": 311, "space": "streaming", "slug": "restreaming-to-three-platforms", "title": "Restreaming to three platforms at once", "url": "/s/streaming/t/restreaming-to-three-platforms", "kind": "discussion", "status": null, "category": { "slug": "help", "name": "Help", "description": null, "position": 1 }, "members_only": null, "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "origin": "user", "pinned": false, "locked": false, "score": 0, "reply_count": 0, "views": 0, "pages": 1, "last_post": null, "crosspost_of": null, "last_activity_at": "2026-09-25T18:00:00.000Z", "created_at": "2026-09-25T18:00:00.000Z", "my_vote": 2 }
ddiff --git a/fixtures/space.forum-thread/valid/new.json b/fixtures/space.forum-thread/valid/new.json
nnew file mode 100644
iindex 00000000..50a44c2a
---- /dev/null
++++ b/fixtures/space.forum-thread/valid/new.json
@@@ -0,0 +1 @@
++{ "id": 311, "space": "streaming", "slug": "restreaming-to-three-platforms", "title": "Restreaming to three platforms at once", "url": "/s/streaming/t/restreaming-to-three-platforms", "kind": "discussion", "status": null, "category": { "slug": "help", "name": "Help", "description": null, "position": 1 }, "members_only": null, "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "origin": "user", "pinned": false, "locked": false, "score": 0, "reply_count": 0, "views": 0, "pages": 1, "last_post": null, "crosspost_of": null, "last_activity_at": "2026-09-25T18:00:00.000Z", "created_at": "2026-09-25T18:00:00.000Z", "my_vote": 0 }
ddiff --git a/fixtures/space.post-write-request/invalid/bad-attachment.json b/fixtures/space.post-write-request/invalid/bad-attachment.json
nnew file mode 100644
iindex 00000000..0138c724
---- /dev/null
++++ b/fixtures/space.post-write-request/invalid/bad-attachment.json
@@@ -0,0 +1 @@
++{ "body": "x", "attachments": ["med_1", "med_2"] }
ddiff --git a/fixtures/space.post-write-request/invalid/bad-vote.json b/fixtures/space.post-write-request/invalid/bad-vote.json
nnew file mode 100644
iindex 00000000..5eefaaee
---- /dev/null
++++ b/fixtures/space.post-write-request/invalid/bad-vote.json
@@@ -0,0 +1 @@
++{ "value": 2 }
ddiff --git a/fixtures/space.post-write-request/valid/new-thread.json b/fixtures/space.post-write-request/valid/new-thread.json
nnew file mode 100644
iindex 00000000..f8696345
---- /dev/null
++++ b/fixtures/space.post-write-request/valid/new-thread.json
@@@ -0,0 +1 @@
++{ "title": "Restreaming to three platforms at once", "body": "What bitrates do you use?", "category": "help", "pastes": ["k3JfQ2"] }
ddiff --git a/fixtures/space.post-write-request/valid/reply.json b/fixtures/space.post-write-request/valid/reply.json
nnew file mode 100644
iindex 00000000..1fc080d1
---- /dev/null
++++ b/fixtures/space.post-write-request/valid/reply.json
@@@ -0,0 +1 @@
++{ "body_markdown": "6000 kbps works for me", "attachments": ["med_01JAB2C3D4E5F6G7H8J9K0MNPQ"] }
ddiff --git a/fixtures/space.post-write-request/valid/vote.json b/fixtures/space.post-write-request/valid/vote.json
nnew file mode 100644
iindex 00000000..ca57eb68
---- /dev/null
++++ b/fixtures/space.post-write-request/valid/vote.json
@@@ -0,0 +1 @@
++{ "value": -1 }
ddiff --git a/fixtures/space.post-write-result/invalid/bad-deleted.json b/fixtures/space.post-write-result/invalid/bad-deleted.json
nnew file mode 100644
iindex 00000000..664773de
---- /dev/null
++++ b/fixtures/space.post-write-result/invalid/bad-deleted.json
@@@ -0,0 +1 @@
++{ "ok": true, "id": 311, "deleted": "post" }
ddiff --git a/fixtures/space.post-write-result/valid/new-thread.json b/fixtures/space.post-write-result/valid/new-thread.json
nnew file mode 100644
iindex 00000000..413983e9
---- /dev/null
++++ b/fixtures/space.post-write-result/valid/new-thread.json
@@@ -0,0 +1 @@
++{ "thread": { "id": 311, "space": "streaming", "slug": "restreaming-to-three-platforms", "title": "Restreaming to three platforms at once", "url": "/s/streaming/t/restreaming-to-three-platforms", "kind": "discussion", "status": null, "category": { "slug": "help", "name": "Help", "description": null, "position": 1 }, "members_only": null, "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "origin": "user", "pinned": false, "locked": false, "score": 0, "reply_count": 0, "views": 0, "pages": 1, "last_post": null, "crosspost_of": null, "last_activity_at": "2026-09-25T18:00:00.000Z", "created_at": "2026-09-25T18:00:00.000Z", "my_vote": 0 }, "post": { "attachments": [], "pastes": [ { "slug": "k3JfQ2", "title": "ffmpeg restream flags", "language": "text", "type": "paste", "url": "/p/k3JfQ2", "screenshot_url": null, "lines": 1, "excerpt": "ffmpeg -re -i in.flv -c copy -f flv rtmp://example/live" } ], "id": 5120, "thread_id": 311, "is_opening": true, "origin": "user", "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "body_markdown": "What bitrates do you use?", "body_html": "<p>What bitrates do you use?</p>", "revision": 1, "deleted": false, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "can_edit": true } }
ddiff --git a/fixtures/space.post-write-result/valid/reply.json b/fixtures/space.post-write-result/valid/reply.json
nnew file mode 100644
iindex 00000000..e728e59a
---- /dev/null
++++ b/fixtures/space.post-write-result/valid/reply.json
@@@ -0,0 +1 @@
++{ "post": { "attachments": [], "pastes": [ { "slug": "k3JfQ2", "title": "ffmpeg restream flags", "language": "text", "type": "paste", "url": "/p/k3JfQ2", "screenshot_url": null, "lines": 1, "excerpt": "ffmpeg -re -i in.flv -c copy -f flv rtmp://example/live" } ], "id": 5120, "thread_id": 311, "is_opening": false, "origin": "user", "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "body_markdown": "What bitrates do you use?", "body_html": "<p>What bitrates do you use?</p>", "revision": 1, "deleted": false, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "can_edit": true }, "page": 1, "url": "/s/streaming/t/restreaming-to-three-platforms#post-5121" }
ddiff --git a/fixtures/space.post-write-result/valid/thread-deleted.json b/fixtures/space.post-write-result/valid/thread-deleted.json
nnew file mode 100644
iindex 00000000..2fa039aa
---- /dev/null
++++ b/fixtures/space.post-write-result/valid/thread-deleted.json
@@@ -0,0 +1 @@
++{ "ok": true, "id": 311, "deleted": "thread" }
ddiff --git a/fixtures/space.post-write-result/valid/vote.json b/fixtures/space.post-write-result/valid/vote.json
nnew file mode 100644
iindex 00000000..2115df28
---- /dev/null
++++ b/fixtures/space.post-write-result/valid/vote.json
@@@ -0,0 +1 @@
++{ "thread_id": 311, "score": 3, "upvotes": 4, "downvotes": 1, "my_vote": 1 }
ddiff --git a/fixtures/space.thread-read-result/invalid/page-zero.json b/fixtures/space.thread-read-result/invalid/page-zero.json
nnew file mode 100644
iindex 00000000..e3014b10
---- /dev/null
++++ b/fixtures/space.thread-read-result/invalid/page-zero.json
@@@ -0,0 +1 @@
++{ "space": {}, "threads": [], "page": 0, "pages": 1, "total": 0 }
ddiff --git a/fixtures/space.thread-read-result/valid/rss.json b/fixtures/space.thread-read-result/valid/rss.json
nnew file mode 100644
iindex 00000000..c6667c1d
---- /dev/null
++++ b/fixtures/space.thread-read-result/valid/rss.json
@@@ -0,0 +1 @@
++"<?xml version=\"1.0\"?><rss version=\"2.0\"></rss>"
ddiff --git a/fixtures/space.thread-read-result/valid/thread.json b/fixtures/space.thread-read-result/valid/thread.json
nnew file mode 100644
iindex 00000000..fc86cd4c
---- /dev/null
++++ b/fixtures/space.thread-read-result/valid/thread.json
@@@ -0,0 +1 @@
++{ "space": { "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" }, "thread": { "id": 311, "space": "streaming", "slug": "restreaming-to-three-platforms", "title": "Restreaming to three platforms at once", "url": "/s/streaming/t/restreaming-to-three-platforms", "kind": "discussion", "status": null, "category": { "slug": "help", "name": "Help", "description": null, "position": 1 }, "members_only": null, "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "origin": "user", "pinned": false, "locked": false, "score": 0, "reply_count": 0, "views": 0, "pages": 1, "last_post": null, "crosspost_of": null, "last_activity_at": "2026-09-25T18:00:00.000Z", "created_at": "2026-09-25T18:00:00.000Z", "my_vote": 0 }, "posts": [ { "attachments": [], "pastes": [ { "slug": "k3JfQ2", "title": "ffmpeg restream flags", "language": "text", "type": "paste", "url": "/p/k3JfQ2", "screenshot_url": null, "lines": 1, "excerpt": "ffmpeg -re -i in.flv -c copy -f flv rtmp://example/live" } ], "id": 5120, "thread_id": 311, "is_opening": true, "origin": "user", "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "body_markdown": "What bitrates do you use?", "body_html": "<p>What bitrates do you use?</p>", "revision": 1, "deleted": false, "created_at": "2026-09-25T18:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z", "can_edit": true } ], "reactions": [], "crosspost": { "from": null, "to": [], "targets": [] }, "categories": [], "attachments": { "enabled": true, "max": 4, "max_bytes": 8388608 }, "page": 1, "per_page": 20, "pages": 1, "total": 1, "viewer": { "signed_in": true } }
ddiff --git a/fixtures/space.thread-read-result/valid/threads.json b/fixtures/space.thread-read-result/valid/threads.json
nnew file mode 100644
iindex 00000000..9e026119
---- /dev/null
++++ b/fixtures/space.thread-read-result/valid/threads.json
@@@ -0,0 +1 @@
++{ "space": { "slug": "streaming", "name": "Streaming", "description": "Encoders, bitrates and restreams.", "visibility": "public", "url": "/s/streaming", "thread_kind": "discussion", "statuses": [], "style": "feed", "votes": true, "reactions": true, "group": { "slug": "make", "name": "Make things" }, "parent": null, "members_only": null, "thread_count": 42, "last_activity_at": "2026-09-25T18:00:00.000Z" }, "sort": "hot", "page": 1, "per_page": 25, "total": 1, "pages": 1, "categories": [], "category": null, "status": null, "viewer": { "can_start": true, "can_moderate": false, "signed_in": true }, "children": [], "threads": [ { "id": 311, "space": "streaming", "slug": "restreaming-to-three-platforms", "title": "Restreaming to three platforms at once", "url": "/s/streaming/t/restreaming-to-three-platforms", "kind": "discussion", "status": null, "category": { "slug": "help", "name": "Help", "description": null, "position": 1 }, "members_only": null, "author": { "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "username": "alice", "display_name": "Alice", "avatar_url": null, "profile_color": "#3b82f6" }, "origin": "user", "pinned": false, "locked": false, "score": 0, "reply_count": 0, "views": 0, "pages": 1, "last_post": null, "crosspost_of": null, "last_activity_at": "2026-09-25T18:00:00.000Z", "created_at": "2026-09-25T18:00:00.000Z", "my_vote": 0 } ] }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 3a58bd67..ca1a86f0 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -51068,7 +51068,7 @@
"community.post-write-request": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/post-write-request.v1.json",
@@@ -51235,7 +51235,7 @@
"community.forum-post": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-post.v1.json",
@@@ -51430,7 +51430,7 @@
"community.forum-thread": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-thread.v1.json",
@@@ -51715,7 +51715,7 @@
"community.post-write-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/post-write-result.v1.json",
@@@ -52304,7 +52304,7 @@
"community.forum-space": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/forum-space.v1.json",
@@@ -52606,7 +52606,7 @@
"community.space-read-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/space-read-result.v1.json",
@@@ -52681,7 +52681,7 @@
"community.thread-read-result": {
"version": "1.0.0",
"owner": "community",
-- "status": "active",
++ "status": "deprecated",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/community/thread-read-result.v1.json",
@@@ -52817,6 +52817,1034 @@
]
}
},
++ "space.forum-post": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-post.v1.json",
++ "title": "SpaceForumPost",
++ "description": "space.forum-post@1: one forum post (the opening post of a thread or a reply) as OpenVibe.Space's API shows it (server/forum/service.js shapePost). A deleted post keeps its place with no author, body, attachments or pastes.",
++ "type": "object",
++ "required": [
++ "id",
++ "thread_id",
++ "is_opening",
++ "origin",
++ "author",
++ "body_markdown",
++ "body_html",
++ "revision",
++ "deleted",
++ "can_edit",
++ "attachments",
++ "pastes"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "thread_id": {
++ "type": "integer"
++ },
++ "is_opening": {
++ "type": "boolean"
++ },
++ "origin": {
++ "type": "string"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "body_markdown": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "body_html": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "deleted": {
++ "type": "boolean"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "can_edit": {
++ "type": "boolean"
++ },
++ "attachments": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "media_id",
++ "url"
++ ],
++ "properties": {
++ "media_id": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "filename": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "mime": {
++ "type": "string"
++ },
++ "size_bytes": {
++ "type": "integer"
++ }
++ }
++ }
++ },
++ "pastes": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "url"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string"
++ },
++ "language": {
++ "type": "string"
++ },
++ "type": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "screenshot_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "lines": {
++ "type": "integer"
++ },
++ "excerpt": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "space.forum-read-result": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-read-result.v1.json",
++ "title": "SpaceForumReadResult",
++ "description": "space.forum-read-result@1: answers of space.forum.read on OpenVibe.Space. GET /api/v1/spaces → { spaces, groups } (the spaces the caller can open, each with its last post, and the board index: groups in order with their top-level spaces and child boards); GET /api/v1/spaces/:space → { space } (a staff or members-only space the caller cannot open is 404 or 401/403 vip.members_only); GET /s is the board index as an HTML page.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "spaces",
++ "groups"
++ ],
++ "properties": {
++ "spaces": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-space.v1.json"
++ }
++ },
++ "groups": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "spaces"
++ ],
++ "properties": {
++ "slug": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "spaces": {
++ "type": "array"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space"
++ ],
++ "properties": {
++ "space": {
++ "$ref": "forum-space.v1.json"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s: the board index page."
++ }
++ ]
++ }
++ },
++ "space.forum-space": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-space.v1.json",
++ "title": "SpaceForumSpace",
++ "description": "space.forum-space@1: one forum space as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeSpace): its slug, name, visibility, thread kind and statuses, style, whether votes and reactions are on, its group and parent board, the members-only gate and, in lists, its counts and last activity.",
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "visibility",
++ "url",
++ "thread_kind",
++ "statuses",
++ "style",
++ "votes",
++ "reactions",
++ "group",
++ "parent",
++ "members_only"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "staff"
++ ]
++ },
++ "url": {
++ "type": "string"
++ },
++ "thread_kind": {
++ "type": "string"
++ },
++ "statuses": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "style": {
++ "enum": [
++ "feed",
++ "forum"
++ ]
++ },
++ "votes": {
++ "type": "boolean"
++ },
++ "reactions": {
++ "type": "boolean"
++ },
++ "group": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "parent": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "post_count": {
++ "type": "integer"
++ },
++ "thread_count": {
++ "type": "integer"
++ },
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ]
++ },
++ "children": {
++ "type": "array"
++ }
++ }
++ }
++ },
++ "space.forum-thread": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/forum-thread.v1.json",
++ "title": "SpaceForumThread",
++ "description": "space.forum-thread@1: one forum thread as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeThread): where it lives, its kind and status, category, members-only gate, author, origin, pin and lock state, score, reply count, views, pages, the last post and the caller's vote.",
++ "type": "object",
++ "required": [
++ "id",
++ "space",
++ "slug",
++ "title",
++ "url",
++ "kind",
++ "author",
++ "origin",
++ "pinned",
++ "locked",
++ "score",
++ "reply_count",
++ "pages",
++ "created_at",
++ "my_vote"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "space": {
++ "type": "string"
++ },
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "description": "/s/<space>/t/<slug>"
++ },
++ "kind": {
++ "type": "string",
++ "description": "discussion, request, roadmap, …"
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "category": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "position": {},
++ "thread_count": {
++ "type": "integer"
++ }
++ }
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "origin": {
++ "type": "string"
++ },
++ "pinned": {
++ "type": "boolean"
++ },
++ "locked": {
++ "type": "boolean"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "reply_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "views": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ }
++ }
++ },
++ "crosspost_of": {},
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++ }
++ },
++ "space.post-write-request": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$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, …).",
++ "anyOf": [
++ {
++ "type": "object",
++ "anyOf": [
++ {
++ "required": [
++ "body"
++ ]
++ },
++ {
++ "required": [
++ "body_markdown"
++ ]
++ }
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "description": "A new thread's title."
++ },
++ "body": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40000,
++ "description": "Markdown."
++ },
++ "body_markdown": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40000,
++ "description": "Read when body is absent."
++ },
++ "members_only": {
++ "description": "A new thread only: true gates it to the author's own VIP members; { owner: usr_… } (or the subject itself) names the creator (moderators, or the author naming themselves); null or false: open.",
++ "anyOf": [
++ {
++ "type": [
++ "boolean",
++ "null"
++ ]
++ },
++ {
++ "type": "string",
++ "pattern": "^usr_"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_"
++ }
++ }
++ }
++ ]
++ },
++ "category": {
++ "type": "string",
++ "description": "A new thread only: the slug of one of the space's categories."
++ },
++ "attachments": {
++ "description": "At most 4 images the writer uploaded (POST /api/v1/spaces/:space/attachments) and has not used yet, by media id. People only.",
++ "anyOf": [
++ {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ ]
++ },
++ "pastes": {
++ "description": "At most 4 shareable pastes (not private, not burn-after-read) by slug or /p/<slug> link: an array, or one string separated by spaces or commas.",
++ "type": [
++ "array",
++ "string"
++ ],
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "enum": [
++ 1,
++ -1,
++ 0,
++ "1",
++ "-1",
++ "0"
++ ],
++ "description": "A thread vote; 0 takes the vote back."
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
++ "space.post-write-result": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/post-write-result.v1.json",
++ "title": "SpacePostWriteResult",
++ "description": "space.post-write-result@1: answers of the forum writes on OpenVibe.Space (space.post.write). POST /api/v1/spaces/:space/threads → 201 { thread, post } (the thread and its opening post); POST …/threads/:slug/posts → 201 { post, page, url } (the reply, the page it lands on and its address with #post-<id>); PUT /api/v1/posts/:id → { post }; POST …/threads/:slug/votes → { thread_id, score, upvotes, downvotes, my_vote }; DELETE /api/v1/posts/:id → { ok, id } (deleting the opening post deletes the thread: { ok, id, deleted: thread }); DELETE /api/v1/spaces/:space/threads/:slug → { ok, id, deleted: thread }. Refusals are problem+json.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "thread",
++ "post"
++ ],
++ "properties": {
++ "thread": {
++ "$ref": "forum-thread.v1.json"
++ },
++ "post": {
++ "$ref": "forum-post.v1.json"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "post"
++ ],
++ "properties": {
++ "post": {
++ "$ref": "forum-post.v1.json"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "url": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "thread_id",
++ "score",
++ "upvotes",
++ "downvotes",
++ "my_vote"
++ ],
++ "properties": {
++ "thread_id": {
++ "type": "integer"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "upvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "downvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "id"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer"
++ },
++ "deleted": {
++ "const": "thread"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "space.thread-read-result": {
++ "version": "1.0.0",
++ "owner": "space",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/space/thread-read-result.v1.json",
++ "title": "SpaceThreadReadResult",
++ "description": "space.thread-read-result@1: answers of space.thread.read on OpenVibe.Space. GET /api/v1/spaces/:space/threads?sort=&page=&limit=&category=&status= → a page of threads with the space, paging, categories, children and what the viewer may do; GET /api/v1/spaces/:space/threads/:slug?page= → the thread with a page of posts (reactions, author stats, crosspost info, attachments settings, paging, viewer). GET /s/:space and /s/:space/t/:slug are the same as HTML pages; GET /s/feed.xml and /s/:space/feed.xml are RSS feeds of public threads.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "threads",
++ "page",
++ "pages",
++ "total"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "sort": {
++ "type": "string"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "total": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "categories": {
++ "type": "array"
++ },
++ "category": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "viewer": {
++ "type": "object"
++ },
++ "children": {
++ "type": "array"
++ },
++ "groups": {
++ "type": "array"
++ },
++ "threads": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-thread.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "thread",
++ "posts",
++ "page",
++ "pages"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "thread": {
++ "$ref": "forum-thread.v1.json"
++ },
++ "posts": {
++ "type": "array",
++ "items": {
++ "$ref": "forum-post.v1.json"
++ }
++ },
++ "reactions": {
++ "type": "array"
++ },
++ "crosspost": {
++ "type": "object"
++ },
++ "categories": {
++ "type": "array"
++ },
++ "attachments": {
++ "type": "object"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "total": {
++ "type": "integer"
++ },
++ "viewer": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s/:space and /s/:space/t/:slug: HTML pages."
++ },
++ {
++ "type": "string",
++ "contentMediaType": "application/rss+xml",
++ "description": "GET /s/feed.xml and /s/:space/feed.xml: RSS feeds."
++ }
++ ]
++ }
++ },
"openre.destination": {
"version": "1.0.0",
"owner": "openre",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 0da392da..52476d07 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.81.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.82.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex e1587309..1a5c957c 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.81.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 e1304685..29af17e6 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.81.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 eca1e67f..5e6edcde 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.81.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 831c0b62..1174de89 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.81.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 360013ca..d23c207d 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.81.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.82.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"
@@@ -855,18 +855,15 @@
"delete": {
"operationId": "delete_api_v1_posts_id",
"summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
-- "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.\n\n**community.post.create** (public): 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": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.",
"tags": [
-- "community.comment",
-- "community.post"
++ "community.comment"
],
"x-openvibe-capabilities": [
-- "community.comment.moderate",
-- "community.post.create"
++ "community.comment.moderate"
],
"x-openvibe-visibility": [
-- "internal",
-- "public"
++ "internal"
],
"x-openvibe-quota-class": "community-moderate",
"security": [
@@@ -874,22 +871,10 @@
"openvibeToken": [
"community.comment.moderate"
]
-- },
-- {
-- "openvibeToken": [
-- "community.post.create"
-- ]
}
],
"x-openvibe-input": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-request.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.post-write-request.v1"
-- }
-- ]
++ "$ref": "#/components/schemas/community.moderation-request.v1"
},
"parameters": [
{
@@@ -907,14 +892,7 @@
"content": {
"application/json": {
"schema": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-result.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.post-write-result.v1"
-- }
-- ]
++ "$ref": "#/components/schemas/community.moderation-result.v1"
}
}
}
@@@ -934,18 +912,15 @@
"put": {
"operationId": "put_api_v1_posts_id",
"summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
-- "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.\n\n**community.post.create** (public): 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": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.",
"tags": [
-- "community.comment",
-- "community.post"
++ "community.comment"
],
"x-openvibe-capabilities": [
-- "community.comment.moderate",
-- "community.post.create"
++ "community.comment.moderate"
],
"x-openvibe-visibility": [
-- "internal",
-- "public"
++ "internal"
],
"x-openvibe-quota-class": "community-moderate",
"security": [
@@@ -953,11 +928,6 @@
"openvibeToken": [
"community.comment.moderate"
]
-- },
-- {
-- "openvibeToken": [
-- "community.post.create"
-- ]
}
],
"requestBody": {
@@@ -965,14 +935,7 @@
"content": {
"application/json": {
"schema": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-request.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.post-write-request.v1"
-- }
-- ]
++ "$ref": "#/components/schemas/community.moderation-request.v1"
}
}
}
@@@ -993,14 +956,7 @@
"content": {
"application/json": {
"schema": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-result.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.post-write-result.v1"
-- }
-- ]
++ "$ref": "#/components/schemas/community.moderation-result.v1"
}
}
}
@@@ -1352,74 +1308,31 @@
}
}
},
-- "/api/v1/spaces": {
-- "get": {
-- "operationId": "get_api_v1_spaces",
-- "summary": "List the spaces the caller can open and read one space's metadata (roadmap §30.2 community.space.*).",
-- "description": "**community.space.read** (public): 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.",
-- "tags": [
-- "community.space"
-- ],
-- "x-openvibe-capabilities": [
-- "community.space.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.space.read"
-- ]
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.space-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}": {
-- "get": {
-- "operationId": "get_api_v1_spaces_space",
-- "summary": "List the spaces the caller can open and read one space's metadata (roadmap §30.2 community.space.*).",
-- "description": "**community.space.read** (public): 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.",
++ "/api/v1/spaces/{space}/threads/{slug}": {
++ "delete": {
++ "operationId": "delete_api_v1_spaces_space_threads_slug",
++ "summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
++ "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.",
"tags": [
-- "community.space"
++ "community.comment"
],
"x-openvibe-capabilities": [
-- "community.space.read"
++ "community.comment.moderate"
],
"x-openvibe-visibility": [
-- "public"
++ "internal"
],
-- "x-openvibe-quota-class": "community-read",
++ "x-openvibe-quota-class": "community-moderate",
"security": [
{
"openvibeToken": [
-- "community.space.read"
++ "community.comment.moderate"
]
}
],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/community.moderation-request.v1"
++ },
"parameters": [
{
"name": "space",
@@@ -1428,57 +1341,9 @@
"schema": {
"type": "string"
}
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.space-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}/threads": {
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "parameters": [
{
-- "name": "space",
++ "name": "slug",
"in": "path",
"required": true,
"schema": {
@@@ -1492,7 +1357,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/community.thread-read-result.v1"
++ "$ref": "#/components/schemas/community.moderation-result.v1"
}
}
}
@@@ -1508,25 +1373,27 @@
}
}
}
-- },
-- "post": {
-- "operationId": "post_api_v1_spaces_space_threads",
-- "summary": "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",
-- "description": "**community.post.create** (public): 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.",
++ }
++ },
++ "/api/v1/spaces/{space}/threads/{slug}/state": {
++ "put": {
++ "operationId": "put_api_v1_spaces_space_threads_slug_state",
++ "summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
++ "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.",
"tags": [
-- "community.post"
++ "community.comment"
],
"x-openvibe-capabilities": [
-- "community.post.create"
++ "community.comment.moderate"
],
"x-openvibe-visibility": [
-- "public"
++ "internal"
],
-- "x-openvibe-quota-class": "community-write",
++ "x-openvibe-quota-class": "community-moderate",
"security": [
{
"openvibeToken": [
-- "community.post.create"
++ "community.comment.moderate"
]
}
],
@@@ -1535,7 +1402,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/community.post-write-request.v1"
++ "$ref": "#/components/schemas/community.moderation-request.v1"
}
}
}
@@@ -1548,6 +1415,14 @@
"schema": {
"type": "string"
}
++ },
++ {
++ "name": "slug",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
}
],
"responses": {
@@@ -1556,7 +1431,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/community.post-write-result.v1"
++ "$ref": "#/components/schemas/community.moderation-result.v1"
}
}
}
@@@ -1574,78 +1449,35 @@
}
}
},
-- "/api/v1/spaces/{space}/threads/{slug}": {
-- "delete": {
-- "operationId": "delete_api_v1_spaces_space_threads_slug",
-- "summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
-- "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.\n\n**community.post.create** (public): 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.",
++ "/pulse": {
++ "get": {
++ "operationId": "get_pulse",
++ "summary": "Read the network's public activity feed (Pulse): entity reference, title, URL and origin (user, ai or system), newest first with a cursor.",
++ "description": "**community.pulse.read** (public): Read the network's public activity feed (Pulse): entity reference, title, URL and origin (user, ai or system), newest first with a cursor. Only public items are ever stored. Served to anyone (CORS allow-list for browsers). Live's /api/home/pulse is a different, Live-only feed.",
"tags": [
-- "community.comment",
-- "community.post"
++ "community.pulse"
],
"x-openvibe-capabilities": [
-- "community.comment.moderate",
-- "community.post.create"
++ "community.pulse.read"
],
"x-openvibe-visibility": [
-- "internal",
"public"
],
-- "x-openvibe-quota-class": "community-moderate",
++ "x-openvibe-quota-class": "community-read",
"security": [
{
"openvibeToken": [
-- "community.comment.moderate"
-- ]
-- },
-- {
-- "openvibeToken": [
-- "community.post.create"
++ "community.pulse.read"
]
}
],
-- "x-openvibe-input": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-request.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.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": {
-- "anyOf": [
-- {
-- "$ref": "#/components/schemas/community.moderation-result.v1"
-- },
-- {
-- "$ref": "#/components/schemas/community.post-write-result.v1"
-- }
-- ]
++ "$ref": "#/components/schemas/community.pulse-read-result.v1"
}
}
}
@@@ -1661,1360 +1493,141 @@
}
}
}
-- },
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "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/community.thread-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}/threads/{slug}/posts": {
-- "post": {
-- "operationId": "post_api_v1_spaces_space_threads_slug_posts",
-- "summary": "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",
-- "description": "**community.post.create** (public): 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.",
-- "tags": [
-- "community.post"
-- ],
-- "x-openvibe-capabilities": [
-- "community.post.create"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-write",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.post.create"
-- ]
-- }
-- ],
-- "requestBody": {
-- "required": false,
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.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/community.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}/state": {
-- "put": {
-- "operationId": "put_api_v1_spaces_space_threads_slug_state",
-- "summary": "Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g.",
-- "description": "**community.comment.moderate** (internal): Moderation of discussions: set a comment thread's visibility (public, hidden, locked — e.g. when the owner service hides the entity), delete any comment or forum post, pin and lock forum threads, read hidden threads, administer the Discord relay. Acting as itself (no X-OV-Subject) the service is the moderator; acting for a person it must vouch with X-OV-Staff: 1 that the person is staff.",
-- "tags": [
-- "community.comment"
-- ],
-- "x-openvibe-capabilities": [
-- "community.comment.moderate"
-- ],
-- "x-openvibe-visibility": [
-- "internal"
-- ],
-- "x-openvibe-quota-class": "community-moderate",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.comment.moderate"
-- ]
-- }
-- ],
-- "requestBody": {
-- "required": false,
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.moderation-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/community.moderation-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}/votes": {
-- "post": {
-- "operationId": "post_api_v1_spaces_space_threads_slug_votes",
-- "summary": "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",
-- "description": "**community.post.create** (public): 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.",
-- "tags": [
-- "community.post"
-- ],
-- "x-openvibe-capabilities": [
-- "community.post.create"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-write",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.post.create"
-- ]
-- }
-- ],
-- "requestBody": {
-- "required": false,
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.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/community.post-write-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/pulse": {
-- "get": {
-- "operationId": "get_pulse",
-- "summary": "Read the network's public activity feed (Pulse): entity reference, title, URL and origin (user, ai or system), newest first with a cursor.",
-- "description": "**community.pulse.read** (public): Read the network's public activity feed (Pulse): entity reference, title, URL and origin (user, ai or system), newest first with a cursor. Only public items are ever stored. Served to anyone (CORS allow-list for browsers). Live's /api/home/pulse is a different, Live-only feed.",
-- "tags": [
-- "community.pulse"
-- ],
-- "x-openvibe-capabilities": [
-- "community.pulse.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.pulse.read"
-- ]
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.pulse-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/s": {
-- "get": {
-- "operationId": "get_s",
-- "summary": "List the spaces the caller can open and read one space's metadata (roadmap §30.2 community.space.*).",
-- "description": "**community.space.read** (public): 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.",
-- "tags": [
-- "community.space"
-- ],
-- "x-openvibe-capabilities": [
-- "community.space.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.space.read"
-- ]
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.space-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/s/{space}": {
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "parameters": [
-- {
-- "name": "space",
-- "in": "path",
-- "required": true,
-- "schema": {
-- "type": "string"
-- }
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.thread-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/s/{space}/feed.xml": {
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "parameters": [
-- {
-- "name": "space",
-- "in": "path",
-- "required": true,
-- "schema": {
-- "type": "string"
-- }
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.thread-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/s/{space}/t/{slug}": {
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "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/community.thread-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "/s/feed.xml": {
-- "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.",
-- "description": "**community.thread.read** (public): 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.",
-- "tags": [
-- "community.thread"
-- ],
-- "x-openvibe-capabilities": [
-- "community.thread.read"
-- ],
-- "x-openvibe-visibility": [
-- "public"
-- ],
-- "x-openvibe-quota-class": "community-read",
-- "security": [
-- {
-- "openvibeToken": [
-- "community.thread.read"
-- ]
-- }
-- ],
-- "responses": {
-- "2XX": {
-- "description": "Success",
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/community.thread-read-result.v1"
-- }
-- }
-- }
-- },
-- "default": {
-- "description": "An error (RFC 9457 problem details)",
-- "content": {
-- "application/problem+json": {
-- "schema": {
-- "$ref": "#/components/schemas/errors.problem.v1"
-- }
-- }
-- }
-- }
-- }
-- }
-- }
-- },
-- "components": {
-- "schemas": {
-- "common.entity-ref.v1": {
-- "title": "EntityRef",
-- "description": "Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.",
-- "type": "object",
-- "required": [
-- "service",
-- "type",
-- "id"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "service": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9-]{1,39}$"
-- },
-- "type": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_]{1,39}$"
-- },
-- "id": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 128
-- },
-- "revision": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "label": {
-- "type": "string",
-- "maxLength": 200,
-- "description": "Cached display label. Never authoritative."
-- }
-- }
-- },
-- "common.no-body.v1": {
-- "title": "NoBody",
-- "description": "common.no-body@1: the capability's request carries no body; its parameters are the path and query of the routes in implementedBy (an id, a page cursor). An input schema that says so explicitly, so every active capability names one (roadmap WS-C task 4).",
-- "type": "object",
-- "maxProperties": 0
-- },
-- "community.comment-thread.v1": {
-- "title": "CommunityCommentThread",
-- "description": "community.comment-thread@1: one embeddable comment thread on OpenVibe.Community (server/comments/service.js shapeThread): the entity it belongs to, its visibility and comment count. id is the sequential id for services and the access id (cth_…) for everyone else; a hidden thread shows moderators its count, others only that it is hidden.",
-- "type": "object",
-- "required": [
-- "id",
-- "access_id",
-- "ref",
-- "visibility",
-- "comment_count"
-- ],
-- "properties": {
-- "id": {
-- "type": [
-- "integer",
-- "string"
-- ]
-- },
-- "access_id": {
-- "type": "string",
-- "pattern": "^cth_"
-- },
-- "ref": {
-- "$ref": "#/components/schemas/common.entity-ref.v1"
-- },
-- "visibility": {
-- "enum": [
-- "public",
-- "hidden",
-- "locked"
-- ]
-- },
-- "comment_count": {
-- "type": [
-- "integer",
-- "null"
-- ]
-- },
-- "created_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- }
-- }
-- },
-- "community.comment-write-request.v1": {
-- "title": "CommunityCommentWriteRequest",
-- "description": "community.comment-write-request@1: bodies of community.comment.write on OpenVibe.Community (people, anonymous people with a name where the thread allows it, and services acting as X-OV-Subject or as AI). POST /api/v1/comments/threads/resolve: { ref } — get or create the thread of an entity (browsers only for the allow-listed entity types; a label is taken only from a service). POST /api/v1/comments/threads/:id/comments: { message, parent_id?, anon_name? } (message trimmed, at most 5000 characters; a reply to a reply joins the top-level comment). POST /api/v1/comments/:commentId/votes: { value: 1 | -1 | 0 } (people). GET /api/v1/comments/threads/:id and DELETE /api/v1/comments/:commentId take no body. Unknown fields are ignored.",
-- "anyOf": [
-- {
-- "type": "object",
-- "required": [
-- "ref"
-- ],
-- "properties": {
-- "ref": {
-- "$ref": "#/components/schemas/common.entity-ref.v1"
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "message"
-- ],
-- "properties": {
-- "message": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 5000,
-- "pattern": "\\S"
-- },
-- "parent_id": {
-- "type": [
-- "integer",
-- "string",
-- "null"
-- ]
-- },
-- "anon_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "value"
-- ],
-- "properties": {
-- "value": {
-- "enum": [
-- 1,
-- -1,
-- 0,
-- "1",
-- "-1",
-- "0"
-- ]
-- }
-- }
-- },
-- {
-- "$ref": "#/components/schemas/common.no-body.v1"
-- }
-- ]
-- },
-- "community.comment-write-result.v1": {
-- "title": "CommunityCommentWriteResult",
-- "description": "community.comment-write-result@1: answers of community.comment.write on OpenVibe.Community. POST …/threads/resolve → { thread, created } (201 when created); GET …/threads/:id?after=&sort=&limit=&parent= → { thread, comments, next_cursor, viewer }; POST …/threads/:id/comments → 201 { comment }; DELETE /api/v1/comments/:commentId → { ok, id }; POST …/:commentId/votes → { comment_id, score, upvotes, downvotes, my_vote }. Refusals are problem+json (thread.locked, auth.required, community.blocked, …).",
-- "anyOf": [
-- {
-- "type": "object",
-- "required": [
-- "thread",
-- "created"
-- ],
-- "properties": {
-- "thread": {
-- "$ref": "#/components/schemas/community.comment-thread.v1"
-- },
-- "created": {
-- "type": "boolean"
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "thread",
-- "comments",
-- "next_cursor",
-- "viewer"
-- ],
-- "properties": {
-- "thread": {
-- "$ref": "#/components/schemas/community.comment-thread.v1"
-- },
-- "comments": {
-- "type": "array",
-- "items": {
-- "$ref": "#/components/schemas/community.comment.v1"
-- }
-- },
-- "next_cursor": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "viewer": {
-- "type": "object",
-- "properties": {
-- "signed_in": {
-- "type": "boolean"
-- },
-- "can_comment": {
-- "type": "boolean"
-- },
-- "can_vote": {
-- "type": "boolean"
-- },
-- "can_moderate": {
-- "type": "boolean"
-- }
-- }
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "comment"
-- ],
-- "properties": {
-- "comment": {
-- "$ref": "#/components/schemas/community.comment.v1"
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "ok",
-- "id"
-- ],
-- "properties": {
-- "ok": {
-- "const": true
-- },
-- "id": {
-- "type": "integer"
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "comment_id",
-- "score",
-- "upvotes",
-- "downvotes",
-- "my_vote"
-- ],
-- "properties": {
-- "comment_id": {
-- "type": "integer"
-- },
-- "score": {
-- "type": "integer"
-- },
-- "upvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "downvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "my_vote": {
-- "enum": [
-- 1,
-- -1,
-- 0
-- ]
-- }
-- }
-- }
-- ]
-- },
-- "community.comment.v1": {
-- "title": "CommunityComment",
-- "description": "community.comment@1: one comment in an embeddable comment thread on OpenVibe.Community (server/comments/service.js shapeComment), with its author (or anonymous name), score and the caller's vote, reply count and, for top-level comments in a page, the first replies. A deleted comment keeps its place with no author or message.",
-- "type": "object",
-- "required": [
-- "id",
-- "thread_id",
-- "parent_id",
-- "origin",
-- "author",
-- "message",
-- "deleted",
-- "score",
-- "my_vote",
-- "can_edit",
-- "can_delete"
-- ],
-- "properties": {
-- "id": {
-- "type": "integer"
-- },
-- "thread_id": {
-- "type": [
-- "integer",
-- "string"
-- ]
-- },
-- "parent_id": {
-- "type": [
-- "integer",
-- "null"
-- ]
-- },
-- "origin": {
-- "type": "string"
-- },
-- "author": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "properties": {
-- "subject": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "username": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "display_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "avatar_url": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "profile_color": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "is_ai": {
-- "const": true
-- },
-- "is_system": {
-- "const": true
-- }
-- }
-- },
-- "anon_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "display_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "message": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "maxLength": 5000
-- },
-- "deleted": {
-- "type": "boolean"
-- },
-- "score": {
-- "type": "integer"
-- },
-- "upvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "downvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "my_vote": {
-- "enum": [
-- 1,
-- -1,
-- 0
-- ]
-- },
-- "reply_count": {
-- "type": [
-- "integer",
-- "null"
-- ]
-- },
-- "created_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "edited_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "can_edit": {
-- "type": "boolean"
-- },
-- "can_delete": {
-- "type": "boolean"
-- },
-- "replies": {
-- "type": "array",
-- "items": {
-- "type": "object"
-- }
-- }
-- }
-- },
-- "community.forum-post.v1": {
-- "title": "CommunityForumPost",
-- "description": "community.forum-post@1: one forum post (the opening post of a thread or a reply) as OpenVibe.Community's API shows it (server/forum/service.js shapePost). A deleted post keeps its place with no author, body, attachments or pastes.",
-- "type": "object",
-- "required": [
-- "id",
-- "thread_id",
-- "is_opening",
-- "origin",
-- "author",
-- "body_markdown",
-- "body_html",
-- "revision",
-- "deleted",
-- "can_edit",
-- "attachments",
-- "pastes"
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "common.entity-ref.v1": {
++ "title": "EntityRef",
++ "description": "Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.",
++ "type": "object",
++ "required": [
++ "service",
++ "type",
++ "id"
],
++ "additionalProperties": false,
"properties": {
-- "id": {
-- "type": "integer"
-- },
-- "thread_id": {
-- "type": "integer"
-- },
-- "is_opening": {
-- "type": "boolean"
-- },
-- "origin": {
-- "type": "string"
-- },
-- "author": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
-- "properties": {
-- "subject": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "username": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "display_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "avatar_url": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "profile_color": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "is_ai": {
-- "const": true
-- },
-- "is_system": {
-- "const": true
-- }
-- }
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
},
-- "body_markdown": {
-- "type": [
-- "string",
-- "null"
-- ]
++ "type": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{1,39}$"
},
-- "body_html": {
-- "type": [
-- "string",
-- "null"
-- ]
++ "id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 128
},
"revision": {
"type": "integer",
-- "minimum": 1
-- },
-- "deleted": {
-- "type": "boolean"
-- },
-- "created_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time"
-- },
-- "can_edit": {
-- "type": "boolean"
-- },
-- "attachments": {
-- "type": "array",
-- "maxItems": 4,
-- "items": {
-- "type": "object",
-- "required": [
-- "media_id",
-- "url"
-- ],
-- "properties": {
-- "media_id": {
-- "type": "string"
-- },
-- "url": {
-- "type": "string"
-- },
-- "filename": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "mime": {
-- "type": "string"
-- },
-- "size_bytes": {
-- "type": "integer"
-- }
-- }
-- }
++ "minimum": 0
},
-- "pastes": {
-- "type": "array",
-- "maxItems": 4,
-- "items": {
-- "type": "object",
-- "required": [
-- "slug",
-- "url"
-- ],
-- "properties": {
-- "slug": {
-- "type": "string"
-- },
-- "title": {
-- "type": "string"
-- },
-- "language": {
-- "type": "string"
-- },
-- "type": {
-- "type": "string"
-- },
-- "url": {
-- "type": "string"
-- },
-- "screenshot_url": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "lines": {
-- "type": "integer"
-- },
-- "excerpt": {
-- "type": [
-- "string",
-- "null"
-- ]
-- }
-- }
-- }
++ "label": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "Cached display label. Never authoritative."
}
}
},
-- "community.forum-space.v1": {
-- "title": "CommunityForumSpace",
-- "description": "community.forum-space@1: one forum space as OpenVibe.Community's /api/v1/spaces API shows it (server/forum/service.js shapeSpace): its slug, name, visibility, thread kind and statuses, style, whether votes and reactions are on, its group and parent board, the members-only gate and, in lists, its counts and last activity.",
++ "common.no-body.v1": {
++ "title": "NoBody",
++ "description": "common.no-body@1: the capability's request carries no body; its parameters are the path and query of the routes in implementedBy (an id, a page cursor). An input schema that says so explicitly, so every active capability names one (roadmap WS-C task 4).",
++ "type": "object",
++ "maxProperties": 0
++ },
++ "community.comment-thread.v1": {
++ "title": "CommunityCommentThread",
++ "description": "community.comment-thread@1: one embeddable comment thread on OpenVibe.Community (server/comments/service.js shapeThread): the entity it belongs to, its visibility and comment count. id is the sequential id for services and the access id (cth_…) for everyone else; a hidden thread shows moderators its count, others only that it is hidden.",
"type": "object",
"required": [
-- "slug",
-- "name",
++ "id",
++ "access_id",
++ "ref",
"visibility",
-- "url",
-- "thread_kind",
-- "statuses",
-- "style",
-- "votes",
-- "reactions",
-- "group",
-- "parent",
-- "members_only"
++ "comment_count"
],
"properties": {
-- "slug": {
-- "type": "string"
-- },
-- "name": {
-- "type": "string"
-- },
-- "description": {
++ "id": {
"type": [
-- "string",
-- "null"
-- ]
-- },
-- "visibility": {
-- "enum": [
-- "public",
-- "members",
-- "staff"
++ "integer",
++ "string"
]
},
-- "url": {
-- "type": "string"
-- },
-- "thread_kind": {
-- "type": "string"
++ "access_id": {
++ "type": "string",
++ "pattern": "^cth_"
},
-- "statuses": {
-- "type": "array",
-- "items": {
-- "type": "string"
-- }
++ "ref": {
++ "$ref": "#/components/schemas/common.entity-ref.v1"
},
-- "style": {
++ "visibility": {
"enum": [
-- "feed",
-- "forum"
-- ]
-- },
-- "votes": {
-- "type": "boolean"
-- },
-- "reactions": {
-- "type": "boolean"
-- },
-- "group": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "properties": {
-- "slug": {
-- "type": "string"
-- },
-- "name": {
-- "type": "string"
-- }
-- }
++ "public",
++ "hidden",
++ "locked"
++ ]
},
-- "parent": {
++ "comment_count": {
"type": [
-- "object",
++ "integer",
"null"
-- ],
-- "properties": {
-- "slug": {
-- "type": "string"
-- },
-- "name": {
-- "type": "string"
-- }
-- }
-- },
-- "post_count": {
-- "type": "integer"
-- },
-- "thread_count": {
-- "type": "integer"
++ ]
},
-- "last_activity_at": {
++ "created_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
-- "members_only": {
++ "updated_at": {
"type": [
-- "object",
++ "string",
"null"
],
++ "format": "date-time"
++ }
++ }
++ },
++ "community.comment-write-request.v1": {
++ "title": "CommunityCommentWriteRequest",
++ "description": "community.comment-write-request@1: bodies of community.comment.write on OpenVibe.Community (people, anonymous people with a name where the thread allows it, and services acting as X-OV-Subject or as AI). POST /api/v1/comments/threads/resolve: { ref } — get or create the thread of an entity (browsers only for the allow-listed entity types; a label is taken only from a service). POST /api/v1/comments/threads/:id/comments: { message, parent_id?, anon_name? } (message trimmed, at most 5000 characters; a reply to a reply joins the top-level comment). POST /api/v1/comments/:commentId/votes: { value: 1 | -1 | 0 } (people). GET /api/v1/comments/threads/:id and DELETE /api/v1/comments/:commentId take no body. Unknown fields are ignored.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ref"
++ ],
"properties": {
-- "owner": {
-- "type": "string"
++ "ref": {
++ "$ref": "#/components/schemas/common.entity-ref.v1"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "message": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 5000,
++ "pattern": "\\S"
},
-- "owner_username": {
++ "parent_id": {
"type": [
++ "integer",
"string",
"null"
]
},
-- "join_url": {
++ "anon_name": {
"type": [
"string",
"null"
@@@ -3022,119 +1635,193 @@
}
}
},
-- "last_post": {
-- "type": [
-- "object",
-- "null"
-- ]
++ {
++ "type": "object",
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "enum": [
++ 1,
++ -1,
++ 0,
++ "1",
++ "-1",
++ "0"
++ ]
++ }
++ }
},
-- "children": {
-- "type": "array"
++ {
++ "$ref": "#/components/schemas/common.no-body.v1"
}
-- }
++ ]
},
-- "community.forum-thread.v1": {
-- "title": "CommunityForumThread",
-- "description": "community.forum-thread@1: one forum thread as OpenVibe.Community's /api/v1/spaces API shows it (server/forum/service.js shapeThread): where it lives, its kind and status, category, members-only gate, author, origin, pin and lock state, score, reply count, views, pages, the last post and the caller's vote.",
-- "type": "object",
-- "required": [
-- "id",
-- "space",
-- "slug",
-- "title",
-- "url",
-- "kind",
-- "author",
-- "origin",
-- "pinned",
-- "locked",
-- "score",
-- "reply_count",
-- "pages",
-- "created_at",
-- "my_vote"
-- ],
-- "properties": {
-- "id": {
-- "type": "integer"
-- },
-- "space": {
-- "type": "string"
-- },
-- "slug": {
-- "type": "string"
-- },
-- "title": {
-- "type": "string",
-- "maxLength": 200
-- },
-- "url": {
-- "type": "string",
-- "description": "/s/<space>/t/<slug>"
-- },
-- "kind": {
-- "type": "string",
-- "description": "discussion, request, roadmap, …"
-- },
-- "status": {
-- "type": [
-- "string",
-- "null"
-- ]
++ "community.comment-write-result.v1": {
++ "title": "CommunityCommentWriteResult",
++ "description": "community.comment-write-result@1: answers of community.comment.write on OpenVibe.Community. POST …/threads/resolve → { thread, created } (201 when created); GET …/threads/:id?after=&sort=&limit=&parent= → { thread, comments, next_cursor, viewer }; POST …/threads/:id/comments → 201 { comment }; DELETE /api/v1/comments/:commentId → { ok, id }; POST …/:commentId/votes → { comment_id, score, upvotes, downvotes, my_vote }. Refusals are problem+json (thread.locked, auth.required, community.blocked, …).",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "thread",
++ "created"
++ ],
++ "properties": {
++ "thread": {
++ "$ref": "#/components/schemas/community.comment-thread.v1"
++ },
++ "created": {
++ "type": "boolean"
++ }
++ }
},
-- "category": {
-- "type": [
-- "object",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "thread",
++ "comments",
++ "next_cursor",
++ "viewer"
],
"properties": {
-- "slug": {
-- "type": "string"
++ "thread": {
++ "$ref": "#/components/schemas/community.comment-thread.v1"
},
-- "name": {
-- "type": "string"
++ "comments": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/community.comment.v1"
++ }
},
-- "description": {
++ "next_cursor": {
"type": [
"string",
"null"
]
},
-- "position": {},
-- "thread_count": {
++ "viewer": {
++ "type": "object",
++ "properties": {
++ "signed_in": {
++ "type": "boolean"
++ },
++ "can_comment": {
++ "type": "boolean"
++ },
++ "can_vote": {
++ "type": "boolean"
++ },
++ "can_moderate": {
++ "type": "boolean"
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "comment"
++ ],
++ "properties": {
++ "comment": {
++ "$ref": "#/components/schemas/community.comment.v1"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "id"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
"type": "integer"
}
}
},
-- "members_only": {
-- "type": [
-- "object",
-- "null"
++ {
++ "type": "object",
++ "required": [
++ "comment_id",
++ "score",
++ "upvotes",
++ "downvotes",
++ "my_vote"
],
"properties": {
-- "owner": {
-- "type": "string"
++ "comment_id": {
++ "type": "integer"
},
-- "owner_username": {
-- "type": [
-- "string",
-- "null"
-- ]
++ "score": {
++ "type": "integer"
},
-- "join_url": {
-- "type": [
-- "string",
-- "null"
++ "upvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "downvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
]
}
}
++ }
++ ]
++ },
++ "community.comment.v1": {
++ "title": "CommunityComment",
++ "description": "community.comment@1: one comment in an embeddable comment thread on OpenVibe.Community (server/comments/service.js shapeComment), with its author (or anonymous name), score and the caller's vote, reply count and, for top-level comments in a page, the first replies. A deleted comment keeps its place with no author or message.",
++ "type": "object",
++ "required": [
++ "id",
++ "thread_id",
++ "parent_id",
++ "origin",
++ "author",
++ "message",
++ "deleted",
++ "score",
++ "my_vote",
++ "can_edit",
++ "can_delete"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "thread_id": {
++ "type": [
++ "integer",
++ "string"
++ ]
++ },
++ "parent_id": {
++ "type": [
++ "integer",
++ "null"
++ ]
++ },
++ "origin": {
++ "type": "string"
},
"author": {
"type": [
"object",
"null"
],
-- "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
"properties": {
"subject": {
"type": [
@@@ -3174,107 +1861,84 @@
}
}
},
-- "origin": {
-- "type": "string"
++ "anon_name": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "pinned": {
-- "type": "boolean"
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- "locked": {
++ "message": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 5000
++ },
++ "deleted": {
"type": "boolean"
},
"score": {
"type": "integer"
},
-- "reply_count": {
++ "upvotes": {
"type": "integer",
"minimum": 0
},
-- "views": {
++ "downvotes": {
"type": "integer",
"minimum": 0
},
-- "pages": {
-- "type": "integer",
-- "minimum": 1
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
},
-- "last_post": {
++ "reply_count": {
"type": [
-- "object",
++ "integer",
++ "null"
++ ]
++ },
++ "created_at": {
++ "type": [
++ "string",
"null"
],
-- "properties": {
-- "id": {
-- "type": "integer"
-- },
-- "author": {
-- "type": [
-- "object",
-- "null"
-- ],
-- "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
-- "properties": {
-- "subject": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "username": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "display_name": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "avatar_url": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "profile_color": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "is_ai": {
-- "const": true
-- },
-- "is_system": {
-- "const": true
-- }
-- }
-- }
-- }
++ "format": "date-time"
},
-- "crosspost_of": {},
-- "last_activity_at": {
++ "updated_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
-- "created_at": {
++ "edited_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
-- "my_vote": {
-- "enum": [
-- 1,
-- -1,
-- 0
-- ]
++ "can_edit": {
++ "type": "boolean"
++ },
++ "can_delete": {
++ "type": "boolean"
++ },
++ "replies": {
++ "type": "array",
++ "items": {
++ "type": "object"
++ }
}
}
},
@@@ -4196,283 +2860,74 @@
"type": "boolean"
},
"ai_summary": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "ai_tags": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "JSON text as the AI pass wrote it."
-- },
-- "ai_analyzed_at": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "revision": {
-- "type": "integer"
-- },
-- "url": {
-- "type": "string",
-- "description": "/p/<slug>"
-- },
-- "raw_url": {
-- "type": "string",
-- "description": "/p/<slug>/raw"
-- },
-- "created_at": {
-- "type": "string"
-- },
-- "updated_at": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "is_owner": {
-- "type": "boolean",
-- "description": "Whether the caller owns it."
-- },
-- "username": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "display_name": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "description": "OpenVibe AI for an AI paste."
-- },
-- "avatar_url": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "profile_color": {
-- "type": [
-- "string",
-- "null"
-- ]
-- }
-- }
-- },
-- "community.post-write-request.v1": {
-- "title": "CommunityPostWriteRequest",
-- "description": "community.post-write-request@1: bodies of the forum writes on OpenVibe.Community (community.post.create; 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, …).",
-- "anyOf": [
-- {
-- "type": "object",
-- "anyOf": [
-- {
-- "required": [
-- "body"
-- ]
-- },
-- {
-- "required": [
-- "body_markdown"
-- ]
-- }
-- ],
-- "properties": {
-- "title": {
-- "type": "string",
-- "description": "A new thread's title."
-- },
-- "body": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 40000,
-- "description": "Markdown."
-- },
-- "body_markdown": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 40000,
-- "description": "Read when body is absent."
-- },
-- "members_only": {
-- "description": "A new thread only: true gates it to the author's own VIP members; { owner: usr_… } (or the subject itself) names the creator (moderators, or the author naming themselves); null or false: open.",
-- "anyOf": [
-- {
-- "type": [
-- "boolean",
-- "null"
-- ]
-- },
-- {
-- "type": "string",
-- "pattern": "^usr_"
-- },
-- {
-- "type": "object",
-- "required": [
-- "owner"
-- ],
-- "properties": {
-- "owner": {
-- "type": "string",
-- "pattern": "^usr_"
-- }
-- }
-- }
-- ]
-- },
-- "category": {
-- "type": "string",
-- "description": "A new thread only: the slug of one of the space's categories."
-- },
-- "attachments": {
-- "description": "At most 4 images the writer uploaded (POST /api/v1/spaces/:space/attachments) and has not used yet, by media id. People only.",
-- "anyOf": [
-- {
-- "type": "string",
-- "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
-- },
-- {
-- "type": "array",
-- "maxItems": 4,
-- "items": {
-- "type": "string",
-- "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
-- }
-- }
-- ]
-- },
-- "pastes": {
-- "description": "At most 4 shareable pastes (not private, not burn-after-read) by slug or /p/<slug> link: an array, or one string separated by spaces or commas.",
-- "type": [
-- "array",
-- "string"
-- ],
-- "items": {
-- "type": "string"
-- }
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "value"
-- ],
-- "properties": {
-- "value": {
-- "enum": [
-- 1,
-- -1,
-- 0,
-- "1",
-- "-1",
-- "0"
-- ],
-- "description": "A thread vote; 0 takes the vote back."
-- }
-- }
++ "type": [
++ "string",
++ "null"
++ ]
},
-- {
-- "$ref": "#/components/schemas/common.no-body.v1"
-- }
-- ]
-- },
-- "community.post-write-result.v1": {
-- "title": "CommunityPostWriteResult",
-- "description": "community.post-write-result@1: answers of the forum writes on OpenVibe.Community (community.post.create). POST /api/v1/spaces/:space/threads → 201 { thread, post } (the thread and its opening post); POST …/threads/:slug/posts → 201 { post, page, url } (the reply, the page it lands on and its address with #post-<id>); PUT /api/v1/posts/:id → { post }; POST …/threads/:slug/votes → { thread_id, score, upvotes, downvotes, my_vote }; DELETE /api/v1/posts/:id → { ok, id } (deleting the opening post deletes the thread: { ok, id, deleted: thread }); DELETE /api/v1/spaces/:space/threads/:slug → { ok, id, deleted: thread }. Refusals are problem+json.",
-- "anyOf": [
-- {
-- "type": "object",
-- "required": [
-- "thread",
-- "post"
++ "ai_tags": {
++ "type": [
++ "string",
++ "null"
],
-- "properties": {
-- "thread": {
-- "$ref": "#/components/schemas/community.forum-thread.v1"
-- },
-- "post": {
-- "$ref": "#/components/schemas/community.forum-post.v1"
-- }
-- }
++ "description": "JSON text as the AI pass wrote it."
},
-- {
-- "type": "object",
-- "required": [
-- "post"
-- ],
-- "properties": {
-- "post": {
-- "$ref": "#/components/schemas/community.forum-post.v1"
-- },
-- "page": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "url": {
-- "type": "string"
-- }
-- }
++ "ai_analyzed_at": {
++ "type": [
++ "string",
++ "null"
++ ]
},
-- {
-- "type": "object",
-- "required": [
-- "thread_id",
-- "score",
-- "upvotes",
-- "downvotes",
-- "my_vote"
-- ],
-- "properties": {
-- "thread_id": {
-- "type": "integer"
-- },
-- "score": {
-- "type": "integer"
-- },
-- "upvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "downvotes": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "my_vote": {
-- "enum": [
-- 1,
-- -1,
-- 0
-- ]
-- }
-- }
++ "revision": {
++ "type": "integer"
},
-- {
-- "type": "object",
-- "required": [
-- "ok",
-- "id"
++ "url": {
++ "type": "string",
++ "description": "/p/<slug>"
++ },
++ "raw_url": {
++ "type": "string",
++ "description": "/p/<slug>/raw"
++ },
++ "created_at": {
++ "type": "string"
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_owner": {
++ "type": "boolean",
++ "description": "Whether the caller owns it."
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
],
-- "properties": {
-- "ok": {
-- "const": true
-- },
-- "id": {
-- "type": "integer"
-- },
-- "deleted": {
-- "const": "thread"
-- }
-- }
++ "description": "OpenVibe AI for an AI paste."
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
}
-- ]
++ }
},
"community.pulse-item.v1": {
"title": "CommunityPulseItem",
@@@ -4706,206 +3161,6 @@
}
]
},
-- "community.space-read-result.v1": {
-- "title": "CommunitySpaceReadResult",
-- "description": "community.space-read-result@1: answers of community.space.read on OpenVibe.Community. GET /api/v1/spaces → { spaces, groups } (the spaces the caller can open, each with its last post, and the board index: groups in order with their top-level spaces and child boards); GET /api/v1/spaces/:space → { space } (a staff or members-only space the caller cannot open is 404 or 401/403 vip.members_only); GET /s is the board index as an HTML page.",
-- "anyOf": [
-- {
-- "type": "object",
-- "required": [
-- "spaces",
-- "groups"
-- ],
-- "properties": {
-- "spaces": {
-- "type": "array",
-- "items": {
-- "$ref": "#/components/schemas/community.forum-space.v1"
-- }
-- },
-- "groups": {
-- "type": "array",
-- "items": {
-- "type": "object",
-- "required": [
-- "slug",
-- "name",
-- "spaces"
-- ],
-- "properties": {
-- "slug": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "name": {
-- "type": "string"
-- },
-- "description": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "spaces": {
-- "type": "array"
-- }
-- }
-- }
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "space"
-- ],
-- "properties": {
-- "space": {
-- "$ref": "#/components/schemas/community.forum-space.v1"
-- }
-- }
-- },
-- {
-- "type": "string",
-- "contentMediaType": "text/html",
-- "description": "GET /s: the board index page."
-- }
-- ]
-- },
-- "community.thread-read-result.v1": {
-- "title": "CommunityThreadReadResult",
-- "description": "community.thread-read-result@1: answers of community.thread.read on OpenVibe.Community. GET /api/v1/spaces/:space/threads?sort=&page=&limit=&category=&status= → a page of threads with the space, paging, categories, children and what the viewer may do; GET /api/v1/spaces/:space/threads/:slug?page= → the thread with a page of posts (reactions, author stats, crosspost info, attachments settings, paging, viewer). GET /s/:space and /s/:space/t/:slug are the same as HTML pages; GET /s/feed.xml and /s/:space/feed.xml are RSS feeds of public threads.",
-- "anyOf": [
-- {
-- "type": "object",
-- "required": [
-- "space",
-- "threads",
-- "page",
-- "pages",
-- "total"
-- ],
-- "properties": {
-- "space": {
-- "type": "object"
-- },
-- "sort": {
-- "type": "string"
-- },
-- "page": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "per_page": {
-- "type": "integer"
-- },
-- "total": {
-- "type": "integer",
-- "minimum": 0
-- },
-- "pages": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "categories": {
-- "type": "array"
-- },
-- "category": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "status": {
-- "type": [
-- "string",
-- "null"
-- ]
-- },
-- "viewer": {
-- "type": "object"
-- },
-- "children": {
-- "type": "array"
-- },
-- "groups": {
-- "type": "array"
-- },
-- "threads": {
-- "type": "array",
-- "items": {
-- "$ref": "#/components/schemas/community.forum-thread.v1"
-- }
-- }
-- }
-- },
-- {
-- "type": "object",
-- "required": [
-- "space",
-- "thread",
-- "posts",
-- "page",
-- "pages"
-- ],
-- "properties": {
-- "space": {
-- "type": "object"
-- },
-- "thread": {
-- "$ref": "#/components/schemas/community.forum-thread.v1"
-- },
-- "posts": {
-- "type": "array",
-- "items": {
-- "$ref": "#/components/schemas/community.forum-post.v1"
-- }
-- },
-- "reactions": {
-- "type": "array"
-- },
-- "crosspost": {
-- "type": "object"
-- },
-- "categories": {
-- "type": "array"
-- },
-- "attachments": {
-- "type": "object"
-- },
-- "page": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "per_page": {
-- "type": "integer"
-- },
-- "pages": {
-- "type": "integer",
-- "minimum": 1
-- },
-- "total": {
-- "type": "integer"
-- },
-- "viewer": {
-- "type": "object"
-- }
-- }
-- },
-- {
-- "type": "string",
-- "contentMediaType": "text/html",
-- "description": "GET /s/:space and /s/:space/t/:slug: HTML pages."
-- },
-- {
-- "type": "string",
-- "contentMediaType": "application/rss+xml",
-- "description": "GET /s/feed.xml and /s/:space/feed.xml: RSS feeds."
-- }
-- ]
-- },
"errors.problem.v1": {
"title": "Problem",
"description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex e3f66d4f..5894178d 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.81.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 5c6d2805..335a2398 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.81.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 7c43c4cb..950d306b 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.81.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 060f0eb6..3fe4f47b 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.81.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 021b40fe..53c4e976 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.81.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 ffed804c..0fac9a2e 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -40,8 +40,8 @@
"service": "community",
"name": "OpenVibe.Community",
"origin": "https://openvibe.community",
-- "operations": 36,
-- "capabilities": 10
++ "operations": 24,
++ "capabilities": 7
},
{
"service": "coupons",
@@@ -134,6 +134,13 @@
"operations": 13,
"capabilities": 3
},
++ {
++ "service": "space",
++ "name": "OpenVibe.Space",
++ "origin": "https://openvibe.space",
++ "operations": 15,
++ "capabilities": 3
++ },
{
"service": "tips",
"name": "OpenVibe.Tips",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 53846056..67d46226 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.81.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 adfb13cc..fbc0f7f4 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.81.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 98e30c85..34e2d5a3 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.81.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 82b8b1eb..6a3e9d77 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.81.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 3c1a5faa..c4af2a25 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.81.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 83997ff5..a5e09b52 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.81.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 acc032e5..c7f03474 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.81.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 e6e2672f..56becb80 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.81.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.82.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
nnew file mode 100644
iindex 00000000..a07e6701
---- /dev/null
++++ b/generated/openapi/space.json
@@@ -0,0 +1,1973 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Space API",
++ "version": "0.82.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.82.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"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.space"
++ }
++ ],
++ "paths": {
++ "/api/v1/posts/{id}": {
++ "delete": {
++ "operationId": "delete_api_v1_posts_id",
++ "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"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$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"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_api_v1_posts_id",
++ "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/spaces": {
++ "get": {
++ "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"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/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}/threads": {
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.thread-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_space_threads",
++ "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}/threads/{slug}": {
++ "delete": {
++ "operationId": "delete_api_v1_spaces_space_threads_slug",
++ "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"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$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"
++ }
++ }
++ }
++ }
++ }
++ },
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "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.thread-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}/threads/{slug}/posts": {
++ "post": {
++ "operationId": "post_api_v1_spaces_space_threads_slug_posts",
++ "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}/votes": {
++ "post": {
++ "operationId": "post_api_v1_spaces_space_threads_slug_votes",
++ "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"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/s": {
++ "get": {
++ "operationId": "get_s",
++ "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"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/s/{space}": {
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.thread-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/s/{space}/feed.xml": {
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "space",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.thread-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/s/{space}/t/{slug}": {
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "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.thread-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/s/feed.xml": {
++ "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.",
++ "tags": [
++ "space.thread"
++ ],
++ "x-openvibe-capabilities": [
++ "space.thread.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "space-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "space.thread.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/space.thread-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "common.no-body.v1": {
++ "title": "NoBody",
++ "description": "common.no-body@1: the capability's request carries no body; its parameters are the path and query of the routes in implementedBy (an id, a page cursor). An input schema that says so explicitly, so every active capability names one (roadmap WS-C task 4).",
++ "type": "object",
++ "maxProperties": 0
++ },
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "space.forum-post.v1": {
++ "title": "SpaceForumPost",
++ "description": "space.forum-post@1: one forum post (the opening post of a thread or a reply) as OpenVibe.Space's API shows it (server/forum/service.js shapePost). A deleted post keeps its place with no author, body, attachments or pastes.",
++ "type": "object",
++ "required": [
++ "id",
++ "thread_id",
++ "is_opening",
++ "origin",
++ "author",
++ "body_markdown",
++ "body_html",
++ "revision",
++ "deleted",
++ "can_edit",
++ "attachments",
++ "pastes"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "thread_id": {
++ "type": "integer"
++ },
++ "is_opening": {
++ "type": "boolean"
++ },
++ "origin": {
++ "type": "string"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "body_markdown": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "body_html": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "deleted": {
++ "type": "boolean"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "can_edit": {
++ "type": "boolean"
++ },
++ "attachments": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "media_id",
++ "url"
++ ],
++ "properties": {
++ "media_id": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "filename": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "mime": {
++ "type": "string"
++ },
++ "size_bytes": {
++ "type": "integer"
++ }
++ }
++ }
++ },
++ "pastes": {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "url"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string"
++ },
++ "language": {
++ "type": "string"
++ },
++ "type": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string"
++ },
++ "screenshot_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "lines": {
++ "type": "integer"
++ },
++ "excerpt": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ },
++ "space.forum-read-result.v1": {
++ "title": "SpaceForumReadResult",
++ "description": "space.forum-read-result@1: answers of space.forum.read on OpenVibe.Space. GET /api/v1/spaces → { spaces, groups } (the spaces the caller can open, each with its last post, and the board index: groups in order with their top-level spaces and child boards); GET /api/v1/spaces/:space → { space } (a staff or members-only space the caller cannot open is 404 or 401/403 vip.members_only); GET /s is the board index as an HTML page.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "spaces",
++ "groups"
++ ],
++ "properties": {
++ "spaces": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/space.forum-space.v1"
++ }
++ },
++ "groups": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "spaces"
++ ],
++ "properties": {
++ "slug": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "spaces": {
++ "type": "array"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space"
++ ],
++ "properties": {
++ "space": {
++ "$ref": "#/components/schemas/space.forum-space.v1"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s: the board index page."
++ }
++ ]
++ },
++ "space.forum-space.v1": {
++ "title": "SpaceForumSpace",
++ "description": "space.forum-space@1: one forum space as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeSpace): its slug, name, visibility, thread kind and statuses, style, whether votes and reactions are on, its group and parent board, the members-only gate and, in lists, its counts and last activity.",
++ "type": "object",
++ "required": [
++ "slug",
++ "name",
++ "visibility",
++ "url",
++ "thread_kind",
++ "statuses",
++ "style",
++ "votes",
++ "reactions",
++ "group",
++ "parent",
++ "members_only"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ "members",
++ "staff"
++ ]
++ },
++ "url": {
++ "type": "string"
++ },
++ "thread_kind": {
++ "type": "string"
++ },
++ "statuses": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ }
++ },
++ "style": {
++ "enum": [
++ "feed",
++ "forum"
++ ]
++ },
++ "votes": {
++ "type": "boolean"
++ },
++ "reactions": {
++ "type": "boolean"
++ },
++ "group": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "parent": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ }
++ }
++ },
++ "post_count": {
++ "type": "integer"
++ },
++ "thread_count": {
++ "type": "integer"
++ },
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ]
++ },
++ "children": {
++ "type": "array"
++ }
++ }
++ },
++ "space.forum-thread.v1": {
++ "title": "SpaceForumThread",
++ "description": "space.forum-thread@1: one forum thread as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeThread): where it lives, its kind and status, category, members-only gate, author, origin, pin and lock state, score, reply count, views, pages, the last post and the caller's vote.",
++ "type": "object",
++ "required": [
++ "id",
++ "space",
++ "slug",
++ "title",
++ "url",
++ "kind",
++ "author",
++ "origin",
++ "pinned",
++ "locked",
++ "score",
++ "reply_count",
++ "pages",
++ "created_at",
++ "my_vote"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "space": {
++ "type": "string"
++ },
++ "slug": {
++ "type": "string"
++ },
++ "title": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "url": {
++ "type": "string",
++ "description": "/s/<space>/t/<slug>"
++ },
++ "kind": {
++ "type": "string",
++ "description": "discussion, request, roadmap, …"
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "category": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "slug": {
++ "type": "string"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "position": {},
++ "thread_count": {
++ "type": "integer"
++ }
++ }
++ },
++ "members_only": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string"
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "join_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ },
++ "origin": {
++ "type": "string"
++ },
++ "pinned": {
++ "type": "boolean"
++ },
++ "locked": {
++ "type": "boolean"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "reply_count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "views": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "last_post": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "author": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.",
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "username": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "display_name": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "avatar_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "profile_color": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "is_ai": {
++ "const": true
++ },
++ "is_system": {
++ "const": true
++ }
++ }
++ }
++ }
++ },
++ "crosspost_of": {},
++ "last_activity_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "created_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++ },
++ "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, …).",
++ "anyOf": [
++ {
++ "type": "object",
++ "anyOf": [
++ {
++ "required": [
++ "body"
++ ]
++ },
++ {
++ "required": [
++ "body_markdown"
++ ]
++ }
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "description": "A new thread's title."
++ },
++ "body": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40000,
++ "description": "Markdown."
++ },
++ "body_markdown": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40000,
++ "description": "Read when body is absent."
++ },
++ "members_only": {
++ "description": "A new thread only: true gates it to the author's own VIP members; { owner: usr_… } (or the subject itself) names the creator (moderators, or the author naming themselves); null or false: open.",
++ "anyOf": [
++ {
++ "type": [
++ "boolean",
++ "null"
++ ]
++ },
++ {
++ "type": "string",
++ "pattern": "^usr_"
++ },
++ {
++ "type": "object",
++ "required": [
++ "owner"
++ ],
++ "properties": {
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_"
++ }
++ }
++ }
++ ]
++ },
++ "category": {
++ "type": "string",
++ "description": "A new thread only: the slug of one of the space's categories."
++ },
++ "attachments": {
++ "description": "At most 4 images the writer uploaded (POST /api/v1/spaces/:space/attachments) and has not used yet, by media id. People only.",
++ "anyOf": [
++ {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ {
++ "type": "array",
++ "maxItems": 4,
++ "items": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ ]
++ },
++ "pastes": {
++ "description": "At most 4 shareable pastes (not private, not burn-after-read) by slug or /p/<slug> link: an array, or one string separated by spaces or commas.",
++ "type": [
++ "array",
++ "string"
++ ],
++ "items": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "enum": [
++ 1,
++ -1,
++ 0,
++ "1",
++ "-1",
++ "0"
++ ],
++ "description": "A thread vote; 0 takes the vote back."
++ }
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.no-body.v1"
++ }
++ ]
++ },
++ "space.post-write-result.v1": {
++ "title": "SpacePostWriteResult",
++ "description": "space.post-write-result@1: answers of the forum writes on OpenVibe.Space (space.post.write). POST /api/v1/spaces/:space/threads → 201 { thread, post } (the thread and its opening post); POST …/threads/:slug/posts → 201 { post, page, url } (the reply, the page it lands on and its address with #post-<id>); PUT /api/v1/posts/:id → { post }; POST …/threads/:slug/votes → { thread_id, score, upvotes, downvotes, my_vote }; DELETE /api/v1/posts/:id → { ok, id } (deleting the opening post deletes the thread: { ok, id, deleted: thread }); DELETE /api/v1/spaces/:space/threads/:slug → { ok, id, deleted: thread }. Refusals are problem+json.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "thread",
++ "post"
++ ],
++ "properties": {
++ "thread": {
++ "$ref": "#/components/schemas/space.forum-thread.v1"
++ },
++ "post": {
++ "$ref": "#/components/schemas/space.forum-post.v1"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "post"
++ ],
++ "properties": {
++ "post": {
++ "$ref": "#/components/schemas/space.forum-post.v1"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "url": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "thread_id",
++ "score",
++ "upvotes",
++ "downvotes",
++ "my_vote"
++ ],
++ "properties": {
++ "thread_id": {
++ "type": "integer"
++ },
++ "score": {
++ "type": "integer"
++ },
++ "upvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "downvotes": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "my_vote": {
++ "enum": [
++ 1,
++ -1,
++ 0
++ ]
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "id"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "id": {
++ "type": "integer"
++ },
++ "deleted": {
++ "const": "thread"
++ }
++ }
++ }
++ ]
++ },
++ "space.thread-read-result.v1": {
++ "title": "SpaceThreadReadResult",
++ "description": "space.thread-read-result@1: answers of space.thread.read on OpenVibe.Space. GET /api/v1/spaces/:space/threads?sort=&page=&limit=&category=&status= → a page of threads with the space, paging, categories, children and what the viewer may do; GET /api/v1/spaces/:space/threads/:slug?page= → the thread with a page of posts (reactions, author stats, crosspost info, attachments settings, paging, viewer). GET /s/:space and /s/:space/t/:slug are the same as HTML pages; GET /s/feed.xml and /s/:space/feed.xml are RSS feeds of public threads.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "threads",
++ "page",
++ "pages",
++ "total"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "sort": {
++ "type": "string"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "total": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "categories": {
++ "type": "array"
++ },
++ "category": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "status": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "viewer": {
++ "type": "object"
++ },
++ "children": {
++ "type": "array"
++ },
++ "groups": {
++ "type": "array"
++ },
++ "threads": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/space.forum-thread.v1"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "space",
++ "thread",
++ "posts",
++ "page",
++ "pages"
++ ],
++ "properties": {
++ "space": {
++ "type": "object"
++ },
++ "thread": {
++ "$ref": "#/components/schemas/space.forum-thread.v1"
++ },
++ "posts": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/space.forum-post.v1"
++ }
++ },
++ "reactions": {
++ "type": "array"
++ },
++ "crosspost": {
++ "type": "object"
++ },
++ "categories": {
++ "type": "array"
++ },
++ "attachments": {
++ "type": "object"
++ },
++ "page": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "per_page": {
++ "type": "integer"
++ },
++ "pages": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "total": {
++ "type": "integer"
++ },
++ "viewer": {
++ "type": "object"
++ }
++ }
++ },
++ {
++ "type": "string",
++ "contentMediaType": "text/html",
++ "description": "GET /s/:space and /s/:space/t/:slug: HTML pages."
++ },
++ {
++ "type": "string",
++ "contentMediaType": "application/rss+xml",
++ "description": "GET /s/feed.xml and /s/:space/feed.xml: RSS feeds."
++ }
++ ]
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex c8b6119a..54381901 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.81.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 6d503bfa..480da12b 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.81.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 cbe2f19a..a6e8d654 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.81.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 f735c618..9db5d9f1 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.81.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 8e8b6270..f4d0578d 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.81.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.81.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.82.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.82.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 89e2dc50..73345c1d 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -45238,6 +45238,435 @@ export type CommunityThreadReadResult =
}
| string;
++/** [email protected] (owner: space) */
++/**
++ * space.forum-post@1: one forum post (the opening post of a thread or a reply) as OpenVibe.Space's API shows it (server/forum/service.js shapePost). A deleted post keeps its place with no author, body, attachments or pastes.
++ */
++export interface SpaceForumPost {
++ id: number;
++ thread_id: number;
++ is_opening: boolean;
++ origin: string;
++ /**
++ * The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.
++ */
++ author: {
++ subject?: string | null;
++ username?: string | null;
++ display_name?: string | null;
++ avatar_url?: string | null;
++ profile_color?: string | null;
++ is_ai?: true;
++ is_system?: true;
++ } | null;
++ body_markdown: string | null;
++ body_html: string | null;
++ revision: number;
++ deleted: boolean;
++ created_at?: string | null;
++ updated_at?: string | null;
++ can_edit: boolean;
++ /**
++ * @maxItems 4
++ */
++ attachments:
++ | []
++ | [
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ }
++ ]
++ | [
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ }
++ ]
++ | [
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ }
++ ]
++ | [
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ },
++ {
++ media_id: string;
++ url: string;
++ filename?: string | null;
++ mime?: string;
++ size_bytes?: number;
++ }
++ ];
++ /**
++ * @maxItems 4
++ */
++ pastes:
++ | []
++ | [
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ }
++ ]
++ | [
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ }
++ ]
++ | [
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ }
++ ]
++ | [
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ },
++ {
++ slug: string;
++ title?: string;
++ language?: string;
++ type?: string;
++ url: string;
++ screenshot_url?: string | null;
++ lines?: number;
++ excerpt?: string | null;
++ }
++ ];
++}
++
++/** [email protected] (owner: space) */
++/**
++ * space.forum-read-result@1: answers of space.forum.read on OpenVibe.Space. GET /api/v1/spaces → { spaces, groups } (the spaces the caller can open, each with its last post, and the board index: groups in order with their top-level spaces and child boards); GET /api/v1/spaces/:space → { space } (a staff or members-only space the caller cannot open is 404 or 401/403 vip.members_only); GET /s is the board index as an HTML page.
++ */
++export type SpaceForumReadResult =
++ | {
++ spaces: SpaceForumSpace[];
++ groups: {
++ slug: string | null;
++ name: string;
++ description?: string | null;
++ spaces: unknown[];
++ }[];
++ }
++ | {
++ space: SpaceForumSpace;
++ }
++ | string;
++
++/** [email protected] (owner: space) */
++/**
++ * space.forum-space@1: one forum space as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeSpace): its slug, name, visibility, thread kind and statuses, style, whether votes and reactions are on, its group and parent board, the members-only gate and, in lists, its counts and last activity.
++ */
++export interface SpaceForumSpace {
++ slug: string;
++ name: string;
++ description?: string | null;
++ visibility: "public" | "members" | "staff";
++ url: string;
++ thread_kind: string;
++ statuses: string[];
++ style: "feed" | "forum";
++ votes: boolean;
++ reactions: boolean;
++ group: {
++ slug?: string;
++ name?: string;
++ } | null;
++ parent: {
++ slug?: string;
++ name?: string;
++ } | null;
++ post_count?: number;
++ thread_count?: number;
++ last_activity_at?: string | null;
++ members_only: {
++ owner?: string;
++ owner_username?: string | null;
++ join_url?: string | null;
++ } | null;
++ last_post?: {} | null;
++ children?: unknown[];
++}
++
++/** [email protected] (owner: space) */
++/**
++ * space.forum-thread@1: one forum thread as OpenVibe.Space's /api/v1/spaces API shows it (server/forum/service.js shapeThread): where it lives, its kind and status, category, members-only gate, author, origin, pin and lock state, score, reply count, views, pages, the last post and the caller's vote.
++ */
++export interface SpaceForumThread {
++ id: number;
++ space: string;
++ slug: string;
++ title: string;
++ /**
++ * /s/<space>/t/<slug>
++ */
++ url: string;
++ /**
++ * discussion, request, roadmap, …
++ */
++ kind: string;
++ status?: string | null;
++ category?: {
++ slug?: string;
++ name?: string;
++ description?: string | null;
++ position?: unknown;
++ thread_count?: number;
++ } | null;
++ members_only?: {
++ owner?: string;
++ owner_username?: string | null;
++ join_url?: string | null;
++ } | null;
++ /**
++ * The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.
++ */
++ author: {
++ subject?: string | null;
++ username?: string | null;
++ display_name?: string | null;
++ avatar_url?: string | null;
++ profile_color?: string | null;
++ is_ai?: true;
++ is_system?: true;
++ } | null;
++ origin: string;
++ pinned: boolean;
++ locked: boolean;
++ score: number;
++ reply_count: number;
++ views?: number;
++ pages: number;
++ last_post?: {
++ id?: number;
++ /**
++ * The author projection: a person (subject and Network profile), OpenVibe AI (is_ai) or the system (is_system); null when unknown or deleted.
++ */
++ author?: {
++ subject?: string | null;
++ username?: string | null;
++ display_name?: string | null;
++ avatar_url?: string | null;
++ profile_color?: string | null;
++ is_ai?: true;
++ is_system?: true;
++ } | null;
++ } | null;
++ crosspost_of?: unknown;
++ last_activity_at?: string | null;
++ created_at: string | null;
++ my_vote: 1 | -1 | 0;
++}
++
++/** [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, …).
++ */
++export type SpacePostWriteRequest =
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | {
++ /**
++ * A thread vote; 0 takes the vote back.
++ */
++ value: 1 | -1 | 0 | "1" | "-1" | "0";
++ }
++ | NoBody;
++
++/** [email protected] (owner: space) */
++/**
++ * space.post-write-result@1: answers of the forum writes on OpenVibe.Space (space.post.write). POST /api/v1/spaces/:space/threads → 201 { thread, post } (the thread and its opening post); POST …/threads/:slug/posts → 201 { post, page, url } (the reply, the page it lands on and its address with #post-<id>); PUT /api/v1/posts/:id → { post }; POST …/threads/:slug/votes → { thread_id, score, upvotes, downvotes, my_vote }; DELETE /api/v1/posts/:id → { ok, id } (deleting the opening post deletes the thread: { ok, id, deleted: thread }); DELETE /api/v1/spaces/:space/threads/:slug → { ok, id, deleted: thread }. Refusals are problem+json.
++ */
++export type SpacePostWriteResult =
++ | {
++ thread: SpaceForumThread;
++ post: SpaceForumPost;
++ }
++ | {
++ post: SpaceForumPost;
++ page?: number;
++ url?: string;
++ }
++ | {
++ thread_id: number;
++ score: number;
++ upvotes: number;
++ downvotes: number;
++ my_vote: 1 | -1 | 0;
++ }
++ | {
++ ok: true;
++ id: number;
++ deleted?: "thread";
++ };
++
++/** [email protected] (owner: space) */
++/**
++ * space.thread-read-result@1: answers of space.thread.read on OpenVibe.Space. GET /api/v1/spaces/:space/threads?sort=&page=&limit=&category=&status= → a page of threads with the space, paging, categories, children and what the viewer may do; GET /api/v1/spaces/:space/threads/:slug?page= → the thread with a page of posts (reactions, author stats, crosspost info, attachments settings, paging, viewer). GET /s/:space and /s/:space/t/:slug are the same as HTML pages; GET /s/feed.xml and /s/:space/feed.xml are RSS feeds of public threads.
++ */
++export type SpaceThreadReadResult =
++ | {
++ space: {};
++ sort?: string;
++ page: number;
++ per_page?: number;
++ total: number;
++ pages: number;
++ categories?: unknown[];
++ category?: string | null;
++ status?: string | null;
++ viewer?: {};
++ children?: unknown[];
++ groups?: unknown[];
++ threads: SpaceForumThread[];
++ }
++ | {
++ space: {};
++ thread: SpaceForumThread;
++ posts: SpaceForumPost[];
++ reactions?: unknown[];
++ crosspost?: {};
++ categories?: unknown[];
++ attachments?: {};
++ page: number;
++ per_page?: number;
++ pages: number;
++ total?: number;
++ viewer?: {};
++ }
++ | string;
++
/** [email protected] (owner: openre) */
/**
* openre.destination@1: one restream destination of a stream on OpenRe.Stream (server/store/outputs.js publicDest). The stream key and SRT passphrase are write-only: only whether they are set and a hint of the key are shown.
ddiff --git a/manifests/capabilities/community.post.create.json b/manifests/capabilities/community.post.create.json
iindex 879f613d..877173bd 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": "active",
++ "status": "deprecated",
"visibility": "public",
"sensitive": true,
-- "description": "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": "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.",
"outputSchema": "community.post-write-result@1",
"permissions": [
"community:write"
ddiff --git a/manifests/capabilities/community.space.manage.json b/manifests/capabilities/community.space.manage.json
iindex 4c379ec2..4a736ada 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": "planned",
++ "status": "deprecated",
"visibility": "internal",
-- "description": "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": "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.",
"permissions": [
"community:moderate"
],
ddiff --git a/manifests/capabilities/community.space.read.json b/manifests/capabilities/community.space.read.json
iindex 1b61d2f8..177625da 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": "active",
++ "status": "deprecated",
"visibility": "public",
-- "description": "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": "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.",
"outputSchema": "community.space-read-result@1",
"permissions": [
"community:read"
ddiff --git a/manifests/capabilities/community.thread.read.json b/manifests/capabilities/community.thread.read.json
iindex f5d9bab4..a9e11dc3 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": "active",
++ "status": "deprecated",
"visibility": "public",
-- "description": "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": "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.",
"outputSchema": "community.thread-read-result@1",
"permissions": [
"community:read"
ddiff --git a/manifests/capabilities/community.vote.set.json b/manifests/capabilities/community.vote.set.json
iindex c4ded914..ef4a1c45 100644
---- a/manifests/capabilities/community.vote.set.json
++++ b/manifests/capabilities/community.vote.set.json
@@@ -4,7 +4,7 @@
"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 community.post.create (threads) and community.comment.write (comments), which keep covering them.",
++ "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.",
"inputSchema": "common.entity-ref@1",
"permissions": [
"community:vote"
ddiff --git a/manifests/capabilities/download.file.read.json b/manifests/capabilities/download.file.read.json
nnew file mode 100644
iindex 00000000..d1fd73d7
---- /dev/null
++++ b/manifests/capabilities/download.file.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "download.file.read",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: list and read files and folders in the caller's drive on openvibe.download, and resolve a share link (plan T10 item 12). Bytes are Media files/objects.",
++ "permissions": [
++ "download:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "download-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/download.file.upload.json b/manifests/capabilities/download.file.upload.json
nnew file mode 100644
iindex 00000000..e84fa915
---- /dev/null
++++ b/manifests/capabilities/download.file.upload.json
@@@ -0,0 +1,18 @@
++{
++ "id": "download.file.upload",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: upload a file to openvibe.download, resumable and for large files, as the acting person (account-only until T14, plan T10 D8/item 12). Bytes go to OpenVibe.Media.",
++ "permissions": [
++ "download:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "download-upload",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/download.share.create.json b/manifests/capabilities/download.share.create.json
nnew file mode 100644
iindex 00000000..2122e2e2
---- /dev/null
++++ b/manifests/capabilities/download.share.create.json
@@@ -0,0 +1,18 @@
++{
++ "id": "download.share.create",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: create a share link for a file or folder on openvibe.download (plan T10 item 12). Account-only.",
++ "permissions": [
++ "download:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "download-share",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/pics.album.manage.json b/manifests/capabilities/pics.album.manage.json
nnew file mode 100644
iindex 00000000..7c6b4011
---- /dev/null
++++ b/manifests/capabilities/pics.album.manage.json
@@@ -0,0 +1,18 @@
++{
++ "id": "pics.album.manage",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: create, rename and delete an album and add or remove its images on openvibe.pics (plan T10 D5). Account-only.",
++ "permissions": [
++ "pics:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "pics-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/pics.album.read.json b/manifests/capabilities/pics.album.read.json
nnew file mode 100644
iindex 00000000..c2636ebc
---- /dev/null
++++ b/manifests/capabilities/pics.album.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "pics.album.read",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: read an album and its images on openvibe.pics (plan T10 D5).",
++ "permissions": [
++ "pics:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "pics-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/pics.image.read.json b/manifests/capabilities/pics.image.read.json
nnew file mode 100644
iindex 00000000..52ce3197
---- /dev/null
++++ b/manifests/capabilities/pics.image.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "pics.image.read",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: read an image on openvibe.pics: its metadata and page, and the image pastes/screenshots that become Pics (plan T10 D5). Bytes are Media objects under a pics tenant.",
++ "permissions": [
++ "pics:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "pics-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/pics.image.upload.json b/manifests/capabilities/pics.image.upload.json
nnew file mode 100644
iindex 00000000..c818f305
---- /dev/null
++++ b/manifests/capabilities/pics.image.upload.json
@@@ -0,0 +1,18 @@
++{
++ "id": "pics.image.upload",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: upload an image to openvibe.pics as the acting person (account-only until T14 upload safety, plan T10 D8). Bytes go to OpenVibe.Media under a pics tenant.",
++ "permissions": [
++ "pics:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "pics-upload",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/space.forum.manage.json b/manifests/capabilities/space.forum.manage.json
nnew file mode 100644
iindex 00000000..7a1f8b2a
---- /dev/null
++++ b/manifests/capabilities/space.forum.manage.json
@@@ -0,0 +1,19 @@
++{
++ "id": "space.forum.manage",
++ "version": "1.0",
++ "owner": "space",
++ "status": "planned",
++ "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.",
++ "permissions": [
++ "space:moderate"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "space-moderate",
++ "events": [],
++ "implementedBy": [
++ "PUT /api/v1/spaces/:space/members-only"
++ ]
++}
ddiff --git a/manifests/capabilities/space.forum.read.json b/manifests/capabilities/space.forum.read.json
nnew file mode 100644
iindex 00000000..522c8aea
---- /dev/null
++++ b/manifests/capabilities/space.forum.read.json
@@@ -0,0 +1,23 @@
++{
++ "id": "space.forum.read",
++ "version": "1.0",
++ "owner": "space",
++ "status": "active",
++ "visibility": "public",
++ "description": "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.",
++ "outputSchema": "space.forum-read-result@1",
++ "permissions": [
++ "space:read"
++ ],
++ "resourceConstraints": [
++ "space_member"
++ ],
++ "quotaClass": "space-read",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/spaces",
++ "GET /api/v1/spaces/:space",
++ "GET /s"
++ ],
++ "inputSchema": "common.no-body@1"
++}
ddiff --git a/manifests/capabilities/space.post.write.json b/manifests/capabilities/space.post.write.json
nnew file mode 100644
iindex 00000000..d022c64f
---- /dev/null
++++ b/manifests/capabilities/space.post.write.json
@@@ -0,0 +1,26 @@
++{
++ "id": "space.post.write",
++ "version": "1.1",
++ "owner": "space",
++ "status": "active",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "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.",
++ "outputSchema": "space.post-write-result@1",
++ "permissions": [
++ "space:write"
++ ],
++ "resourceConstraints": [
++ "space_member"
++ ],
++ "quotaClass": "space-write",
++ "events": [],
++ "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"
++ ],
++ "inputSchema": "space.post-write-request@1"
++}
ddiff --git a/manifests/capabilities/space.pulse.read.json b/manifests/capabilities/space.pulse.read.json
nnew file mode 100644
iindex 00000000..8ff9805b
---- /dev/null
++++ b/manifests/capabilities/space.pulse.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "space.pulse.read",
++ "version": "1.0",
++ "owner": "space",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: read the network Pulse activity OpenVibe.Space shows beside its forums (plan T10 D3). Community keeps the Pulse store and its own community.pulse.read/write; Space reads the same public feed for its pages. Not built.",
++ "permissions": [
++ "space:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "space-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/space.thread.read.json b/manifests/capabilities/space.thread.read.json
nnew file mode 100644
iindex 00000000..a88231ee
---- /dev/null
++++ b/manifests/capabilities/space.thread.read.json
@@@ -0,0 +1,26 @@
++{
++ "id": "space.thread.read",
++ "version": "1.0",
++ "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.",
++ "outputSchema": "space.thread-read-result@1",
++ "permissions": [
++ "space:read"
++ ],
++ "resourceConstraints": [
++ "space_member"
++ ],
++ "quotaClass": "space-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"
++ ],
++ "inputSchema": "common.no-body@1"
++}
ddiff --git a/manifests/capabilities/space.thread.write.json b/manifests/capabilities/space.thread.write.json
nnew file mode 100644
iindex 00000000..a7a4ad52
---- /dev/null
++++ b/manifests/capabilities/space.thread.write.json
@@@ -0,0 +1,18 @@
++{
++ "id": "space.thread.write",
++ "version": "1.0",
++ "owner": "space",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: thread lifecycle as its own grant on OpenVibe.Space: create, rename, pin, lock and delete a thread (split out of community.post.create, plan T10 D3; space.post.write keeps replies, edits, votes and deletes until this lands). Account-only.",
++ "permissions": [
++ "space:write"
++ ],
++ "resourceConstraints": [
++ "space_member"
++ ],
++ "quotaClass": "space-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/video.playlist.manage.json b/manifests/capabilities/video.playlist.manage.json
nnew file mode 100644
iindex 00000000..4145b78c
---- /dev/null
++++ b/manifests/capabilities/video.playlist.manage.json
@@@ -0,0 +1,18 @@
++{
++ "id": "video.playlist.manage",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "sensitive": true,
++ "description": "PLANNED: create, rename, reorder and delete a playlist on openvibe.video as the acting person (plan T10 D6). Account-only.",
++ "permissions": [
++ "video:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "video-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/video.playlist.read.json b/manifests/capabilities/video.playlist.read.json
nnew file mode 100644
iindex 00000000..1e3748c6
---- /dev/null
++++ b/manifests/capabilities/video.playlist.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "video.playlist.read",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: read a playlist and its ordered items on openvibe.video (a new table; playlists do not exist anywhere today, plan T10 D6).",
++ "permissions": [
++ "video:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "video-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/video.vod.read.json b/manifests/capabilities/video.vod.read.json
nnew file mode 100644
iindex 00000000..224cbe1f
---- /dev/null
++++ b/manifests/capabilities/video.vod.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "video.vod.read",
++ "version": "1.0",
++ "owner": "media-hub",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED: read one VOD or clip for a watch page on openvibe.video: the player source (a Media object), its chapters (derived from the AI moments/timeline), the WebVTT caption track (rendered from the AI transcript) and the playlists it belongs to. Bytes stay in OpenVibe.Media; the media hub reads Media, AI and Live (plan T10, D4/D6).",
++ "permissions": [
++ "video:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "video-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/services/media-hub.json b/manifests/services/media-hub.json
nnew file mode 100644
iindex 00000000..74553fd2
---- /dev/null
++++ b/manifests/services/media-hub.json
@@@ -0,0 +1,69 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.82.0 <1.0.0"
++ },
++ "id": "media-hub",
++ "name": "OpenVibe.MediaHub",
++ "status": "placeholder",
++ "repository": "OpenVibers/OpenVibe.MediaHub",
++ "domains": [
++ "openvibe.video",
++ "openvibe.pics",
++ "openvibe.download"
++ ],
++ "publicOrigin": "https://openvibe.video",
++ "exposure": {
++ "state": "placeholder",
++ "publicSite": "placeholder",
++ "note": "planned: no media-hub service yet; openvibe.video, openvibe.pics and openvibe.download still serve the OpenVibe.Sites placeholder. One service, three brand surfaces (plan T10, D4)"
++ },
++ "site": {
++ "name": "Media Hub",
++ "icon": "media-hub",
++ "tagline": "Video, pics and downloads",
++ "what": "The media-hub products in one service: video watch pages, chapters, captions and playlists (openvibe.video); image hosting and albums (openvibe.pics); share links, drive and backup (openvibe.download). Bytes stay in OpenVibe.Media.",
++ "legalProfile": "hosting",
++ "position": 20
++ },
++ "capabilities": [
++ "video.vod.read",
++ "video.playlist.read",
++ "video.playlist.manage",
++ "pics.image.read",
++ "pics.image.upload",
++ "pics.album.read",
++ "pics.album.manage",
++ "download.file.read",
++ "download.file.upload",
++ "download.share.create"
++ ],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "video.*",
++ "pics.*",
++ "download.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "none": "planned service: no OpenVibe.MediaHub process runs yet (plan T10 items 10-12)"
++ },
++ "shutdown": {
++ "none": "planned service: nothing to stop yet"
++ },
++ "startupRecovery": {
++ "none": "planned service: nothing runs to resume"
++ },
++ "rollback": {
++ "none": "planned service: nothing is deployed to roll back"
++ },
++ "contracts": {
++ "range": ">=0.82.0 <1.0.0"
++ },
++ "leases": {
++ "none": "planned service: no process, no claims"
++ }
++ },
++ "notes": "One planned media-hub service with three brand profiles (plan T10, D4). The three domains are carried in one manifest (domains is a list, as network/live/openre/games already do); the single `site` block presents the hub, and the per-brand nav profiles await the Web platform brand mechanism (ADR-039, T11). Uploads are account-only until T14 (D8). No video.*, pics.* or download.* capability is active, so none has a request/result contract yet."
++}
ddiff --git a/manifests/services/space.json b/manifests/services/space.json
nnew file mode 100644
iindex 00000000..53871b9b
---- /dev/null
++++ b/manifests/services/space.json
@@@ -0,0 +1,61 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.82.0 <1.0.0"
++ },
++ "id": "space",
++ "name": "OpenVibe.Space",
++ "status": "placeholder",
++ "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"
++ },
++ "site": {
++ "name": "Space",
++ "icon": "space",
++ "tagline": "Forums, boards and threads",
++ "what": "OpenVibe's forums: public and members-only spaces, threads, posts, votes, categories and the Discord relay, moved off Community (plan T10, D1/D2).",
++ "legalProfile": "ugc",
++ "position": 19
++ },
++ "capabilities": [
++ "space.forum.read",
++ "space.forum.manage",
++ "space.thread.read",
++ "space.thread.write",
++ "space.post.write",
++ "space.pulse.read"
++ ],
++ "eventsProduced": [],
++ "eventsConsumed": [],
++ "namespacesOwned": [
++ "space.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "none": "planned service: no OpenVibe.Space process runs yet (plan T10 item 3)"
++ },
++ "shutdown": {
++ "none": "planned service: nothing to stop yet"
++ },
++ "startupRecovery": {
++ "none": "planned service: nothing runs to resume"
++ },
++ "rollback": {
++ "none": "planned service: nothing is deployed to roll back"
++ },
++ "contracts": {
++ "range": ">=0.82.0 <1.0.0"
++ },
++ "leases": {
++ "none": "planned service: no process, no claims"
++ }
++ },
++ "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."
++}
ddiff --git a/package.json b/package.json
iindex bb4520d9..68d3d504 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.81.0",
++ "version": "0.82.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 2cbda9af..50c7e04f 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -440,7 +440,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.space.read': 'active', 'community.space.manage': 'planned', 'community.thread.read': 'active', 'community.vote.set': 'planned', 'community.pulse.read': 'active',
++ 'community.vote.set': 'planned', 'community.pulse.read': 'active', 'space.forum.read': 'active', 'space.forum.manage': 'planned', 'space.thread.read': 'active',
'search.query.run': 'active',
};
for (const [id, status] of Object.entries(gaps)) {
@@@ -451,7 +451,7 @@ for (const id of ['events.event.publish', 'events.event.read', 'events.subscript
}
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(/community\.post\.create/.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/.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(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');