openvibers/openvibe.contracts Commits

Add browser-provider job contracts and planned dispatch capability (#112)

OP OpenVibers committed 14 Oct 2026 · c7c07e5c41 · 75 files, +838 −67

local checks ✓ 3/3 · CI ✓ 3/3 · review SHIP by Claude opus (QUALITY=high)

https://github.com/OpenVibers/OpenVibe.Contracts/pull/112

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex b167040c..ada4a41a 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ 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.155.0 — 2026-10-13
++
++- **Contracts 0.155.0: browser-provider job contracts (plan T14 R4a, ADR-034; planned, internal).** What OpenVibe.Run
++ sends to and reads from an external managed-browser provider: `browser-provider.job-create-request@1` (`id`, `class`
++ browser, `args`, `inputs` pinned by sha256, `limits`, `egress`; no project, subject, requester or credential, so the
++ provider never learns who pays), `browser-provider.job-state@1` (`state`, `started_ms`, `finished_ms`, `wall_ms` as the
++ provider's claim only, `exit_reason`, `exit_code`, `result`, `stdout` in Bot's ring shape; no node, project or class
++ field) and `browser-provider.job-create-result@1` (`{ job, created }`, 201 or 200 on an idempotent repeat). New
++ capability `browser_provider.job.dispatch` (Run's own outbound call, `planned`, `implementedBy` empty). Additive only.
++
## 0.154.0 — 2026-10-13
- **Contracts 0.154.0: `services.resource.control` capability (ADR-048 control operations)** (first-party, active):
ddiff --git a/contracts/browser-provider/job-create-request.v1.json b/contracts/browser-provider/job-create-request.v1.json
nnew file mode 100644
iindex 00000000..94d6780d
---- /dev/null
++++ b/contracts/browser-provider/job-create-request.v1.json
@@@ -0,0 +1,74 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-create-request.v1.json",
++ "title": "BrowserProviderJobCreateRequest",
++ "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "class",
++ "args",
++ "inputs",
++ "limits",
++ "egress"
++ ],
++ "properties": {
++ "id": {
++ "$ref": "../platform/job-frame.v1.json#/$defs/job_id"
++ },
++ "class": {
++ "const": "browser"
++ },
++ "args": {
++ "type": "object",
++ "description": "The job's declared input as JSON; Run enforces the 512 KiB cap before sending."
++ },
++ "inputs": {
++ "type": "array",
++ "maxItems": 32,
++ "items": {
++ "$ref": "../run/job-create-request.v1.json#/$defs/input"
++ },
++ "description": "Files for the job, each an OpenVibe.Media object pinned by sha256; names are unique; empty when the job has none."
++ },
++ "limits": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "Caps on the running job (platform.job@1 limits); the provider ends a job that hits one with exit_reason limit.",
++ "required": [
++ "wall_ms"
++ ],
++ "properties": {
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Running time from the job's start"
++ },
++ "cpu_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "CPU time, user plus system; when the provider can enforce it"
++ },
++ "mem_bytes": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Peak resident memory; when the provider can enforce it"
++ }
++ }
++ },
++ "egress": {
++ "$ref": "../run/job-create-request.v1.json#/$defs/egress"
++ }
++ },
++ "examples": [
++ {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "class": "browser",
++ "args": { "url": "https://example.com", "screenshot": true },
++ "inputs": [],
++ "limits": { "wall_ms": 60000 },
++ "egress": "public"
++ }
++ ]
++}
ddiff --git a/contracts/browser-provider/job-create-result.v1.json b/contracts/browser-provider/job-create-result.v1.json
nnew file mode 100644
iindex 00000000..a7078acf
---- /dev/null
++++ b/contracts/browser-provider/job-create-result.v1.json
@@@ -0,0 +1,37 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-create-result.v1.json",
++ "title": "BrowserProviderJobCreateResult",
++ "description": "browser-provider.job-create-result@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's answer to OpenVibe.Run's job POST (browser-provider.job-create-request@1): 201 { job, created: true } when it accepted a new job, 200 { job, created: false } for an idempotent repeat of an id it already holds (the first job's state, nothing started twice). JOB is browser-provider.job-state@1 and Run validates it before adopting any of it. A refused POST answers a problem instead (4xx; 5xx is retried by Run on its next tick), never this contract. For the later calls, GET <provider>/jobs/:id answers browser-provider.job-state@1 and POST <provider>/jobs/:id/cancel answers it too; a 404 on either means the provider does not know the job.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "job",
++ "created"
++ ],
++ "properties": {
++ "job": {
++ "$ref": "job-state.v1.json"
++ },
++ "created": {
++ "type": "boolean",
++ "description": "True when this POST created the job (201); false for an idempotent repeat (200)."
++ }
++ },
++ "examples": [
++ {
++ "job": {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "state": "queued",
++ "started_ms": null,
++ "finished_ms": null,
++ "wall_ms": 0,
++ "exit_reason": null,
++ "exit_code": null,
++ "result": null,
++ "stdout": null
++ },
++ "created": true
++ }
++ ]
++}
ddiff --git a/contracts/browser-provider/job-state.v1.json b/contracts/browser-provider/job-state.v1.json
nnew file mode 100644
iindex 00000000..6182cf40
---- /dev/null
++++ b/contracts/browser-provider/job-state.v1.json
@@@ -0,0 +1,143 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-state.v1.json",
++ "title": "BrowserProviderJobState",
++ "description": "browser-provider.job-state@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's state of one job, the body of GET <provider>/jobs/:id and the `job` of browser-provider.job-create-result@1. OpenVibe.Run validates it before adopting any of it: a malformed state is never adopted, and the job stays as Run last saw it. A 404 on GET or on cancel means the provider does not know the job (Run fails the job run.job.worker_failed). STATE is queued, running, then one end state: succeeded, failed, cancelled or expired. WALL_MS is the provider's CLAIM of the time the job ran, nothing more: Run bills from its own observed duration and clamps a larger claim (observed plus 5 s), so this field never sets a price. STARTED_MS and FINISHED_MS are the provider's clock, Unix ms, null until they happen. EXIT_REASON uses the reasons of platform.job-frame@1 `job_exit` (exited, cancelled, ttl, limit, stopped, failed) and is null until the job ended. EXIT_CODE is the browser script's exit status, null when it was killed or never ran. RESULT is the declared output (an object, at most 256 KiB serialized), null unless the job succeeded. STDOUT is the captured output in the ring shape OpenVibe.Bot returns (bot.job-dispatch-result@1) so Run's poller streams it the same way, or null when there is none. There is no node, project or class field: a provider cannot name a node or a project.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "state",
++ "started_ms",
++ "finished_ms",
++ "wall_ms",
++ "exit_reason",
++ "exit_code",
++ "result",
++ "stdout"
++ ],
++ "properties": {
++ "id": {
++ "$ref": "../platform/job-frame.v1.json#/$defs/job_id"
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ],
++ "description": "queued (accepted, not started), running, then one end state: succeeded (exited 0), failed (another exit code, a limit, or the provider could not run it), cancelled (a cancel), expired (the job's lifetime ran out)."
++ },
++ "started_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "When the job started, provider's clock, Unix ms; null until it started."
++ },
++ "finished_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "When the job ended, provider's clock, Unix ms; null until it ended."
++ },
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The provider's CLAIM of the running time; Run clamps it to its own observed duration plus 5 s and never bills above that. 0 while the job has not run."
++ },
++ "exit_reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "exited",
++ "cancelled",
++ "ttl",
++ "limit",
++ "stopped",
++ "failed",
++ null
++ ],
++ "description": "Why the job ended, as platform.job-frame@1 job_exit reason; null until it ended."
++ },
++ "exit_code": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "description": "The script's exit status; null when it was killed or never ran."
++ },
++ "result": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The declared output as a JSON object, at most 256 KiB serialized; null unless the job succeeded."
++ },
++ "stdout": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "The held tail of the job's stdout, the ring shape of bot.job-dispatch-result@1.",
++ "required": [
++ "text",
++ "first_seq",
++ "last_seq",
++ "truncated"
++ ],
++ "properties": {
++ "text": {
++ "type": "string",
++ "maxLength": 1048576,
++ "description": "The held chunks joined in order, at most 1 MiB."
++ },
++ "first_seq": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The chunk_seq of the first held chunk; null when none is held."
++ },
++ "last_seq": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The highest chunk_seq seen for the job; 0 when none."
++ },
++ "truncated": {
++ "type": "boolean",
++ "description": "True when an earlier chunk fell off the ring."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "The captured stdout, or null when the job has none."
++ }
++ },
++ "examples": [
++ {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "state": "succeeded",
++ "started_ms": 1790935200250,
++ "finished_ms": 1790935202600,
++ "wall_ms": 2350,
++ "exit_reason": "exited",
++ "exit_code": 0,
++ "result": { "title": "Example Domain" },
++ "stdout": { "text": "loaded\n", "first_seq": 1, "last_seq": 1, "truncated": false }
++ }
++ ]
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex f343918a..8bacb077 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6225,6 +6225,36 @@
"status": "active",
"adr": "ADR-043"
},
++ {
++ "id": "browser-provider.job-create-request",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "browser-provider/job-create-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
++ {
++ "id": "browser-provider.job-create-result",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "browser-provider/job-create-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
++ {
++ "id": "browser-provider.job-state",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "browser-provider/job-state.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
{
"id": "run.job-create-request",
"version": "1.0.0",
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/bad-id.json b/fixtures/browser-provider.job-create-request/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..fe55563e
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/bad-id.json
@@@ -0,0 +1 @@
++{"id":"run_1","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/class-not-browser.json b/fixtures/browser-provider.job-create-request/invalid/class-not-browser.json
nnew file mode 100644
iindex 00000000..0a7bf00f
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/class-not-browser.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/input-url.json b/fixtures/browser-provider.job-create-request/invalid/input-url.json
nnew file mode 100644
iindex 00000000..a3ef577c
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/input-url.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[{"name":"page.html","url":"https://example.com/page.html"}],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/limits-without-wall.json b/fixtures/browser-provider.job-create-request/invalid/limits-without-wall.json
nnew file mode 100644
iindex 00000000..7123856e
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/limits-without-wall.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"cpu_ms":1000},"egress":"none"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/project-id.json b/fixtures/browser-provider.job-create-request/invalid/project-id.json
nnew file mode 100644
iindex 00000000..65074a1d
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/project-id.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/subject.json b/fixtures/browser-provider.job-create-request/invalid/subject.json
nnew file mode 100644
iindex 00000000..ac084e7e
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/subject.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none","subject":"user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/browser-provider.job-create-request/invalid/unknown-egress.json b/fixtures/browser-provider.job-create-request/invalid/unknown-egress.json
nnew file mode 100644
iindex 00000000..05736666
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/invalid/unknown-egress.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"deny"}
ddiff --git a/fixtures/browser-provider.job-create-request/valid/full.json b/fixtures/browser-provider.job-create-request/valid/full.json
nnew file mode 100644
iindex 00000000..1939e2b4
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/valid/full.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{"url":"https://example.com","screenshot":true},"inputs":[{"name":"script.js","media_id":"med_01JAB2C3D4E5F6G7H8J9K0MNPQ","sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","size_bytes":482}],"limits":{"wall_ms":120000,"cpu_ms":120000,"mem_bytes":1073741824},"egress":"public"}
ddiff --git a/fixtures/browser-provider.job-create-request/valid/minimal.json b/fixtures/browser-provider.job-create-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..a15452d1
---- /dev/null
++++ b/fixtures/browser-provider.job-create-request/valid/minimal.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/browser-provider.job-create-result/invalid/created-string.json b/fixtures/browser-provider.job-create-result/invalid/created-string.json
nnew file mode 100644
iindex 00000000..66b31647
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/invalid/created-string.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null},"created":"yes"}
ddiff --git a/fixtures/browser-provider.job-create-result/invalid/extra-top-level.json b/fixtures/browser-provider.job-create-result/invalid/extra-top-level.json
nnew file mode 100644
iindex 00000000..389bf55f
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/invalid/extra-top-level.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null},"created":true,"sent":true}
ddiff --git a/fixtures/browser-provider.job-create-result/invalid/job-extra-field.json b/fixtures/browser-provider.job-create-result/invalid/job-extra-field.json
nnew file mode 100644
iindex 00000000..9136f2a1
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/invalid/job-extra-field.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null,"project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ"},"created":true}
ddiff --git a/fixtures/browser-provider.job-create-result/invalid/missing-created.json b/fixtures/browser-provider.job-create-result/invalid/missing-created.json
nnew file mode 100644
iindex 00000000..b7345379
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/invalid/missing-created.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null}}
ddiff --git a/fixtures/browser-provider.job-create-result/invalid/unknown-state.json b/fixtures/browser-provider.job-create-result/invalid/unknown-state.json
nnew file mode 100644
iindex 00000000..b3aa445f
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/invalid/unknown-state.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"placed","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null},"created":true}
ddiff --git a/fixtures/browser-provider.job-create-result/valid/created-queued.json b/fixtures/browser-provider.job-create-result/valid/created-queued.json
nnew file mode 100644
iindex 00000000..016b3e6e
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/valid/created-queued.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null},"created":true}
ddiff --git a/fixtures/browser-provider.job-create-result/valid/created-succeeded.json b/fixtures/browser-provider.job-create-result/valid/created-succeeded.json
nnew file mode 100644
iindex 00000000..083e1c91
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/valid/created-succeeded.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":2350,"exit_reason":"exited","exit_code":0,"result":{"title":"Example Domain"},"stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}},"created":false}
ddiff --git a/fixtures/browser-provider.job-create-result/valid/repeat-running.json b/fixtures/browser-provider.job-create-result/valid/repeat-running.json
nnew file mode 100644
iindex 00000000..0c8ce92e
---- /dev/null
++++ b/fixtures/browser-provider.job-create-result/valid/repeat-running.json
@@@ -0,0 +1 @@
++{"job":{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"running","started_ms":1790935200250,"finished_ms":null,"wall_ms":1200,"exit_reason":null,"exit_code":null,"result":null,"stdout":{"text":"loaded\n","first_seq":1,"last_seq":1,"truncated":false}},"created":false}
ddiff --git a/fixtures/browser-provider.job-state/invalid/class-field.json b/fixtures/browser-provider.job-state/invalid/class-field.json
nnew file mode 100644
iindex 00000000..4d033039
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/class-field.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null,"class":"browser"}
ddiff --git a/fixtures/browser-provider.job-state/invalid/missing-wall-ms.json b/fixtures/browser-provider.job-state/invalid/missing-wall-ms.json
nnew file mode 100644
iindex 00000000..8f1528cc
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/missing-wall-ms.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"exit_reason":null,"exit_code":null,"result":null,"stdout":null}
ddiff --git a/fixtures/browser-provider.job-state/invalid/negative-started-ms.json b/fixtures/browser-provider.job-state/invalid/negative-started-ms.json
nnew file mode 100644
iindex 00000000..4071c452
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/negative-started-ms.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"running","started_ms":-5,"finished_ms":null,"wall_ms":1200,"exit_reason":null,"exit_code":null,"result":null,"stdout":{"text":"loaded\n","first_seq":1,"last_seq":1,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/invalid/negative-wall-ms.json b/fixtures/browser-provider.job-state/invalid/negative-wall-ms.json
nnew file mode 100644
iindex 00000000..7f5849ea
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/negative-wall-ms.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":-1,"exit_reason":"exited","exit_code":0,"result":{"title":"Example Domain"},"stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/invalid/node-id.json b/fixtures/browser-provider.job-state/invalid/node-id.json
nnew file mode 100644
iindex 00000000..747fb0f4
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/node-id.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null,"node_id":"dev_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/browser-provider.job-state/invalid/project-id.json b/fixtures/browser-provider.job-state/invalid/project-id.json
nnew file mode 100644
iindex 00000000..7451b420
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/project-id.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null,"project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/browser-provider.job-state/invalid/result-not-object.json b/fixtures/browser-provider.job-state/invalid/result-not-object.json
nnew file mode 100644
iindex 00000000..ac203aaf
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/result-not-object.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":2350,"exit_reason":"exited","exit_code":0,"result":"ok","stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/invalid/stdout-without-seq.json b/fixtures/browser-provider.job-state/invalid/stdout-without-seq.json
nnew file mode 100644
iindex 00000000..3d931626
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/stdout-without-seq.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"running","started_ms":1,"finished_ms":null,"wall_ms":1,"exit_reason":null,"exit_code":null,"result":null,"stdout":{"text":"x"}}
ddiff --git a/fixtures/browser-provider.job-state/invalid/unknown-exit-reason.json b/fixtures/browser-provider.job-state/invalid/unknown-exit-reason.json
nnew file mode 100644
iindex 00000000..6694748a
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/unknown-exit-reason.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":2350,"exit_reason":"crashed","exit_code":0,"result":{"title":"Example Domain"},"stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/invalid/unknown-state.json b/fixtures/browser-provider.job-state/invalid/unknown-state.json
nnew file mode 100644
iindex 00000000..60385888
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/unknown-state.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"placed","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null}
ddiff --git a/fixtures/browser-provider.job-state/invalid/wall-ms-string.json b/fixtures/browser-provider.job-state/invalid/wall-ms-string.json
nnew file mode 100644
iindex 00000000..fb6ee30b
---- /dev/null
++++ b/fixtures/browser-provider.job-state/invalid/wall-ms-string.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":"2350","exit_reason":"exited","exit_code":0,"result":{"title":"Example Domain"},"stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/valid/failed-limit.json b/fixtures/browser-provider.job-state/valid/failed-limit.json
nnew file mode 100644
iindex 00000000..42237742
---- /dev/null
++++ b/fixtures/browser-provider.job-state/valid/failed-limit.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"failed","started_ms":1790935200250,"finished_ms":1790935260250,"wall_ms":60000,"exit_reason":"limit","exit_code":null,"result":null,"stdout":null}
ddiff --git a/fixtures/browser-provider.job-state/valid/queued.json b/fixtures/browser-provider.job-state/valid/queued.json
nnew file mode 100644
iindex 00000000..7de04f98
---- /dev/null
++++ b/fixtures/browser-provider.job-state/valid/queued.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"queued","started_ms":null,"finished_ms":null,"wall_ms":0,"exit_reason":null,"exit_code":null,"result":null,"stdout":null}
ddiff --git a/fixtures/browser-provider.job-state/valid/running-with-stdout.json b/fixtures/browser-provider.job-state/valid/running-with-stdout.json
nnew file mode 100644
iindex 00000000..58f97006
---- /dev/null
++++ b/fixtures/browser-provider.job-state/valid/running-with-stdout.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"running","started_ms":1790935200250,"finished_ms":null,"wall_ms":1200,"exit_reason":null,"exit_code":null,"result":null,"stdout":{"text":"loaded\n","first_seq":1,"last_seq":1,"truncated":false}}
ddiff --git a/fixtures/browser-provider.job-state/valid/succeeded.json b/fixtures/browser-provider.job-state/valid/succeeded.json
nnew file mode 100644
iindex 00000000..bcaecf02
---- /dev/null
++++ b/fixtures/browser-provider.job-state/valid/succeeded.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","state":"succeeded","started_ms":1790935200250,"finished_ms":1790935202600,"wall_ms":2350,"exit_reason":"exited","exit_code":0,"result":{"title":"Example Domain"},"stdout":{"text":"loaded\ndone\n","first_seq":1,"last_seq":2,"truncated":false}}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 0c505b58..b1d34d62 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -71917,6 +71917,287 @@
}
}
},
++ "browser-provider.job-create-request": {
++ "version": "1.0.0",
++ "owner": "run",
++ "status": "planned",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-create-request.v1.json",
++ "title": "BrowserProviderJobCreateRequest",
++ "description": "browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "class",
++ "args",
++ "inputs",
++ "limits",
++ "egress"
++ ],
++ "properties": {
++ "id": {
++ "$ref": "../platform/job-frame.v1.json#/$defs/job_id"
++ },
++ "class": {
++ "const": "browser"
++ },
++ "args": {
++ "type": "object",
++ "description": "The job's declared input as JSON; Run enforces the 512 KiB cap before sending."
++ },
++ "inputs": {
++ "type": "array",
++ "maxItems": 32,
++ "items": {
++ "$ref": "../run/job-create-request.v1.json#/$defs/input"
++ },
++ "description": "Files for the job, each an OpenVibe.Media object pinned by sha256; names are unique; empty when the job has none."
++ },
++ "limits": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "Caps on the running job (platform.job@1 limits); the provider ends a job that hits one with exit_reason limit.",
++ "required": [
++ "wall_ms"
++ ],
++ "properties": {
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Running time from the job's start"
++ },
++ "cpu_ms": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "CPU time, user plus system; when the provider can enforce it"
++ },
++ "mem_bytes": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Peak resident memory; when the provider can enforce it"
++ }
++ }
++ },
++ "egress": {
++ "$ref": "../run/job-create-request.v1.json#/$defs/egress"
++ }
++ },
++ "examples": [
++ {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "class": "browser",
++ "args": {
++ "url": "https://example.com",
++ "screenshot": true
++ },
++ "inputs": [],
++ "limits": {
++ "wall_ms": 60000
++ },
++ "egress": "public"
++ }
++ ]
++ }
++ },
++ "browser-provider.job-create-result": {
++ "version": "1.0.0",
++ "owner": "run",
++ "status": "planned",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-create-result.v1.json",
++ "title": "BrowserProviderJobCreateResult",
++ "description": "browser-provider.job-create-result@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's answer to OpenVibe.Run's job POST (browser-provider.job-create-request@1): 201 { job, created: true } when it accepted a new job, 200 { job, created: false } for an idempotent repeat of an id it already holds (the first job's state, nothing started twice). JOB is browser-provider.job-state@1 and Run validates it before adopting any of it. A refused POST answers a problem instead (4xx; 5xx is retried by Run on its next tick), never this contract. For the later calls, GET <provider>/jobs/:id answers browser-provider.job-state@1 and POST <provider>/jobs/:id/cancel answers it too; a 404 on either means the provider does not know the job.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "job",
++ "created"
++ ],
++ "properties": {
++ "job": {
++ "$ref": "job-state.v1.json"
++ },
++ "created": {
++ "type": "boolean",
++ "description": "True when this POST created the job (201); false for an idempotent repeat (200)."
++ }
++ },
++ "examples": [
++ {
++ "job": {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "state": "queued",
++ "started_ms": null,
++ "finished_ms": null,
++ "wall_ms": 0,
++ "exit_reason": null,
++ "exit_code": null,
++ "result": null,
++ "stdout": null
++ },
++ "created": true
++ }
++ ]
++ }
++ },
++ "browser-provider.job-state": {
++ "version": "1.0.0",
++ "owner": "run",
++ "status": "planned",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/browser-provider/job-state.v1.json",
++ "title": "BrowserProviderJobState",
++ "description": "browser-provider.job-state@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's state of one job, the body of GET <provider>/jobs/:id and the `job` of browser-provider.job-create-result@1. OpenVibe.Run validates it before adopting any of it: a malformed state is never adopted, and the job stays as Run last saw it. A 404 on GET or on cancel means the provider does not know the job (Run fails the job run.job.worker_failed). STATE is queued, running, then one end state: succeeded, failed, cancelled or expired. WALL_MS is the provider's CLAIM of the time the job ran, nothing more: Run bills from its own observed duration and clamps a larger claim (observed plus 5 s), so this field never sets a price. STARTED_MS and FINISHED_MS are the provider's clock, Unix ms, null until they happen. EXIT_REASON uses the reasons of platform.job-frame@1 `job_exit` (exited, cancelled, ttl, limit, stopped, failed) and is null until the job ended. EXIT_CODE is the browser script's exit status, null when it was killed or never ran. RESULT is the declared output (an object, at most 256 KiB serialized), null unless the job succeeded. STDOUT is the captured output in the ring shape OpenVibe.Bot returns (bot.job-dispatch-result@1) so Run's poller streams it the same way, or null when there is none. There is no node, project or class field: a provider cannot name a node or a project.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "id",
++ "state",
++ "started_ms",
++ "finished_ms",
++ "wall_ms",
++ "exit_reason",
++ "exit_code",
++ "result",
++ "stdout"
++ ],
++ "properties": {
++ "id": {
++ "$ref": "../platform/job-frame.v1.json#/$defs/job_id"
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "queued",
++ "running",
++ "succeeded",
++ "failed",
++ "cancelled",
++ "expired"
++ ],
++ "description": "queued (accepted, not started), running, then one end state: succeeded (exited 0), failed (another exit code, a limit, or the provider could not run it), cancelled (a cancel), expired (the job's lifetime ran out)."
++ },
++ "started_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "When the job started, provider's clock, Unix ms; null until it started."
++ },
++ "finished_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "When the job ended, provider's clock, Unix ms; null until it ended."
++ },
++ "wall_ms": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The provider's CLAIM of the running time; Run clamps it to its own observed duration plus 5 s and never bills above that. 0 while the job has not run."
++ },
++ "exit_reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "exited",
++ "cancelled",
++ "ttl",
++ "limit",
++ "stopped",
++ "failed",
++ null
++ ],
++ "description": "Why the job ended, as platform.job-frame@1 job_exit reason; null until it ended."
++ },
++ "exit_code": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "description": "The script's exit status; null when it was killed or never ran."
++ },
++ "result": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "description": "The declared output as a JSON object, at most 256 KiB serialized; null unless the job succeeded."
++ },
++ "stdout": {
++ "anyOf": [
++ {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "The held tail of the job's stdout, the ring shape of bot.job-dispatch-result@1.",
++ "required": [
++ "text",
++ "first_seq",
++ "last_seq",
++ "truncated"
++ ],
++ "properties": {
++ "text": {
++ "type": "string",
++ "maxLength": 1048576,
++ "description": "The held chunks joined in order, at most 1 MiB."
++ },
++ "first_seq": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 1,
++ "description": "The chunk_seq of the first held chunk; null when none is held."
++ },
++ "last_seq": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The highest chunk_seq seen for the job; 0 when none."
++ },
++ "truncated": {
++ "type": "boolean",
++ "description": "True when an earlier chunk fell off the ring."
++ }
++ }
++ },
++ {
++ "type": "null"
++ }
++ ],
++ "description": "The captured stdout, or null when the job has none."
++ }
++ },
++ "examples": [
++ {
++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "state": "succeeded",
++ "started_ms": 1790935200250,
++ "finished_ms": 1790935202600,
++ "wall_ms": 2350,
++ "exit_reason": "exited",
++ "exit_code": 0,
++ "result": {
++ "title": "Example Domain"
++ },
++ "stdout": {
++ "text": "loaded\n",
++ "first_seq": 1,
++ "last_seq": 1,
++ "truncated": false
++ }
++ }
++ ]
++ }
++ },
"run.job-create-request": {
"version": "1.0.0",
"owner": "run",
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 2fd335e9..dcd7a079 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 1ec67b83..45e9aace 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.154.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex ac0759d9..64d1a0da 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.154.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 018ca7d5..ac8087bd 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.154.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 ce8d509f..b8211851 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.154.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 86033eb6..c98be948 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.154.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex a2f2b410..18b2a95c 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.154.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 5a5f015c..e4d989a5 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.154.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 4d9ae477..7a5347de 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.154.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 1efa304c..458c66cd 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.154.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex ef240f1c..08016253 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 20b1f9a9..23c240b4 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.154.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 39037dd5..c1510cd5 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.154.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 31fd713f..fe44b212 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex ff685c63..d260bd39 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.154.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
iindex ea3a0c12..d6a1740b 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 1a01de98..1791655a 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.154.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex bfecde21..534928de 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.154.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex d30b0d33..bcd4c13d 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.154.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 5024ae88..06359eff 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.154.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 a67727ad..3f2f34d3 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.154.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex b6461999..8dc6a98b 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 95c565df..26ae2ebf 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.154.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex 1d62b4c2..500f5c4c 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 151933fe..c9e0bfe4 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.154.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 cb578053..73adebcd 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.154.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 85eb647a..94a5a51b 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.154.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 6dd2e128..5bdea9a6 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.154.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 9b7edb92..05e7b812 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.154.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 23caaa77..19c054de 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.154.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 62a160fd..6c7e37c4 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.154.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 c911ecf4..6f27b9d5 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.154.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 2938a928..014ba236 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.154.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.154.0): each operation lists the capabilities it performs; a token for 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.155.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.155.0): each operation lists the capabilities it performs; a token for 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 97a2acb5..3c87c4e9 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -169882,6 +169882,132 @@ export interface BotCommandRefusedPayload {
role: string | null;
}
++/** [email protected] (owner: run) */
++/**
++ * browser-provider.job-create-request@1 (PLANNED; plan T14 R4a, ADR-034): the body OpenVibe.Run POSTs to an external managed-browser provider's job endpoint (capability browser_provider.job.dispatch). ID is the platform.job@1 id (job_<ULID>) and is also the idempotency key: a repeat POST with the same id answers the first job (200, created false) and never starts a second. CLASS is always browser. ARGS is the job's declared input as JSON (Run refuses more than 512 KiB serialized before it ever sends). INPUTS are files, each an OpenVibe.Media object pinned by sha256; never a URL, so the provider fetches nothing a caller chose. LIMITS are the hard caps of platform.job@1 (wall_ms required; cpu_ms and mem_bytes when the provider can enforce them). EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress, as platform.job@1 `net` carries it. The request carries no project, subject, requester, token or other credential: the provider never learns who pays, and Run authenticates to it with a key from Run's own environment, sent in a header and never in this body. The provider's answer is browser-provider.job-create-result@1.
++ */
++export interface BrowserProviderJobCreateRequest {
++ /**
++ * platform.job@1 `id`
++ */
++ id: string;
++ class: "browser";
++ /**
++ * The job's declared input as JSON; Run enforces the 512 KiB cap before sending.
++ */
++ args: {};
++ /**
++ * Files for the job, each an OpenVibe.Media object pinned by sha256; names are unique; empty when the job has none.
++ *
++ * @maxItems 32
++ */
++ inputs: {
++ /**
++ * File name in the job directory; no path separators.
++ */
++ name: string;
++ media_id: string;
++ /**
++ * Lowercase hex SHA-256 of the object's bytes.
++ */
++ sha256: string;
++ size_bytes?: number;
++ }[];
++ /**
++ * Caps on the running job (platform.job@1 limits); the provider ends a job that hits one with exit_reason limit.
++ */
++ limits: {
++ /**
++ * Running time from the job's start
++ */
++ wall_ms: number;
++ /**
++ * CPU time, user plus system; when the provider can enforce it
++ */
++ cpu_ms?: number;
++ /**
++ * Peak resident memory; when the provider can enforce it
++ */
++ mem_bytes?: number;
++ };
++ /**
++ * 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).
++ */
++ egress: "none" | "public" | "openvibe-only";
++}
++
++/** [email protected] (owner: run) */
++/**
++ * browser-provider.job-create-result@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's answer to OpenVibe.Run's job POST (browser-provider.job-create-request@1): 201 { job, created: true } when it accepted a new job, 200 { job, created: false } for an idempotent repeat of an id it already holds (the first job's state, nothing started twice). JOB is browser-provider.job-state@1 and Run validates it before adopting any of it. A refused POST answers a problem instead (4xx; 5xx is retried by Run on its next tick), never this contract. For the later calls, GET <provider>/jobs/:id answers browser-provider.job-state@1 and POST <provider>/jobs/:id/cancel answers it too; a 404 on either means the provider does not know the job.
++ */
++export interface BrowserProviderJobCreateResult {
++ job: BrowserProviderJobState;
++ /**
++ * True when this POST created the job (201); false for an idempotent repeat (200).
++ */
++ created: boolean;
++}
++
++/** [email protected] (owner: run) */
++/**
++ * browser-provider.job-state@1 (PLANNED; plan T14 R4a, ADR-034): an external managed-browser provider's state of one job, the body of GET <provider>/jobs/:id and the `job` of browser-provider.job-create-result@1. OpenVibe.Run validates it before adopting any of it: a malformed state is never adopted, and the job stays as Run last saw it. A 404 on GET or on cancel means the provider does not know the job (Run fails the job run.job.worker_failed). STATE is queued, running, then one end state: succeeded, failed, cancelled or expired. WALL_MS is the provider's CLAIM of the time the job ran, nothing more: Run bills from its own observed duration and clamps a larger claim (observed plus 5 s), so this field never sets a price. STARTED_MS and FINISHED_MS are the provider's clock, Unix ms, null until they happen. EXIT_REASON uses the reasons of platform.job-frame@1 `job_exit` (exited, cancelled, ttl, limit, stopped, failed) and is null until the job ended. EXIT_CODE is the browser script's exit status, null when it was killed or never ran. RESULT is the declared output (an object, at most 256 KiB serialized), null unless the job succeeded. STDOUT is the captured output in the ring shape OpenVibe.Bot returns (bot.job-dispatch-result@1) so Run's poller streams it the same way, or null when there is none. There is no node, project or class field: a provider cannot name a node or a project.
++ */
++export interface BrowserProviderJobState {
++ /**
++ * platform.job@1 `id`
++ */
++ id: string;
++ /**
++ * queued (accepted, not started), running, then one end state: succeeded (exited 0), failed (another exit code, a limit, or the provider could not run it), cancelled (a cancel), expired (the job's lifetime ran out).
++ */
++ state: "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
++ /**
++ * When the job started, provider's clock, Unix ms; null until it started.
++ */
++ started_ms: number | null;
++ /**
++ * When the job ended, provider's clock, Unix ms; null until it ended.
++ */
++ finished_ms: number | null;
++ /**
++ * The provider's CLAIM of the running time; Run clamps it to its own observed duration plus 5 s and never bills above that. 0 while the job has not run.
++ */
++ wall_ms: number;
++ /**
++ * Why the job ended, as platform.job-frame@1 job_exit reason; null until it ended.
++ */
++ exit_reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed" | null;
++ /**
++ * The script's exit status; null when it was killed or never ran.
++ */
++ exit_code: number | null;
++ /**
++ * The declared output as a JSON object, at most 256 KiB serialized; null unless the job succeeded.
++ */
++ result: {} | null;
++ /**
++ * The captured stdout, or null when the job has none.
++ */
++ stdout: {
++ /**
++ * The held chunks joined in order, at most 1 MiB.
++ */
++ text: string;
++ /**
++ * The chunk_seq of the first held chunk; null when none is held.
++ */
++ first_seq: number | null;
++ /**
++ * The highest chunk_seq seen for the job; 0 when none.
++ */
++ last_seq: number;
++ /**
++ * True when an earlier chunk fell off the ring.
++ */
++ truncated: boolean;
++ } | null;
++}
++
/** [email protected] (owner: run) */
/**
* 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, or, for a first-party service acting for a person (X-OV-Subject), the project it names with X-OV-Project once Network confirms the person is an admin or owner of it (ADR-003 amendment 2026-10-12); the person is then the requester and the service is recorded as via. 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.
ddiff --git a/manifests/capabilities/browser_provider.job.dispatch.json b/manifests/capabilities/browser_provider.job.dispatch.json
nnew file mode 100644
iindex 00000000..d84312e7
---- /dev/null
++++ b/manifests/capabilities/browser_provider.job.dispatch.json
@@@ -0,0 +1,18 @@
++{
++ "id": "browser_provider.job.dispatch",
++ "version": "1.0",
++ "owner": "run",
++ "status": "planned",
++ "visibility": "internal",
++ "sensitive": true,
++ "description": "PLANNED (plan T14 R4a, ADR-034): OpenVibe.Run's outbound call to the external managed-browser provider it places class browser jobs on: POST <provider>/jobs {id, class, args, inputs, limits, egress} (idempotent by job id; 201 created, 200 repeat), GET <provider>/jobs/:id for the state, POST <provider>/jobs/:id/cancel to stop it; a 404 on the last two means the provider does not know the job. The request carries no project, subject or credential: the provider never learns who pays. Run authenticates with a key from its own environment, never from an offer and never logged, validates every answer against browser-provider.job-state@1 (a malformed state is never adopted), and bills from its own observed duration, clamping the provider's claimed wall_ms to that plus 5 s. Run's own outbound call; no token grants it and it is never delegated.",
++ "permissions": [],
++ "resourceConstraints": [
++ "project"
++ ],
++ "quotaClass": "bot-jobs",
++ "events": [],
++ "implementedBy": [],
++ "inputSchema": "browser-provider.job-create-request@1",
++ "outputSchema": "browser-provider.job-create-result@1"
++}
ddiff --git a/package.json b/package.json
iindex 54b7f5a4..c5c0847a 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.154.0",
++ "version": "0.155.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 687f22f4..9183221f 100644
---- a/test/platform-run.test.js
++++ b/test/platform-run.test.js
@@@ -102,4 +102,24 @@ for (const [id, routes] of Object.entries({
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}`);
++// Run's outbound call to an external managed-browser provider (T14 R4a). The request is a platform.job@1 reduced to
++// what the provider may know: it never carries a project, subject, requester or credential, and the provider's
++// answer cannot name a node, project or class. The provider's wall_ms is only a claim (Run clamps it).
++const bp = id => contracts.schema(`browser-provider.${id}`);
++const bpReq = bp('job-create-request');
++assert.deepStrictEqual(Object.keys(bpReq.properties).sort(), ['args', 'class', 'egress', 'id', 'inputs', 'limits']);
++assert.deepStrictEqual(Object.keys(bp('job-state').properties).sort(), ['exit_code', 'exit_reason', 'finished_ms', 'id', 'result', 'started_ms', 'state', 'stdout', 'wall_ms']);
++checks += 2;
++ok(bpReq.additionalProperties === false && bp('job-state').additionalProperties === false && bp('job-create-result').additionalProperties === false, 'browser-provider schemas refuse unknown fields');
++for (const k of ['wall_ms', 'cpu_ms', 'mem_bytes']) assert.deepStrictEqual({ ...bpReq.properties.limits.properties[k], description: 0 }, { ...job.properties.limits.properties[k], description: 0 });
++assert.deepStrictEqual(bp('job-state').properties.exit_reason.enum.filter(Boolean), contracts.schema('platform.job-frame').oneOf.find(f => f.properties.type.const === 'job_exit').properties.reason.enum);
++checks += 2;
++ok(bpReq.properties.limits.required.length === 1 && bpReq.properties.limits.required[0] === 'wall_ms', 'only wall_ms is required of a provider job');
++const botStdout = contracts.schema('bot.job-dispatch-result').$defs.stdout.properties;
++assert.deepStrictEqual(Object.keys(bp('job-state').properties.stdout.anyOf[0].properties), Object.keys(botStdout));
++checks++;
++const dispatch = contracts.capabilities.get('browser_provider.job.dispatch');
++ok(dispatch && dispatch.owner === 'run' && dispatch.status === 'planned' && dispatch.visibility === 'internal' && dispatch.sensitive === true && dispatch.implementedBy.length === 0 && dispatch.permissions.length === 0, 'browser_provider.job.dispatch is a planned internal sensitive capability of Run');
++ok(dispatch.inputSchema === 'browser-provider.job-create-request@1' && dispatch.outputSchema === 'browser-provider.job-create-result@1' && !run.capabilities.includes('browser_provider.job.dispatch'), 'it names the two contracts and is not yet a capability Run serves');
++
console.log(`platform run: ${checks} job, frame and metering checks passed`);