openvibers/openvibe.contracts Commits

0.315.0: the seven shipped config.manage capabilities and the AI editor review capabilities (#323)

OP OpenVibers committed 6 days ago · 45690ecd4c · 70 files, +8387 −341

* 0.315.0: the seven shipped config.manage capabilities and the AI editor review capabilities

List chat, connect, deals, work, sources, design and domains config.manage in
their service manifests and turn them active now that each mounts
openvibe-shared/config's /internal/config routes; Media stays planned.

Add blog.review.ai_editor, news.review.ai_editor and wiki.review.ai_editor as
proposed by Blog, News and Wiki, granted only to the orchestrator's service
principal. Generated output refreshed.

* Merge remote-tracking branch 'origin/main' into cfgm/manifests-and-review-caps

# Conflicts:
#	CHANGELOG.md
#	package.json

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 27646d1c..238b2781 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,11 @@
# Changelog
++## 0.315.0 — 2026-11-07
++
++- The owner's Setup tab reaches seven more services: `<service>.config.manage` for `chat`, `connect`, `deals`, `work`, `sources`, `design` and `domains` is now **active** and listed in each service's manifest, because each mounts openvibe-shared/config's routes on `/internal/config` (`GET /internal/config`, `GET|POST /internal/config/:namespace`, `PUT|DELETE /internal/config/:namespace/secrets/:key`, `POST /internal/config/:namespace/test/:key`). Only Network's admin proxy, acting for the owner, holds them and they are never grantable to apps. `media.config.manage` stays planned until Media mounts the routes.
++- Add `blog.review.ai_editor`, `news.review.ai_editor` and `wiki.review.ai_editor`: the editorial queue's AI editor decision (approved or rejected; approved or needs changes on Wiki) recorded by the first-party service principal acting as itself (a `svc:` principal with no `X-OV-Subject`) through Blog's `POST /api/v1/posts/:id/reviews`, News's `POST /api/v1/stories/:id/reviews` and `POST /api/v1/briefs/:id/reviews`, and Wiki's `POST /api/v1/pages/:id/revisions/:n/review`. An approval lets AI-written text publish and index and the page says "Checked by OpenVibe's AI editor" with its date, never "Reviewed by a person"; each is granted only to the orchestrator's service principal. Their requests and results are a recorded schema gap (`compatibility/capability-schema-gaps.json`).
++- `generated/` refreshed: Connect and Design gain an OpenAPI document now that each has an active capability, and Blog, Chat, Deals, Domains, News, Sources, Wiki and Work gain the new operations.
++
## 0.314.0 — 2026-11-07
- Add the capability `live.agent_sandbox.register` (Agent Streams S6): the chat plays sandbox worker's check-in on OpenVibe.Live, `POST /api/streams/agent/:id/plays/worker`, owned by Live and internal. Only a first-party service token whose principal holds it may register a worker, and the token names the person it works for in `X-OV-Subject`, who must own the stream. The owner's own session, a desk ticket (`wpt_`) and a person token are refused: an owner who could register a worker would declare their own unisolated machine a sandbox. The worker reports the isolation facts Live requires (no host network, no credentials, no profile, egress through the proxy, an ephemeral workspace) and Live derives health only from a check-in within the last minute; Live stores no endpoint and never calls the worker. Name the capability in Live's service manifest.
ddiff --git a/compatibility/capability-schema-gaps.json b/compatibility/capability-schema-gaps.json
iindex 76bf3bb3..076d011f 100644
---- a/compatibility/capability-schema-gaps.json
++++ b/compatibility/capability-schema-gaps.json
@@@ -28,6 +28,27 @@
"inputSchema",
"outputSchema"
]
++ },
++ {
++ "id": "blog.review.ai_editor",
++ "missing": [
++ "inputSchema",
++ "outputSchema"
++ ]
++ },
++ {
++ "id": "news.review.ai_editor",
++ "missing": [
++ "inputSchema",
++ "outputSchema"
++ ]
++ },
++ {
++ "id": "wiki.review.ai_editor",
++ "missing": [
++ "inputSchema",
++ "outputSchema"
++ ]
}
]
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex fd81628f..6bb20299 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.314.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 117f378f..fda6584b 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.314.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 dedcfc86..437f7c9a 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.314.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 023da764..7884b9ad 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.314.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 d9d75859..8c9ce7d3 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.314.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1075,6 +1075,55 @@
}
}
},
++ "/api/v1/posts/{id}/reviews": {
++ "post": {
++ "operationId": "post_api_v1_posts_id_reviews",
++ "summary": "Record the editorial queue's AI editor decision (approved or rejected) on a post revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject).",
++ "description": "**blog.review.ai_editor** (first-party): Record the editorial queue's AI editor decision (approved or rejected) on a post revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets an AI draft publish through the existing publish action and the post says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "tags": [
++ "blog.review"
++ ],
++ "x-openvibe-capabilities": [
++ "blog.review.ai_editor"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "blog-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "blog.review.ai_editor"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/posts/{id}/revisions": {
"get": {
"operationId": "get_api_v1_posts_id_revisions",
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 756bbb06..928edcbc 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.314.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 8290f694..f49539e9 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.314.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -5110,45 +5110,96 @@
}
}
},
-- "/internal/live/calls": {
-- "post": {
-- "operationId": "post_internal_live_calls",
-- "summary": "Send a chat message to a room the actor is a member of (browsers over the chat socket; first-party services over Chat's service API).",
-- "description": "**chat.message.send** (first-party): Send a chat message to a room the actor is a member of (browsers over the chat socket; first-party services over Chat's service API).",
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"tags": [
-- "chat.message"
++ "chat.config"
],
"x-openvibe-capabilities": [
-- "chat.message.send"
++ "chat.config.manage"
],
"x-openvibe-visibility": [
-- "first-party"
++ "internal"
],
-- "x-openvibe-quota-class": "chat-send",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "chat.message.send"
++ "chat.config.manage"
]
}
],
-- "requestBody": {
-- "required": false,
-- "content": {
-- "application/json": {
-- "schema": {
-- "$ref": "#/components/schemas/chat.send-request.v1"
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
}
-- }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
}
-- },
++ ],
"responses": {
"2XX": {
"description": "Success",
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/chat.send-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -5166,36 +5217,95 @@
}
}
},
-- "/internal/moderation/channels/{channelId}": {
++ "/internal/config/{namespace}": {
"get": {
-- "operationId": "get_internal_moderation_channels_channelId",
-- "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
-- "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"tags": [
-- "chat.moderation"
++ "chat.config"
],
"x-openvibe-capabilities": [
-- "chat.moderation.read"
++ "chat.config.manage"
],
"x-openvibe-visibility": [
"internal"
],
-- "x-openvibe-quota-class": "internal-read",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "chat.moderation.read"
++ "chat.config.manage"
]
}
],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
"parameters": [
{
-- "name": "channelId",
++ "name": "namespace",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
}
],
"responses": {
@@@ -5204,7 +5314,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -5220,33 +5330,41 @@
}
}
}
-- }
-- },
-- "/internal/moderation/channels/{channelId}/emote-count": {
-- "get": {
-- "operationId": "get_internal_moderation_channels_channelId_emote_count",
-- "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
-- "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"tags": [
-- "chat.moderation"
++ "chat.config"
],
"x-openvibe-capabilities": [
-- "chat.moderation.read"
++ "chat.config.manage"
],
"x-openvibe-visibility": [
"internal"
],
-- "x-openvibe-quota-class": "internal-read",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "chat.moderation.read"
++ "chat.config.manage"
]
}
],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
"parameters": [
{
-- "name": "channelId",
++ "name": "namespace",
"in": "path",
"required": true,
"schema": {
@@@ -5260,7 +5378,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -5278,36 +5396,103 @@
}
}
},
-- "/internal/moderation/users/{userId}/channels": {
-- "get": {
-- "operationId": "get_internal_moderation_users_userId_channels",
-- "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
-- "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"tags": [
-- "chat.moderation"
++ "chat.config"
],
"x-openvibe-capabilities": [
-- "chat.moderation.read"
++ "chat.config.manage"
],
"x-openvibe-visibility": [
"internal"
],
-- "x-openvibe-quota-class": "internal-read",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "chat.moderation.read"
++ "chat.config.manage"
]
}
],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
"parameters": [
{
-- "name": "userId",
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
}
],
"responses": {
@@@ -5316,7 +5501,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -5332,50 +5517,420 @@
}
}
}
-- }
-- }
-- },
-- "components": {
-- "schemas": {
-- "chat.agent-digest.v1": {
-- "title": "ChatAgentDigest",
-- "description": "chat.agent-digest@1 (the Agent Streams plan, S5): what a chat room said to an agent between two of its steps, GET /api/v1/rooms/agent:<id>/digest?since= on OpenVibe.Chat. The agent never reads the raw room: it asks for a digest and gets at most ten items and 1,500 characters. Each item names who (the display name and the handle), what (cut to 200 characters), its kind (a question, a suggestion or a reaction) and its upvotes (a thumbs up on the message counts). Chat chooses what goes in: repeats are grouped, greetings and spam are dropped, and the most-upvoted questions and suggestions come first. Nobody is left out by accident: a person who is timed out or banned, whose account is younger than a day, or who set 'Agents don't read my messages' is never in the digest. A link is kept as its domain only ('a link to github.com'), because the agent never follows a link from chat. The code and the commands stay as text: the agent's own tools and approvals decide what may run, never chat.",
-- "type": "object",
-- "additionalProperties": false,
-- "required": [
-- "room",
-- "since",
-- "until",
-- "items"
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "chat.config"
],
-- "properties": {
-- "room": {
-- "type": "string",
-- "pattern": "^agent:ags_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The room the digest came from: an agent stream's room, `agent:<ags_id>`."
-- },
-- "since": {
-- "type": [
-- "string",
-- "null"
-- ],
-- "format": "date-time",
-- "description": "The start of the window: the moment the agent last read, or null for the first read."
-- },
-- "until": {
-- "type": "string",
-- "format": "date-time",
-- "description": "The end of the window: the moment this digest was made."
++ "x-openvibe-capabilities": [
++ "chat.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
},
-- "items": {
-- "type": "array",
-- "maxItems": 10,
-- "description": "What chat said, at most ten items. The order is the one Chat chose: the most-upvoted questions and suggestions first, the reactions last.",
-- "items": {
-- "type": "object",
-- "additionalProperties": false,
-- "required": [
-- "who",
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**chat.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "chat.config"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/live/calls": {
++ "post": {
++ "operationId": "post_internal_live_calls",
++ "summary": "Send a chat message to a room the actor is a member of (browsers over the chat socket; first-party services over Chat's service API).",
++ "description": "**chat.message.send** (first-party): Send a chat message to a room the actor is a member of (browsers over the chat socket; first-party services over Chat's service API).",
++ "tags": [
++ "chat.message"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.message.send"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "chat-send",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.message.send"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.send-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.send-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/moderation/channels/{channelId}": {
++ "get": {
++ "operationId": "get_internal_moderation_channels_channelId",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "channelId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/moderation/channels/{channelId}/emote-count": {
++ "get": {
++ "operationId": "get_internal_moderation_channels_channelId_emote_count",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "channelId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/moderation/users/{userId}/channels": {
++ "get": {
++ "operationId": "get_internal_moderation_users_userId_channels",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "userId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "chat.agent-digest.v1": {
++ "title": "ChatAgentDigest",
++ "description": "chat.agent-digest@1 (the Agent Streams plan, S5): what a chat room said to an agent between two of its steps, GET /api/v1/rooms/agent:<id>/digest?since= on OpenVibe.Chat. The agent never reads the raw room: it asks for a digest and gets at most ten items and 1,500 characters. Each item names who (the display name and the handle), what (cut to 200 characters), its kind (a question, a suggestion or a reaction) and its upvotes (a thumbs up on the message counts). Chat chooses what goes in: repeats are grouped, greetings and spam are dropped, and the most-upvoted questions and suggestions come first. Nobody is left out by accident: a person who is timed out or banned, whose account is younger than a day, or who set 'Agents don't read my messages' is never in the digest. A link is kept as its domain only ('a link to github.com'), because the agent never follows a link from chat. The code and the commands stay as text: the agent's own tools and approvals decide what may run, never chat.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "room",
++ "since",
++ "until",
++ "items"
++ ],
++ "properties": {
++ "room": {
++ "type": "string",
++ "pattern": "^agent:ags_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The room the digest came from: an agent stream's room, `agent:<ags_id>`."
++ },
++ "since": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "The start of the window: the moment the agent last read, or null for the first read."
++ },
++ "until": {
++ "type": "string",
++ "format": "date-time",
++ "description": "The end of the window: the moment this digest was made."
++ },
++ "items": {
++ "type": "array",
++ "maxItems": 10,
++ "description": "What chat said, at most ten items. The order is the one Chat chose: the most-upvoted questions and suggestions first, the reactions last.",
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "who",
"what",
"kind",
"upvotes"
@@@ -9817,164 +10372,591 @@
"maxLength": 2000,
"description": "What they asked."
},
-- "source": {
++ "source": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "enum": [
++ "voice"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
++ "description": "The source's stable id (the machine's device id): one chat per source."
++ },
++ "name": {
++ "type": "string",
++ "maxLength": 80,
++ "description": "What to call it in the chat's name (the machine's name)."
++ }
++ }
++ },
++ "lang": {
++ "type": "string",
++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"
++ },
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
++ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."
++ }
++ }
++ },
++ "chat.vibe-ask-result.v1": {
++ "title": "ChatVibeAskResult",
++ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",
++ "type": "object",
++ "required": [
++ "state",
++ "text"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "state": {
++ "enum": [
++ "answered",
++ "refused"
++ ]
++ },
++ "text": {
++ "type": "string",
++ "maxLength": 4000
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://"
++ },
++ "chat": {
++ "type": "string",
++ "pattern": "^v-[0-9a-f]{12}$",
++ "description": "The Vibe chat's slug."
++ },
++ "code": {
++ "type": "string",
++ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."
++ }
++ }
++ },
++ "chat.vibe-deliver-request.v1": {
++ "title": "ChatVibeDeliverRequest",
++ "description": "chat.vibe-deliver-request@1: a first-party service puts a result of the person's own work into their Vibe chats (OpenVibe.Actor: each finished run of one of their OpenVibe Agents, when the agent sends its results to Vibe). Chat keeps one chat per source, named after it, and posts the text there as Vibe's message, so each run is a chat they can open and continue. No AI answer is made and nothing counts against their allowance. POST /internal/chat/vibe/deliver, capability chat.vibe.deliver, loopback only.",
++ "type": "object",
++ "required": [
++ "subject",
++ "source",
++ "text",
++ "idempotency_key"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The person whose work it is, as the calling service vouches (Actor: the agent's owner)."
++ },
++ "source": {
++ "type": "object",
++ "required": [
++ "kind",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "kind": {
++ "enum": [
++ "agent"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
++ "description": "The source's stable id (the agent's agt_ id): one chat per source."
++ },
++ "name": {
++ "type": "string",
++ "maxLength": 80,
++ "description": "What to call it in the chat's name (the agent's name)."
++ }
++ }
++ },
++ "text": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 8000,
++ "description": "The result, in Markdown (as Vibe's answers are shown)."
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://",
++ "description": "Where the whole run is (its steps, sources and cost)."
++ },
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
++ "description": "The caller's id for this result (Actor: the run's task id): sent again, it is posted once."
++ }
++ }
++ },
++ "chat.vibe-deliver-result.v1": {
++ "title": "ChatVibeDeliverResult",
++ "description": "chat.vibe-deliver-result@1: where a chat.vibe-deliver-request@1 was posted. `repeated` is true when the idempotency key was seen before (nothing was posted again).",
++ "type": "object",
++ "required": [
++ "chat",
++ "url",
++ "repeated"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "chat": {
++ "type": "string",
++ "pattern": "^v-[0-9a-f]{12}$",
++ "description": "The Vibe chat's slug."
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://"
++ },
++ "repeated": {
++ "type": "boolean"
++ }
++ }
++ },
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
"type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
"required": [
-- "kind",
-- "id"
++ "redacted",
++ "fingerprint"
],
-- "additionalProperties": false,
"properties": {
-- "kind": {
-- "enum": [
-- "voice"
-- ]
++ "redacted": {
++ "const": true
},
-- "id": {
++ "fingerprint": {
"type": "string",
-- "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
-- "description": "The source's stable id (the machine's device id): one chat per source."
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
},
-- "name": {
++ "hint": {
"type": "string",
-- "maxLength": 80,
-- "description": "What to call it in the chat's name (the machine's name)."
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
}
}
-- },
-- "lang": {
-- "type": "string",
-- "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"
-- },
-- "idempotency_key": {
-- "type": "string",
-- "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
-- "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."
}
}
},
-- "chat.vibe-ask-result.v1": {
-- "title": "ChatVibeAskResult",
-- "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
"type": "object",
"required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
"state",
-- "text"
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
],
"additionalProperties": false,
"properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
"state": {
"enum": [
-- "answered",
-- "refused"
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
]
},
-- "text": {
-- "type": "string",
-- "maxLength": 4000
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
},
-- "url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https://"
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
},
-- "chat": {
++ "checksum": {
"type": "string",
-- "pattern": "^v-[0-9a-f]{12}$",
-- "description": "The Vibe chat's slug."
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
},
-- "code": {
-- "type": "string",
-- "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."
-- }
-- }
-- },
-- "chat.vibe-deliver-request.v1": {
-- "title": "ChatVibeDeliverRequest",
-- "description": "chat.vibe-deliver-request@1: a first-party service puts a result of the person's own work into their Vibe chats (OpenVibe.Actor: each finished run of one of their OpenVibe Agents, when the agent sends its results to Vibe). Chat keeps one chat per source, named after it, and posts the text there as Vibe's message, so each run is a chat they can open and continue. No AI answer is made and nothing counts against their allowance. POST /internal/chat/vibe/deliver, capability chat.vibe.deliver, loopback only.",
-- "type": "object",
-- "required": [
-- "subject",
-- "source",
-- "text",
-- "idempotency_key"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "subject": {
++ "created_at": {
"type": "string",
-- "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The person whose work it is, as the calling service vouches (Actor: the agent's owner)."
++ "format": "date-time"
},
-- "source": {
-- "type": "object",
-- "required": [
-- "kind",
-- "id"
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
],
-- "additionalProperties": false,
-- "properties": {
-- "kind": {
-- "enum": [
-- "agent"
-- ]
-- },
-- "id": {
-- "type": "string",
-- "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
-- "description": "The source's stable id (the agent's agt_ id): one chat per source."
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
},
-- "name": {
-- "type": "string",
-- "maxLength": 80,
-- "description": "What to call it in the chat's name (the agent's name)."
++ {
++ "type": "null"
}
-- }
++ ]
},
-- "text": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 8000,
-- "description": "The result, in Markdown (as Vibe's answers are shown)."
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
},
-- "url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https://",
-- "description": "Where the whole run is (its steps, sources and cost)."
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
},
-- "idempotency_key": {
-- "type": "string",
-- "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
-- "description": "The caller's id for this result (Actor: the run's task id): sent again, it is posted once."
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
}
-- }
-- },
-- "chat.vibe-deliver-result.v1": {
-- "title": "ChatVibeDeliverResult",
-- "description": "chat.vibe-deliver-result@1: where a chat.vibe-deliver-request@1 was posted. `repeated` is true when the idempotency key was seen before (nothing was posted again).",
-- "type": "object",
-- "required": [
-- "chat",
-- "url",
-- "repeated"
-- ],
-- "additionalProperties": false,
-- "properties": {
-- "chat": {
-- "type": "string",
-- "pattern": "^v-[0-9a-f]{12}$",
-- "description": "The Vibe chat's slug."
-- },
-- "url": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https://"
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
},
-- "repeated": {
-- "type": "boolean"
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
}
-- }
++ ]
},
"common.owned-resource-control-request.v1": {
"title": "OwnedResourceControlRequest",
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex b9d7573b..5c8570f3 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.314.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 473e8025..8c0064cb 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.314.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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/connect.json b/generated/openapi/connect.json
nnew file mode 100644
iindex 00000000..f85b24f3
---- /dev/null
++++ b/generated/openapi/connect.json
@@@ -0,0 +1,1197 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe Connect API",
++ "version": "0.315.0",
++ "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://connect.openvibe.website"
++ }
++ ],
++ "paths": {
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**connect.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "connect.config"
++ ],
++ "x-openvibe-capabilities": [
++ "connect.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "connect.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 7a98d826..099976b2 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.314.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 602aaa92..edb19b21 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.314.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1112,10 +1112,992 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists t",
++ "description": "**deals.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "deals.config"
++ ],
++ "x-openvibe-capabilities": [
++ "deals.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "deals.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
"schemas": {
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
"common.no-body.v1": {
"title": "NoBody",
"description": "common.no-body@1: the capability's request carries no body; its parameters are the path and query of the routes in implementedBy (an id, a page cursor). An input schema that says so explicitly, so every active capability names one (roadmap WS-C task 4).",
@@@ -3001,6 +3983,123 @@
"description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
}
}
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
}
},
"securitySchemes": {
ddiff --git a/generated/openapi/design.json b/generated/openapi/design.json
nnew file mode 100644
iindex 00000000..1cb2c957
---- /dev/null
++++ b/generated/openapi/design.json
@@@ -0,0 +1,1197 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Design API",
++ "version": "0.315.0",
++ "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.design"
++ }
++ ],
++ "paths": {
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists ",
++ "description": "**design.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "design.config"
++ ],
++ "x-openvibe-capabilities": [
++ "design.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "design.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/domains.json b/generated/openapi/domains.json
iindex 8039c6d3..3ab3a599 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.314.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -117,6 +117,561 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**domains.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "domains.config"
++ ],
++ "x-openvibe-capabilities": [
++ "domains.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "domains.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -285,6 +840,433 @@
}
}
},
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
"errors.problem.v1": {
"title": "Problem",
"description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
@@@ -350,6 +1332,123 @@
"description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
}
}
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
}
},
"securitySchemes": {
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 409c4af4..7d416cea 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.314.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 032bb2e4..d01defb2 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.314.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 a1c0904f..a7edabfa 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.314.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 d2dfde8b..f59ac00e 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.314.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 d74c9a02..2e388b3e 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -33,8 +33,8 @@
"service": "blog",
"name": "OpenVibe.Blog",
"origin": "https://openvibe.blog",
-- "operations": 22,
-- "capabilities": 12
++ "operations": 23,
++ "capabilities": 13
},
{
"service": "bot",
@@@ -47,8 +47,8 @@
"service": "chat",
"name": "OpenVibe.Chat",
"origin": "https://openvibe.chat",
-- "operations": 60,
-- "capabilities": 24
++ "operations": 66,
++ "capabilities": 25
},
{
"service": "codes",
@@@ -64,6 +64,13 @@
"operations": 40,
"capabilities": 13
},
++ {
++ "service": "connect",
++ "name": "OpenVibe Connect",
++ "origin": "https://connect.openvibe.website",
++ "operations": 6,
++ "capabilities": 1
++ },
{
"service": "coupons",
"name": "OpenVibe.Coupons",
@@@ -75,15 +82,22 @@
"service": "deals",
"name": "OpenVibe.Deals",
"origin": "https://openvibe.deals",
-- "operations": 18,
-- "capabilities": 12
++ "operations": 24,
++ "capabilities": 13
++ },
++ {
++ "service": "design",
++ "name": "OpenVibe.Design",
++ "origin": "https://openvibe.design",
++ "operations": 6,
++ "capabilities": 1
},
{
"service": "domains",
"name": "OpenVibe.Domains",
"origin": "https://openvibe.domains",
-- "operations": 2,
-- "capabilities": 2
++ "operations": 8,
++ "capabilities": 3
},
{
"service": "events",
@@@ -152,8 +166,8 @@
"service": "news",
"name": "OpenVibe.News",
"origin": "https://openvibe.news",
-- "operations": 26,
-- "capabilities": 11
++ "operations": 28,
++ "capabilities": 12
},
{
"service": "openre",
@@@ -215,8 +229,8 @@
"service": "sources",
"name": "OpenVibe.Sources",
"origin": "https://sources.openvibe.network",
-- "operations": 13,
-- "capabilities": 3
++ "operations": 19,
++ "capabilities": 4
},
{
"service": "space",
@@@ -279,14 +293,14 @@
"name": "OpenVibe.Wiki",
"origin": "https://openvibe.wiki",
"operations": 25,
-- "capabilities": 8
++ "capabilities": 9
},
{
"service": "work",
"name": "OpenVibe.Work",
"origin": "https://openvibe.work",
-- "operations": 5,
-- "capabilities": 1
++ "operations": 11,
++ "capabilities": 2
},
{
"service": "zone",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 2167ebce..156af2a4 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.314.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 a244b224..7fa79c85 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.314.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 a06b811f..92682e0a 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.314.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 60d60fc7..ba97b65c 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.314.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 f8f6dc6e..0b36c033 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.314.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex de1aa57d..db9513c6 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.314.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -16,6 +16,55 @@
}
],
"paths": {
++ "/api/v1/briefs/{id}/reviews": {
++ "post": {
++ "operationId": "post_api_v1_briefs_id_reviews",
++ "summary": "Record the editorial queue's AI editor decision (approved or rejected) on a story revision or an AI brief, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject).",
++ "description": "**news.review.ai_editor** (first-party): Record the editorial queue's AI editor decision (approved or rejected) on a story revision or an AI brief, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets AI text publish and index and the page says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "tags": [
++ "news.review"
++ ],
++ "x-openvibe-capabilities": [
++ "news.review.ai_editor"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "news-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "news.review.ai_editor"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/clusters": {
"get": {
"operationId": "get_api_v1_clusters",
@@@ -983,6 +1032,55 @@
}
}
},
++ "/api/v1/stories/{id}/reviews": {
++ "post": {
++ "operationId": "post_api_v1_stories_id_reviews",
++ "summary": "Record the editorial queue's AI editor decision (approved or rejected) on a story revision or an AI brief, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject).",
++ "description": "**news.review.ai_editor** (first-party): Record the editorial queue's AI editor decision (approved or rejected) on a story revision or an AI brief, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets AI text publish and index and the page says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "tags": [
++ "news.review"
++ ],
++ "x-openvibe-capabilities": [
++ "news.review.ai_editor"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "news-write",
++ "security": [
++ {
++ "openvibeToken": [
++ "news.review.ai_editor"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/stories/{id}/revisions": {
"get": {
"operationId": "get_api_v1_stories_id_revisions",
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex 62d5046c..a6dcd7f7 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.314.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 635bd8aa..c562a034 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.314.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 13c4a1ed..ebd28aca 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.314.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 4b4b5d7c..59c320ec 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.314.0",
-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 88876653..4274f21d 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.314.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 ecce6110..8003bfc9 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.314.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 85020095..374a3f81 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.314.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 804e5fa1..74003cca 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.314.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 849c6dc2..dd36973a 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.314.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -731,10 +731,992 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists",
++ "description": "**sources.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "sources.config"
++ ],
++ "x-openvibe-capabilities": [
++ "sources.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "sources.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
"schemas": {
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
"common.no-body.v1": {
"title": "NoBody",
"description": "common.no-body@1: the capability's request carries no body; its parameters are the path and query of the routes in implementedBy (an id, a page cursor). An input schema that says so explicitly, so every active capability names one (roadmap WS-C task 4).",
@@@ -807,6 +1789,123 @@
}
}
},
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ },
"sources.item-read-result.v1": {
"title": "SourcesItemReadResult",
"description": "sources.item-read-result@1: answers of sources.item.read on OpenVibe.Sources. GET /api/v1/items?source=&category=&after=&limit=&include_removed= → { items, next_after, more, sources } (a change feed: items in change order, at most 500; sources gives each listed source's health); GET /api/v1/items/:id[?revisions=1] → { item, source } (the item, with its revisions when asked, and its source's health; null when the source is gone).",
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex d66657d8..7a829e80 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.314.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 8d5224f1..ee56cd34 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.314.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 312f4226..81dd6903 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.314.0",
-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 4eede512..501942ec 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.314.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 a89ad039..01ab5153 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.314.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 b832e7e1..0017c8b5 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.314.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 19556862..8ae12b2c 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.314.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 60ed9262..c758083e 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.314.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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 f6f21310..b9c32dc0 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.314.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -836,12 +836,14 @@
"/api/v1/pages/{id}/revisions/{n}/review": {
"post": {
"operationId": "post_api_v1_pages_id_revisions_n_review",
-- "summary": "Publish a revision now or at a scheduled time (idempotent), unpublish a page, and record a person's review (approved or rejected) of an AI proposal or of an existing AI-assisted revision, on behalf of",
-- "description": "**wiki.revision.publish** (first-party): Publish a revision now or at a scheduled time (idempotent), unpublish a page, and record a person's review (approved or rejected) of an AI proposal or of an existing AI-assisted revision, on behalf of the editor named in X-OV-Subject. AI-generated revisions cannot be published without an approving review by a person.",
++ "summary": "Record the editorial queue's AI editor decision (approved or needs changes) on an existing revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject).",
++ "description": "**wiki.review.ai_editor** (first-party): Record the editorial queue's AI editor decision (approved or needs changes) on an existing revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets AI-written text publish and index, and the page says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.\n\n**wiki.revision.publish** (first-party): Publish a revision now or at a scheduled time (idempotent), unpublish a page, and record a person's review (approved or rejected) of an AI proposal or of an existing AI-assisted revision, on behalf of the editor named in X-OV-Subject. AI-generated revisions cannot be published without an approving review by a person.",
"tags": [
++ "wiki.review",
"wiki.revision"
],
"x-openvibe-capabilities": [
++ "wiki.review.ai_editor",
"wiki.revision.publish"
],
"x-openvibe-visibility": [
@@@ -849,6 +851,11 @@
],
"x-openvibe-quota-class": "wiki-write",
"security": [
++ {
++ "openvibeToken": [
++ "wiki.review.ai_editor"
++ ]
++ },
{
"openvibeToken": [
"wiki.revision.publish"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex abe4a42f..51e479ea 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.314.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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"
@@@ -591,10 +591,992 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}": {
++ "get": {
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/secrets/{key}": {
++ "delete": {
++ "operationId": "delete_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "values",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused."
++ },
++ {
++ "name": "reason",
++ "in": "query",
++ "required": true,
++ "schema": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ {
++ "name": "merge",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "description": "Merge into the active values instead of replacing them."
++ },
++ {
++ "name": "unset",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "description": "Keys to remove from the new revision."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "put": {
++ "operationId": "put_internal_config_namespace_secrets_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/config/{namespace}/test/{key}": {
++ "post": {
++ "operationId": "post_internal_config_namespace_test_key",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists th",
++ "description": "**work.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "tags": [
++ "work.config"
++ ],
++ "x-openvibe-capabilities": [
++ "work.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "work.config.manage"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ },
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-namespace.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
"schemas": {
++ "common.config-change.v1": {
++ "title": "ConfigChange",
++ "description": "common.config-change@1 (AI Exchange D11): body of POST /internal/config/:namespace on a service's openvibe-shared/config admin routes. It proposes a new revision of the namespace and activates it (the store validates against the namespace schema, applies env pins and calls onActivate). `merge: true` starts from the active values; otherwise `values` is the whole new set. `unset` removes keys. A secret-class key's value is never sent here: it is 422 config.secret_write_only; use common.config-secret-put@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "values",
++ "reason"
++ ],
++ "properties": {
++ "values": {
++ "type": "object",
++ "description": "The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value; a redaction marker keeps the value it stands for."
++ }
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the change is made; it is the revision's reason in the history."
++ },
++ "merge": {
++ "type": "boolean",
++ "default": false,
++ "description": "Merge into the active values instead of replacing them."
++ },
++ "unset": {
++ "type": "array",
++ "maxItems": 256,
++ "uniqueItems": true,
++ "description": "Keys to remove from the new revision.",
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ }
++ }
++ },
++ "common.config-namespace.v1": {
++ "title": "ConfigNamespace",
++ "description": "common.config-namespace@1 (AI Exchange D11): one service configuration namespace as openvibe-shared/config's summary() returns it (config-pg.js), plus `sealed`, `pins` and `schema`. GET /internal/config lists these for a service and GET /internal/config/:namespace returns one. Values are the active ones over the defaults, with every secret-class value replaced by its marker; a marker never carries the value. `sealed` says whether the service has a KEK so secrets are envelopes (false is the pre-D11 plaintext behaviour). `pins` names the keys whose x-env variable is set, so their effective value is the environment's and a write is 409 config.pinned_in_env. `schema` is the namespace's JSON Schema so a client can draw the form; its annotations are title, description, x-group, x-env, x-danger, x-bind and x-unit (nano_usd, ms, bps, seconds), and for the owner's Setup tab and the clear admin panel (openvibe-shared 4.125.0) x-owner-input ({ group, unlocks, get_it_at, get_it_how, test }: a field only the owner can give), x-required-for (the feature an unset field blocks), x-enum-labels ({ value: { label, description } }), x-advanced (folded under Advanced settings) and x-example (a placeholder; never on a secret).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "values",
++ "classification",
++ "sealed",
++ "pins",
++ "schema"
++ ],
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. ai.exchange or live.site_settings."
++ },
++ "revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The active revision; null before the namespace has one."
++ },
++ "values": {
++ "type": "object",
++ "description": "The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a secret marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "$ref": "#/components/schemas/common.config-namespace.v1/$defs/marker"
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "sealed": {
++ "type": "boolean",
++ "description": "True when the service has a KEK: secret-class values are stored as envelopes and only opened for a caller that needs them. False keeps the pre-D11 plaintext-inside-values_json behaviour, and the tab shows it in red."
++ },
++ "pins": {
++ "type": "array",
++ "description": "The keys whose x-env variable is set in the service process: their effective value is the environment's, read-only, and a write answers 409 config.pinned_in_env.",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ }
++ },
++ "schema": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The namespace's JSON Schema, or null when it has none. Annotations the client may read: title, description, x-group, x-env, x-danger, x-bind (the key whose value the secret is bound to), x-unit (nano_usd, ms, bps or seconds), x-owner-input, x-required-for, x-enum-labels, x-advanced and x-example."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ }
++ },
++ "$defs": {
++ "marker": {
++ "type": "object",
++ "description": "A secret-class value, as the summary and the secret endpoints show it: never the value. The fingerprint is keyed, so equal fingerprints within one namespace mean the value did not change.",
++ "additionalProperties": false,
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key."
++ },
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
++ },
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "bound_to": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
++ }
++ }
++ }
++ }
++ },
++ "common.config-snapshot.v1": {
++ "title": "ConfigSnapshot",
++ "description": "One immutable revision of one service's configuration namespace (roadmap WS-C task 7; kept by openvibe-shared/config). A revision's values never change: revisions grow by one per namespace, and a rollback is a new revision that copies an older one's values. Only the state moves: proposed -> active -> superseded or rolled_back, or proposed -> rejected when validation or activation fails, in which case the previous active revision stays or is restored. A secret-class key's value never appears here: it is { redacted: true, fingerprint }, an HMAC-SHA256 of the value under a secret key the service keeps per namespace, so equal fingerprints within one namespace mean the value did not change, while the value cannot be recovered or guessed offline without that key. checksum is the sha256 of the canonical JSON (keys sorted, no whitespace) of values exactly as they appear here, secrets as their markers, so anyone can check it, and two snapshots of one namespace with the same checksum hold the same configuration.",
++ "type": "object",
++ "required": [
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "namespace": {
++ "type": "string",
++ "maxLength": 120,
++ "pattern": "^[a-z][a-z0-9-]{1,39}(\\.[a-z0-9_-]+)+$",
++ "description": "<service>.<name>, e.g. live.site_settings or media.storage_tier."
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
++ },
++ "state": {
++ "enum": [
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
++ },
++ "values": {
++ "type": "object",
++ "description": "The configuration, one member per key. A secret-class key's value is a redaction marker.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "description": "Any JSON value. An object with a redacted member is a redaction marker, and nothing else.",
++ "anyOf": [
++ {
++ "type": [
++ "string",
++ "number",
++ "boolean",
++ "null",
++ "array"
++ ]
++ },
++ {
++ "type": "object",
++ "additionalProperties": true,
++ "not": {
++ "required": [
++ "redacted"
++ ]
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "redacted",
++ "fingerprint"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "redacted": {
++ "const": true
++ },
++ "fingerprint": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key: equal within one namespace when the value is unchanged, and not recoverable or guessable without the key."
++ }
++ },
++ "description": "A redacted secret-class value."
++ }
++ ]
++ }
++ },
++ "classification": {
++ "type": "object",
++ "description": "Every key of values, classified. A key the service does not classify is internal.",
++ "propertyNames": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$"
++ },
++ "additionalProperties": {
++ "enum": [
++ "public",
++ "internal",
++ "secret"
++ ]
++ }
++ },
++ "checksum": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
++ },
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
++ },
++ "copied_from": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "rejected"
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "error"
++ ],
++ "properties": {
++ "error": {
++ "type": "string",
++ "minLength": 1
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "active",
++ "superseded",
++ "rolled_back"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "activated_at"
++ ],
++ "properties": {
++ "activated_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ ]
++ },
"errors.problem.v1": {
"title": "Problem",
"description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
@@@ -661,6 +1643,123 @@
}
}
},
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ },
"work.application-result.v1": {
"title": "WorkApplicationResult",
"description": "work.application-result@1: what work.application.manage answers: one application, or a page of them (GET /api/v1/me/applications, newest first, ?status= and ?cursor=; `next` is the next page's cursor or null). DELETE answers 204 with no body.",
ddiff --git a/generated/openapi/zone.json b/generated/openapi/zone.json
iindex d56920c1..b9c1658a 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.314.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.314.0): each operation lists the capabilities it performs; a token for 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.315.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.315.0): each operation lists the capabilities it performs; a token for 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/manifests/capabilities/blog.review.ai_editor.json b/manifests/capabilities/blog.review.ai_editor.json
nnew file mode 100644
iindex 00000000..a8021346
---- /dev/null
++++ b/manifests/capabilities/blog.review.ai_editor.json
@@@ -0,0 +1,23 @@
++{
++ "id": "blog.review.ai_editor",
++ "version": "1.0",
++ "owner": "blog",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Record the editorial queue's AI editor decision (approved or rejected) on a post revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets an AI draft publish through the existing publish action and the post says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "permissions": [
++ "blog:write"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "blog-write",
++ "events": [
++ "blog.post.updated",
++ "blog.index_document.upserted",
++ "blog.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/posts/:id/reviews"
++ ]
++}
ddiff --git a/manifests/capabilities/chat.config.manage.json b/manifests/capabilities/chat.config.manage.json
iindex dc57eafb..e6a12f00 100644
---- a/manifests/capabilities/chat.config.manage.json
++++ b/manifests/capabilities/chat.config.manage.json
@@@ -2,9 +2,9 @@
"id": "chat.config.manage",
"version": "1.0",
"owner": "chat",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Chat mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Chat through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/connect.config.manage.json b/manifests/capabilities/connect.config.manage.json
iindex c0214ecc..4dffcddb 100644
---- a/manifests/capabilities/connect.config.manage.json
++++ b/manifests/capabilities/connect.config.manage.json
@@@ -2,9 +2,9 @@
"id": "connect.config.manage",
"version": "1.0",
"owner": "connect",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe Connect mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe Connect through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/deals.config.manage.json b/manifests/capabilities/deals.config.manage.json
iindex b938d27b..5ec2bc88 100644
---- a/manifests/capabilities/deals.config.manage.json
++++ b/manifests/capabilities/deals.config.manage.json
@@@ -2,9 +2,9 @@
"id": "deals.config.manage",
"version": "1.0",
"owner": "deals",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Deals mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Deals through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/design.config.manage.json b/manifests/capabilities/design.config.manage.json
iindex fcd6ef51..2ba083c5 100644
---- a/manifests/capabilities/design.config.manage.json
++++ b/manifests/capabilities/design.config.manage.json
@@@ -2,9 +2,9 @@
"id": "design.config.manage",
"version": "1.0",
"owner": "design",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Design mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Design through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/domains.config.manage.json b/manifests/capabilities/domains.config.manage.json
iindex a5a0811a..d5f9e048 100644
---- a/manifests/capabilities/domains.config.manage.json
++++ b/manifests/capabilities/domains.config.manage.json
@@@ -2,9 +2,9 @@
"id": "domains.config.manage",
"version": "1.0",
"owner": "domains",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Domains mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Domains through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/news.review.ai_editor.json b/manifests/capabilities/news.review.ai_editor.json
nnew file mode 100644
iindex 00000000..a6ecfb4f
---- /dev/null
++++ b/manifests/capabilities/news.review.ai_editor.json
@@@ -0,0 +1,24 @@
++{
++ "id": "news.review.ai_editor",
++ "version": "1.0",
++ "owner": "news",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Record the editorial queue's AI editor decision (approved or rejected) on a story revision or an AI brief, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets AI text publish and index and the page says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "permissions": [
++ "news:write"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "news-write",
++ "events": [
++ "news.story.updated",
++ "news.index_document.upserted",
++ "news.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/stories/:id/reviews",
++ "POST /api/v1/briefs/:id/reviews"
++ ]
++}
ddiff --git a/manifests/capabilities/sources.config.manage.json b/manifests/capabilities/sources.config.manage.json
iindex e6e5006c..7b1934a0 100644
---- a/manifests/capabilities/sources.config.manage.json
++++ b/manifests/capabilities/sources.config.manage.json
@@@ -2,9 +2,9 @@
"id": "sources.config.manage",
"version": "1.0",
"owner": "sources",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Sources mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Sources through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/capabilities/wiki.review.ai_editor.json b/manifests/capabilities/wiki.review.ai_editor.json
nnew file mode 100644
iindex 00000000..eb0e1764
---- /dev/null
++++ b/manifests/capabilities/wiki.review.ai_editor.json
@@@ -0,0 +1,21 @@
++{
++ "id": "wiki.review.ai_editor",
++ "version": "1.0",
++ "owner": "wiki",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Record the editorial queue's AI editor decision (approved or needs changes) on an existing revision, as the first-party service principal acting as itself (a svc: principal with no X-OV-Subject). This is never a person's review: an approval lets AI-written text publish and index, and the page says \"Checked by OpenVibe's AI editor\" with its date, never \"Reviewed by a person\". Granted only to the orchestrator's service principal.",
++ "permissions": [
++ "wiki:review:ai_editor"
++ ],
++ "resourceConstraints": [],
++ "quotaClass": "wiki-write",
++ "events": [
++ "wiki.page.updated",
++ "wiki.index_document.upserted",
++ "wiki.index_document.deleted"
++ ],
++ "implementedBy": [
++ "POST /api/v1/pages/:id/revisions/:n/review"
++ ]
++}
ddiff --git a/manifests/capabilities/work.config.manage.json b/manifests/capabilities/work.config.manage.json
iindex 2aea1c71..108329cd 100644
---- a/manifests/capabilities/work.config.manage.json
++++ b/manifests/capabilities/work.config.manage.json
@@@ -2,9 +2,9 @@
"id": "work.config.manage",
"version": "1.0",
"owner": "work",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
-- "description": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Planned until OpenVibe.Work mounts these routes; it then turns active and its service manifest lists it. Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Work through openvibe-shared/config (D11), so the owner can give its keys and accounts on Network's Setup tab: GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common.config-namespace@1, POST applies common.config-change@1, and PUT /internal/config/:namespace/secrets/:key sets a secret with common.config-secret-put@1 (write-only; the answer is the marker) while DELETE removes it. POST /internal/config/:namespace/test/:key asks the service's tester to try a stored value with the provider on the server, answering { ok, message } in one line that never carries a secret (the owner's Setup tab). Only Network's admin proxy, acting for the owner, holds this capability; it is never grantable to apps. Each request carries the owner as actor, and the service audits every write with that actor and key, never a secret value.",
"permissions": [
"config:manage"
],
ddiff --git a/manifests/services/blog.json b/manifests/services/blog.json
iindex d0ab05db..cf50cd22 100644
---- a/manifests/services/blog.json
++++ b/manifests/services/blog.json
@@@ -53,6 +53,7 @@
"blog.post.schedule",
"blog.post.unpublish",
"blog.post.delete",
++ "blog.review.ai_editor",
"blog.feed.read"
],
"eventsProduced": [
ddiff --git a/manifests/services/chat.json b/manifests/services/chat.json
iindex 89624789..255cfc40 100644
---- a/manifests/services/chat.json
++++ b/manifests/services/chat.json
@@@ -16,6 +16,7 @@
"chat.agent_poll.write",
"chat.analysis.read",
"chat.cache.invalidate",
++ "chat.config.manage",
"chat.event.publish",
"chat.inbox.manage",
"chat.inbox.read",
ddiff --git a/manifests/services/connect.json b/manifests/services/connect.json
iindex 6761d8bf..02f3409b 100644
---- a/manifests/services/connect.json
++++ b/manifests/services/connect.json
@@@ -15,7 +15,9 @@
"state": "live",
"publicSite": "service"
},
-- "capabilities": [],
++ "capabilities": [
++ "connect.config.manage"
++ ],
"eventsProduced": ["connect.hook.received"],
"eventsConsumed": ["network.account.export_requested", "network.account.deleted", "connect.hook.received"],
"namespacesOwned": [
ddiff --git a/manifests/services/deals.json b/manifests/services/deals.json
iindex 01fe446f..5e75165b 100644
---- a/manifests/services/deals.json
++++ b/manifests/services/deals.json
@@@ -78,6 +78,7 @@
"neighbourhood": "shop"
},
"capabilities": [
++ "deals.config.manage",
"deals.offer.submit",
"deals.offer.update",
"deals.offer.expire",
ddiff --git a/manifests/services/design.json b/manifests/services/design.json
iindex 0b8dbab2..eb13ed2e 100644
---- a/manifests/services/design.json
++++ b/manifests/services/design.json
@@@ -15,7 +15,9 @@
"state": "live",
"publicSite": "service"
},
-- "capabilities": [],
++ "capabilities": [
++ "design.config.manage"
++ ],
"eventsProduced": [
"design.index_document.deleted",
"design.index_document.upserted"
ddiff --git a/manifests/services/domains.json b/manifests/services/domains.json
iindex 6c88ace2..d2d5b3a7 100644
---- a/manifests/services/domains.json
++++ b/manifests/services/domains.json
@@@ -17,6 +17,7 @@
},
"capabilities": [
"domains.actions.read",
++ "domains.config.manage",
"domains.proposal.decide"
],
"eventsProduced": [],
ddiff --git a/manifests/services/news.json b/manifests/services/news.json
iindex 82526de8..19812e71 100644
---- a/manifests/services/news.json
++++ b/manifests/services/news.json
@@@ -88,6 +88,7 @@
"news.source.attach",
"news.timeline.update",
"news.perspective.update",
++ "news.review.ai_editor",
"news.topic.manage"
],
"eventsProduced": [
ddiff --git a/manifests/services/sources.json b/manifests/services/sources.json
iindex 05f09a88..2083561e 100644
---- a/manifests/services/sources.json
++++ b/manifests/services/sources.json
@@@ -20,6 +20,7 @@
"note": "internal ingestion service; sources.openvibe.network says so (its API is loopback-only)"
},
"capabilities": [
++ "sources.config.manage",
"sources.source.read",
"sources.item.read",
"sources.source.manage"
ddiff --git a/manifests/services/wiki.json b/manifests/services/wiki.json
iindex 6047287e..30d65d51 100644
---- a/manifests/services/wiki.json
++++ b/manifests/services/wiki.json
@@@ -45,6 +45,7 @@
"wiki.space.create",
"wiki.page.create",
"wiki.page.read",
++ "wiki.review.ai_editor",
"wiki.revision.propose",
"wiki.revision.publish",
"wiki.revision.revert",
ddiff --git a/manifests/services/work.json b/manifests/services/work.json
iindex f15b4f5c..66f1c4d4 100644
---- a/manifests/services/work.json
++++ b/manifests/services/work.json
@@@ -16,7 +16,8 @@
"publicSite": "service"
},
"capabilities": [
-- "work.application.manage"
++ "work.application.manage",
++ "work.config.manage"
],
"eventsProduced": [
"work.index_document.upserted",
ddiff --git a/package-lock.json b/package-lock.json
iindex 71e58b53..bbde8b3f 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.311.0",
++ "version": "0.315.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.311.0",
++ "version": "0.315.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 55918607..8d95982a 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.314.0",
++ "version": "0.315.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/run.js b/test/run.js
iindex 4b7bdb71..3228e07e 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -113,17 +113,16 @@ for (const cap of capabilities.manifests) {
// openvibe-shared/config's admin routes. Only Network's admin proxy, acting for the owner, holds
// them; they are never grantable to apps. Network uses its own admin config routes, not the proxy.
// 0.311.0 (the owner's Setup tab): the config admin routes gained POST /internal/config/:namespace/test/:key
--// (openvibe-shared 4.125.0's test hook), and eight more services that hold an owner input are reachable. Those
--// eight are planned until each mounts the routes: then it turns active and its service manifest lists it (Deals'
--// own test pins that list, so listing it early would break the service's pin bump).
++// (openvibe-shared 4.125.0's test hook), and eight more services that hold an owner input are reachable. Seven of
++// them now mount the routes and are active and listed in their service manifest; Media is still planned until it does.
const CONFIG_ROUTES = ['GET /internal/config', 'GET|POST /internal/config/:namespace', 'PUT|DELETE /internal/config/:namespace/secrets/:key', 'POST /internal/config/:namespace/test/:key'];
--for (const svc of ['ai', 'billing', 'services', 'watch', 'bench', 'report', 'host', 'codes', 'actor']) {
++for (const svc of ['ai', 'billing', 'services', 'watch', 'bench', 'report', 'host', 'codes', 'actor', 'chat', 'connect', 'deals', 'work', 'sources', 'design', 'domains']) {
const c = capabilities.get(`${svc}.config.manage`);
ok(c && c.owner === svc && c.status === 'active' && c.visibility === 'internal', `${svc}.config.manage is an active internal capability`);
ok(JSON.stringify(c.implementedBy) === JSON.stringify(CONFIG_ROUTES), `${svc}.config.manage names the config routes`);
ok(/Only Network's admin proxy, acting for the owner, holds this capability/.test(c.description) && /never grantable to apps/.test(c.description), `${svc}.config.manage says who holds it and that an app never does`);
}
--for (const svc of ['chat', 'connect', 'deals', 'work', 'sources', 'design', 'domains', 'media']) {
++for (const svc of ['media']) {
const c = capabilities.get(`${svc}.config.manage`);
ok(c && c.owner === svc && c.status === 'planned' && c.visibility === 'internal', `${svc}.config.manage is a planned internal capability`);
ok(JSON.stringify(c.implementedBy) === JSON.stringify(CONFIG_ROUTES), `${svc}.config.manage names the config routes`);