openvibers/openvibe.contracts Commits

0.241.0: ai.credential-put@2 and ai.credential@2 — one key per provider (#249)

OP OpenVibers committed 14 days ago · 811a1aa2ed · 59 files, +801 −97

Codes C3 (research-2026-10-30/codes-c3-connections.md items 1 and 2): a person
may keep an Anthropic, OpenAI, DeepSeek, MiniMax, OpenRouter or custom key side
by side. Both contracts are additive on @1: the provider enum widens, a PUT may
carry `key` (v1's `api_key` stays accepted), `custom` requires an https
`base_url`, and each credential has its own daily budget.

The registry now holds more than one major of one contract id: the bare id
resolves to the newest listed and id@N to that major. The bundle keys a
multi-major id as id@N, the catalog check looks each entry up by its own major,
and a fixture set is checked against the newest major of its id.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 2fe3d2f0..159bd4ea 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,20 @@ 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.241.0 — 2026-10-30
++
++- **AI: one credential per provider** (Codes C3; research `research-2026-10-30/codes-c3-connections.md` items 1 and 2).
++ `ai.credential-put@2` and `ai.credential@2` are additive on `@1`: the provider enum gains `deepseek`, `minimax`,
++ `openrouter` and `custom` beside `anthropic`, `openai` and `node`, and a PUT may carry `key` (v1's `api_key` stays
++ accepted) and its own `budget_usd_per_day`. `custom` requires an https `base_url`; `node` still takes a machine and no
++ key. A person may now keep one key per provider, and the route without a provider still stores the default one.
++ `ai.credential@2` returns the same body as `@1` with the widened provider, and `key_hint` for every provider except
++ `node`.
++- **Tooling:** the registry can hold more than one major of one contract id — the bare id resolves to the newest listed
++ and `id@N` to that major — so `ai.credential-put@1` keeps resolving while `@2` is the one callers name. The bundle
++ keys a multi-major id as `id@N`, the catalog check looks each entry up by its own major, and a fixture set is checked
++ against the newest major of its id. `ai.credential-put@2` is the first contract to use this.
++
## 0.240.0 — 2026-10-29
- **OpenVibe.Vision's API** (plan §5.8b): the service manifest now lists the 20 capabilities its routes serve today —
ddiff --git a/contracts/ai/credential-put.v2.json b/contracts/ai/credential-put.v2.json
nnew file mode 100644
iindex 00000000..a45e5ea1
---- /dev/null
++++ b/contracts/ai/credential-put.v2.json
@@@ -0,0 +1,142 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential-put.v2.json",
++ "title": "AiCredentialPutV2",
++ "description": "ai.credential-put@2 (OpenVibe.AI; Codes C3): one person's own provider key per provider, stored by the service that holds their consent, PUT /api/v1/credentials/{subject}/{provider} with capability ai.credential.manage. It adds the provider dimension to ai.credential-put@1: a person may keep an Anthropic, OpenAI, DeepSeek, MiniMax, OpenRouter or custom key side by side, each with its own daily budget, and the route without a provider still stores the default one. `key` is the secret (v1's `api_key` stays accepted); `base_url` names the endpoint of a custom provider and is optional elsewhere. OpenVibe.AI encrypts the key at rest, bound to its owner, subject and provider, and never returns it. Provider `node` is unchanged: the person's own machine, named by `machine`, with no key.",
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "provider": {
++ "enum": [
++ "anthropic",
++ "openai",
++ "deepseek",
++ "minimax",
++ "openrouter",
++ "custom",
++ "node"
++ ],
++ "description": "anthropic = Anthropic's Messages API; openai = any OpenAI-compatible endpoint; deepseek, minimax and openrouter = those providers; custom = the endpoint in base_url (a URL that speaks the OpenAI or the Anthropic format, by the route it is used on); node = the person's own machine (no key, no base_url, no cost)."
++ },
++ "key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400,
++ "description": "The provider key (ai.credential-put@2's name for v1's api_key). Required the first time, and whenever provider or base_url changes (a key is never sent to a new endpoint without being entered again); otherwise the stored key is kept, and it is never returned."
++ },
++ "api_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400,
++ "description": "The ai.credential-put@1 name for `key`, still accepted. When both are sent, `key` wins."
++ },
++ "base_url": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$",
++ "description": "The endpoint of a custom provider (required for it): https, no credentials, query or fragment. OpenVibe.AI connects only to public addresses."
++ },
++ "machine": {
++ "type": "string",
++ "pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own."
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per role on this provider; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this provider's key; 0 or absent = no cap."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "const": "node"
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "machine"
++ ],
++ "not": {
++ "anyOf": [
++ {
++ "required": [
++ "key"
++ ]
++ },
++ {
++ "required": [
++ "api_key"
++ ]
++ },
++ {
++ "required": [
++ "base_url"
++ ]
++ }
++ ]
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "const": "custom"
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "base_url"
++ ]
++ }
++ }
++ ]
++}
ddiff --git a/contracts/ai/credential.v2.json b/contracts/ai/credential.v2.json
nnew file mode 100644
iindex 00000000..223f0b6f
---- /dev/null
++++ b/contracts/ai/credential.v2.json
@@@ -0,0 +1,136 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential.v2.json",
++ "title": "AiCredentialV2",
++ "description": "ai.credential@2 (OpenVibe.AI; Codes C3): one stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject} or /{subject}/{provider}): never the key, only its last four characters. It widens ai.credential@1's provider to the providers of ai.credential-put@2, so a person may keep one key per provider; `key_hint` is present for every provider except `node`, whose machine is named instead.",
++ "type": "object",
++ "required": [
++ "subject",
++ "owner",
++ "provider",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}$",
++ "description": "The service that stored it; only it may use or change it."
++ },
++ "provider": {
++ "enum": [
++ "anthropic",
++ "openai",
++ "deepseek",
++ "minimax",
++ "openrouter",
++ "custom",
++ "node"
++ ]
++ },
++ "machine": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
++ "description": "provider node: the machine it runs on, or `any` of the person's own"
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ },
++ "key_hint": {
++ "type": "string",
++ "pattern": "^…[\\s\\S]{1,4}$"
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per role on this provider; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "spent_usd_today": {
++ "type": "number",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_used_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "not": {
++ "const": "node"
++ }
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "key_hint"
++ ]
++ }
++ }
++ ]
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 681bb7ea..01c67cd8 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -1306,6 +1306,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "ai.credential-put",
++ "version": "2.0.0",
++ "owner": "ai",
++ "schema": "ai/credential-put.v2.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "ai.credential",
"version": "1.0.0",
@@@ -1315,6 +1324,15 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "ai.credential",
++ "version": "2.0.0",
++ "owner": "ai",
++ "schema": "ai/credential.v2.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "ai.attribution-quota-put",
"version": "1.0.0",
ddiff --git a/fixtures/ai.credential-put/invalid/custom-http-base.json b/fixtures/ai.credential-put/invalid/custom-http-base.json
nnew file mode 100644
iindex 00000000..b9403263
---- /dev/null
++++ b/fixtures/ai.credential-put/invalid/custom-http-base.json
@@@ -0,0 +1 @@
++{"provider":"custom","key":"sk-custom-EXAMPLEEXAMPLE","base_url":"http://llm.example.com/anthropic"}
ddiff --git a/fixtures/ai.credential-put/invalid/custom-without-base.json b/fixtures/ai.credential-put/invalid/custom-without-base.json
nnew file mode 100644
iindex 00000000..786f5d75
---- /dev/null
++++ b/fixtures/ai.credential-put/invalid/custom-without-base.json
@@@ -0,0 +1 @@
++{"provider":"custom","key":"sk-custom-EXAMPLEEXAMPLE"}
ddiff --git a/fixtures/ai.credential-put/invalid/unknown-provider.json b/fixtures/ai.credential-put/invalid/unknown-provider.json
nnew file mode 100644
iindex 00000000..6136af90
---- /dev/null
++++ b/fixtures/ai.credential-put/invalid/unknown-provider.json
@@@ -0,0 +1 @@
++{"provider":"groq","key":"sk-groq-EXAMPLEEXAMPLE"}
ddiff --git a/fixtures/ai.credential-put/valid/custom-base.json b/fixtures/ai.credential-put/valid/custom-base.json
nnew file mode 100644
iindex 00000000..17958e61
---- /dev/null
++++ b/fixtures/ai.credential-put/valid/custom-base.json
@@@ -0,0 +1 @@
++{"provider":"custom","key":"sk-custom-EXAMPLEEXAMPLEEXAMPLE","base_url":"https://llm.example.com/anthropic","budget_usd_per_day":1}
ddiff --git a/fixtures/ai.credential-put/valid/deepseek-key.json b/fixtures/ai.credential-put/valid/deepseek-key.json
nnew file mode 100644
iindex 00000000..df92e1eb
---- /dev/null
++++ b/fixtures/ai.credential-put/valid/deepseek-key.json
@@@ -0,0 +1 @@
++{"provider":"deepseek","key":"sk-deepseek-EXAMPLEEXAMPLEEXAMPLE","models":{"chat":"deepseek-chat"},"budget_usd_per_day":3}
ddiff --git a/fixtures/ai.credential-put/valid/minimax-key.json b/fixtures/ai.credential-put/valid/minimax-key.json
nnew file mode 100644
iindex 00000000..1a0e85ff
---- /dev/null
++++ b/fixtures/ai.credential-put/valid/minimax-key.json
@@@ -0,0 +1 @@
++{"provider":"minimax","key":"sk-minimax-EXAMPLEEXAMPLEEXAMPLE","models":{"chat":"MiniMax-M2"}}
ddiff --git a/fixtures/ai.credential/invalid/unknown-provider.json b/fixtures/ai.credential/invalid/unknown-provider.json
nnew file mode 100644
iindex 00000000..4f8fd194
---- /dev/null
++++ b/fixtures/ai.credential/invalid/unknown-provider.json
@@@ -0,0 +1 @@
++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","owner":"codes","provider":"groq","key_hint":"…MPLE","created_at":"2026-10-30T00:00:00.000Z","updated_at":"2026-10-30T00:00:00.000Z"}
ddiff --git a/fixtures/ai.credential/valid/custom.json b/fixtures/ai.credential/valid/custom.json
nnew file mode 100644
iindex 00000000..286c6326
---- /dev/null
++++ b/fixtures/ai.credential/valid/custom.json
@@@ -0,0 +1 @@
++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","owner":"codes","provider":"custom","base_url":"https://llm.example.com/anthropic","key_hint":"…MPLE","models":{},"budget_usd_per_day":null,"spent_usd_today":0,"created_at":"2026-10-30T00:00:00.000Z","updated_at":"2026-10-30T00:00:00.000Z","last_used_at":null}
ddiff --git a/fixtures/ai.credential/valid/deepseek.json b/fixtures/ai.credential/valid/deepseek.json
nnew file mode 100644
iindex 00000000..35501123
---- /dev/null
++++ b/fixtures/ai.credential/valid/deepseek.json
@@@ -0,0 +1 @@
++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","owner":"codes","provider":"deepseek","base_url":null,"key_hint":"…MPLE","models":{"chat":"deepseek-chat"},"budget_usd_per_day":3,"spent_usd_today":0.02,"created_at":"2026-10-30T00:00:00.000Z","updated_at":"2026-10-30T00:00:00.000Z","last_used_at":null}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 1d2e0716..67daaf07 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -13857,7 +13857,7 @@
}
}
},
-- "ai.credential-put": {
++ "ai.credential-put@1": {
"version": "1.0.0",
"owner": "ai",
"status": "active",
@@@ -13971,7 +13971,154 @@
]
}
},
-- "ai.credential": {
++ "ai.credential-put@2": {
++ "version": "2.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential-put.v2.json",
++ "title": "AiCredentialPutV2",
++ "description": "ai.credential-put@2 (OpenVibe.AI; Codes C3): one person's own provider key per provider, stored by the service that holds their consent, PUT /api/v1/credentials/{subject}/{provider} with capability ai.credential.manage. It adds the provider dimension to ai.credential-put@1: a person may keep an Anthropic, OpenAI, DeepSeek, MiniMax, OpenRouter or custom key side by side, each with its own daily budget, and the route without a provider still stores the default one. `key` is the secret (v1's `api_key` stays accepted); `base_url` names the endpoint of a custom provider and is optional elsewhere. OpenVibe.AI encrypts the key at rest, bound to its owner, subject and provider, and never returns it. Provider `node` is unchanged: the person's own machine, named by `machine`, with no key.",
++ "type": "object",
++ "required": [
++ "provider"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "provider": {
++ "enum": [
++ "anthropic",
++ "openai",
++ "deepseek",
++ "minimax",
++ "openrouter",
++ "custom",
++ "node"
++ ],
++ "description": "anthropic = Anthropic's Messages API; openai = any OpenAI-compatible endpoint; deepseek, minimax and openrouter = those providers; custom = the endpoint in base_url (a URL that speaks the OpenAI or the Anthropic format, by the route it is used on); node = the person's own machine (no key, no base_url, no cost)."
++ },
++ "key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400,
++ "description": "The provider key (ai.credential-put@2's name for v1's api_key). Required the first time, and whenever provider or base_url changes (a key is never sent to a new endpoint without being entered again); otherwise the stored key is kept, and it is never returned."
++ },
++ "api_key": {
++ "type": "string",
++ "minLength": 8,
++ "maxLength": 400,
++ "description": "The ai.credential-put@1 name for `key`, still accepted. When both are sent, `key` wins."
++ },
++ "base_url": {
++ "type": "string",
++ "maxLength": 300,
++ "pattern": "^https://[^\\s/?#@]+(/[^\\s?#]*)?$",
++ "description": "The endpoint of a custom provider (required for it): https, no credentials, query or fragment. OpenVibe.AI connects only to public addresses."
++ },
++ "machine": {
++ "type": "string",
++ "pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
++ "description": "provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own."
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per role on this provider; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "The person's own daily cap on estimated spend with this provider's key; 0 or absent = no cap."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "const": "node"
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "machine"
++ ],
++ "not": {
++ "anyOf": [
++ {
++ "required": [
++ "key"
++ ]
++ },
++ {
++ "required": [
++ "api_key"
++ ]
++ },
++ {
++ "required": [
++ "base_url"
++ ]
++ }
++ ]
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "const": "custom"
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "base_url"
++ ]
++ }
++ }
++ ]
++ }
++ },
++ "ai.credential@1": {
"version": "1.0.0",
"owner": "ai",
"status": "active",
@@@ -14109,6 +14256,147 @@
]
}
},
++ "ai.credential@2": {
++ "version": "2.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/credential.v2.json",
++ "title": "AiCredentialV2",
++ "description": "ai.credential@2 (OpenVibe.AI; Codes C3): one stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject} or /{subject}/{provider}): never the key, only its last four characters. It widens ai.credential@1's provider to the providers of ai.credential-put@2, so a person may keep one key per provider; `key_hint` is present for every provider except `node`, whose machine is named instead.",
++ "type": "object",
++ "required": [
++ "subject",
++ "owner",
++ "provider",
++ "created_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "owner": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}$",
++ "description": "The service that stored it; only it may use or change it."
++ },
++ "provider": {
++ "enum": [
++ "anthropic",
++ "openai",
++ "deepseek",
++ "minimax",
++ "openrouter",
++ "custom",
++ "node"
++ ]
++ },
++ "machine": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^(dev_[0-9A-HJKMNP-TV-Z]{26}|any)$",
++ "description": "provider node: the machine it runs on, or `any` of the person's own"
++ },
++ "base_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ },
++ "key_hint": {
++ "type": "string",
++ "pattern": "^…[\\s\\S]{1,4}$"
++ },
++ "models": {
++ "type": "object",
++ "additionalProperties": false,
++ "maxProperties": 5,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "vision": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "director": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "summary": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "legacy": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ }
++ },
++ "description": "Model per role on this provider; `chat` is the default for the others."
++ },
++ "budget_usd_per_day": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "spent_usd_today": {
++ "type": "number",
++ "minimum": 0
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_used_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "provider": {
++ "not": {
++ "const": "node"
++ }
++ }
++ },
++ "required": [
++ "provider"
++ ]
++ },
++ "then": {
++ "required": [
++ "key_hint"
++ ]
++ }
++ }
++ ]
++ }
++ },
"ai.attribution-quota-put": {
"version": "1.0.0",
"owner": "ai",
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 92f62bf9..2fb16549 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.240.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 8e3d4b85..7ba8014d 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.240.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/bench.json b/generated/openapi/bench.json
iindex 42ee335b..88e32133 100644
---- a/generated/openapi/bench.json
++++ b/generated/openapi/bench.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bench API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 2d99f19a..132ecd47 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.240.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 69445204..be01c938 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.240.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 e5cfd995..a5f6054c 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.240.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 4f21aabe..728898b6 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.240.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.241.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex e1aaf729..5a8a67e2 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 02e0798b..660cd630 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.240.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 10923bdc..03d8a7b4 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.240.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 74cac7d5..80eb229d 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.240.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/domains.json b/generated/openapi/domains.json
iindex e0ecc604..63cc14db 100644
---- a/generated/openapi/domains.json
++++ b/generated/openapi/domains.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Domains API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 b101a83c..96dfb9f6 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.240.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 8724757d..1d808df6 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.240.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 ea76ff07..b150c137 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.240.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 224de392..ffd1ef9d 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.240.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/inventory.json b/generated/openapi/inventory.json
iindex d3932757..fefa9def 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 25e12ffc..187bda7e 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.240.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.241.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
iindex c107b3db..a04cb577 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 ec291d2a..fa5ae1ca 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.240.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 a9d31f70..c9db8f50 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.240.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 34ab8c91..547d88c5 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.240.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 8b1a8d17..c3343b6f 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.240.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.241.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/play.json b/generated/openapi/play.json
iindex 22df3194..263c10d6 100644
---- a/generated/openapi/play.json
++++ b/generated/openapi/play.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Play API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/quest.json b/generated/openapi/quest.json
iindex 49603bcc..be9cd290 100644
---- a/generated/openapi/quest.json
++++ b/generated/openapi/quest.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Quest API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 3a00c747..1b67aa5b 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.240.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 ddd4d1c3..01f625a1 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.240.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 7121488a..69b8a54b 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.240.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 0e86740f..c5c0424e 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.240.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 1c0996f6..5d8d5d27 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.240.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.241.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 45169ae4..ea42ea07 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 23e2bf25..3120f6b7 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.240.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 7572905c..6f3e4511 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.240.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 0cb27171..a925fc9c 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.240.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 89de768d..72285c95 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.240.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/vision.json b/generated/openapi/vision.json
iindex 28d219ed..7976ee9f 100644
---- a/generated/openapi/vision.json
++++ b/generated/openapi/vision.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Vision API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 9ce7d600..1bf3c24a 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.240.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 b26e5151..4e9fb8b2 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.240.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.241.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 35c9db7b..c3a34fb6 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.241.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/zone.json b/generated/openapi/zone.json
iindex e37a7555..6ea9ba62 100644
---- a/generated/openapi/zone.json
++++ b/generated/openapi/zone.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Zone API",
-- "version": "0.240.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.240.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.241.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.241.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 31921bf5..a3fa134e 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -151930,6 +151930,49 @@ export type AiCredentialPut = {
budget_usd_per_day?: number;
};
++/** [email protected] (owner: ai) */
++/**
++ * ai.credential-put@2 (OpenVibe.AI; Codes C3): one person's own provider key per provider, stored by the service that holds their consent, PUT /api/v1/credentials/{subject}/{provider} with capability ai.credential.manage. It adds the provider dimension to ai.credential-put@1: a person may keep an Anthropic, OpenAI, DeepSeek, MiniMax, OpenRouter or custom key side by side, each with its own daily budget, and the route without a provider still stores the default one. `key` is the secret (v1's `api_key` stays accepted); `base_url` names the endpoint of a custom provider and is optional elsewhere. OpenVibe.AI encrypts the key at rest, bound to its owner, subject and provider, and never returns it. Provider `node` is unchanged: the person's own machine, named by `machine`, with no key.
++ */
++export type AiCredentialPutV2 = {
++ [k: string]: unknown | undefined;
++} & {
++ /**
++ * anthropic = Anthropic's Messages API; openai = any OpenAI-compatible endpoint; deepseek, minimax and openrouter = those providers; custom = the endpoint in base_url (a URL that speaks the OpenAI or the Anthropic format, by the route it is used on); node = the person's own machine (no key, no base_url, no cost).
++ */
++ provider: "anthropic" | "openai" | "deepseek" | "minimax" | "openrouter" | "custom" | "node";
++ /**
++ * The provider key (ai.credential-put@2's name for v1's api_key). Required the first time, and whenever provider or base_url changes (a key is never sent to a new endpoint without being entered again); otherwise the stored key is kept, and it is never returned.
++ */
++ key?: string;
++ /**
++ * The ai.credential-put@1 name for `key`, still accepted. When both are sent, `key` wins.
++ */
++ api_key?: string;
++ /**
++ * The endpoint of a custom provider (required for it): https, no credentials, query or fragment. OpenVibe.AI connects only to public addresses.
++ */
++ base_url?: string;
++ /**
++ * provider node: the person's own machine (openvibe.bot/machines) whose OpenVibe Node runs the model, or `any` of their own.
++ */
++ machine?: string;
++ /**
++ * Model per role on this provider; `chat` is the default for the others.
++ */
++ models?: {
++ chat?: string;
++ vision?: string;
++ director?: string;
++ summary?: string;
++ legacy?: string;
++ };
++ /**
++ * The person's own daily cap on estimated spend with this provider's key; 0 or absent = no cap.
++ */
++ budget_usd_per_day?: number;
++};
++
/** [email protected] (owner: ai) */
/**
* ai.credential@1 (OpenVibe.AI; roadmap WS-O task 2): a stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject}): never the key, only its last four characters.
@@@ -151966,6 +152009,42 @@ export type AiCredential = {
last_used_at?: string | null;
};
++/** [email protected] (owner: ai) */
++/**
++ * ai.credential@2 (OpenVibe.AI; Codes C3): one stored credential as its owner service reads it (GET or PUT /api/v1/credentials/{subject} or /{subject}/{provider}): never the key, only its last four characters. It widens ai.credential@1's provider to the providers of ai.credential-put@2, so a person may keep one key per provider; `key_hint` is present for every provider except `node`, whose machine is named instead.
++ */
++export type AiCredentialV2 = {
++ [k: string]: unknown | undefined;
++} & {
++ subject: string;
++ /**
++ * The service that stored it; only it may use or change it.
++ */
++ owner: string;
++ provider: "anthropic" | "openai" | "deepseek" | "minimax" | "openrouter" | "custom" | "node";
++ /**
++ * provider node: the machine it runs on, or `any` of the person's own
++ */
++ machine?: string | null;
++ base_url?: string | null;
++ key_hint?: string;
++ /**
++ * Model per role on this provider; `chat` is the default for the others.
++ */
++ models?: {
++ chat?: string;
++ vision?: string;
++ director?: string;
++ summary?: string;
++ legacy?: string;
++ };
++ budget_usd_per_day?: number | null;
++ spent_usd_today?: number;
++ created_at: string;
++ updated_at: string;
++ last_used_at?: string | null;
++};
++
/** [email protected] (owner: ai) */
/**
* ai.attribution-quota-put@1 (OpenVibe.AI; roadmap WS-O task 2): a cap on runs attributed to one of the caller's own entities, PUT /api/v1/attribution-quotas/{attribution} with capability ai.quota.attribution.manage; the attribution's service must be the caller (Live caps live:user:<id>, a streamer's AI viewers). Over the cap a run is refused 429 quota.exceeded before any provider is called. Cost counts what runs have cost so far in the window, so the run that crosses the cap finishes and the next is refused.
ddiff --git a/lib/registry.js b/lib/registry.js
iindex d711b7f0..be29f397 100644
---- a/lib/registry.js
++++ b/lib/registry.js
@@@ -13,20 +13,28 @@ const deprecations = JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/d
const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false, allowUnionTypes: true });
addFormats(ajv);
--const byId = new Map();
++// One contract id may hold more than one major (a v2 file, a catalog entry each). The bare id always
++// resolves to the newest major listed, and `id@N` to that major, so a v1 caller keeps resolving while
++// a v2 caller names it exactly. The catalog lists versions in ascending order.
++const byRef = new Map();
for (const entry of catalog) {
const schema = JSON.parse(fs.readFileSync(path.join(CONTRACTS, entry.schema), 'utf8'));
ajv.addSchema(schema);
-- byId.set(entry.id, { ...entry, $id: schema.$id, schemaObject: schema });
++ const record = { ...entry, $id: schema.$id, schemaObject: schema };
++ byRef.set(`${entry.id}@${entry.version.split('.')[0]}`, record);
++ byRef.set(entry.id, record);
}
--/** 'identity.subject-ref' | 'identity.subject-ref@1' | an alias -> catalog entry. */
++/** 'identity.subject-ref' | 'identity.subject-ref@1' | an alias -> catalog entry (the newest major, or the named one). */
function resolve(ref) {
const [raw, major] = String(ref).split('@');
const id = aliases[raw] || raw;
-- const entry = byId.get(id);
-- if (!entry) throw new Error(`unknown contract "${ref}"`);
-- if (major && entry.version.split('.')[0] !== major) throw new Error(`contract "${id}" is v${entry.version}, not @${major}`);
++ const entry = major ? byRef.get(`${id}@${major}`) : byRef.get(id);
++ if (!entry) {
++ const known = byRef.get(id);
++ if (major && known) throw new Error(`contract "${id}" is v${known.version}, not @${major}`);
++ throw new Error(`unknown contract "${ref}"`);
++ }
return entry;
}
ddiff --git a/package.json b/package.json
iindex 5f6dcc52..9ab3cfba 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.240.0",
++ "version": "0.241.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
ddiff --git a/scripts/generate.js b/scripts/generate.js
iindex b139d3b0..943b3afe 100644
---- a/scripts/generate.js
++++ b/scripts/generate.js
@@@ -73,13 +73,18 @@ function onlyOnce(ts, title, owned, emitted) {
const stale = [];
const bundle = { _about: 'Generated by scripts/generate.js. Do not edit.', contracts: {} };
let dts = '/* Generated by scripts/generate.js from contracts/. Do not edit. */\n/* eslint-disable */\n';
-- const schemas = new Map(catalog.map((entry) => [entry.id, JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', entry.schema), 'utf8'))]));
++ const schemas = new Map(catalog.map((entry) => [entry.schema, JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', entry.schema), 'utf8'))]));
++ // An id with more than one major in the catalog is keyed `id@N` in the bundle, so neither version
++ // overwrites the other; every other id keeps its bare key.
++ const majors = new Map();
++ for (const e of catalog) majors.set(e.id, (majors.get(e.id) || 0) + 1);
const owned = new Set([...schemas.values()].map((s) => s.title));
const emitted = new Set();
for (const entry of catalog) {
const file = path.join(ROOT, 'contracts', entry.schema);
-- const schema = schemas.get(entry.id);
-- bundle.contracts[entry.id] = { version: entry.version, owner: entry.owner, status: entry.status, schema };
++ const schema = schemas.get(entry.schema);
++ const bundleKey = majors.get(entry.id) > 1 ? `${entry.id}@${entry.version.split('.')[0]}` : entry.id;
++ bundle.contracts[bundleKey] = { version: entry.version, owner: entry.owner, status: entry.status, schema };
const ts = await compile(inlineDefs(schema, file), schema.title, {
bannerComment: '', cwd: path.dirname(file), declareExternallyReferenced: false,
additionalProperties: false, strictIndexSignatures: true, format: true,
ddiff --git a/test/run.js b/test/run.js
iindex 99317f9c..bcf9d96d 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -42,10 +42,11 @@ const files = [];
for (const f of files) ok(contracts.catalog.some(c => c.schema === f), `${f} is in catalog.json`);
for (const c of contracts.catalog) {
for (const k of ['id', 'version', 'owner', 'schema', 'visibility', 'compatibility', 'status']) ok(c[k], `${c.id} has ${k}`);
-- const s = contracts.schema(c.id);
-- ok(s.$id === BASE + c.schema, `${c.id} $id matches its path`);
-- ok(c.schema.endsWith(`.v${c.version.split('.')[0]}.json`), `${c.id} file major matches version ${c.version}`);
-- ok(s.title && s.description, `${c.id} has title and description`);
++ const ref = `${c.id}@${c.version.split('.')[0]}`;
++ const s = contracts.schema(ref);
++ ok(s.$id === BASE + c.schema, `${ref} $id matches its path`);
++ ok(c.schema.endsWith(`.v${c.version.split('.')[0]}.json`), `${ref} file major matches version ${c.version}`);
++ ok(s.title && s.description, `${ref} has title and description`);
ok(/^\d+\.\d+\.\d+$/.test(c.version), `${c.id} semver`);
}
for (const d of contracts.catalog.length ? JSON.parse(fs.readFileSync(path.join(ROOT, 'compatibility/deprecations.json'), 'utf8')).deprecations : []) {
@@@ -63,7 +64,9 @@ const FULL_CHECK = {
'common.resource-name': (v) => contracts.resources.checkName(v), 'common.resource-control-request': (v) => contracts.resources.checkControlRequest(v),
'common.resource-control-result': (v) => contracts.resources.checkControlResult(v),
};
--for (const c of contracts.catalog) {
++// `fixtures/<id>/` is one set per contract id, checked against its newest major (a v1 file's fixtures
++// stay valid under an additive v2, and a v2-only example belongs there too).
++for (const c of [...new Map(contracts.catalog.map((e) => [e.id, e])).values()]) {
for (const kind of ['valid', 'invalid']) {
const dir = path.join(ROOT, 'fixtures', c.id, kind);
const list = fs.existsSync(dir) ? fs.readdirSync(dir).filter(f => f.endsWith('.json')) : [];
@@@ -76,6 +79,8 @@ for (const c of contracts.catalog) {
}
}
assert.throws(() => contracts.resolve('identity.subject-ref@2'), /not @2/);
++ok(contracts.resolve('ai.credential-put@1').schema === 'ai/credential-put.v1.json' && contracts.resolve('ai.credential-put@2').schema === 'ai/credential-put.v2.json'
++ && contracts.resolve('ai.credential-put').version === '2.0.0', 'a contract with two majors resolves by id (newest) or by major');
assert.throws(() => contracts.assertValid('identity.subject-ref', { type: 'user', id: '1' }), e => e.code === 'contract.invalid');
// ── Manifests ────────────────────────────────────────────────────────────