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)
Changes
@@@ -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-25Platform 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.@@@ -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",nnew file mode 100644@@@ -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"++ }++ ]++ }++ }++ }++ ]++}nnew file mode 100644@@@ -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"++ ]++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -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++ ]++ }++ }++}nnew file mode 100644@@@ -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"++ }++ }++}nnew file mode 100644@@@ -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"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -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"++ }++ ]++}@@@ -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",nnew file mode 100644@@@ -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."++ }++ }++ }++ ]++}@@@ -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",nnew file mode 100644@@@ -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"++ }++ }++ }++ ]++ }++ ]++}nnew file mode 100644@@@ -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"++ }++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{ "app_id": "not-an-app", "trust": { "tier": "unreviewed" }, "releases": [] }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -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 } } ] }nnew file mode 100644@@@ -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" }} }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -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": [] }nnew file mode 100644@@@ -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 }nnew file mode 100644@@@ -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 }nnew file mode 100644@@@ -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 }nnew file mode 100644@@@ -0,0 +1 @@++{ "id": 4412, "slug": "k3JfQ2", "url": "/p/k3JfQ2" }nnew file mode 100644@@@ -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" } }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "id": 311, "deleted": "post" }nnew file mode 100644@@@ -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 } }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "id": 311, "deleted": "thread" }nnew file mode 100644@@@ -0,0 +1 @@++{ "thread_id": 311, "score": 3, "upvotes": 4, "downvotes": 1, "my_vote": 1 }nnew file mode 100644@@@ -0,0 +1 @@++{ "ref": { "service": "live", "type": "clip" }, "title": "t", "url": "ftp://x" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ref": { "service": "live", "type": "clip", "id": "x" }, "title": "t", "url": "https://openvibe.live/clip/x", "visibility": "unlisted" }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -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 }nnew file mode 100644@@@ -0,0 +1 @@++{ "capability": "games.prop.place" }nnew file mode 100644@@@ -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 }nnew file mode 100644@@@ -0,0 +1 @@++{ "reason": "abusive announcements" }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -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" } ] }nnew file mode 100644@@@ -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 } ] } }nnew file mode 100644@@@ -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" }nnew file mode 100644@@@ -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" }@@@ -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",@@@ -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.@@@ -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",@@@ -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"],@@@ -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"],@@@ -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"],@@@ -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"],@@@ -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",@@@ -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",
@@ -10,6 +10,8 @@ Capability schemas (roadmap WS-C task 4), starting with Search: **`search.query@
@@ -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.
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
## 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.
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.
@@ -268,6 +268,15 @@
@@ -268,6 +268,15 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"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",
"id": "events.redaction-directive",
"version": "1.0.0",
"version": "1.0.0",
@@ -3633,6 +3642,33 @@
@@ -3633,6 +3642,33 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"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",
"id": "media.object.deleted",
"version": "1.0.0",
"version": "1.0.0",
@@ -3897,6 +3933,51 @@
@@ -3897,6 +3933,51 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"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",
"id": "openre.destination",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,120 @@
@@ -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"
+ }
+ ]
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,190 @@
@@ -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"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,247 @@
@@ -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
+ ]
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -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"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,92 @@
@@ -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"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,56 @@
@@ -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"
+ }
+ ]
+}
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"title": "EventsReadResult",
"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": [
"anyOf": [
{
{
"type": "object",
"type": "object",
new file mode 100644
new file mode 100644
@@ -0,0 +1,110 @@
@@ -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."
+ }
+ }
+ }
+ ]
+}
@@ -5,76 +5,7 @@
@@ -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, …).",
"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": [
"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",
"type": "object",
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
@@ -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"
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,76 @@
@@ -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"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "app_id": "not-an-app", "trust": { "tier": "unreviewed" }, "releases": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 } } ] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }} }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "id": 4412, "slug": "k3JfQ2", "url": "/p/k3JfQ2" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "id": 311, "deleted": "post" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "id": 311, "deleted": "thread" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "thread_id": 311, "score": 3, "upvotes": 4, "downvotes": 1, "my_vote": 1 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ref": { "service": "live", "type": "clip" }, "title": "t", "url": "ftp://x" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ref": { "service": "live", "type": "clip", "id": "x" }, "title": "t", "url": "https://openvibe.live/clip/x", "visibility": "unlisted" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "capability": "games.prop.place" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "reason": "abusive announcements" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" } ] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 } ] } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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" }
@@ -3451,6 +3451,131 @@
@@ -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": {
"events.redaction-directive": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "events",
"owner": "events",
@@ -3821,7 +3946,7 @@
@@ -3821,7 +3946,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"$id": "https://openvibe.network/contracts/events/read-result.v1.json",
"title": "EventsReadResult",
"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": [
"anyOf": [
{
{
"type": "object",
"type": "object",
@@ -41145,76 +41270,7 @@
@@ -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, …).",
"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": [
"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",
"type": "object",
@@ -41348,80 +41404,325 @@
@@ -41348,80 +41404,325 @@
]
]
}
}
},
},
- "media.object.deleted": {
+ "games.mod-manage-request": {
"version": "1.0.0",
"version": "1.0.0",
- "owner": "media",
+ "owner": "games",
"status": "active",
"status": "active",
"schema": {
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/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",
"type": "object",
"required": [
"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": {
"properties": {
- "object_id": {
+ "id": {
"type": "string",
"type": "string",
- "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
+ "pattern": "^mod_"
},
},
- "app_id": {
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "target": {
"type": "string",
"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": [
"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",
"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",
"version": "1.0.0",
"owner": "media",
"owner": "media",
"status": "active",
"status": "active",
"schema": {
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/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",
"type": "object",
"required": [
"required": [
"object_id",
"object_id",
"app_id",
"app_id",
"kind",
"kind",
"legacy_ref",
"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,
"additionalProperties": false,
"properties": {
"properties": {
@@ -44957,6 +45258,644 @@
@@ -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": {
"openre.destination": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "openre",
"owner": "openre",
@@ -5798,6 +5798,34 @@ export interface CodesRelease {
@@ -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) */
/** [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.
* 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 =
@@ -5932,7 +5960,7 @@ export type EventsReadRequest =
/** [email protected] (owner: events) */
/** [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 =
export type EventsReadResult =
| {
| {
@@ -36359,31 +36387,7 @@ export interface GamesAnnouncement {
@@ -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, …).
* 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 =
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
* @maxItems 500
@@ -36427,6 +36431,148 @@ export type GamesPropPlacement =
@@ -36427,6 +36431,148 @@ export type GamesPropPlacement =
key: string;
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) */
/** [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.
* 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 =
@@ -37574,6 +37720,358 @@ export type CommunityPulseWriteResult =
removed: number;
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) */
/** [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.
* 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.
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "public",
"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.",
"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"],
"permissions": ["codes:releases:read"],
"resourceConstraints": ["none"],
"resourceConstraints": ["none"],
"quotaClass": "codes-read",
"quotaClass": "codes-read",
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "public",
"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).",
"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": [
"permissions": [
"community:write"
"community:write"
],
],
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "public",
"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.",
"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": [
"permissions": [
"community:write"
"community:write"
],
],
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "first-party",
"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.",
"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": [
"permissions": [
"community:write"
"community:write"
],
],
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "internal",
"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.",
"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": [
"permissions": [
"events:read"
"events:read"
],
],
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "first-party",
"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.",
"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"],
"permissions": ["games:mods:admin"],
"resourceConstraints": ["none"],
"resourceConstraints": ["none"],
"quotaClass": "games-admin",
"quotaClass": "games-admin",
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"status": "active",
"status": "active",
"visibility": "public",
"visibility": "public",
"description": "Read the browser game's installed mods: manifest, content pack, status, trust tier, requested and granted capabilities. Public; no token needed.",
"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"],
"permissions": ["games:mods:read"],
"resourceConstraints": ["none"],
"resourceConstraints": ["none"],
"quotaClass": "games-read",
"quotaClass": "games-read",