openvibers/openvibe.contracts Commits

Contracts 0.121.0: ADR-054 — the OpenVibe inventory authority (kinds, definitions, instances, equipped, ledger); its planned service, capabilities, schemas and events (plan T21 step 1) (#68)

OP OpenVibers committed 9 Oct 2026 · 2baa26721e · 103 files, +4515 −61

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex c82dc028..4529caa2 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,24 @@ 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.121.0 — 2026-10-09
++
++**The OpenVibe inventory, decided (ADR-054, plan T21 step 1).** A new authority, OpenVibe.Inventory, will own items
++across the network: kinds, definitions, instances, the equipped set and an append-only ledger.
++- `docs/adr/ADR-054-inventory-authority.md` (accepted):
++ - service `inventory`, `ov_inventory`, loopback 5030, `inventory.openvibe.network`;
++ - issuers grant within their namespace;
++ - nothing is sold, bought, traded or converted until a later money ADR (ADR-012, ADR-025);
++ - Live's cosmetics become the first kinds, migrated convert-verify-delete.
++- `manifests/services/inventory.json` (placeholder), with seven planned capabilities: `inventory.item.read`,
++ `inventory.item.list`, `inventory.equip.manage`, `inventory.item.grant`, `inventory.item.consume`,
++ `inventory.definition.manage` and `inventory.definition.review`.
++- Schemas, with fixtures: `inventory.kind@1`, `inventory.definition@1`, `inventory.instance@1`,
++ `inventory.inventory@1`, `inventory.equipped@1`, `inventory.equipped-batch@1`, `inventory.grant-request@1`,
++ `inventory.equip-request@1` and `inventory.definition-request@1`.
++- Events: `inventory.item.granted`, `inventory.item.consumed`, `inventory.item.revoked`, `inventory.item.equipped`,
++ `inventory.item.unequipped` and `inventory.definition.published`.
++
## 0.120.0 — 2026-10-09
**Account export and deletion everywhere a person has rows.** Nine more services declare the two account events.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 4fcaf40e..6e649596 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6398,6 +6398,156 @@
"visibility": "public",
"compatibility": "backward",
"status": "active"
++ },
++ {
++ "id": "inventory.kind",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/kind.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.definition",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/definition.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.instance",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/instance.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.inventory",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/inventory.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.equipped",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/equipped.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.equipped-batch",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/equipped-batch.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.grant-request",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/grant-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.equip-request",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/equip-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.definition-request",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "inventory/definition-request.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.item.granted",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.item.granted.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.item.consumed",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.item.consumed.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.item.revoked",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.item.revoked.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.item.equipped",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.item.equipped.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.item.unequipped",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.item.unequipped.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
++ },
++ {
++ "id": "inventory.definition.published",
++ "version": "1.0.0",
++ "owner": "inventory",
++ "schema": "events/payloads/inventory.definition.published.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-054"
}
]
}
ddiff --git a/contracts/events/payloads/inventory.definition.published.v1.json b/contracts/events/payloads/inventory.definition.published.v1.json
nnew file mode 100644
iindex 00000000..7dacdedb
---- /dev/null
++++ b/contracts/events/payloads/inventory.definition.published.v1.json
@@@ -0,0 +1,43 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.definition.published.v1.json",
++ "title": "InventoryDefinitionPublishedPayload",
++ "description": "inventory.definition.published v1 (OpenVibe.Inventory, ADR-054). A definition became grantable (a service's on request, a creator's after review). Envelope: subject { type: item_definition, id }, visibility public.",
++ "type": "object",
++ "required": [
++ "definition_id",
++ "kind",
++ "name",
++ "rarity",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ]
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.item.consumed.v1.json b/contracts/events/payloads/inventory.item.consumed.v1.json
nnew file mode 100644
iindex 00000000..490a5d4f
---- /dev/null
++++ b/contracts/events/payloads/inventory.item.consumed.v1.json
@@@ -0,0 +1,32 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.consumed.v1.json",
++ "title": "InventoryItemConsumedPayload",
++ "description": "inventory.item.consumed v1 (OpenVibe.Inventory, ADR-054). An issuer consumed an instance (a one-use item used up). Envelope: subject { type: user, id: owner }, visibility subject.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "owner",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.item.equipped.v1.json b/contracts/events/payloads/inventory.item.equipped.v1.json
nnew file mode 100644
iindex 00000000..f20df519
---- /dev/null
++++ b/contracts/events/payloads/inventory.item.equipped.v1.json
@@@ -0,0 +1,37 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.equipped.v1.json",
++ "title": "InventoryItemEquippedPayload",
++ "description": "inventory.item.equipped v1 (OpenVibe.Inventory, ADR-054). A person equipped an instance in a slot; surfaces drop their cached equipped set. Envelope: subject { type: user, id: owner }, visibility public.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "kind",
++ "slot",
++ "owner"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.item.granted.v1.json b/contracts/events/payloads/inventory.item.granted.v1.json
nnew file mode 100644
iindex 00000000..e413b586
---- /dev/null
++++ b/contracts/events/payloads/inventory.item.granted.v1.json
@@@ -0,0 +1,45 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.granted.v1.json",
++ "title": "InventoryItemGrantedPayload",
++ "description": "inventory.item.granted v1 (OpenVibe.Inventory, ADR-054). An instance was granted or earned (ADR-054 §4). Envelope: subject { type: user, id: owner }, visibility subject, actor the issuer.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "kind",
++ "owner",
++ "origin",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned",
++ "migrated"
++ ]
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.item.revoked.v1.json b/contracts/events/payloads/inventory.item.revoked.v1.json
nnew file mode 100644
iindex 00000000..f8e2aac9
---- /dev/null
++++ b/contracts/events/payloads/inventory.item.revoked.v1.json
@@@ -0,0 +1,33 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.revoked.v1.json",
++ "title": "InventoryItemRevokedPayload",
++ "description": "inventory.item.revoked v1 (OpenVibe.Inventory, ADR-054). An issuer or staff revoked an instance (granted in error, abuse). Envelope: subject { type: user, id: owner }, visibility subject.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "owner",
++ "reason"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ }
++}
ddiff --git a/contracts/events/payloads/inventory.item.unequipped.v1.json b/contracts/events/payloads/inventory.item.unequipped.v1.json
nnew file mode 100644
iindex 00000000..1f801d30
---- /dev/null
++++ b/contracts/events/payloads/inventory.item.unequipped.v1.json
@@@ -0,0 +1,27 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.unequipped.v1.json",
++ "title": "InventoryItemUnequippedPayload",
++ "description": "inventory.item.unequipped v1 (OpenVibe.Inventory, ADR-054). A person cleared a slot. Envelope: subject { type: user, id: owner }, visibility public.",
++ "type": "object",
++ "required": [
++ "kind",
++ "slot",
++ "owner"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++}
ddiff --git a/contracts/inventory/definition-request.v1.json b/contracts/inventory/definition-request.v1.json
nnew file mode 100644
iindex 00000000..24d42a24
---- /dev/null
++++ b/contracts/inventory/definition-request.v1.json
@@@ -0,0 +1,84 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/definition-request.v1.json",
++ "title": "InventoryDefinitionRequest",
++ "description": "Create or edit a definition in the issuer's namespace (inventory.definition.manage). A service's or app's definition is published when it asks; a creator's goes to staff review (in_review) first.",
++ "type": "object",
++ "required": [
++ "kind",
++ "name",
++ "art",
++ "rarity"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "art": {
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "media_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "An OpenVibe.Media object (an image, an animation)."
++ },
++ "token": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "A token the kind's renderers know (a CSS class, a speech preset)."
++ },
++ "emoji": {
++ "type": "string",
++ "maxLength": 16
++ }
++ }
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ]
++ },
++ "attributes": {
++ "type": "object"
++ },
++ "supply_cap": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1
++ },
++ "aliases": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,63}$"
++ }
++ },
++ "publish": {
++ "type": "boolean",
++ "description": "Ask for publication (a creator's definition goes to review)."
++ }
++ }
++}
ddiff --git a/contracts/inventory/definition.v1.json b/contracts/inventory/definition.v1.json
nnew file mode 100644
iindex 00000000..3951c897
---- /dev/null
++++ b/contracts/inventory/definition.v1.json
@@@ -0,0 +1,147 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/definition.v1.json",
++ "title": "InventoryDefinition",
++ "description": "An item definition (ADR-054 §2): one item of a kind, issued by a service, an app or a creator, with an honest rarity, attributes valid against the kind and an optional supply cap. Published definitions can be granted; retired ones cannot, and their instances stay.",
++ "type": "object",
++ "required": [
++ "id",
++ "kind",
++ "name",
++ "art",
++ "rarity",
++ "attributes",
++ "issuer",
++ "supply",
++ "status",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "art": {
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "media_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "An OpenVibe.Media object (an image, an animation)."
++ },
++ "token": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "A token the kind's renderers know (a CSS class, a speech preset)."
++ },
++ "emoji": {
++ "type": "string",
++ "maxLength": 16
++ }
++ }
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ],
++ "description": "Shown as is; any odds that apply are shown with it, never hidden."
++ },
++ "attributes": {
++ "type": "object"
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ },
++ "aliases": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,63}$"
++ },
++ "description": "Earlier ids of the same item (Live's fx_rainbow, hat_crown, …)."
++ },
++ "supply": {
++ "type": "object",
++ "required": [
++ "cap",
++ "issued"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "cap": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "null: unlimited."
++ },
++ "issued": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "status": {
++ "enum": [
++ "draft",
++ "in_review",
++ "published",
++ "retired"
++ ]
++ },
++ "credit": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "name": {
++ "type": "string",
++ "maxLength": 80
++ }
++ },
++ "description": "The creator of a community item (ADR-054 §6)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "published_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/inventory/equip-request.v1.json b/contracts/inventory/equip-request.v1.json
nnew file mode 100644
iindex 00000000..46cbf276
---- /dev/null
++++ b/contracts/inventory/equip-request.v1.json
@@@ -0,0 +1,31 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equip-request.v1.json",
++ "title": "InventoryEquipRequest",
++ "description": "Equip an instance the person owns in one slot of its kind, or clear the slot (instance_id null). One item per slot; equipping replaces what was there.",
++ "type": "object",
++ "required": [
++ "kind",
++ "slot",
++ "instance_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "instance_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++}
ddiff --git a/contracts/inventory/equipped-batch.v1.json b/contracts/inventory/equipped-batch.v1.json
nnew file mode 100644
iindex 00000000..9dd07821
---- /dev/null
++++ b/contracts/inventory/equipped-batch.v1.json
@@@ -0,0 +1,20 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equipped-batch.v1.json",
++ "title": "InventoryEquippedBatch",
++ "description": "The equipped sets of many people at once (GET /api/v1/equipped?subjects=…, up to 100): one query on the authority, one call per chat render batch.",
++ "type": "object",
++ "required": [
++ "equipped"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "equipped": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "equipped.v1.json"
++ }
++ }
++ }
++}
ddiff --git a/contracts/inventory/equipped.v1.json b/contracts/inventory/equipped.v1.json
nnew file mode 100644
iindex 00000000..83be54b2
---- /dev/null
++++ b/contracts/inventory/equipped.v1.json
@@@ -0,0 +1,53 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equipped.v1.json",
++ "title": "InventoryEquipped",
++ "description": "What a person has equipped (ADR-054 §2), keyed <kind>:<slot>. Surfaces read it in batches (inventory.equipped-batch@1) and cache it briefly.",
++ "type": "object",
++ "required": [
++ "subject",
++ "slots",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "slots": {
++ "type": "object",
++ "propertyNames": {
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]{0,31}$"
++ },
++ "additionalProperties": {
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "token": {
++ "type": "string",
++ "maxLength": 80,
++ "description": "The definition's art token, so a renderer needs no second read."
++ }
++ }
++ },
++ "description": "<kind>:<slot> → the instance in use."
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/inventory/grant-request.v1.json b/contracts/inventory/grant-request.v1.json
nnew file mode 100644
iindex 00000000..f78343c5
---- /dev/null
++++ b/contracts/inventory/grant-request.v1.json
@@@ -0,0 +1,43 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/grant-request.v1.json",
++ "title": "InventoryGrantRequest",
++ "description": "An issuer grants one instance of a definition it issued to a person (inventory.item.grant). Idempotent per idempotency_key: a repeat answers the instance already granted. A capped definition that is sold out answers 409 inventory.supply_exhausted.",
++ "type": "object",
++ "required": [
++ "definition_id",
++ "subject",
++ "idempotency_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "idempotency_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 128
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned"
++ ],
++ "default": "granted"
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "Shown in the ledger: what the person did (a quest, an event, a milestone)."
++ },
++ "attributes": {
++ "type": "object"
++ }
++ }
++}
ddiff --git a/contracts/inventory/instance.v1.json b/contracts/inventory/instance.v1.json
nnew file mode 100644
iindex 00000000..c5871488
---- /dev/null
++++ b/contracts/inventory/instance.v1.json
@@@ -0,0 +1,59 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/instance.v1.json",
++ "title": "InventoryInstance",
++ "description": "One item a person owns (ADR-054 §2): a definition, its origin and state. bought and traded are reserved for the later money ADR (ADR-054 §5) and are refused until it is accepted.",
++ "type": "object",
++ "required": [
++ "id",
++ "definition_id",
++ "owner",
++ "origin",
++ "state",
++ "acquired_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned",
++ "migrated",
++ "bought",
++ "traded"
++ ]
++ },
++ "state": {
++ "enum": [
++ "owned",
++ "consumed",
++ "revoked"
++ ]
++ },
++ "attributes": {
++ "type": "object",
++ "description": "Per-instance values (a serial number, a wear value), valid against the kind."
++ },
++ "serial": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The instance's number within a capped supply."
++ },
++ "acquired_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/inventory/inventory.v1.json b/contracts/inventory/inventory.v1.json
nnew file mode 100644
iindex 00000000..e1643ac2
---- /dev/null
++++ b/contracts/inventory/inventory.v1.json
@@@ -0,0 +1,43 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/inventory.v1.json",
++ "title": "InventoryList",
++ "description": "A person's inventory page: their instances, newest first, with the definitions they refer to. The public read shows only owned instances of published definitions; the person's own read (inventory.item.list) also shows consumed and revoked ones.",
++ "type": "object",
++ "required": [
++ "subject",
++ "instances",
++ "definitions",
++ "next_cursor"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "instances": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "$ref": "instance.v1.json"
++ }
++ },
++ "definitions": {
++ "type": "object",
++ "propertyNames": {
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "additionalProperties": {
++ "$ref": "definition.v1.json"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ }
++ }
++}
ddiff --git a/contracts/inventory/kind.v1.json b/contracts/inventory/kind.v1.json
nnew file mode 100644
iindex 00000000..985c2db7
---- /dev/null
++++ b/contracts/inventory/kind.v1.json
@@@ -0,0 +1,83 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/kind.v1.json",
++ "title": "InventoryKind",
++ "description": "An item kind (ADR-054 §2): what items of the kind carry (an attribute schema), where they can be equipped (slots) and which surfaces render them. A new kind is a contract plus a renderer, never a new table.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "slots",
++ "surfaces",
++ "attributes",
++ "stackable",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64,
++ "description": "<namespace>.<kind>, e.g. live.name_effect, games.skin, network.badge."
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "slots": {
++ "type": "array",
++ "maxItems": 16,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "description": "Where one item of the kind can be equipped; empty: the kind is not equippable (a collectible, a badge shown on the profile)."
++ },
++ "surfaces": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 16,
++ "items": {
++ "type": "object",
++ "required": [
++ "surface",
++ "renderer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "surface": {
++ "type": "string",
++ "pattern": "^(chat|overlay|profile|game:[a-z][a-z0-9-]*)$",
++ "description": "Where an item of this kind shows."
++ },
++ "renderer": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1."
++ }
++ }
++ }
++ },
++ "attributes": {
++ "type": "object",
++ "description": "A JSON Schema every definition's attributes must satisfy."
++ },
++ "stackable": {
++ "type": "boolean",
++ "description": "True: one person may hold several instances of one definition."
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$",
++ "description": "Who owns the kind's namespace."
++ }
++ }
++}
ddiff --git a/docs/adr/ADR-054-inventory-authority.md b/docs/adr/ADR-054-inventory-authority.md
nnew file mode 100644
iindex 00000000..73ae1515
---- /dev/null
++++ b/docs/adr/ADR-054-inventory-authority.md
@@@ -0,0 +1,153 @@
++# ADR-054: The OpenVibe inventory — one authority for items across the network
++
++**Status:** Accepted 2026-10-09 (plan track T21, step 1), on the owner's direction of 2026-10-08. Builds on ADR-012
++(money lives only in Billing), ADR-025 (nothing traded between people without a new ADR), ADR-029 (account merge),
++ADR-033 (account export and deletion), ADR-035 (each authority owns its PostgreSQL database) and ADR-048 (one authority
++per resource).
++
++## Context
++
++> **Owner, 2026-10-08:** "improve and overhaul/remake the inventory system to be more like steam / csgo / etc style
++> community driven and modular through sdk / api type shit rather than our current inventory seen on openvibe.live for
++> name fx and particles and all that shit it should be a cross site network wide shared multi purpose modular system
++> with user profiles and shit".
++
++What exists today is Live's cosmetics, and only Live can see them:
++
++- **The catalog is code.** `OpenVibe.Live` `server/monetization/cosmetics.js` `COSMETICS` lists name effects,
++ particles, hats and voices, each with a tier and the CSS or speech values Live renders.
++- **Ownership is two tables in Live's database.** `user_cosmetics` holds what a person unlocked (`user_id`, `item_id`,
++ `category`) and `user_equipped` holds one item per slot. On 2026-10-09 production held 153 unlock rows for 8 people
++ (voice 116, name effect 20, particle 10, hat 7) and 16 equipped slots; `user_equipped_tag` was empty.
++- **Unlocks come only from Live's own routes** (`server/monetization/cosmetics-routes.js`). Nothing else can grant,
++ show or equip an item, and nothing outside Live knows a person owns one.
++
++## Decision
++
++### 1. A new authority: OpenVibe.Inventory
++
++- **Identity:** service `inventory`, repository `OpenVibers/OpenVibe.Inventory`, loopback port 5030.
++- **Storage:** its own PostgreSQL database `ov_inventory` (ADR-035).
++- **Public read API:** `https://inventory.openvibe.network`, a Network subdomain like `api.` and `auth.`.
++- **Owns:** item kinds, definitions, instances, the equipped set and the item ledger. Nothing else holds item
++ ownership. Live keeps only the renderers for the kinds it shows.
++- **Profile pages** stay with Network, which owns people: the public profile shows the person's showcase by reading
++ Inventory.
++
++### 2. The model
++
++- **Kind** (a contract, string id `<namespace>.<kind>`, e.g. `live.name_effect`, `games.skin`, `network.badge`, `network.profile_frame`).
++ A kind declares:
++ - its attribute schema (JSON Schema);
++ - its slots, so one item of the kind can be equipped per slot and surface;
++ - the surfaces that render it (`chat`, `overlay`, `profile`, `game:<id>`), each with the renderer contract the
++ surface implements.
++
++ A new kind is a contract plus a renderer, never a new table.
++- **Definition** (`itd_<ULID>`): kind, name, description, art (a Media object or a renderer token such as a CSS class
++ the kind's renderers know), rarity, attributes valid against the kind's schema, issuer, optional supply cap and
++ status (`draft` → `in_review` → `published` → `retired`).
++ - The issuer is a service, an app (ADR-014) or a creator.
++ - Rarity is one of `common`, `uncommon`, `rare`, `epic`, `legendary`, shown with any odds that apply, never hidden.
++- **Instance** (`inv_<ULID>`): one definition owned by one subject, with its origin (`granted`, `earned`, `migrated`;
++ `bought` and `traded` are reserved for §5), acquisition time, optional per-instance attributes (a serial number, a
++ wear value) and state (`owned`, `consumed`, `revoked`).
++- **Equipped set:** per subject and slot, the instance in use. Surfaces read it in batches (many subjects in one call)
++ and cache it briefly.
++- **Ledger:** an append-only row for every movement (granted, earned, migrated, equipped, unequipped, consumed,
++ revoked, merged), with the acting principal and the reason. This is the audit trail later trading needs.
++
++### 3. Who can do what (capabilities, guarded with `requireCapability`)
++
++| Capability | Who | What |
++|---|---|---|
++| `inventory.item.read` | public | a person's public inventory and equipped set, definitions, kinds |
++| `inventory.item.list` | the person, or a service acting for them | their own full inventory, including hidden items |
++| `inventory.equip.manage` | the person, or a service acting for them | equip and unequip their own instances |
++| `inventory.item.grant` | an issuer, for definitions it issued | grant an instance to a subject (idempotent per key) |
++| `inventory.item.consume` | an issuer, for definitions it issued | consume or revoke an instance |
++| `inventory.definition.manage` | an issuer, for its namespace | create, edit, submit, retire definitions |
++| `inventory.definition.review` | staff | publish or reject a submitted definition |
++
++Apps act with grants from Services (ADR-048). A grant to an app names the definition namespace it may issue in.
++
++### 4. Events
++
++The authority publishes these on OpenVibe.Events through its outbox:
++
++- `inventory.item.granted`
++- `inventory.item.consumed`
++- `inventory.item.revoked`
++- `inventory.item.equipped`
++- `inventory.item.unequipped`
++- `inventory.definition.published`
++
++A person's own item events have visibility `subject`, and public profile changes are `public`.
++
++### 5. The money boundary
++
++ADR-012 and ADR-025 hold until a later ADR changes them.
++
++- **Not in v1:** items are not sold, bought, traded between people, cashed out or converted into OpenCoins or Vibes.
++ There are no drops, cases or paid random rewards.
++- **What a later ADR must cover first:** trading with escrow and confirmation, a marketplace priced in OpenCoins and
++ Vibes, and a creator revenue share. It must give the legal basis per country, the Billing flows (ADR-012
++ classification of every item that can be traded), trade holds, fraud and dispute handling, and minors' protection.
++- **Allowed now:** issuers grant items for things people do (a quest, an event, a stream milestone, a game
++ achievement), and staff grant them.
++
++### 6. Community items (the Workshop)
++
++- Creators submit definitions in their own namespace (`creator.<username>.*`).
++- Review covers safety, art rights and honest rarity before anything is published.
++- A published item credits its creator. A revenue share waits for §5's ADR.
++
++### 7. Account data and merge
++
++- **Export (ADR-033):** the person's instances, equipped set and ledger rows.
++- **Deletion:** their instances and equipped set are deleted, and their ledger rows lose their subject, which keeps the
++ supply counts true. A creator's published definitions stay, with the creator credit removed.
++- **Merge (ADR-029):** instances move to the surviving subject, and duplicates of a unique definition keep the oldest.
++
++### 8. Live's cosmetics are the first kinds (convert, verify, delete)
++
++1. **Convert:**
++ - Kinds `live.name_effect`, `live.particle`, `live.hat`, `live.voice` and `live.chat_tag` are registered.
++ - Each `COSMETICS` entry becomes a definition issued by `live`, keeping its id as an alias. Tier maps to rarity:
++ 1 common, 2 uncommon, 3 rare, 4 epic, 5 and 6 legendary.
++ - Every `user_cosmetics` row becomes an instance with origin `migrated`, and every `user_equipped` row becomes the
++ equipped slot.
++2. **Verify:** each person's unlocked set and equipped slots match, row for row, on both sides.
++3. **Switch:** Live reads through Inventory, behind a Live setting `INVENTORY_AUTHORITY` (`live` until the switch, then
++ `inventory`). Its routes become calls to Inventory with Live's service token acting for the person.
++4. **Delete:** after the N-1 window, a contract migration drops `user_cosmetics`, `user_equipped` and
++ `user_equipped_tag`. Live keeps the CSS and speech renderers.
++
++## Alternatives considered
++
++- **Inside Network** (the wallet and profile area): rejected.
++ - Network is identity and the money ledger, and an inventory has its own lifecycle, write load (grants from every
++ game and site) and review queue.
++ - Keeping it apart keeps the ADR-012 boundary visible: an item is never a balance.
++- **Inside Live:** rejected, because items must work across every site.
++- **Inside Games:** rejected, because Live, Chat, Community and the profile also show items, and Games is one issuer
++ among many.
++
++## Rollback
++
++- **Before the Live switch:** stop the Inventory service; Live still reads its own tables.
++- **After the switch and before the delete:** set `INVENTORY_AUTHORITY=live`. Live writes no item rows while switched,
++ so anything granted in between is replayed from the Inventory ledger.
++
++## Acceptance tests
++
++- A kind's schema refuses a definition with invalid attributes, and a renderer is chosen per surface from the kind.
++- A grant is idempotent per key, honours the supply cap, and writes one ledger row and one `inventory.item.granted`.
++- An issuer cannot grant, consume or edit outside its namespace, and staff review is required to publish a creator's
++ definition.
++- Equip refuses an instance the person does not own or that is consumed, and holds one item per slot.
++- A batch read of equipped sets for 100 subjects is one query.
++- No route sells, buys, trades or converts an item (a route-inventory test, as for Trade).
++- Live's migration verifies row for row before the switch; Live renders the same name effects, particles, hats and
++ voices after it.
++- Export, deletion and merge behave as in §7.
ddiff --git a/docs/adr/README.md b/docs/adr/README.md
iindex 57000b61..52534be3 100644
---- a/docs/adr/README.md
++++ b/docs/adr/README.md
@@@ -47,3 +47,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review |
| [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |
| [ADR-053](ADR-053-website-host-boundary.md) | The Website/Host boundary: OpenVibe.Website owns authoring (projects, pages, revisions, builds, Media asset pointers and intended domains) and OpenVibe.Host owns site, deploy, activation, domain binding/verification/TLS and serving; Website keeps no duplicate `host_sites`/`host_deploys` rows and calls Host's routes; a Website build is not a Host deploy; Website composes Host + Codes + Actor + Run + Media and is not OpenVibe.Sites | Proposed 2026-10-05 (plan T19), for the owner's review |
++| [ADR-054](ADR-054-inventory-authority.md) | The OpenVibe inventory: a new authority, OpenVibe.Inventory (`inventory`, `ov_inventory`), owns item kinds (contract + renderer per surface), definitions (`itd_`), instances (`inv_`), the equipped set and an append-only ledger; issuers grant within their namespace; no selling, buying, trading or conversion until a later money ADR (ADR-012/ADR-025); Live's cosmetics are the first kinds, migrated convert-verify-delete | Accepted 2026-10-09 (plan T21) |
ddiff --git a/fixtures/inventory.definition-request/invalid/no-art.json b/fixtures/inventory.definition-request/invalid/no-art.json
nnew file mode 100644
iindex 00000000..367c723e
---- /dev/null
++++ b/fixtures/inventory.definition-request/invalid/no-art.json
@@@ -0,0 +1,5 @@
++{
++ "kind": "network.profile_frame",
++ "name": "x",
++ "rarity": "rare"
++}
ddiff --git a/fixtures/inventory.definition-request/valid/frame.json b/fixtures/inventory.definition-request/valid/frame.json
nnew file mode 100644
iindex 00000000..aff97057
---- /dev/null
++++ b/fixtures/inventory.definition-request/valid/frame.json
@@@ -0,0 +1,10 @@
++{
++ "kind": "network.profile_frame",
++ "name": "Aurora frame",
++ "art": {
++ "media_id": "med_01JZ0000000000000000000MED"
++ },
++ "rarity": "rare",
++ "supply_cap": 500,
++ "publish": true
++}
ddiff --git a/fixtures/inventory.definition.published/invalid/missing-field.json b/fixtures/inventory.definition.published/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..c22089f2
---- /dev/null
++++ b/fixtures/inventory.definition.published/invalid/missing-field.json
@@@ -0,0 +1,6 @@
++{
++ "kind": "network.profile_frame",
++ "name": "Aurora frame",
++ "rarity": "rare",
++ "issuer": "user:usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.definition.published/valid/example.json b/fixtures/inventory.definition.published/valid/example.json
nnew file mode 100644
iindex 00000000..6397fd44
---- /dev/null
++++ b/fixtures/inventory.definition.published/valid/example.json
@@@ -0,0 +1,7 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001T2",
++ "kind": "network.profile_frame",
++ "name": "Aurora frame",
++ "rarity": "rare",
++ "issuer": "user:usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.definition/invalid/bad-issuer.json b/fixtures/inventory.definition/invalid/bad-issuer.json
nnew file mode 100644
iindex 00000000..3ff9dd6d
---- /dev/null
++++ b/fixtures/inventory.definition/invalid/bad-issuer.json
@@@ -0,0 +1,26 @@
++{
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "token": "name-fx-rainbow",
++ "emoji": "🌈"
++ },
++ "rarity": "common",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.definition/invalid/bad-rarity.json b/fixtures/inventory.definition/invalid/bad-rarity.json
nnew file mode 100644
iindex 00000000..7fd97ffc
---- /dev/null
++++ b/fixtures/inventory.definition/invalid/bad-rarity.json
@@@ -0,0 +1,26 @@
++{
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "token": "name-fx-rainbow",
++ "emoji": "🌈"
++ },
++ "rarity": "mythic",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "service:live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.definition/invalid/no-art.json b/fixtures/inventory.definition/invalid/no-art.json
nnew file mode 100644
iindex 00000000..57284df8
---- /dev/null
++++ b/fixtures/inventory.definition/invalid/no-art.json
@@@ -0,0 +1,23 @@
++{
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {},
++ "rarity": "common",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "service:live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.definition/valid/community-capped.json b/fixtures/inventory.definition/valid/community-capped.json
nnew file mode 100644
iindex 00000000..fcc338d2
---- /dev/null
++++ b/fixtures/inventory.definition/valid/community-capped.json
@@@ -0,0 +1,25 @@
++{
++ "id": "itd_01JZ00000000000000000001T2",
++ "kind": "network.profile_frame",
++ "name": "Aurora frame",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "media_id": "med_01JZ0000000000000000000MED"
++ },
++ "rarity": "rare",
++ "attributes": {},
++ "issuer": "user:usr_01JZ0000000000000000000AAA",
++ "aliases": [],
++ "supply": {
++ "cap": 500,
++ "issued": 12
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z",
++ "credit": {
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "name": "aurora_artist"
++ }
++}
ddiff --git a/fixtures/inventory.definition/valid/live-name-effect.json b/fixtures/inventory.definition/valid/live-name-effect.json
nnew file mode 100644
iindex 00000000..903ba501
---- /dev/null
++++ b/fixtures/inventory.definition/valid/live-name-effect.json
@@@ -0,0 +1,26 @@
++{
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "token": "name-fx-rainbow",
++ "emoji": "🌈"
++ },
++ "rarity": "common",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "service:live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.equip-request/invalid/no-slot.json b/fixtures/inventory.equip-request/invalid/no-slot.json
nnew file mode 100644
iindex 00000000..947dfb41
---- /dev/null
++++ b/fixtures/inventory.equip-request/invalid/no-slot.json
@@@ -0,0 +1,4 @@
++{
++ "kind": "live.hat",
++ "instance_id": "inv_01JZ00000000000000000001NV"
++}
ddiff --git a/fixtures/inventory.equip-request/valid/clear.json b/fixtures/inventory.equip-request/valid/clear.json
nnew file mode 100644
iindex 00000000..280eb049
---- /dev/null
++++ b/fixtures/inventory.equip-request/valid/clear.json
@@@ -0,0 +1,5 @@
++{
++ "kind": "live.hat",
++ "slot": "hat",
++ "instance_id": null
++}
ddiff --git a/fixtures/inventory.equip-request/valid/equip.json b/fixtures/inventory.equip-request/valid/equip.json
nnew file mode 100644
iindex 00000000..b54ecae8
---- /dev/null
++++ b/fixtures/inventory.equip-request/valid/equip.json
@@@ -0,0 +1,5 @@
++{
++ "kind": "live.name_effect",
++ "slot": "name_effect",
++ "instance_id": "inv_01JZ00000000000000000001NV"
++}
ddiff --git a/fixtures/inventory.equipped-batch/invalid/not-array.json b/fixtures/inventory.equipped-batch/invalid/not-array.json
nnew file mode 100644
iindex 00000000..b823886b
---- /dev/null
++++ b/fixtures/inventory.equipped-batch/invalid/not-array.json
@@@ -0,0 +1,13 @@
++{
++ "equipped": {
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "slots": {
++ "live.name_effect:name_effect": {
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "token": "name-fx-rainbow"
++ }
++ },
++ "updated_at": "2026-10-09T03:00:00Z"
++ }
++}
ddiff --git a/fixtures/inventory.equipped-batch/valid/two.json b/fixtures/inventory.equipped-batch/valid/two.json
nnew file mode 100644
iindex 00000000..e05bca05
---- /dev/null
++++ b/fixtures/inventory.equipped-batch/valid/two.json
@@@ -0,0 +1,20 @@
++{
++ "equipped": [
++ {
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "slots": {
++ "live.name_effect:name_effect": {
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "token": "name-fx-rainbow"
++ }
++ },
++ "updated_at": "2026-10-09T03:00:00Z"
++ },
++ {
++ "subject": "usr_01JZ0000000000000000000BBB",
++ "slots": {},
++ "updated_at": "2026-10-09T03:00:00Z"
++ }
++ ]
++}
ddiff --git a/fixtures/inventory.equipped/invalid/bad-slot-key.json b/fixtures/inventory.equipped/invalid/bad-slot-key.json
nnew file mode 100644
iindex 00000000..628059ed
---- /dev/null
++++ b/fixtures/inventory.equipped/invalid/bad-slot-key.json
@@@ -0,0 +1,10 @@
++{
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "slots": {
++ "name_effect": {
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD"
++ }
++ },
++ "updated_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.equipped/valid/nothing.json b/fixtures/inventory.equipped/valid/nothing.json
nnew file mode 100644
iindex 00000000..751a8f61
---- /dev/null
++++ b/fixtures/inventory.equipped/valid/nothing.json
@@@ -0,0 +1,5 @@
++{
++ "subject": "usr_01JZ0000000000000000000BBB",
++ "slots": {},
++ "updated_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.equipped/valid/one-slot.json b/fixtures/inventory.equipped/valid/one-slot.json
nnew file mode 100644
iindex 00000000..e58e17cb
---- /dev/null
++++ b/fixtures/inventory.equipped/valid/one-slot.json
@@@ -0,0 +1,11 @@
++{
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "slots": {
++ "live.name_effect:name_effect": {
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "token": "name-fx-rainbow"
++ }
++ },
++ "updated_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.grant-request/invalid/bought-origin.json b/fixtures/inventory.grant-request/invalid/bought-origin.json
nnew file mode 100644
iindex 00000000..aa7b1afc
---- /dev/null
++++ b/fixtures/inventory.grant-request/invalid/bought-origin.json
@@@ -0,0 +1,6 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "idempotency_key": "k-12345678",
++ "origin": "bought"
++}
ddiff --git a/fixtures/inventory.grant-request/invalid/short-key.json b/fixtures/inventory.grant-request/invalid/short-key.json
nnew file mode 100644
iindex 00000000..df4adc76
---- /dev/null
++++ b/fixtures/inventory.grant-request/invalid/short-key.json
@@@ -0,0 +1,5 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "idempotency_key": "k"
++}
ddiff --git a/fixtures/inventory.grant-request/valid/quest.json b/fixtures/inventory.grant-request/valid/quest.json
nnew file mode 100644
iindex 00000000..89b24d61
---- /dev/null
++++ b/fixtures/inventory.grant-request/valid/quest.json
@@@ -0,0 +1,7 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "idempotency_key": "quest-0000-0001",
++ "origin": "earned",
++ "reason": "Completed the first-stream quest"
++}
ddiff --git a/fixtures/inventory.instance/invalid/bad-owner.json b/fixtures/inventory.instance/invalid/bad-owner.json
nnew file mode 100644
iindex 00000000..695b0439
---- /dev/null
++++ b/fixtures/inventory.instance/invalid/bad-owner.json
@@@ -0,0 +1,8 @@
++{
++ "id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "gst_01JZ0000000000000000000AAA",
++ "origin": "migrated",
++ "state": "owned",
++ "acquired_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.instance/invalid/bad-state.json b/fixtures/inventory.instance/invalid/bad-state.json
nnew file mode 100644
iindex 00000000..1ceadab4
---- /dev/null
++++ b/fixtures/inventory.instance/invalid/bad-state.json
@@@ -0,0 +1,8 @@
++{
++ "id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "migrated",
++ "state": "sold",
++ "acquired_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.instance/valid/migrated.json b/fixtures/inventory.instance/valid/migrated.json
nnew file mode 100644
iindex 00000000..d75ad85f
---- /dev/null
++++ b/fixtures/inventory.instance/valid/migrated.json
@@@ -0,0 +1,8 @@
++{
++ "id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "migrated",
++ "state": "owned",
++ "acquired_at": "2026-10-09T03:00:00Z"
++}
ddiff --git a/fixtures/inventory.instance/valid/serial.json b/fixtures/inventory.instance/valid/serial.json
nnew file mode 100644
iindex 00000000..5f7a48b1
---- /dev/null
++++ b/fixtures/inventory.instance/valid/serial.json
@@@ -0,0 +1,12 @@
++{
++ "id": "inv_01JZ00000000000000000001N2",
++ "definition_id": "itd_01JZ00000000000000000001T2",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "earned",
++ "state": "owned",
++ "acquired_at": "2026-10-09T03:00:00Z",
++ "serial": 12,
++ "attributes": {
++ "wear": 0.1
++ }
++}
ddiff --git a/fixtures/inventory.inventory/invalid/bad-definition-key.json b/fixtures/inventory.inventory/invalid/bad-definition-key.json
nnew file mode 100644
iindex 00000000..8dff9a70
---- /dev/null
++++ b/fixtures/inventory.inventory/invalid/bad-definition-key.json
@@@ -0,0 +1,33 @@
++{
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "instances": [],
++ "definitions": {
++ "fx_rainbow": {
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "token": "name-fx-rainbow",
++ "emoji": "🌈"
++ },
++ "rarity": "common",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "service:live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++ }
++ },
++ "next_cursor": null
++}
ddiff --git a/fixtures/inventory.inventory/invalid/no-cursor.json b/fixtures/inventory.inventory/invalid/no-cursor.json
nnew file mode 100644
iindex 00000000..171aa424
---- /dev/null
++++ b/fixtures/inventory.inventory/invalid/no-cursor.json
@@@ -0,0 +1,5 @@
++{
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "instances": [],
++ "definitions": {}
++}
ddiff --git a/fixtures/inventory.inventory/valid/empty.json b/fixtures/inventory.inventory/valid/empty.json
nnew file mode 100644
iindex 00000000..5ff9d255
---- /dev/null
++++ b/fixtures/inventory.inventory/valid/empty.json
@@@ -0,0 +1,6 @@
++{
++ "subject": "usr_01JZ0000000000000000000BBB",
++ "instances": [],
++ "definitions": {},
++ "next_cursor": null
++}
ddiff --git a/fixtures/inventory.inventory/valid/one.json b/fixtures/inventory.inventory/valid/one.json
nnew file mode 100644
iindex 00000000..c861b0dd
---- /dev/null
++++ b/fixtures/inventory.inventory/valid/one.json
@@@ -0,0 +1,42 @@
++{
++ "subject": "usr_01JZ0000000000000000000AAA",
++ "instances": [
++ {
++ "id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "migrated",
++ "state": "owned",
++ "acquired_at": "2026-10-09T03:00:00Z"
++ }
++ ],
++ "definitions": {
++ "itd_01JZ00000000000000000001TD": {
++ "id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "name": "Rainbow Name",
++ "description": "Rainbow cycling colors",
++ "art": {
++ "token": "name-fx-rainbow",
++ "emoji": "🌈"
++ },
++ "rarity": "common",
++ "attributes": {
++ "css_class": "name-fx-rainbow"
++ },
++ "issuer": "service:live",
++ "aliases": [
++ "fx_rainbow"
++ ],
++ "supply": {
++ "cap": null,
++ "issued": 4
++ },
++ "status": "published",
++ "created_at": "2026-10-09T03:00:00Z",
++ "updated_at": "2026-10-09T03:00:00Z",
++ "published_at": "2026-10-09T03:00:00Z"
++ }
++ },
++ "next_cursor": null
++}
ddiff --git a/fixtures/inventory.item.consumed/invalid/missing-field.json b/fixtures/inventory.item.consumed/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..e35d844e
---- /dev/null
++++ b/fixtures/inventory.item.consumed/invalid/missing-field.json
@@@ -0,0 +1,5 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "issuer": "service:games"
++}
ddiff --git a/fixtures/inventory.item.consumed/valid/example.json b/fixtures/inventory.item.consumed/valid/example.json
nnew file mode 100644
iindex 00000000..896d5032
---- /dev/null
++++ b/fixtures/inventory.item.consumed/valid/example.json
@@@ -0,0 +1,6 @@
++{
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "issuer": "service:games"
++}
ddiff --git a/fixtures/inventory.item.equipped/invalid/missing-field.json b/fixtures/inventory.item.equipped/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..48b77529
---- /dev/null
++++ b/fixtures/inventory.item.equipped/invalid/missing-field.json
@@@ -0,0 +1,6 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "slot": "name_effect",
++ "owner": "usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.item.equipped/valid/example.json b/fixtures/inventory.item.equipped/valid/example.json
nnew file mode 100644
iindex 00000000..a7f297bd
---- /dev/null
++++ b/fixtures/inventory.item.equipped/valid/example.json
@@@ -0,0 +1,7 @@
++{
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "slot": "name_effect",
++ "owner": "usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.item.granted/invalid/missing-field.json b/fixtures/inventory.item.granted/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..2e148565
---- /dev/null
++++ b/fixtures/inventory.item.granted/invalid/missing-field.json
@@@ -0,0 +1,7 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "earned",
++ "issuer": "service:quest"
++}
ddiff --git a/fixtures/inventory.item.granted/valid/example.json b/fixtures/inventory.item.granted/valid/example.json
nnew file mode 100644
iindex 00000000..835e6d7c
---- /dev/null
++++ b/fixtures/inventory.item.granted/valid/example.json
@@@ -0,0 +1,8 @@
++{
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "kind": "live.name_effect",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "origin": "earned",
++ "issuer": "service:quest"
++}
ddiff --git a/fixtures/inventory.item.revoked/invalid/missing-field.json b/fixtures/inventory.item.revoked/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..d5756339
---- /dev/null
++++ b/fixtures/inventory.item.revoked/invalid/missing-field.json
@@@ -0,0 +1,5 @@
++{
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "reason": "granted twice by mistake"
++}
ddiff --git a/fixtures/inventory.item.revoked/valid/example.json b/fixtures/inventory.item.revoked/valid/example.json
nnew file mode 100644
iindex 00000000..9cbf9e4e
---- /dev/null
++++ b/fixtures/inventory.item.revoked/valid/example.json
@@@ -0,0 +1,6 @@
++{
++ "instance_id": "inv_01JZ00000000000000000001NV",
++ "definition_id": "itd_01JZ00000000000000000001TD",
++ "owner": "usr_01JZ0000000000000000000AAA",
++ "reason": "granted twice by mistake"
++}
ddiff --git a/fixtures/inventory.item.unequipped/invalid/missing-field.json b/fixtures/inventory.item.unequipped/invalid/missing-field.json
nnew file mode 100644
iindex 00000000..ff614d15
---- /dev/null
++++ b/fixtures/inventory.item.unequipped/invalid/missing-field.json
@@@ -0,0 +1,4 @@
++{
++ "slot": "hat",
++ "owner": "usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.item.unequipped/valid/example.json b/fixtures/inventory.item.unequipped/valid/example.json
nnew file mode 100644
iindex 00000000..0e3fabf1
---- /dev/null
++++ b/fixtures/inventory.item.unequipped/valid/example.json
@@@ -0,0 +1,5 @@
++{
++ "kind": "live.hat",
++ "slot": "hat",
++ "owner": "usr_01JZ0000000000000000000AAA"
++}
ddiff --git a/fixtures/inventory.kind/invalid/bad-id.json b/fixtures/inventory.kind/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..32d6f816
---- /dev/null
++++ b/fixtures/inventory.kind/invalid/bad-id.json
@@@ -0,0 +1,31 @@
++{
++ "id": "NameEffect",
++ "name": "Name effect",
++ "description": "How your name looks in chat.",
++ "slots": [
++ "name_effect"
++ ],
++ "surfaces": [
++ {
++ "surface": "chat",
++ "renderer": "live.name_effect.css@1"
++ },
++ {
++ "surface": "profile",
++ "renderer": "live.name_effect.css@1"
++ }
++ ],
++ "attributes": {
++ "type": "object",
++ "required": [
++ "css_class"
++ ],
++ "properties": {
++ "css_class": {
++ "type": "string"
++ }
++ }
++ },
++ "stackable": false,
++ "issuer": "service:live"
++}
ddiff --git a/fixtures/inventory.kind/invalid/bad-surface.json b/fixtures/inventory.kind/invalid/bad-surface.json
nnew file mode 100644
iindex 00000000..178188b0
---- /dev/null
++++ b/fixtures/inventory.kind/invalid/bad-surface.json
@@@ -0,0 +1,27 @@
++{
++ "id": "live.name_effect",
++ "name": "Name effect",
++ "description": "How your name looks in chat.",
++ "slots": [
++ "name_effect"
++ ],
++ "surfaces": [
++ {
++ "surface": "billboard",
++ "renderer": "x"
++ }
++ ],
++ "attributes": {
++ "type": "object",
++ "required": [
++ "css_class"
++ ],
++ "properties": {
++ "css_class": {
++ "type": "string"
++ }
++ }
++ },
++ "stackable": false,
++ "issuer": "service:live"
++}
ddiff --git a/fixtures/inventory.kind/invalid/no-surface.json b/fixtures/inventory.kind/invalid/no-surface.json
nnew file mode 100644
iindex 00000000..432b92d7
---- /dev/null
++++ b/fixtures/inventory.kind/invalid/no-surface.json
@@@ -0,0 +1,22 @@
++{
++ "id": "live.name_effect",
++ "name": "Name effect",
++ "description": "How your name looks in chat.",
++ "slots": [
++ "name_effect"
++ ],
++ "surfaces": [],
++ "attributes": {
++ "type": "object",
++ "required": [
++ "css_class"
++ ],
++ "properties": {
++ "css_class": {
++ "type": "string"
++ }
++ }
++ },
++ "stackable": false,
++ "issuer": "service:live"
++}
ddiff --git a/fixtures/inventory.kind/valid/badge-not-equippable.json b/fixtures/inventory.kind/valid/badge-not-equippable.json
nnew file mode 100644
iindex 00000000..69233469
---- /dev/null
++++ b/fixtures/inventory.kind/valid/badge-not-equippable.json
@@@ -0,0 +1,25 @@
++{
++ "id": "network.badge",
++ "name": "Badge",
++ "description": "How your name looks in chat.",
++ "slots": [],
++ "surfaces": [
++ {
++ "surface": "profile",
++ "renderer": "network.badge.img@1"
++ }
++ ],
++ "attributes": {
++ "type": "object",
++ "required": [
++ "css_class"
++ ],
++ "properties": {
++ "css_class": {
++ "type": "string"
++ }
++ }
++ },
++ "stackable": true,
++ "issuer": "service:network"
++}
ddiff --git a/fixtures/inventory.kind/valid/name-effect.json b/fixtures/inventory.kind/valid/name-effect.json
nnew file mode 100644
iindex 00000000..778f700e
---- /dev/null
++++ b/fixtures/inventory.kind/valid/name-effect.json
@@@ -0,0 +1,31 @@
++{
++ "id": "live.name_effect",
++ "name": "Name effect",
++ "description": "How your name looks in chat.",
++ "slots": [
++ "name_effect"
++ ],
++ "surfaces": [
++ {
++ "surface": "chat",
++ "renderer": "live.name_effect.css@1"
++ },
++ {
++ "surface": "profile",
++ "renderer": "live.name_effect.css@1"
++ }
++ ],
++ "attributes": {
++ "type": "object",
++ "required": [
++ "css_class"
++ ],
++ "properties": {
++ "css_class": {
++ "type": "string"
++ }
++ }
++ },
++ "stackable": false,
++ "issuer": "service:live"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex d63d76f8..97da12da 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -73814,6 +73814,861 @@
}
]
}
++ },
++ "inventory.kind": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/kind.v1.json",
++ "title": "InventoryKind",
++ "description": "An item kind (ADR-054 §2): what items of the kind carry (an attribute schema), where they can be equipped (slots) and which surfaces render them. A new kind is a contract plus a renderer, never a new table.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "slots",
++ "surfaces",
++ "attributes",
++ "stackable",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64,
++ "description": "<namespace>.<kind>, e.g. live.name_effect, games.skin, network.badge."
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "slots": {
++ "type": "array",
++ "maxItems": 16,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "description": "Where one item of the kind can be equipped; empty: the kind is not equippable (a collectible, a badge shown on the profile)."
++ },
++ "surfaces": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 16,
++ "items": {
++ "type": "object",
++ "required": [
++ "surface",
++ "renderer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "surface": {
++ "type": "string",
++ "pattern": "^(chat|overlay|profile|game:[a-z][a-z0-9-]*)$",
++ "description": "Where an item of this kind shows."
++ },
++ "renderer": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1."
++ }
++ }
++ }
++ },
++ "attributes": {
++ "type": "object",
++ "description": "A JSON Schema every definition's attributes must satisfy."
++ },
++ "stackable": {
++ "type": "boolean",
++ "description": "True: one person may hold several instances of one definition."
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$",
++ "description": "Who owns the kind's namespace."
++ }
++ }
++ }
++ },
++ "inventory.definition": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/definition.v1.json",
++ "title": "InventoryDefinition",
++ "description": "An item definition (ADR-054 §2): one item of a kind, issued by a service, an app or a creator, with an honest rarity, attributes valid against the kind and an optional supply cap. Published definitions can be granted; retired ones cannot, and their instances stay.",
++ "type": "object",
++ "required": [
++ "id",
++ "kind",
++ "name",
++ "art",
++ "rarity",
++ "attributes",
++ "issuer",
++ "supply",
++ "status",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "art": {
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "media_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "An OpenVibe.Media object (an image, an animation)."
++ },
++ "token": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "A token the kind's renderers know (a CSS class, a speech preset)."
++ },
++ "emoji": {
++ "type": "string",
++ "maxLength": 16
++ }
++ }
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ],
++ "description": "Shown as is; any odds that apply are shown with it, never hidden."
++ },
++ "attributes": {
++ "type": "object"
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ },
++ "aliases": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,63}$"
++ },
++ "description": "Earlier ids of the same item (Live's fx_rainbow, hat_crown, …)."
++ },
++ "supply": {
++ "type": "object",
++ "required": [
++ "cap",
++ "issued"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "cap": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "null: unlimited."
++ },
++ "issued": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "status": {
++ "enum": [
++ "draft",
++ "in_review",
++ "published",
++ "retired"
++ ]
++ },
++ "credit": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "name": {
++ "type": "string",
++ "maxLength": 80
++ }
++ },
++ "description": "The creator of a community item (ADR-054 §6)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "published_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "inventory.instance": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/instance.v1.json",
++ "title": "InventoryInstance",
++ "description": "One item a person owns (ADR-054 §2): a definition, its origin and state. bought and traded are reserved for the later money ADR (ADR-054 §5) and are refused until it is accepted.",
++ "type": "object",
++ "required": [
++ "id",
++ "definition_id",
++ "owner",
++ "origin",
++ "state",
++ "acquired_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned",
++ "migrated",
++ "bought",
++ "traded"
++ ]
++ },
++ "state": {
++ "enum": [
++ "owned",
++ "consumed",
++ "revoked"
++ ]
++ },
++ "attributes": {
++ "type": "object",
++ "description": "Per-instance values (a serial number, a wear value), valid against the kind."
++ },
++ "serial": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The instance's number within a capped supply."
++ },
++ "acquired_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "inventory.inventory": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/inventory.v1.json",
++ "title": "InventoryList",
++ "description": "A person's inventory page: their instances, newest first, with the definitions they refer to. The public read shows only owned instances of published definitions; the person's own read (inventory.item.list) also shows consumed and revoked ones.",
++ "type": "object",
++ "required": [
++ "subject",
++ "instances",
++ "definitions",
++ "next_cursor"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "instances": {
++ "type": "array",
++ "maxItems": 500,
++ "items": {
++ "$ref": "instance.v1.json"
++ }
++ },
++ "definitions": {
++ "type": "object",
++ "propertyNames": {
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "additionalProperties": {
++ "$ref": "definition.v1.json"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ }
++ }
++ }
++ },
++ "inventory.equipped": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equipped.v1.json",
++ "title": "InventoryEquipped",
++ "description": "What a person has equipped (ADR-054 §2), keyed <kind>:<slot>. Surfaces read it in batches (inventory.equipped-batch@1) and cache it briefly.",
++ "type": "object",
++ "required": [
++ "subject",
++ "slots",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "slots": {
++ "type": "object",
++ "propertyNames": {
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]{0,31}$"
++ },
++ "additionalProperties": {
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "token": {
++ "type": "string",
++ "maxLength": 80,
++ "description": "The definition's art token, so a renderer needs no second read."
++ }
++ }
++ },
++ "description": "<kind>:<slot> → the instance in use."
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "inventory.equipped-batch": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equipped-batch.v1.json",
++ "title": "InventoryEquippedBatch",
++ "description": "The equipped sets of many people at once (GET /api/v1/equipped?subjects=…, up to 100): one query on the authority, one call per chat render batch.",
++ "type": "object",
++ "required": [
++ "equipped"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "equipped": {
++ "type": "array",
++ "maxItems": 100,
++ "items": {
++ "$ref": "equipped.v1.json"
++ }
++ }
++ }
++ }
++ },
++ "inventory.grant-request": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/grant-request.v1.json",
++ "title": "InventoryGrantRequest",
++ "description": "An issuer grants one instance of a definition it issued to a person (inventory.item.grant). Idempotent per idempotency_key: a repeat answers the instance already granted. A capped definition that is sold out answers 409 inventory.supply_exhausted.",
++ "type": "object",
++ "required": [
++ "definition_id",
++ "subject",
++ "idempotency_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "idempotency_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 128
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned"
++ ],
++ "default": "granted"
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "Shown in the ledger: what the person did (a quest, an event, a milestone)."
++ },
++ "attributes": {
++ "type": "object"
++ }
++ }
++ }
++ },
++ "inventory.equip-request": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/equip-request.v1.json",
++ "title": "InventoryEquipRequest",
++ "description": "Equip an instance the person owns in one slot of its kind, or clear the slot (instance_id null). One item per slot; equipping replaces what was there.",
++ "type": "object",
++ "required": [
++ "kind",
++ "slot",
++ "instance_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "instance_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ "inventory.definition-request": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/inventory/definition-request.v1.json",
++ "title": "InventoryDefinitionRequest",
++ "description": "Create or edit a definition in the issuer's namespace (inventory.definition.manage). A service's or app's definition is published when it asks; a creator's goes to staff review (in_review) first.",
++ "type": "object",
++ "required": [
++ "kind",
++ "name",
++ "art",
++ "rarity"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$",
++ "maxLength": 64
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 500
++ },
++ "art": {
++ "type": "object",
++ "additionalProperties": false,
++ "minProperties": 1,
++ "properties": {
++ "media_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "An OpenVibe.Media object (an image, an animation)."
++ },
++ "token": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "A token the kind's renderers know (a CSS class, a speech preset)."
++ },
++ "emoji": {
++ "type": "string",
++ "maxLength": 16
++ }
++ }
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ]
++ },
++ "attributes": {
++ "type": "object"
++ },
++ "supply_cap": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1
++ },
++ "aliases": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,63}$"
++ }
++ },
++ "publish": {
++ "type": "boolean",
++ "description": "Ask for publication (a creator's definition goes to review)."
++ }
++ }
++ }
++ },
++ "inventory.item.granted": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.granted.v1.json",
++ "title": "InventoryItemGrantedPayload",
++ "description": "inventory.item.granted v1 (OpenVibe.Inventory, ADR-054). An instance was granted or earned (ADR-054 §4). Envelope: subject { type: user, id: owner }, visibility subject, actor the issuer.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "kind",
++ "owner",
++ "origin",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "origin": {
++ "enum": [
++ "granted",
++ "earned",
++ "migrated"
++ ]
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++ }
++ },
++ "inventory.item.consumed": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.consumed.v1.json",
++ "title": "InventoryItemConsumedPayload",
++ "description": "inventory.item.consumed v1 (OpenVibe.Inventory, ADR-054). An issuer consumed an instance (a one-use item used up). Envelope: subject { type: user, id: owner }, visibility subject.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "owner",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++ }
++ },
++ "inventory.item.revoked": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.revoked.v1.json",
++ "title": "InventoryItemRevokedPayload",
++ "description": "inventory.item.revoked v1 (OpenVibe.Inventory, ADR-054). An issuer or staff revoked an instance (granted in error, abuse). Envelope: subject { type: user, id: owner }, visibility subject.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "owner",
++ "reason"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ }
++ }
++ }
++ },
++ "inventory.item.equipped": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.equipped.v1.json",
++ "title": "InventoryItemEquippedPayload",
++ "description": "inventory.item.equipped v1 (OpenVibe.Inventory, ADR-054). A person equipped an instance in a slot; surfaces drop their cached equipped set. Envelope: subject { type: user, id: owner }, visibility public.",
++ "type": "object",
++ "required": [
++ "instance_id",
++ "definition_id",
++ "kind",
++ "slot",
++ "owner"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "instance_id": {
++ "type": "string",
++ "pattern": "^inv_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ "inventory.item.unequipped": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.item.unequipped.v1.json",
++ "title": "InventoryItemUnequippedPayload",
++ "description": "inventory.item.unequipped v1 (OpenVibe.Inventory, ADR-054). A person cleared a slot. Envelope: subject { type: user, id: owner }, visibility public.",
++ "type": "object",
++ "required": [
++ "kind",
++ "slot",
++ "owner"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "slot": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ "inventory.definition.published": {
++ "version": "1.0.0",
++ "owner": "inventory",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/inventory.definition.published.v1.json",
++ "title": "InventoryDefinitionPublishedPayload",
++ "description": "inventory.definition.published v1 (OpenVibe.Inventory, ADR-054). A definition became grantable (a service's on request, a creator's after review). Envelope: subject { type: item_definition, id }, visibility public.",
++ "type": "object",
++ "required": [
++ "definition_id",
++ "kind",
++ "name",
++ "rarity",
++ "issuer"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "definition_id": {
++ "type": "string",
++ "pattern": "^itd_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "kind": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "rarity": {
++ "enum": [
++ "common",
++ "uncommon",
++ "rare",
++ "epic",
++ "legendary"
++ ]
++ },
++ "issuer": {
++ "type": "string",
++ "pattern": "^(service:[a-z][a-z0-9-]*|app:app_[0-9A-HJKMNP-TV-Z]{26}|user:usr_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 53905347..1bdd1c09 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.120.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 85b35e31..b822a660 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.120.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 c042f9cb..98274632 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.120.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 92ae3271..79193869 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.120.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 f61bff4a..fcee0ed6 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.120.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 ce7775c2..e413943b 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.120.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 4244204b..88638af9 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.120.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 43d2a2f9..a4e5e7c0 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.120.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 c6050b10..55bd7626 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.120.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 a002afbe..7ed9778f 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.120.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 735ec44b..90ba76f1 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.120.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 81f57564..5ea8198c 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.120.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 8214c5c1..abdcff60 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.120.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 eda63121..40ba4162 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.120.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 f900f69e..7cd7c10e 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.120.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 06530907..e99320da 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.120.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 7bfaed64..6f903fcf 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.120.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 57dcd7fb..a54c29ae 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.120.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.121.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 518c1a13..6611b32b 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.120.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 37b69063..5628377b 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.120.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 066a55e5..c679dbbb 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.120.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 480a0294..27e4e96a 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.120.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 db7fb450..edeede36 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.120.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 a3c65563..862ae6fa 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.120.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 168bad85..27a30eb7 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.120.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 00186116..b025647c 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.120.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 40ea2072..0a8bae77 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.120.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 d53f116c..726ea740 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.120.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 16f45f9c..8360b6ed 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.120.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.120.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.121.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.121.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 df7cbb89..e1326831 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -59044,3 +59044,1750 @@ export type FoodPlanResult =
plans: FoodPlan[];
}
| FoodPlan;
++
++/** [email protected] (owner: inventory) */
++/**
++ * An item kind (ADR-054 §2): what items of the kind carry (an attribute schema), where they can be equipped (slots) and which surfaces render them. A new kind is a contract plus a renderer, never a new table.
++ */
++export interface InventoryKind {
++ /**
++ * <namespace>.<kind>, e.g. live.name_effect, games.skin, network.badge.
++ */
++ id: string;
++ name: string;
++ description?: string;
++ /**
++ * Where one item of the kind can be equipped; empty: the kind is not equippable (a collectible, a badge shown on the profile).
++ *
++ * @maxItems 16
++ */
++ slots:
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ /**
++ * @minItems 1
++ * @maxItems 16
++ */
++ surfaces:
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ]
++ | [
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ },
++ {
++ /**
++ * Where an item of this kind shows.
++ */
++ surface: string;
++ /**
++ * The renderer contract the surface implements for this kind, e.g. live.name_effect.css@1.
++ */
++ renderer: string;
++ }
++ ];
++ /**
++ * A JSON Schema every definition's attributes must satisfy.
++ */
++ attributes: {};
++ /**
++ * True: one person may hold several instances of one definition.
++ */
++ stackable: boolean;
++ /**
++ * Who owns the kind's namespace.
++ */
++ issuer: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * An item definition (ADR-054 §2): one item of a kind, issued by a service, an app or a creator, with an honest rarity, attributes valid against the kind and an optional supply cap. Published definitions can be granted; retired ones cannot, and their instances stay.
++ */
++export interface InventoryDefinition {
++ id: string;
++ kind: string;
++ name: string;
++ description?: string;
++ art: {
++ /**
++ * An OpenVibe.Media object (an image, an animation).
++ */
++ media_id?: string;
++ /**
++ * A token the kind's renderers know (a CSS class, a speech preset).
++ */
++ token?: string;
++ emoji?: string;
++ };
++ /**
++ * Shown as is; any odds that apply are shown with it, never hidden.
++ */
++ rarity: "common" | "uncommon" | "rare" | "epic" | "legendary";
++ attributes: {};
++ issuer: string;
++ /**
++ * Earlier ids of the same item (Live's fx_rainbow, hat_crown, …).
++ *
++ * @maxItems 8
++ */
++ aliases?:
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string];
++ supply: {
++ /**
++ * null: unlimited.
++ */
++ cap: number | null;
++ issued: number;
++ };
++ status: "draft" | "in_review" | "published" | "retired";
++ /**
++ * The creator of a community item (ADR-054 §6).
++ */
++ credit?: {
++ subject?: string;
++ name?: string;
++ };
++ created_at: string;
++ updated_at: string;
++ published_at?: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * One item a person owns (ADR-054 §2): a definition, its origin and state. bought and traded are reserved for the later money ADR (ADR-054 §5) and are refused until it is accepted.
++ */
++export interface InventoryInstance {
++ id: string;
++ definition_id: string;
++ owner: string;
++ origin: "granted" | "earned" | "migrated" | "bought" | "traded";
++ state: "owned" | "consumed" | "revoked";
++ /**
++ * Per-instance values (a serial number, a wear value), valid against the kind.
++ */
++ attributes?: {};
++ /**
++ * The instance's number within a capped supply.
++ */
++ serial?: number;
++ acquired_at: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * A person's inventory page: their instances, newest first, with the definitions they refer to. The public read shows only owned instances of published definitions; the person's own read (inventory.item.list) also shows consumed and revoked ones.
++ */
++export interface InventoryList {
++ subject: string;
++ /**
++ * @maxItems 500
++ */
++ instances: InventoryInstance[];
++ definitions: {
++ [k: string]: InventoryDefinition | undefined;
++ };
++ next_cursor: string | null;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * What a person has equipped (ADR-054 §2), keyed <kind>:<slot>. Surfaces read it in batches (inventory.equipped-batch@1) and cache it briefly.
++ */
++export interface InventoryEquipped {
++ subject: string;
++ /**
++ * <kind>:<slot> → the instance in use.
++ */
++ slots: {
++ [k: string]:
++ | {
++ instance_id: string;
++ definition_id: string;
++ /**
++ * The definition's art token, so a renderer needs no second read.
++ */
++ token?: string;
++ }
++ | undefined;
++ };
++ updated_at: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * The equipped sets of many people at once (GET /api/v1/equipped?subjects=…, up to 100): one query on the authority, one call per chat render batch.
++ */
++export interface InventoryEquippedBatch {
++ /**
++ * @maxItems 100
++ */
++ equipped: InventoryEquipped[];
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * An issuer grants one instance of a definition it issued to a person (inventory.item.grant). Idempotent per idempotency_key: a repeat answers the instance already granted. A capped definition that is sold out answers 409 inventory.supply_exhausted.
++ */
++export interface InventoryGrantRequest {
++ definition_id: string;
++ subject: string;
++ idempotency_key: string;
++ origin?: "granted" | "earned";
++ /**
++ * Shown in the ledger: what the person did (a quest, an event, a milestone).
++ */
++ reason?: string;
++ attributes?: {};
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * Equip an instance the person owns in one slot of its kind, or clear the slot (instance_id null). One item per slot; equipping replaces what was there.
++ */
++export interface InventoryEquipRequest {
++ kind: string;
++ slot: string;
++ instance_id: string | null;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * Create or edit a definition in the issuer's namespace (inventory.definition.manage). A service's or app's definition is published when it asks; a creator's goes to staff review (in_review) first.
++ */
++export interface InventoryDefinitionRequest {
++ kind: string;
++ name: string;
++ description?: string;
++ art: {
++ /**
++ * An OpenVibe.Media object (an image, an animation).
++ */
++ media_id?: string;
++ /**
++ * A token the kind's renderers know (a CSS class, a speech preset).
++ */
++ token?: string;
++ emoji?: string;
++ };
++ rarity: "common" | "uncommon" | "rare" | "epic" | "legendary";
++ attributes?: {};
++ supply_cap?: number | null;
++ /**
++ * @maxItems 8
++ */
++ aliases?:
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string];
++ /**
++ * Ask for publication (a creator's definition goes to review).
++ */
++ publish?: boolean;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.item.granted v1 (OpenVibe.Inventory, ADR-054). An instance was granted or earned (ADR-054 §4). Envelope: subject { type: user, id: owner }, visibility subject, actor the issuer.
++ */
++export interface InventoryItemGrantedPayload {
++ instance_id: string;
++ definition_id: string;
++ kind: string;
++ owner: string;
++ origin: "granted" | "earned" | "migrated";
++ issuer: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.item.consumed v1 (OpenVibe.Inventory, ADR-054). An issuer consumed an instance (a one-use item used up). Envelope: subject { type: user, id: owner }, visibility subject.
++ */
++export interface InventoryItemConsumedPayload {
++ instance_id: string;
++ definition_id: string;
++ owner: string;
++ issuer: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.item.revoked v1 (OpenVibe.Inventory, ADR-054). An issuer or staff revoked an instance (granted in error, abuse). Envelope: subject { type: user, id: owner }, visibility subject.
++ */
++export interface InventoryItemRevokedPayload {
++ instance_id: string;
++ definition_id: string;
++ owner: string;
++ reason: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.item.equipped v1 (OpenVibe.Inventory, ADR-054). A person equipped an instance in a slot; surfaces drop their cached equipped set. Envelope: subject { type: user, id: owner }, visibility public.
++ */
++export interface InventoryItemEquippedPayload {
++ instance_id: string;
++ definition_id: string;
++ kind: string;
++ slot: string;
++ owner: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.item.unequipped v1 (OpenVibe.Inventory, ADR-054). A person cleared a slot. Envelope: subject { type: user, id: owner }, visibility public.
++ */
++export interface InventoryItemUnequippedPayload {
++ kind: string;
++ slot: string;
++ owner: string;
++}
++
++/** [email protected] (owner: inventory) */
++/**
++ * inventory.definition.published v1 (OpenVibe.Inventory, ADR-054). A definition became grantable (a service's on request, a creator's after review). Envelope: subject { type: item_definition, id }, visibility public.
++ */
++export interface InventoryDefinitionPublishedPayload {
++ definition_id: string;
++ kind: string;
++ name: string;
++ rarity: "common" | "uncommon" | "rare" | "epic" | "legendary";
++ issuer: string;
++}
ddiff --git a/manifests/capabilities/inventory.definition.manage.json b/manifests/capabilities/inventory.definition.manage.json
nnew file mode 100644
iindex 00000000..9977a002
---- /dev/null
++++ b/manifests/capabilities/inventory.definition.manage.json
@@@ -0,0 +1,19 @@
++{
++ "id": "inventory.definition.manage",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "first-party",
++ "description": "PLANNED (ADR-054, plan T21): Create, edit, submit and retire definitions in the caller's namespace (POST /api/v1/definitions, PATCH /api/v1/definitions/:id with inventory.definition-request@1 → inventory.definition@1). A creator's definition goes to review; a service's or app's is published when it asks. It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "inventory.definition-request@1",
++ "outputSchema": "inventory.definition@1",
++ "permissions": [
++ "inventory:define"
++ ],
++ "resourceConstraints": [
++ "namespace"
++ ],
++ "quotaClass": "inventory-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.definition.review.json b/manifests/capabilities/inventory.definition.review.json
nnew file mode 100644
iindex 00000000..3cf5ae1e
---- /dev/null
++++ b/manifests/capabilities/inventory.definition.review.json
@@@ -0,0 +1,17 @@
++{
++ "id": "inventory.definition.review",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "internal",
++ "description": "PLANNED (ADR-054, plan T21): Staff publish or reject a submitted definition (POST /api/v1/definitions/:id/review → inventory.definition@1), checking safety, art rights and an honest rarity (ADR-054 §6). It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "inventory.definition@1",
++ "permissions": [
++ "inventory:review"
++ ],
++ "resourceConstraints": [],
++ "quotaClass": "inventory-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.equip.manage.json b/manifests/capabilities/inventory.equip.manage.json
nnew file mode 100644
iindex 00000000..5fa7e8bd
---- /dev/null
++++ b/manifests/capabilities/inventory.equip.manage.json
@@@ -0,0 +1,19 @@
++{
++ "id": "inventory.equip.manage",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED (ADR-054, plan T21): Equip or clear a slot with an instance the person owns (PUT /api/v1/me/equipped with inventory.equip-request@1 → inventory.equipped@1). One item per slot. Refuses an instance not owned or not in state owned (409 inventory.not_owned). It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "inventory.equip-request@1",
++ "outputSchema": "inventory.equipped@1",
++ "permissions": [
++ "inventory:own:write"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "inventory-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.item.consume.json b/manifests/capabilities/inventory.item.consume.json
nnew file mode 100644
iindex 00000000..f3b503ed
---- /dev/null
++++ b/manifests/capabilities/inventory.item.consume.json
@@@ -0,0 +1,19 @@
++{
++ "id": "inventory.item.consume",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "first-party",
++ "description": "PLANNED (ADR-054, plan T21): Consume or revoke an instance of a definition the caller issued (POST /api/v1/instances/:id/consume | /revoke → inventory.instance@1). It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "inventory.instance@1",
++ "permissions": [
++ "inventory:grant"
++ ],
++ "resourceConstraints": [
++ "namespace"
++ ],
++ "quotaClass": "inventory-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.item.grant.json b/manifests/capabilities/inventory.item.grant.json
nnew file mode 100644
iindex 00000000..8e53f7c3
---- /dev/null
++++ b/manifests/capabilities/inventory.item.grant.json
@@@ -0,0 +1,19 @@
++{
++ "id": "inventory.item.grant",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "first-party",
++ "description": "PLANNED (ADR-054, plan T21): Grant an instance of a definition the caller issued (POST /api/v1/grants with inventory.grant-request@1 → inventory.instance@1; Idempotency by idempotency_key). Outside the issuer's namespace: 403 inventory.not_issuer. Sold out: 409 inventory.supply_exhausted. Never for money (ADR-054 §5). It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "inventory.grant-request@1",
++ "outputSchema": "inventory.instance@1",
++ "permissions": [
++ "inventory:grant"
++ ],
++ "resourceConstraints": [
++ "namespace"
++ ],
++ "quotaClass": "inventory-write",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.item.list.json b/manifests/capabilities/inventory.item.list.json
nnew file mode 100644
iindex 00000000..0a3f7576
---- /dev/null
++++ b/manifests/capabilities/inventory.item.list.json
@@@ -0,0 +1,19 @@
++{
++ "id": "inventory.item.list",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED (ADR-054, plan T21): The person's own full inventory, consumed and revoked instances included (GET /api/v1/me/items → inventory.inventory@1). A service may read it acting for the person (on_behalf_of). It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "inventory.inventory@1",
++ "permissions": [
++ "inventory:own:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "inventory-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/capabilities/inventory.item.read.json b/manifests/capabilities/inventory.item.read.json
nnew file mode 100644
iindex 00000000..87e47638
---- /dev/null
++++ b/manifests/capabilities/inventory.item.read.json
@@@ -0,0 +1,17 @@
++{
++ "id": "inventory.item.read",
++ "version": "1.0",
++ "owner": "inventory",
++ "status": "planned",
++ "visibility": "public",
++ "description": "PLANNED (ADR-054, plan T21): Read a person's public inventory (GET /api/v1/people/:subject/items → inventory.inventory@1: owned instances of published definitions), their equipped set (GET /api/v1/people/:subject/equipped, GET /api/v1/equipped?subjects=… up to 100 → inventory.equipped-batch@1), definitions and kinds. It becomes active when OpenVibe.Inventory implements the route.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "inventory.inventory@1",
++ "permissions": [
++ "inventory:read"
++ ],
++ "resourceConstraints": [],
++ "quotaClass": "inventory-read",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/services/inventory.json b/manifests/services/inventory.json
nnew file mode 100644
iindex 00000000..9fc914ae
---- /dev/null
++++ b/manifests/services/inventory.json
@@@ -0,0 +1,59 @@
++{
++ "version": "0.0.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.121.0 <1.0.0"
++ },
++ "id": "inventory",
++ "name": "OpenVibe.Inventory",
++ "status": "placeholder",
++ "repository": "OpenVibers/OpenVibe.Inventory",
++ "domains": [
++ "inventory.openvibe.network"
++ ],
++ "publicOrigin": "https://inventory.openvibe.network",
++ "exposure": {
++ "state": "placeholder",
++ "publicSite": null,
++ "note": "planned (ADR-054, plan T21 step 1); nothing is built or deployed yet. Live's cosmetics stay in Live until the migration in ADR-054 §8."
++ },
++ "capabilities": [
++ "inventory.item.read",
++ "inventory.item.list",
++ "inventory.equip.manage",
++ "inventory.item.grant",
++ "inventory.item.consume",
++ "inventory.definition.manage",
++ "inventory.definition.review"
++ ],
++ "eventsProduced": [
++ "inventory.item.granted",
++ "inventory.item.consumed",
++ "inventory.item.revoked",
++ "inventory.item.equipped",
++ "inventory.item.unequipped",
++ "inventory.definition.published"
++ ],
++ "eventsConsumed": [],
++ "namespacesOwned": [],
++ "lifecycle": {
++ "liveness": {
++ "none": "not built yet; nothing listens on a loopback address"
++ },
++ "shutdown": {
++ "none": "not built yet; nothing runs or drains"
++ },
++ "startupRecovery": {
++ "none": "not built yet; nothing starts"
++ },
++ "rollback": {
++ "none": "not built yet; nothing is released or rolled back"
++ },
++ "contracts": {
++ "range": ">=0.121.0 <1.0.0"
++ },
++ "leases": {
++ "none": "not built yet; nothing runs"
++ }
++ },
++ "notes": "The network-wide inventory (ADR-054): item kinds (a contract plus a renderer per surface), definitions (itd_), instances (inv_), the equipped set and an append-only ledger, in its own PostgreSQL database (ov_inventory, loopback 5030). Issuers (services, apps, creators) grant within their namespace; no selling, buying, trading or conversion until a later money ADR (ADR-012, ADR-025). Live's cosmetics become the first kinds, migrated convert-verify-delete. Profiles are Network's and read the showcase here."
++}
ddiff --git a/package-lock.json b/package-lock.json
iindex 9394cf0d..97adf349 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.120.0",
++ "version": "0.121.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.120.0",
++ "version": "0.121.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex e598bd01..51d0ab6f 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.120.0",
++ "version": "0.121.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",