openvibers/openvibe.contracts Commits

Capability schemas (WS-C task 4): seven older references now describe what the routes really take and return. events.event.read answers events.read-result@1; codes.release.read answers codes.release-read-result@1; community.paste.create answers community.paste-create-result@1; community.post.create answers community.post-write-result@1 (forum-thread and forum-post records); community.pulse.write takes community.pulse-write-request@1; games.mod.manage takes games.mod-manage-request@1 and games.mod.read answers games.mod-read-result@1 (mod-view record). Fixtures for each (unreleased)

OP OpenVibers committed 25 Sept 2026 · 46f6517727 · 51 files, +2660 −217


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex dc8d6f4f..1b1fd5a3 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -10,6 +10,8 @@ Capability schemas (roadmap WS-C task 4), starting with Search: **`search.query@
Every active capability now names both schemas: **the gap list is empty (162 → 0; 178/178 active capabilities complete)**. 275 new request, result and record schemas, each read from the owning service's routes, with valid and invalid fixtures. By owner: **chat** (2: the `/ws/chat` chat frame and its broadcast, the Live bridge batch), **community** (10: paste record, paste create/write/moderate, forum post writes, discussion moderation, Pulse writes), **billing** (19: transaction, balance, cashout, subscription, entitlement and intent records with each capability's request and result), **ai** (7: provider and model records, provider management, usage, template/workflow/route versions), **events** (8: publish result, pull reads and checkpoints, subscriptions, delivery admin), **network** (9), **vip** (26), **tips** (25), **live** (6), **games** (5, including the mod runtime bindings for props and announcements), **openre** (13), **sources** (2), **wiki** (18), **blog** (20), **codes** (2), **host** (10), **news** (24), **reviews** (23), **deals** (15), **coupons** (12) and **trade** (19). Where one capability covers several routes with different bodies, its schema is an `anyOf` of one branch per body. Requests set `additionalProperties: false` only where the service refuses unknown fields. Capabilities whose routes all take no body name `common.no-body@1`. Additive.
++Seven older capability schema references are corrected to what the routes take and return. `events.event.read` now answers `events.read-result@1` (was the bare envelope). `codes.release.read` answers the new **`codes.release-read-result@1`** (was `codes.app-manifest@1`). `community.paste.create` answers the new **`community.paste-create-result@1`**. `community.post.create` answers the new **`community.post-write-result@1`**, built on the new **`community.forum-thread@1`** and **`community.forum-post@1`** records (both were `common.entity-ref@1`). `community.pulse.write` takes the new **`community.pulse-write-request@1`** (was `common.entity-ref@1`). `games.mod.manage` takes the new **`games.mod-manage-request@1`**, and `games.mod.read` answers the new **`games.mod-read-result@1`** built on the new **`games.mod-view@1`** (both were `mods.mod-manifest@1`). No contract changes shape; only the capability manifests point elsewhere. Additive.
++
## 0.49.0 — 2026-09-25
Platform blocks (roadmap WS-E task 5). **`network.block.changed`**: a person blocked or unblocked someone (`blocker`, `blocked`, `active`, a per-pair `revision`); Network keeps the blocks, keyed by subjects, and Chat and Community consume the event. New capability **`network.blocks.read`** (first-party, never for apps): `GET /internal/blocks?subject=` lists who a person blocked and who blocked them. Fixtures for a block and an unblock. Additive.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 651bc39d..1aaadd03 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -268,6 +268,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "codes.release-read-result",
++ "version": "1.0.0",
++ "owner": "codes",
++ "schema": "codes/release-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "events.redaction-directive",
"version": "1.0.0",
@@@ -3633,6 +3642,33 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "games.mod-manage-request",
++ "version": "1.0.0",
++ "owner": "games",
++ "schema": "games/mod-manage-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "games.mod-read-result",
++ "version": "1.0.0",
++ "owner": "games",
++ "schema": "games/mod-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "games.mod-view",
++ "version": "1.0.0",
++ "owner": "games",
++ "schema": "games/mod-view.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "media.object.deleted",
"version": "1.0.0",
@@@ -3897,6 +3933,51 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "community.forum-post",
++ "version": "1.0.0",
++ "owner": "community",
++ "schema": "community/forum-post.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "community.forum-thread",
++ "version": "1.0.0",
++ "owner": "community",
++ "schema": "community/forum-thread.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "community.paste-create-result",
++ "version": "1.0.0",
++ "owner": "community",
++ "schema": "community/paste-create-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "community.post-write-result",
++ "version": "1.0.0",
++ "owner": "community",
++ "schema": "community/post-write-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "community.pulse-write-request",
++ "version": "1.0.0",
++ "owner": "community",
++ "schema": "community/pulse-write-request.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "openre.destination",
"version": "1.0.0",
ddiff --git a/contracts/codes/release-read-result.v1.json b/contracts/codes/release-read-result.v1.json
nnew file mode 100644
iindex 00000000..62eb25d7
---- /dev/null
++++ b/contracts/codes/release-read-result.v1.json
@@@ -0,0 +1,120 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/codes/release-read-result.v1.json",
++ "title": "CodesReleaseReadResult",
++ "description": "codes.release-read-result@1: answers of codes.release.read on OpenVibe.Codes (public; no token needed, but a token that is presented must hold the capability; never drafts). GET /api/v1/apps/:app/releases → { app_id, trust, releases } (published, deprecated and revoked releases, newest first); GET /api/v1/apps/:app/trust → { app_id, tier, note, set_by, set_at, note_on_authority } (metadata only: grants in OpenVibe.Network are the authority); GET /api/v1/releases/:id → { release, manifest } (the manifest as published: codes.app-manifest@1 for an app, mods.mod-manifest@1 for a mod). An id that is not an app or a release is 404.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "app_id",
++ "trust",
++ "releases"
++ ],
++ "properties": {
++ "app_id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "trust": {
++ "type": "object",
++ "required": [
++ "tier"
++ ],
++ "properties": {
++ "tier": {
++ "type": "string"
++ },
++ "note": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_by": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "releases": {
++ "type": "array",
++ "items": {
++ "$ref": "release.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "app_id",
++ "tier",
++ "note_on_authority"
++ ],
++ "properties": {
++ "app_id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "tier": {
++ "type": "string"
++ },
++ "note": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_by": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "note_on_authority": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "release",
++ "manifest"
++ ],
++ "properties": {
++ "release": {
++ "$ref": "release.v1.json"
++ },
++ "manifest": {
++ "anyOf": [
++ {
++ "$ref": "app-manifest.v1.json"
++ },
++ {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/community/forum-post.v1.json b/contracts/community/forum-post.v1.json
nnew file mode 100644
iindex 00000000..2f0f95f7
---- /dev/null
++++ b/contracts/community/forum-post.v1.json
@@@ -0,0 +1,190 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/forum-post.v1.json",
++ "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"
++ ],
++ "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/community/forum-thread.v1.json b/contracts/community/forum-thread.v1.json
nnew file mode 100644
iindex 00000000..c5cc0051
---- /dev/null
++++ b/contracts/community/forum-thread.v1.json
@@@ -0,0 +1,247 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/forum-thread.v1.json",
++ "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"
++ ]
++ },
++ "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/community/paste-create-result.v1.json b/contracts/community/paste-create-result.v1.json
nnew file mode 100644
iindex 00000000..cc68661c
---- /dev/null
++++ b/contracts/community/paste-create-result.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/paste-create-result.v1.json",
++ "title": "CommunityPasteCreateResult",
++ "description": "community.paste-create-result@1: the answer of POST /api/pastes on OpenVibe.Community (community.paste.create), a text paste or, with a multipart screenshot part, an image paste (server/pastes/service.js created): 201 { id, slug, url, paste } — url is /p/<slug> and paste the new paste as community.paste@1. Refusals are { error } with 400 (no content, too large, not an image), 409 (a service's slug is taken), 429 (cooldown or daily limit) or 502/503 (Media unavailable for a screenshot).",
++ "type": "object",
++ "required": [
++ "id",
++ "slug",
++ "url",
++ "paste"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "slug": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string",
++ "pattern": "^/p/"
++ },
++ "paste": {
++ "$ref": "paste.v1.json"
++ }
++ }
++}
ddiff --git a/contracts/community/post-write-result.v1.json b/contracts/community/post-write-result.v1.json
nnew file mode 100644
iindex 00000000..e457a085
---- /dev/null
++++ b/contracts/community/post-write-result.v1.json
@@@ -0,0 +1,92 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/post-write-result.v1.json",
++ "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"
++ ],
++ "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/community/pulse-write-request.v1.json b/contracts/community/pulse-write-request.v1.json
nnew file mode 100644
iindex 00000000..f36de4a3
---- /dev/null
++++ b/contracts/community/pulse-write-request.v1.json
@@@ -0,0 +1,56 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/pulse-write-request.v1.json",
++ "title": "CommunityPulseWriteRequest",
++ "description": "community.pulse-write-request@1: the body of POST /api/v1/pulse/items on OpenVibe.Community (community.pulse.write; service tokens only): an item about one of the calling service's own entities (ref.service must be that service: 403 pulse.foreign_ref otherwise), with a title (whitespace collapsed, 1 to 300 characters) and an absolute http(s) url (at most 2000 characters). origin defaults to X-OV-Origin (user, ai or system; X-OV-Origin: ai always wins); an origin user item names the person in X-OV-Subject. occurred_at is an ISO time, not in the future (default now). Only public activity enters Pulse: a visibility other than public is 400 pulse.not_public. The same ref again updates the item. Unknown fields are ignored. DELETE /api/v1/pulse/items/:service/:type/:id takes no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ref",
++ "title",
++ "url"
++ ],
++ "properties": {
++ "ref": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "pattern": "\\S",
++ "description": "At most 300 characters once whitespace is collapsed."
++ },
++ "url": {
++ "type": "string",
++ "maxLength": 2000,
++ "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
++ },
++ "origin": {
++ "enum": [
++ "user",
++ "ai",
++ "system",
++ null
++ ]
++ },
++ "occurred_at": {
++ "type": [
++ "string",
++ "integer",
++ "null"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ null
++ ]
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/events/read-result.v1.json b/contracts/events/read-result.v1.json
iindex 83094ffc..e7301222 100644
---- a/contracts/events/read-result.v1.json
++++ b/contracts/events/read-result.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"title": "EventsReadResult",
-- "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.",
++ "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). GET /realtime/stream?topics= (events.event.read) is a Server-Sent Events feed whose messages carry the same { seq, event } (id: the seq; a gap message when the cursor is older than retention). An app sees only its project's events in its token's environment plus public first-party events.",
"anyOf": [
{
"type": "object",
ddiff --git a/contracts/games/mod-manage-request.v1.json b/contracts/games/mod-manage-request.v1.json
nnew file mode 100644
iindex 00000000..0c980b99
---- /dev/null
++++ b/contracts/games/mod-manage-request.v1.json
@@@ -0,0 +1,110 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/games/mod-manage-request.v1.json",
++ "title": "GamesModManageRequest",
++ "description": "games.mod-manage-request@1: bodies of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods installs a mod: { manifest, pack?, approve?, trustTier?, enable? } — manifest is a mods.mod-manifest@1 the Games runtime can run (games.browser, games-content@1); pack is its games-content@1 data (announcements and props, validated strictly: unknown keys refused); approve lists at most 64 requested capabilities to grant; trustTier is unreviewed (default), reviewed or first-party; enable true starts it enabled. A mod already installed is 409 mod.exists. POST /api/v1/mods/:id/grants: { capability }. POST /api/v1/mods/:id/revoke: { reason? } (terminal). POST /api/v1/mods/:id/enable|disable take an empty JSON object or no body; DELETE /api/v1/mods/:id/grants/:capability and GET /api/v1/mods/:id/audit take no body. Bodies are at most 256 KB; unknown top-level fields are ignored.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "manifest"
++ ],
++ "properties": {
++ "manifest": {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ "pack": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "announcements": {
++ "type": "array",
++ "maxItems": 10,
++ "items": {
++ "$ref": "announcement.v1.json"
++ }
++ },
++ "props": {
++ "type": "array",
++ "maxItems": 50,
++ "items": {
++ "type": "object",
++ "required": [
++ "key",
++ "item",
++ "pos"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,39}$"
++ },
++ "item": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 64
++ },
++ "pos": {
++ "type": "array",
++ "minItems": 3,
++ "maxItems": 3,
++ "items": {
++ "type": "number",
++ "minimum": -4096,
++ "maximum": 4096
++ }
++ },
++ "yaw": {
++ "type": "number",
++ "minimum": -10,
++ "maximum": 10
++ }
++ }
++ }
++ }
++ }
++ },
++ "approve": {
++ "type": "array",
++ "maxItems": 64,
++ "items": {
++ "type": "string"
++ }
++ },
++ "trustTier": {
++ "enum": [
++ "unreviewed",
++ "reviewed",
++ "first-party"
++ ]
++ },
++ "enable": {
++ "type": "boolean"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "capability"
++ ],
++ "properties": {
++ "capability": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "reason": {
++ "type": "string",
++ "maxLength": 500,
++ "description": "Revoke only; cut to 500 characters."
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/games/mod-manage-result.v1.json b/contracts/games/mod-manage-result.v1.json
iindex 045cab82..5810b989 100644
---- a/contracts/games/mod-manage-result.v1.json
++++ b/contracts/games/mod-manage-result.v1.json
@@@ -5,76 +5,7 @@
"description": "games.mod-manage-result@1: answers of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods → 201 the installed mod's public view; POST /api/v1/mods/:id/enable|disable|revoke, POST /api/v1/mods/:id/grants and DELETE /api/v1/mods/:id/grants/:capability → 200 the mod's public view after the change (revoke is terminal); GET /api/v1/mods/:id/audit?limit= → { audit } (install, grant, use, deny and revoke entries, at most 500). Errors are problem+json (mod.not_found, mod.invalid_request, capability.denied, …).",
"anyOf": [
{
-- "type": "object",
-- "required": [
-- "id",
-- "name",
-- "version",
-- "target",
-- "runtime",
-- "trust_tier",
-- "status",
-- "requested",
-- "granted",
-- "installed_at",
-- "updated_at"
-- ],
-- "properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^mod_"
-- },
-- "name": {
-- "type": "string"
-- },
-- "version": {
-- "type": "string"
-- },
-- "target": {
-- "type": "string",
-- "description": "e.g. games.browser"
-- },
-- "runtime": {
-- "type": "string",
-- "description": "e.g. games-content@1"
-- },
-- "trust_tier": {
-- "enum": [
-- "unreviewed",
-- "reviewed",
-- "first-party"
-- ]
-- },
-- "status": {
-- "enum": [
-- "enabled",
-- "disabled",
-- "revoked"
-- ]
-- },
-- "requested": {
-- "type": "array",
-- "items": {
-- "type": "string"
-- },
-- "description": "Capabilities the manifest asks for."
-- },
-- "granted": {
-- "type": "array",
-- "items": {
-- "type": "string"
-- },
-- "description": "Capabilities approved and not revoked."
-- },
-- "installed_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": "string",
-- "format": "date-time"
-- }
-- }
++ "$ref": "mod-view.v1.json"
},
{
"type": "object",
ddiff --git a/contracts/games/mod-read-result.v1.json b/contracts/games/mod-read-result.v1.json
nnew file mode 100644
iindex 00000000..87a9ea90
---- /dev/null
++++ b/contracts/games/mod-read-result.v1.json
@@@ -0,0 +1,44 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/games/mod-read-result.v1.json",
++ "title": "GamesModReadResult",
++ "description": "games.mod-read-result@1: answers of games.mod.read on OpenVibe.Games (public). GET /api/v1/mods → { mods } (every installed mod's public view: status and grants); GET /api/v1/mods/:id → one mod's public view with its installed manifest (mods.mod-manifest@1) and pack. An unknown id is 404 mod.not_found.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "mods"
++ ],
++ "properties": {
++ "mods": {
++ "type": "array",
++ "items": {
++ "$ref": "mod-view.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "allOf": [
++ {
++ "$ref": "mod-view.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "manifest",
++ "pack"
++ ],
++ "properties": {
++ "manifest": {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ "pack": {
++ "type": "object"
++ }
++ }
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/contracts/games/mod-view.v1.json b/contracts/games/mod-view.v1.json
nnew file mode 100644
iindex 00000000..cfcc064c
---- /dev/null
++++ b/contracts/games/mod-view.v1.json
@@@ -0,0 +1,76 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/games/mod-view.v1.json",
++ "title": "GamesModView",
++ "description": "games.mod-view@1: an installed mod's public view on OpenVibe.Games (apps/server/src/mods/routes.ts publicView): identity, runtime, trust tier, lifecycle status, the capabilities its manifest requests and those currently granted.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "version",
++ "target",
++ "runtime",
++ "trust_tier",
++ "status",
++ "requested",
++ "granted",
++ "installed_at",
++ "updated_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^mod_"
++ },
++ "name": {
++ "type": "string"
++ },
++ "version": {
++ "type": "string"
++ },
++ "target": {
++ "type": "string",
++ "description": "e.g. games.browser"
++ },
++ "runtime": {
++ "type": "string",
++ "description": "e.g. games-content@1"
++ },
++ "trust_tier": {
++ "enum": [
++ "unreviewed",
++ "reviewed",
++ "first-party"
++ ]
++ },
++ "status": {
++ "enum": [
++ "enabled",
++ "disabled",
++ "revoked"
++ ]
++ },
++ "requested": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ },
++ "description": "Capabilities the manifest asks for."
++ },
++ "granted": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ },
++ "description": "Capabilities approved and not revoked."
++ },
++ "installed_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/fixtures/codes.release-read-result/invalid/bad-app-id.json b/fixtures/codes.release-read-result/invalid/bad-app-id.json
nnew file mode 100644
iindex 00000000..e6537d6e
---- /dev/null
++++ b/fixtures/codes.release-read-result/invalid/bad-app-id.json
@@@ -0,0 +1 @@
++{ "app_id": "not-an-app", "trust": { "tier": "unreviewed" }, "releases": [] }
ddiff --git a/fixtures/codes.release-read-result/invalid/manifest-text.json b/fixtures/codes.release-read-result/invalid/manifest-text.json
nnew file mode 100644
iindex 00000000..95f625dd
---- /dev/null
++++ b/fixtures/codes.release-read-result/invalid/manifest-text.json
@@@ -0,0 +1 @@
++{ "release": { "id": "rel_01JAB2C3D4E5F6G7H8J9K0MNPQ", "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR", "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPS", "environment": "production", "kind": "app", "subject_id": null, "name": "Stream Deck Bridge", "version": "1.4.0", "status": "published", "manifest_id": "man_01JAB2C3D4E5F6G7H8J9K0MNPT", "compatibility": { "openvibe-contracts": ">=0.40.0" }, "notes": "Scene switching over the chat socket.", "created_at": "2026-09-24T10:00:00.000Z", "created_by": "app:app_01JAB2C3D4E5F6G7H8J9K0MNPR", "published_at": "2026-09-24T10:05:00.000Z", "deprecated_at": null, "deprecation_reason": null, "replacement": null, "revoked_at": null, "revocation_reason": null, "trust": { "tier": "unreviewed", "note": "", "set_by": null, "set_at": null } }, "manifest": "not a manifest" }
ddiff --git a/fixtures/codes.release-read-result/valid/app-releases.json b/fixtures/codes.release-read-result/valid/app-releases.json
nnew file mode 100644
iindex 00000000..f80a02fa
---- /dev/null
++++ b/fixtures/codes.release-read-result/valid/app-releases.json
@@@ -0,0 +1 @@
++{ "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR", "trust": { "tier": "unreviewed", "note": "", "set_by": null, "set_at": null }, "releases": [ { "id": "rel_01JAB2C3D4E5F6G7H8J9K0MNPQ", "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR", "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPS", "environment": "production", "kind": "app", "subject_id": null, "name": "Stream Deck Bridge", "version": "1.4.0", "status": "published", "manifest_id": "man_01JAB2C3D4E5F6G7H8J9K0MNPT", "compatibility": { "openvibe-contracts": ">=0.40.0" }, "notes": "Scene switching over the chat socket.", "created_at": "2026-09-24T10:00:00.000Z", "created_by": "app:app_01JAB2C3D4E5F6G7H8J9K0MNPR", "published_at": "2026-09-24T10:05:00.000Z", "deprecated_at": null, "deprecation_reason": null, "replacement": null, "revoked_at": null, "revocation_reason": null, "trust": { "tier": "unreviewed", "note": "", "set_by": null, "set_at": null } } ] }
ddiff --git a/fixtures/codes.release-read-result/valid/release-with-manifest.json b/fixtures/codes.release-read-result/valid/release-with-manifest.json
nnew file mode 100644
iindex 00000000..885c7499
---- /dev/null
++++ b/fixtures/codes.release-read-result/valid/release-with-manifest.json
@@@ -0,0 +1 @@
++{ "release": { "id": "rel_01JAB2C3D4E5F6G7H8J9K0MNPQ", "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR", "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPS", "environment": "production", "kind": "app", "subject_id": null, "name": "Stream Deck Bridge", "version": "1.4.0", "status": "published", "manifest_id": "man_01JAB2C3D4E5F6G7H8J9K0MNPT", "compatibility": { "openvibe-contracts": ">=0.40.0" }, "notes": "Scene switching over the chat socket.", "created_at": "2026-09-24T10:00:00.000Z", "created_by": "app:app_01JAB2C3D4E5F6G7H8J9K0MNPR", "published_at": "2026-09-24T10:05:00.000Z", "deprecated_at": null, "deprecation_reason": null, "replacement": null, "revoked_at": null, "revocation_reason": null, "trust": { "tier": "unreviewed", "note": "", "set_by": null, "set_at": null } }, "manifest": { "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0", "name": "My app", "version": "0.1.0", "publisher": { "type": "app", "id": "app_01JABCDEFGHJKMNPQRSTVWXYZ0" }, "project_id": "prj_01JABCDEFGHJKMNPQRSTVWXYZ0", "environment": "sandbox", "capabilities": [], "compatibility": { "contracts": "^0.26.0", "sdk": "^0.2.2" }} }
ddiff --git a/fixtures/codes.release-read-result/valid/trust.json b/fixtures/codes.release-read-result/valid/trust.json
nnew file mode 100644
iindex 00000000..a778d931
---- /dev/null
++++ b/fixtures/codes.release-read-result/valid/trust.json
@@@ -0,0 +1 @@
++{ "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPR", "tier": "reviewed", "note": "checked 1.4.0", "set_by": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "set_at": "2026-09-25T10:00:00.000Z", "note_on_authority": "metadata only; grants in OpenVibe.Network are the authority" }
ddiff --git a/fixtures/community.forum-post/invalid/revision-zero.json b/fixtures/community.forum-post/invalid/revision-zero.json
nnew file mode 100644
iindex 00000000..6f42d5cf
---- /dev/null
++++ b/fixtures/community.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/community.forum-post/valid/opening.json b/fixtures/community.forum-post/valid/opening.json
nnew file mode 100644
iindex 00000000..eed41637
---- /dev/null
++++ b/fixtures/community.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/community.forum-thread/invalid/bad-vote.json b/fixtures/community.forum-thread/invalid/bad-vote.json
nnew file mode 100644
iindex 00000000..3e5f903f
---- /dev/null
++++ b/fixtures/community.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/community.forum-thread/valid/new.json b/fixtures/community.forum-thread/valid/new.json
nnew file mode 100644
iindex 00000000..50a44c2a
---- /dev/null
++++ b/fixtures/community.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/community.paste-create-result/invalid/no-paste.json b/fixtures/community.paste-create-result/invalid/no-paste.json
nnew file mode 100644
iindex 00000000..ae6b0879
---- /dev/null
++++ b/fixtures/community.paste-create-result/invalid/no-paste.json
@@@ -0,0 +1 @@
++{ "id": 4412, "slug": "k3JfQ2", "url": "/p/k3JfQ2" }
ddiff --git a/fixtures/community.paste-create-result/valid/created.json b/fixtures/community.paste-create-result/valid/created.json
nnew file mode 100644
iindex 00000000..fe5e6bfa
---- /dev/null
++++ b/fixtures/community.paste-create-result/valid/created.json
@@@ -0,0 +1 @@
++{ "id": 4412, "slug": "k3JfQ2", "url": "/p/k3JfQ2", "paste": { "unique_views": 12, "id": 4412, "app_id": "community", "slug": "k3JfQ2", "user_id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ", "origin": "user", "type": "paste", "title": "ffmpeg restream flags", "content": "ffmpeg -re -i in.flv -c copy -f flv rtmp://example/live", "language": "text", "visibility": "public", "stream_id": 812, "stream_ref": { "service": "live", "type": "stream", "id": "812" }, "screenshot_url": null, "metadata": null, "burn_after_read": false, "forked_from": null, "pinned": false, "views": 30, "copies": 2, "likes": 5, "is_nsfw": false, "ai_summary": null, "ai_tags": null, "ai_analyzed_at": null, "revision": 1, "url": "/p/k3JfQ2", "raw_url": "/p/k3JfQ2/raw", "created_at": "2026-09-25 18:02:11", "updated_at": "2026-09-25 18:02:11", "is_owner": true, "username": "alice", "display_name": "Alice", "avatar_url": "https://openvibe.media/avatar/alice", "profile_color": "#3b82f6" } }
ddiff --git a/fixtures/community.post-write-result/invalid/bad-deleted.json b/fixtures/community.post-write-result/invalid/bad-deleted.json
nnew file mode 100644
iindex 00000000..664773de
---- /dev/null
++++ b/fixtures/community.post-write-result/invalid/bad-deleted.json
@@@ -0,0 +1 @@
++{ "ok": true, "id": 311, "deleted": "post" }
ddiff --git a/fixtures/community.post-write-result/valid/new-thread.json b/fixtures/community.post-write-result/valid/new-thread.json
nnew file mode 100644
iindex 00000000..413983e9
---- /dev/null
++++ b/fixtures/community.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/community.post-write-result/valid/reply.json b/fixtures/community.post-write-result/valid/reply.json
nnew file mode 100644
iindex 00000000..e728e59a
---- /dev/null
++++ b/fixtures/community.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/community.post-write-result/valid/thread-deleted.json b/fixtures/community.post-write-result/valid/thread-deleted.json
nnew file mode 100644
iindex 00000000..2fa039aa
---- /dev/null
++++ b/fixtures/community.post-write-result/valid/thread-deleted.json
@@@ -0,0 +1 @@
++{ "ok": true, "id": 311, "deleted": "thread" }
ddiff --git a/fixtures/community.post-write-result/valid/vote.json b/fixtures/community.post-write-result/valid/vote.json
nnew file mode 100644
iindex 00000000..2115df28
---- /dev/null
++++ b/fixtures/community.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/community.pulse-write-request/invalid/bad-ref-and-url.json b/fixtures/community.pulse-write-request/invalid/bad-ref-and-url.json
nnew file mode 100644
iindex 00000000..4e077319
---- /dev/null
++++ b/fixtures/community.pulse-write-request/invalid/bad-ref-and-url.json
@@@ -0,0 +1 @@
++{ "ref": { "service": "live", "type": "clip" }, "title": "t", "url": "ftp://x" }
ddiff --git a/fixtures/community.pulse-write-request/invalid/not-public.json b/fixtures/community.pulse-write-request/invalid/not-public.json
nnew file mode 100644
iindex 00000000..8f238456
---- /dev/null
++++ b/fixtures/community.pulse-write-request/invalid/not-public.json
@@@ -0,0 +1 @@
++{ "ref": { "service": "live", "type": "clip", "id": "x" }, "title": "t", "url": "https://openvibe.live/clip/x", "visibility": "unlisted" }
ddiff --git a/fixtures/community.pulse-write-request/valid/clip.json b/fixtures/community.pulse-write-request/valid/clip.json
nnew file mode 100644
iindex 00000000..01228a39
---- /dev/null
++++ b/fixtures/community.pulse-write-request/valid/clip.json
@@@ -0,0 +1 @@
++{ "ref": { "service": "live", "type": "clip", "id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ" }, "title": "Alice clipped a clean 1v3", "url": "https://openvibe.live/clip/med_01JAB2C3D4E5F6G7H8J9K0MNPQ", "origin": "user", "occurred_at": "2026-09-25T18:00:00Z", "visibility": "public" }
ddiff --git a/fixtures/community.pulse-write-request/valid/retract.json b/fixtures/community.pulse-write-request/valid/retract.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/community.pulse-write-request/valid/retract.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/games.mod-manage-request/invalid/unknown-pack-section.json b/fixtures/games.mod-manage-request/invalid/unknown-pack-section.json
nnew file mode 100644
iindex 00000000..c77f3026
---- /dev/null
++++ b/fixtures/games.mod-manage-request/invalid/unknown-pack-section.json
@@@ -0,0 +1 @@
++{ "manifest": { "id": "mod_01JABCDEFGHJKMNPQRSTVWXYZ0", "name": "Town Square", "version": "1.0.0", "description": "A public workbench and a greeting.", "publisher": { "type": "user", "id": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0" }, "target": "games.browser", "runtime": "games-content@1", "permissions": { "capabilities": ["games.world.announce", "games.prop.place"] }, "resources": { "cpuMs": 1, "memoryMb": 0, "storageMb": 0 }, "compatibility": { "runtime": ">=1.0.0 <2.0.0" }}, "pack": { "music": [] }, "capability": 5, "reason": 7 }
ddiff --git a/fixtures/games.mod-manage-request/valid/grant.json b/fixtures/games.mod-manage-request/valid/grant.json
nnew file mode 100644
iindex 00000000..9f7d75f2
---- /dev/null
++++ b/fixtures/games.mod-manage-request/valid/grant.json
@@@ -0,0 +1 @@
++{ "capability": "games.prop.place" }
ddiff --git a/fixtures/games.mod-manage-request/valid/install.json b/fixtures/games.mod-manage-request/valid/install.json
nnew file mode 100644
iindex 00000000..e6648ea9
---- /dev/null
++++ b/fixtures/games.mod-manage-request/valid/install.json
@@@ -0,0 +1 @@
++{ "manifest": { "id": "mod_01JABCDEFGHJKMNPQRSTVWXYZ0", "name": "Town Square", "version": "1.0.0", "description": "A public workbench and a greeting.", "publisher": { "type": "user", "id": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0" }, "target": "games.browser", "runtime": "games-content@1", "permissions": { "capabilities": ["games.world.announce", "games.prop.place"] }, "resources": { "cpuMs": 1, "memoryMb": 0, "storageMb": 0 }, "compatibility": { "runtime": ">=1.0.0 <2.0.0" }}, "pack": { "announcements": [ { "text": "Welcome to the town square.", "everySeconds": 900 } ] }, "approve": ["games.world.announce"], "trustTier": "reviewed", "enable": true }
ddiff --git a/fixtures/games.mod-manage-request/valid/revoke.json b/fixtures/games.mod-manage-request/valid/revoke.json
nnew file mode 100644
iindex 00000000..ab84daba
---- /dev/null
++++ b/fixtures/games.mod-manage-request/valid/revoke.json
@@@ -0,0 +1 @@
++{ "reason": "abusive announcements" }
ddiff --git a/fixtures/games.mod-read-result/invalid/one-without-manifest.json b/fixtures/games.mod-read-result/invalid/one-without-manifest.json
nnew file mode 100644
iindex 00000000..da735119
---- /dev/null
++++ b/fixtures/games.mod-read-result/invalid/one-without-manifest.json
@@@ -0,0 +1 @@
++{ "id": "mod_01JAB2C3D4E5F6G7H8J9K0MNPQ", "name": "Harbor Lights", "version": "1.2.0", "target": "games.browser", "runtime": "games-content@1", "trust_tier": "reviewed", "status": "enabled", "requested": ["games.prop.place", "games.world.announce"], "granted": ["games.prop.place"], "installed_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/fixtures/games.mod-read-result/valid/list.json b/fixtures/games.mod-read-result/valid/list.json
nnew file mode 100644
iindex 00000000..395d1cc0
---- /dev/null
++++ b/fixtures/games.mod-read-result/valid/list.json
@@@ -0,0 +1 @@
++{ "mods": [ { "id": "mod_01JAB2C3D4E5F6G7H8J9K0MNPQ", "name": "Harbor Lights", "version": "1.2.0", "target": "games.browser", "runtime": "games-content@1", "trust_tier": "reviewed", "status": "enabled", "requested": ["games.prop.place", "games.world.announce"], "granted": ["games.prop.place"], "installed_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" } ] }
ddiff --git a/fixtures/games.mod-read-result/valid/one.json b/fixtures/games.mod-read-result/valid/one.json
nnew file mode 100644
iindex 00000000..5cc351eb
---- /dev/null
++++ b/fixtures/games.mod-read-result/valid/one.json
@@@ -0,0 +1 @@
++{ "id": "mod_01JAB2C3D4E5F6G7H8J9K0MNPQ", "name": "Harbor Lights", "version": "1.2.0", "target": "games.browser", "runtime": "games-content@1", "trust_tier": "reviewed", "status": "enabled", "requested": ["games.prop.place", "games.world.announce"], "granted": ["games.prop.place"], "installed_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" , "manifest": { "id": "mod_01JABCDEFGHJKMNPQRSTVWXYZ0", "name": "Town Square", "version": "1.0.0", "description": "A public workbench and a greeting.", "publisher": { "type": "user", "id": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0" }, "target": "games.browser", "runtime": "games-content@1", "permissions": { "capabilities": ["games.world.announce", "games.prop.place"] }, "resources": { "cpuMs": 1, "memoryMb": 0, "storageMb": 0 }, "compatibility": { "runtime": ">=1.0.0 <2.0.0" }}, "pack": { "announcements": [ { "text": "Welcome to the town square.", "everySeconds": 900 } ] } }
ddiff --git a/fixtures/games.mod-view/invalid/bad-status.json b/fixtures/games.mod-view/invalid/bad-status.json
nnew file mode 100644
iindex 00000000..582b0021
---- /dev/null
++++ b/fixtures/games.mod-view/invalid/bad-status.json
@@@ -0,0 +1 @@
++{ "id": "mod_01JAB2C3D4E5F6G7H8J9K0MNPQ", "name": "Harbor Lights", "version": "1.2.0", "target": "games.browser", "runtime": "games-content@1", "trust_tier": "reviewed", "status": "paused", "requested": ["games.prop.place", "games.world.announce"], "granted": ["games.prop.place"], "installed_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/fixtures/games.mod-view/valid/view.json b/fixtures/games.mod-view/valid/view.json
nnew file mode 100644
iindex 00000000..da735119
---- /dev/null
++++ b/fixtures/games.mod-view/valid/view.json
@@@ -0,0 +1 @@
++{ "id": "mod_01JAB2C3D4E5F6G7H8J9K0MNPQ", "name": "Harbor Lights", "version": "1.2.0", "target": "games.browser", "runtime": "games-content@1", "trust_tier": "reviewed", "status": "enabled", "requested": ["games.prop.place", "games.world.announce"], "granted": ["games.prop.place"], "installed_at": "2026-09-20T10:00:00.000Z", "updated_at": "2026-09-25T18:00:00.000Z" }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 47befc2c..30dbf61c 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -3451,6 +3451,131 @@
}
}
},
++ "codes.release-read-result": {
++ "version": "1.0.0",
++ "owner": "codes",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/codes/release-read-result.v1.json",
++ "title": "CodesReleaseReadResult",
++ "description": "codes.release-read-result@1: answers of codes.release.read on OpenVibe.Codes (public; no token needed, but a token that is presented must hold the capability; never drafts). GET /api/v1/apps/:app/releases → { app_id, trust, releases } (published, deprecated and revoked releases, newest first); GET /api/v1/apps/:app/trust → { app_id, tier, note, set_by, set_at, note_on_authority } (metadata only: grants in OpenVibe.Network are the authority); GET /api/v1/releases/:id → { release, manifest } (the manifest as published: codes.app-manifest@1 for an app, mods.mod-manifest@1 for a mod). An id that is not an app or a release is 404.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "app_id",
++ "trust",
++ "releases"
++ ],
++ "properties": {
++ "app_id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "trust": {
++ "type": "object",
++ "required": [
++ "tier"
++ ],
++ "properties": {
++ "tier": {
++ "type": "string"
++ },
++ "note": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_by": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "releases": {
++ "type": "array",
++ "items": {
++ "$ref": "release.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "app_id",
++ "tier",
++ "note_on_authority"
++ ],
++ "properties": {
++ "app_id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "tier": {
++ "type": "string"
++ },
++ "note": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_by": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "set_at": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "note_on_authority": {
++ "type": "string"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "release",
++ "manifest"
++ ],
++ "properties": {
++ "release": {
++ "$ref": "release.v1.json"
++ },
++ "manifest": {
++ "anyOf": [
++ {
++ "$ref": "app-manifest.v1.json"
++ },
++ {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ }
++ }
++ ]
++ }
++ },
"events.redaction-directive": {
"version": "1.0.0",
"owner": "events",
@@@ -3821,7 +3946,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"title": "EventsReadResult",
-- "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.",
++ "description": "events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). GET /realtime/stream?topics= (events.event.read) is a Server-Sent Events feed whose messages carry the same { seq, event } (id: the seq; a gap message when the cursor is older than retention). An app sees only its project's events in its token's environment plus public first-party events.",
"anyOf": [
{
"type": "object",
@@@ -41145,76 +41270,7 @@
"description": "games.mod-manage-result@1: answers of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods → 201 the installed mod's public view; POST /api/v1/mods/:id/enable|disable|revoke, POST /api/v1/mods/:id/grants and DELETE /api/v1/mods/:id/grants/:capability → 200 the mod's public view after the change (revoke is terminal); GET /api/v1/mods/:id/audit?limit= → { audit } (install, grant, use, deny and revoke entries, at most 500). Errors are problem+json (mod.not_found, mod.invalid_request, capability.denied, …).",
"anyOf": [
{
-- "type": "object",
-- "required": [
-- "id",
-- "name",
-- "version",
-- "target",
-- "runtime",
-- "trust_tier",
-- "status",
-- "requested",
-- "granted",
-- "installed_at",
-- "updated_at"
-- ],
-- "properties": {
-- "id": {
-- "type": "string",
-- "pattern": "^mod_"
-- },
-- "name": {
-- "type": "string"
-- },
-- "version": {
-- "type": "string"
-- },
-- "target": {
-- "type": "string",
-- "description": "e.g. games.browser"
-- },
-- "runtime": {
-- "type": "string",
-- "description": "e.g. games-content@1"
-- },
-- "trust_tier": {
-- "enum": [
-- "unreviewed",
-- "reviewed",
-- "first-party"
-- ]
-- },
-- "status": {
-- "enum": [
-- "enabled",
-- "disabled",
-- "revoked"
-- ]
-- },
-- "requested": {
-- "type": "array",
-- "items": {
-- "type": "string"
-- },
-- "description": "Capabilities the manifest asks for."
-- },
-- "granted": {
-- "type": "array",
-- "items": {
-- "type": "string"
-- },
-- "description": "Capabilities approved and not revoked."
-- },
-- "installed_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": "string",
-- "format": "date-time"
-- }
-- }
++ "$ref": "mod-view.v1.json"
},
{
"type": "object",
@@@ -41348,80 +41404,325 @@
]
}
},
-- "media.object.deleted": {
++ "games.mod-manage-request": {
"version": "1.0.0",
-- "owner": "media",
++ "owner": "games",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/media.object.deleted.v1.json",
-- "title": "MediaObjectDeletedPayload",
-- "description": "media.object.deleted v1 (OpenVibe.Media server/events.js objectChangeEvent and recordObjectChanges, commit 039dc48, from the media_object_changes rows that the AFTER UPDATE OF lifecycle_status trigger on media_objects writes). An object's lifecycle_status became deleted: a native object soft-deleted through DELETE /api/v2/:app/objects/:id, or an inherited vods/clips/files/pastes row removed, which marks its object deleted. Every write path counts (operator SQL included), and the event exists if and only if the change committed: the trigger row and the outbox envelope are written in the transaction of the change, or by the relay's drain for a change made outside Media's own transactions. Consumers drop any public copy, index entry or cache they hold for the object (roadmap §29.3). Deliberately minimal, identity and state only: never the title, description or metadata, the owner's ids or subject, storage keys, paths or providers, sizes or hashes. Developer-project sandbox tenants produce none. Envelope: subject { type: object, id: <object_id> }, visibility internal, priority important, actor service:media.",
++ "$id": "https://openvibe.network/contracts/games/mod-manage-request.v1.json",
++ "title": "GamesModManageRequest",
++ "description": "games.mod-manage-request@1: bodies of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods installs a mod: { manifest, pack?, approve?, trustTier?, enable? } — manifest is a mods.mod-manifest@1 the Games runtime can run (games.browser, games-content@1); pack is its games-content@1 data (announcements and props, validated strictly: unknown keys refused); approve lists at most 64 requested capabilities to grant; trustTier is unreviewed (default), reviewed or first-party; enable true starts it enabled. A mod already installed is 409 mod.exists. POST /api/v1/mods/:id/grants: { capability }. POST /api/v1/mods/:id/revoke: { reason? } (terminal). POST /api/v1/mods/:id/enable|disable take an empty JSON object or no body; DELETE /api/v1/mods/:id/grants/:capability and GET /api/v1/mods/:id/audit take no body. Bodies are at most 256 KB; unknown top-level fields are ignored.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "manifest"
++ ],
++ "properties": {
++ "manifest": {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ "pack": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "announcements": {
++ "type": "array",
++ "maxItems": 10,
++ "items": {
++ "$ref": "announcement.v1.json"
++ }
++ },
++ "props": {
++ "type": "array",
++ "maxItems": 50,
++ "items": {
++ "type": "object",
++ "required": [
++ "key",
++ "item",
++ "pos"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "key": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,39}$"
++ },
++ "item": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 64
++ },
++ "pos": {
++ "type": "array",
++ "minItems": 3,
++ "maxItems": 3,
++ "items": {
++ "type": "number",
++ "minimum": -4096,
++ "maximum": 4096
++ }
++ },
++ "yaw": {
++ "type": "number",
++ "minimum": -10,
++ "maximum": 10
++ }
++ }
++ }
++ }
++ }
++ },
++ "approve": {
++ "type": "array",
++ "maxItems": 64,
++ "items": {
++ "type": "string"
++ }
++ },
++ "trustTier": {
++ "enum": [
++ "unreviewed",
++ "reviewed",
++ "first-party"
++ ]
++ },
++ "enable": {
++ "type": "boolean"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "capability"
++ ],
++ "properties": {
++ "capability": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ },
++ {
++ "type": "object",
++ "properties": {
++ "reason": {
++ "type": "string",
++ "maxLength": 500,
++ "description": "Revoke only; cut to 500 characters."
++ }
++ }
++ }
++ ]
++ }
++ },
++ "games.mod-read-result": {
++ "version": "1.0.0",
++ "owner": "games",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/games/mod-read-result.v1.json",
++ "title": "GamesModReadResult",
++ "description": "games.mod-read-result@1: answers of games.mod.read on OpenVibe.Games (public). GET /api/v1/mods → { mods } (every installed mod's public view: status and grants); GET /api/v1/mods/:id → one mod's public view with its installed manifest (mods.mod-manifest@1) and pack. An unknown id is 404 mod.not_found.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "mods"
++ ],
++ "properties": {
++ "mods": {
++ "type": "array",
++ "items": {
++ "$ref": "mod-view.v1.json"
++ }
++ }
++ }
++ },
++ {
++ "allOf": [
++ {
++ "$ref": "mod-view.v1.json"
++ },
++ {
++ "type": "object",
++ "required": [
++ "manifest",
++ "pack"
++ ],
++ "properties": {
++ "manifest": {
++ "$ref": "../mods/mod-manifest.v1.json"
++ },
++ "pack": {
++ "type": "object"
++ }
++ }
++ }
++ ]
++ }
++ ]
++ }
++ },
++ "games.mod-view": {
++ "version": "1.0.0",
++ "owner": "games",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/games/mod-view.v1.json",
++ "title": "GamesModView",
++ "description": "games.mod-view@1: an installed mod's public view on OpenVibe.Games (apps/server/src/mods/routes.ts publicView): identity, runtime, trust tier, lifecycle status, the capabilities its manifest requests and those currently granted.",
"type": "object",
"required": [
-- "object_id",
-- "app_id",
-- "kind",
-- "legacy_ref",
-- "deleted_at"
++ "id",
++ "name",
++ "version",
++ "target",
++ "runtime",
++ "trust_tier",
++ "status",
++ "requested",
++ "granted",
++ "installed_at",
++ "updated_at"
],
-- "additionalProperties": false,
"properties": {
-- "object_id": {
++ "id": {
"type": "string",
-- "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ "pattern": "^mod_"
},
-- "app_id": {
++ "name": {
++ "type": "string"
++ },
++ "version": {
++ "type": "string"
++ },
++ "target": {
"type": "string",
-- "minLength": 1,
-- "maxLength": 200,
-- "description": "The tenant that owns the object: an app (live, tools…) or a developer project's production tenant (prj_…)."
++ "description": "e.g. games.browser"
},
-- "kind": {
++ "runtime": {
++ "type": "string",
++ "description": "e.g. games-content@1"
++ },
++ "trust_tier": {
"enum": [
-- "vod",
-- "clip",
-- "file",
-- "thumbnail",
-- "screenshot",
-- "avatar",
-- "asset"
++ "unreviewed",
++ "reviewed",
++ "first-party"
]
},
-- "legacy_ref": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "maxLength": 300,
-- "description": "The inherited row the object projects (legacy:<app>:<kind>:<id>, a media.media-ref@1 legacy id); null for a native v2 object."
++ "status": {
++ "enum": [
++ "enabled",
++ "disabled",
++ "revoked"
++ ]
},
-- "deleted_at": {
++ "requested": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ },
++ "description": "Capabilities the manifest asks for."
++ },
++ "granted": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ },
++ "description": "Capabilities approved and not revoked."
++ },
++ "installed_at": {
"type": "string",
-- "format": "date-time",
-- "description": "When the change committed, ISO 8601 UTC."
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
}
}
}
},
-- "media.object.visibility_changed": {
++ "media.object.deleted": {
"version": "1.0.0",
"owner": "media",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
-- "$id": "https://openvibe.network/contracts/events/payloads/media.object.visibility_changed.v1.json",
-- "title": "MediaObjectVisibilityChangedPayload",
-- "description": "media.object.visibility_changed v1 (OpenVibe.Media server/events.js objectChangeEvent and recordObjectChanges, commit 039dc48, from the media_object_changes rows that the AFTER UPDATE OF visibility trigger on media_objects writes). An object's visibility changed to another value (public, unlisted or private), by any write path: the v2 objects API, the inherited vods/clips/files rows it projects, or operator SQL. Never emitted when the value did not change, nor for an object that is deleted at the time of the change (that object's last word is media.object.deleted). The event exists if and only if the change committed: the trigger row and the outbox envelope are written in the transaction of the change, or by the relay's drain for a change made outside Media's own transactions. Consumers that hold a public copy, index entry or cache of the object re-check it (roadmap §29.3); a move away from public means stop showing it. Deliberately minimal, identity and state only: never the title, description or metadata, the owner's ids or subject, storage keys, paths or providers, sizes or hashes. Developer-project sandbox tenants produce none. Envelope: subject { type: object, id: <object_id> }, visibility internal, priority important, actor service:media.",
++ "$id": "https://openvibe.network/contracts/events/payloads/media.object.deleted.v1.json",
++ "title": "MediaObjectDeletedPayload",
++ "description": "media.object.deleted v1 (OpenVibe.Media server/events.js objectChangeEvent and recordObjectChanges, commit 039dc48, from the media_object_changes rows that the AFTER UPDATE OF lifecycle_status trigger on media_objects writes). An object's lifecycle_status became deleted: a native object soft-deleted through DELETE /api/v2/:app/objects/:id, or an inherited vods/clips/files/pastes row removed, which marks its object deleted. Every write path counts (operator SQL included), and the event exists if and only if the change committed: the trigger row and the outbox envelope are written in the transaction of the change, or by the relay's drain for a change made outside Media's own transactions. Consumers drop any public copy, index entry or cache they hold for the object (roadmap §29.3). Deliberately minimal, identity and state only: never the title, description or metadata, the owner's ids or subject, storage keys, paths or providers, sizes or hashes. Developer-project sandbox tenants produce none. Envelope: subject { type: object, id: <object_id> }, visibility internal, priority important, actor service:media.",
"type": "object",
"required": [
"object_id",
"app_id",
"kind",
"legacy_ref",
-- "visibility",
-- "previous_visibility",
-- "changed_at"
++ "deleted_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "object_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "app_id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200,
++ "description": "The tenant that owns the object: an app (live, tools…) or a developer project's production tenant (prj_…)."
++ },
++ "kind": {
++ "enum": [
++ "vod",
++ "clip",
++ "file",
++ "thumbnail",
++ "screenshot",
++ "avatar",
++ "asset"
++ ]
++ },
++ "legacy_ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300,
++ "description": "The inherited row the object projects (legacy:<app>:<kind>:<id>, a media.media-ref@1 legacy id); null for a native v2 object."
++ },
++ "deleted_at": {
++ "type": "string",
++ "format": "date-time",
++ "description": "When the change committed, ISO 8601 UTC."
++ }
++ }
++ }
++ },
++ "media.object.visibility_changed": {
++ "version": "1.0.0",
++ "owner": "media",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/media.object.visibility_changed.v1.json",
++ "title": "MediaObjectVisibilityChangedPayload",
++ "description": "media.object.visibility_changed v1 (OpenVibe.Media server/events.js objectChangeEvent and recordObjectChanges, commit 039dc48, from the media_object_changes rows that the AFTER UPDATE OF visibility trigger on media_objects writes). An object's visibility changed to another value (public, unlisted or private), by any write path: the v2 objects API, the inherited vods/clips/files rows it projects, or operator SQL. Never emitted when the value did not change, nor for an object that is deleted at the time of the change (that object's last word is media.object.deleted). The event exists if and only if the change committed: the trigger row and the outbox envelope are written in the transaction of the change, or by the relay's drain for a change made outside Media's own transactions. Consumers that hold a public copy, index entry or cache of the object re-check it (roadmap §29.3); a move away from public means stop showing it. Deliberately minimal, identity and state only: never the title, description or metadata, the owner's ids or subject, storage keys, paths or providers, sizes or hashes. Developer-project sandbox tenants produce none. Envelope: subject { type: object, id: <object_id> }, visibility internal, priority important, actor service:media.",
++ "type": "object",
++ "required": [
++ "object_id",
++ "app_id",
++ "kind",
++ "legacy_ref",
++ "visibility",
++ "previous_visibility",
++ "changed_at"
],
"additionalProperties": false,
"properties": {
@@@ -44957,6 +45258,644 @@
]
}
},
++ "community.forum-post": {
++ "version": "1.0.0",
++ "owner": "community",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/forum-post.v1.json",
++ "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"
++ ],
++ "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"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "community.forum-thread": {
++ "version": "1.0.0",
++ "owner": "community",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/forum-thread.v1.json",
++ "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"
++ ]
++ },
++ "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
++ ]
++ }
++ }
++ }
++ },
++ "community.paste-create-result": {
++ "version": "1.0.0",
++ "owner": "community",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/paste-create-result.v1.json",
++ "title": "CommunityPasteCreateResult",
++ "description": "community.paste-create-result@1: the answer of POST /api/pastes on OpenVibe.Community (community.paste.create), a text paste or, with a multipart screenshot part, an image paste (server/pastes/service.js created): 201 { id, slug, url, paste } — url is /p/<slug> and paste the new paste as community.paste@1. Refusals are { error } with 400 (no content, too large, not an image), 409 (a service's slug is taken), 429 (cooldown or daily limit) or 502/503 (Media unavailable for a screenshot).",
++ "type": "object",
++ "required": [
++ "id",
++ "slug",
++ "url",
++ "paste"
++ ],
++ "properties": {
++ "id": {
++ "type": "integer"
++ },
++ "slug": {
++ "type": "string"
++ },
++ "url": {
++ "type": "string",
++ "pattern": "^/p/"
++ },
++ "paste": {
++ "$ref": "paste.v1.json"
++ }
++ }
++ }
++ },
++ "community.post-write-result": {
++ "version": "1.0.0",
++ "owner": "community",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/post-write-result.v1.json",
++ "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"
++ ],
++ "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"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "community.pulse-write-request": {
++ "version": "1.0.0",
++ "owner": "community",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/community/pulse-write-request.v1.json",
++ "title": "CommunityPulseWriteRequest",
++ "description": "community.pulse-write-request@1: the body of POST /api/v1/pulse/items on OpenVibe.Community (community.pulse.write; service tokens only): an item about one of the calling service's own entities (ref.service must be that service: 403 pulse.foreign_ref otherwise), with a title (whitespace collapsed, 1 to 300 characters) and an absolute http(s) url (at most 2000 characters). origin defaults to X-OV-Origin (user, ai or system; X-OV-Origin: ai always wins); an origin user item names the person in X-OV-Subject. occurred_at is an ISO time, not in the future (default now). Only public activity enters Pulse: a visibility other than public is 400 pulse.not_public. The same ref again updates the item. Unknown fields are ignored. DELETE /api/v1/pulse/items/:service/:type/:id takes no body.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ref",
++ "title",
++ "url"
++ ],
++ "properties": {
++ "ref": {
++ "$ref": "../common/entity-ref.v1.json"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "pattern": "\\S",
++ "description": "At most 300 characters once whitespace is collapsed."
++ },
++ "url": {
++ "type": "string",
++ "maxLength": 2000,
++ "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
++ },
++ "origin": {
++ "enum": [
++ "user",
++ "ai",
++ "system",
++ null
++ ]
++ },
++ "occurred_at": {
++ "type": [
++ "string",
++ "integer",
++ "null"
++ ]
++ },
++ "visibility": {
++ "enum": [
++ "public",
++ null
++ ]
++ }
++ }
++ },
++ {
++ "$ref": "../common/no-body.v1.json"
++ }
++ ]
++ }
++ },
"openre.destination": {
"version": "1.0.0",
"owner": "openre",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 18f79f81..c49e9908 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -5798,6 +5798,34 @@ export interface CodesRelease {
};
}
++/** [email protected] (owner: codes) */
++/**
++ * codes.release-read-result@1: answers of codes.release.read on OpenVibe.Codes (public; no token needed, but a token that is presented must hold the capability; never drafts). GET /api/v1/apps/:app/releases → { app_id, trust, releases } (published, deprecated and revoked releases, newest first); GET /api/v1/apps/:app/trust → { app_id, tier, note, set_by, set_at, note_on_authority } (metadata only: grants in OpenVibe.Network are the authority); GET /api/v1/releases/:id → { release, manifest } (the manifest as published: codes.app-manifest@1 for an app, mods.mod-manifest@1 for a mod). An id that is not an app or a release is 404.
++ */
++export type CodesReleaseReadResult =
++ | {
++ app_id: string;
++ trust: {
++ tier: string;
++ note?: string | null;
++ set_by?: string | null;
++ set_at?: string | null;
++ };
++ releases: CodesRelease[];
++ }
++ | {
++ app_id: string;
++ tier: string;
++ note?: string | null;
++ set_by?: string | null;
++ set_at?: string | null;
++ note_on_authority: string;
++ }
++ | {
++ release: CodesRelease;
++ manifest: AppManifest | ModManifest | null;
++ };
++
/** [email protected] (owner: events) */
/**
* payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.
@@@ -5932,7 +5960,7 @@ export type EventsReadRequest =
/** [email protected] (owner: events) */
/**
-- * events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). An app sees only its project's events in its token's environment plus public first-party events.
++ * events.read-result@1: answers of the pull API on OpenVibe.Events. GET /api/v1/events → { events: [{ seq, event }], next_after_seq, latest_seq, gap? } (keep next_after_seq as the cursor: it moves past events that did not match; gap { from_seq, to_seq } says events after after_seq were already pruned by retention); GET /api/v1/events/:event_id → { seq, event }; GET|PUT /api/v1/checkpoints → { consumer, topic, cursor, updated_at } (cursor 0 and updated_at null when none is stored). GET /realtime/stream?topics= (events.event.read) is a Server-Sent Events feed whose messages carry the same { seq, event } (id: the seq; a gap message when the cursor is older than retention). An app sees only its project's events in its token's environment plus public first-party events.
*/
export type EventsReadResult =
| {
@@@ -36359,31 +36387,7 @@ export interface GamesAnnouncement {
* games.mod-manage-result@1: answers of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods → 201 the installed mod's public view; POST /api/v1/mods/:id/enable|disable|revoke, POST /api/v1/mods/:id/grants and DELETE /api/v1/mods/:id/grants/:capability → 200 the mod's public view after the change (revoke is terminal); GET /api/v1/mods/:id/audit?limit= → { audit } (install, grant, use, deny and revoke entries, at most 500). Errors are problem+json (mod.not_found, mod.invalid_request, capability.denied, …).
*/
export type GamesModManageResult =
-- | {
-- id: string;
-- name: string;
-- version: string;
-- /**
-- * e.g. games.browser
-- */
-- target: string;
-- /**
-- * e.g. games-content@1
-- */
-- runtime: string;
-- trust_tier: "unreviewed" | "reviewed" | "first-party";
-- status: "enabled" | "disabled" | "revoked";
-- /**
-- * Capabilities the manifest asks for.
-- */
-- requested: string[];
-- /**
-- * Capabilities approved and not revoked.
-- */
-- granted: string[];
-- installed_at: string;
-- updated_at: string;
-- }
++ | GamesModView
| {
/**
* @maxItems 500
@@@ -36427,6 +36431,148 @@ export type GamesPropPlacement =
key: string;
};
++/** [email protected] (owner: games) */
++/**
++ * games.mod-manage-request@1: bodies of games.mod.manage on OpenVibe.Games (staff: an owner/admin session, or a service token with games.mod.manage). POST /api/v1/mods installs a mod: { manifest, pack?, approve?, trustTier?, enable? } — manifest is a mods.mod-manifest@1 the Games runtime can run (games.browser, games-content@1); pack is its games-content@1 data (announcements and props, validated strictly: unknown keys refused); approve lists at most 64 requested capabilities to grant; trustTier is unreviewed (default), reviewed or first-party; enable true starts it enabled. A mod already installed is 409 mod.exists. POST /api/v1/mods/:id/grants: { capability }. POST /api/v1/mods/:id/revoke: { reason? } (terminal). POST /api/v1/mods/:id/enable|disable take an empty JSON object or no body; DELETE /api/v1/mods/:id/grants/:capability and GET /api/v1/mods/:id/audit take no body. Bodies are at most 256 KB; unknown top-level fields are ignored.
++ */
++export type GamesModManageRequest =
++ | {
++ manifest: ModManifest;
++ pack?: {
++ /**
++ * @maxItems 10
++ */
++ announcements?:
++ | []
++ | [GamesAnnouncement]
++ | [GamesAnnouncement, GamesAnnouncement]
++ | [GamesAnnouncement, GamesAnnouncement, GamesAnnouncement]
++ | [GamesAnnouncement, GamesAnnouncement, GamesAnnouncement, GamesAnnouncement]
++ | [GamesAnnouncement, GamesAnnouncement, GamesAnnouncement, GamesAnnouncement, GamesAnnouncement]
++ | [
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement
++ ]
++ | [
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement
++ ]
++ | [
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement
++ ]
++ | [
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement
++ ]
++ | [
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement,
++ GamesAnnouncement
++ ];
++ /**
++ * @maxItems 50
++ */
++ props?: {
++ key: string;
++ item: string;
++ /**
++ * @minItems 3
++ * @maxItems 3
++ */
++ pos: [number, number, number];
++ yaw?: number;
++ }[];
++ };
++ /**
++ * @maxItems 64
++ */
++ approve?: string[];
++ trustTier?: "unreviewed" | "reviewed" | "first-party";
++ enable?: boolean;
++ }
++ | {
++ capability: string;
++ }
++ | {
++ /**
++ * Revoke only; cut to 500 characters.
++ */
++ reason?: string;
++ };
++
++/** [email protected] (owner: games) */
++/**
++ * games.mod-read-result@1: answers of games.mod.read on OpenVibe.Games (public). GET /api/v1/mods → { mods } (every installed mod's public view: status and grants); GET /api/v1/mods/:id → one mod's public view with its installed manifest (mods.mod-manifest@1) and pack. An unknown id is 404 mod.not_found.
++ */
++export type GamesModReadResult =
++ | {
++ mods: GamesModView[];
++ }
++ | (GamesModView & {
++ manifest: ModManifest;
++ pack: {};
++ });
++
++/** [email protected] (owner: games) */
++/**
++ * games.mod-view@1: an installed mod's public view on OpenVibe.Games (apps/server/src/mods/routes.ts publicView): identity, runtime, trust tier, lifecycle status, the capabilities its manifest requests and those currently granted.
++ */
++export interface GamesModView {
++ id: string;
++ name: string;
++ version: string;
++ /**
++ * e.g. games.browser
++ */
++ target: string;
++ /**
++ * e.g. games-content@1
++ */
++ runtime: string;
++ trust_tier: "unreviewed" | "reviewed" | "first-party";
++ status: "enabled" | "disabled" | "revoked";
++ /**
++ * Capabilities the manifest asks for.
++ */
++ requested: string[];
++ /**
++ * Capabilities approved and not revoked.
++ */
++ granted: string[];
++ installed_at: string;
++ updated_at: string;
++}
++
/** [email protected] (owner: media) */
/**
* media.object.deleted v1 (OpenVibe.Media server/events.js objectChangeEvent and recordObjectChanges, commit 039dc48, from the media_object_changes rows that the AFTER UPDATE OF lifecycle_status trigger on media_objects writes). An object's lifecycle_status became deleted: a native object soft-deleted through DELETE /api/v2/:app/objects/:id, or an inherited vods/clips/files/pastes row removed, which marks its object deleted. Every write path counts (operator SQL included), and the event exists if and only if the change committed: the trigger row and the outbox envelope are written in the transaction of the change, or by the relay's drain for a change made outside Media's own transactions. Consumers drop any public copy, index entry or cache they hold for the object (roadmap §29.3). Deliberately minimal, identity and state only: never the title, description or metadata, the owner's ids or subject, storage keys, paths or providers, sizes or hashes. Developer-project sandbox tenants produce none. Envelope: subject { type: object, id: <object_id> }, visibility internal, priority important, actor service:media.
@@@ -37574,6 +37720,358 @@ export type CommunityPulseWriteResult =
removed: number;
};
++/** [email protected] (owner: community) */
++/**
++ * 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.
++ */
++export interface CommunityForumPost {
++ 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: community) */
++/**
++ * 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.
++ */
++export interface CommunityForumThread {
++ 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: community) */
++/**
++ * community.paste-create-result@1: the answer of POST /api/pastes on OpenVibe.Community (community.paste.create), a text paste or, with a multipart screenshot part, an image paste (server/pastes/service.js created): 201 { id, slug, url, paste } — url is /p/<slug> and paste the new paste as community.paste@1. Refusals are { error } with 400 (no content, too large, not an image), 409 (a service's slug is taken), 429 (cooldown or daily limit) or 502/503 (Media unavailable for a screenshot).
++ */
++export interface CommunityPasteCreateResult {
++ id: number;
++ slug: string;
++ url: string;
++ paste: CommunityPaste;
++}
++
++/** [email protected] (owner: community) */
++/**
++ * 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.
++ */
++export type CommunityPostWriteResult =
++ | {
++ thread: CommunityForumThread;
++ post: CommunityForumPost;
++ }
++ | {
++ post: CommunityForumPost;
++ 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: community) */
++/**
++ * community.pulse-write-request@1: the body of POST /api/v1/pulse/items on OpenVibe.Community (community.pulse.write; service tokens only): an item about one of the calling service's own entities (ref.service must be that service: 403 pulse.foreign_ref otherwise), with a title (whitespace collapsed, 1 to 300 characters) and an absolute http(s) url (at most 2000 characters). origin defaults to X-OV-Origin (user, ai or system; X-OV-Origin: ai always wins); an origin user item names the person in X-OV-Subject. occurred_at is an ISO time, not in the future (default now). Only public activity enters Pulse: a visibility other than public is 400 pulse.not_public. The same ref again updates the item. Unknown fields are ignored. DELETE /api/v1/pulse/items/:service/:type/:id takes no body.
++ */
++export type CommunityPulseWriteRequest =
++ | {
++ ref: EntityRef;
++ /**
++ * At most 300 characters once whitespace is collapsed.
++ */
++ title: string;
++ url: string;
++ origin?: "user" | "ai" | "system" | null;
++ occurred_at?: string | number | null;
++ visibility?: "public" | null;
++ }
++ | NoBody;
++
/** [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/codes.release.read.json b/manifests/capabilities/codes.release.read.json
iindex 110025a9..ea9bdfc3 100644
---- a/manifests/capabilities/codes.release.read.json
++++ b/manifests/capabilities/codes.release.read.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "public",
"description": "Read public release metadata: an app's published, deprecated and revoked releases with their manifests, compatibility ranges and the app's trust tier (metadata only; it never grants anything). Drafts are never returned. Public; no token needed.",
-- "outputSchema": "codes.app-manifest@1",
++ "outputSchema": "codes.release-read-result@1",
"permissions": ["codes:releases:read"],
"resourceConstraints": ["none"],
"quotaClass": "codes-read",
ddiff --git a/manifests/capabilities/community.paste.create.json b/manifests/capabilities/community.paste.create.json
iindex 07662764..884b9eec 100644
---- a/manifests/capabilities/community.paste.create.json
++++ b/manifests/capabilities/community.paste.create.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "public",
"description": "Create a paste (text or screenshot). A service token creates it for the acting subject named in X-OV-Subject, or as origin \"ai\" for derived pastes (roadmap 33).",
-- "outputSchema": "common.entity-ref@1",
++ "outputSchema": "community.paste-create-result@1",
"permissions": [
"community:write"
],
ddiff --git a/manifests/capabilities/community.post.create.json b/manifests/capabilities/community.post.create.json
iindex 43c6d883..4eeff517 100644
---- a/manifests/capabilities/community.post.create.json
++++ b/manifests/capabilities/community.post.create.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "public",
"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.",
-- "outputSchema": "common.entity-ref@1",
++ "outputSchema": "community.post-write-result@1",
"permissions": [
"community:write"
],
ddiff --git a/manifests/capabilities/community.pulse.write.json b/manifests/capabilities/community.pulse.write.json
iindex 75fca772..cd63a3b9 100644
---- a/manifests/capabilities/community.pulse.write.json
++++ b/manifests/capabilities/community.pulse.write.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "first-party",
"description": "Publish public activity of the calling service's own entities to Pulse (EntityRef + title + url + origin user|ai|system + occurred_at), or retract it. Only visibility public is accepted; the ref's service must be the caller's. Provenance is kept: the first record's origin, actor (X-OV-Subject, never for ai/system) and time stand.",
-- "inputSchema": "common.entity-ref@1",
++ "inputSchema": "community.pulse-write-request@1",
"permissions": [
"community:write"
],
ddiff --git a/manifests/capabilities/events.event.read.json b/manifests/capabilities/events.event.read.json
iindex ad514453..cfee3c6b 100644
---- a/manifests/capabilities/events.event.read.json
++++ b/manifests/capabilities/events.event.read.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "internal",
"description": "Read retained events of every visibility with a seq cursor (pull consumers), keep per-consumer checkpoints, and open realtime streams as a service principal.",
-- "outputSchema": "events.event-envelope@1",
++ "outputSchema": "events.read-result@1",
"permissions": [
"events:read"
],
ddiff --git a/manifests/capabilities/games.mod.manage.json b/manifests/capabilities/games.mod.manage.json
iindex 31439949..b95a91ea 100644
---- a/manifests/capabilities/games.mod.manage.json
++++ b/manifests/capabilities/games.mod.manage.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "first-party",
"description": "Manage mod installs in the browser game's registry (ADR-013): install a mods/mod-manifest.v1 manifest with its games-content@1 pack, approve or revoke individual capabilities of an install, enable, disable, and revoke an install for good (its effects end on the next server tick). Every action is audited and published as a games.mod.* event. Trust tiers are metadata and never grant anything. Also accepted from an openvibe.network owner/admin session.",
-- "inputSchema": "mods.mod-manifest@1",
++ "inputSchema": "games.mod-manage-request@1",
"permissions": ["games:mods:admin"],
"resourceConstraints": ["none"],
"quotaClass": "games-admin",
ddiff --git a/manifests/capabilities/games.mod.read.json b/manifests/capabilities/games.mod.read.json
iindex 06e70dad..ae070869 100644
---- a/manifests/capabilities/games.mod.read.json
++++ b/manifests/capabilities/games.mod.read.json
@@@ -5,7 +5,7 @@
"status": "active",
"visibility": "public",
"description": "Read the browser game's installed mods: manifest, content pack, status, trust tier, requested and granted capabilities. Public; no token needed.",
-- "outputSchema": "mods.mod-manifest@1",
++ "outputSchema": "games.mod-read-result@1",
"permissions": ["games:mods:read"],
"resourceConstraints": ["none"],
"quotaClass": "games-read",