openvibers/openvibe.contracts Commits

Contracts 0.172.0: OpenVibe.Play — titles, achievements and leaderboards for every game (play.title.manage, play.result.write, play.player.read) (#128)

OP OpenVibers committed 29 days ago · 37678a1e76 · 60 files, +2278 −69

OpenVibe.Games ADR-0007 decision 10 (M5): the platform's game services get their own service, **OpenVibe.Play**.

**`manifests/services/play.json`.**
- play.openvibe.network on port 5040, under the openvibe.network wildcard certificate.
- Repository OpenVibers/OpenVibe.Play, alpha, live.
- It consumes the two account events (ADR-033).
- Site block: Play, position 35, with the games icon (no new glyph needed).

**Capabilities** (active, public, so developer apps can be granted them; owner constraint):
- **`play.title.manage`**: `PUT /api/v1/titles/:title`. A title belongs to the principal that registered it.
- **`play.result.write`**: score posts (idempotent by ref), unlocks, and guest adoption.
- **`play.player.read`**: a player's unlocks in the caller's own title.

**Schemas,** each with valid and invalid fixtures:
- `play.title-definition@1`
- `play.title-result@1`
- `play.result-request@1` (score post | unlock | adopt)
- `play.result-response@1`
- `play.player-achievements@1`

Additive; 0.172.0. The suite passes (19,841 checks); `compat.js v0.171.0` finds no breaking change.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 850b3255..78410ee0 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.172.0 — 2026-10-14
++
++- **`play`, a new service** (OpenVibe.Games ADR-0007 decision 10): OpenVibe.Play at play.openvibe.network (port 5040),
++ the platform's game services. A title is registered by its own principal, which alone writes to it; it holds
++ achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, and guest
++ adoption. Boards and titles are public reads. People's rows are in the account export and erasure (consumes
++ `network.account.export_requested` and `network.account.deleted`). Site block: Play, position 35 (the games icon).
++- Capabilities (active, public): **`play.title.manage`**, **`play.result.write`**, **`play.player.read`**.
++- Additive.
++
## 0.171.0 — 2026-10-14
- **`input`, a computer kind** (plan T14/T17, "keyboard and mouse input"): `bot.command@1` takes
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 4cebe9cc..cc3ea535 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -7155,6 +7155,56 @@
"visibility": "first-party",
"compatibility": "backward",
"status": "active"
++ },
++ {
++ "id": "play.title-definition",
++ "version": "1.0.0",
++ "owner": "play",
++ "schema": "play/title-definition.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "Games ADR-0007"
++ },
++ {
++ "id": "play.title-result",
++ "version": "1.0.0",
++ "owner": "play",
++ "schema": "play/title-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "Games ADR-0007"
++ },
++ {
++ "id": "play.result-request",
++ "version": "1.0.0",
++ "owner": "play",
++ "schema": "play/result-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "Games ADR-0007"
++ },
++ {
++ "id": "play.result-response",
++ "version": "1.0.0",
++ "owner": "play",
++ "schema": "play/result-response.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "Games ADR-0007"
++ },
++ {
++ "id": "play.player-achievements",
++ "version": "1.0.0",
++ "owner": "play",
++ "schema": "play/player-achievements.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "Games ADR-0007"
}
]
}
ddiff --git a/contracts/play/player-achievements.v1.json b/contracts/play/player-achievements.v1.json
nnew file mode 100644
iindex 00000000..7b0168a0
---- /dev/null
++++ b/contracts/play/player-achievements.v1.json
@@@ -0,0 +1,52 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",
++ "title": "PlayPlayerAchievements",
++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
++ "type": "object",
++ "required": [
++ "title",
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "unlocked_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "unlocked_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/play/result-request.v1.json b/contracts/play/result-request.v1.json
nnew file mode 100644
iindex 00000000..5e229772
---- /dev/null
++++ b/contracts/play/result-request.v1.json
@@@ -0,0 +1,106 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",
++ "title": "PlayResultRequest",
++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "board",
++ "ref",
++ "entries"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "board": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "ref": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 128,
++ "description": "the round or run; a repeat changes nothing"
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ },
++ "entries": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "player",
++ "name",
++ "score"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40
++ },
++ "score": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "person"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "person": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/play/result-response.v1.json b/contracts/play/result-response.v1.json
nnew file mode 100644
iindex 00000000..7f5b26a3
---- /dev/null
++++ b/contracts/play/result-response.v1.json
@@@ -0,0 +1,60 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",
++ "title": "PlayResultResponse",
++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "recorded",
++ "repeated"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "recorded": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "repeated": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocked"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocked": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocks",
++ "scores"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocks": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "scores": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/play/title-definition.v1.json b/contracts/play/title-definition.v1.json
nnew file mode 100644
iindex 00000000..fc16d8ab
---- /dev/null
++++ b/contracts/play/title-definition.v1.json
@@@ -0,0 +1,93 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",
++ "title": "PlayTitleDefinition",
++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
++ "type": "object",
++ "required": [
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^https://",
++ "maxLength": 300,
++ "description": "where the game is played"
++ },
++ "summary": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "achievements": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "hidden": {
++ "type": "boolean",
++ "description": "a secret achievement: its name and text show only to whoever unlocked it"
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ],
++ "description": "best: each player's best score; sum: the total of their scores"
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/play/title-result.v1.json b/contracts/play/title-result.v1.json
nnew file mode 100644
iindex 00000000..060002c0
---- /dev/null
++++ b/contracts/play/title-result.v1.json
@@@ -0,0 +1,111 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",
++ "title": "PlayTitleResult",
++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
++ "type": "object",
++ "required": [
++ "title",
++ "created"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "achievements",
++ "boards"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "summary": {
++ "type": "string"
++ },
++ "players": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "hidden": {
++ "type": "boolean"
++ },
++ "unlocked_by": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ]
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "created": {
++ "type": "boolean"
++ }
++ }
++}
ddiff --git a/fixtures/play.player-achievements/invalid/basic.json b/fixtures/play.player-achievements/invalid/basic.json
nnew file mode 100644
iindex 00000000..8ecb4c95
---- /dev/null
++++ b/fixtures/play.player-achievements/invalid/basic.json
@@@ -0,0 +1,5 @@
++{
++ "title": "salvage-rush",
++ "player": "nobody",
++ "achievements": []
++}
ddiff --git a/fixtures/play.player-achievements/valid/basic.json b/fixtures/play.player-achievements/valid/basic.json
nnew file mode 100644
iindex 00000000..dd2afbd3
---- /dev/null
++++ b/fixtures/play.player-achievements/valid/basic.json
@@@ -0,0 +1,12 @@
++{
++ "title": "salvage-rush",
++ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "achievements": [
++ {
++ "id": "first-haul",
++ "name": "First Haul",
++ "description": "Finish a round with scrap.",
++ "unlocked_at": "2026-10-14T12:00:00.000Z"
++ }
++ ]
++}
ddiff --git a/fixtures/play.result-request/invalid/basic.json b/fixtures/play.result-request/invalid/basic.json
nnew file mode 100644
iindex 00000000..a8a50919
---- /dev/null
++++ b/fixtures/play.result-request/invalid/basic.json
@@@ -0,0 +1,11 @@
++{
++ "board": "best",
++ "ref": "round-1",
++ "entries": [
++ {
++ "player": "bob",
++ "name": "Bob",
++ "score": 1
++ }
++ ]
++}
ddiff --git a/fixtures/play.result-request/valid/basic.json b/fixtures/play.result-request/valid/basic.json
nnew file mode 100644
iindex 00000000..66811724
---- /dev/null
++++ b/fixtures/play.result-request/valid/basic.json
@@@ -0,0 +1,16 @@
++{
++ "board": "best",
++ "ref": "round-1",
++ "entries": [
++ {
++ "player": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "name": "Ada",
++ "score": 450
++ },
++ {
++ "player": "guest:a1b2c3d4e5f6",
++ "name": "Guest 7",
++ "score": 300
++ }
++ ]
++}
ddiff --git a/fixtures/play.result-response/invalid/basic.json b/fixtures/play.result-response/invalid/basic.json
nnew file mode 100644
iindex 00000000..8c12d809
---- /dev/null
++++ b/fixtures/play.result-response/invalid/basic.json
@@@ -0,0 +1,4 @@
++{
++ "recorded": -1,
++ "repeated": 0
++}
ddiff --git a/fixtures/play.result-response/valid/basic.json b/fixtures/play.result-response/valid/basic.json
nnew file mode 100644
iindex 00000000..f6a7cf4a
---- /dev/null
++++ b/fixtures/play.result-response/valid/basic.json
@@@ -0,0 +1,5 @@
++{
++ "unlocked": [
++ "first-haul"
++ ]
++}
ddiff --git a/fixtures/play.title-definition/invalid/basic.json b/fixtures/play.title-definition/invalid/basic.json
nnew file mode 100644
iindex 00000000..50de99e2
---- /dev/null
++++ b/fixtures/play.title-definition/invalid/basic.json
@@@ -0,0 +1,10 @@
++{
++ "name": "X",
++ "boards": [
++ {
++ "id": "best",
++ "name": "Best",
++ "kind": "median"
++ }
++ ]
++}
ddiff --git a/fixtures/play.title-definition/valid/basic.json b/fixtures/play.title-definition/valid/basic.json
nnew file mode 100644
iindex 00000000..397ca94c
---- /dev/null
++++ b/fixtures/play.title-definition/valid/basic.json
@@@ -0,0 +1,18 @@
++{
++ "name": "Salvage Rush",
++ "url": "https://openvibe.games/play?game=salvage-rush",
++ "achievements": [
++ {
++ "id": "first-haul",
++ "name": "First Haul",
++ "description": "Finish a round with scrap."
++ }
++ ],
++ "boards": [
++ {
++ "id": "best",
++ "name": "Best round",
++ "kind": "best"
++ }
++ ]
++}
ddiff --git a/fixtures/play.title-result/invalid/basic.json b/fixtures/play.title-result/invalid/basic.json
nnew file mode 100644
iindex 00000000..68cf5e12
---- /dev/null
++++ b/fixtures/play.title-result/invalid/basic.json
@@@ -0,0 +1,6 @@
++{
++ "title": {
++ "id": "salvage-rush"
++ },
++ "created": "yes"
++}
ddiff --git a/fixtures/play.title-result/valid/basic.json b/fixtures/play.title-result/valid/basic.json
nnew file mode 100644
iindex 00000000..9597c85e
---- /dev/null
++++ b/fixtures/play.title-result/valid/basic.json
@@@ -0,0 +1,27 @@
++{
++ "title": {
++ "id": "salvage-rush",
++ "name": "Salvage Rush",
++ "url": null,
++ "summary": "",
++ "players": 2,
++ "achievements": [
++ {
++ "id": "first-haul",
++ "name": "First Haul",
++ "description": "",
++ "unlocked_by": 2
++ }
++ ],
++ "boards": [
++ {
++ "id": "best",
++ "name": "Best round",
++ "kind": "best"
++ }
++ ],
++ "created_at": "2026-10-14T12:00:00.000Z",
++ "updated_at": "2026-10-14T12:00:00.000Z"
++ },
++ "created": true
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 6a47afc7..bbb71ec9 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -80306,6 +80306,453 @@
}
}
}
++ },
++ "play.title-definition": {
++ "version": "1.0.0",
++ "owner": "play",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/title-definition.v1.json",
++ "title": "PlayTitleDefinition",
++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
++ "type": "object",
++ "required": [
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^https://",
++ "maxLength": 300,
++ "description": "where the game is played"
++ },
++ "summary": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "achievements": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "hidden": {
++ "type": "boolean",
++ "description": "a secret achievement: its name and text show only to whoever unlocked it"
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ],
++ "description": "best: each player's best score; sum: the total of their scores"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "play.title-result": {
++ "version": "1.0.0",
++ "owner": "play",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/title-result.v1.json",
++ "title": "PlayTitleResult",
++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
++ "type": "object",
++ "required": [
++ "title",
++ "created"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "achievements",
++ "boards"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "summary": {
++ "type": "string"
++ },
++ "players": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "hidden": {
++ "type": "boolean"
++ },
++ "unlocked_by": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ]
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "created": {
++ "type": "boolean"
++ }
++ }
++ }
++ },
++ "play.result-request": {
++ "version": "1.0.0",
++ "owner": "play",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/result-request.v1.json",
++ "title": "PlayResultRequest",
++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "board",
++ "ref",
++ "entries"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "board": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "ref": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 128,
++ "description": "the round or run; a repeat changes nothing"
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ },
++ "entries": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "player",
++ "name",
++ "score"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40
++ },
++ "score": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "person"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "person": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "play.result-response": {
++ "version": "1.0.0",
++ "owner": "play",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/result-response.v1.json",
++ "title": "PlayResultResponse",
++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "recorded",
++ "repeated"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "recorded": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "repeated": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocked"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocked": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocks",
++ "scores"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocks": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "scores": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++ }
++ },
++ "play.player-achievements": {
++ "version": "1.0.0",
++ "owner": "play",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/play/player-achievements.v1.json",
++ "title": "PlayPlayerAchievements",
++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
++ "type": "object",
++ "required": [
++ "title",
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "unlocked_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "unlocked_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 3291effd..f78980ff 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 2db5d322..cd262713 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 85a809e7..d71c351b 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 8b3c552b..d5cf60d3 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 2fdfca87..39385692 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex cc9a868e..d30076f7 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex fbd200d9..cb1629bf 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex c717d8b8..b6717fea 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex b89d835e..75b79361 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex fee5c197..548927b7 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex bea06e87..288c958f 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex 5873b005..16f7c042 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 7483ac87..885afb90 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex 10b364c3..a0645d7e 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 9398f2b7..ff4aa409 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -148,6 +148,13 @@
"operations": 24,
"capabilities": 8
},
++ {
++ "service": "play",
++ "name": "OpenVibe.Play",
++ "origin": "https://play.openvibe.network",
++ "operations": 5,
++ "capabilities": 3
++ },
{
"service": "reviews",
"name": "OpenVibe.Reviews",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 9a694740..9a2b4c75 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex d072e08a..ed2e27ef 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
iindex 0808c4d3..900f4d17 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 8859b13d..8ec82b83 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex 65676ef0..288eda13 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex 7e033a44..2ee11831 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex dd2a90c1..9373e64f 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.171.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/play.json b/generated/openapi/play.json
nnew file mode 100644
iindex 00000000..b9cb66b0
---- /dev/null
++++ b/generated/openapi/play.json
@@@ -0,0 +1,846 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Play API",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://play.openvibe.network"
++ }
++ ],
++ "paths": {
++ "/api/v1/titles/{title}": {
++ "put": {
++ "operationId": "put_api_v1_titles_title",
++ "summary": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, na",
++ "description": "**play.title.manage** (public): Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",
++ "tags": [
++ "play.title"
++ ],
++ "x-openvibe-capabilities": [
++ "play.title.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "play-define",
++ "security": [
++ {
++ "openvibeToken": [
++ "play.title.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.title-definition.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "title",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.title-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/titles/{title}/players/{guest}/adopt": {
++ "post": {
++ "operationId": "post_api_v1_titles_title_players_guest_adopt",
++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
++ "tags": [
++ "play.result"
++ ],
++ "x-openvibe-capabilities": [
++ "play.result.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "play-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "play.result.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "title",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "guest",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-response.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/titles/{title}/players/{player}/achievements": {
++ "get": {
++ "operationId": "get_api_v1_titles_title_players_player_achievements",
++ "summary": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have.",
++ "description": "**play.player.read** (public): Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",
++ "tags": [
++ "play.player"
++ ],
++ "x-openvibe-capabilities": [
++ "play.player.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "play.player.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "title",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "player",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.player-achievements.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/titles/{title}/scores": {
++ "post": {
++ "operationId": "post_api_v1_titles_title_scores",
++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
++ "tags": [
++ "play.result"
++ ],
++ "x-openvibe-capabilities": [
++ "play.result.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "play-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "play.result.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "title",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-response.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/titles/{title}/unlocks": {
++ "post": {
++ "operationId": "post_api_v1_titles_title_unlocks",
++ "summary": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a playe",
++ "description": "**play.result.write** (public): Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
++ "tags": [
++ "play.result"
++ ],
++ "x-openvibe-capabilities": [
++ "play.result.write"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "play-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "play.result.write"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "title",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/play.result-response.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "play.player-achievements.v1": {
++ "title": "PlayPlayerAchievements",
++ "description": "One player's unlocked achievements in a title the caller manages (play.player.read), newest first.",
++ "type": "object",
++ "required": [
++ "title",
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "unlocked_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "unlocked_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ }
++ }
++ },
++ "play.result-request.v1": {
++ "title": "PlayResultRequest",
++ "description": "What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "board",
++ "ref",
++ "entries"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "board": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "ref": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 128,
++ "description": "the round or run; a repeat changes nothing"
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ },
++ "entries": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "player",
++ "name",
++ "score"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40
++ },
++ "score": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "player",
++ "achievements"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "player": {
++ "type": "string",
++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|guest:[A-Za-z0-9_-]{8,128})$",
++ "description": "a person (usr_) or a guest the title names (guest:<opaque>)"
++ },
++ "achievements": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 200,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ },
++ "at": {
++ "type": "integer",
++ "minimum": 1577836800000,
++ "description": "epoch milliseconds, never in the future (an import of past results); default now"
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "person"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "person": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ ]
++ },
++ "play.result-response.v1": {
++ "title": "PlayResultResponse",
++ "description": "The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "recorded",
++ "repeated"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "recorded": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "repeated": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocked"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocked": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "unlocks",
++ "scores"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "unlocks": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "scores": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ ]
++ },
++ "play.title-definition.v1": {
++ "title": "PlayTitleDefinition",
++ "description": "A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.",
++ "type": "object",
++ "required": [
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^https://",
++ "maxLength": 300,
++ "description": "where the game is played"
++ },
++ "summary": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "achievements": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 300
++ },
++ "hidden": {
++ "type": "boolean",
++ "description": "a secret achievement: its name and text show only to whoever unlocked it"
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ],
++ "description": "best: each player's best score; sum: the total of their scores"
++ }
++ }
++ }
++ }
++ }
++ },
++ "play.title-result.v1": {
++ "title": "PlayTitleResult",
++ "description": "The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).",
++ "type": "object",
++ "required": [
++ "title",
++ "created"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "title": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "achievements",
++ "boards"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9-]{1,39}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "summary": {
++ "type": "string"
++ },
++ "players": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "achievements": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "description": {
++ "type": "string"
++ },
++ "hidden": {
++ "type": "boolean"
++ },
++ "unlocked_by": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ },
++ "boards": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "kind"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
++ },
++ "name": {
++ "type": "string"
++ },
++ "kind": {
++ "enum": [
++ "best",
++ "sum"
++ ]
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "created": {
++ "type": "boolean"
++ }
++ }
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 12b68f33..61621752 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex f19758e7..456ee8c1 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 4757012c..05f30ccd 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex ed3b3388..39a2dccf 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 34e979a2..1f7e7da3 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 06effa5f..69254153 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 6d4a1dd6..798b92c9 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex b1c592eb..ed480b36 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 04111c39..99f9663b 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 84b803de..5637a6ba 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex dd64e2ca..e4cc2484 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex a47afec5..b10eb608 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 64b3cd46..3220f3ee 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.171.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.171.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.172.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.172.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 70256c45..2508be7a 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -183915,3 +183915,160 @@ export interface CodesTask {
error?: string;
};
}
++
++/** [email protected] (owner: play) */
++/**
++ * A title as its own principal registers it (play.title.manage, PUT /api/v1/titles/:title). Achievements and boards are listed in display order; one left out is retired, never deleted. A board's kind never changes.
++ */
++export interface PlayTitleDefinition {
++ name: string;
++ /**
++ * where the game is played
++ */
++ url?: string | null;
++ summary?: string;
++ /**
++ * @maxItems 200
++ */
++ achievements?: {
++ id: string;
++ name: string;
++ description?: string;
++ /**
++ * a secret achievement: its name and text show only to whoever unlocked it
++ */
++ hidden?: boolean;
++ }[];
++ /**
++ * @maxItems 200
++ */
++ boards?: {
++ id: string;
++ name: string;
++ /**
++ * best: each player's best score; sum: the total of their scores
++ */
++ kind: "best" | "sum";
++ }[];
++}
++
++/** [email protected] (owner: play) */
++/**
++ * The answer to registering or updating a title: the title as anyone reads it, and whether this call created it (201) or updated it (200).
++ */
++export interface PlayTitleResult {
++ title: {
++ id: string;
++ name: string;
++ url?: string | null;
++ summary?: string;
++ players?: number;
++ achievements: {
++ id: string;
++ name: string;
++ description?: string;
++ hidden?: boolean;
++ unlocked_by?: number;
++ }[];
++ boards: {
++ id: string;
++ name: string;
++ kind: "best" | "sum";
++ }[];
++ created_at?: string;
++ updated_at?: string;
++ };
++ created: boolean;
++}
++
++/** [email protected] (owner: play) */
++/**
++ * What a title's principal posts about its players (play.result.write): a round's scores to a board (POST …/scores; idempotent by ref), achievements a player unlocked (POST …/unlocks), or the person a guest became (POST …/players/:guest/adopt).
++ */
++export type PlayResultRequest =
++ | {
++ board: string;
++ /**
++ * the round or run; a repeat changes nothing
++ */
++ ref: string;
++ /**
++ * epoch milliseconds, never in the future (an import of past results); default now
++ */
++ at?: number;
++ /**
++ * @minItems 1
++ * @maxItems 100
++ */
++ entries: [
++ {
++ /**
++ * a person (usr_) or a guest the title names (guest:<opaque>)
++ */
++ player: string;
++ name: string;
++ score: number;
++ },
++ ...{
++ /**
++ * a person (usr_) or a guest the title names (guest:<opaque>)
++ */
++ player: string;
++ name: string;
++ score: number;
++ }[]
++ ];
++ }
++ | {
++ /**
++ * a person (usr_) or a guest the title names (guest:<opaque>)
++ */
++ player: string;
++ /**
++ * @minItems 1
++ * @maxItems 200
++ */
++ achievements: [string, ...string[]];
++ /**
++ * epoch milliseconds, never in the future (an import of past results); default now
++ */
++ at?: number;
++ }
++ | {
++ person: string;
++ };
++
++/** [email protected] (owner: play) */
++/**
++ * The answer to a result post: scores recorded and repeated, the achievements that were new, or the unlocks and scores a guest's adoption moved.
++ */
++export type PlayResultResponse =
++ | {
++ recorded: number;
++ repeated: number;
++ }
++ | {
++ unlocked: string[];
++ }
++ | {
++ unlocks: number;
++ scores: number;
++ };
++
++/** [email protected] (owner: play) */
++/**
++ * One player's unlocked achievements in a title the caller manages (play.player.read), newest first.
++ */
++export interface PlayPlayerAchievements {
++ title: string;
++ /**
++ * a person (usr_) or a guest the title names (guest:<opaque>)
++ */
++ player: string;
++ achievements: {
++ id: string;
++ name: string;
++ description?: string;
++ unlocked_at: string;
++ }[];
++}
ddiff --git a/manifests/capabilities/play.player.read.json b/manifests/capabilities/play.player.read.json
nnew file mode 100644
iindex 00000000..8e4d8dee
---- /dev/null
++++ b/manifests/capabilities/play.player.read.json
@@@ -0,0 +1,21 @@
++{
++ "id": "play.player.read",
++ "version": "1.0",
++ "owner": "play",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read one player's unlocked achievements in a title the caller manages (GET /api/v1/titles/:title/players/:player/achievements): how a game shows a guest or a person what they have. The boards and titles themselves are public reads that need no capability.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "play.player-achievements@1",
++ "permissions": [
++ "play:player:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/titles/:title/players/:player/achievements"
++ ]
++}
ddiff --git a/manifests/capabilities/play.result.write.json b/manifests/capabilities/play.result.write.json
nnew file mode 100644
iindex 00000000..1af4baeb
---- /dev/null
++++ b/manifests/capabilities/play.result.write.json
@@@ -0,0 +1,23 @@
++{
++ "id": "play.result.write",
++ "version": "1.0",
++ "owner": "play",
++ "status": "active",
++ "visibility": "public",
++ "description": "Post what players did in a title the caller manages: scores for a round to one of its boards (POST /api/v1/titles/:title/scores, up to 100 entries, idempotent by ref), achievements unlocked by a player (POST /api/v1/titles/:title/unlocks, answering the new ones), and a guest's progress moved to the person who signed in (POST /api/v1/titles/:title/players/:guest/adopt). A player is a person's subject (usr_) or a guest the title names (guest:<opaque>).",
++ "inputSchema": "play.result-request@1",
++ "outputSchema": "play.result-response@1",
++ "permissions": [
++ "play:result:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "play-write",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/titles/:title/scores",
++ "POST /api/v1/titles/:title/unlocks",
++ "POST /api/v1/titles/:title/players/:guest/adopt"
++ ]
++}
ddiff --git a/manifests/capabilities/play.title.manage.json b/manifests/capabilities/play.title.manage.json
nnew file mode 100644
iindex 00000000..8fb0b86e
---- /dev/null
++++ b/manifests/capabilities/play.title.manage.json
@@@ -0,0 +1,21 @@
++{
++ "id": "play.title.manage",
++ "version": "1.0",
++ "owner": "play",
++ "status": "active",
++ "visibility": "public",
++ "description": "Register or update a title the caller manages on OpenVibe.Play (PUT /api/v1/titles/:title): its name, address and summary, its achievements (id, name, description, hidden) and its leaderboards (id, name, kind best or sum). The principal that registers a title is its only writer; another one is told it does not exist (404) or, registering it, 409 play.title_taken. A definition left out is retired, never deleted: what players earned stays. A board's kind never changes (409 play.board_kind_fixed).",
++ "inputSchema": "play.title-definition@1",
++ "outputSchema": "play.title-result@1",
++ "permissions": [
++ "play:title:manage"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "play-define",
++ "events": [],
++ "implementedBy": [
++ "PUT /api/v1/titles/:title"
++ ]
++}
ddiff --git a/manifests/services/play.json b/manifests/services/play.json
nnew file mode 100644
iindex 00000000..44da97dd
---- /dev/null
++++ b/manifests/services/play.json
@@@ -0,0 +1,91 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.122.0 <1.0.0"
++ },
++ "id": "play",
++ "name": "OpenVibe.Play",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.Play",
++ "domains": [
++ "play.openvibe.network"
++ ],
++ "publicOrigin": "https://play.openvibe.network",
++ "exposure": {
++ "state": "live",
++ "publicSite": "service"
++ },
++ "capabilities": [
++ "play.title.manage",
++ "play.result.write",
++ "play.player.read"
++ ],
++ "eventsProduced": [],
++ "eventsConsumed": [
++ "network.account.export_requested",
++ "network.account.deleted"
++ ],
++ "namespacesOwned": [
++ "play.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; the database is what ready checks"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 10,
++ "drains": [
++ "HTTP server closed",
++ "Events subscription retries stopped",
++ "Network key refresher stopped",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "sessions",
++ "what": "sign-in sessions are cookies verified against Network's keys, fetched again at boot"
++ },
++ {
++ "kind": "state",
++ "what": "every title, unlock and score is a committed row; posts are idempotent, so a game retries after a restart without counting twice"
++ },
++ {
++ "kind": "consumer",
++ "what": "OpenVibe.Events redelivers network.account.export_requested and network.account.deleted that were pending during the stop (at least once; account_data_events applies each once)"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "migration 0001 only creates Play's own tables"
++ }
++ },
++ "contracts": {
++ "range": ">=0.122.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process serves the site and the API"
++ }
++ },
++ "notes": "Port 5040, unit openvibe-play.service, play.openvibe.network (the openvibe.network wildcard certificate). The platform's game services (OpenVibe.Games ADR-0007 decision 10): titles registered by their own principal, achievements and leaderboards (best or sum, this week and all time), idempotent score and unlock posts, guest adoption, in its own PostgreSQL database (ov_play). OpenVibe.Games is its first title owner (Scraplandia, Salvage Rush). People's rows are in the account export and erased with the account (ADR-033).",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:5040",
++ "site": {
++ "name": "Play",
++ "icon": "games",
++ "tagline": "Achievements and leaderboards for every OpenVibe game.",
++ "what": "See this week's leaders and the all-time boards of every OpenVibe game, and every achievement you have unlocked with your account.",
++ "legalProfile": "ugc",
++ "position": 35
++ }
++}
ddiff --git a/package.json b/package.json
iindex aebc9e0f..42b1b66d 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.171.0",
++ "version": "0.172.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",