openvibers/openvibe.contracts Commits

0.288.0: config contracts and capabilities (AI Exchange D11 S1) (#297)

OP OpenVibers committed 9 days ago · 3b536e9e4f · 86 files, +10325 −521

* 0.288.0: owner settings contracts and capabilities (AI Exchange D11 S1)

Adds platform.owner-settings@1 (a service's typed settings document; a secret setting never carries value or default), platform.owner-settings-change@1 (PATCH changes, owner actor) and platform.owner-secret-put@1 (write-only value, optional https origin bind), each with valid and invalid fixtures. Adds one internal <svc>.settings.manage capability for ai, billing, network, services, watch, bench, report, host, codes and actor, implemented by GET|PATCH /internal/owner-settings and PUT|DELETE /internal/owner-settings/secrets/:key; only Network's admin proxy, acting for the owner, holds them and they are never grantable to apps. Contracts keeps no default-grants list, so granting them to network is OpenVibe.Network's S3 change. Bumps to 0.288.0 with generated output.

* 0.288.0: config contracts and capabilities (AI Exchange D11 S1)

Revises D11 S1 onto openvibe-shared/config: drops platform.owner-settings@1, platform.owner-settings-change@1 and platform.owner-secret-put@1 and adds common.config-namespace@1 (the config-pg summary() shape plus sealed, pins and schema, with secret markers and the allowed annotations), common.config-change@1 ({ values, reason, merge?, unset? }) and common.config-secret-put@1 ({ value, reason }), each with valid and invalid fixtures; an invalid summary carries a secret value in its marker. Replaces the ten settings capabilities with nine internal <svc>.config.manage capabilities for ai, billing, services, watch, bench, report, host, codes and actor on GET /internal/config, GET|POST /internal/config/:namespace and PUT|DELETE /internal/config/:namespace/secrets/:key. Only Network's admin proxy, acting for the owner, holds them and they are never grantable to apps. Contracts keeps no default-grants list, so granting them to network is OpenVibe.Network's change. Version stays 0.288.0 with generated output.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 87d14d36..8c70d9d3 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,6 +1,29 @@
# Changelog
++## 0.288.0 — 2026-11-04
++
++- **Configuration contracts (AI Exchange D11, S1), built on openvibe-shared/config:
++ `common.config-namespace@1`, `common.config-change@1` and `common.config-secret-put@1`** (internal).
++ - `common.config-namespace@1` is the shape `openvibe-shared/config`'s `summary()` returns (`service`, `namespace`,
++ `revision`, `values`, `classification`, `active`, `last_known_good`), plus `sealed`, `pins` and `schema`: whether
++ a KEK seals secret values, the keys pinned by their `x-env` variable, and the namespace's JSON Schema so a client
++ can draw the form. The annotations `title`, `description`, `x-group`, `x-env`, `x-danger`, `x-bind` and `x-unit`
++ (`nano_usd`, `ms`, `bps`) are allowed.
++ - A secret-class value in `values` is a marker (`redacted: true`, a keyed 64-hex `fingerprint`, and the optional
++ four-character `hint`, `set_at`, `set_by` and `bound_to`); a marker that carries `value` is invalid, so a
++ summary never carries a secret.
++ - `common.config-change@1` is `{ values, reason, merge?, unset? }`; a secret value is refused by the store
++ (`422 config.secret_write_only`) and is written only through the secret route.
++ - `common.config-secret-put@1` is `{ value, reason }`; the answer is the marker.
++- **Nine internal capabilities, one per service:** `ai`, `billing`, `services`, `watch`, `bench`, `report`, `host`,
++ `codes` and `actor` each gain `<svc>.config.manage` (owner that service, active, internal, never grantable to apps),
++ implemented by `GET /internal/config`, `GET|POST /internal/config/:namespace` and
++ `PUT|DELETE /internal/config/:namespace/secrets/:key`. Only Network's admin proxy, acting for the owner, holds them;
++ Network uses its own admin config routes.
++- Contracts keeps no default-grants list: Network's seeded default grants live in OpenVibe.Network, so granting
++ these nine to the network service is that repository's change.
++
## 0.287.0 — 2026-11-04
- **`billing.credit.settled@1`** (AI Exchange money, D1 and D9).
ddiff --git a/README.md b/README.md
iindex 94e9d5ae..85b197e5 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Machine-readable contracts for the whole OpenVibe network.
--**Status:** alpha (first released in Wave 1; the release is package.json's `version`, tagged). At v0.287.0: 975 schemas in `contracts/catalog.json`, 45 service manifests, 440 capability manifests, 13 namespace manifests and the staff capability map. Service repositories run `openvibe-contracts-check` and the pin-drift check in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Consumers pin release tags (each repository's `STATUS.json` names its pin). `node scripts/docs-currency.js <root>` checks every checkout's README and `STATUS.json` against its code: the pins and versions they name, a library's consumer list, the deployed flag against the manifest, README links, and the paths and migrations they name. `node scripts/estate.js` generates `docs/ESTATE.md` and `manifests/repositories/` from the estate checkouts, and `--check` fails on drift (plan T1 step 3).
++**Status:** alpha (first released in Wave 1; the release is package.json's `version`, tagged). At v0.288.0: 978 schemas in `contracts/catalog.json`, 60 service manifests, 450 capability manifests, 13 namespace manifests and the staff capability map. Service repositories run `openvibe-contracts-check` and the pin-drift check in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Consumers pin release tags (each repository's `STATUS.json` names its pin). `node scripts/docs-currency.js <root>` checks every checkout's README and `STATUS.json` against its code: the pins and versions they name, a library's consumer list, the deployed flag against the manifest, README links, and the paths and migrations they name. `node scripts/estate.js` generates `docs/ESTATE.md` and `manifests/repositories/` from the estate checkouts, and `--check` fails on drift (plan T1 step 3).
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**License:** AGPL-3.0 (same as every OpenVibe service).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex bbbdb421..5923f9fa 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -209,6 +209,33 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "common.config-namespace",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "common/config-namespace.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "common.config-change",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "common/config-change.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "common.config-secret-put",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "common/config-secret-put.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "common.content-labels",
"version": "1.0.0",
ddiff --git a/contracts/common/config-change.v1.json b/contracts/common/config-change.v1.json
nnew file mode 100644
iindex 00000000..53f33d22
---- /dev/null
++++ b/contracts/common/config-change.v1.json
@@@ -0,0 +1,43 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-change.v1.json",
++ "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}$"
++ }
++ }
++ }
++}
ddiff --git a/contracts/common/config-namespace.v1.json b/contracts/common/config-namespace.v1.json
nnew file mode 100644
iindex 00000000..290ec78a
---- /dev/null
++++ b/contracts/common/config-namespace.v1.json
@@@ -0,0 +1,138 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-namespace.v1.json",
++ "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).",
++ "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": "#/$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) and x-unit (nano_usd, ms or bps)."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "config-snapshot.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "config-snapshot.v1.json"
++ },
++ {
++ "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": "../identity/subject-ref.v1.json"
++ },
++ "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."
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/common/config-secret-put.v1.json b/contracts/common/config-secret-put.v1.json
nnew file mode 100644
iindex 00000000..53957b2e
---- /dev/null
++++ b/contracts/common/config-secret-put.v1.json
@@@ -0,0 +1,23 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-secret-put.v1.json",
++ "title": "ConfigSecretPut",
++ "description": "common.config-secret-put@1 (AI Exchange D11): body of PUT /internal/config/:namespace/secrets/:key on a service's openvibe-shared/config admin routes. The value is written once, sealed as an envelope under the service's KEK (or kept as today when it has none), and never returned, logged or put in values_json. Its answer is the marker: { redacted: true, fingerprint, hint (four characters), set_at, set_by, bound_to }, as common.config-namespace@1 defines it. A `x-bind` key on the namespace schema binds the secret to another key's value (a provider key to its base_url origin); a changed bind no longer opens the value until the owner enters it again.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["value", "reason"],
++ "properties": {
++ "value": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 8192,
++ "description": "The secret, once."
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the secret is set; it is the revision's reason in the history."
++ }
++ }
++}
ddiff --git a/fixtures/common.config-change/invalid/missing-reason.json b/fixtures/common.config-change/invalid/missing-reason.json
nnew file mode 100644
iindex 00000000..7af0759f
---- /dev/null
++++ b/fixtures/common.config-change/invalid/missing-reason.json
@@@ -0,0 +1,5 @@
++{
++ "values": {
++ "site_name": "OpenVibe.Live"
++ }
++}
ddiff --git a/fixtures/common.config-change/invalid/unknown-field.json b/fixtures/common.config-change/invalid/unknown-field.json
nnew file mode 100644
iindex 00000000..674dc339
---- /dev/null
++++ b/fixtures/common.config-change/invalid/unknown-field.json
@@@ -0,0 +1,10 @@
++{
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "reason": "Set the site name",
++ "actor": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ }
++}
ddiff --git a/fixtures/common.config-change/invalid/unset-not-a-list.json b/fixtures/common.config-change/invalid/unset-not-a-list.json
nnew file mode 100644
iindex 00000000..2ff28f3b
---- /dev/null
++++ b/fixtures/common.config-change/invalid/unset-not-a-list.json
@@@ -0,0 +1,7 @@
++{
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "reason": "Remove the old banner",
++ "unset": "legacy_banner"
++}
ddiff --git a/fixtures/common.config-change/valid/merge-change.json b/fixtures/common.config-change/valid/merge-change.json
nnew file mode 100644
iindex 00000000..1c131db2
---- /dev/null
++++ b/fixtures/common.config-change/valid/merge-change.json
@@@ -0,0 +1,12 @@
++{
++ "values": {
++ "max_bitrate_kbps": 7000,
++ "youtube_api_key": {
++ "redacted": true,
++ "fingerprint": "c61d1b1293d59c54c56624f6d6161b8af976449db60f230ed977dcf3ba4bd31b"
++ }
++ },
++ "reason": "Raise the bitrate cap for the tournament",
++ "merge": true,
++ "unset": ["legacy_banner"]
++}
ddiff --git a/fixtures/common.config-change/valid/replace.json b/fixtures/common.config-change/valid/replace.json
nnew file mode 100644
iindex 00000000..eda06f02
---- /dev/null
++++ b/fixtures/common.config-change/valid/replace.json
@@@ -0,0 +1,6 @@
++{
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "reason": "Set the site name"
++}
ddiff --git a/fixtures/common.config-namespace/invalid/extra-field.json b/fixtures/common.config-namespace/invalid/extra-field.json
nnew file mode 100644
iindex 00000000..770a1b67
---- /dev/null
++++ b/fixtures/common.config-namespace/invalid/extra-field.json
@@@ -0,0 +1,19 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "classification": {
++ "site_name": "public"
++ },
++ "sealed": true,
++ "pins": [],
++ "schema": null,
++ "active": null,
++ "last_known_good": null,
++ "secret_values": {
++ "youtube_api_key": "AIza-example-secret"
++ }
++}
ddiff --git a/fixtures/common.config-namespace/invalid/pins-not-keys.json b/fixtures/common.config-namespace/invalid/pins-not-keys.json
nnew file mode 100644
iindex 00000000..274282e5
---- /dev/null
++++ b/fixtures/common.config-namespace/invalid/pins-not-keys.json
@@@ -0,0 +1,16 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "classification": {
++ "site_name": "public"
++ },
++ "sealed": true,
++ "pins": ["not a key"],
++ "schema": null,
++ "active": null,
++ "last_known_good": null
++}
ddiff --git a/fixtures/common.config-namespace/invalid/schema-string.json b/fixtures/common.config-namespace/invalid/schema-string.json
nnew file mode 100644
iindex 00000000..3b56cfae
---- /dev/null
++++ b/fixtures/common.config-namespace/invalid/schema-string.json
@@@ -0,0 +1,16 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "classification": {
++ "site_name": "public"
++ },
++ "sealed": true,
++ "pins": [],
++ "schema": "object",
++ "active": null,
++ "last_known_good": null
++}
ddiff --git a/fixtures/common.config-namespace/invalid/secret-value.json b/fixtures/common.config-namespace/invalid/secret-value.json
nnew file mode 100644
iindex 00000000..aaa6182c
---- /dev/null
++++ b/fixtures/common.config-namespace/invalid/secret-value.json
@@@ -0,0 +1,20 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "youtube_api_key": {
++ "redacted": true,
++ "fingerprint": "c61d1b1293d59c54c56624f6d6161b8af976449db60f230ed977dcf3ba4bd31b",
++ "value": "AIza-example-secret"
++ }
++ },
++ "classification": {
++ "youtube_api_key": "secret"
++ },
++ "sealed": true,
++ "pins": [],
++ "schema": null,
++ "active": null,
++ "last_known_good": null
++}
ddiff --git a/fixtures/common.config-namespace/invalid/unknown-classification.json b/fixtures/common.config-namespace/invalid/unknown-classification.json
nnew file mode 100644
iindex 00000000..a0fa515f
---- /dev/null
++++ b/fixtures/common.config-namespace/invalid/unknown-classification.json
@@@ -0,0 +1,16 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "site_name": "OpenVibe.Live"
++ },
++ "classification": {
++ "site_name": "private"
++ },
++ "sealed": true,
++ "pins": [],
++ "schema": null,
++ "active": null,
++ "last_known_good": null
++}
ddiff --git a/fixtures/common.config-namespace/valid/empty-namespace.json b/fixtures/common.config-namespace/valid/empty-namespace.json
nnew file mode 100644
iindex 00000000..406cb060
---- /dev/null
++++ b/fixtures/common.config-namespace/valid/empty-namespace.json
@@@ -0,0 +1,12 @@
++{
++ "service": "ai",
++ "namespace": "ai.exchange",
++ "revision": null,
++ "values": {},
++ "classification": {},
++ "sealed": false,
++ "pins": [],
++ "schema": null,
++ "active": null,
++ "last_known_good": null
++}
ddiff --git a/fixtures/common.config-namespace/valid/live-site-settings.json b/fixtures/common.config-namespace/valid/live-site-settings.json
nnew file mode 100644
iindex 00000000..c59c53a9
---- /dev/null
++++ b/fixtures/common.config-namespace/valid/live-site-settings.json
@@@ -0,0 +1,152 @@
++{
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "values": {
++ "site_name": "OpenVibe.Live",
++ "registration_open": true,
++ "max_bitrate_kbps": 6000,
++ "youtube_api_key": {
++ "redacted": true,
++ "fingerprint": "c61d1b1293d59c54c56624f6d6161b8af976449db60f230ed977dcf3ba4bd31b",
++ "hint": "a1b2",
++ "set_at": "2026-11-04T18:34:00.000Z",
++ "set_by": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "bound_to": "https://www.googleapis.com"
++ },
++ "home_banner": {
++ "text": "Welcome",
++ "link": "/about"
++ }
++ },
++ "classification": {
++ "site_name": "public",
++ "registration_open": "public",
++ "max_bitrate_kbps": "internal",
++ "youtube_api_key": "secret",
++ "home_banner": "public"
++ },
++ "sealed": true,
++ "pins": ["youtube_api_key"],
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "type": "object",
++ "properties": {
++ "site_name": {
++ "type": "string",
++ "title": "Site name",
++ "description": "The name shown in the page header.",
++ "x-group": "Site"
++ },
++ "registration_open": {
++ "type": "boolean",
++ "title": "Registration open",
++ "x-group": "Site"
++ },
++ "max_bitrate_kbps": {
++ "type": "integer",
++ "title": "Maximum bitrate",
++ "x-group": "Streaming",
++ "x-unit": "bps",
++ "x-danger": true
++ },
++ "youtube_api_key": {
++ "type": "string",
++ "title": "YouTube API key",
++ "x-group": "Integrations",
++ "x-env": "YOUTUBE_API_KEY",
++ "x-bind": "base_url"
++ },
++ "base_url": {
++ "type": "string",
++ "title": "Base URL",
++ "x-group": "Integrations"
++ }
++ }
++ },
++ "active": {
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "previous_revision": 11,
++ "state": "active",
++ "values": {
++ "site_name": "OpenVibe.Live",
++ "registration_open": true,
++ "max_bitrate_kbps": 6000,
++ "youtube_api_key": {
++ "redacted": true,
++ "fingerprint": "c61d1b1293d59c54c56624f6d6161b8af976449db60f230ed977dcf3ba4bd31b"
++ },
++ "home_banner": {
++ "text": "Welcome",
++ "link": "/about"
++ }
++ },
++ "classification": {
++ "site_name": "public",
++ "registration_open": "public",
++ "max_bitrate_kbps": "internal",
++ "youtube_api_key": "secret",
++ "home_banner": "public"
++ },
++ "checksum": "29cd8cc21471b306491daa584d01892c05b41e5e51d5bf83aaf5fc6f4a28ca87",
++ "created_at": "2026-09-25T18:02:11.412Z",
++ "created_by": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "activated_at": "2026-09-25T18:02:11.530Z",
++ "activated_by": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "reason": "Raise the bitrate cap for the tournament",
++ "error": null,
++ "copied_from": null
++ },
++ "last_known_good": {
++ "service": "live",
++ "namespace": "live.site_settings",
++ "revision": 12,
++ "previous_revision": 11,
++ "state": "active",
++ "values": {
++ "site_name": "OpenVibe.Live",
++ "registration_open": true,
++ "max_bitrate_kbps": 6000,
++ "youtube_api_key": {
++ "redacted": true,
++ "fingerprint": "c61d1b1293d59c54c56624f6d6161b8af976449db60f230ed977dcf3ba4bd31b"
++ },
++ "home_banner": {
++ "text": "Welcome",
++ "link": "/about"
++ }
++ },
++ "classification": {
++ "site_name": "public",
++ "registration_open": "public",
++ "max_bitrate_kbps": "internal",
++ "youtube_api_key": "secret",
++ "home_banner": "public"
++ },
++ "checksum": "29cd8cc21471b306491daa584d01892c05b41e5e51d5bf83aaf5fc6f4a28ca87",
++ "created_at": "2026-09-25T18:02:11.412Z",
++ "created_by": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "activated_at": "2026-09-25T18:02:11.530Z",
++ "activated_by": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "reason": "Raise the bitrate cap for the tournament",
++ "error": null,
++ "copied_from": null
++ }
++}
ddiff --git a/fixtures/common.config-secret-put/invalid/empty-value.json b/fixtures/common.config-secret-put/invalid/empty-value.json
nnew file mode 100644
iindex 00000000..445acdef
---- /dev/null
++++ b/fixtures/common.config-secret-put/invalid/empty-value.json
@@@ -0,0 +1,4 @@
++{
++ "value": "",
++ "reason": "Set the YouTube API key"
++}
ddiff --git a/fixtures/common.config-secret-put/invalid/missing-reason.json b/fixtures/common.config-secret-put/invalid/missing-reason.json
nnew file mode 100644
iindex 00000000..52c019e5
---- /dev/null
++++ b/fixtures/common.config-secret-put/invalid/missing-reason.json
@@@ -0,0 +1,3 @@
++{
++ "value": "AIza-example-secret"
++}
ddiff --git a/fixtures/common.config-secret-put/valid/put.json b/fixtures/common.config-secret-put/valid/put.json
nnew file mode 100644
iindex 00000000..0f5af21e
---- /dev/null
++++ b/fixtures/common.config-secret-put/valid/put.json
@@@ -0,0 +1,4 @@
++{
++ "value": "AIza-example-secret",
++ "reason": "Set the YouTube API key"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex d5323e60..c9358d6d 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -2966,6 +2966,264 @@
]
}
},
++ "common.config-namespace": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-namespace.v1.json",
++ "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).",
++ "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": "#/$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) and x-unit (nano_usd, ms or bps)."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "config-snapshot.v1.json"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "last_known_good": {
++ "description": "The last revision that activated cleanly (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "config-snapshot.v1.json"
++ },
++ {
++ "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": "../identity/subject-ref.v1.json"
++ },
++ "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-change": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-change.v1.json",
++ "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-secret-put": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/config-secret-put.v1.json",
++ "title": "ConfigSecretPut",
++ "description": "common.config-secret-put@1 (AI Exchange D11): body of PUT /internal/config/:namespace/secrets/:key on a service's openvibe-shared/config admin routes. The value is written once, sealed as an envelope under the service's KEK (or kept as today when it has none), and never returned, logged or put in values_json. Its answer is the marker: { redacted: true, fingerprint, hint (four characters), set_at, set_by, bound_to }, as common.config-namespace@1 defines it. A `x-bind` key on the namespace schema binds the secret to another key's value (a provider key to its base_url origin); a changed bind no longer opens the value until the owner enters it again.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "value",
++ "reason"
++ ],
++ "properties": {
++ "value": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 8192,
++ "description": "The secret, once."
++ },
++ "reason": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500,
++ "description": "Why the secret is set; it is the revision's reason in the history."
++ }
++ }
++ }
++ },
"common.content-labels": {
"version": "1.0.0",
"owner": "contracts",
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 7503a6a4..305cce79 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.287.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1903,6 +1903,487 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**actor.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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": [
++ "actor.config"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.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.Actor through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**actor.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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": [
++ "actor.config"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.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.Actor through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**actor.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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": [
++ "actor.config"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.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.Actor through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**actor.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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": [
++ "actor.config"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.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.Actor through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**actor.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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": [
++ "actor.config"
++ ],
++ "x-openvibe-capabilities": [
++ "actor.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "actor.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": {
@@@ -3092,6 +3573,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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.resource-control-request.v1": {
"title": "ResourceControlRequest",
"description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 8d8b53b5..216661d7 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.287.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -3950,35 +3950,96 @@
}
}
},
-- "/v1/models": {
++ "/internal/config": {
"get": {
-- "operationId": "get_v1_models",
-- "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
-- "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "operationId": "get_internal_config",
++ "summary": "Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns ",
++ "description": "**ai.config.manage** (internal): Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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": [
-- "ai.model"
++ "ai.config"
],
"x-openvibe-capabilities": [
-- "ai.model.read"
++ "ai.config.manage"
],
"x-openvibe-visibility": [
-- "public"
++ "internal"
],
-- "x-openvibe-quota-class": "ai-read",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "ai.model.read"
++ "ai.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/ai.model-read-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -3996,36 +4057,95 @@
}
}
},
-- "/v1/models/{id}": {
++ "/internal/config/{namespace}": {
"get": {
-- "operationId": "get_v1_models_id",
-- "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
-- "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "operationId": "get_internal_config_namespace",
++ "summary": "Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns ",
++ "description": "**ai.config.manage** (internal): Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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": [
-- "ai.model"
++ "ai.config"
],
"x-openvibe-capabilities": [
-- "ai.model.read"
++ "ai.config.manage"
],
"x-openvibe-visibility": [
-- "public"
++ "internal"
],
-- "x-openvibe-quota-class": "ai-read",
++ "x-openvibe-quota-class": "config-manage",
"security": [
{
"openvibeToken": [
-- "ai.model.read"
++ "ai.config.manage"
]
}
],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
"parameters": [
{
-- "name": "id",
++ "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": {
@@@ -4034,7 +4154,7 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/ai.model-read-result.v1"
++ "$ref": "#/components/schemas/common.config-namespace.v1"
}
}
}
@@@ -4050,93 +4170,454 @@
}
}
}
-- }
-- }
-- },
-- "components": {
-- "schemas": {
-- "ai.attribution-quota-put.v1": {
-- "title": "AiAttributionQuotaPut",
-- "description": "ai.attribution-quota-put@1 (OpenVibe.AI; roadmap WS-O task 2): a cap on runs attributed to one of the caller's own entities, PUT /api/v1/attribution-quotas/{attribution} with capability ai.quota.attribution.manage; the attribution's service must be the caller (Live caps live:user:<id>, a streamer's AI viewers). Over the cap a run is refused 429 quota.exceeded before any provider is called. Cost counts what runs have cost so far in the window, so the run that crosses the cap finishes and the next is refused.",
-- "type": "object",
-- "required": [
-- "window"
++ },
++ "post": {
++ "operationId": "post_internal_config_namespace",
++ "summary": "Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns ",
++ "description": "**ai.config.manage** (internal): Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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": [
++ "ai.config"
],
-- "additionalProperties": false,
-- "properties": {
-- "window": {
-- "enum": [
-- "hour",
-- "day"
++ "x-openvibe-capabilities": [
++ "ai.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.config.manage"
]
-- },
-- "max_cost_usd": {
-- "type": "number",
-- "minimum": 0,
-- "maximum": 10000
-- },
-- "max_requests": {
-- "type": "integer",
-- "minimum": 0,
-- "maximum": 1000000
-- },
-- "workflow_prefix": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9_.-]{1,79}$",
-- "description": "Only runs of workflows whose key starts with this (live.viewers.)."
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ }
++ }
}
},
-- "anyOf": [
++ "parameters": [
{
-- "required": [
-- "max_cost_usd"
-- ]
++ "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 ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns ",
++ "description": "**ai.config.manage** (internal): Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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": [
++ "ai.config"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
{
-- "required": [
-- "max_requests"
++ "openvibeToken": [
++ "ai.config.manage"
]
}
-- ]
-- },
-- "ai.attribution-quota.v1": {
-- "title": "AiAttributionQuota",
-- "description": "ai.attribution-quota@1 (OpenVibe.AI; roadmap WS-O task 2): a cap on one attribution as its service reads it (GET or PUT /api/v1/attribution-quotas/{attribution}), with what the current window has used.",
-- "type": "object",
-- "required": [
-- "attribution",
-- "window",
-- "used",
-- "window_resets_at",
-- "updated_at"
],
-- "additionalProperties": false,
-- "properties": {
-- "attribution": {
-- "type": "string",
-- "pattern": "^[a-z][a-z0-9-]{1,31}:[a-z][a-z0-9_]{1,31}:[A-Za-z0-9_.-]{1,80}$",
-- "description": "<service>:<type>:<id>, the run's attribution as AI keys it (Live: live:user:<id>)."
-- },
-- "window": {
-- "enum": [
-- "hour",
-- "day"
-- ]
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/common.config-change.v1"
++ },
++ "parameters": [
++ {
++ "name": "namespace",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
},
-- "workflow_prefix": {
-- "type": [
-- "string",
-- "null"
-- ]
++ {
++ "name": "key",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
},
-- "max_cost_usd": {
-- "type": [
-- "number",
-- "null"
-- ],
-- "minimum": 0
++ {
++ "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."
},
-- "max_requests": {
++ {
++ "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 ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns ",
++ "description": "**ai.config.manage** (internal): Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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": [
++ "ai.config"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.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"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/v1/models": {
++ "get": {
++ "operationId": "get_v1_models",
++ "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
++ "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "tags": [
++ "ai.model"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.model.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.model.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.model-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/v1/models/{id}": {
++ "get": {
++ "operationId": "get_v1_models_id",
++ "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
++ "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "tags": [
++ "ai.model"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.model.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.model.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.model-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "ai.attribution-quota-put.v1": {
++ "title": "AiAttributionQuotaPut",
++ "description": "ai.attribution-quota-put@1 (OpenVibe.AI; roadmap WS-O task 2): a cap on runs attributed to one of the caller's own entities, PUT /api/v1/attribution-quotas/{attribution} with capability ai.quota.attribution.manage; the attribution's service must be the caller (Live caps live:user:<id>, a streamer's AI viewers). Over the cap a run is refused 429 quota.exceeded before any provider is called. Cost counts what runs have cost so far in the window, so the run that crosses the cap finishes and the next is refused.",
++ "type": "object",
++ "required": [
++ "window"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "window": {
++ "enum": [
++ "hour",
++ "day"
++ ]
++ },
++ "max_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 10000
++ },
++ "max_requests": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 1000000
++ },
++ "workflow_prefix": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_.-]{1,79}$",
++ "description": "Only runs of workflows whose key starts with this (live.viewers.)."
++ }
++ },
++ "anyOf": [
++ {
++ "required": [
++ "max_cost_usd"
++ ]
++ },
++ {
++ "required": [
++ "max_requests"
++ ]
++ }
++ ]
++ },
++ "ai.attribution-quota.v1": {
++ "title": "AiAttributionQuota",
++ "description": "ai.attribution-quota@1 (OpenVibe.AI; roadmap WS-O task 2): a cap on one attribution as its service reads it (GET or PUT /api/v1/attribution-quotas/{attribution}), with what the current window has used.",
++ "type": "object",
++ "required": [
++ "attribution",
++ "window",
++ "used",
++ "window_resets_at",
++ "updated_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "attribution": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,31}:[a-z][a-z0-9_]{1,31}:[A-Za-z0-9_.-]{1,80}$",
++ "description": "<service>:<type>:<id>, the run's attribution as AI keys it (Live: live:user:<id>)."
++ },
++ "window": {
++ "enum": [
++ "hour",
++ "day"
++ ]
++ },
++ "workflow_prefix": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "max_cost_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "max_requests": {
"type": [
"integer",
"null"
@@@ -7213,171 +7694,598 @@
}
}
}
-- ]
++ ]
++ },
++ "common.action-item.v1": {
++ "title": "ActionItem",
++ "description": "One piece of a person's agent work in the Action Center (plan 2026-10-28 §3.10): an agent task, a coding task, an AI run, or an approval waiting on them. Each authority answers its own items in this shape (common.action-list-result@1); OpenVibe.Network merges them into buckets (network.me-actions-result@1). Text is plain (no markup); a cost is what the person was charged in USD, null when unknown or free; href is a page on the authority's own public site.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "service",
++ "id",
++ "kind",
++ "title",
++ "bucket",
++ "state",
++ "created_at",
++ "updated_at",
++ "href",
++ "actions"
++ ],
++ "properties": {
++ "service": {
++ "enum": [
++ "actor",
++ "codes",
++ "ai",
++ "network",
++ "run",
++ "domains"
++ ],
++ "description": "The authority that owns the item."
++ },
++ "id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "The authority's own id (tsk_…, a Codes task id, run_…, a confirmation id, dpr_…)."
++ },
++ "kind": {
++ "enum": [
++ "agent_task",
++ "code_task",
++ "ai_run",
++ "approval",
++ "desk_session"
++ ],
++ "description": "What the item is."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 160,
++ "description": "A short plain-text name: the start of the task text, the workflow, or what the approval is for."
++ },
++ "summary": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 400,
++ "description": "The last step, the outcome, or what will happen on approval."
++ },
++ "bucket": {
++ "enum": [
++ "running",
++ "waiting",
++ "scheduled",
++ "done",
++ "failed"
++ ],
++ "description": "Where the Action Center shows it: running (queued or working), waiting (needs the person), scheduled (will run later), done, failed (failed, cancelled or expired)."
++ },
++ "state": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 32,
++ "description": "The authority's own state word (queued, running, needs_you, succeeded, failed, cancelled, pending…)."
++ },
++ "agent": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80,
++ "description": "The agent, model or workflow doing it, when known."
++ },
++ "cost_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "What it cost the person, in US dollars; null when unknown or free."
++ },
++ "steps": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Tool calls or steps so far, when counted."
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 400,
++ "description": "Why it failed, in words a person can act on."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "next_run_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When a scheduled item runs next."
++ },
++ "href": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://",
++ "maxLength": 2048,
++ "description": "The item's page: its live steps, receipt or approval."
++ },
++ "actions": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "What the person can do from the list. approve/decline/cancel/retry go through OpenVibe.Network (POST /api/v1/me/actions/:service/:id/:action); open is a link.",
++ "items": {
++ "enum": [
++ "open",
++ "approve",
++ "decline",
++ "cancel",
++ "retry"
++ ]
++ }
++ }
++ }
++ },
++ "common.action-list-result.v1": {
++ "title": "ActionListResult",
++ "description": "One authority's Action Center items for a person (capabilities actor.actions.read, codes.actions.read, ai.actions.read, domains.actions.read): GET /api/v1/actions?subject=usr_… with the caller's service token. Everything running, waiting or scheduled, then done and failed items from the last 30 days, newest first, at most 50.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 50,
++ "items": {
++ "$ref": "#/components/schemas/common.action-item.v1"
++ }
++ }
++ }
++ },
++ "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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.action-item.v1": {
-- "title": "ActionItem",
-- "description": "One piece of a person's agent work in the Action Center (plan 2026-10-28 §3.10): an agent task, a coding task, an AI run, or an approval waiting on them. Each authority answers its own items in this shape (common.action-list-result@1); OpenVibe.Network merges them into buckets (network.me-actions-result@1). Text is plain (no markup); a cost is what the person was charged in USD, null when unknown or free; href is a page on the authority's own public site.",
++ "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",
-- "additionalProperties": false,
"required": [
"service",
-- "id",
-- "kind",
-- "title",
-- "bucket",
++ "namespace",
++ "revision",
++ "previous_revision",
"state",
++ "values",
++ "classification",
++ "checksum",
"created_at",
-- "updated_at",
-- "href",
-- "actions"
++ "created_by"
],
++ "additionalProperties": false,
"properties": {
"service": {
-- "enum": [
-- "actor",
-- "codes",
-- "ai",
-- "network",
-- "run",
-- "domains"
-- ],
-- "description": "The authority that owns the item."
-- },
-- "id": {
"type": "string",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "The authority's own id (tsk_…, a Codes task id, run_…, a confirmation id, dpr_…)."
-- },
-- "kind": {
-- "enum": [
-- "agent_task",
-- "code_task",
-- "ai_run",
-- "approval",
-- "desk_session"
-- ],
-- "description": "What the item is."
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
},
-- "title": {
++ "namespace": {
"type": "string",
-- "minLength": 1,
-- "maxLength": 160,
-- "description": "A short plain-text name: the start of the task text, the workflow, or what the approval is for."
++ "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."
},
-- "summary": {
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
"type": [
-- "string",
++ "integer",
"null"
],
-- "maxLength": 400,
-- "description": "The last step, the outcome, or what will happen on approval."
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
},
-- "bucket": {
++ "state": {
"enum": [
-- "running",
-- "waiting",
-- "scheduled",
-- "done",
-- "failed"
-- ],
-- "description": "Where the Action Center shows it: running (queued or working), waiting (needs the person), scheduled (will run later), done, failed (failed, cancelled or expired)."
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
},
-- "state": {
++ "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",
-- "minLength": 1,
-- "maxLength": 32,
-- "description": "The authority's own state word (queued, running, needs_you, succeeded, failed, cancelled, pending…)."
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
},
-- "agent": {
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
"type": [
"string",
"null"
],
-- "maxLength": 80,
-- "description": "The agent, model or workflow doing it, when known."
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
},
-- "cost_usd": {
-- "type": [
-- "number",
-- "null"
-- ],
-- "minimum": 0,
-- "description": "What it cost the person, in US dollars; null when unknown or free."
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
-- "steps": {
++ "reason": {
"type": [
-- "integer",
++ "string",
"null"
],
-- "minimum": 0,
-- "description": "Tool calls or steps so far, when counted."
++ "maxLength": 500
},
"error": {
"type": [
"string",
"null"
],
-- "maxLength": 400,
-- "description": "Why it failed, in words a person can act on."
-- },
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": "string",
-- "format": "date-time"
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
},
-- "next_run_at": {
++ "copied_from": {
"type": [
-- "string",
++ "integer",
"null"
],
-- "format": "date-time",
-- "description": "When a scheduled item runs next."
-- },
-- "href": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https://",
-- "maxLength": 2048,
-- "description": "The item's page: its live steps, receipt or approval."
-- },
-- "actions": {
-- "type": "array",
-- "maxItems": 4,
-- "description": "What the person can do from the list. approve/decline/cancel/retry go through OpenVibe.Network (POST /api/v1/me/actions/:service/:id/:action); open is a link.",
-- "items": {
-- "enum": [
-- "open",
-- "approve",
-- "decline",
-- "cancel",
-- "retry"
-- ]
-- }
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
}
-- }
-- },
-- "common.action-list-result.v1": {
-- "title": "ActionListResult",
-- "description": "One authority's Action Center items for a person (capabilities actor.actions.read, codes.actions.read, ai.actions.read, domains.actions.read): GET /api/v1/actions?subject=usr_… with the caller's service token. Everything running, waiting or scheduled, then done and failed items from the last 30 days, newest first, at most 50.",
-- "type": "object",
-- "additionalProperties": false,
-- "required": [
-- "items"
-- ],
-- "properties": {
-- "items": {
-- "type": "array",
-- "maxItems": 50,
-- "items": {
-- "$ref": "#/components/schemas/common.action-item.v1"
++ },
++ "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.entity-ref.v1": {
"title": "EntityRef",
ddiff --git a/generated/openapi/bench.json b/generated/openapi/bench.json
iindex 8c858b47..a521ad68 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.287.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1172,6 +1172,487 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**bench.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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": [
++ "bench.config"
++ ],
++ "x-openvibe-capabilities": [
++ "bench.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "bench.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.Bench through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**bench.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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": [
++ "bench.config"
++ ],
++ "x-openvibe-capabilities": [
++ "bench.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "bench.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.Bench through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**bench.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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": [
++ "bench.config"
++ ],
++ "x-openvibe-capabilities": [
++ "bench.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "bench.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.Bench through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**bench.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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": [
++ "bench.config"
++ ],
++ "x-openvibe-capabilities": [
++ "bench.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "bench.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.Bench through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**bench.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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": [
++ "bench.config"
++ ],
++ "x-openvibe-capabilities": [
++ "bench.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "bench.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": {
@@@ -1909,125 +2390,552 @@
],
"description": "codes: an openvibe.codes task. run: a Run worker. node: the person's own machine. submitted: nobody ran it."
},
-- "id": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 160
++ "id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 160
++ }
++ }
++ },
++ "task": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "task_id",
++ "score",
++ "cost_usd",
++ "ms",
++ "tokens",
++ "artifacts"
++ ],
++ "properties": {
++ "task_id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "score": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 100
++ },
++ "cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "artifacts": {
++ "type": "array",
++ "maxItems": 40,
++ "items": {
++ "$ref": "#/components/schemas/bench.run.v1/$defs/artifact"
++ }
++ },
++ "transcript_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ }
++ }
++ },
++ "artifact": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "kind",
++ "url"
++ ],
++ "properties": {
++ "kind": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40
++ },
++ "url": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 500
++ }
++ }
++ }
++ }
++ },
++ "bench.task.v1": {
++ "title": "BenchTask",
++ "description": "bench.task@1: a benchmark task on OpenVibe.Bench. KIND_ID names the kind; PROMPT is the task; TASK_CLASS is an optional classifier for grouping; PUBLIC says whether the task may appear in the open dataset.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "kind_id",
++ "prompt",
++ "public",
++ "created_by",
++ "created_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string"
++ },
++ "kind_id": {
++ "type": "string"
++ },
++ "prompt": {
++ "type": "string"
++ },
++ "task_class": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "public": {
++ "type": "boolean"
++ },
++ "created_by": {
++ "type": "string"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "active": {
++ "description": "The active revision as a snapshot (common.config-snapshot@1), or null.",
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/common.config-snapshot.v1"
++ },
++ {
++ "type": "null"
}
-- }
++ ]
},
-- "task": {
++ "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": [
-- "task_id",
-- "score",
-- "cost_usd",
-- "ms",
-- "tokens",
-- "artifacts"
++ "redacted",
++ "fingerprint"
],
"properties": {
-- "task_id": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 80
-- },
-- "score": {
-- "type": "number",
-- "minimum": 0,
-- "maximum": 100
++ "redacted": {
++ "const": true
},
-- "cost_usd": {
-- "type": "number",
-- "minimum": 0
++ "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."
},
-- "ms": {
-- "type": "integer",
-- "minimum": 0
++ "hint": {
++ "type": "string",
++ "minLength": 4,
++ "maxLength": 4,
++ "description": "The four-character hint (the value's last four characters), never more."
},
-- "tokens": {
-- "type": "integer",
-- "minimum": 0
++ "set_at": {
++ "type": "string",
++ "format": "date-time"
},
-- "artifacts": {
-- "type": "array",
-- "maxItems": 40,
-- "items": {
-- "$ref": "#/components/schemas/bench.run.v1/$defs/artifact"
-- }
++ "set_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
},
-- "transcript_url": {
++ "bound_to": {
"type": [
"string",
"null"
],
-- "maxLength": 500
-- }
-- }
-- },
-- "artifact": {
-- "type": "object",
-- "additionalProperties": false,
-- "required": [
-- "kind",
-- "url"
-- ],
-- "properties": {
-- "kind": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 40
-- },
-- "url": {
-- "type": "string",
-- "minLength": 1,
-- "maxLength": 500
++ "maxLength": 2048,
++ "description": "The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin."
}
}
}
}
},
-- "bench.task.v1": {
-- "title": "BenchTask",
-- "description": "bench.task@1: a benchmark task on OpenVibe.Bench. KIND_ID names the kind; PROMPT is the task; TASK_CLASS is an optional classifier for grouping; PUBLIC says whether the task may appear in the open dataset.",
++ "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",
-- "additionalProperties": false,
"required": [
-- "id",
-- "kind_id",
-- "prompt",
-- "public",
-- "created_by",
-- "created_at"
++ "service",
++ "namespace",
++ "revision",
++ "previous_revision",
++ "state",
++ "values",
++ "classification",
++ "checksum",
++ "created_at",
++ "created_by"
],
++ "additionalProperties": false,
"properties": {
-- "id": {
-- "type": "string"
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
},
-- "kind_id": {
-- "type": "string"
++ "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."
},
-- "prompt": {
-- "type": "string"
++ "revision": {
++ "type": "integer",
++ "minimum": 1
},
-- "task_class": {
++ "previous_revision": {
"type": [
-- "string",
++ "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"
]
},
-- "public": {
-- "type": "boolean"
++ "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."
++ }
++ ]
++ }
},
-- "created_by": {
-- "type": "string"
++ "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",
@@@ -2094,6 +3002,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/billing.json b/generated/openapi/billing.json
iindex 5301a7a6..8614b049 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.287.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1933,6 +1933,487 @@
}
}
},
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common",
++ "description": "**billing.config.manage** (internal): Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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": [
++ "billing.config"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.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 billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common",
++ "description": "**billing.config.manage** (internal): Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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": [
++ "billing.config"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.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 billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common",
++ "description": "**billing.config.manage** (internal): Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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": [
++ "billing.config"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.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 billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common",
++ "description": "**billing.config.manage** (internal): Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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": [
++ "billing.config"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.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 billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one as common",
++ "description": "**billing.config.manage** (internal): Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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": [
++ "billing.config"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.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/credits/holds": {
"post": {
"operationId": "post_internal_credits_holds",
@@@ -4504,6 +4985,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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.entity-ref.v1": {
"title": "EntityRef",
"description": "Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.",
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex d037c657..417adc05 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.287.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex bc0d95d3..e26f6dd8 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.287.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 9d525e41..58c5246d 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.287.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex b5b66942..238c32c4 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.287.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -229,6 +229,487 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**codes.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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": [
++ "codes.config"
++ ],
++ "x-openvibe-capabilities": [
++ "codes.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "codes.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.Codes through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**codes.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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": [
++ "codes.config"
++ ],
++ "x-openvibe-capabilities": [
++ "codes.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "codes.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.Codes through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**codes.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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": [
++ "codes.config"
++ ],
++ "x-openvibe-capabilities": [
++ "codes.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "codes.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.Codes through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**codes.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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": [
++ "codes.config"
++ ],
++ "x-openvibe-capabilities": [
++ "codes.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "codes.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.Codes through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**codes.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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": [
++ "codes.config"
++ ],
++ "x-openvibe-capabilities": [
++ "codes.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "codes.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": {
@@@ -917,150 +1398,577 @@
],
"properties": {
"service": {
-- "enum": [
-- "actor",
-- "codes",
-- "ai",
-- "network",
-- "run",
-- "domains"
-- ],
-- "description": "The authority that owns the item."
-- },
-- "id": {
++ "enum": [
++ "actor",
++ "codes",
++ "ai",
++ "network",
++ "run",
++ "domains"
++ ],
++ "description": "The authority that owns the item."
++ },
++ "id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "description": "The authority's own id (tsk_…, a Codes task id, run_…, a confirmation id, dpr_…)."
++ },
++ "kind": {
++ "enum": [
++ "agent_task",
++ "code_task",
++ "ai_run",
++ "approval",
++ "desk_session"
++ ],
++ "description": "What the item is."
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 160,
++ "description": "A short plain-text name: the start of the task text, the workflow, or what the approval is for."
++ },
++ "summary": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 400,
++ "description": "The last step, the outcome, or what will happen on approval."
++ },
++ "bucket": {
++ "enum": [
++ "running",
++ "waiting",
++ "scheduled",
++ "done",
++ "failed"
++ ],
++ "description": "Where the Action Center shows it: running (queued or working), waiting (needs the person), scheduled (will run later), done, failed (failed, cancelled or expired)."
++ },
++ "state": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 32,
++ "description": "The authority's own state word (queued, running, needs_you, succeeded, failed, cancelled, pending…)."
++ },
++ "agent": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80,
++ "description": "The agent, model or workflow doing it, when known."
++ },
++ "cost_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "What it cost the person, in US dollars; null when unknown or free."
++ },
++ "steps": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Tool calls or steps so far, when counted."
++ },
++ "error": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 400,
++ "description": "Why it failed, in words a person can act on."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "next_run_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When a scheduled item runs next."
++ },
++ "href": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://",
++ "maxLength": 2048,
++ "description": "The item's page: its live steps, receipt or approval."
++ },
++ "actions": {
++ "type": "array",
++ "maxItems": 4,
++ "description": "What the person can do from the list. approve/decline/cancel/retry go through OpenVibe.Network (POST /api/v1/me/actions/:service/:id/:action); open is a link.",
++ "items": {
++ "enum": [
++ "open",
++ "approve",
++ "decline",
++ "cancel",
++ "retry"
++ ]
++ }
++ }
++ }
++ },
++ "common.action-list-result.v1": {
++ "title": "ActionListResult",
++ "description": "One authority's Action Center items for a person (capabilities actor.actions.read, codes.actions.read, ai.actions.read, domains.actions.read): GET /api/v1/actions?subject=usr_… with the caller's service token. Everything running, waiting or scheduled, then done and failed items from the last 30 days, newest first, at most 50.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "items"
++ ],
++ "properties": {
++ "items": {
++ "type": "array",
++ "maxItems": 50,
++ "items": {
++ "$ref": "#/components/schemas/common.action-item.v1"
++ }
++ }
++ }
++ },
++ "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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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",
-- "minLength": 1,
-- "maxLength": 80,
-- "description": "The authority's own id (tsk_…, a Codes task id, run_…, a confirmation id, dpr_…)."
-- },
-- "kind": {
-- "enum": [
-- "agent_task",
-- "code_task",
-- "ai_run",
-- "approval",
-- "desk_session"
-- ],
-- "description": "What the item is."
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
},
-- "title": {
++ "namespace": {
"type": "string",
-- "minLength": 1,
-- "maxLength": 160,
-- "description": "A short plain-text name: the start of the task text, the workflow, or what the approval is for."
++ "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."
},
-- "summary": {
++ "revision": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "previous_revision": {
"type": [
-- "string",
++ "integer",
"null"
],
-- "maxLength": 400,
-- "description": "The last step, the outcome, or what will happen on approval."
++ "minimum": 1,
++ "description": "The revision that was active when this one was proposed; null for the namespace's first."
},
-- "bucket": {
++ "state": {
"enum": [
-- "running",
-- "waiting",
-- "scheduled",
-- "done",
-- "failed"
-- ],
-- "description": "Where the Action Center shows it: running (queued or working), waiting (needs the person), scheduled (will run later), done, failed (failed, cancelled or expired)."
++ "proposed",
++ "active",
++ "superseded",
++ "rejected",
++ "rolled_back"
++ ]
},
-- "state": {
++ "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",
-- "minLength": 1,
-- "maxLength": 32,
-- "description": "The authority's own state word (queued, running, needs_you, succeeded, failed, cancelled, pending…)."
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "sha256 (hex) of the canonical JSON of values as they appear in this snapshot (secrets as their markers)."
},
-- "agent": {
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "created_by": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "activated_at": {
"type": [
"string",
"null"
],
-- "maxLength": 80,
-- "description": "The agent, model or workflow doing it, when known."
++ "format": "date-time",
++ "description": "When it became active; null while proposed, and for a rejected revision."
},
-- "cost_usd": {
-- "type": [
-- "number",
-- "null"
-- ],
-- "minimum": 0,
-- "description": "What it cost the person, in US dollars; null when unknown or free."
++ "activated_by": {
++ "oneOf": [
++ {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ {
++ "type": "null"
++ }
++ ]
},
-- "steps": {
++ "reason": {
"type": [
-- "integer",
++ "string",
"null"
],
-- "minimum": 0,
-- "description": "Tool calls or steps so far, when counted."
++ "maxLength": 500
},
"error": {
"type": [
"string",
"null"
],
-- "maxLength": 400,
-- "description": "Why it failed, in words a person can act on."
-- },
-- "created_at": {
-- "type": "string",
-- "format": "date-time"
-- },
-- "updated_at": {
-- "type": "string",
-- "format": "date-time"
++ "maxLength": 2000,
++ "description": "Why it was rejected: the validation errors, or what activation reported. Never a secret value."
},
-- "next_run_at": {
++ "copied_from": {
"type": [
-- "string",
++ "integer",
"null"
],
-- "format": "date-time",
-- "description": "When a scheduled item runs next."
-- },
-- "href": {
-- "type": "string",
-- "format": "uri",
-- "pattern": "^https://",
-- "maxLength": 2048,
-- "description": "The item's page: its live steps, receipt or approval."
-- },
-- "actions": {
-- "type": "array",
-- "maxItems": 4,
-- "description": "What the person can do from the list. approve/decline/cancel/retry go through OpenVibe.Network (POST /api/v1/me/actions/:service/:id/:action); open is a link.",
-- "items": {
-- "enum": [
-- "open",
-- "approve",
-- "decline",
-- "cancel",
-- "retry"
-- ]
-- }
++ "minimum": 1,
++ "description": "A rollback's source: the revision whose values this one copies."
}
-- }
-- },
-- "common.action-list-result.v1": {
-- "title": "ActionListResult",
-- "description": "One authority's Action Center items for a person (capabilities actor.actions.read, codes.actions.read, ai.actions.read, domains.actions.read): GET /api/v1/actions?subject=usr_… with the caller's service token. Everything running, waiting or scheduled, then done and failed items from the last 30 days, newest first, at most 50.",
-- "type": "object",
-- "additionalProperties": false,
-- "required": [
-- "items"
-- ],
-- "properties": {
-- "items": {
-- "type": "array",
-- "maxItems": 50,
-- "items": {
-- "$ref": "#/components/schemas/common.action-item.v1"
++ },
++ "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",
@@@ -1127,6 +2035,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/community.json b/generated/openapi/community.json
iindex d8cf7c81..665e590c 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.287.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex 526ae780..dd13ba29 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.287.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 5e90d98a..6fc80c5f 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.287.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/domains.json b/generated/openapi/domains.json
iindex 94176f0f..17015689 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.287.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 2da0b410..1750e182 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.287.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 666caaa2..1b088019 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.287.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 0223c1ee..461f8816 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.287.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 8ca15140..d20afd0e 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.287.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -381,10 +381,918 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one a",
++ "description": "**host.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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": [
++ "host.config"
++ ],
++ "x-openvibe-capabilities": [
++ "host.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "host.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.Host through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one a",
++ "description": "**host.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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": [
++ "host.config"
++ ],
++ "x-openvibe-capabilities": [
++ "host.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "host.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.Host through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one a",
++ "description": "**host.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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": [
++ "host.config"
++ ],
++ "x-openvibe-capabilities": [
++ "host.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "host.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.Host through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one a",
++ "description": "**host.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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": [
++ "host.config"
++ ],
++ "x-openvibe-capabilities": [
++ "host.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "host.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.Host through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one a",
++ "description": "**host.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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": [
++ "host.config"
++ ],
++ "x-openvibe-capabilities": [
++ "host.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "host.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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.",
@@@ -755,6 +1663,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."
++ }
++ ]
}
},
"securitySchemes": {
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 96607f9c..63636ec7 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -5,29 +5,29 @@
"service": "actor",
"name": "OpenVibe.Actor",
"origin": "https://openvibe.actor",
-- "operations": 31,
-- "capabilities": 12
++ "operations": 36,
++ "capabilities": 13
},
{
"service": "ai",
"name": "OpenVibe.AI",
"origin": "https://ai.openvibe.services",
-- "operations": 60,
-- "capabilities": 14
++ "operations": 65,
++ "capabilities": 15
},
{
"service": "bench",
"name": "OpenVibe.Bench",
"origin": "https://bench.openvibe.services",
-- "operations": 21,
-- "capabilities": 12
++ "operations": 26,
++ "capabilities": 13
},
{
"service": "billing",
"name": "OpenVibe.Billing",
"origin": "https://billing.openvibe.network",
-- "operations": 38,
-- "capabilities": 16
++ "operations": 43,
++ "capabilities": 17
},
{
"service": "blog",
@@@ -54,8 +54,8 @@
"service": "codes",
"name": "OpenVibe.Codes",
"origin": "https://openvibe.codes",
-- "operations": 4,
-- "capabilities": 4
++ "operations": 9,
++ "capabilities": 5
},
{
"service": "community",
@@@ -110,8 +110,8 @@
"service": "host",
"name": "OpenVibe.Host",
"origin": "https://openvibe.host",
-- "operations": 6,
-- "capabilities": 1
++ "operations": 11,
++ "capabilities": 2
},
{
"service": "inventory",
@@@ -180,8 +180,8 @@
"service": "report",
"name": "OpenVibe.Report",
"origin": "https://openvibe.report",
-- "operations": 1,
-- "capabilities": 1
++ "operations": 6,
++ "capabilities": 2
},
{
"service": "reviews",
@@@ -208,8 +208,8 @@
"service": "services",
"name": "OpenVibe.Services",
"origin": "https://openvibe.services",
-- "operations": 10,
-- "capabilities": 4
++ "operations": 15,
++ "capabilities": 5
},
{
"service": "sources",
@@@ -271,8 +271,8 @@
"service": "watch",
"name": "OpenVibe.Watch",
"origin": "https://openvibe.watch",
-- "operations": 13,
-- "capabilities": 6
++ "operations": 18,
++ "capabilities": 7
},
{
"service": "wiki",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex c3d24580..8384ea8f 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.287.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 e6acccd5..36f28b4e 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.287.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 e2cf44e8..69287f3c 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.287.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 c3d11f6b..2e98f570 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.287.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 53b3b919..2454bc6f 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.287.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 53594957..71d21296 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.287.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex c4b03d07..524f6728 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.287.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 9756e2fd..7a790588 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.287.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 cbcc413d..65f0efa4 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.287.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 ea7268b1..7667b52f 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.287.0",
-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -61,10 +61,918 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one",
++ "description": "**report.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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": [
++ "report.config"
++ ],
++ "x-openvibe-capabilities": [
++ "report.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.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.Report through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one",
++ "description": "**report.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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": [
++ "report.config"
++ ],
++ "x-openvibe-capabilities": [
++ "report.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.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.Report through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one",
++ "description": "**report.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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": [
++ "report.config"
++ ],
++ "x-openvibe-capabilities": [
++ "report.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.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.Report through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one",
++ "description": "**report.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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": [
++ "report.config"
++ ],
++ "x-openvibe-capabilities": [
++ "report.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.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.Report through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one",
++ "description": "**report.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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": [
++ "report.config"
++ ],
++ "x-openvibe-capabilities": [
++ "report.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "report.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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.",
@@@ -131,6 +1039,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."
++ }
++ ]
++ },
"report.ai-usage-result.v1": {
"title": "ReportAiUsageResult",
"description": "report.ai-usage-result@1: the AI meter in groups (plan L40/L41). GET /api/v1/ai/usage needs a service token with report.ai_usage.read; the rows are grouped by day, site, model, system or paid_by. A group that fewer than ten people share reads fewer than 10 instead of counting them.",
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex eb3aceca..ad44ef54 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.287.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 94e8b67e..9ec3b334 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.287.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 7e4a258d..d7dd7d39 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.287.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 b41a933f..485bb9a8 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.287.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -603,10 +603,918 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns o",
++ "description": "**services.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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": [
++ "services.config"
++ ],
++ "x-openvibe-capabilities": [
++ "services.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.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.Services through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns o",
++ "description": "**services.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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": [
++ "services.config"
++ ],
++ "x-openvibe-capabilities": [
++ "services.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.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.Services through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns o",
++ "description": "**services.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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": [
++ "services.config"
++ ],
++ "x-openvibe-capabilities": [
++ "services.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.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.Services through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns o",
++ "description": "**services.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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": [
++ "services.config"
++ ],
++ "x-openvibe-capabilities": [
++ "services.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.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.Services through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns o",
++ "description": "**services.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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": [
++ "services.config"
++ ],
++ "x-openvibe-capabilities": [
++ "services.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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).",
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 702aefef..41a2d23e 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.287.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 9536a278..4a18a24f 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.287.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 ddb00997..d7024c69 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.287.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 58f451ba..a18c1740 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.287.0",
-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 be84a9ff..33b81238 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.287.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 078903da..179fdcba 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.287.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 26165b4c..07ddf1a0 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.287.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 6b452f8f..b6bf82c3 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.287.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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 e82c4b82..6cf536ce 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.287.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for 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"
@@@ -881,10 +881,918 @@
}
}
}
++ },
++ "/internal/config": {
++ "get": {
++ "operationId": "get_internal_config",
++ "summary": "Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**watch.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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": [
++ "watch.config"
++ ],
++ "x-openvibe-capabilities": [
++ "watch.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "watch.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.Watch through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**watch.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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": [
++ "watch.config"
++ ],
++ "x-openvibe-capabilities": [
++ "watch.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "watch.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.Watch through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**watch.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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": [
++ "watch.config"
++ ],
++ "x-openvibe-capabilities": [
++ "watch.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "watch.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.Watch through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**watch.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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": [
++ "watch.config"
++ ],
++ "x-openvibe-capabilities": [
++ "watch.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "watch.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.Watch through openvibe-shared/config (D11): GET /internal/config lists the namespace summaries, GET /internal/config/:namespace returns one ",
++ "description": "**watch.config.manage** (internal): Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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": [
++ "watch.config"
++ ],
++ "x-openvibe-capabilities": [
++ "watch.config.manage"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "config-manage",
++ "security": [
++ {
++ "openvibeToken": [
++ "watch.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).",
++ "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) and x-unit (nano_usd, ms or bps)."
++ },
++ "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.resource-control-request.v1": {
"title": "ResourceControlRequest",
"description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex d5271c11..0c2fec4c 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.287.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 97d743b2..13700874 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.287.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/zone.json b/generated/openapi/zone.json
iindex 1a22ee7d..d4154e44 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.287.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.287.0): each operation lists the capabilities it performs; a token for 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.288.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.288.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 847a2998..e63b9407 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -4680,6 +4680,130 @@ export type ConfigSnapshot = {
copied_from?: number | null;
};
++/** [email protected] (owner: network) */
++/**
++ * 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).
++ */
++export interface ConfigNamespace {
++ service: string;
++ /**
++ * <service>.<name>, e.g. ai.exchange or live.site_settings.
++ */
++ namespace: string;
++ /**
++ * The active revision; null before the namespace has one.
++ */
++ revision: number | null;
++ /**
++ * The active values over the defaults, one member per key. A secret-class key's value is a marker, never the value.
++ */
++ values: {
++ /**
++ * Any JSON value. An object with a redacted member is a secret marker, and nothing else.
++ */
++ [k: string]:
++ | (
++ | (string | number | boolean | null | unknown[])
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | {
++ redacted: true;
++ /**
++ * HMAC-SHA256 (hex) of the canonical JSON of the value, keyed with the namespace's secret key.
++ */
++ fingerprint: string;
++ /**
++ * The four-character hint (the value's last four characters), never more.
++ */
++ hint?: string;
++ set_at?: string;
++ set_by?: SubjectRef;
++ /**
++ * The value of the key named by x-bind when the secret was set, e.g. a provider base_url origin.
++ */
++ bound_to?: string | null;
++ }
++ )
++ | undefined;
++ };
++ /**
++ * Every key of values, classified. A key the service does not classify is internal.
++ */
++ classification: {
++ [k: string]: ("public" | "internal" | "secret") | undefined;
++ };
++ /**
++ * 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.
++ */
++ sealed: boolean;
++ /**
++ * 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.
++ */
++ pins: string[];
++ /**
++ * 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) and x-unit (nano_usd, ms or bps).
++ */
++ schema: {} | null;
++ /**
++ * The active revision as a snapshot (common.config-snapshot@1), or null.
++ */
++ active?: ConfigSnapshot | null;
++ /**
++ * The last revision that activated cleanly (common.config-snapshot@1), or null.
++ */
++ last_known_good?: ConfigSnapshot | null;
++}
++
++/** [email protected] (owner: network) */
++/**
++ * 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.
++ */
++export interface ConfigChange {
++ /**
++ * The new values, one member per key. The store validates them against the namespace schema; a secret-class value here is refused.
++ */
++ values: {
++ /**
++ * Any JSON value; a redaction marker keeps the value it stands for.
++ */
++ [k: string]:
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | undefined;
++ };
++ /**
++ * Why the change is made; it is the revision's reason in the history.
++ */
++ reason: string;
++ /**
++ * Merge into the active values instead of replacing them.
++ */
++ merge?: boolean;
++ /**
++ * Keys to remove from the new revision.
++ *
++ * @maxItems 256
++ */
++ unset?: string[];
++}
++
++/** [email protected] (owner: network) */
++/**
++ * common.config-secret-put@1 (AI Exchange D11): body of PUT /internal/config/:namespace/secrets/:key on a service's openvibe-shared/config admin routes. The value is written once, sealed as an envelope under the service's KEK (or kept as today when it has none), and never returned, logged or put in values_json. Its answer is the marker: { redacted: true, fingerprint, hint (four characters), set_at, set_by, bound_to }, as common.config-namespace@1 defines it. A `x-bind` key on the namespace schema binds the secret to another key's value (a provider key to its base_url origin); a changed bind no longer opens the value until the owner enters it again.
++ */
++export interface ConfigSecretPut {
++ /**
++ * The secret, once.
++ */
++ value: string;
++ /**
++ * Why the secret is set; it is the revision's reason in the history.
++ */
++ reason: string;
++}
++
/** [email protected] (owner: contracts) */
/**
* common.content-labels@1 (L1, labels and choices, 2026-10-31): what a stream, VOD, clip, picture or post is labelled as, and what the AI has suggested but the creator has not confirmed. Labels are choices, never bans: a label never hides content from the person who posted it, and it never hides content from a viewer whose label preferences show it. A creator sets a label; the AI may only suggest one (a suggestion the creator confirms, never a takedown); three viewer suggestions or a moderator put one on. The label set is one list everywhere: nudity, language, graphic, 18+, flashing and spoilers.
ddiff --git a/manifests/capabilities/actor.config.manage.json b/manifests/capabilities/actor.config.manage.json
nnew file mode 100644
iindex 00000000..9f991e74
---- /dev/null
++++ b/manifests/capabilities/actor.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "actor.config.manage",
++ "version": "1.0",
++ "owner": "actor",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Actor through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/ai.config.manage.json b/manifests/capabilities/ai.config.manage.json
nnew file mode 100644
iindex 00000000..d7bcf475
---- /dev/null
++++ b/manifests/capabilities/ai.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "ai.config.manage",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the ai.exchange and ai.provider_keys namespaces of OpenVibe.AI through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/bench.config.manage.json b/manifests/capabilities/bench.config.manage.json
nnew file mode 100644
iindex 00000000..8f58d74f
---- /dev/null
++++ b/manifests/capabilities/bench.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "bench.config.manage",
++ "version": "1.0",
++ "owner": "bench",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Bench through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/billing.config.manage.json b/manifests/capabilities/billing.config.manage.json
nnew file mode 100644
iindex 00000000..100c2646
---- /dev/null
++++ b/manifests/capabilities/billing.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "billing.config.manage",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the billing.credits namespace of OpenVibe.Billing through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/codes.config.manage.json b/manifests/capabilities/codes.config.manage.json
nnew file mode 100644
iindex 00000000..718910a3
---- /dev/null
++++ b/manifests/capabilities/codes.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "codes.config.manage",
++ "version": "1.0",
++ "owner": "codes",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Codes through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/host.config.manage.json b/manifests/capabilities/host.config.manage.json
nnew file mode 100644
iindex 00000000..f33dc37a
---- /dev/null
++++ b/manifests/capabilities/host.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "host.config.manage",
++ "version": "1.0",
++ "owner": "host",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Host through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/report.config.manage.json b/manifests/capabilities/report.config.manage.json
nnew file mode 100644
iindex 00000000..90d0bd97
---- /dev/null
++++ b/manifests/capabilities/report.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "report.config.manage",
++ "version": "1.0",
++ "owner": "report",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Report through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/services.config.manage.json b/manifests/capabilities/services.config.manage.json
nnew file mode 100644
iindex 00000000..7016a2c5
---- /dev/null
++++ b/manifests/capabilities/services.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "services.config.manage",
++ "version": "1.0",
++ "owner": "services",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Services through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/capabilities/watch.config.manage.json b/manifests/capabilities/watch.config.manage.json
nnew file mode 100644
iindex 00000000..36c7c5d3
---- /dev/null
++++ b/manifests/capabilities/watch.config.manage.json
@@@ -0,0 +1,23 @@
++{
++ "id": "watch.config.manage",
++ "version": "1.0",
++ "owner": "watch",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Manage the configuration namespaces and secrets of OpenVibe.Watch through openvibe-shared/config (D11): 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. 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"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "config-manage",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/config",
++ "GET|POST /internal/config/:namespace",
++ "PUT|DELETE /internal/config/:namespace/secrets/:key"
++ ],
++ "inputSchema": "common.config-change@1",
++ "outputSchema": "common.config-namespace@1"
++}
ddiff --git a/manifests/services/actor.json b/manifests/services/actor.json
iindex 85c1dd71..181ac43b 100644
---- a/manifests/services/actor.json
++++ b/manifests/services/actor.json
@@@ -27,7 +27,8 @@
"actor.actions.read",
"actor.approval.decide",
"actor.model.manage",
-- "actor.a2a.task"
++ "actor.a2a.task",
++ "actor.config.manage"
],
"eventsProduced": [],
"eventsConsumed": [
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex e71b9320..70fd104a 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -33,7 +33,8 @@
"ai.memory.read",
"ai.memory.write",
"ai.me.read",
-- "ai.me.manage"
++ "ai.me.manage",
++ "ai.config.manage"
],
"eventsProduced": [
"ai.call.recorded",
ddiff --git a/manifests/services/bench.json b/manifests/services/bench.json
iindex 821ec81d..7b253dfa 100644
---- a/manifests/services/bench.json
++++ b/manifests/services/bench.json
@@@ -31,7 +31,8 @@
"bench.eval.manage",
"bench.benchmark.write",
"bench.run.publish",
-- "bench.run.read"
++ "bench.run.read",
++ "bench.config.manage"
],
"eventsProduced": [
"bench.round.finished",
ddiff --git a/manifests/services/billing.json b/manifests/services/billing.json
iindex 08bc824f..651e7f3c 100644
---- a/manifests/services/billing.json
++++ b/manifests/services/billing.json
@@@ -35,7 +35,8 @@
"billing.credit.hold.extend",
"billing.credit.hold.settle",
"billing.credit.hold.release",
-- "billing.credit.sandbox.grant"
++ "billing.credit.sandbox.grant",
++ "billing.config.manage"
],
"eventsProduced": [
"billing.transaction.settled",
ddiff --git a/manifests/services/codes.json b/manifests/services/codes.json
iindex 7fd1ffb5..8f81e3c1 100644
---- a/manifests/services/codes.json
++++ b/manifests/services/codes.json
@@@ -45,7 +45,8 @@
"codes.task.start",
"codes.task.read",
"codes.task.cancel",
-- "codes.actions.read"
++ "codes.actions.read",
++ "codes.config.manage"
],
"eventsProduced": [
"codes.task.queued",
ddiff --git a/manifests/services/host.json b/manifests/services/host.json
iindex 46ca5207..da1a3a62 100644
---- a/manifests/services/host.json
++++ b/manifests/services/host.json
@@@ -95,7 +95,8 @@
"neighbourhood": "make"
},
"capabilities": [
-- "host.catalog.manage"
++ "host.catalog.manage",
++ "host.config.manage"
],
"eventsProduced": [
"host.release.published",
ddiff --git a/manifests/services/report.json b/manifests/services/report.json
iindex 90b8cba0..6f54a24f 100644
---- a/manifests/services/report.json
++++ b/manifests/services/report.json
@@@ -16,7 +16,8 @@
"publicSite": "service"
},
"capabilities": [
-- "report.ai_usage.read"
++ "report.ai_usage.read",
++ "report.config.manage"
],
"eventsProduced": [
"report.index_document.deleted",
ddiff --git a/manifests/services/services.json b/manifests/services/services.json
iindex ad3777f4..f48b0a4f 100644
---- a/manifests/services/services.json
++++ b/manifests/services/services.json
@@@ -22,7 +22,8 @@
"services.resource.control",
"services.recipe.run",
"services.usage.read",
-- "services.cost.read"
++ "services.cost.read",
++ "services.config.manage"
],
"eventsProduced": [
"services.app.published",
ddiff --git a/manifests/services/watch.json b/manifests/services/watch.json
iindex 0fe24834..513f5341 100644
---- a/manifests/services/watch.json
++++ b/manifests/services/watch.json
@@@ -21,7 +21,8 @@
"watch.observation.read",
"watch.check.run",
"watch.resource.read",
-- "watch.resource.control"
++ "watch.resource.control",
++ "watch.config.manage"
],
"eventsProduced": [
"watch.watch.created",
ddiff --git a/package.json b/package.json
iindex 47bd71cc..cf81f940 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.287.0",
++ "version": "0.288.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 840087c2..1f965b53 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -99,6 +99,16 @@ for (const cap of capabilities.manifests) {
for (const k of ['inputSchema', 'outputSchema']) if (cap[k]) ok(contracts.resolve(cap[k]), `${cap.id} ${k} resolves`);
ok(cap.status !== 'active' || cap.implementedBy.length > 0, `active ${cap.id} names the route that implements it`);
}
++// Owner settings (AI Exchange D11, S1): one internal config capability per service, on
++// 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.
++for (const svc of ['ai', 'billing', 'services', 'watch', 'bench', 'report', 'host', 'codes', 'actor']) {
++ 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(['GET /internal/config', 'GET|POST /internal/config/:namespace', 'PUT|DELETE /internal/config/:namespace/secrets/:key']), `${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`);
++}
++ok(!capabilities.get('network.config.manage'), 'network uses its own admin config routes, not the proxy capability');
// Developer-app events (v0.28, ADR-014): grantable to apps, owned by Events, project-scoped. The
// existing service-level events.* capabilities stay internal.
for (const id of ['events.app.publish', 'events.app.read', 'events.app.subscribe']) {