openvibers/openvibe.contracts Commits

Apps ask for consent per purpose at sign-in: the consent claim (0.310.0) (#318)

OP OpenVibers committed 6 days ago · ab0b0dea5a · 74 files, +2111 −114

* Apps ask for consent per purpose at sign-in: the consent claim (0.307.0)

An app adds one scope consent:<purpose> per purpose to /oauth/authorize. It may ask for
every purpose of the consent family except the retired ai.looks. network.consent-claim@1
is the consent claim of the tokens Network issues from that sign-in: exactly the purposes
granted to that app and active at issue, [] when none, never a refused purpose or another
app's grant. identity.service-token-claims@1 gains the optional consent claim.
network.me-consent-apps-result@1 is "Apps you allowed" on the account page.
network.consent.manage names its two new routes. The consent record and check descriptions
now say an app's grant is one record per purpose with the app as client.

* Merge main; consent scopes release as 0.310.0 (after pooled machines 0.309.0)

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex e25280af..52718095 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,14 @@
# Changelog
++## 0.310.0 — 2026-11-07
++
++- Apps ask for consent per purpose at sign-in (the consent plan's C4). An app adds one scope `consent:<purpose>` per purpose to /oauth/authorize, beside its other scopes. It may ask for every purpose of the consent family except the retired `ai.looks`: `ai.train`, `data.public`, `face.recognize` and `app.data`. Any other `consent:` scope is refused with `invalid_scope`. The consent screen lists each purpose in plain words, nothing ticked, and the person can sign in with every purpose refused. A refused purpose is not asked again by that app for 30 days, unless the person asks for it again on their account page.
++- Add `network.consent-claim@1`: the `consent` claim of the tokens Network issues from that sign-in (a first-party user token, a Dock token or an app token). It holds exactly the purposes the sign-in asked for that the person granted to that app and that are active when the token is issued, `[]` when none. It never holds a refused purpose or another app's grant, and the app learns only "not granted". The claim is absent when no consent scope was asked. Every new token works it out again, so a purpose turned off is gone from the next one.
++- `identity.service-token-claims@1` gains the optional `consent` claim (additive): an app token from the authorization-code flow carries it when the sign-in asked for consent scopes.
++- Add `network.me-consent-apps-result@1`: "Apps you allowed" on the account page (`GET /api/v1/me/consent/apps`), listing each app's granted purposes and the ones refused in the last 30 days (`quiet`, with the day it ends), with the words the app consent screen shows. `network.consent.manage` names that route and `DELETE /api/v1/me/consent/:purpose/:client/refusal`, which ends a refusal's quiet days so the app's next sign-in asks again.
++- `network.consent@1`: an app's grant is one record per purpose, with the app as `client` and `source` `oauth`. `client` is no longer only for `app.data`, and `options.purposes` is not written any more (kept so older records validate). `network.consent-check-request@1`'s `client` may name the app for any purpose: one app's grant never answers for OpenVibe itself or for another app. Descriptions only: no shape changes.
++- Valid and invalid fixtures cover a user token with one purpose, an app token with none and with all four, a retired, unknown, duplicated or missing claim, a claim that is not a list, an app's grant record, the account page with two apps and with none, a bad client id, a retired purpose, an extra key and a quiet refusal with no end.
++
## 0.309.0 — 2026-11-07
- Add `bot.machine-pool@1` (the pooled machines design, 2026-11-06): several of one owner's machines on the same local network that serve one model together through llama.cpp RPC (`rpc-server` on the members, `llama-server --rpc` on the head). OpenVibe.Network holds the record: the owner proposes it on /machines with two to eight of their own machines (the default head has the most free VRAM), and each machine reads it with its own node token and approves it on itself. A member's `certificate` is null until it approves; then it is that machine's self-signed Ed25519 public certificate, which the other members pin for the pool's private link. The private key stays on the machine: a certificate slot holding anything with `PRIVATE KEY` in it is refused, and so is any field the schema does not name. A member's `address` is a private RFC 1918 address and prefix (never 0.0.0.0, loopback or a public address) and its `public_ip` is the address Network saw it from. The rules a schema cannot say are written in the description and checked by Network and Node: the head is a member, every member is the owner's today, all share one public address and one private subnet, the head's direct link probe to each passes, every key is its own, and any change of membership, owner, head or key revokes the pool so every machine approves a new one.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex e9e6cded..791c0e4e 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -8323,6 +8323,24 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "network.consent-claim",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/consent-claim.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "network.me-consent-apps-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/me-consent-apps-result.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "network.consent-check-request",
"version": "1.0.0",
ddiff --git a/contracts/identity/service-token-claims.v1.json b/contracts/identity/service-token-claims.v1.json
iindex 9ee0d9b2..8a7b8f96 100644
---- a/contracts/identity/service-token-claims.v1.json
++++ b/contracts/identity/service-token-claims.v1.json
@@@ -92,6 +92,20 @@
"pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
"description": "The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Also present on a node token that asks for an audience where the machine acts for its owner (Agent Streams S1: openvibe.live and live.agent_stream.publish); absent on every other client_credentials token."
},
++ "consent": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "description": "App tokens from the authorization-code flow (0.310.0): the purposes the person (on_behalf_of) granted this app at sign-in and that are still active when the token is issued, the claim network.consent-claim@1 defines. Present when the sign-in asked for consent:<purpose> scopes, [] when none is granted, absent otherwise."
++ },
"act": {
"type": "object",
"required": [
ddiff --git a/contracts/network/consent-check-request.v1.json b/contracts/network/consent-check-request.v1.json
iindex b3925923..ad1b45c8 100644
---- a/contracts/network/consent-check-request.v1.json
++++ b/contracts/network/consent-check-request.v1.json
@@@ -18,7 +18,7 @@
"client": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "app.data only: the app asking. Required for app.data."
++ "description": "The app the use is for. Required for app.data. For any other purpose it asks about that app's own grant from its sign-in (consent:<purpose>, 0.310.0); without it the answer is about OpenVibe's own use. One app's grant never answers for OpenVibe or for another app."
},
"feature": {
"enum": ["verify", "find", "cast", "library"],
ddiff --git a/contracts/network/consent-check-result.v1.json b/contracts/network/consent-check-result.v1.json
iindex e25be4a8..b18cfd44 100644
---- a/contracts/network/consent-check-result.v1.json
++++ b/contracts/network/consent-check-result.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/consent-check-result.v1.json",
"title": "NetworkConsentCheckResult",
-- "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
++ "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An app's grant (client set) answers only a check that names that app. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
"type": "object",
"additionalProperties": false,
"required": ["purpose", "allowed", "checked_at"],
ddiff --git a/contracts/network/consent-claim.v1.json b/contracts/network/consent-claim.v1.json
nnew file mode 100644
iindex 00000000..df119495
---- /dev/null
++++ b/contracts/network/consent-claim.v1.json
@@@ -0,0 +1,18 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/consent-claim.v1.json",
++ "title": "NetworkConsentClaim",
++ "description": "network.consent-claim@1 (the consent plan's C4, 2026-11-07): the purposes a person granted to one app at sign-in, as the `consent` claim of the tokens OpenVibe.Network issues to that app. An app asks with one scope `consent:<purpose>` per purpose, beside its other scopes, in /oauth/authorize. The purposes an app may ask for are network.consent@1's except the retired ai.looks: ai.train, data.public, face.recognize and app.data. Any other `consent:` scope is refused with invalid_scope. The consent screen lists each purpose in plain words, off until the person ticks it, and the person can sign in with every purpose refused. A granted purpose is a network.consent@1 record with the app's OAuth client id as client and source oauth; the person turns it off on their account page (DELETE /api/v1/me/consent/:purpose/:client). A refused purpose is not asked again by that app for 30 days, unless the person asks for it again on their account page. The schema is the claims object of any token that carries the claim: a first-party user access token, a Dock token, or an app token (identity.service-token-claims@1).",
++ "type": "object",
++ "required": ["consent"],
++ "additionalProperties": true,
++ "properties": {
++ "consent": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": { "enum": ["ai.train", "data.public", "face.recognize", "app.data"] },
++ "description": "Exactly the purposes this sign-in asked for that the person has granted to this app and that are active when the token is issued, in network.consent@1's order. Never a refused or unasked purpose and never another app's grant. An empty list means none is granted: the app learns \"not granted\", never whether the person refused or was not asked. The claim is absent when the sign-in asked for no consent scope. Every new token works it out again, so a purpose the person turns off is gone from the next one (a refresh, or the next code); a token already issued keeps it until it expires. A service about to use a person's content for an app still asks POST /internal/consent/check with the app as client. The token response's `scope` lists consent:<purpose> only for the purposes granted."
++ }
++ }
++}
ddiff --git a/contracts/network/consent.v1.json b/contracts/network/consent.v1.json
iindex 5f191933..ba5f0f40 100644
---- a/contracts/network/consent.v1.json
++++ b/contracts/network/consent.v1.json
@@@ -35,7 +35,7 @@
"client": {
"type": ["string", "null"],
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of an app the person granted (required for app.data, and only there)."
++ "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of the app the person granted it to. app.data always names one. Since 0.310.0 an app may ask for any purpose at sign-in (consent:<purpose>, network.consent-claim@1): the grant is one record per purpose with this client and source oauth, and it allows that app only, never OpenVibe itself or another app."
},
"options": {
"type": "object",
@@@ -57,7 +57,7 @@
"maxItems": 4,
"uniqueItems": true,
"items": { "enum": ["ai.train", "data.public", "face.recognize", "ai.looks"] },
-- "description": "app.data only: what the app may do with the person's content, as the consent screen showed it."
++ "description": "app.data only, and not written since 0.310.0: an app's consent screen grants each purpose as its own record (client set), so a reader asks for the purpose itself. Kept so older records still validate."
}
},
"description": "What the purpose needs beyond on and kinds. An empty object for ai.train and ai.looks."
@@@ -69,7 +69,7 @@
},
"source": {
"enum": ["account", "oauth", "api", "import:vision"],
-- "description": "Where the choice was made: the account page, an app's consent screen, the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
++ "description": "Where the choice was made: the account page, an app's consent screen at sign-in (consent:<purpose>, with client set), the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
},
"granted_at": {
"type": "string",
ddiff --git a/contracts/network/me-consent-apps-result.v1.json b/contracts/network/me-consent-apps-result.v1.json
nnew file mode 100644
iindex 00000000..ad9d8a0a
---- /dev/null
++++ b/contracts/network/me-consent-apps-result.v1.json
@@@ -0,0 +1,75 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/me-consent-apps-result.v1.json",
++ "title": "NetworkMeConsentAppsResult",
++ "description": "network.me-consent-apps-result@1 (the consent plan's C4, 2026-11-07): \"Apps you allowed\" on the signed-in person's account page. GET /api/v1/me/consent/apps on OpenVibe.Network. It lists every app the person granted a purpose to on its sign-in screen (consent:<purpose>, network.consent-claim@1), with the purposes granted and those refused in the last 30 days. It also carries the words the app consent screen shows for each purpose. Only the person's own session reads it; an app never sees this list, its own entry included.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["purposes", "apps"],
++ "properties": {
++ "purposes": {
++ "type": "array",
++ "maxItems": 8,
++ "description": "The purposes an app may ask for, with the exact words the app consent screen shows.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["id", "title", "allows", "cannot_undo", "adults_only", "text_version"],
++ "properties": {
++ "id": { "enum": ["ai.train", "data.public", "face.recognize", "app.data"] },
++ "title": { "type": "string", "minLength": 1, "maxLength": 80 },
++ "allows": { "type": "string", "minLength": 1, "maxLength": 300, "description": "What ticking it allows the app, in one plain line." },
++ "cannot_undo": { "type": "string", "maxLength": 300, "description": "What turning it off later cannot undo (empty when nothing)." },
++ "adults_only": { "type": "boolean", "description": "Ticking it asks the person to confirm they are 18 or older." },
++ "text_version": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$" }
++ }
++ }
++ },
++ "apps": {
++ "type": "array",
++ "maxItems": 200,
++ "description": "One entry per app with a purpose granted or refused, apps with an active grant first, then by name.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["client", "name", "third_party", "granted", "quiet"],
++ "properties": {
++ "client": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$", "description": "The app's OAuth client id." },
++ "name": { "type": "string", "minLength": 1, "maxLength": 120, "description": "The app's name as its developer (or OpenVibe, for its own sites) registered it." },
++ "third_party": { "type": "boolean", "description": "True for a developer's app (app_<ULID>), false for an OpenVibe site." },
++ "granted": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "The purposes the app holds now (active network.consent@1 records with this client).",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["consent_id", "purpose", "granted_at", "text_version"],
++ "properties": {
++ "consent_id": { "type": "string", "pattern": "^cns_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "purpose": { "enum": ["ai.train", "data.public", "face.recognize", "app.data"] },
++ "granted_at": { "type": "string", "format": "date-time" },
++ "text_version": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$" }
++ }
++ }
++ },
++ "quiet": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "The purposes the person refused this app in the last 30 days: the app does not ask for them again until quiet_until. DELETE /api/v1/me/consent/:purpose/:client/refusal ends that early, so the next sign-in asks.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["purpose", "refused_at", "quiet_until"],
++ "properties": {
++ "purpose": { "enum": ["ai.train", "data.public", "face.recognize", "app.data"] },
++ "refused_at": { "type": "string", "format": "date-time" },
++ "quiet_until": { "type": "string", "format": "date-time" }
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/identity.service-token-claims/invalid/app-consent-retired-purpose.json b/fixtures/identity.service-token-claims/invalid/app-consent-retired-purpose.json
nnew file mode 100644
iindex 00000000..41685092
---- /dev/null
++++ b/fixtures/identity.service-token-claims/invalid/app-consent-retired-purpose.json
@@@ -0,0 +1,22 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "app:app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "actor_type": "app",
++ "aud": [
++ "openvibe.media"
++ ],
++ "cap": [],
++ "ns": [
++ "prj_01J0000000000000000000000Z",
++ "app.prj_01J0000000000000000000000Z.*"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_abcdef21",
++ "project_id": "prj_01J0000000000000000000000Z",
++ "env": "production",
++ "on_behalf_of": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "consent": [
++ "ai.looks"
++ ]
++}
ddiff --git a/fixtures/identity.service-token-claims/valid/app-consent-purposes.json b/fixtures/identity.service-token-claims/valid/app-consent-purposes.json
nnew file mode 100644
iindex 00000000..b4ca034f
---- /dev/null
++++ b/fixtures/identity.service-token-claims/valid/app-consent-purposes.json
@@@ -0,0 +1,23 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "app:app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "actor_type": "app",
++ "aud": [
++ "openvibe.media"
++ ],
++ "cap": [],
++ "ns": [
++ "prj_01J0000000000000000000000Z",
++ "app.prj_01J0000000000000000000000Z.*"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_abcdef21",
++ "project_id": "prj_01J0000000000000000000000Z",
++ "env": "production",
++ "on_behalf_of": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "consent": [
++ "ai.train",
++ "app.data"
++ ]
++}
ddiff --git a/fixtures/network.consent-claim/invalid/duplicate.json b/fixtures/network.consent-claim/invalid/duplicate.json
nnew file mode 100644
iindex 00000000..4cb5882e
---- /dev/null
++++ b/fixtures/network.consent-claim/invalid/duplicate.json
@@@ -0,0 +1,13 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "consent": [
++ "ai.train",
++ "ai.train"
++ ],
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent-claim/invalid/missing-claim.json b/fixtures/network.consent-claim/invalid/missing-claim.json
nnew file mode 100644
iindex 00000000..b93e953d
---- /dev/null
++++ b/fixtures/network.consent-claim/invalid/missing-claim.json
@@@ -0,0 +1,9 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent-claim/invalid/not-a-list.json b/fixtures/network.consent-claim/invalid/not-a-list.json
nnew file mode 100644
iindex 00000000..7eab6a8d
---- /dev/null
++++ b/fixtures/network.consent-claim/invalid/not-a-list.json
@@@ -0,0 +1,10 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "consent": "ai.train",
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent-claim/invalid/retired-purpose.json b/fixtures/network.consent-claim/invalid/retired-purpose.json
nnew file mode 100644
iindex 00000000..74cd343f
---- /dev/null
++++ b/fixtures/network.consent-claim/invalid/retired-purpose.json
@@@ -0,0 +1,12 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "consent": [
++ "ai.looks"
++ ],
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent-claim/invalid/unknown-purpose.json b/fixtures/network.consent-claim/invalid/unknown-purpose.json
nnew file mode 100644
iindex 00000000..39a1d4c4
---- /dev/null
++++ b/fixtures/network.consent-claim/invalid/unknown-purpose.json
@@@ -0,0 +1,12 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "consent": [
++ "data.sell"
++ ],
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent-claim/valid/app-token-all-four.json b/fixtures/network.consent-claim/valid/app-token-all-four.json
nnew file mode 100644
iindex 00000000..739aa197
---- /dev/null
++++ b/fixtures/network.consent-claim/valid/app-token-all-four.json
@@@ -0,0 +1,25 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "app:app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "actor_type": "app",
++ "aud": [
++ "openvibe.media"
++ ],
++ "cap": [],
++ "ns": [
++ "prj_01J0000000000000000000000Z",
++ "app.prj_01J0000000000000000000000Z.*"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_abcdef21",
++ "project_id": "prj_01J0000000000000000000000Z",
++ "env": "production",
++ "on_behalf_of": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "consent": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++}
ddiff --git a/fixtures/network.consent-claim/valid/app-token-none-granted.json b/fixtures/network.consent-claim/valid/app-token-none-granted.json
nnew file mode 100644
iindex 00000000..4693e8a9
---- /dev/null
++++ b/fixtures/network.consent-claim/valid/app-token-none-granted.json
@@@ -0,0 +1,20 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "app:app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "actor_type": "app",
++ "aud": [
++ "openvibe.media"
++ ],
++ "cap": [],
++ "ns": [
++ "prj_01J0000000000000000000000Z",
++ "app.prj_01J0000000000000000000000Z.*"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_abcdef21",
++ "project_id": "prj_01J0000000000000000000000Z",
++ "env": "production",
++ "on_behalf_of": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "consent": []
++}
ddiff --git a/fixtures/network.consent-claim/valid/user-token-one-granted.json b/fixtures/network.consent-claim/valid/user-token-one-granted.json
nnew file mode 100644
iindex 00000000..866259f7
---- /dev/null
++++ b/fixtures/network.consent-claim/valid/user-token-one-granted.json
@@@ -0,0 +1,12 @@
++{
++ "sub": "42",
++ "id": "42",
++ "subject_id": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "username": "ana",
++ "client_id": "openvibe-live",
++ "consent": [
++ "ai.train"
++ ],
++ "iat": 1790000000,
++ "exp": 1790003600
++}
ddiff --git a/fixtures/network.consent/valid/oauth-app-train.json b/fixtures/network.consent/valid/oauth-app-train.json
nnew file mode 100644
iindex 00000000..a1f6c761
---- /dev/null
++++ b/fixtures/network.consent/valid/oauth-app-train.json
@@@ -0,0 +1,14 @@
++{
++ "id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
++ "purpose": "ai.train",
++ "on": true,
++ "kinds": [],
++ "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "options": {},
++ "text_version": "2026-11-07.1",
++ "source": "oauth",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "revoked_at": null,
++ "expires_at": null
++}
ddiff --git a/fixtures/network.me-consent-apps-result/invalid/bad-client.json b/fixtures/network.me-consent-apps-result/invalid/bad-client.json
nnew file mode 100644
iindex 00000000..3a8ca78c
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/invalid/bad-client.json
@@@ -0,0 +1,55 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": [
++ {
++ "client": "not an app id!",
++ "name": "Clip Helper",
++ "third_party": true,
++ "granted": [
++ {
++ "consent_id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "purpose": "ai.train",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "quiet": [
++ {
++ "purpose": "face.recognize",
++ "refused_at": "2026-11-07T05:00:00.000Z",
++ "quiet_until": "2026-12-07T05:00:00.000Z"
++ }
++ ]
++ },
++ {
++ "client": "openvibe-live",
++ "name": "OpenVibe.Live",
++ "third_party": false,
++ "granted": [],
++ "quiet": [
++ {
++ "purpose": "data.public",
++ "refused_at": "2026-11-06T05:00:00.000Z",
++ "quiet_until": "2026-12-06T05:00:00.000Z"
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/fixtures/network.me-consent-apps-result/invalid/extra-key.json b/fixtures/network.me-consent-apps-result/invalid/extra-key.json
nnew file mode 100644
iindex 00000000..d86445c0
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/invalid/extra-key.json
@@@ -0,0 +1,58 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": [
++ {
++ "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "name": "Clip Helper",
++ "third_party": true,
++ "granted": [
++ {
++ "consent_id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "purpose": "ai.train",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "quiet": [
++ {
++ "purpose": "face.recognize",
++ "refused_at": "2026-11-07T05:00:00.000Z",
++ "quiet_until": "2026-12-07T05:00:00.000Z"
++ }
++ ],
++ "refused": [
++ "face.recognize"
++ ]
++ },
++ {
++ "client": "openvibe-live",
++ "name": "OpenVibe.Live",
++ "third_party": false,
++ "granted": [],
++ "quiet": [
++ {
++ "purpose": "data.public",
++ "refused_at": "2026-11-06T05:00:00.000Z",
++ "quiet_until": "2026-12-06T05:00:00.000Z"
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/fixtures/network.me-consent-apps-result/invalid/quiet-without-end.json b/fixtures/network.me-consent-apps-result/invalid/quiet-without-end.json
nnew file mode 100644
iindex 00000000..cbda35e8
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/invalid/quiet-without-end.json
@@@ -0,0 +1,54 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": [
++ {
++ "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "name": "Clip Helper",
++ "third_party": true,
++ "granted": [
++ {
++ "consent_id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "purpose": "ai.train",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "quiet": [
++ {
++ "purpose": "face.recognize",
++ "refused_at": "2026-11-07T05:00:00.000Z"
++ }
++ ]
++ },
++ {
++ "client": "openvibe-live",
++ "name": "OpenVibe.Live",
++ "third_party": false,
++ "granted": [],
++ "quiet": [
++ {
++ "purpose": "data.public",
++ "refused_at": "2026-11-06T05:00:00.000Z",
++ "quiet_until": "2026-12-06T05:00:00.000Z"
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/fixtures/network.me-consent-apps-result/invalid/retired-purpose.json b/fixtures/network.me-consent-apps-result/invalid/retired-purpose.json
nnew file mode 100644
iindex 00000000..e859047a
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/invalid/retired-purpose.json
@@@ -0,0 +1,55 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": [
++ {
++ "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "name": "Clip Helper",
++ "third_party": true,
++ "granted": [
++ {
++ "consent_id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "purpose": "ai.looks",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "quiet": [
++ {
++ "purpose": "face.recognize",
++ "refused_at": "2026-11-07T05:00:00.000Z",
++ "quiet_until": "2026-12-07T05:00:00.000Z"
++ }
++ ]
++ },
++ {
++ "client": "openvibe-live",
++ "name": "OpenVibe.Live",
++ "third_party": false,
++ "granted": [],
++ "quiet": [
++ {
++ "purpose": "data.public",
++ "refused_at": "2026-11-06T05:00:00.000Z",
++ "quiet_until": "2026-12-06T05:00:00.000Z"
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/fixtures/network.me-consent-apps-result/valid/none.json b/fixtures/network.me-consent-apps-result/valid/none.json
nnew file mode 100644
iindex 00000000..d9975fc5
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/valid/none.json
@@@ -0,0 +1,21 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": []
++}
ddiff --git a/fixtures/network.me-consent-apps-result/valid/two-apps.json b/fixtures/network.me-consent-apps-result/valid/two-apps.json
nnew file mode 100644
iindex 00000000..6ae7fdf2
---- /dev/null
++++ b/fixtures/network.me-consent-apps-result/valid/two-apps.json
@@@ -0,0 +1,55 @@
++{
++ "purposes": [
++ {
++ "id": "ai.train",
++ "title": "Train AI on your content",
++ "allows": "It may use your streams, videos, voice, chat, posts, pictures and code to train or improve its AI.",
++ "cannot_undo": "A model it already trained keeps what it learned.",
++ "adults_only": true,
++ "text_version": "2026-11-07.1"
++ },
++ {
++ "id": "app.data",
++ "title": "Use your content",
++ "allows": "It may read and use your streams, videos, posts and other content for its own features.",
++ "cannot_undo": "A copy it already made stays with the app.",
++ "adults_only": false,
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "apps": [
++ {
++ "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
++ "name": "Clip Helper",
++ "third_party": true,
++ "granted": [
++ {
++ "consent_id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
++ "purpose": "ai.train",
++ "granted_at": "2026-11-07T05:00:00.000Z",
++ "text_version": "2026-11-07.1"
++ }
++ ],
++ "quiet": [
++ {
++ "purpose": "face.recognize",
++ "refused_at": "2026-11-07T05:00:00.000Z",
++ "quiet_until": "2026-12-07T05:00:00.000Z"
++ }
++ ]
++ },
++ {
++ "client": "openvibe-live",
++ "name": "OpenVibe.Live",
++ "third_party": false,
++ "granted": [],
++ "quiet": [
++ {
++ "purpose": "data.public",
++ "refused_at": "2026-11-06T05:00:00.000Z",
++ "quiet_until": "2026-12-06T05:00:00.000Z"
++ }
++ ]
++ }
++ ]
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex da998509..c1907169 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -277,6 +277,20 @@
"pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
"description": "The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Also present on a node token that asks for an audience where the machine acts for its owner (Agent Streams S1: openvibe.live and live.agent_stream.publish); absent on every other client_credentials token."
},
++ "consent": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "description": "App tokens from the authorization-code flow (0.310.0): the purposes the person (on_behalf_of) granted this app at sign-in and that are still active when the token is issued, the claim network.consent-claim@1 defines. Present when the sign-in asked for consent:<purpose> scopes, [] when none is granted, absent otherwise."
++ },
"act": {
"type": "object",
"required": [
@@@ -92745,7 +92759,7 @@
"null"
],
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of an app the person granted (required for app.data, and only there)."
++ "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of the app the person granted it to. app.data always names one. Since 0.310.0 an app may ask for any purpose at sign-in (consent:<purpose>, network.consent-claim@1): the grant is one record per purpose with this client and source oauth, and it allows that app only, never OpenVibe itself or another app."
},
"options": {
"type": "object",
@@@ -92784,7 +92798,7 @@
"ai.looks"
]
},
-- "description": "app.data only: what the app may do with the person's content, as the consent screen showed it."
++ "description": "app.data only, and not written since 0.310.0: an app's consent screen grants each purpose as its own record (client set), so a reader asks for the purpose itself. Kept so older records still validate."
}
},
"description": "What the purpose needs beyond on and kinds. An empty object for ai.train and ai.looks."
@@@ -92801,7 +92815,7 @@
"api",
"import:vision"
],
-- "description": "Where the choice was made: the account page, an app's consent screen, the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
++ "description": "Where the choice was made: the account page, an app's consent screen at sign-in (consent:<purpose>, with client set), the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
},
"granted_at": {
"type": "string",
@@@ -93068,6 +93082,210 @@
}
}
},
++ "network.consent-claim": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/consent-claim.v1.json",
++ "title": "NetworkConsentClaim",
++ "description": "network.consent-claim@1 (the consent plan's C4, 2026-11-07): the purposes a person granted to one app at sign-in, as the `consent` claim of the tokens OpenVibe.Network issues to that app. An app asks with one scope `consent:<purpose>` per purpose, beside its other scopes, in /oauth/authorize. The purposes an app may ask for are network.consent@1's except the retired ai.looks: ai.train, data.public, face.recognize and app.data. Any other `consent:` scope is refused with invalid_scope. The consent screen lists each purpose in plain words, off until the person ticks it, and the person can sign in with every purpose refused. A granted purpose is a network.consent@1 record with the app's OAuth client id as client and source oauth; the person turns it off on their account page (DELETE /api/v1/me/consent/:purpose/:client). A refused purpose is not asked again by that app for 30 days, unless the person asks for it again on their account page. The schema is the claims object of any token that carries the claim: a first-party user access token, a Dock token, or an app token (identity.service-token-claims@1).",
++ "type": "object",
++ "required": [
++ "consent"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "consent": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "description": "Exactly the purposes this sign-in asked for that the person has granted to this app and that are active when the token is issued, in network.consent@1's order. Never a refused or unasked purpose and never another app's grant. An empty list means none is granted: the app learns \"not granted\", never whether the person refused or was not asked. The claim is absent when the sign-in asked for no consent scope. Every new token works it out again, so a purpose the person turns off is gone from the next one (a refresh, or the next code); a token already issued keeps it until it expires. A service about to use a person's content for an app still asks POST /internal/consent/check with the app as client. The token response's `scope` lists consent:<purpose> only for the purposes granted."
++ }
++ }
++ }
++ },
++ "network.me-consent-apps-result": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/me-consent-apps-result.v1.json",
++ "title": "NetworkMeConsentAppsResult",
++ "description": "network.me-consent-apps-result@1 (the consent plan's C4, 2026-11-07): \"Apps you allowed\" on the signed-in person's account page. GET /api/v1/me/consent/apps on OpenVibe.Network. It lists every app the person granted a purpose to on its sign-in screen (consent:<purpose>, network.consent-claim@1), with the purposes granted and those refused in the last 30 days. It also carries the words the app consent screen shows for each purpose. Only the person's own session reads it; an app never sees this list, its own entry included.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "purposes",
++ "apps"
++ ],
++ "properties": {
++ "purposes": {
++ "type": "array",
++ "maxItems": 8,
++ "description": "The purposes an app may ask for, with the exact words the app consent screen shows.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "title",
++ "allows",
++ "cannot_undo",
++ "adults_only",
++ "text_version"
++ ],
++ "properties": {
++ "id": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "allows": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 300,
++ "description": "What ticking it allows the app, in one plain line."
++ },
++ "cannot_undo": {
++ "type": "string",
++ "maxLength": 300,
++ "description": "What turning it off later cannot undo (empty when nothing)."
++ },
++ "adults_only": {
++ "type": "boolean",
++ "description": "Ticking it asks the person to confirm they are 18 or older."
++ },
++ "text_version": {
++ "type": "string",
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$"
++ }
++ }
++ }
++ },
++ "apps": {
++ "type": "array",
++ "maxItems": 200,
++ "description": "One entry per app with a purpose granted or refused, apps with an active grant first, then by name.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "client",
++ "name",
++ "third_party",
++ "granted",
++ "quiet"
++ ],
++ "properties": {
++ "client": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
++ "description": "The app's OAuth client id."
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120,
++ "description": "The app's name as its developer (or OpenVibe, for its own sites) registered it."
++ },
++ "third_party": {
++ "type": "boolean",
++ "description": "True for a developer's app (app_<ULID>), false for an OpenVibe site."
++ },
++ "granted": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "The purposes the app holds now (active network.consent@1 records with this client).",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "consent_id",
++ "purpose",
++ "granted_at",
++ "text_version"
++ ],
++ "properties": {
++ "consent_id": {
++ "type": "string",
++ "pattern": "^cns_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "purpose": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "granted_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "text_version": {
++ "type": "string",
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$"
++ }
++ }
++ }
++ },
++ "quiet": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "The purposes the person refused this app in the last 30 days: the app does not ask for them again until quiet_until. DELETE /api/v1/me/consent/:purpose/:client/refusal ends that early, so the next sign-in asks.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "purpose",
++ "refused_at",
++ "quiet_until"
++ ],
++ "properties": {
++ "purpose": {
++ "enum": [
++ "ai.train",
++ "data.public",
++ "face.recognize",
++ "app.data"
++ ]
++ },
++ "refused_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "quiet_until": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"network.consent-check-request": {
"version": "1.0.0",
"owner": "network",
@@@ -93110,7 +93328,7 @@
"client": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "app.data only: the app asking. Required for app.data."
++ "description": "The app the use is for. Required for app.data. For any other purpose it asks about that app's own grant from its sign-in (consent:<purpose>, 0.310.0); without it the answer is about OpenVibe's own use. One app's grant never answers for OpenVibe or for another app."
},
"feature": {
"enum": [
@@@ -93143,7 +93361,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/network/consent-check-result.v1.json",
"title": "NetworkConsentCheckResult",
-- "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
++ "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An app's grant (client set) answers only a check that names that app. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
"type": "object",
"additionalProperties": false,
"required": [
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex e1c5ec35..baa9d8ef 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.309.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 81e38e5c..9caaf4dd 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.309.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 646c19a7..55262122 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.309.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 9f622fee..9b8f70ab 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.309.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 2014f3d2..ade4d6e6 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.309.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 f3570aca..96a147fa 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.309.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 12c74e87..b67997df 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.309.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 b069241d..4735b9fd 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.309.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 fd26a853..07d61f5a 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.309.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 c14a6a38..c95b1c35 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.309.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 ef159b84..83136a03 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.309.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 b04e788e..778dc1d8 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.309.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 cab5bce0..c58f5142 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.309.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 d59f42c4..1187774a 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.309.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 fd29edba..9c62a75f 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.309.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 a667cc39..315b4bdd 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.309.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.310.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 50763bd8..422b2da7 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -145,7 +145,7 @@
"service": "network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
-- "operations": 60,
++ "operations": 62,
"capabilities": 35
},
{
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 29db81b8..38242146 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.309.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 99641bc7..8df5b653 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.309.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 a1893412..aaaaa780 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.309.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 cfc9fee0..a00a89b2 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.309.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 afc6c53e..d77740bb 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.309.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.310.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"
@@@ -658,7 +658,7 @@
"get": {
"operationId": "get_api_v1_me_consent",
"summary": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or",
-- "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client revokes one app. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
"tags": [
"network.consent"
],
@@@ -790,7 +790,7 @@
"put": {
"operationId": "put_api_v1_me_consent_purpose",
"summary": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or",
-- "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client revokes one app. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
"tags": [
"network.consent"
],
@@@ -856,7 +856,7 @@
"delete": {
"operationId": "delete_api_v1_me_consent_purpose_client",
"summary": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or",
-- "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client revokes one app. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
"tags": [
"network.consent"
],
@@@ -1000,6 +1000,286 @@
}
}
},
++ "/api/v1/me/consent/{purpose}/{client}/refusal": {
++ "delete": {
++ "operationId": "delete_api_v1_me_consent_purpose_client_refusal",
++ "summary": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or",
++ "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "tags": [
++ "network.consent"
++ ],
++ "x-openvibe-capabilities": [
++ "network.consent.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.consent.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/network.consent-choice.v1"
++ },
++ "parameters": [
++ {
++ "name": "purpose",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "client",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "on",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "boolean"
++ }
++ },
++ {
++ "name": "kinds",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "streams",
++ "vods",
++ "clips",
++ "voice",
++ "chat",
++ "posts",
++ "pictures",
++ "code"
++ ]
++ }
++ }
++ },
++ {
++ "name": "options",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "licence": {
++ "enum": [
++ "cc-by-4.0",
++ "cc0-1.0"
++ ]
++ },
++ "features": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "verify",
++ "find",
++ "cast",
++ "library"
++ ]
++ }
++ }
++ }
++ }
++ },
++ {
++ "name": "text_version",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$",
++ "description": "The wording the page showed. Required to turn on; a stale one is refused (consent.stale_text) so the page shows the new words first."
++ },
++ "description": "The wording the page showed. Required to turn on; a stale one is refused (consent.stale_text) so the page shows the new words first."
++ },
++ {
++ "name": "adult",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "description": "The person confirmed they are 18 or older. Required (true) to turn on face.recognize, ai.looks, ai.train or data.public."
++ },
++ "description": "The person confirmed they are 18 or older. Required (true) to turn on face.recognize, ai.looks, ai.train or data.public."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.me-consent-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/me/consent/apps": {
++ "get": {
++ "operationId": "get_api_v1_me_consent_apps",
++ "summary": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or",
++ "description": "**network.consent.manage** (public): \"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "tags": [
++ "network.consent"
++ ],
++ "x-openvibe-capabilities": [
++ "network.consent.manage"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "network.consent.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/network.consent-choice.v1"
++ },
++ "parameters": [
++ {
++ "name": "on",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "boolean"
++ }
++ },
++ {
++ "name": "kinds",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 8,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "streams",
++ "vods",
++ "clips",
++ "voice",
++ "chat",
++ "posts",
++ "pictures",
++ "code"
++ ]
++ }
++ }
++ },
++ {
++ "name": "options",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "licence": {
++ "enum": [
++ "cc-by-4.0",
++ "cc0-1.0"
++ ]
++ },
++ "features": {
++ "type": "array",
++ "maxItems": 4,
++ "uniqueItems": true,
++ "items": {
++ "enum": [
++ "verify",
++ "find",
++ "cast",
++ "library"
++ ]
++ }
++ }
++ }
++ }
++ },
++ {
++ "name": "text_version",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.[0-9]{1,3}$",
++ "description": "The wording the page showed. Required to turn on; a stale one is refused (consent.stale_text) so the page shows the new words first."
++ },
++ "description": "The wording the page showed. Required to turn on; a stale one is refused (consent.stale_text) so the page shows the new words first."
++ },
++ {
++ "name": "adult",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "description": "The person confirmed they are 18 or older. Required (true) to turn on face.recognize, ai.looks, ai.train or data.public."
++ },
++ "description": "The person confirmed they are 18 or older. Required (true) to turn on face.recognize, ai.looks, ai.train or data.public."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/network.me-consent-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/node/self/capabilities": {
"put": {
"operationId": "put_api_v1_node_self_capabilities",
@@@ -4735,7 +5015,7 @@
"client": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "app.data only: the app asking. Required for app.data."
++ "description": "The app the use is for. Required for app.data. For any other purpose it asks about that app's own grant from its sign-in (consent:<purpose>, 0.310.0); without it the answer is about OpenVibe's own use. One app's grant never answers for OpenVibe or for another app."
},
"feature": {
"enum": [
@@@ -4761,7 +5041,7 @@
},
"network.consent-check-result.v1": {
"title": "NetworkConsentCheckResult",
-- "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
++ "description": "network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An app's grant (client set) answers only a check that names that app. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -4967,7 +5247,7 @@
"null"
],
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$",
-- "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of an app the person granted (required for app.data, and only there)."
++ "description": "The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of the app the person granted it to. app.data always names one. Since 0.310.0 an app may ask for any purpose at sign-in (consent:<purpose>, network.consent-claim@1): the grant is one record per purpose with this client and source oauth, and it allows that app only, never OpenVibe itself or another app."
},
"options": {
"type": "object",
@@@ -5006,7 +5286,7 @@
"ai.looks"
]
},
-- "description": "app.data only: what the app may do with the person's content, as the consent screen showed it."
++ "description": "app.data only, and not written since 0.310.0: an app's consent screen grants each purpose as its own record (client set), so a reader asks for the purpose itself. Kept so older records still validate."
}
},
"description": "What the purpose needs beyond on and kinds. An empty object for ai.train and ai.looks."
@@@ -5023,7 +5303,7 @@
"api",
"import:vision"
],
-- "description": "Where the choice was made: the account page, an app's consent screen, the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
++ "description": "Where the choice was made: the account page, an app's consent screen at sign-in (consent:<purpose>, with client set), the person's own API call, or OpenVibe.Vision's older face consents moved into Network."
},
"granted_at": {
"type": "string",
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex b014ef0c..6016b383 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.309.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 4bc3b3a7..8b365167 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.309.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 385a907d..d1db4c61 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.309.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 61ca3ca3..99870493 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.309.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.310.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/report.json b/generated/openapi/report.json
iindex 18b09a49..3ec91fb0 100644
---- a/generated/openapi/report.json
++++ b/generated/openapi/report.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Report API",
-- "version": "0.309.0",
-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 2b26c947..217e68dd 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.309.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 ed2fd1ee..b7de26c6 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.309.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 b99f1022..19159612 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.309.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 3139c836..62b0c79e 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.309.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 dcae7708..f904a5ec 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.309.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 a54364e7..b3878b6e 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.309.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 8ab0a87d..5d1610cc 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.309.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.310.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/today.json b/generated/openapi/today.json
iindex 353bfbfe..1fac479a 100644
---- a/generated/openapi/today.json
++++ b/generated/openapi/today.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Today API",
-- "version": "0.309.0",
-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 d4d3ce12..62251317 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.309.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 c32a1946..78b69b46 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.309.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 510d92b7..397b1336 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.309.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 6fe0bc95..ac6fe2ba 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.309.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 4c81c299..ff234d70 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.309.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 15a65e5f..3b7fc515 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.309.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 307abd39..014de86a 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.309.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 a27daabc..c171c7b3 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.309.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.309.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.310.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.310.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 900efc4e..f8e17ba0 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -95,6 +95,29 @@ export type ServiceTokenClaims = {
* The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Also present on a node token that asks for an audience where the machine acts for its owner (Agent Streams S1: openvibe.live and live.agent_stream.publish); absent on every other client_credentials token.
*/
on_behalf_of?: string;
++ /**
++ * App tokens from the authorization-code flow (0.310.0): the purposes the person (on_behalf_of) granted this app at sign-in and that are still active when the token is issued, the claim network.consent-claim@1 defines. Present when the sign-in asked for consent:<purpose> scopes, [] when none is granted, absent otherwise.
++ *
++ * @maxItems 4
++ */
++ consent?:
++ | []
++ | ["ai.train" | "data.public" | "face.recognize" | "app.data"]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ];
/**
* Agent tokens: the host that runs the agent and the token was minted for (RFC 8693 actor claim), a service or an app.
*/
@@@ -272002,7 +272025,7 @@ export interface NetworkConsent {
"streams" | "vods" | "clips" | "voice" | "chat" | "posts" | "pictures" | "code"
];
/**
-- * The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of an app the person granted (required for app.data, and only there).
++ * The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of the app the person granted it to. app.data always names one. Since 0.310.0 an app may ask for any purpose at sign-in (consent:<purpose>, network.consent-claim@1): the grant is one record per purpose with this client and source oauth, and it allows that app only, never OpenVibe itself or another app.
*/
client: string | null;
/**
@@@ -272034,7 +272057,7 @@ export interface NetworkConsent {
"verify" | "find" | "cast" | "library"
];
/**
-- * app.data only: what the app may do with the person's content, as the consent screen showed it.
++ * app.data only, and not written since 0.310.0: an app's consent screen grants each purpose as its own record (client set), so a reader asks for the purpose itself. Kept so older records still validate.
*
* @maxItems 4
*/
@@@ -272062,7 +272085,7 @@ export interface NetworkConsent {
*/
text_version: string;
/**
-- * Where the choice was made: the account page, an app's consent screen, the person's own API call, or OpenVibe.Vision's older face consents moved into Network.
++ * Where the choice was made: the account page, an app's consent screen at sign-in (consent:<purpose>, with client set), the person's own API call, or OpenVibe.Vision's older face consents moved into Network.
*/
source: "account" | "oauth" | "api" | "import:vision";
/**
@@@ -282846,6 +282869,838 @@ export interface NetworkMeConsentResult {
}[];
}
++/** [email protected] (owner: network) */
++/**
++ * network.consent-claim@1 (the consent plan's C4, 2026-11-07): the purposes a person granted to one app at sign-in, as the `consent` claim of the tokens OpenVibe.Network issues to that app. An app asks with one scope `consent:<purpose>` per purpose, beside its other scopes, in /oauth/authorize. The purposes an app may ask for are network.consent@1's except the retired ai.looks: ai.train, data.public, face.recognize and app.data. Any other `consent:` scope is refused with invalid_scope. The consent screen lists each purpose in plain words, off until the person ticks it, and the person can sign in with every purpose refused. A granted purpose is a network.consent@1 record with the app's OAuth client id as client and source oauth; the person turns it off on their account page (DELETE /api/v1/me/consent/:purpose/:client). A refused purpose is not asked again by that app for 30 days, unless the person asks for it again on their account page. The schema is the claims object of any token that carries the claim: a first-party user access token, a Dock token, or an app token (identity.service-token-claims@1).
++ */
++export interface NetworkConsentClaim {
++ /**
++ * Exactly the purposes this sign-in asked for that the person has granted to this app and that are active when the token is issued, in network.consent@1's order. Never a refused or unasked purpose and never another app's grant. An empty list means none is granted: the app learns "not granted", never whether the person refused or was not asked. The claim is absent when the sign-in asked for no consent scope. Every new token works it out again, so a purpose the person turns off is gone from the next one (a refresh, or the next code); a token already issued keeps it until it expires. A service about to use a person's content for an app still asks POST /internal/consent/check with the app as client. The token response's `scope` lists consent:<purpose> only for the purposes granted.
++ *
++ * @maxItems 4
++ */
++ consent:
++ | []
++ | ["ai.train" | "data.public" | "face.recognize" | "app.data"]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ]
++ | [
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data",
++ "ai.train" | "data.public" | "face.recognize" | "app.data"
++ ];
++ [k: string]: unknown | undefined;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * network.me-consent-apps-result@1 (the consent plan's C4, 2026-11-07): "Apps you allowed" on the signed-in person's account page. GET /api/v1/me/consent/apps on OpenVibe.Network. It lists every app the person granted a purpose to on its sign-in screen (consent:<purpose>, network.consent-claim@1), with the purposes granted and those refused in the last 30 days. It also carries the words the app consent screen shows for each purpose. Only the person's own session reads it; an app never sees this list, its own entry included.
++ */
++export interface NetworkMeConsentAppsResult {
++ /**
++ * The purposes an app may ask for, with the exact words the app consent screen shows.
++ *
++ * @maxItems 8
++ */
++ purposes:
++ | []
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ },
++ {
++ id: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ title: string;
++ /**
++ * What ticking it allows the app, in one plain line.
++ */
++ allows: string;
++ /**
++ * What turning it off later cannot undo (empty when nothing).
++ */
++ cannot_undo: string;
++ /**
++ * Ticking it asks the person to confirm they are 18 or older.
++ */
++ adults_only: boolean;
++ text_version: string;
++ }
++ ];
++ /**
++ * One entry per app with a purpose granted or refused, apps with an active grant first, then by name.
++ *
++ * @maxItems 200
++ */
++ apps: {
++ /**
++ * The app's OAuth client id.
++ */
++ client: string;
++ /**
++ * The app's name as its developer (or OpenVibe, for its own sites) registered it.
++ */
++ name: string;
++ /**
++ * True for a developer's app (app_<ULID>), false for an OpenVibe site.
++ */
++ third_party: boolean;
++ /**
++ * The purposes the app holds now (active network.consent@1 records with this client).
++ *
++ * @maxItems 4
++ */
++ granted:
++ | []
++ | [
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ }
++ ]
++ | [
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ },
++ {
++ consent_id: string;
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ granted_at: string;
++ text_version: string;
++ }
++ ];
++ /**
++ * The purposes the person refused this app in the last 30 days: the app does not ask for them again until quiet_until. DELETE /api/v1/me/consent/:purpose/:client/refusal ends that early, so the next sign-in asks.
++ *
++ * @maxItems 4
++ */
++ quiet:
++ | []
++ | [
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ }
++ ]
++ | [
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ }
++ ]
++ | [
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ }
++ ]
++ | [
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ },
++ {
++ purpose: "ai.train" | "data.public" | "face.recognize" | "app.data";
++ refused_at: string;
++ quiet_until: string;
++ }
++ ];
++ }[];
++}
++
/** [email protected] (owner: network) */
/**
* network.consent-check-request@1 (L24): may this purpose be used for these people? POST /internal/consent/check on OpenVibe.Network with a service token holding network.consent.read. One purpose per call, up to 500 subjects (a dataset build or a chat log checks its authors in batches).
@@@ -282860,7 +283715,7 @@ export interface NetworkConsentCheckRequest {
*/
kind?: "streams" | "vods" | "clips" | "voice" | "chat" | "posts" | "pictures" | "code";
/**
-- * app.data only: the app asking. Required for app.data.
++ * The app the use is for. Required for app.data. For any other purpose it asks about that app's own grant from its sign-in (consent:<purpose>, 0.310.0); without it the answer is about OpenVibe's own use. One app's grant never answers for OpenVibe or for another app.
*/
client?: string;
/**
@@@ -282878,7 +283733,7 @@ export interface NetworkConsentCheckRequest {
/** [email protected] (owner: network) */
/**
-- * network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.
++ * network.consent-check-result@1 (L24): the answer to network.consent-check-request@1. Every subject asked about is in allowed: true only for an active choice that covers the purpose, the kind, the app and the feature asked about. An app's grant (client set) answers only a check that names that app. An unknown or deleted subject is false, the same as a person who never chose (the answer never says whether an account exists). A service caches an answer for at most 60 s and drops it on network.consent.changed; when Network cannot answer, every subject is false.
*/
export interface NetworkConsentCheckResult {
purpose: "ai.train" | "data.public" | "face.recognize" | "ai.looks" | "app.data";
ddiff --git a/manifests/capabilities/network.consent.manage.json b/manifests/capabilities/network.consent.manage.json
iindex b64bad19..e4b095b0 100644
---- a/manifests/capabilities/network.consent.manage.json
++++ b/manifests/capabilities/network.consent.manage.json
@@@ -4,7 +4,7 @@
"owner": "network",
"status": "active",
"visibility": "public",
-- "description": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client revokes one app. Off is the default for every purpose. Only the person's own session or token reaches it.",
++ "description": "\"Your data choices\" for the signed-in person (L24): GET /api/v1/me/consent lists every purpose with its wording, the person's choices and their history; PUT /api/v1/me/consent/:purpose turns one on or off (network.consent-choice@1); DELETE /api/v1/me/consent/:purpose/:client turns one app's purpose off. \"Apps you allowed\" (0.310.0): GET /api/v1/me/consent/apps lists the purposes each app was granted or refused at sign-in (network.me-consent-apps-result@1), and DELETE /api/v1/me/consent/:purpose/:client/refusal ends a refusal's 30 quiet days so that app's next sign-in asks again. Off is the default for every purpose. Only the person's own session or token reaches it.",
"inputSchema": "network.consent-choice@1",
"outputSchema": "network.me-consent-result@1",
"permissions": [
@@@ -21,6 +21,8 @@
"implementedBy": [
"GET /api/v1/me/consent",
"PUT /api/v1/me/consent/:purpose",
-- "DELETE /api/v1/me/consent/:purpose/:client"
++ "DELETE /api/v1/me/consent/:purpose/:client",
++ "GET /api/v1/me/consent/apps",
++ "DELETE /api/v1/me/consent/:purpose/:client/refusal"
]
}
ddiff --git a/package-lock.json b/package-lock.json
iindex f7f3f8e5..742cef93 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.309.0",
++ "version": "0.310.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.309.0",
++ "version": "0.310.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex ee2d9cbf..449dc993 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.309.0",
++ "version": "0.310.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/test/network-consent.test.js b/test/network-consent.test.js
iindex 0d6cccfc..f01e6b53 100644
---- a/test/network-consent.test.js
++++ b/test/network-consent.test.js
@@@ -57,6 +57,44 @@ for (const id of ['network.consent', 'network.consent-choice', 'network.me-conse
}
}
++// Apps ask per purpose at sign-in (C4, 0.310.0): consent:<purpose> scopes and the `consent` claim. An app may ask for
++// every purpose of the family except the retired ai.looks, and the claim, the app token's claim and the account page's
++// "Apps you allowed" all list that same set.
++const APP_PURPOSES = PURPOSES.filter((p) => p !== 'ai.looks');
++const claim = schema('network/consent-claim.v1.json');
++const tokenClaims = schema('identity/service-token-claims.v1.json');
++const apps = schema('network/me-consent-apps-result.v1.json');
++for (const [name, list] of [
++ ['claim.consent', claim.properties.consent.items.enum],
++ ['service-token.consent', tokenClaims.properties.consent.items.enum],
++ ['apps.purposes.id', apps.properties.purposes.items.properties.id.enum],
++ ['apps.granted.purpose', apps.properties.apps.items.properties.granted.items.properties.purpose.enum],
++ ['apps.quiet.purpose', apps.properties.apps.items.properties.quiet.items.properties.purpose.enum],
++]) assert.deepStrictEqual(list, APP_PURPOSES, `${name} lists the purposes an app may ask for`);
++assert.strictEqual(claim.properties.consent.uniqueItems, true, 'a purpose is in the claim once');
++assert.strictEqual(claim.properties.consent.maxItems, APP_PURPOSES.length);
++// The claim carries what was granted, nothing about what was refused.
++assert.deepStrictEqual(Object.keys(claim.properties), ['consent'], 'the claim names granted purposes only');
++assert.ok(!('refused' in apps.properties.apps.items.properties), 'the account page calls a refusal quiet, with its end');
++assert.strictEqual(apps.properties.apps.items.additionalProperties, false);
++// An app's grant is a consent record like any other: client set, source oauth.
++const appGrant = fx('network.consent', 'valid', 'oauth-app-train.json');
++assert.strictEqual(appGrant.source, 'oauth');
++assert.ok(appGrant.client && appGrant.purpose !== 'app.data', 'an app holds a purpose other than app.data under its own client id');
++// The account page reaches the new routes through the same capability.
++const manage = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'capabilities', 'network.consent.manage.json'), 'utf8'));
++for (const route of ['GET /api/v1/me/consent/apps', 'DELETE /api/v1/me/consent/:purpose/:client/refusal']) {
++ assert.ok(manage.implementedBy.includes(route), `network.consent.manage names ${route}`);
++}
++for (const id of ['network.consent-claim', 'network.me-consent-apps-result']) {
++ for (const kind of ['valid', 'invalid']) {
++ for (const f of fs.readdirSync(path.join(ROOT, 'fixtures', id, kind))) {
++ const r = c.validate(`${id}@1`, fx(id, kind, f));
++ assert.strictEqual(r.valid, kind === 'valid', `${id} ${kind}/${f}: ${JSON.stringify(r.errors)}`);
++ }
++ }
++}
++
// The change event names the change, never the content it covers.
assert.strictEqual(changed.additionalProperties, false);
assert.ok(!('content' in changed.properties) && !('text' in changed.properties), 'the event carries no content');
@@@ -74,4 +112,4 @@ const network = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'service
assert.ok(network.capabilities.includes('network.consent.read') && network.capabilities.includes('network.consent.manage'));
assert.ok(network.eventsProduced.includes('network.consent.changed'));
--console.log('network.consent@1: one list of purposes and kinds, cns_ ids, no selling purpose, fixtures, capabilities');
++console.log('network.consent@1: one list of purposes and kinds, cns_ ids, no selling purpose, the consent claim and Apps you allowed, fixtures, capabilities');