openvibers/openvibe.contracts Commits

Contracts 0.109.0: Run and Services register, partial resource pages, Bot button/point commands, ai.app.run (#56)

OP OpenVibers committed 7 Oct 2026 · e81901dd89 · 58 files, +3073 −133

**Changes (one line per file)**

1. `manifests/services/run.json` — status `alpha`; exposure `{ internal, publicSite: "placeholder", note: "deployed on loopback :4920 (2026-10-07); openvibe.run keeps its OpenVibe.Sites placeholder until Run has a product surface" }`; notes de-PLANNED.
2. `manifests/capabilities/run.job.{submit,read,list,cancel,stream,admin}.json` — status `active`, PLANNED wording dropped, `implementedBy` exactly the given routes (submit `POST /api/v1/jobs`; read `GET /api/v1/jobs/:id`; list `GET /api/v1/jobs`; cancel `POST /api/v1/jobs/:id/cancel`; stream `POST /api/v1/jobs/:id/stream/ticket` + `GET /api/v1/jobs/:id/stream`; admin `GET /api/v1/admin/jobs`).
3. `test/platform-run.test.js` — asserts `alpha` + internal exposure and the active routes instead of placeholder/planned.
4. `manifests/services/services.json` — status `alpha`, exposure internal (loopback :4930, placeholder kept), capabilities `["services.resource.read"]`, notes updated.
5. `manifests/capabilities/services.resource.read.json` — new, modelled on `network.resource.read.json`: owner `services`, first-party, active, `GET /api/v1/resources` + `/:ovrn`, given description.
6. `contracts/common/resource-list-result.v1.json` — optional `partial` array of `{ service, code }` (`additionalProperties:false` throughout); `contracts/catalog.json` bumped to 1.1.0.
7. `fixtures/common.resource-list-result/valid/partial.json` — new valid fixture with `partial`.
8. `docs/adr/ADR-048-services-control-plane.md` — one sentence in the resource-index section (a bad authority is omitted and named in `partial`, never fails the page).
9. `contracts/bot/command.v1.json` — kinds `button` (`value.name` pattern `^[a-z][a-z0-9_]{0,31}$`, optional `state: down|up`) and `point` (`x`,`y` in [0,1]), via `if/then`; description updated.
10. `contracts/bot/robot-profile.v1.json` — `commands.button` (names → label ≤40, optional key ≤16, cooldown_ms ≥0, hold), `commands.point` (cooldown_ms); widget `type` pattern widened to admit `_` (so `video_click` is valid) and widget `command.kind` gains `button`/`point`; descriptions updated.
11. `fixtures/bot.command/valid/button.json`, `valid/hold-button.json`, `valid/point.json`, `invalid/point-out-of-range.json`; `fixtures/bot.robot-profile/valid/buttons.json` — new; all existing bot fixtures and Bot's shipped `server/profiles/*.json` still validate.
12. `manifests/capabilities/ai.app.run.json` — new: owner `ai`, public, planned, `implementedBy []`, `permissions ["ai:run"]`, `resourceConstraints ["project"]`, `quotaClass ai-app-run`, ai.run.create's schemas, given description.
13. `manifests/services/ai.json` — capabilities now list `ai.app.run`.
14. `package.json`, `package-lock.json` — 0.109.0; `CHANGELOG.md` — `## 0.109.0 — 2026-10-07` entry; `generated/` regenerated (new `openapi/run.json` and `openapi/services.json`, version stamps, bot/resource types).

**Suite:** `openvibe-contracts: 17035 checks passed` (`test/run.js`; the `OV_ALLOW_SUITE=1 nice -n 15 node test/run.js` form was refused by the permission layer, so it was run via the allowed `nice -n 15 ov test --no-cache --shards 1 --all` fallback — `12 passed in 25 s.` — and directly).
**Compat:** `compat vs v0.107.0: no breaking changes`

For Opus: no `manifests/namespaces/services.*.json` was needed (nothing requires it). Three judgment calls: I bumped `common.resource-list-result` to catalog 1.1.0 for the additive `partial`; to make the design's `video_click` widget legal I widened t

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 8643d8c6..2aaaca86 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,34 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.109.0 — 2026-10-07
++
++**OpenVibe.Run runs, OpenVibe.Services registers, and Bot learns buttons and points.** Two schemas gained optional
++shape, Run's job API turned active, and Services registered its first capability and AI its first public one; everything here is
++additive, so every existing fixture and profile stays valid.
++- `manifests/services/run.json`: `status` alpha; `exposure` internal (deployed on loopback :4920 on 2026-10-07,
++ Run #1 and #2); openvibe.run keeps its OpenVibe.Sites placeholder until Run has a product surface.
++- `run.job.submit`, `run.job.read`, `run.job.list`, `run.job.cancel`, `run.job.stream`, `run.job.admin`: active,
++ with the routes Run serves (`POST /api/v1/jobs`, `GET /api/v1/jobs`, `GET /api/v1/jobs/:id`,
++ `POST /api/v1/jobs/:id/cancel`, `POST /api/v1/jobs/:id/stream/ticket` and `GET /api/v1/jobs/:id/stream`,
++ `GET /api/v1/admin/jobs`).
++- `manifests/services/services.json`: `status` alpha; `exposure` internal (deployed on loopback :4930 on 2026-10-07);
++ openvibe.services keeps its placeholder. `services.resource.read` (owner `services`, first-party, active) is
++ Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service
++ token holding the capability or by a person's user token scoped to one project they own or belong to.
++- `common.resource-list-result@1` is 1.1.0: an optional `partial`, the authorities a merged index could not read for
++ this page (`{ service, code }`). A slow or failing authority is omitted, never fails the page (ADR-048).
++- `bot.command@1` gains the kinds `button` (`name`, and an optional `state` `down|up` for a hold button) and `point`
++ (`x`, `y` in `[0, 1]`). `bot.robot-profile@1` gains `commands.button` (`names`: `label`, optional `key`,
++ `cooldown_ms` and `hold`) and `commands.point` (optional `cooldown_ms`), the widget types `buttons` and
++ `video_click` (the widget type pattern now admits `_`), and `button`/`point` as widget command kinds (plan T15 R9).
++- `ai.app.run` (owner `ai`, public, active; OpenVibe.AI #22): the AI operations as a developer app (ADR-014, plan
++ T6) — `POST /api/v1/chat`, `/generate`, `/summarize`, `/classify`, `/extract`, `/embed` and `GET /api/v1/runs/:id`
++ for the app's own runs. Every run is metered to the app's project; metered capacity is spent only under the
++ project's tier budget, and a sandbox run (or one no tier budget governs) uses only free and local capacity.
++- `generated/` gains `openapi/run.json` and `openapi/services.json` (their first active capabilities) and stamps
++ every document with the release version.
++
## 0.108.0 — 2026-10-07
**OpenVibe.Watch runs, and four authorities serve their resource index.** Status changes only; no schema changed.
ddiff --git a/contracts/bot/command.v1.json b/contracts/bot/command.v1.json
iindex 469e75ec..2b2189a1 100644
---- a/contracts/bot/command.v1.json
++++ b/contracts/bot/command.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/command.v1.json",
"title": "BotCommand",
-- "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1).",
++ "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9).",
"type": "object",
"required": [
"type",
@@@ -36,7 +36,9 @@
"ptz",
"say",
"display",
-- "halt"
++ "halt",
++ "button",
++ "point"
]
},
"value": {
@@@ -46,5 +48,86 @@
"type": "integer",
"minimum": 1
}
-- }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "button"
++ }
++ },
++ "required": [
++ "kind"
++ ]
++ },
++ "then": {
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "type": "object",
++ "required": [
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "name": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$",
++ "description": "one of the profile's commands.button.names"
++ },
++ "state": {
++ "enum": [
++ "down",
++ "up"
++ ],
++ "description": "present only for a hold button: down on press, up on release"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "point"
++ }
++ },
++ "required": [
++ "kind"
++ ]
++ },
++ "then": {
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "type": "object",
++ "required": [
++ "x",
++ "y"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "x": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1
++ },
++ "y": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
}
ddiff --git a/contracts/bot/robot-profile.v1.json b/contracts/bot/robot-profile.v1.json
iindex 6a1bfecb..625388c8 100644
---- a/contracts/bot/robot-profile.v1.json
++++ b/contracts/bot/robot-profile.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/robot-profile.v1.json",
"title": "BotRobotProfile",
-- "description": "bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver.",
++ "description": "bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver. commands.button (an owner's buttons: names, each a label with an optional key, cooldown_ms and hold) and commands.point (a video-click layer, with its own cooldown_ms), rendered by the widget types buttons and video_click, were added in 0.109.0 (plan T15 R9).",
"type": "object",
"required": [
"id",
@@@ -315,6 +315,56 @@
}
}
},
++ "button": {
++ "type": "object",
++ "required": [
++ "names"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "names": {
++ "type": "object",
++ "minProperties": 1,
++ "propertyNames": {
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "additionalProperties": {
++ "type": "object",
++ "required": [
++ "label"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "label": {
++ "type": "string",
++ "maxLength": 40
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 16
++ },
++ "cooldown_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "hold": {
++ "type": "boolean"
++ }
++ }
++ }
++ }
++ }
++ },
++ "point": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "cooldown_ms": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
"halt": {
"type": "object",
"required": [],
@@@ -335,7 +385,7 @@
"properties": {
"type": {
"type": "string",
-- "pattern": "^[a-z][a-z0-9-]*$"
++ "pattern": "^[a-z][a-z0-9_-]*$"
},
"capability": {
"type": "string"
@@@ -361,7 +411,9 @@
"ptz",
"say",
"display",
-- "halt"
++ "halt",
++ "button",
++ "point"
]
},
"names": {
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 45cc6057..247e6cf3 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -5759,7 +5759,7 @@
},
{
"id": "common.resource-list-result",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "contracts",
"schema": "common/resource-list-result.v1.json",
"visibility": "public",
ddiff --git a/contracts/common/resource-list-result.v1.json b/contracts/common/resource-list-result.v1.json
iindex 1aef9549..2b5361cc 100644
---- a/contracts/common/resource-list-result.v1.json
++++ b/contracts/common/resource-list-result.v1.json
@@@ -21,6 +21,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
}
ddiff --git a/docs/adr/ADR-048-services-control-plane.md b/docs/adr/ADR-048-services-control-plane.md
iindex 9b22750a..5212a806 100644
---- a/docs/adr/ADR-048-services-control-plane.md
++++ b/docs/adr/ADR-048-services-control-plane.md
@@@ -73,6 +73,9 @@ them in a Network release** to `GET /api/v1/offers`, `GET /api/v1/offers/:offer_
`network.resource.report` guard. Moving a public route is a release-note change: OpenVibe.Network's release must name
the old and new paths.
++A merged page never fails for one bad authority: a slow or failing authority is omitted from the page and named, with
++the problem code it answered, in `common.resource-list-result@1`'s `partial` (added 0.109.0).
++
The move has consumers outside `server/registry/offers.js` itself, which the Network release note must update with it:
- the **discovery index key** `resources: '/api/v1/resources'` in `server/registry/ecosystem.js:329` (documented at
ddiff --git a/fixtures/bot.command/invalid/point-out-of-range.json b/fixtures/bot.command/invalid/point-out-of-range.json
nnew file mode 100644
iindex 00000000..763e4f05
---- /dev/null
++++ b/fixtures/bot.command/invalid/point-out-of-range.json
@@@ -0,0 +1,12 @@
++{
++ "v": 1,
++ "seq": 23,
++ "ts": 1759600000000,
++ "type": "command",
++ "id": "op-11",
++ "kind": "point",
++ "value": {
++ "x": 1.5,
++ "y": 0.5
++ }
++}
ddiff --git a/fixtures/bot.command/valid/button.json b/fixtures/bot.command/valid/button.json
nnew file mode 100644
iindex 00000000..578d24ff
---- /dev/null
++++ b/fixtures/bot.command/valid/button.json
@@@ -0,0 +1,11 @@
++{
++ "v": 1,
++ "seq": 20,
++ "ts": 1759600000000,
++ "type": "command",
++ "id": "op-8",
++ "kind": "button",
++ "value": {
++ "name": "lights_on"
++ }
++}
ddiff --git a/fixtures/bot.command/valid/hold-button.json b/fixtures/bot.command/valid/hold-button.json
nnew file mode 100644
iindex 00000000..31ff0945
---- /dev/null
++++ b/fixtures/bot.command/valid/hold-button.json
@@@ -0,0 +1,12 @@
++{
++ "v": 1,
++ "seq": 21,
++ "ts": 1759600000000,
++ "type": "command",
++ "id": "op-9",
++ "kind": "button",
++ "value": {
++ "name": "horn",
++ "state": "down"
++ }
++}
ddiff --git a/fixtures/bot.command/valid/point.json b/fixtures/bot.command/valid/point.json
nnew file mode 100644
iindex 00000000..2a56b850
---- /dev/null
++++ b/fixtures/bot.command/valid/point.json
@@@ -0,0 +1,12 @@
++{
++ "v": 1,
++ "seq": 22,
++ "ts": 1759600000000,
++ "type": "command",
++ "id": "op-10",
++ "kind": "point",
++ "value": {
++ "x": 0.25,
++ "y": 0.75
++ }
++}
ddiff --git a/fixtures/bot.robot-profile/valid/buttons.json b/fixtures/bot.robot-profile/valid/buttons.json
nnew file mode 100644
iindex 00000000..f9d8085e
---- /dev/null
++++ b/fixtures/bot.robot-profile/valid/buttons.json
@@@ -0,0 +1,63 @@
++{
++ "id": "live.controls",
++ "version": 1,
++ "name": "Live camp controls",
++ "vendor": null,
++ "kind": "server",
++ "description": null,
++ "capabilities": [
++ "lights.rgb",
++ "camera"
++ ],
++ "variants": null,
++ "mapping": {
++ "driver": "sim"
++ },
++ "commands": {
++ "button": {
++ "names": {
++ "lights_on": {
++ "label": "Lights on",
++ "key": "1",
++ "cooldown_ms": 2000
++ },
++ "horn": {
++ "label": "Horn",
++ "key": "h",
++ "cooldown_ms": 0,
++ "hold": true
++ }
++ }
++ },
++ "point": {
++ "cooldown_ms": 1000
++ }
++ },
++ "widgets": [
++ {
++ "type": "buttons",
++ "capability": "lights.rgb",
++ "label": "Buttons",
++ "order": 1,
++ "command": {
++ "kind": "button"
++ }
++ },
++ {
++ "type": "video_click",
++ "capability": "camera",
++ "label": "Click to look",
++ "order": 2,
++ "command": {
++ "kind": "point"
++ }
++ }
++ ],
++ "camera": null,
++ "limits": {
++ "max_speed": 1,
++ "max_turn": 1,
++ "max_command_ms": 300,
++ "heartbeat_ms": 1000
++ }
++}
ddiff --git a/fixtures/common.resource-list-result/valid/partial.json b/fixtures/common.resource-list-result/valid/partial.json
nnew file mode 100644
iindex 00000000..77bfa723
---- /dev/null
++++ b/fixtures/common.resource-list-result/valid/partial.json
@@@ -0,0 +1,22 @@
++{
++ "resources": [
++ {
++ "id": "sub_1",
++ "kind": "events.subscription",
++ "service": "events",
++ "state": "enabled",
++ "created_at": "2026-09-28T23:00:00Z"
++ }
++ ],
++ "next_cursor": null,
++ "partial": [
++ {
++ "service": "media",
++ "code": "media.unavailable"
++ },
++ {
++ "service": "host",
++ "code": "host.timeout"
++ }
++ ]
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 65bd7d24..05fc56a6 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -66617,7 +66617,7 @@
}
},
"common.resource-list-result": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "contracts",
"status": "active",
"schema": {
@@@ -66643,6 +66643,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
}
@@@ -68271,7 +68293,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/robot-profile.v1.json",
"title": "BotRobotProfile",
-- "description": "bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver.",
++ "description": "bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver. commands.button (an owner's buttons: names, each a label with an optional key, cooldown_ms and hold) and commands.point (a video-click layer, with its own cooldown_ms), rendered by the widget types buttons and video_click, were added in 0.109.0 (plan T15 R9).",
"type": "object",
"required": [
"id",
@@@ -68584,6 +68606,56 @@
}
}
},
++ "button": {
++ "type": "object",
++ "required": [
++ "names"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "names": {
++ "type": "object",
++ "minProperties": 1,
++ "propertyNames": {
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "additionalProperties": {
++ "type": "object",
++ "required": [
++ "label"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "label": {
++ "type": "string",
++ "maxLength": 40
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 16
++ },
++ "cooldown_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "hold": {
++ "type": "boolean"
++ }
++ }
++ }
++ }
++ }
++ },
++ "point": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "cooldown_ms": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
"halt": {
"type": "object",
"required": [],
@@@ -68604,7 +68676,7 @@
"properties": {
"type": {
"type": "string",
-- "pattern": "^[a-z][a-z0-9-]*$"
++ "pattern": "^[a-z][a-z0-9_-]*$"
},
"capability": {
"type": "string"
@@@ -68630,7 +68702,9 @@
"ptz",
"say",
"display",
-- "halt"
++ "halt",
++ "button",
++ "point"
]
},
"names": {
@@@ -69594,7 +69668,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/command.v1.json",
"title": "BotCommand",
-- "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1).",
++ "description": "bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9).",
"type": "object",
"required": [
"type",
@@@ -69628,7 +69702,9 @@
"ptz",
"say",
"display",
-- "halt"
++ "halt",
++ "button",
++ "point"
]
},
"value": {
@@@ -69638,7 +69714,88 @@
"type": "integer",
"minimum": 1
}
-- }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "button"
++ }
++ },
++ "required": [
++ "kind"
++ ]
++ },
++ "then": {
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "type": "object",
++ "required": [
++ "name"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "name": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$",
++ "description": "one of the profile's commands.button.names"
++ },
++ "state": {
++ "enum": [
++ "down",
++ "up"
++ ],
++ "description": "present only for a hold button: down on press, up on release"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "kind": {
++ "const": "point"
++ }
++ },
++ "required": [
++ "kind"
++ ]
++ },
++ "then": {
++ "required": [
++ "value"
++ ],
++ "properties": {
++ "value": {
++ "type": "object",
++ "required": [
++ "x",
++ "y"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "x": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1
++ },
++ "y": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1
++ }
++ }
++ }
++ }
++ }
++ }
++ ]
}
},
"bot.command-result": {
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex e4613282..b66142e4 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.108.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -338,19 +338,27 @@
"/api/v1/chat": {
"post": {
"operationId": "post_api_v1_chat",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
@@@ -394,19 +402,27 @@
"/api/v1/classify": {
"post": {
"operationId": "post_api_v1_classify",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
@@@ -802,19 +818,27 @@
"/api/v1/embed": {
"post": {
"operationId": "post_api_v1_embed",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
@@@ -914,19 +938,27 @@
"/api/v1/extract": {
"post": {
"operationId": "post_api_v1_extract",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
@@@ -970,19 +1002,27 @@
"/api/v1/generate": {
"post": {
"operationId": "post_api_v1_generate",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
@@@ -1950,25 +1990,36 @@
"/api/v1/runs/{id}": {
"get": {
"operationId": "get_api_v1_runs_id",
-- "summary": "Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",
-- "description": "**ai.run.read** (first-party): Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.read** (first-party): Read the caller's own runs, their citations and request-log metadata (hashes, tokens, latency; never raw prompts).",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.read"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-read",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.read"
]
}
],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/ai.run-create-request.v1"
++ },
"parameters": [
{
"name": "id",
@@@ -1985,7 +2036,14 @@
"content": {
"application/json": {
"schema": {
-- "$ref": "#/components/schemas/ai.run-read-result.v1"
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/ai.run-create-result.v1"
++ },
++ {
++ "$ref": "#/components/schemas/ai.run-read-result.v1"
++ }
++ ]
}
}
}
@@@ -2304,19 +2362,27 @@
"/api/v1/summarize": {
"post": {
"operationId": "post_api_v1_summarize",
-- "summary": "Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them.",
-- "description": "**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
++ "summary": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs.",
++ "description": "**ai.app.run** (public): Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.\n\n**ai.run.create** (first-party): Create workflow runs (POST /api/v1/runs and the direct chat/generate/summarize/classify/extract/enrich/embed operations), cancel or retry the caller's own runs and attach citations to them. A token's `ns` claim limits which workflow namespaces it may run (live.*, wiki.*, ...). Quotas are checked before any provider call.",
"tags": [
++ "ai.app",
"ai.run"
],
"x-openvibe-capabilities": [
++ "ai.app.run",
"ai.run.create"
],
"x-openvibe-visibility": [
++ "public",
"first-party"
],
-- "x-openvibe-quota-class": "ai-run",
++ "x-openvibe-quota-class": "ai-app-run",
"security": [
++ {
++ "openvibeToken": [
++ "ai.app.run"
++ ]
++ },
{
"openvibeToken": [
"ai.run.create"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 62e925e8..7a861ae6 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.108.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex dae901cc..286f57ff 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.108.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 22172810..f38a36c3 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.108.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 8fd2eda1..5c69f899 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.108.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 270a8efd..e024e66b 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.108.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -1052,6 +1052,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
},
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex ba51afe2..4eda6549 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.108.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 eae2530b..98cafafe 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.108.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 c107ab55..7b0a6605 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.108.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 6acd0d8b..c3b7e7a5 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.108.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -941,6 +941,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
},
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 7894ecd3..079ffa8d 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.108.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 e98d2b31..96a8e71b 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.108.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -2320,6 +2320,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
},
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 3d7ee5a4..04d3f040 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -6,7 +6,7 @@
"name": "OpenVibe.AI",
"origin": "https://ai.openvibe.network",
"operations": 43,
-- "capabilities": 7
++ "capabilities": 8
},
{
"service": "billing",
@@@ -127,6 +127,13 @@
"operations": 29,
"capabilities": 8
},
++ {
++ "service": "run",
++ "name": "OpenVibe.Run",
++ "origin": "https://openvibe.run",
++ "operations": 7,
++ "capabilities": 6
++ },
{
"service": "search",
"name": "OpenVibe.Search",
@@@ -134,6 +141,13 @@
"operations": 8,
"capabilities": 3
},
++ {
++ "service": "services",
++ "name": "OpenVibe.Services",
++ "origin": "https://openvibe.services",
++ "operations": 2,
++ "capabilities": 1
++ },
{
"service": "sources",
"name": "OpenVibe.Sources",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 4630ba32..ce1f19f0 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.108.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 5c1f57c5..eb4897af 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.108.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -568,6 +568,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
},
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex ba8780a4..90e9967f 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.108.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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"
@@@ -2285,6 +2285,28 @@
"string",
"null"
]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
}
}
},
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex 3189a220..7aca1859 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.108.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 39d609f2..6ca0c1dc 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.108.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex e21251c7..3ae09d44 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.108.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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
nnew file mode 100644
iindex 00000000..5c54981c
---- /dev/null
++++ b/generated/openapi/run.json
@@@ -0,0 +1,1735 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Run API",
++ "version": "0.109.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.run"
++ }
++ ],
++ "paths": {
++ "/api/v1/admin/jobs": {
++ "get": {
++ "operationId": "get_api_v1_admin_jobs",
++ "summary": "Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job.",
++ "description": "**run.job.admin** (internal): Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job. Never delegated and never in an app or mod token.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.admin"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "run-admin",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.admin"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/run.job-list-query.v1"
++ },
++ "parameters": [
++ {
++ "name": "state",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "enum": [
++ "queued",
++ "placed",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ]
++ }
++ },
++ {
++ "name": "project_id",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
++ },
++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
++ },
++ {
++ "name": "limit",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "oneOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 200
++ },
++ {
++ "type": "string",
++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
++ }
++ ],
++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
++ },
++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
++ },
++ {
++ "name": "cursor",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 512,
++ "description": "next_cursor of the previous page."
++ },
++ "description": "next_cursor of the previous page."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/jobs": {
++ "get": {
++ "operationId": "get_api_v1_jobs",
++ "summary": "List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=.",
++ "description": "**run.job.list** (public): List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.list"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.list"
++ ]
++ }
++ ],
++ "x-openvibe-input": {
++ "$ref": "#/components/schemas/run.job-list-query.v1"
++ },
++ "parameters": [
++ {
++ "name": "state",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "enum": [
++ "queued",
++ "placed",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ]
++ }
++ },
++ {
++ "name": "project_id",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
++ },
++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
++ },
++ {
++ "name": "limit",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "oneOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 200
++ },
++ {
++ "type": "string",
++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
++ }
++ ],
++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
++ },
++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
++ },
++ {
++ "name": "cursor",
++ "in": "query",
++ "required": false,
++ "schema": {
++ "type": "string",
++ "maxLength": 512,
++ "description": "next_cursor of the previous page."
++ },
++ "description": "next_cursor of the previous page."
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ },
++ "post": {
++ "operationId": "post_api_v1_jobs",
++ "summary": "Submit a job to OpenVibe.Run: POST /api/v1/jobs.",
++ "description": "**run.job.submit** (public): Submit a job to OpenVibe.Run: POST /api/v1/jobs. The job belongs to the token's project, which is billed per wall-clock second it runs (service run, operation function.invoke, unit s). An idempotency_key makes a retry return the first job.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.submit"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-submit",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.submit"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-create-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-create-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/jobs/{id}": {
++ "get": {
++ "operationId": "get_api_v1_jobs_id",
++ "summary": "Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result).",
++ "description": "**run.job.read** (public): Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result).",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/jobs/{id}/cancel": {
++ "post": {
++ "operationId": "post_api_v1_jobs_id_cancel",
++ "summary": "Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel.",
++ "description": "**run.job.cancel** (public): Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel. A queued or placed job never starts; a running one is killed (platform.job-frame@1 job_cancel); cancelling an ended job changes nothing and answers it as it is.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.cancel"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-control",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.cancel"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/jobs/{id}/stream": {
++ "get": {
++ "operationId": "get_api_v1_jobs_id_stream",
++ "summary": "Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer toke",
++ "description": "**run.job.stream** (public): Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.stream"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-stream",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.stream"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-stream-ticket-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/jobs/{id}/stream/ticket": {
++ "post": {
++ "operationId": "post_api_v1_jobs_id_stream_ticket",
++ "summary": "Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer toke",
++ "description": "**run.job.stream** (public): Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID.",
++ "tags": [
++ "run.job"
++ ],
++ "x-openvibe-capabilities": [
++ "run.job.stream"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "run-stream",
++ "security": [
++ {
++ "openvibeToken": [
++ "run.job.stream"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/run.job-stream-ticket-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ },
++ "platform.runtime-class.v1": {
++ "title": "RuntimeClass",
++ "description": "platform.runtime-class@1: the kind of execution environment a platform.job@1 asks for (plan T14 Run; ADR-034 proposed). A name only: this contract defines no behavior, and a worker runs only the classes it advertises and refuses every other one (`nack`). `function` is the first class implemented (OpenVibe.Node's function worker); the others are reserved names. Each class names one reserved Fabric capability in [email protected] — `worker:` + the class (worker:function, worker:code, worker:browser, worker:linux, worker:desktop, worker:gpu) — and a node advertises the name of each class it runs.",
++ "$comment": "Additive: a class may be added to v1; none is removed, renamed or reordered.",
++ "type": "string",
++ "oneOf": [
++ {
++ "const": "function",
++ "description": "A pre-registered artifact (name and exact version) run once with declared args to one result: single-shot, no persistent filesystem, no dependency resolution, network denied."
++ },
++ {
++ "const": "code",
++ "description": "Arbitrary source with its dependency tree in a longer-lived environment; needs image handling and isolation stronger than a container."
++ },
++ {
++ "const": "browser",
++ "description": "A headless or headed web browser session driven remotely."
++ },
++ {
++ "const": "linux",
++ "description": "A full Linux environment (shell, filesystem, processes) that lives across commands."
++ },
++ {
++ "const": "desktop",
++ "description": "A graphical desktop session with screen and input under control levels."
++ },
++ {
++ "const": "gpu",
++ "description": "A workload that holds one or more GPUs for its lifetime."
++ }
++ ],
++ "examples": [
++ "function",
++ "gpu"
++ ]
++ },
++ "platform.workload-requirements.v1": {
++ "title": "WorkloadRequirements",
++ "description": "What a workload needs, so the platform can place it (roadmap WS-Z9). Hard constraints are filtered before any objective is scored; a cheaper candidate that misses one is never chosen.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "kind",
++ "mobility",
++ "latency_class",
++ "objective"
++ ],
++ "properties": {
++ "kind": {
++ "type": "string",
++ "description": "what runs, e.g. watch.browser, media.transcode, ai.run, events.deliver"
++ },
++ "mobility": {
++ "type": "string",
++ "enum": [
++ "request",
++ "job",
++ "session",
++ "stateful-partition"
++ ]
++ },
++ "latency_class": {
++ "type": "string",
++ "enum": [
++ "realtime",
++ "interactive",
++ "background",
++ "bulk",
++ "critical"
++ ]
++ },
++ "objective": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "lowest-latency",
++ "balanced",
++ "private",
++ "local-only",
++ "first-party-only",
++ "high-reliability",
++ "correctness"
++ ]
++ },
++ "max_latency_ms": {
++ "type": "number",
++ "minimum": 0
++ },
++ "deadline_ms": {
++ "type": "number",
++ "minimum": 0
++ },
++ "max_cost_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "durability": {
++ "type": "string",
++ "enum": [
++ "none",
++ "at-least-once",
++ "authoritative"
++ ]
++ },
++ "residency": {
++ "type": "string",
++ "description": "region or jurisdiction the work and its data must stay in"
++ },
++ "trust": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "first-party",
++ "user-owned",
++ "partner",
++ "community",
++ "external"
++ ]
++ },
++ "minItems": 1,
++ "description": "trust classes allowed to run it (ADR-046); when absent, first-party, partner, community and external. user-owned is never a default: a user-owned offer is eligible only when this list names it"
++ },
++ "region": {
++ "type": "string",
++ "description": "a region, or 'nearest'"
++ },
++ "capabilities": {
++ "type": "array",
++ "items": {
++ "type": "string"
++ },
++ "description": "capabilities a candidate must offer (worker:browser, …)"
++ },
++ "resources": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "cpu": {
++ "type": "number",
++ "minimum": 0
++ },
++ "memory_mb": {
++ "type": "number",
++ "minimum": 0
++ },
++ "gpu": {
++ "type": "boolean"
++ },
++ "scratch_gb": {
++ "type": "number",
++ "minimum": 0
++ },
++ "egress": {
++ "type": "string",
++ "enum": [
++ "none",
++ "public",
++ "openvibe-only"
++ ]
++ }
++ }
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "partition_key": {
++ "type": "string",
++ "description": "stable key for rendezvous placement (room, stream, session)"
++ }
++ }
++ },
++ "run.job-create-request.v1": {
++ "title": "RunJobCreateRequest",
++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, carried per job as the same-named platform.job@1 net: none (default; no network), public or openvibe-only. A job asking for public or openvibe-only is placed only on a node whose worker.egress allows it; a worker whose host policy is stricter runs the stricter one. REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "class",
++ "limits"
++ ],
++ "properties": {
++ "class": {
++ "$ref": "#/components/schemas/platform.runtime-class.v1"
++ },
++ "artifact": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/artifact"
++ },
++ "args": {
++ "type": "object",
++ "description": "The artifact's declared input as JSON; default {}."
++ },
++ "inputs": {
++ "type": "array",
++ "maxItems": 32,
++ "items": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/input"
++ },
++ "description": "Files placed in the job directory before the process starts; names are unique (else 422)."
++ },
++ "limits": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/limits"
++ },
++ "ttl_ms": {
++ "type": "integer",
++ "minimum": 1000,
++ "maximum": 86400000,
++ "description": "Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000."
++ },
++ "egress": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/egress"
++ },
++ "requirements": {
++ "$ref": "#/components/schemas/platform.workload-requirements.v1"
++ },
++ "idempotency_key": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/idempotency_key"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "required": [
++ "class"
++ ],
++ "properties": {
++ "class": {
++ "enum": [
++ "function",
++ "code"
++ ]
++ }
++ }
++ },
++ "then": {
++ "required": [
++ "artifact"
++ ]
++ }
++ }
++ ],
++ "$defs": {
++ "artifact": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "The pre-registered artifact to run (platform.job@1 artifact).",
++ "required": [
++ "name",
++ "version"
++ ],
++ "properties": {
++ "name": {
++ "type": "string",
++ "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$"
++ },
++ "version": {
++ "type": "string",
++ "pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$",
++ "description": "One exact version, never a range"
++ }
++ }
++ },
++ "input": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "One input file: an OpenVibe.Media object the caller's project can read, pinned by digest.",
++ "required": [
++ "name",
++ "media_id",
++ "sha256"
++ ],
++ "properties": {
++ "name": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$",
++ "description": "File name in the job directory; no path separators."
++ },
++ "media_id": {
++ "type": "string",
++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{64}$",
++ "description": "Lowercase hex SHA-256 of the object's bytes."
++ },
++ "size_bytes": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "limits": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "Caps on the running process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.",
++ "required": [
++ "wall_ms",
++ "cpu_ms",
++ "mem_bytes"
++ ],
++ "properties": {
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Running time from `job_started`"
++ },
++ "cpu_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "CPU time, user plus system"
++ },
++ "mem_bytes": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Peak resident memory"
++ }
++ }
++ },
++ "egress": {
++ "type": "string",
++ "enum": [
++ "none",
++ "public",
++ "openvibe-only"
++ ],
++ "default": "none",
++ "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
++ },
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{1,128}$"
++ }
++ },
++ "examples": [
++ {
++ "class": "function",
++ "artifact": {
++ "name": "thumbnail",
++ "version": "1.2.0"
++ },
++ "args": {
++ "width": 320
++ },
++ "inputs": [
++ {
++ "name": "photo.jpg",
++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
++ "size_bytes": 482113
++ }
++ ],
++ "limits": {
++ "wall_ms": 30000,
++ "cpu_ms": 30000,
++ "mem_bytes": 268435456
++ },
++ "egress": "none",
++ "idempotency_key": "thumb:photo-7"
++ }
++ ]
++ },
++ "run.job-create-result.v1": {
++ "title": "RunJobCreateResult",
++ "description": "run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "created",
++ "job"
++ ],
++ "properties": {
++ "created": {
++ "type": "boolean",
++ "description": "false when the idempotency_key matched an earlier job."
++ },
++ "job": {
++ "$ref": "#/components/schemas/run.job-read-result.v1"
++ }
++ }
++ },
++ "run.job-list-query.v1": {
++ "title": "RunJobListQuery",
++ "description": "run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.",
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "state": {
++ "enum": [
++ "queued",
++ "placed",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ]
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
++ },
++ "limit": {
++ "oneOf": [
++ {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 200
++ },
++ {
++ "type": "string",
++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
++ }
++ ],
++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
++ },
++ "cursor": {
++ "type": "string",
++ "maxLength": 512,
++ "description": "next_cursor of the previous page."
++ }
++ }
++ },
++ "run.job-list-result.v1": {
++ "title": "RunJobListResult",
++ "description": "run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "jobs",
++ "next_cursor"
++ ],
++ "properties": {
++ "jobs": {
++ "type": "array",
++ "maxItems": 200,
++ "items": {
++ "$ref": "#/components/schemas/run.job-read-result.v1"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 512,
++ "description": "Pass as cursor for the next page; null on the last page."
++ }
++ }
++ },
++ "run.job-read-result.v1": {
++ "title": "RunJobReadResult",
++ "description": "run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "project_id",
++ "requester",
++ "state",
++ "class",
++ "artifact",
++ "inputs",
++ "limits",
++ "ttl_ms",
++ "egress",
++ "requirements",
++ "idempotency_key",
++ "placement",
++ "timings",
++ "exit",
++ "result",
++ "usage",
++ "error"
++ ],
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>)."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The project the caller's token names; usage is billed to it."
++ },
++ "requester": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1",
++ "description": "The token principal that submitted the job."
++ },
++ "state": {
++ "enum": [
++ "queued",
++ "placed",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ]
++ },
++ "class": {
++ "$ref": "#/components/schemas/platform.runtime-class.v1"
++ },
++ "artifact": {
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/artifact"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "inputs": {
++ "type": "array",
++ "maxItems": 32,
++ "items": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/input"
++ }
++ },
++ "limits": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/limits"
++ },
++ "ttl_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "The job's lifetime from created_at, the default filled in."
++ },
++ "egress": {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/egress"
++ },
++ "requirements": {
++ "$ref": "#/components/schemas/platform.workload-requirements.v1",
++ "description": "What the job was placed against, the default filled in."
++ },
++ "idempotency_key": {
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/run.job-create-request.v1/$defs/idempotency_key"
++ },
++ {
++ "type": "null"
++ }
++ ]
++ },
++ "placement": {
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/run.job-read-result.v1/$defs/placement"
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Where the job was sent; null while queued, and for a job cancelled or expired before placement."
++ },
++ "timings": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "created_at",
++ "placed_at",
++ "started_at",
++ "finished_at"
++ ],
++ "properties": {
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "placed_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "started_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "From job_started started_ms; null until the process started."
++ },
++ "finished_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When the job reached its end state; null before."
++ }
++ }
++ },
++ "exit": {
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/run.job-read-result.v1/$defs/exit"
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "The node's job_exit; null until it arrived, and for a job that ended before a node ran it."
++ },
++ "result": {
++ "description": "The artifact's declared output (job_exit result, any JSON); null unless state is succeeded."
++ },
++ "usage": {
++ "$ref": "#/components/schemas/run.job-read-result.v1/$defs/usage"
++ },
++ "error": {
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/run.job-read-result.v1/$defs/error"
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "Why the job failed or expired; null in every other state."
++ }
++ },
++ "$defs": {
++ "placement": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "node",
++ "provider",
++ "region"
++ ],
++ "properties": {
++ "node": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 128,
++ "description": "The worker's device id (the usage reading's `node`)."
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128,
++ "description": "The offer's provider (the usage reading's `provider`); null for a first-party node."
++ },
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64
++ }
++ }
++ },
++ "exit": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "reason",
++ "code"
++ ],
++ "properties": {
++ "reason": {
++ "enum": [
++ "exited",
++ "cancelled",
++ "ttl",
++ "limit",
++ "stopped",
++ "failed"
++ ],
++ "description": "platform.job-frame@1 job_exit reason."
++ },
++ "code": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "description": "The process's exit status; null when it was killed or never ran."
++ }
++ }
++ },
++ "usage": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "seconds",
++ "wall_ms",
++ "cpu_ms",
++ "mem_peak_bytes"
++ ],
++ "properties": {
++ "seconds": {
++ "type": "number",
++ "minimum": 0,
++ "description": "Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started."
++ },
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cpu_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Informational, never billed."
++ },
++ "mem_peak_bytes": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Informational, never billed."
++ }
++ }
++ },
++ "error": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "code",
++ "detail"
++ ],
++ "properties": {
++ "code": {
++ "type": "string",
++ "pattern": "^run\\.[a-z0-9_]+(\\.[a-z0-9_]+)*$",
++ "description": "run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl."
++ },
++ "detail": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 1024
++ }
++ }
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "queued"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "placement": {
++ "type": "null"
++ },
++ "exit": {
++ "type": "null"
++ },
++ "timings": {
++ "type": "object",
++ "properties": {
++ "placed_at": {
++ "type": "null"
++ },
++ "started_at": {
++ "type": "null"
++ },
++ "finished_at": {
++ "type": "null"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "placed",
++ "running"
++ ]
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "placement": {
++ "type": "object"
++ },
++ "exit": {
++ "type": "null"
++ },
++ "timings": {
++ "type": "object",
++ "properties": {
++ "placed_at": {
++ "type": "string"
++ },
++ "finished_at": {
++ "type": "null"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "running"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "timings": {
++ "type": "object",
++ "properties": {
++ "started_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ]
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "timings": {
++ "type": "object",
++ "properties": {
++ "finished_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "succeeded"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "placement": {
++ "type": "object"
++ },
++ "exit": {
++ "type": "object",
++ "properties": {
++ "reason": {
++ "const": "exited"
++ },
++ "code": {
++ "const": 0
++ }
++ }
++ },
++ "timings": {
++ "type": "object",
++ "properties": {
++ "started_at": {
++ "type": "string"
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "enum": [
++ "failed",
++ "expired"
++ ]
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "properties": {
++ "error": {
++ "type": "object"
++ }
++ }
++ },
++ "else": {
++ "properties": {
++ "error": {
++ "type": "null"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "succeeded"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "else": {
++ "properties": {
++ "result": {
++ "type": "null"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "class": {
++ "enum": [
++ "function",
++ "code"
++ ]
++ }
++ },
++ "required": [
++ "class"
++ ]
++ },
++ "then": {
++ "properties": {
++ "artifact": {
++ "type": "object"
++ }
++ }
++ }
++ }
++ ]
++ },
++ "run.job-stream-ticket-result.v1": {
++ "title": "RunJobStreamTicketResult",
++ "description": "run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "ticket",
++ "expires_at",
++ "expires_in",
++ "stream_url",
++ "job_id"
++ ],
++ "properties": {
++ "ticket": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
++ "maxLength": 4096,
++ "description": "The signed ticket (a compact JWS)."
++ },
++ "expires_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "expires_in": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 300,
++ "description": "Seconds until the ticket expires (120)."
++ },
++ "stream_url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https?://[^?#]+/api/v1/jobs/job_[0-9A-HJKMNP-TV-Z]{26}/stream$"
++ },
++ "job_id": {
++ "type": "string",
++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex fb4d5241..bca56c38 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.108.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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
nnew file mode 100644
iindex 00000000..eda49d10
---- /dev/null
++++ b/generated/openapi/services.json
@@@ -0,0 +1,442 @@
++{
++ "openapi": "3.1.0",
++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
++ "info": {
++ "title": "OpenVibe.Services API",
++ "version": "0.109.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "license": {
++ "name": "MIT",
++ "identifier": "MIT"
++ }
++ },
++ "servers": [
++ {
++ "url": "https://openvibe.services"
++ }
++ ],
++ "paths": {
++ "/api/v1/resources": {
++ "get": {
++ "operationId": "get_api_v1_resources",
++ "summary": "Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service token holding the capability or by a person's user token scoped to a project they own ",
++ "description": "**services.resource.read** (first-party): Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service token holding the capability or by a person's user token scoped to a project they own or belong to (one project per request).",
++ "tags": [
++ "services.resource"
++ ],
++ "x-openvibe-capabilities": [
++ "services.resource.read"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.resource.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.resource-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/api/v1/resources/{ovrn}": {
++ "get": {
++ "operationId": "get_api_v1_resources_ovrn",
++ "summary": "Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service token holding the capability or by a person's user token scoped to a project they own ",
++ "description": "**services.resource.read** (first-party): Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service token holding the capability or by a person's user token scoped to a project they own or belong to (one project per request).",
++ "tags": [
++ "services.resource"
++ ],
++ "x-openvibe-capabilities": [
++ "services.resource.read"
++ ],
++ "x-openvibe-visibility": [
++ "first-party"
++ ],
++ "x-openvibe-quota-class": "standard",
++ "security": [
++ {
++ "openvibeToken": [
++ "services.resource.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "ovrn",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/common.resource-list-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "components": {
++ "schemas": {
++ "common.resource-list-result.v1": {
++ "title": "ResourceListResult",
++ "description": "A page of the resource index (roadmap WS-Z7).",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "resources",
++ "next_cursor"
++ ],
++ "properties": {
++ "resources": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/common.resource-summary.v1"
++ }
++ },
++ "next_cursor": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "partial": {
++ "type": "array",
++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ }
++ }
++ }
++ }
++ }
++ },
++ "common.resource-summary.v1": {
++ "title": "ResourceSummary",
++ "description": "One resource of any service, as the resource index lists it (roadmap WS-Z7): every service answers GET /api/v1/resources with these, so OpenVibe.Services shows any resource without owning its data.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "kind",
++ "service",
++ "state",
++ "created_at"
++ ],
++ "properties": {
++ "id": {
++ "type": "string"
++ },
++ "kind": {
++ "type": "string",
++ "description": "service.kind, e.g. watch.watch, actor.actor, media.object, events.subscription"
++ },
++ "service": {
++ "type": "string"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "ovrn": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^ovrn:[a-z][a-z0-9-]{0,31}:prj_[0-9A-HJKMNP-TV-Z]{26}:[a-z][a-z0-9-]{0,39}/[a-z]{3}_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Optional resource name (ADR-034 section 2), the name grants, audit, events, usage records and bills use: ovrn:<service>:<project_id>:<type>/<id>, e.g. ovrn:watch:<project_id>:watch/<wch_id>, with the pattern of common.usage-recorded@1 resource. Its service, project and id are this summary's service, project_id and id. Added in 1.1.0."
++ },
++ "owner": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "name": {
++ "type": "string"
++ },
++ "state": {
++ "type": "string"
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "usage": {
++ "type": "object",
++ "additionalProperties": {
++ "type": "number",
++ "minimum": 0
++ },
++ "properties": {},
++ "description": "cost units by name (WS-Z1)"
++ },
++ "links": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "self": {
++ "type": "string",
++ "format": "uri"
++ },
++ "console": {
++ "type": "string",
++ "format": "uri"
++ },
++ "docs": {
++ "type": "string",
++ "format": "uri"
++ }
++ }
++ }
++ }
++ },
++ "errors.problem.v1": {
++ "title": "Problem",
++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
++ "type": "object",
++ "required": [
++ "type",
++ "title",
++ "status",
++ "code"
++ ],
++ "additionalProperties": true,
++ "properties": {
++ "type": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "title": {
++ "type": "string",
++ "minLength": 1
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 400,
++ "maximum": 599
++ },
++ "detail": {
++ "type": "string"
++ },
++ "instance": {
++ "type": "string",
++ "format": "uri-reference"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
++ },
++ "request_id": {
++ "type": "string"
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "errors": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "message"
++ ],
++ "properties": {
++ "path": {
++ "type": "string"
++ },
++ "message": {
++ "type": "string"
++ }
++ }
++ }
++ },
++ "error": {
++ "type": "string",
++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
++ }
++ }
++ },
++ "identity.subject-ref.v1": {
++ "title": "SubjectRef",
++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
++ "oneOf": [
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "user"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "UserSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "guest"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "GuestSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "app"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AppSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "mod"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "ModSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "enum": [
++ "service",
++ "system"
++ ]
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ }
++ },
++ "description": "PrincipalSubject"
++ },
++ {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "const": "agent"
++ },
++ "id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ },
++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
++ }
++ ]
++ }
++ },
++ "securitySchemes": {
++ "openvibeToken": {
++ "type": "http",
++ "scheme": "bearer",
++ "bearerFormat": "JWT",
++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
++ }
++ }
++ }
++}
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 50ee0249..1bb8e225 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.108.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 80518359..1fa4a9c8 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.108.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 731d1a2e..61d5b8e7 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.108.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 fc41ade6..a80fb961 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.108.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 5397acd6..863f5a7e 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.108.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 68e2617d..792742c5 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.108.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 0e897ded..acc689b8 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.108.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 24b14e0f..c8105a43 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.108.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.108.0): each operation lists the capabilities it performs; a token for 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.109.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.109.0): each operation lists the capabilities it performs; a token for 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 e56f0166..a54bf774 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -56689,13 +56689,20 @@ export interface ResourceSummary {
};
}
--/** [email protected] (owner: contracts) */
++/** [email protected] (owner: contracts) */
/**
* A page of the resource index (roadmap WS-Z7).
*/
export interface ResourceListResult {
resources: ResourceSummary[];
next_cursor: string | null;
++ /**
++ * Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.
++ */
++ partial?: {
++ service: string;
++ code: string;
++ }[];
}
/** [email protected] (owner: contracts) */
@@@ -57130,7 +57137,7 @@ export type BotDeviceConnectResult =
/** [email protected] (owner: bot) */
/**
-- * bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver.
++ * bot.robot-profile@1 (ADR-043 decision 7): a robot profile as OpenVibe.Bot ships it (server/profiles/*.json) and serves it (GET /api/v1/profiles, /profiles/:id; validateProfile in server/profiles/index.js). The panel is a function of the profile. Capability and widget names are open strings that Bot checks against registries in code, so a new one never needs a schema change. As served, vendor, kind, description, variants and camera are null when the file leaves them out, limits carry their defaults (max_speed 1, max_turn 1, max_command_ms 300, heartbeat_ms 1000) and commands always include halt. mapping is the driver's own hardware map, open beyond driver. commands.button (an owner's buttons: names, each a label with an optional key, cooldown_ms and hold) and commands.point (a video-click layer, with its own cooldown_ms), rendered by the widget types buttons and video_click, were added in 0.109.0 (plan T15 R9).
*/
export interface BotRobotProfile {
id: string;
@@@ -57241,6 +57248,21 @@ export interface BotRobotProfile {
faces?: string[];
max_chars?: number;
};
++ button?: {
++ names: {
++ [k: string]:
++ | {
++ label: string;
++ key?: string;
++ cooldown_ms?: number;
++ hold?: boolean;
++ }
++ | undefined;
++ };
++ };
++ point?: {
++ cooldown_ms?: number;
++ };
halt?: {};
};
/**
@@@ -57253,7 +57275,7 @@ export interface BotRobotProfile {
label?: string;
order?: number;
command?: {
-- kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt";
++ kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt" | "button" | "point";
/**
* @minItems 1
*/
@@@ -57266,7 +57288,7 @@ export interface BotRobotProfile {
label?: string;
order?: number;
command?: {
-- kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt";
++ kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt" | "button" | "point";
/**
* @minItems 1
*/
@@@ -57505,18 +57527,20 @@ export type BotDeviceMessage =
/** [email protected] (owner: bot) */
/**
-- * bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1).
++ * bot.command@1: an operator's command frame on OpenVibe.Bot's operator WebSocket (wss://openvibe.bot/control, after join; server/realtime.js onOperatorCommand). id is the operator's idempotency key (at most 64 characters; Bot mints one when it is missing): a repeated id answers with the first result. Bot's gate stamps the robot, the operator and the role, builds the value from the robot profile's commands (clamped by the owner's limits) and sets the deadline; ms asks for a duration within max_command_ms. Answered by one command_result frame (bot.command-result@1). The kinds button (a profile button by name, with an optional state for a hold button) and point (a normalised x, y video click) were added in 0.109.0 (plan T15 R9).
*/
--export interface BotCommand {
++export type BotCommand = {
++ [k: string]: unknown | undefined;
++} & {
v?: 1;
seq?: number;
ts?: number;
type: "command";
id?: string;
-- kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt";
++ kind: "drive" | "actuator" | "ptz" | "say" | "display" | "halt" | "button" | "point";
value?: {};
ms?: number;
--}
++};
/** [email protected] (owner: bot) */
/**
ddiff --git a/manifests/capabilities/ai.app.run.json b/manifests/capabilities/ai.app.run.json
nnew file mode 100644
iindex 00000000..fc5cac8a
---- /dev/null
++++ b/manifests/capabilities/ai.app.run.json
@@@ -0,0 +1,27 @@
++{
++ "id": "ai.app.run",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "public",
++ "description": "Run the AI operations as a developer app (ADR-014): POST /api/v1/chat, /generate, /summarize, /classify, /extract, /embed and GET /api/v1/runs/:id for the app's own runs. The app token (sub app:app_<ULID>, project_id prj_<ULID>, env sandbox|production, audience openvibe.ai) runs as its project: every run is metered (platform.usage-sample@1 to OpenVibe.Billing, subject project:prj_<ULID>) against the project's bounded free allowance, and metered capacity is spent only under the project's tier budget; a sandbox run, and every run no tier budget governs, uses only free and local capacity. An app runs only these operations (never a workflow by key), never reads another project's runs, and never uses a person's BYO provider key.",
++ "inputSchema": "ai.run-create-request@1",
++ "outputSchema": "ai.run-create-result@1",
++ "permissions": [
++ "ai:run"
++ ],
++ "resourceConstraints": [
++ "project"
++ ],
++ "quotaClass": "ai-app-run",
++ "events": [],
++ "implementedBy": [
++ "POST /api/v1/chat",
++ "POST /api/v1/generate",
++ "POST /api/v1/summarize",
++ "POST /api/v1/classify",
++ "POST /api/v1/extract",
++ "POST /api/v1/embed",
++ "GET /api/v1/runs/:id"
++ ]
++}
ddiff --git a/manifests/capabilities/run.job.admin.json b/manifests/capabilities/run.job.admin.json
iindex 367cfb68..d95f6575 100644
---- a/manifests/capabilities/run.job.admin.json
++++ b/manifests/capabilities/run.job.admin.json
@@@ -2,10 +2,10 @@
"id": "run.job.admin",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "internal",
"sensitive": true,
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job. Never delegated and never in an app or mod token. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job. Never delegated and never in an app or mod token.",
"inputSchema": "run.job-list-query@1",
"outputSchema": "run.job-list-result@1",
"permissions": [
@@@ -18,5 +18,7 @@
"events": [
"run.job.cancelled"
],
-- "implementedBy": []
++ "implementedBy": [
++ "GET /api/v1/admin/jobs"
++ ]
}
ddiff --git a/manifests/capabilities/run.job.cancel.json b/manifests/capabilities/run.job.cancel.json
iindex 4deb6e9a..179757ab 100644
---- a/manifests/capabilities/run.job.cancel.json
++++ b/manifests/capabilities/run.job.cancel.json
@@@ -2,9 +2,9 @@
"id": "run.job.cancel",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel. A queued or placed job never starts; a running one is killed (platform.job-frame@1 job_cancel); cancelling an ended job changes nothing and answers it as it is. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel. A queued or placed job never starts; a running one is killed (platform.job-frame@1 job_cancel); cancelling an ended job changes nothing and answers it as it is.",
"inputSchema": "common.no-body@1",
"outputSchema": "run.job-read-result@1",
"permissions": [
@@@ -17,5 +17,7 @@
"events": [
"run.job.cancelled"
],
-- "implementedBy": []
++ "implementedBy": [
++ "POST /api/v1/jobs/:id/cancel"
++ ]
}
ddiff --git a/manifests/capabilities/run.job.list.json b/manifests/capabilities/run.job.list.json
iindex 28231d02..1fe4c861 100644
---- a/manifests/capabilities/run.job.list.json
++++ b/manifests/capabilities/run.job.list.json
@@@ -2,9 +2,9 @@
"id": "run.job.list",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=.",
"inputSchema": "run.job-list-query@1",
"outputSchema": "run.job-list-result@1",
"permissions": [
@@@ -15,5 +15,7 @@
],
"quotaClass": "run-read",
"events": [],
-- "implementedBy": []
++ "implementedBy": [
++ "GET /api/v1/jobs"
++ ]
}
ddiff --git a/manifests/capabilities/run.job.read.json b/manifests/capabilities/run.job.read.json
iindex 26fe1bd0..cbcd05a0 100644
---- a/manifests/capabilities/run.job.read.json
++++ b/manifests/capabilities/run.job.read.json
@@@ -2,9 +2,9 @@
"id": "run.job.read",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result). It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result).",
"inputSchema": "common.no-body@1",
"outputSchema": "run.job-read-result@1",
"permissions": [
@@@ -15,5 +15,7 @@
],
"quotaClass": "run-read",
"events": [],
-- "implementedBy": []
++ "implementedBy": [
++ "GET /api/v1/jobs/:id"
++ ]
}
ddiff --git a/manifests/capabilities/run.job.stream.json b/manifests/capabilities/run.job.stream.json
iindex 09a8656d..57ca8f7c 100644
---- a/manifests/capabilities/run.job.stream.json
++++ b/manifests/capabilities/run.job.stream.json
@@@ -2,9 +2,9 @@
"id": "run.job.stream",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID.",
"inputSchema": "common.no-body@1",
"outputSchema": "run.job-stream-ticket-result@1",
"permissions": [
@@@ -15,5 +15,8 @@
],
"quotaClass": "run-stream",
"events": [],
-- "implementedBy": []
++ "implementedBy": [
++ "POST /api/v1/jobs/:id/stream/ticket",
++ "GET /api/v1/jobs/:id/stream"
++ ]
}
ddiff --git a/manifests/capabilities/run.job.submit.json b/manifests/capabilities/run.job.submit.json
iindex 1b9564c5..6e6107c8 100644
---- a/manifests/capabilities/run.job.submit.json
++++ b/manifests/capabilities/run.job.submit.json
@@@ -2,10 +2,10 @@
"id": "run.job.submit",
"version": "1.0",
"owner": "run",
-- "status": "planned",
++ "status": "active",
"visibility": "public",
"sensitive": true,
-- "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Submit a job to OpenVibe.Run: POST /api/v1/jobs. The job belongs to the token's project, which is billed per wall-clock second it runs (service run, operation function.invoke, unit s). An idempotency_key makes a retry return the first job. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
++ "description": "Submit a job to OpenVibe.Run: POST /api/v1/jobs. The job belongs to the token's project, which is billed per wall-clock second it runs (service run, operation function.invoke, unit s). An idempotency_key makes a retry return the first job.",
"inputSchema": "run.job-create-request@1",
"outputSchema": "run.job-create-result@1",
"permissions": [
@@@ -22,5 +22,7 @@
"run.job.failed",
"run.job.expired"
],
-- "implementedBy": []
++ "implementedBy": [
++ "POST /api/v1/jobs"
++ ]
}
ddiff --git a/manifests/capabilities/services.resource.read.json b/manifests/capabilities/services.resource.read.json
nnew file mode 100644
iindex 00000000..96baa16e
---- /dev/null
++++ b/manifests/capabilities/services.resource.read.json
@@@ -0,0 +1,22 @@
++{
++ "id": "services.resource.read",
++ "version": "1.0",
++ "owner": "services",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Services' merged resource index over every authority that serves one (ADR-048), readable by a first-party service token holding the capability or by a person's user token scoped to a project they own or belong to (one project per request).",
++ "permissions": [
++ "resources:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "standard",
++ "events": [],
++ "implementedBy": [
++ "GET /api/v1/resources",
++ "GET /api/v1/resources/:ovrn"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "common.resource-list-result@1"
++}
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex 9f4e3f61..a30dda34 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -20,6 +20,7 @@
"note": "called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page"
},
"capabilities": [
++ "ai.app.run",
"ai.credential.manage",
"ai.provider.manage",
"ai.quota.attribution.manage",
ddiff --git a/manifests/services/run.json b/manifests/services/run.json
iindex d56310e3..80310de5 100644
---- a/manifests/services/run.json
++++ b/manifests/services/run.json
@@@ -5,16 +5,16 @@
},
"id": "run",
"name": "OpenVibe.Run",
-- "status": "placeholder",
++ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Run",
"domains": [
"openvibe.run"
],
"publicOrigin": "https://openvibe.run",
"exposure": {
-- "state": "placeholder",
-- "publicSite": null,
-- "note": "planned (plan T14 R1); the OpenVibe.Run repository does not exist yet, and nothing serves openvibe.run"
++ "state": "internal",
++ "publicSite": "placeholder",
++ "note": "deployed on loopback :4920 (2026-10-07); openvibe.run keeps its OpenVibe.Sites placeholder until Run has a product surface"
},
"capabilities": [
"run.job.submit",
@@@ -54,5 +54,5 @@
"none": "not built yet; nothing runs"
}
},
-- "notes": "The job service of the network (plan T14, ADR-034): accepts jobs (run.job-create-request@1), places each on a node that advertises worker:<class> (platform.resource-offer@1, platform.runtime-offer@1, platform.workload-requirements@1), sends it as platform.job@1 over the node link and follows its platform.job-frame@1 frames. Consumes billing.usage.record to write one usage reading per wall-clock second (run:<job id>:<n>, service run, operation function.invoke, unit s) and media.object.read to resolve digest-pinned inputs. Planned: nothing is built."
++ "notes": "The job service of the network (plan T14, ADR-034): accepts jobs (run.job-create-request@1), places each on a node that advertises worker:<class> (platform.resource-offer@1, platform.runtime-offer@1, platform.workload-requirements@1), sends it as platform.job@1 over the node link and follows its platform.job-frame@1 frames. Consumes billing.usage.record to write one usage reading per wall-clock second (run:<job id>:<n>, service run, operation function.invoke, unit s) and media.object.read to resolve digest-pinned inputs. Serves its job API on loopback :4920 (Run #1 and #2, 2026-10-07)."
}
ddiff --git a/manifests/services/services.json b/manifests/services/services.json
iindex 8a46af36..4e6d8124 100644
---- a/manifests/services/services.json
++++ b/manifests/services/services.json
@@@ -5,18 +5,20 @@
},
"id": "services",
"name": "OpenVibe.Services",
-- "status": "placeholder",
++ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Services",
"domains": [
"openvibe.services"
],
"publicOrigin": "https://openvibe.services",
"exposure": {
-- "state": "placeholder",
++ "state": "internal",
"publicSite": "placeholder",
-- "note": "planned (plan T13); the OpenVibe.Services repository does not exist yet, and openvibe.services serves the OpenVibe.Sites placeholder page"
++ "note": "deployed on loopback :4930 (2026-10-07); openvibe.services keeps its OpenVibe.Sites placeholder until Services has a product surface"
},
-- "capabilities": [],
++ "capabilities": [
++ "services.resource.read"
++ ],
"eventsProduced": [],
"eventsConsumed": [],
"namespacesOwned": [],
@@@ -40,5 +42,5 @@
"none": "not built yet; nothing runs"
}
},
-- "notes": "Reserved placeholder (plan T13): the developer platform of the network, where projects, apps, keys, grants, capabilities, usage, quotas, runs, events, deploys and health over every service are looked up, with each service keeping its own data. Reserves the service id and the openvibe.services domain; no repository, route, capability or event exists yet."
++ "notes": "The developer platform of the network (plan T13, ADR-048): where projects, apps, keys, grants, capabilities, usage, quotas, runs, events, deploys and health over every service are looked up, with each service keeping its own data. Serves its merged resource index on loopback :4930 (plan T13 step 4); services.resource.read is its first capability."
}
ddiff --git a/package-lock.json b/package-lock.json
iindex 686ca912..902e4480 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.108.0",
++ "version": "0.109.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.108.0",
++ "version": "0.109.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 85ed0815..a99f5eda 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.108.0",
++ "version": "0.109.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/platform-run.test.js b/test/platform-run.test.js
iindex 60b9d5cd..48ecc020 100644
---- a/test/platform-run.test.js
++++ b/test/platform-run.test.js
@@@ -87,10 +87,17 @@ ok(contracts.ids.newId('job').match(job.properties.id.pattern), 'newId(job) mint
const succeeded = fixture('run.job-read-result', 'valid', 'succeeded');
ok(succeeded.usage.seconds === fromExit(exit).reduce((s, r) => s + r.quantity, 0) && succeeded.usage.wall_ms === exit.usage.wall_ms, 'usage.seconds is the sum of the job\'s readings');
const run = contracts.services.get('run');
--ok(run && run.status === 'placeholder' && contracts.products.get('openvibe.run').relationships.noRepo === true, 'run is a placeholder service and openvibe.run still has no repository');
--for (const id of ['run.job.submit', 'run.job.read', 'run.job.list', 'run.job.cancel', 'run.job.stream', 'run.job.admin']) {
++ok(run && run.status === 'alpha' && run.exposure.state === 'internal' && contracts.products.get('openvibe.run').relationships.noRepo === true, 'run is an alpha service (deployed on loopback) and openvibe.run still has no repository');
++for (const [id, routes] of Object.entries({
++ 'run.job.submit': ['POST /api/v1/jobs'],
++ 'run.job.read': ['GET /api/v1/jobs/:id'],
++ 'run.job.list': ['GET /api/v1/jobs'],
++ 'run.job.cancel': ['POST /api/v1/jobs/:id/cancel'],
++ 'run.job.stream': ['POST /api/v1/jobs/:id/stream/ticket', 'GET /api/v1/jobs/:id/stream'],
++ 'run.job.admin': ['GET /api/v1/admin/jobs'],
++})) {
const c = contracts.capabilities.get(id);
-- ok(c && c.owner === 'run' && c.status === 'planned' && c.description.startsWith('PLANNED') && run.capabilities.includes(id), `${id} is a planned capability Run serves`);
++ ok(c && c.owner === 'run' && c.status === 'active' && JSON.stringify(c.implementedBy) === JSON.stringify(routes) && run.capabilities.includes(id), `${id} is an active capability Run serves`);
}
ok(contracts.capabilities.get('run.job.admin').visibility === 'internal', 'run.job.admin is internal');
for (const s of ['queued', 'started', 'succeeded', 'failed', 'cancelled', 'expired']) ok(run.eventsProduced.includes(`run.job.${s}`), `run produces run.job.${s}`);