openvibers/openvibe.contracts Commits

Add sandbox-provider job contracts and planned dispatch capability

OP OpenVibers committed 14 Oct 2026 · e88c6a578e · 76 files, +834 −69

Asked: T14 step 3 part 1 (R4b, the Contracts half), dossier /home/workstation/openvibe/agents/ds/prep/T14.md lines 78-93. ONE pull request in OpenVibe.Contracts from current origin/main (git fetch first; the local checkout may be stale). The browser-provider set already landed on origin/main (PR 'Add brow…
Harness job contracts-t14-step-3-part-1-014214.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 5d256279..ef5f073d 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.159.0 — 2026-10-14
++
++- **Contracts 0.159.0: sandbox-provider job contracts (plan T14 R4b, ADR-034; planned, internal).** What OpenVibe.Run
++ sends to and reads from an external sandbox provider such as E2B or Fly Machines, the same shape as the
++ browser-provider set: `sandbox-provider.job-create-request@1` (`id`, `class` code or linux, `args`, `inputs` pinned by
++ sha256, `limits`, `egress`; no project, subject, requester or credential), `sandbox-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) and `sandbox-provider.job-create-result@1` (`{ job, created }`). New capability
++ `sandbox_provider.job.dispatch` (Run's own outbound call, `planned`, `implementedBy` empty). Additive only.
++
## 0.158.0 — 2026-10-14
- **`live.chat_mirror.write` is retired.** The Live read mirror is gone: Chat stopped its capture triggers
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 8bacb077..baf4bfcd 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -6255,6 +6255,36 @@
"status": "planned",
"adr": "ADR-034"
},
++ {
++ "id": "sandbox-provider.job-create-request",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "sandbox-provider/job-create-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
++ {
++ "id": "sandbox-provider.job-create-result",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "sandbox-provider/job-create-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
++ {
++ "id": "sandbox-provider.job-state",
++ "version": "1.0.0",
++ "owner": "run",
++ "schema": "sandbox-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/contracts/sandbox-provider/job-create-request.v1.json b/contracts/sandbox-provider/job-create-request.v1.json
nnew file mode 100644
iindex 00000000..34ea8bb1
---- /dev/null
++++ b/contracts/sandbox-provider/job-create-request.v1.json
@@@ -0,0 +1,74 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/sandbox-provider/job-create-request.v1.json",
++ "title": "SandboxProviderJobCreateRequest",
++ "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_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 code or linux, the platform.runtime-class@1 classes a sandbox hosts. 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 sandbox-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": {
++ "enum": ["code", "linux"]
++ },
++ "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": "code",
++ "args": { "entry": "main.py" },
++ "inputs": [],
++ "limits": { "wall_ms": 60000 },
++ "egress": "public"
++ }
++ ]
++}
ddiff --git a/contracts/sandbox-provider/job-create-result.v1.json b/contracts/sandbox-provider/job-create-result.v1.json
nnew file mode 100644
iindex 00000000..bc29dcda
---- /dev/null
++++ b/contracts/sandbox-provider/job-create-result.v1.json
@@@ -0,0 +1,37 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/sandbox-provider/job-create-result.v1.json",
++ "title": "SandboxProviderJobCreateResult",
++ "description": "sandbox-provider.job-create-result@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) answer to OpenVibe.Run's job POST (sandbox-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 sandbox-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 sandbox-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/sandbox-provider/job-state.v1.json b/contracts/sandbox-provider/job-state.v1.json
nnew file mode 100644
iindex 00000000..42cc9177
---- /dev/null
++++ b/contracts/sandbox-provider/job-state.v1.json
@@@ -0,0 +1,143 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/sandbox-provider/job-state.v1.json",
++ "title": "SandboxProviderJobState",
++ "description": "sandbox-provider.job-state@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) state of one job, the body of GET <provider>/jobs/:id and the `job` of sandbox-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 job'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/fixtures/sandbox-provider.job-create-request/invalid/bad-id.json b/fixtures/sandbox-provider.job-create-request/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..108503cf
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/bad-id.json
@@@ -0,0 +1 @@
++{"id":"run_1","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/class-not-sandbox.json b/fixtures/sandbox-provider.job-create-request/invalid/class-not-sandbox.json
nnew file mode 100644
iindex 00000000..a15452d1
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/class-not-sandbox.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"browser","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/input-url.json b/fixtures/sandbox-provider.job-create-request/invalid/input-url.json
nnew file mode 100644
iindex 00000000..ef8b4f22
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/input-url.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[{"name":"page.html","url":"https://example.com/page.html"}],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/limits-without-wall.json b/fixtures/sandbox-provider.job-create-request/invalid/limits-without-wall.json
nnew file mode 100644
iindex 00000000..a3af9452
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/limits-without-wall.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"cpu_ms":1000},"egress":"none"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/project-id.json b/fixtures/sandbox-provider.job-create-request/invalid/project-id.json
nnew file mode 100644
iindex 00000000..950acafb
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/project-id.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/subject.json b/fixtures/sandbox-provider.job-create-request/invalid/subject.json
nnew file mode 100644
iindex 00000000..91b168d3
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/subject.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none","subject":"user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/invalid/unknown-egress.json b/fixtures/sandbox-provider.job-create-request/invalid/unknown-egress.json
nnew file mode 100644
iindex 00000000..bf021fb6
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/invalid/unknown-egress.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"deny"}
ddiff --git a/fixtures/sandbox-provider.job-create-request/valid/full.json b/fixtures/sandbox-provider.job-create-request/valid/full.json
nnew file mode 100644
iindex 00000000..45a6b7a0
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/valid/full.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{"entry":"main.py"},"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/sandbox-provider.job-create-request/valid/minimal.json b/fixtures/sandbox-provider.job-create-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..0a7bf00f
---- /dev/null
++++ b/fixtures/sandbox-provider.job-create-request/valid/minimal.json
@@@ -0,0 +1 @@
++{"id":"job_01JAB2C3D4E5F6G7H8J9K0MNPQ","class":"code","args":{},"inputs":[],"limits":{"wall_ms":60000},"egress":"none"}
ddiff --git a/fixtures/sandbox-provider.job-create-result/invalid/created-string.json b/fixtures/sandbox-provider.job-create-result/invalid/created-string.json
nnew file mode 100644
iindex 00000000..66b31647
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/invalid/extra-top-level.json b/fixtures/sandbox-provider.job-create-result/invalid/extra-top-level.json
nnew file mode 100644
iindex 00000000..389bf55f
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/invalid/job-extra-field.json b/fixtures/sandbox-provider.job-create-result/invalid/job-extra-field.json
nnew file mode 100644
iindex 00000000..9136f2a1
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/invalid/missing-created.json b/fixtures/sandbox-provider.job-create-result/invalid/missing-created.json
nnew file mode 100644
iindex 00000000..b7345379
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/invalid/unknown-state.json b/fixtures/sandbox-provider.job-create-result/invalid/unknown-state.json
nnew file mode 100644
iindex 00000000..b3aa445f
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/valid/created-queued.json b/fixtures/sandbox-provider.job-create-result/valid/created-queued.json
nnew file mode 100644
iindex 00000000..016b3e6e
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/valid/created-succeeded.json b/fixtures/sandbox-provider.job-create-result/valid/created-succeeded.json
nnew file mode 100644
iindex 00000000..083e1c91
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-create-result/valid/repeat-running.json b/fixtures/sandbox-provider.job-create-result/valid/repeat-running.json
nnew file mode 100644
iindex 00000000..0c8ce92e
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/class-field.json b/fixtures/sandbox-provider.job-state/invalid/class-field.json
nnew file mode 100644
iindex 00000000..89d5437b
---- /dev/null
++++ b/fixtures/sandbox-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":"code"}
ddiff --git a/fixtures/sandbox-provider.job-state/invalid/missing-wall-ms.json b/fixtures/sandbox-provider.job-state/invalid/missing-wall-ms.json
nnew file mode 100644
iindex 00000000..8f1528cc
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/negative-started-ms.json b/fixtures/sandbox-provider.job-state/invalid/negative-started-ms.json
nnew file mode 100644
iindex 00000000..4071c452
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/negative-wall-ms.json b/fixtures/sandbox-provider.job-state/invalid/negative-wall-ms.json
nnew file mode 100644
iindex 00000000..7f5849ea
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/node-id.json b/fixtures/sandbox-provider.job-state/invalid/node-id.json
nnew file mode 100644
iindex 00000000..747fb0f4
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/project-id.json b/fixtures/sandbox-provider.job-state/invalid/project-id.json
nnew file mode 100644
iindex 00000000..7451b420
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/result-not-object.json b/fixtures/sandbox-provider.job-state/invalid/result-not-object.json
nnew file mode 100644
iindex 00000000..ac203aaf
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/stdout-without-seq.json b/fixtures/sandbox-provider.job-state/invalid/stdout-without-seq.json
nnew file mode 100644
iindex 00000000..3d931626
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/unknown-exit-reason.json b/fixtures/sandbox-provider.job-state/invalid/unknown-exit-reason.json
nnew file mode 100644
iindex 00000000..6694748a
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/unknown-state.json b/fixtures/sandbox-provider.job-state/invalid/unknown-state.json
nnew file mode 100644
iindex 00000000..60385888
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/invalid/wall-ms-string.json b/fixtures/sandbox-provider.job-state/invalid/wall-ms-string.json
nnew file mode 100644
iindex 00000000..fb6ee30b
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/valid/failed-limit.json b/fixtures/sandbox-provider.job-state/valid/failed-limit.json
nnew file mode 100644
iindex 00000000..42237742
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/valid/queued.json b/fixtures/sandbox-provider.job-state/valid/queued.json
nnew file mode 100644
iindex 00000000..7de04f98
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/valid/running-with-stdout.json b/fixtures/sandbox-provider.job-state/valid/running-with-stdout.json
nnew file mode 100644
iindex 00000000..58f97006
---- /dev/null
++++ b/fixtures/sandbox-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/sandbox-provider.job-state/valid/succeeded.json b/fixtures/sandbox-provider.job-state/valid/succeeded.json
nnew file mode 100644
iindex 00000000..bcaecf02
---- /dev/null
++++ b/fixtures/sandbox-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 88ae3df6..96eee7e7 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -72199,6 +72199,289 @@
]
}
},
++ "sandbox-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/sandbox-provider/job-create-request.v1.json",
++ "title": "SandboxProviderJobCreateRequest",
++ "description": "sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_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 code or linux, the platform.runtime-class@1 classes a sandbox hosts. 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 sandbox-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": {
++ "enum": [
++ "code",
++ "linux"
++ ]
++ },
++ "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": "code",
++ "args": {
++ "entry": "main.py"
++ },
++ "inputs": [],
++ "limits": {
++ "wall_ms": 60000
++ },
++ "egress": "public"
++ }
++ ]
++ }
++ },
++ "sandbox-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/sandbox-provider/job-create-result.v1.json",
++ "title": "SandboxProviderJobCreateResult",
++ "description": "sandbox-provider.job-create-result@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) answer to OpenVibe.Run's job POST (sandbox-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 sandbox-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 sandbox-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
++ }
++ ]
++ }
++ },
++ "sandbox-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/sandbox-provider/job-state.v1.json",
++ "title": "SandboxProviderJobState",
++ "description": "sandbox-provider.job-state@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) state of one job, the body of GET <provider>/jobs/:id and the `job` of sandbox-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 job'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 3dc9d5ad..ebb546f3 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.158.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 1458a00b..731df7ba 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.158.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 58acce15..6b65f1e6 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.158.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 b2afe4c6..bcf5619e 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.158.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 a168be42..18baf22d 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.158.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 3c68d430..089c1ee3 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.158.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 374482b4..f957b20a 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.158.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 6c8f85c9..54b07c37 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.158.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 371a7719..4e746646 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.158.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 68af1ab7..2a8a15d3 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.158.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 af88c8e9..d001b0ee 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.158.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 a7e9dc24..3058c3b8 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.158.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 69200a2f..2427ec40 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.158.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 080137e1..b60e7c45 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.158.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 97b4c67a..7dee6da5 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.158.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 d47a6ebf..bab1c0a4 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.158.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 19ccd331..547fe29d 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.158.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 3cc77654..86d531df 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.158.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 4710bf37..b6ad8813 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.158.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 429db89a..7dc0a277 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.158.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 70b5c914..01eda997 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.158.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 687ab95d..516786e3 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.158.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 274e968a..008e40e9 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.158.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 ccd27084..18c8c5a0 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.158.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 66c96c5d..e171c432 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.158.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 e9874f12..ce597786 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.158.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 3985b5e2..04a30bbd 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.158.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 39c89d82..2c0e63bc 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.158.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 b4d4d4e8..8eec137f 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.158.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 f2b03515..378281cb 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.158.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 93e35d28..44061911 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.158.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 56440cb5..e77a1c64 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.158.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 9e1a291b..33e4500c 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.158.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.158.0): each operation lists the capabilities it performs; a token for 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.159.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.159.0): each operation lists the capabilities it performs; a token for 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 af762d78..d2c90e11 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -170008,6 +170008,132 @@ export interface BrowserProviderJobState {
} | null;
}
++/** [email protected] (owner: run) */
++/**
++ * sandbox-provider.job-create-request@1 (PLANNED; plan T14 R4b, ADR-034): the body OpenVibe.Run POSTs to an external sandbox provider's (such as E2B or Fly Machines) job endpoint (capability sandbox_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 code or linux, the platform.runtime-class@1 classes a sandbox hosts. 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 sandbox-provider.job-create-result@1.
++ */
++export interface SandboxProviderJobCreateRequest {
++ /**
++ * platform.job@1 `id`
++ */
++ id: string;
++ class: "code" | "linux";
++ /**
++ * 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) */
++/**
++ * sandbox-provider.job-create-result@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) answer to OpenVibe.Run's job POST (sandbox-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 sandbox-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 sandbox-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 SandboxProviderJobCreateResult {
++ job: SandboxProviderJobState;
++ /**
++ * True when this POST created the job (201); false for an idempotent repeat (200).
++ */
++ created: boolean;
++}
++
++/** [email protected] (owner: run) */
++/**
++ * sandbox-provider.job-state@1 (PLANNED; plan T14 R4b, ADR-034): an external sandbox provider's (such as E2B or Fly Machines) state of one job, the body of GET <provider>/jobs/:id and the `job` of sandbox-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 job'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 SandboxProviderJobState {
++ /**
++ * 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/sandbox_provider.job.dispatch.json b/manifests/capabilities/sandbox_provider.job.dispatch.json
nnew file mode 100644
iindex 00000000..8bfdda19
---- /dev/null
++++ b/manifests/capabilities/sandbox_provider.job.dispatch.json
@@@ -0,0 +1,18 @@
++{
++ "id": "sandbox_provider.job.dispatch",
++ "version": "1.0",
++ "owner": "run",
++ "status": "planned",
++ "visibility": "internal",
++ "sensitive": true,
++ "description": "PLANNED (plan T14 R4b, ADR-034): OpenVibe.Run's outbound call to the external sandbox provider (such as E2B or Fly Machines) it places class code and linux 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 sandbox-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": "sandbox-provider.job-create-request@1",
++ "outputSchema": "sandbox-provider.job-create-result@1"
++}
ddiff --git a/package-lock.json b/package-lock.json
iindex 405aa428..59215e54 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.124.0",
++ "version": "0.159.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.124.0",
++ "version": "0.159.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 4652ad16..8e72f57a 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.158.0",
++ "version": "0.159.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 9183221f..bf6b0085 100644
---- a/test/platform-run.test.js
++++ b/test/platform-run.test.js
@@@ -122,4 +122,16 @@ 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');
++// Run's outbound call to an external sandbox provider (T14 R4b): the browser-provider shape for classes code and linux.
++const sp = id => contracts.schema(`sandbox-provider.${id}`);
++for (const id of ['job-create-request', 'job-state', 'job-create-result']) {
++ ok(sp(id).additionalProperties === false, `sandbox-provider.${id} refuses unknown fields`);
++ if (id !== 'job-create-result') assert.deepStrictEqual(Object.keys(sp(id).properties).filter(k => k !== 'class'), Object.keys(bp(id).properties).filter(k => k !== 'class'));
++}
++checks += 2;
++assert.deepStrictEqual(sp('job-create-request').properties.class.enum, ['code', 'linux']);
++checks++;
++const sandboxDispatch = contracts.capabilities.get('sandbox_provider.job.dispatch');
++ok(sandboxDispatch && sandboxDispatch.owner === 'run' && sandboxDispatch.status === 'planned' && sandboxDispatch.visibility === 'internal' && sandboxDispatch.sensitive === true && sandboxDispatch.implementedBy.length === 0 && sandboxDispatch.inputSchema === 'sandbox-provider.job-create-request@1' && sandboxDispatch.outputSchema === 'sandbox-provider.job-create-result@1' && !run.capabilities.includes('sandbox_provider.job.dispatch'), 'sandbox_provider.job.dispatch is a planned internal sensitive capability of Run naming the two contracts');
++
console.log(`platform run: ${checks} job, frame and metering checks passed`);